100 lines
2.8 KiB
Elixir
100 lines
2.8 KiB
Elixir
defmodule AprsWeb.Api.V1.CallsignController do
|
|
@moduledoc """
|
|
API v1 controller for callsign-related endpoints.
|
|
"""
|
|
use AprsWeb, :controller
|
|
|
|
alias Aprs.Packets
|
|
alias AprsWeb.Api.V1.CallsignJSON
|
|
|
|
action_fallback AprsWeb.Api.V1.FallbackController
|
|
|
|
@doc """
|
|
Get the most recent packet for a given callsign.
|
|
|
|
## Parameters
|
|
* `callsign` - The callsign to search for, with optional SSID (e.g., "N0CALL" or "N0CALL-9")
|
|
|
|
## Returns
|
|
* 200 - Success with packet data
|
|
* 404 - No packets found for the callsign
|
|
* 400 - Invalid callsign format
|
|
"""
|
|
def show(conn, %{"callsign" => callsign}) do
|
|
with {:ok, normalized_callsign} <- validate_callsign(callsign),
|
|
{:ok, packet} <- get_latest_packet(normalized_callsign) do
|
|
conn
|
|
|> put_status(:ok)
|
|
|> put_view(json: CallsignJSON)
|
|
|> render(:show, packet: packet)
|
|
else
|
|
{:error, :invalid_callsign} ->
|
|
{:error, "Invalid callsign format"}
|
|
|
|
{:error, :not_found} ->
|
|
conn
|
|
|> put_status(:ok)
|
|
|> put_view(json: CallsignJSON)
|
|
|> render(:not_found, callsign: callsign)
|
|
|
|
{:error, reason} ->
|
|
{:error, reason}
|
|
end
|
|
end
|
|
|
|
defp validate_callsign(callsign) when is_binary(callsign) do
|
|
# Remove any whitespace and convert to uppercase
|
|
normalized = callsign |> String.trim() |> String.upcase()
|
|
|
|
if validate_callsign_format(normalized) do
|
|
{:ok, normalized}
|
|
else
|
|
{:error, :invalid_callsign}
|
|
end
|
|
end
|
|
|
|
defp validate_callsign(nil), do: {:error, :invalid_callsign}
|
|
defp validate_callsign(_), do: {:error, :invalid_callsign}
|
|
|
|
defp validate_callsign_format(callsign) do
|
|
# Basic callsign validation
|
|
# Matches patterns like: N0CALL, N0CALL-9, W1ABC-15, etc.
|
|
# This is a simplified regex - a full implementation might be more comprehensive
|
|
callsign_regex = ~r/^[A-Z0-9]{1,3}[0-9][A-Z]{1,4}(-[0-9]{1,2})?$/
|
|
|
|
String.match?(callsign, callsign_regex) and String.length(callsign) <= 12
|
|
end
|
|
|
|
defp get_latest_packet(callsign) do
|
|
# Try to get the most recent packet for this callsign
|
|
# We'll limit to packets from the last 30 days to keep queries efficient
|
|
thirty_days_ago = DateTime.add(DateTime.utc_now(), -30, :day)
|
|
|
|
# Use get_recent_packets which orders by desc received_at
|
|
opts = %{
|
|
callsign: callsign,
|
|
start_time: thirty_days_ago,
|
|
limit: 1
|
|
}
|
|
|
|
case Packets.get_recent_packets(opts) do
|
|
[] ->
|
|
{:error, :not_found}
|
|
|
|
[packet | _] ->
|
|
{:ok, packet}
|
|
|
|
{:error, reason} ->
|
|
{:error, reason}
|
|
end
|
|
rescue
|
|
Ecto.QueryError ->
|
|
{:error, :database_error}
|
|
|
|
error ->
|
|
require Logger
|
|
|
|
Logger.error("Error fetching packet for callsign #{callsign}: #{inspect(error)}")
|
|
{:error, :internal_error}
|
|
end
|
|
end
|