refactor(aprs): drop unreachable defensive branches, force UTC since

- decode_packet_row no longer guards Decimal.to_float / DateTime.from_naive
  with passthrough clauses — Postgrex returns Decimal for numeric and
  NaiveDateTime for timestamp without time zone, so the WHERE clause
  filters out the only path that could have hit the alternate branches.
- recent_packets_with_paths shifts :since to UTC before to_naive so a
  caller passing a non-UTC %DateTime{} doesn't silently match the wrong
  window.
- moduledoc now documents that the module raises on connection/SQL
  errors and is unsafe in async contexts without a catch.
This commit is contained in:
Graham McIntire 2026-05-01 12:52:11 -05:00
parent 65e97bec6b
commit a7f5bd5f18
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59

View file

@ -15,6 +15,15 @@ defmodule Microwaveprop.Aprs do
Tests run against a local `aprsme_test` database whose schema mirrors
aprs.me's partitioned `packets` table; rows are inserted via raw SQL
in a setup block under the `Ecto.Adapters.SQL.Sandbox`.
> #### Error semantics {: .warning}
>
> Both queries call `Ecto.Adapters.SQL.query!/3` and will raise on
> connection or SQL failures. Today the only caller is the
> `Calibrate.Aprs144` mix task, where a hard exit is the right
> ergonomic. Async callers (`Task.async_stream`, LiveView
> `start_async`, etc.) MUST catch and log per the project's
> swallowed-error rule in CLAUDE.md.
"""
alias Ecto.Adapters.SQL
@ -73,8 +82,12 @@ defmodule Microwaveprop.Aprs do
since = Keyword.get_lazy(opts, :since, fn -> DateTime.add(DateTime.utc_now(), -3600, :second) end)
limit = Keyword.get(opts, :limit, 50_000)
# Force the bound to UTC so a non-UTC %DateTime{} doesn't silently
# match a wrong window after `to_naive` strips the tz tag.
naive_since = since |> DateTime.shift_zone!("Etc/UTC") |> DateTime.to_naive()
%Postgrex.Result{rows: rows} =
SQL.query!(AprsRepo, @recent_packets_sql, [DateTime.to_naive(since), limit])
SQL.query!(AprsRepo, @recent_packets_sql, [naive_since, limit])
Enum.map(rows, &decode_packet_row/1)
end
@ -100,17 +113,14 @@ defmodule Microwaveprop.Aprs do
id: id,
sender: sender,
base_callsign: base_callsign,
lat: to_float(lat),
lon: to_float(lon),
path: path || "",
received_at: to_utc_datetime(received_at)
lat: Decimal.to_float(lat),
lon: Decimal.to_float(lon),
path: path,
received_at: DateTime.from_naive!(received_at, "Etc/UTC")
}
end
defp to_float(%Decimal{} = d), do: Decimal.to_float(d)
defp to_float(n) when is_float(n), do: n
defp to_float(n) when is_integer(n), do: n * 1.0
defp to_utc_datetime(%NaiveDateTime{} = naive), do: DateTime.from_naive!(naive, "Etc/UTC")
defp to_utc_datetime(%DateTime{} = dt), do: dt
end