From 088fd2071174e7996e7a58e5fa65b0386e2126d5 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 1 Jul 2026 18:08:56 -0500 Subject: [PATCH] refactor: FP patterns in packets_live/callsign_view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - mount: extract subscribe_if_connected/2, split assign_valid/assign_invalid to eliminate inline if/else for validation + subscription logic. - enrich_packet_with_device_identifier: extract resolve_canonical_identifier/1 as multi-clause function (binary guard + catch-all) instead of nested if. - update_packet_lists: cond → multi-clause do_update/4 with guards. Three explicit paths: at-cap no-stored, at-cap with-stored, under-cap. - get_timestamp_microseconds: case → multi-clause (%DateTime{}, binary, catch-all) for cleaner dispatch. --- .../live/packets_live/callsign_view.ex | 139 ++++++++---------- 1 file changed, 64 insertions(+), 75 deletions(-) diff --git a/lib/aprsme_web/live/packets_live/callsign_view.ex b/lib/aprsme_web/live/packets_live/callsign_view.ex index 17cdb65..6bb62b0 100644 --- a/lib/aprsme_web/live/packets_live/callsign_view.ex +++ b/lib/aprsme_web/live/packets_live/callsign_view.ex @@ -13,42 +13,43 @@ defmodule AprsmeWeb.PacketsLive.CallsignView do @impl true def mount(%{"callsign" => callsign}, _session, socket) do - # Validate and normalize callsign - normalized_callsign = Callsign.normalize(callsign) + normalized = Callsign.normalize(callsign) - if Callsign.valid?(normalized_callsign) do - # Subscribe to live packet updates for this callsign only - _ = - if connected?(socket) do - Phoenix.PubSub.subscribe(Aprsme.PubSub, "packets:#{normalized_callsign}") - end - - # Get stored packets for this callsign (up to 100) - stored_packets = get_stored_packets(normalized_callsign, 100) - all_packets = stored_packets - - socket = - socket - |> assign(:callsign, normalized_callsign) - |> assign(:packets, stored_packets) - |> assign(:live_packets, []) - |> assign(:all_packets, all_packets) - |> assign(:error, nil) - - {:ok, socket} + if Callsign.valid?(normalized) do + socket = subscribe_if_connected(socket, normalized) + stored = get_stored_packets(normalized, 100) + {:ok, assign_valid(socket, normalized, stored)} else - socket = - socket - |> assign(:callsign, normalized_callsign) - |> assign(:packets, []) - |> assign(:live_packets, []) - |> assign(:all_packets, []) - |> assign(:error, "Invalid callsign format") - - {:ok, socket} + {:ok, assign_invalid(socket, normalized)} end end + defp subscribe_if_connected(socket, callsign) do + if connected?(socket) do + Phoenix.PubSub.subscribe(Aprsme.PubSub, "packets:#{callsign}") + end + + socket + end + + defp assign_valid(socket, callsign, stored) do + socket + |> assign(:callsign, callsign) + |> assign(:packets, stored) + |> assign(:live_packets, []) + |> assign(:all_packets, stored) + |> assign(:error, nil) + end + + defp assign_invalid(socket, callsign) do + socket + |> assign(:callsign, callsign) + |> assign(:packets, []) + |> assign(:live_packets, []) + |> assign(:all_packets, []) + |> assign(:error, "Invalid callsign format") + end + @impl true def handle_info({:postgres_packet, payload}, socket) do SharedPacketHandler.handle_packet_update(payload, socket, @@ -60,7 +61,6 @@ defmodule AprsmeWeb.PacketsLive.CallsignView do def handle_info(_message, socket), do: {:noreply, socket} defp process_matching_packet(enriched_payload, socket) do - # Device identifier enrichment specific to this module enriched_payload = enrich_packet_with_device_identifier(enriched_payload) current_live = socket.assigns.live_packets @@ -69,13 +69,11 @@ defmodule AprsmeWeb.PacketsLive.CallsignView do {updated_stored, updated_live} = update_packet_lists(current_stored, current_live, enriched_payload) all_packets = get_all_packets_list(updated_stored, updated_live) - socket = - socket - |> assign(:packets, updated_stored) - |> assign(:live_packets, updated_live) - |> assign(:all_packets, all_packets) - - {:noreply, socket} + {:noreply, + socket + |> assign(:packets, updated_stored) + |> assign(:live_packets, updated_live) + |> assign(:all_packets, all_packets)} end defp enrich_packet_with_device_identifier(sanitized_payload) do @@ -84,18 +82,18 @@ defmodule AprsmeWeb.PacketsLive.CallsignView do Map.get(sanitized_payload, "device_identifier") || DeviceParser.extract_device_identifier(sanitized_payload) - canonical_identifier = - if is_binary(device_identifier) do - matched_device = Aprsme.DeviceIdentification.lookup_device_by_identifier(device_identifier) - if matched_device, do: matched_device.identifier, else: device_identifier - else - device_identifier - end - + canonical_identifier = resolve_canonical_identifier(device_identifier) Map.put(sanitized_payload, :device_identifier, canonical_identifier) end - # Private helper functions + defp resolve_canonical_identifier(identifier) when is_binary(identifier) do + case Aprsme.DeviceIdentification.lookup_device_by_identifier(identifier) do + %{identifier: canonical} -> canonical + nil -> identifier + end + end + + defp resolve_canonical_identifier(identifier), do: identifier # Get recent packets for this callsign from the database (all packets, not just position) # Returns the latest packets regardless of age, filtered by callsign @@ -133,37 +131,28 @@ defmodule AprsmeWeb.PacketsLive.CallsignView do |> Enum.take(100) end - defp get_timestamp_microseconds(packet) do - case packet.received_at do - %DateTime{} = dt -> - DateTime.to_unix(dt, :microsecond) + defp get_timestamp_microseconds(%DateTime{} = dt), do: DateTime.to_unix(dt, :microsecond) - dt when is_binary(dt) -> - case DateTime.from_iso8601(dt) do - {:ok, parsed_dt, _} -> DateTime.to_unix(parsed_dt, :microsecond) - _ -> 0 - end - - _ -> - 0 + defp get_timestamp_microseconds(dt) when is_binary(dt) do + case DateTime.from_iso8601(dt) do + {:ok, parsed_dt, _} -> DateTime.to_unix(parsed_dt, :microsecond) + _ -> 0 end end - # Helper to update stored and live packet lists, keeping total <= 100 + defp get_timestamp_microseconds(_), do: 0 + + # Helper to update stored and live packet lists, keeping total <= 100. + # Dispatches on capacity: at-cap with no stored, at-cap with stored, under-cap. defp update_packet_lists(current_stored, current_live, sanitized_payload) do - total_count = length(current_live) + length(current_stored) - - cond do - total_count >= 100 and current_stored == [] -> - live_without_oldest = Enum.drop(current_live, -1) - {current_stored, [sanitized_payload | live_without_oldest]} - - total_count >= 100 -> - stored_without_oldest = Enum.drop(current_stored, -1) - {stored_without_oldest, [sanitized_payload | current_live]} - - true -> - {current_stored, [sanitized_payload | current_live]} - end + total = length(current_live) + length(current_stored) + do_update(total, current_stored, current_live, sanitized_payload) end + + defp do_update(total, [], current_live, payload) when total >= 100, do: {[], [payload | Enum.drop(current_live, -1)]} + + defp do_update(total, current_stored, current_live, payload) when total >= 100, + do: {Enum.drop(current_stored, -1), [payload | current_live]} + + defp do_update(_total, current_stored, current_live, payload), do: {current_stored, [payload | current_live]} end