- Create MaidenheadChangesetHelpers: consolidate grid validation, callsign normalization, lat/lon validation, grid/latlon derivation across 6 schemas - Create ContextHelpers: shared fetch_owned with admin bypass, safe_enqueue for Oban workers, UUID casting to replace CastError rescues - Extend LiveHelpers: add current_user/1 (removes 7 duplicate definitions), subscribe/2 (replaces 13 inline PubSub sites), assign_url_params/2 - Extract Propagation.ScoreStore (528 lines): separate file I/O and cache management from scoring logic, 13 defdelegate passthroughs - Split SubmitLive (942->475 lines): extract CSV/ADIF upload rendering into 3 function component modules (csv_upload, adif_upload, preview) - Update 16 LiveViews to use shared helpers
105 lines
3.3 KiB
Elixir
105 lines
3.3 KiB
Elixir
defmodule Microwaveprop.BeaconMeasurements do
|
|
@moduledoc """
|
|
The BeaconMeasurements context: ingests beacon-signal-level
|
|
measurements uploaded by propmonitor clients.
|
|
|
|
See `Microwaveprop.BeaconMeasurements.BeaconMeasurement` for the
|
|
schema and field semantics.
|
|
"""
|
|
|
|
import Ecto.Query
|
|
|
|
alias Microwaveprop.BeaconMeasurements.BeaconMeasurement
|
|
alias Microwaveprop.BeaconMonitors.BeaconMonitor
|
|
alias Microwaveprop.Repo
|
|
|
|
@doc """
|
|
Inserts a measurement for the given monitor. `attrs` is expected to
|
|
carry the wire fields from `POST /api/v1/beacon-monitor/measurements`
|
|
(including `beacon_id` as a UUID string).
|
|
|
|
Returns `{:error, :beacon_not_found}` if the beacon UUID does not
|
|
resolve to an approved on-the-air beacon — the propmonitor client
|
|
drops the measurement on this, so we keep the differentiation from
|
|
generic 422s.
|
|
"""
|
|
@spec create_measurement(BeaconMonitor.t(), map()) ::
|
|
{:ok, BeaconMeasurement.t()}
|
|
| {:error, :beacon_not_found}
|
|
| {:error, Ecto.Changeset.t()}
|
|
def create_measurement(%BeaconMonitor{id: monitor_id}, attrs) when is_map(attrs) do
|
|
case lookup_beacon(attrs) do
|
|
:not_found ->
|
|
{:error, :beacon_not_found}
|
|
|
|
{:ok, beacon_id} ->
|
|
attrs =
|
|
attrs
|
|
|> normalize_attrs()
|
|
|> Map.put("monitor_id", monitor_id)
|
|
|> Map.put("beacon_id", beacon_id)
|
|
|
|
%BeaconMeasurement{}
|
|
|> BeaconMeasurement.changeset(attrs)
|
|
|> Repo.insert()
|
|
end
|
|
end
|
|
|
|
defp lookup_beacon(attrs) do
|
|
attrs = Map.new(attrs, fn {k, v} -> {to_string(k), v} end)
|
|
|
|
with id when is_binary(id) and byte_size(id) > 0 <- Map.get(attrs, "beacon_id"),
|
|
{:ok, uuid} <- Ecto.UUID.cast(id) do
|
|
# Only let monitors report for approved on-the-air beacons. The
|
|
# client drops 404s without retry, so an admin un-approving a
|
|
# beacon cleanly stops the monitor pointing at it.
|
|
query =
|
|
from b in Microwaveprop.Beacons.Beacon,
|
|
where: b.id == ^uuid and b.approved == true and b.on_the_air == true,
|
|
select: b.id
|
|
|
|
case Repo.one(query) do
|
|
nil -> :not_found
|
|
uuid -> {:ok, uuid}
|
|
end
|
|
else
|
|
_ -> :not_found
|
|
end
|
|
end
|
|
|
|
# The wire payload uses string keys (Phoenix JSON params). Make sure
|
|
# we don't drop a value because some intermediate handed us atom keys.
|
|
defp normalize_attrs(attrs) do
|
|
Map.new(attrs, fn
|
|
{k, v} when is_atom(k) -> {Atom.to_string(k), v}
|
|
{k, v} -> {k, v}
|
|
end)
|
|
end
|
|
|
|
@doc """
|
|
Returns the most recent measurements for the given beacon, newest first.
|
|
"""
|
|
@spec list_recent_for_beacon(Ecto.UUID.t(), pos_integer()) :: [BeaconMeasurement.t()]
|
|
def list_recent_for_beacon(beacon_id, limit \\ 100) do
|
|
Repo.all(
|
|
from m in BeaconMeasurement,
|
|
where: m.beacon_id == ^beacon_id,
|
|
order_by: [desc: m.measured_at],
|
|
limit: ^limit,
|
|
preload: [:monitor]
|
|
)
|
|
end
|
|
|
|
@doc """
|
|
Returns the most recent measurements for the given monitor, newest first.
|
|
"""
|
|
@spec list_recent_for_monitor(Ecto.UUID.t(), pos_integer()) :: [BeaconMeasurement.t()]
|
|
def list_recent_for_monitor(monitor_id, limit \\ 100) do
|
|
Repo.all(
|
|
from m in BeaconMeasurement,
|
|
where: m.monitor_id == ^monitor_id,
|
|
order_by: [desc: m.measured_at],
|
|
limit: ^limit
|
|
)
|
|
end
|
|
end
|