prop/lib/microwaveprop/beacon_monitors.ex
Graham McIntire 51e390959c Add dialyzer specs and types across the codebase
277 @spec/@type annotations added to 58 files covering all public
APIs: contexts (propagation, radio, weather, terrain, beacons,
commercial), GRIB2 decoders, terrain analysis, duct detection,
rain scatter, CSV/ADIF import, weather clients, and all Ecto
schemas. Dialyzer passes with 0 errors.
2026-04-12 08:55:04 -05:00

80 lines
2.3 KiB
Elixir

defmodule Microwaveprop.BeaconMonitors do
@moduledoc """
The BeaconMonitors context: manages the monitor stations a user
has registered. Each monitor has a unique random token the remote
program uses to authenticate its reports.
"""
import Ecto.Query
alias Microwaveprop.Accounts.User
alias Microwaveprop.BeaconMonitors.BeaconMonitor
alias Microwaveprop.Repo
@token_bytes 32
@doc """
Returns all monitors for the given user, newest first.
"""
@spec list_monitors_for_user(User.t()) :: [BeaconMonitor.t()]
def list_monitors_for_user(%User{id: user_id}) do
Repo.all(
from m in BeaconMonitor,
where: m.user_id == ^user_id,
order_by: [desc: m.inserted_at]
)
end
@doc """
Creates a new monitor for the given user with a freshly generated token.
"""
@spec create_monitor(User.t(), map()) :: {:ok, BeaconMonitor.t()} | {:error, Ecto.Changeset.t()}
def create_monitor(%User{} = user, attrs) do
%BeaconMonitor{user_id: user.id, token: generate_token()}
|> BeaconMonitor.changeset(attrs)
|> Repo.insert()
end
@doc """
Deletes a monitor owned by the given user.
Returns `{:error, :not_found}` if the monitor does not exist or
belongs to another user.
"""
@spec delete_monitor(User.t(), Ecto.UUID.t()) ::
{:ok, BeaconMonitor.t()} | {:error, :not_found} | {:error, Ecto.Changeset.t()}
def delete_monitor(%User{id: user_id}, monitor_id) do
query =
from m in BeaconMonitor,
where: m.id == ^monitor_id and m.user_id == ^user_id
case Repo.one(query) do
nil -> {:error, :not_found}
monitor -> Repo.delete(monitor)
end
rescue
Ecto.Query.CastError -> {:error, :not_found}
end
@doc """
Looks up a monitor by its token. Returns nil if not found.
"""
@spec get_monitor_by_token(String.t()) :: BeaconMonitor.t() | nil
def get_monitor_by_token(token) when is_binary(token) do
Repo.get_by(BeaconMonitor, token: token)
end
@doc """
Returns a blank changeset for rendering the new-monitor form.
"""
@spec change_monitor(map()) :: Ecto.Changeset.t()
def change_monitor(attrs \\ %{}) do
BeaconMonitor.changeset(%BeaconMonitor{}, attrs)
end
defp generate_token do
@token_bytes
|> :crypto.strong_rand_bytes()
|> Base.url_encode64(padding: false)
end
end