Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 18 additions & 61 deletions src/Api.elm
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,13 @@ This token should never be rendered to the end user, and with this API, it
can't be!

-}
type Cred
= Cred Username String
type Cred = Cred Username String


username : Cred -> Username
username (Cred val _) =
val
username (Cred val _) = val


credHeader : Cred -> Http.Header
credHeader (Cred _ str) =
Http.header "authorization" ("Token " ++ str)
credHeader (Cred _ str) = Http.header "authorization" ("Token " ++ str)


{-| It's important that this is never exposed!
Expand All @@ -58,7 +53,6 @@ ever has access to a `Cred` value, it came from either the login API endpoint
or was passed in via flags.

-}
credDecoder : Decoder Cred
credDecoder =
Decode.succeed Cred
|> required "username" Username.decoder
Expand All @@ -69,7 +63,6 @@ credDecoder =
-- PERSISTENCE


decode : Decoder (Cred -> viewer) -> Value -> Result Decode.Error viewer
decode decoder value =
-- It's stored in localStorage as a JSON String;
-- first decode the Value as a String, then
Expand All @@ -81,12 +74,9 @@ decode decoder value =
port onStoreChange : (Value -> msg) -> Sub msg


viewerChanges : (Maybe viewer -> msg) -> Decoder (Cred -> viewer) -> Sub msg
viewerChanges toMsg decoder =
onStoreChange (\value -> toMsg (decodeFromChange decoder value))
viewerChanges toMsg decoder = onStoreChange (\value -> toMsg (decodeFromChange decoder value))


decodeFromChange : Decoder (Cred -> viewer) -> Value -> Maybe viewer
decodeFromChange viewerDecoder val =
-- It's stored in localStorage as a JSON String;
-- first decode the Value as a String, then
Expand All @@ -95,7 +85,6 @@ decodeFromChange viewerDecoder val =
|> Result.toMaybe


storeCredWith : Cred -> Avatar -> Cmd msg
storeCredWith (Cred uname token) avatar =
let
json =
Expand All @@ -112,7 +101,6 @@ storeCredWith (Cred uname token) avatar =
storeCache (Just json)


logout : Cmd msg
logout =
storeCache Nothing

Expand Down Expand Up @@ -157,35 +145,29 @@ application viewerDecoder config =
}


storageDecoder : Decoder (Cred -> viewer) -> Decoder viewer
storageDecoder viewerDecoder =
Decode.field "user" (decoderFromCred viewerDecoder)
storageDecoder viewerDecoder = Decode.field "user" (decoderFromCred viewerDecoder)



-- HTTP


get : Endpoint -> Maybe Cred -> Decoder a -> Http.Request a
get url maybeCred decoder =
Endpoint.request
{ method = "GET"
, url = url
, expect = Http.expectJson decoder
, headers =
case maybeCred of
Just cred ->
[ credHeader cred ]
Just cred -> [ credHeader cred ]

Nothing ->
[]
Nothing -> []
, body = Http.emptyBody
, timeout = Nothing
, withCredentials = False
}


put : Endpoint -> Cred -> Body -> Decoder a -> Http.Request a
put url cred body decoder =
Endpoint.request
{ method = "PUT"
Expand All @@ -198,26 +180,22 @@ put url cred body decoder =
}


post : Endpoint -> Maybe Cred -> Body -> Decoder a -> Http.Request a
post url maybeCred body decoder =
Endpoint.request
{ method = "POST"
, url = url
, expect = Http.expectJson decoder
, headers =
case maybeCred of
Just cred ->
[ credHeader cred ]
Just cred -> [ credHeader cred ]

Nothing ->
[]
Nothing -> []
, body = body
, timeout = Nothing
, withCredentials = False
}


delete : Endpoint -> Cred -> Body -> Decoder a -> Http.Request a
delete url cred body decoder =
Endpoint.request
{ method = "DELETE"
Expand All @@ -230,71 +208,50 @@ delete url cred body decoder =
}


login : Http.Body -> Decoder (Cred -> a) -> Http.Request a
login body decoder =
post Endpoint.login Nothing body (Decode.field "user" (decoderFromCred decoder))
login body decoder = post Endpoint.login Nothing body (Decode.field "user" (decoderFromCred decoder))


