From b55ff1438c88e7756de3e711d1735a8ca93ec0ba Mon Sep 17 00:00:00 2001 From: Mikko Ahlroth Date: Wed, 11 Jan 2023 20:37:27 +0200 Subject: [PATCH] Begin integration with Elixir code --- .gitignore | 4 + lib/geo_therminator/pump_api/auth/api.ex | 58 ----------- .../pump_api/auth/installation_info.ex | 9 +- lib/geo_therminator/pump_api/auth/server.ex | 66 ++++++++----- lib/geo_therminator/pump_api/auth/token.ex | 8 -- lib/geo_therminator/pump_api/auth/tokens.ex | 18 ++++ lib/geo_therminator/pump_api/auth/user.ex | 16 +-- lib/geo_therminator/pump_api/device/api.ex | 6 +- .../pump_api/device/pub_sub.ex | 11 ++- lib/geo_therminator/pump_api/http.ex | 14 ++- lib/geo_therminator_web/live/main/pump.ex | 2 +- mix.exs | 7 +- src/azure/b2c.gleam | 49 ++++++---- src/helpers/date_time.gleam | 8 +- src/helpers/parsing.gleam | 19 ++++ src/pump_api/auth/api.gleam | 97 ++++++++----------- src/pump_api/auth/token.gleam | 5 - src/pump_api/auth/tokens.gleam | 10 ++ src/pump_api/auth/user.gleam | 14 +-- src/pump_api/http.gleam | 2 +- 20 files changed, 206 insertions(+), 217 deletions(-) delete mode 100644 lib/geo_therminator/pump_api/auth/api.ex delete mode 100644 lib/geo_therminator/pump_api/auth/token.ex create mode 100644 lib/geo_therminator/pump_api/auth/tokens.ex create mode 100644 src/helpers/parsing.gleam delete mode 100644 src/pump_api/auth/token.gleam create mode 100644 src/pump_api/auth/tokens.gleam diff --git a/.gitignore b/.gitignore index 152ffe5..3939c37 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,7 @@ npm-debug.log /assets/node_modules/ .env + +src/geo_therminator.gleam + +.elixir_ls diff --git a/lib/geo_therminator/pump_api/auth/api.ex b/lib/geo_therminator/pump_api/auth/api.ex deleted file mode 100644 index ff60fd4..0000000 --- a/lib/geo_therminator/pump_api/auth/api.ex +++ /dev/null @@ -1,58 +0,0 @@ -defmodule GeoTherminator.PumpAPI.Auth.API do - alias GeoTherminator.PumpAPI.HTTP - alias GeoTherminator.PumpAPI.Auth - - require Logger - - @spec auth(String.t(), String.t()) :: {:ok, Auth.User.t()} | :error - def auth(username, password) do - url = Application.get_env(:geo_therminator, :api_auth_url) - - req = - HTTP.req(:post, url, [], %{ - username: username, - password: password - }) - - with {:ok, response} <- Finch.request(req, HTTP), - 200 <- response.status, - {:ok, json} <- Jason.decode(response.body), - {:ok, valid_to, _} = DateTime.from_iso8601(json["tokenValidToUtc"]) do - token = %Auth.Token{ - token: json["token"], - token_valid_to: valid_to - } - - {:ok, - %Auth.User{ - user_name: json["userName"], - email: json["email"], - first_name: json["firstName"], - last_name: json["lastName"], - culture: json["culture"], - eula_accepted: json["eulaAccepted"], - is_authenticated: json["isAuthenticated"], - time_zone: json["timeZone"], - token: token - }} - else - _ -> :error - end - end - - @spec installations_info(Auth.User.t()) :: [Auth.InstallationInfo.t()] - def installations_info(user) do - url = Application.get_env(:geo_therminator, :api_installations_url) - - req = HTTP.authed_req(user, :get, url) - {:ok, response} = Finch.request(req, HTTP) - - json = Jason.decode!(response.body) - - Enum.map(json["items"], fn item -> - %Auth.InstallationInfo{ - id: item["id"] - } - end) - end -end diff --git a/lib/geo_therminator/pump_api/auth/installation_info.ex b/lib/geo_therminator/pump_api/auth/installation_info.ex index 7868240..cd52047 100644 --- a/lib/geo_therminator/pump_api/auth/installation_info.ex +++ b/lib/geo_therminator/pump_api/auth/installation_info.ex @@ -1,8 +1,7 @@ defmodule GeoTherminator.PumpAPI.Auth.InstallationInfo do - import GeoTherminator.TypedStruct + require Record - deftypedstruct(%{ - id: integer() - # ... - }) + Record.defrecord(:record, :installation_info, [:id]) + + @type t :: record(:record, id: non_neg_integer()) end diff --git a/lib/geo_therminator/pump_api/auth/server.ex b/lib/geo_therminator/pump_api/auth/server.ex index cccb2ca..6662abd 100644 --- a/lib/geo_therminator/pump_api/auth/server.ex +++ b/lib/geo_therminator/pump_api/auth/server.ex @@ -1,8 +1,12 @@ defmodule GeoTherminator.PumpAPI.Auth.Server do + require Logger + require GeoTherminator.PumpAPI.Auth.User + require GeoTherminator.PumpAPI.Auth.InstallationInfo + require GeoTherminator.PumpAPI.Auth.Tokens + use GenServer import GeoTherminator.TypedStruct alias GeoTherminator.PumpAPI.Auth - require Logger @token_check_timer 10_000 @token_check_diff 5 * 60 @@ -32,24 +36,9 @@ defmodule GeoTherminator.PumpAPI.Auth.Server do @impl true def init(%Options{} = opts) do - case Auth.API.auth(opts.username, opts.password) do - {:ok, user} -> - installations = Auth.API.installations_info(user) - - schedule_token_check() - - state = %State{ - authed_user: user, - installations: installations, - installations_fetched: true, - username: opts.username, - password: opts.password - } - - {:ok, state} - - :error -> - {:stop, :error} + case init_state(opts.username, opts.password) do + {:ok, state} -> {:ok, state} + :error -> {:stop, :error} end end @@ -65,7 +54,8 @@ defmodule GeoTherminator.PumpAPI.Auth.Server do end def handle_call({:get_installation, id}, _from, state) do - {:reply, Enum.find(state.installations, &(&1.id == id)), state} + {:reply, Enum.find(state.installations, &(Auth.InstallationInfo.record(&1, :id) == id)), + state} end @impl true @@ -73,17 +63,23 @@ defmodule GeoTherminator.PumpAPI.Auth.Server do def handle_info(:token_check, state) do now = DateTime.utc_now() - diff = DateTime.diff(state.authed_user.token.token_valid_to, now) + + diff = + DateTime.diff( + state.authed_user + |> Auth.User.record(:tokens) + |> Auth.Tokens.record(:access_token_expiry), + now + ) schedule_token_check() if diff < @token_check_diff do Logger.debug("Renewing auth token since #{diff} < #{@token_check_diff}") - with {:ok, user} <- Auth.API.auth(state.username, state.password) do - {:noreply, %State{state | authed_user: user}} - else - _ -> {:noreply, state} + case init_state(state.username, state.password) do + {:ok, new_state} -> {:noreply, new_state} + :error -> {:noreply, state} end else {:noreply, state} @@ -108,4 +104,24 @@ defmodule GeoTherminator.PumpAPI.Auth.Server do defp schedule_token_check() do Process.send_after(self(), :token_check, @token_check_timer) end + + @spec init_state(String.t(), String.t()) :: {:ok, State.t()} | {:stop, :error} + defp init_state(username, password) do + with {:ok, user} <- :pump_api@auth@api.auth(username, password), + {:ok, installations} <- :pump_api@auth@api.installation_info(user) do + schedule_token_check() + + state = %State{ + authed_user: user, + installations: installations, + installations_fetched: true, + username: username, + password: password + } + + {:ok, state} + else + _ -> :error + end + end end diff --git a/lib/geo_therminator/pump_api/auth/token.ex b/lib/geo_therminator/pump_api/auth/token.ex deleted file mode 100644 index 056979d..0000000 --- a/lib/geo_therminator/pump_api/auth/token.ex +++ /dev/null @@ -1,8 +0,0 @@ -defmodule GeoTherminator.PumpAPI.Auth.Token do - import GeoTherminator.TypedStruct - - deftypedstruct(%{ - token: String.t(), - token_valid_to: DateTime.t() - }) -end diff --git a/lib/geo_therminator/pump_api/auth/tokens.ex b/lib/geo_therminator/pump_api/auth/tokens.ex new file mode 100644 index 0000000..af3f0d4 --- /dev/null +++ b/lib/geo_therminator/pump_api/auth/tokens.ex @@ -0,0 +1,18 @@ +defmodule GeoTherminator.PumpAPI.Auth.Tokens do + require Record + + Record.defrecord(:record, :tokens, [ + :access_token, + :access_token_expiry, + :refresh_token, + :refresh_token_expiry + ]) + + @type t :: + record(:record, + access_token: String.t(), + access_token_expiry: DateTime.t(), + refresh_token: String.t(), + refresh_token_expiry: DateTime.t() + ) +end diff --git a/lib/geo_therminator/pump_api/auth/user.ex b/lib/geo_therminator/pump_api/auth/user.ex index 822b75d..a09d577 100644 --- a/lib/geo_therminator/pump_api/auth/user.ex +++ b/lib/geo_therminator/pump_api/auth/user.ex @@ -1,15 +1,7 @@ defmodule GeoTherminator.PumpAPI.Auth.User do - import GeoTherminator.TypedStruct + require Record - deftypedstruct(%{ - user_name: String.t(), - email: String.t(), - first_name: String.t(), - last_name: String.t(), - culture: String.t(), - eula_accepted: boolean(), - is_authenticated: boolean(), - time_zone: String.t(), - token: GeoTherminator.PumpAPI.Auth.Token.t() - }) + Record.defrecord(:record, :user, [:tokens]) + + @type t :: record(:record, tokens: GeoTherminator.PumpAPI.Auth.Tokens.t()) end diff --git a/lib/geo_therminator/pump_api/device/api.ex b/lib/geo_therminator/pump_api/device/api.ex index e4377c9..9436fb2 100644 --- a/lib/geo_therminator/pump_api/device/api.ex +++ b/lib/geo_therminator/pump_api/device/api.ex @@ -1,13 +1,15 @@ defmodule GeoTherminator.PumpAPI.Device.API do + require GeoTherminator.PumpAPI.Auth.InstallationInfo + alias GeoTherminator.PumpAPI.HTTP alias GeoTherminator.PumpAPI.Device alias GeoTherminator.PumpAPI.Auth - @spec device_info(Auth.User.t(), Device.InstallationInfo.t()) :: Device.t() + @spec device_info(Auth.User.t(), Auth.InstallationInfo.t()) :: Device.t() def device_info(user, installation) do url = Application.get_env(:geo_therminator, :api_device_url) - |> String.replace("{id}", to_string(installation.id)) + |> String.replace("{id}", to_string(Auth.InstallationInfo.record(installation, :id))) req = HTTP.authed_req(user, :get, url) diff --git a/lib/geo_therminator/pump_api/device/pub_sub.ex b/lib/geo_therminator/pump_api/device/pub_sub.ex index 7679b1a..56a8d63 100644 --- a/lib/geo_therminator/pump_api/device/pub_sub.ex +++ b/lib/geo_therminator/pump_api/device/pub_sub.ex @@ -1,11 +1,18 @@ defmodule GeoTherminator.PumpAPI.Device.PubSub do + require GeoTherminator.PumpAPI.Auth.InstallationInfo + alias Phoenix.PubSub + alias GeoTherminator.PumpAPI.Auth.InstallationInfo @installation_topic "installation:" - @spec subscribe_installation(GeoTherminator.PumpAPI.Auth.InstallationInfo.t()) :: :ok + @spec subscribe_installation(InstallationInfo.t()) :: :ok def subscribe_installation(installation) do - :ok = PubSub.subscribe(__MODULE__, @installation_topic <> to_string(installation.id)) + :ok = + PubSub.subscribe( + __MODULE__, + @installation_topic <> to_string(InstallationInfo.record(installation, :id)) + ) end @spec broadcast_device(GeoTherminator.PumpAPI.Device.t()) :: :ok diff --git a/lib/geo_therminator/pump_api/http.ex b/lib/geo_therminator/pump_api/http.ex index b171712..97e92a4 100644 --- a/lib/geo_therminator/pump_api/http.ex +++ b/lib/geo_therminator/pump_api/http.ex @@ -1,6 +1,12 @@ defmodule GeoTherminator.PumpAPI.HTTP do + require GeoTherminator.PumpAPI.Auth.User + require GeoTherminator.PumpAPI.Auth.Tokens + + alias GeoTherminator.PumpAPI.Auth.User + alias GeoTherminator.PumpAPI.Auth.Tokens + @spec authed_req( - GeoTherminator.PumpAPI.Auth.User.t(), + User.t(), Finch.Request.method(), Finch.Request.url(), Finch.Request.headers(), @@ -11,7 +17,11 @@ defmodule GeoTherminator.PumpAPI.HTTP do def authed_req(user, method, url, headers \\ [], body \\ nil, opts \\ []) do headers = headers - |> List.keystore("authorization", 0, {"authorization", "Bearer #{user.token.token}"}) + |> List.keystore( + "authorization", + 0, + {"authorization", "Bearer #{User.record(user, :tokens) |> Tokens.record(:access_token)}"} + ) req(method, url, headers, body, opts) end diff --git a/lib/geo_therminator_web/live/main/pump.ex b/lib/geo_therminator_web/live/main/pump.ex index 23f4ef9..d6e7cad 100644 --- a/lib/geo_therminator_web/live/main/pump.ex +++ b/lib/geo_therminator_web/live/main/pump.ex @@ -8,7 +8,7 @@ defmodule GeoTherminatorWeb.MainLive.Pump do def mount(%{"id" => str_id}, _session, socket) do socket = with {id, ""} <- Integer.parse(str_id), - %Auth.InstallationInfo{} = info <- Auth.Server.get_installation(Auth.Server, id), + info when info != nil <- Auth.Server.get_installation(Auth.Server, id), :ok <- Device.PubSub.subscribe_installation(info), {:ok, pid} <- Device.get_device_process(Auth.Server, info), %Device{} = device <- Device.Server.get_device(pid), diff --git a/mix.exs b/mix.exs index 971d41b..f5cfa3d 100644 --- a/mix.exs +++ b/mix.exs @@ -14,7 +14,12 @@ defmodule GeoTherminator.MixProject do ], erlc_include_path: "build/dev/erlang/#{@app}/include", archives: [mix_gleam: "~> 0.6.1"], - compilers: [:gleam, :gettext] ++ Mix.compilers(), + compilers: + if Mix.env() != :test do + [:gleam] + else + [] + end ++ Mix.compilers(), start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps(), diff --git a/src/azure/b2c.gleam b/src/azure/b2c.gleam index 77cd927..5759148 100644 --- a/src/azure/b2c.gleam +++ b/src/azure/b2c.gleam @@ -15,10 +15,10 @@ import gleam/string import gleam/list import gleam/result import gleam/int -import gleam/io import azure/utils import helpers/crypto import helpers/uri as uri_helpers +import helpers/parsing const challenge_length = 43 @@ -33,7 +33,12 @@ const b2c_auth_url = "https://thermialogin.b2clogin.com/thermialogin.onmicrosoft const b2c_authorize_prefix = "var SETTINGS = " pub type Tokens { - Tokens(access_token: String, refresh_token: String) + Tokens( + access_token: String, + access_token_expires_in: Int, + refresh_token: String, + refresh_token_expires_in: Int, + ) } pub type B2CError { @@ -91,14 +96,14 @@ fn authorize(code_challenge: String) -> Result(AuthInfo, B2CError) { body_split, fn(line) { string.starts_with(line, b2c_authorize_prefix) }, ) - |> map_error("Authorize settings string not found.") + |> b2c_error("Authorize settings string not found.") let prefix_len = string.length(b2c_authorize_prefix) let settings_json = string.slice(settings, prefix_len, string.length(settings) - prefix_len - 2) try data = json.decode(settings_json, using: dynamic.dynamic) - |> map_error( + |> b2c_error( "Authorize settings JSON parsing error: " <> string.inspect(settings_json), ) @@ -109,7 +114,7 @@ fn authorize(code_challenge: String) -> Result(AuthInfo, B2CError) { state_code_block |> string.split("=") |> list.at(1) - |> map_error("State code parsing error: " <> state_code_block) + |> b2c_error("State code parsing error: " <> state_code_block) Ok(AuthInfo( state_code: state_code, @@ -163,7 +168,7 @@ fn confirm( try csrf_cookie = list.key_find(auth_info.cookies, csrf_cookie_key) - |> map_error("CSRF cookie not found in auth info.") + |> b2c_error("CSRF cookie not found in auth info.") let cookies = [#(csrf_cookie_key, csrf_cookie), ..self_asserted.cookies] let req = build_req(confirm_url(), http.Get) @@ -182,7 +187,7 @@ fn confirm( try resp = req |> hackney.send() - |> map_error("Confirm HTTP request failed.") + |> b2c_error("Confirm HTTP request failed.") try resp = case resp.status { 302 -> Ok(resp) @@ -191,13 +196,13 @@ fn confirm( try location = response.get_header(resp, "location") - |> map_error("Location not found for confirm response.") + |> b2c_error("Location not found for confirm response.") try code = location |> string.split("code=") |> list.at(1) - |> map_error("Confirmation code not found.") + |> b2c_error("Confirmation code not found.") Ok(Confirmed(code: code)) } @@ -222,12 +227,20 @@ fn get_tokens( try resp = run_req(req) try data = json.decode(resp.body, using: dynamic.dynamic) - |> map_error("Get tokens JSON parsing error: " <> string.inspect(resp.body)) + |> b2c_error("Get tokens JSON parsing error: " <> string.inspect(resp.body)) try token = data_get(data, "access_token", dynamic.string) + try expires_in = data_get(data, "expires_in", dynamic.int) try refresh_token = data_get(data, "refresh_token", dynamic.string) + try refresh_token_expires_in = + data_get(data, "refresh_token_expires_in", dynamic.int) - Ok(Tokens(access_token: token, refresh_token: refresh_token)) + Ok(Tokens( + access_token: token, + access_token_expires_in: expires_in, + refresh_token: refresh_token, + refresh_token_expires_in: refresh_token_expires_in, + )) } fn hash_challenge(challenge: String) -> String { @@ -274,7 +287,7 @@ fn run_req( try resp = req |> hackney.send() - |> map_error("HTTP request failed.") + |> b2c_error("HTTP request failed.") case resp.status { 200 -> Ok(resp) @@ -307,15 +320,9 @@ fn data_get( key: String, data_type: dynamic.Decoder(a), ) -> Result(a, B2CError) { - data - |> dynamic.field(key, data_type) - |> map_error( - "Field " <> key <> " of correct type not found in data: " <> string.inspect( - data, - ), - ) + parsing.data_get(data, key, data_type, B2CError) } -fn map_error(r: Result(a, b), error_msg: String) -> Result(a, B2CError) { - result.map_error(r, fn(_) { B2CError(msg: error_msg) }) +fn b2c_error(r: Result(a, b), msg: String) -> Result(a, B2CError) { + result.replace_error(r, B2CError(msg)) } diff --git a/src/helpers/date_time.gleam b/src/helpers/date_time.gleam index 5b9555d..973f041 100644 --- a/src/helpers/date_time.gleam +++ b/src/helpers/date_time.gleam @@ -1,9 +1,9 @@ -import gleam/dynamic.{Dynamic} -import gleam/map.{Map} import gleam/erlang/atom.{Atom} -pub type DateTime = - Map(Atom, Dynamic) +pub external type DateTime pub external fn from_iso8601(String) -> Result(#(DateTime, Int), Atom) = "Elixir.DateTime" "from_iso8601" + +pub external fn from_unix(Int) -> Result(DateTime, #(Atom, Atom)) = + "Elixir.DateTime" "from_unix" diff --git a/src/helpers/parsing.gleam b/src/helpers/parsing.gleam new file mode 100644 index 0000000..9c1c26f --- /dev/null +++ b/src/helpers/parsing.gleam @@ -0,0 +1,19 @@ +import gleam/dynamic +import gleam/result +import gleam/string + +/// Get field from a dynamic data presumed to be a map, or fail with error +pub fn data_get( + data: dynamic.Dynamic, + key: String, + data_type: dynamic.Decoder(a), + error_constructor: fn(String) -> b, +) -> Result(a, b) { + data + |> dynamic.field(key, data_type) + |> result.replace_error(error_constructor( + "Field " <> key <> " of correct type not found in data: " <> string.inspect( + data, + ), + )) +} diff --git a/src/pump_api/auth/api.gleam b/src/pump_api/auth/api.gleam index d6deefd..310142d 100644 --- a/src/pump_api/auth/api.gleam +++ b/src/pump_api/auth/api.gleam @@ -4,60 +4,48 @@ import gleam/hackney import gleam/result import gleam/dynamic import gleam/list +import gleam/string import pump_api/auth/user.{User} -import pump_api/auth/token.{Token} +import pump_api/auth/tokens.{Tokens} import pump_api/auth/installation_info.{InstallationInfo} import pump_api/http import helpers/config import helpers/date_time +import helpers/parsing +import azure/b2c.{B2CError} pub type ApiError { ApiRequestFailed NotOkResponse - InvalidData + InvalidData(msg: String) + AuthError(inner: B2CError) } pub fn auth(username: String, password: String) -> Result(User, ApiError) { - let url = config.api_auth_url() - assert Ok(raw_req) = request.from_uri(url) + try tokens = + b2c.authenticate(username, password) + |> result.map_error(fn(err) { AuthError(inner: err) }) + try access_token_expires_in = + date_time.from_unix(tokens.access_token_expires_in) + |> result.replace_error(InvalidData( + msg: "Access token expiry could not be converted into DateTime: " <> string.inspect( + tokens.access_token_expires_in, + ), + )) + try refresh_token_expires_in = + date_time.from_unix(tokens.refresh_token_expires_in) + |> result.replace_error(InvalidData( + msg: "Refresh token expiry could not be converted into DateTime: " <> string.inspect( + tokens.refresh_token_expires_in, + ), + )) - let data = - json.object([ - #("username", json.string(username)), - #("password", json.string(password)), - ]) - - let json_req = request.set_body(raw_req, http.Json(data)) - try data = run_req(http.req(json_req)) - - try token_str = data_get(data, "token", dynamic.string) - try valid_to = data_get(data, "tokenValidToUtc", dynamic.string) - try valid_to_dt = - date_time.from_iso8601(valid_to) - |> map_error(InvalidData) - - let token = Token(token: token_str, token_valid_to: valid_to_dt.0) - - try user_name = data_get(data, "userName", dynamic.string) - try email = data_get(data, "email", dynamic.string) - try first_name = data_get(data, "firstName", dynamic.string) - try last_name = data_get(data, "lastName", dynamic.string) - try culture = data_get(data, "culture", dynamic.string) - try eula_accepted = data_get(data, "eulaAccepted", dynamic.bool) - try is_authenticated = data_get(data, "isAuthenticated", dynamic.bool) - try time_zone = data_get(data, "timeZone", dynamic.string) - - Ok(User( - user_name: user_name, - email: email, - first_name: first_name, - last_name: last_name, - culture: culture, - eula_accepted: eula_accepted, - is_authenticated: is_authenticated, - time_zone: time_zone, - token: token, - )) + Ok(User(tokens: Tokens( + access_token: tokens.access_token, + access_token_expiry: access_token_expires_in, + refresh_token: tokens.refresh_token, + refresh_token_expiry: refresh_token_expires_in, + ))) } pub fn installation_info(user: User) -> Result(List(InstallationInfo), ApiError) { @@ -68,7 +56,12 @@ pub fn installation_info(user: User) -> Result(List(InstallationInfo), ApiError) try data = run_req(http.authed_req(user, empty_req)) try items = - data_get(data, "items", dynamic.list(of: dynamic.field("id", dynamic.int))) + parsing.data_get( + data, + "items", + dynamic.list(of: dynamic.field("id", dynamic.int)), + InvalidData, + ) Ok(list.map(items, fn(id) { InstallationInfo(id: id) })) } @@ -77,7 +70,7 @@ fn run_req(req: request.Request(String)) { try resp = req |> hackney.send() - |> map_error(ApiRequestFailed) + |> result.replace_error(ApiRequestFailed) try body = case resp.status { 200 -> Ok(resp.body) @@ -86,19 +79,7 @@ fn run_req(req: request.Request(String)) { body |> json.decode(using: dynamic.dynamic) - |> map_error(InvalidData) -} - -fn data_get( - data: dynamic.Dynamic, - key: String, - data_type: dynamic.Decoder(a), -) -> Result(a, ApiError) { - data - |> dynamic.field(key, data_type) - |> map_error(InvalidData) -} - -fn map_error(r: Result(a, b), new_error: ApiError) -> Result(a, ApiError) { - result.map_error(r, fn(_) { new_error }) + |> result.replace_error(InvalidData( + msg: "Could not parse InstallationInfo JSON.", + )) } diff --git a/src/pump_api/auth/token.gleam b/src/pump_api/auth/token.gleam deleted file mode 100644 index 82b1fca..0000000 --- a/src/pump_api/auth/token.gleam +++ /dev/null @@ -1,5 +0,0 @@ -import helpers/date_time.{DateTime} - -pub type Token { - Token(token: String, token_valid_to: DateTime) -} diff --git a/src/pump_api/auth/tokens.gleam b/src/pump_api/auth/tokens.gleam new file mode 100644 index 0000000..850ac4d --- /dev/null +++ b/src/pump_api/auth/tokens.gleam @@ -0,0 +1,10 @@ +import helpers/date_time.{DateTime} + +pub type Tokens { + Tokens( + access_token: String, + access_token_expiry: DateTime, + refresh_token: String, + refresh_token_expiry: DateTime, + ) +} diff --git a/src/pump_api/auth/user.gleam b/src/pump_api/auth/user.gleam index 6c97ac5..7e40b64 100644 --- a/src/pump_api/auth/user.gleam +++ b/src/pump_api/auth/user.gleam @@ -1,15 +1,5 @@ -import pump_api/auth/token +import pump_api/auth/tokens pub type User { - User( - user_name: String, - email: String, - first_name: String, - last_name: String, - culture: String, - eula_accepted: Bool, - is_authenticated: Bool, - time_zone: String, - token: token.Token, - ) + User(tokens: tokens.Tokens) } diff --git a/src/pump_api/http.gleam b/src/pump_api/http.gleam index 6f3dc6c..6cc2f0f 100644 --- a/src/pump_api/http.gleam +++ b/src/pump_api/http.gleam @@ -13,7 +13,7 @@ pub type ApiRequest = pub fn authed_req(user: User, r: ApiRequest) { r - |> request.set_header("authorization", "Bearer " <> user.token.token) + |> request.set_header("authorization", "Bearer " <> user.tokens.access_token) |> req() }