diff --git a/lib/aprs_web/live/api_docs_live.ex b/lib/aprs_web/live/api_docs_live.ex index 81c3cf1..670be32 100644 --- a/lib/aprs_web/live/api_docs_live.ex +++ b/lib/aprs_web/live/api_docs_live.ex @@ -6,9 +6,204 @@ defmodule AprsWeb.ApiDocsLive do @impl true def mount(_params, _session, socket) do - {:ok, assign(socket, page_title: "API Documentation")} + {:ok, + socket + |> assign(page_title: "API Documentation") + |> assign(test_callsign: "") + |> assign(loading: false) + |> assign(api_result: nil) + |> assign(error: nil)} end + @impl true + def handle_event("update_callsign", %{"callsign" => callsign}, socket) do + {:noreply, + socket + |> assign(test_callsign: callsign) + |> assign(error: nil)} + end + + @impl true + def handle_event("test_api", %{"callsign" => callsign}, socket) do + normalized_callsign = callsign |> String.trim() |> String.upcase() + + if normalized_callsign == "" do + {:noreply, assign(socket, error: "Please enter a callsign")} + else + socket = + socket + |> assign(loading: true) + |> assign(error: nil) + |> assign(api_result: nil) + + # Make the API call + send(self(), {:call_api, normalized_callsign}) + {:noreply, socket} + end + end + + @impl true + def handle_info({:call_api, callsign}, socket) do + case call_api(callsign) do + {:ok, result} -> + {:noreply, + socket + |> assign(loading: false) + |> assign(api_result: result)} + + {:error, error_message} -> + {:noreply, + socket + |> assign(loading: false) + |> assign(error: error_message)} + end + end + + defp call_api(callsign) do + # Use the same validation and lookup logic as the API controller + alias Aprs.Packets + + # Validate callsign format + if validate_callsign_format(callsign) do + # Try to get the most recent packet for this callsign + thirty_days_ago = DateTime.add(DateTime.utc_now(), -30, :day) + + opts = %{ + callsign: callsign, + start_time: thirty_days_ago, + limit: 1 + } + + case Packets.get_recent_packets(opts) do + [] -> + {:ok, %{"data" => nil, "message" => "No packets found for callsign #{callsign}"}} + + [packet | _] -> + # Format the packet data similar to the JSON view + packet_data = format_packet_for_display(packet) + {:ok, %{"data" => packet_data}} + + {:error, _reason} -> + {:error, "Database error occurred while fetching packet data"} + end + else + {:error, "Invalid callsign format"} + end + rescue + error -> + {:error, "An error occurred: #{inspect(error)}"} + end + + defp validate_callsign_format(callsign) do + # Same validation as in the controller + 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 format_packet_for_display(%Aprs.Packet{} = packet) do + %{ + "id" => packet.id, + "callsign" => format_callsign(packet.base_callsign, packet.ssid), + "base_callsign" => packet.base_callsign, + "ssid" => packet.ssid, + "sender" => packet.sender, + "destination" => packet.destination, + "path" => packet.path, + "data_type" => packet.data_type, + "information_field" => packet.information_field, + "raw_packet" => packet.raw_packet, + "received_at" => packet.received_at, + "region" => packet.region, + "position" => position_data(packet), + "symbol" => symbol_data(packet), + "comment" => packet.comment, + "timestamp" => packet.timestamp, + "aprs_messaging" => packet.aprs_messaging, + "weather" => weather_data(packet), + "equipment" => equipment_data(packet), + "message" => message_data(packet), + "has_position" => packet.has_position, + "inserted_at" => packet.inserted_at, + "updated_at" => packet.updated_at + } + end + + defp format_callsign(base_callsign, nil), do: base_callsign + defp format_callsign(base_callsign, "0"), do: base_callsign + defp format_callsign(base_callsign, ssid), do: "#{base_callsign}-#{ssid}" + + defp position_data(%Aprs.Packet{has_position: false}), do: nil + defp position_data(%Aprs.Packet{lat: nil, lon: nil}), do: nil + + defp position_data(%Aprs.Packet{} = packet) do + base_position = %{ + "latitude" => to_float(packet.lat), + "longitude" => to_float(packet.lon) + } + + base_position + |> maybe_add("course", packet.course) + |> maybe_add("speed", packet.speed) + |> maybe_add("altitude", packet.altitude) + end + + defp symbol_data(%Aprs.Packet{symbol_code: nil, symbol_table_id: nil}), do: nil + + defp symbol_data(%Aprs.Packet{} = packet) do + %{ + "code" => packet.symbol_code, + "table_id" => packet.symbol_table_id + } + end + + defp weather_data(%Aprs.Packet{} = packet) do + weather_data = + %{} + |> maybe_add("temperature", packet.temperature) + |> maybe_add("humidity", packet.humidity) + |> maybe_add("wind_speed", packet.wind_speed) + |> maybe_add("wind_direction", packet.wind_direction) + |> maybe_add("wind_gust", packet.wind_gust) + |> maybe_add("pressure", packet.pressure) + |> maybe_add("rain_1h", packet.rain_1h) + |> maybe_add("rain_24h", packet.rain_24h) + |> maybe_add("rain_since_midnight", packet.rain_since_midnight) + + case weather_data do + empty when empty == %{} -> nil + data -> data + end + end + + defp equipment_data(%Aprs.Packet{} = packet) do + equipment_data = + %{} + |> maybe_add("manufacturer", packet.manufacturer) + |> maybe_add("equipment_type", packet.equipment_type) + + case equipment_data do + empty when empty == %{} -> nil + data -> data + end + end + + defp message_data(%Aprs.Packet{addressee: nil, message_text: nil, message_number: nil}), do: nil + + defp message_data(%Aprs.Packet{} = packet) do + %{} + |> maybe_add("addressee", packet.addressee) + |> maybe_add("text", packet.message_text) + |> maybe_add("number", packet.message_number) + end + + defp maybe_add(map, _key, nil), do: map + defp maybe_add(map, _key, ""), do: map + defp maybe_add(map, key, value), do: Map.put(map, key, value) + + defp to_float(%Decimal{} = decimal), do: Decimal.to_float(decimal) + defp to_float(value) when is_number(value), do: value + defp to_float(_), do: nil + @impl true def render(assigns) do ~H""" @@ -434,6 +629,192 @@ defmodule AprsWeb.ApiDocsLive do + +
+ Try the API directly from this page. Enter a callsign to see the most recent packet data. +
+ +{@error}
+<%= Jason.encode!(@api_result, pretty: true) %>+
{@api_result["message"]}
+ + +<%= Jason.encode!(@api_result, pretty: true) %>+