starts with a dedicated Api module, wip...

This commit is contained in:
2019-11-05 16:03:15 +01:00
parent d56ca38dcb
commit dfd1e99d1c
2 changed files with 243 additions and 215 deletions

173
src/Api.elm Normal file
View File

@@ -0,0 +1,173 @@
module Api exposing (..)
import Http
import Json.Decode as D
import Json.Decode exposing (Decoder, int, string)
import Json.Encode as E
type alias HttpResult a = (Result Http.Error a)
type alias Response =
{ value: Maybe String
, notification: Maybe String
, updates : Maybe (List Update)
, errors : Maybe String
}
type Update
= ItemRemoved Item
| ItemAdded Item
| WealthUpdated Wealth
| ClaimAdded ()
| ClaimRemoved ()
type Msg
= GotPlayer (HttpResult Player)
| GotClaims Int (HttpResult Claims)
| GotLoot ToChest (HttpResult Loot)
| GotActionResult (HttpResult Response)
-- PLAYERS
--
fetchPlayer : Int -> Cmd Msg
fetchPlayer id =
Http.get
{ url = "http://localhost:8088/api/players/" ++ (String.fromInt id) ++ "/"
, expect = Http.expectJson Main.GotPlayer (valueDecoder playerDecoder )
}
playerDecoder : Decoder Player
playerDecoder =
D.map4 Player
(D.field "id" int)
(D.field "name" string)
(D.field "debt" int)
wealthDecoder
wealthDecoder : Decoder Wealth
wealthDecoder =
D.map4 Wealth
(D.field "cp" int)
(D.field "sp" int)
(D.field "gp" int)
(D.field "pp" int)
-- LOOT
-- Location of a loot
type ToChest
= OfPlayer Int
| OfGroup
| OfShop
itemDecoder =
D.map3 Item
(D.field "id" int)
(D.field "name" string)
(D.field "base_price" int)
lootDecoder : Decoder Loot
lootDecoder =
Json.Decode.list itemDecoder
fetchLoot : Main.ToChest -> Cmd Msg
fetchLoot dest =
let
url = case dest of
Main.OfPlayer id -> "http://localhost:8088/api/players/" ++ (String.fromInt id) ++ "/loot"
Main.OfShop -> "http://localhost:8088/api/items"
Main.OfGroup -> "http://localhost:8088/api/players/0/loot"
in
Http.get
{ url = url
, expect = Http.expectJson (GotLoot dest) (valueDecoder lootDecoder)}
-- CLAIMS
claimDecoder =
D.map3 Claim
(D.field "id" int)
(D.field "player_id" int)
(D.field "loot_id" int)
fetchClaims : Int -> Cmd Msg
fetchClaims playerId =
Http.get
{ url = "http://localhost:8088/api/claims"
, expect = valueDecoder (D.list claimDecoder)
|> Http.expectJson (GotClaims playerId)
}
-- API Response
--
valueDecoder : Decoder a -> Decoder a
valueDecoder thenDecoder =
D.field "value" thenDecoder
-- TODO: update server to produce better json
-- like an object with list of updates of the same type
-- { ItemRemoved : [..], Wealth : [ .. ], .. }
updatesDecoder : Decoder DbUpdate
updatesDecoder =
-- We expect one update but do not know it's kind
Json.Decode.oneOf
[ (field "ItemRemoved" (itemDecoder |> Json.Decode.andThen (\i -> succeed <| ItemRemoved i)))
, (field "ItemAdded" (itemDecoder |> Json.Decode.andThen (\i -> succeed <| ItemAdded i)))
, (field "Wealth" (wealthDecoder |> Json.Decode.andThen (\i -> succeed <| WealthUpdated i)))
, (field "ClaimRemoved" (succeed () |> Json.Decode.andThen (\i -> succeed <| ClaimRemoved i)))
, (field "ClaimAdded" (succeed () |> Json.Decode.andThen (\i -> succeed <| ClaimAdded i)))
]
apiResponseDecoder : Decoder ApiResponse
apiResponseDecoder =
Json.Decode.map4 ApiResponse
(D.maybe (field "value" string))
(Json.Decode.maybe (field "notification" string))
(Json.Decode.maybe (field "updates" (Json.Decode.list updatesDecoder)))
(Json.Decode.maybe (field "errors" string))
undoLastAction id = Http.request
{ url = "http://localhost:8088/api/players/" ++ String.fromInt id ++"/events/last"
, method = "DELETE"
, headers = []
, body = Http.emptyBody
, expect = Http.expectJson GotActionResult apiResponseDecoder
, timeout = Nothing
, tracker = Nothing
}
sendRequest : Maybe ViewMode -> Model -> Cmd Msg
sendRequest activeMode model =
case activeMode of
Nothing -> Cmd.none
Just mode ->
let
(endpoint, method) = case mode of
Add ->
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
, "POST"
)
Buy ->
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
, "PUT"
)
Sell ->
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
, "DELETE"
)
Grab ->
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/claims"
, "POST")
in
Http.request
{ method = method
, headers = []
, url = endpoint
, body = Http.jsonBody <| buildPayload mode model
, expect = Http.expectJson GotActionResult apiResponseDecoder
, timeout = Nothing
, tracker = Nothing
}

