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 @token_check_timer 10_000 @token_check_diff 5 * 60 defmodule Options do deftypedstruct(%{ server_name: GenServer.name(), username: String.t(), password: String.t() }) end defmodule State do deftypedstruct(%{ username: String.t(), password: String.t(), authed_user: {Auth.User.t() | nil, nil}, installations_fetched: {boolean(), false}, installations: {[Auth.InstallationInfo.t()], []} }) end @spec start_link(Options.t()) :: GenServer.on_start() def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: opts.server_name) end @impl true def init(%Options{} = opts) do case init_state(opts.username, opts.password) do {:ok, state} -> {:ok, state} :error -> {:stop, :error} end end @impl true def handle_call(msg, from, state) def handle_call(:get_auth, _from, state) do {:reply, state.authed_user, state} end def handle_call(:get_installations, _from, state) do {:reply, state.installations, state} end def handle_call({:get_installation, id}, _from, state) do {:reply, Enum.find(state.installations, &(Auth.InstallationInfo.record(&1, :id) == id)), state} end @impl true def handle_info(msg, state) def handle_info(:token_check, state) do now = DateTime.utc_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.info("Renewing auth token since #{diff} < #{@token_check_diff}") case init_state(state.username, state.password) do {:ok, new_state} -> {:noreply, new_state} :error -> {:noreply, state} end else {:noreply, state} end end @spec get_auth(GenServer.name()) :: Auth.User.t() def get_auth(server) do GenServer.call(server, :get_auth) end @spec get_installations(GenServer.name()) :: Auth.InstallationInfo.t() def get_installations(server) do GenServer.call(server, :get_installations) end @spec get_installation(GenServer.name(), integer()) :: Auth.InstallationInfo.t() | nil def get_installation(server, id) do GenServer.call(server, {:get_installation, id}) end 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 err -> Logger.error("Could not auth or fetch installations! #{inspect(err)}") :error end end end