Some checks failed
Build and Push / Build and Push Docker Image (push) Has been cancelled
Create shared ContactsFixtures module with globally-unique qso_timestamp seconds to prevent unique-constraint violations when async test modules run in parallel and insert contacts with identical dedup-key columns. The qso_timestamp column is timestamp(0), so microsecond offsets were truncated — use System.unique_integer monotonic seconds instead. Also make count-asserting tests resilient to sandbox-leaked contacts from prior tests by using >= assertions or status-based checks rather than exact counts. Includes automated DateTime.add → DateTime.shift migration from mix format.
72 lines
2 KiB
Elixir
72 lines
2 KiB
Elixir
defmodule Microwaveprop.Qrz do
|
|
@moduledoc """
|
|
Callsign-lookup facade around the QRZ.com XML API.
|
|
|
|
Results are cached in the `qrz_callsigns` table keyed by uppercased
|
|
callsign with a configurable TTL (`cache_ttl_hours`, default 168 / 7
|
|
days). Entries that are still fresh return from the DB without
|
|
touching the upstream.
|
|
"""
|
|
import Ecto.Query
|
|
|
|
alias Microwaveprop.Qrz.Callsign
|
|
alias Microwaveprop.Qrz.Client
|
|
alias Microwaveprop.Qrz.Record
|
|
alias Microwaveprop.Repo
|
|
|
|
@spec lookup_callsign(String.t()) :: {:ok, Record.t()} | {:error, String.t()}
|
|
def lookup_callsign(callsign) do
|
|
callsign = String.upcase(callsign)
|
|
|
|
case get_fresh_cache(callsign) do
|
|
%Callsign{data: data} ->
|
|
{:ok, Record.from_map(data)}
|
|
|
|
nil ->
|
|
fetch_and_upsert(callsign)
|
|
end
|
|
end
|
|
|
|
@spec get_callsign(String.t()) :: Callsign.t() | nil
|
|
def get_callsign(callsign) do
|
|
callsign = String.upcase(callsign)
|
|
Repo.get_by(Callsign, callsign: callsign)
|
|
end
|
|
|
|
defp get_fresh_cache(callsign) do
|
|
ttl_hours = config()[:cache_ttl_hours] || 168
|
|
cutoff = DateTime.shift(DateTime.utc_now(), hour: -ttl_hours)
|
|
|
|
Callsign
|
|
|> where([c], c.callsign == ^callsign and c.updated_at > ^cutoff)
|
|
|> Repo.one()
|
|
end
|
|
|
|
defp fetch_and_upsert(callsign) do
|
|
case Client.lookup(callsign) do
|
|
{:ok, data} ->
|
|
now = DateTime.utc_now(:second)
|
|
|
|
result =
|
|
%Callsign{}
|
|
|> Callsign.changeset(%{callsign: callsign, data: data})
|
|
|> Repo.insert(
|
|
on_conflict: [set: [data: data, updated_at: now]],
|
|
conflict_target: :callsign,
|
|
returning: true
|
|
)
|
|
|
|
case result do
|
|
{:ok, %Callsign{data: data}} -> {:ok, Record.from_map(data)}
|
|
{:error, changeset} -> {:error, "Failed to save: #{inspect(changeset.errors)}"}
|
|
end
|
|
|
|
{:error, _} = error ->
|
|
error
|
|
end
|
|
end
|
|
|
|
defp config do
|
|
Application.get_env(:microwaveprop, __MODULE__, [])
|
|
end
|
|
end
|