register : Http.Body -> Decoder (Cred -> a) -> Http.Request a
register body decoder =
post Endpoint.users Nothing body (Decode.field "user" (decoderFromCred decoder))
register body decoder = post Endpoint.users Nothing body (Decode.field "user" (decoderFromCred decoder))


settings : Cred -> Http.Body -> Decoder (Cred -> a) -> Http.Request a
settings cred body decoder =
put Endpoint.user cred body (Decode.field "user" (decoderFromCred decoder))
settings cred body decoder = put Endpoint.user cred body (Decode.field "user" (decoderFromCred decoder))


decoderFromCred : Decoder (Cred -> a) -> Decoder a
decoderFromCred decoder =
Decode.map2 (\fromCred cred -> fromCred cred)
decoder
credDecoder
decoderFromCred decoder = Decode.map2 (\fromCred cred -> fromCred cred) decoder credDecoder



-- ERRORS


addServerError : List String -> List String
addServerError list =
"Server error" :: list
addServerError list = "Server error" :: list


{-| Many API endpoints include an "errors" field in their BadStatus responses.
-}
decodeErrors : Http.Error -> List String
decodeErrors error =
case error of
Http.BadStatus response ->
response.body
|> decodeString (field "errors" errorsDecoder)
|> Result.withDefault [ "Server error" ]

err ->
[ "Server error" ]
err -> [ "Server error" ]


errorsDecoder : Decoder (List String)
errorsDecoder =
Decode.keyValuePairs (Decode.list Decode.string)
|> Decode.map (List.concatMap fromPair)


fromPair : ( String, List String ) -> List String
fromPair ( field, errors ) =
List.map (\error -> field ++ " " ++ error) errors
fromPair ( field, errors ) = List.map (\error -> field ++ " " ++ error) errors



-- LOCALSTORAGE KEYS


cacheStorageKey : String
cacheStorageKey =
"cache"
cacheStorageKey = "cache"


credStorageKey : String
credStorageKey =
"cred"
credStorageKey = "cred"
24 changes: 0 additions & 24 deletions src/Api/Endpoint.elm
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,6 @@ import Username exposing (Username)

{-| Http.request, except it takes an Endpoint instead of a Url.
-}
request :
{ body : Http.Body
, expect : Http.Expect a
, headers : List Http.Header
, method : String
, timeout : Maybe Float
, url : Endpoint
, withCredentials : Bool
}
-> Http.Request a
request config =
Http.request
{ body = config.body
Expand All @@ -44,12 +34,10 @@ type Endpoint
= Endpoint String


unwrap : Endpoint -> String
unwrap (Endpoint str) =
str


url : List String -> List QueryParameter -> Endpoint
url paths queryParams =
-- NOTE: Url.Builder takes care of percent-encoding special URL characters.
-- See https://package.elm-lang.org/packages/elm/url/latest/Url#percentEncode
Expand All @@ -63,22 +51,18 @@ url paths queryParams =
-- ENDPOINTS


login : Endpoint
login =
url [ "users", "login" ] []


user : Endpoint
user =
url [ "user" ] []


users : Endpoint
users =
url [ "users" ] []


follow : Username -> Endpoint
follow uname =
url [ "profiles", Username.toString uname, "follow" ] []

Expand All @@ -87,41 +71,33 @@ follow uname =
-- ARTICLE ENDPOINTS


article : Slug -> Endpoint
article slug =
url [ "articles", Slug.toString slug ] []


comments : Slug -> Endpoint
comments slug =
url [ "articles", Slug.toString slug, "comments" ] []


comment : Slug -> CommentId -> Endpoint
comment slug commentId =
url [ "articles", Slug.toString slug, "comments", CommentId.toString commentId ] []


favorite : Slug -> Endpoint
favorite slug =
url [ "articles", Slug.toString slug, "favorite" ] []


articles : List QueryParameter -> Endpoint
articles params =
url [ "articles" ] params


profiles : Username -> Endpoint
profiles uname =
url [ "profiles", Username.toString uname ] []


feed : List QueryParameter -> Endpoint
feed params =
url [ "articles", "feed" ] params


tags : Endpoint
tags =
url [ "tags" ] []
Loading