View File

@@ -2,18 +2,15 @@ module Main exposing (..)
import Browser
import Browser.Navigation as Nav
import Platform.Cmd exposing (Cmd)
import Url
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Svg.Attributes
import Http
import Json.Decode exposing (Decoder, field, list, string, int, succeed)
import Json.Encode as E
import Url.Parser as P exposing (Parser, (</>), oneOf, s)
import Set exposing (Set)
import Api
-- Main
@@ -32,8 +29,6 @@ main =
type alias Selection = Set Int
emptySelection = []
type alias State =
{ navKey : Nav.Key
, route : Route
@@ -75,13 +70,16 @@ fetchInitialData : Int -> Cmd Msg
fetchInitialData playerId =
Cmd.batch
[ initPlayer playerId
, fetchShopInventory
, fetchGroupLoot
, Cmd.map ApiMsg <| Api.fetchLoot Api.OfShop
, Cmd.map ApiMsg <| Api.fetchLoot Api.OfGroup
]
---
-- PLAYER
-- MODELS
---
-- Player
type alias Player =
{ id: Int
, name: String
@@ -92,23 +90,13 @@ type alias Player =
blankPlayer =
Player 0 "Loot-a-lot" 0 (Wealth 0 0 0 0)
initPlayer id =
Cmd.batch [fetchPlayer id, fetchLoot id, fetchClaims id]
fetchPlayer : Int -> Cmd Msg
fetchPlayer id =
Http.get
{ url = "http://localhost:8088/api/players/" ++ (String.fromInt id) ++ "/"
, expect = Http.expectJson GotPlayer (valueDecoder playerDecoder )
}
playerDecoder : Decoder Player
playerDecoder =
Json.Decode.map4 Player
(field "id" int)
(field "name" string)
(field "debt" int)
wealthDecoder
Cmd.batch
[ Cmd.map ApiMsg <| Api.fetchPlayer id
, Cmd.map ApiMsg <| Api.fetchLoot (OfPlayer id)
, Cmd.map ApiMsg <| Api.fetchClaims id
]
type alias Wealth =
{ cp: Int
@@ -117,13 +105,9 @@ type alias Wealth =
, pp: Int
}
wealthDecoder : Decoder Wealth
wealthDecoder =
Json.Decode.map4 Wealth
(field "cp" int)
(field "sp" int)
(field "gp" int)
(field "pp" int)
-- Loot
type alias Loot = List Item
type alias Item =
{ id: Int
@@ -131,38 +115,7 @@ type alias Item =
, base_price: Int
}
itemDecoder =
Json.Decode.map3 Item
(field "id" int)
(field "name" string)
(field "base_price" int)
type alias Loot =
List Item
lootDecoder : Decoder Loot
lootDecoder =
Json.Decode.list itemDecoder
fetchLoot id =
Http.get
{ url = "http://localhost:8088/api/players/" ++ (String.fromInt id) ++ "/loot"
, expect = Http.expectJson (GotLoot OfPlayer) (valueDecoder lootDecoder)}
fetchShopInventory =
Http.get
{ url = "http://localhost:8088/api/items"
, expect = Http.expectJson (GotLoot OfShop) (valueDecoder lootDecoder)}
fetchGroupLoot =
Http.get
{ url = "http://localhost:8088/api/players/0/loot"
, expect = Http.expectJson (GotLoot OfGroup) (valueDecoder lootDecoder)}
type ToChest
= OfPlayer
| OfGroup
| OfShop
-- Claims
type alias Claims = List Claim
@@ -172,44 +125,13 @@ type alias Claim =
, loot_id: Int
}
claimDecoder =
Json.Decode.map3 Claim
(field "id" int)
(field "player_id" int)
(field "loot_id" int)
fetchClaims : Int -> Cmd Msg
fetchClaims playerId =
Http.get
{ url = "http://localhost:8088/api/claims"
, expect = valueDecoder (Json.Decode.list claimDecoder)
|> Http.expectJson (GotClaims playerId)
}
-- API Response
--
valueDecoder : Decoder a -> Decoder a
valueDecoder thenDecoder =
field "value" thenDecoder
-- UPDATE
type alias HttpResult a = (Result Http.Error a)
type alias ApiResponse =
{ value : Maybe String
, notification : Maybe String
, updates : Maybe (List DbUpdate)
, errors : Maybe String
}
type Msg
= LinkClicked Browser.UrlRequest
| UrlChanged Url.Url
| ApiMsg Api.Msg
| PlayerChanged Int
| GotPlayer (HttpResult Player)
| GotClaims Int (HttpResult Claims)
| GotLoot ToChest (HttpResult Loot)
| GotActionResult (HttpResult ApiResponse)
| LootViewItemSwitched Int
| ModeSwitched (Maybe ViewMode)
| ConfirmAction
@@ -248,6 +170,21 @@ update msg model =
PlayerChanged newId ->
( { model | player = blankPlayer }, initPlayer newId )
ApiMsg apiMsg -> case apiMsg of
GotActionResult response ->
case response of
Ok result ->
let
updates = Maybe.withDefault [] result.updates
notification = result.notification
errors = Maybe.withDefault "" result.errors
in
List.foldl applyUpdate model updates
|> setNotification notification
|> setError errors
|> update (ModeSwitched Nothing)
Err r -> (setError (Debug.toString r) model, Cmd.none)
GotPlayer result ->
case result of
Ok player ->
@@ -268,9 +205,9 @@ update msg model =
case result of
Ok loot ->
( case dest of
OfPlayer -> { model | loot = Just loot}
OfGroup -> { model | groupLoot = Just loot}
OfShop -> { model | merchantItems = Just loot}
Api.OfPlayer _ -> { model | loot = Just loot}
Api.OfGroup -> { model | groupLoot = Just loot}
Api.OfShop -> { model | merchantItems = Just loot}
, Cmd.none
)
Err error ->
@@ -293,14 +230,13 @@ update msg model =
in
( { model | state =
{ state | activeMode = newMode
, selection = case newMode of
, selection =
case newMode of
Nothing ->
Nothing
Just Grab -> -- Currently claimed object are initially selected
Just ( Set.fromList
<| List.map (\c -> c.loot_id) model.claims
)
Just ( Set.fromList <| List.map (\c -> c.loot_id) model.claims)
Just others ->
Just Set.empty
@@ -311,34 +247,11 @@ update msg model =
let
currentMode = model.state.activeMode
in
(model, sendRequest currentMode model)
(model, Cmd.map ApiMsg Api.sendRequest currentMode model)
UndoLastAction ->
let playerId = String.fromInt model.player.id
in
(model, Http.request
{ url = "http://localhost:8088/api/players/" ++ playerId ++"/events/last"
, method = "DELETE"
, headers = []
, body = Http.emptyBody
, expect = Http.expectJson GotActionResult apiResponseDecoder
, timeout = Nothing
, tracker = Nothing
})
(model, Cmd.map ApiMsg Api.undoLastAction model.player.id)
GotActionResult response ->
case response of
Ok result ->
let
updates = Maybe.withDefault [] result.updates
notification = result.notification
errors = Maybe.withDefault "" result.errors
in
List.foldl applyUpdate model updates
|> setNotification notification
|> setError errors
|> update (ModeSwitched Nothing)
Err r -> (setError (Debug.toString r) model, Cmd.none)
ClearNotification ->
( { model | notification = Nothing }, Cmd.none )
@@ -409,64 +322,6 @@ applyUpdate u model =
ClaimAdded _ -> model
ClaimRemoved _ -> model
-- TODO: update server to produce better json
-- like an object with list of updates of the same type
-- { ItemRemoved : [..], Wealth : [ .. ], .. }
updatesDecoder : Decoder DbUpdate
updatesDecoder =
-- We expect one update but do not know it's kind
Json.Decode.oneOf
[ (field "ItemRemoved" (itemDecoder |> Json.Decode.andThen (\i -> succeed <| ItemRemoved i)))
, (field "ItemAdded" (itemDecoder |> Json.Decode.andThen (\i -> succeed <| ItemAdded i)))
, (field "Wealth" (wealthDecoder |> Json.Decode.andThen (\i -> succeed <| WealthUpdated i)))
, (field "ClaimRemoved" (succeed () |> Json.Decode.andThen (\i -> succeed <| ClaimRemoved i)))
, (field "ClaimAdded" (succeed () |> Json.Decode.andThen (\i -> succeed <| ClaimAdded i)))
]
apiResponseDecoder : Decoder ApiResponse
apiResponseDecoder =
Json.Decode.map4 ApiResponse
(Json.Decode.maybe (field "value" string))
(Json.Decode.maybe (field "notification" string))
(Json.Decode.maybe (field "updates" (Json.Decode.list updatesDecoder)))
(Json.Decode.maybe (field "errors" string))
sendRequest : Maybe ViewMode -> Model -> Cmd Msg
sendRequest activeMode model =
case activeMode of
Nothing -> Cmd.none
Just mode ->
let
(endpoint, method) = case mode of
Add ->
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
, "POST"
)
Buy ->
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
, "PUT"
)
Sell ->
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/loot"
, "DELETE"
)
Grab ->
( "http://localhost:8088/api/players/" ++ (String.fromInt model.player.id) ++ "/claims"
, "POST")
in
Http.request
{ method = method
, headers = []
, url = endpoint
, body = Http.jsonBody <| buildPayload mode model
, expect = Http.expectJson GotActionResult apiResponseDecoder
, timeout = Nothing
, tracker = Nothing
}
-- ERRORS