diff --git a/lib/microwaveprop/radio.ex b/lib/microwaveprop/radio.ex index 42f5fc52..58f120f8 100644 --- a/lib/microwaveprop/radio.ex +++ b/lib/microwaveprop/radio.ex @@ -128,6 +128,22 @@ defmodule Microwaveprop.Radio do |> Repo.all() end + @doc """ + Returns all contacts where the given callsign appears as either + `station1` or `station2`, newest first. Matching is case-insensitive. + """ + @spec list_contacts_involving_callsign(String.t() | nil) :: [Contact.t()] + def list_contacts_involving_callsign(callsign) when callsign in [nil, ""], do: [] + + def list_contacts_involving_callsign(callsign) when is_binary(callsign) do + upcased = String.upcase(callsign) + + Contact + |> where([c], fragment("upper(?)", c.station1) == ^upcased or fragment("upper(?)", c.station2) == ^upcased) + |> order_by([c], desc: c.qso_timestamp, desc: c.id) + |> Repo.all() + end + @spec list_contacts(keyword()) :: contact_page() def list_contacts(opts \\ []) do page = max(Keyword.get(opts, :page, 1), 1) diff --git a/lib/microwaveprop_web/live/user_profile_live.ex b/lib/microwaveprop_web/live/user_profile_live.ex index 738af46a..5307556f 100644 --- a/lib/microwaveprop_web/live/user_profile_live.ex +++ b/lib/microwaveprop_web/live/user_profile_live.ex @@ -19,13 +19,15 @@ defmodule MicrowavepropWeb.UserProfileLive do user -> contacts = Radio.list_contacts_for_user(user) beacons = Beacons.list_beacons_for_user(user) + involving = Radio.list_contacts_involving_callsign(user.callsign) {:ok, assign(socket, page_title: user.callsign, profile: user, contacts: contacts, - beacons: beacons + beacons: beacons, + involving: involving )} end end @@ -106,7 +108,7 @@ defmodule MicrowavepropWeb.UserProfileLive do -
+

Beacons

<%= if @beacons == [] do %> @@ -150,6 +152,44 @@ defmodule MicrowavepropWeb.UserProfileLive do <% end %>
+ +
+
+

Contacts {@profile.callsign} is in

+ <%= if @involving == [] do %> +

No contacts on file with this callsign yet.

+ <% else %> +
+ + + + + + + + + + + + + + + + + + + +
WhenStationsBandModeDistance
+ {Calendar.strftime(contact.qso_timestamp, "%Y-%m-%d %H:%M UTC")} + + <.link navigate={~p"/contacts/#{contact.id}"} class="link link-hover font-mono"> + {contact.station1} ↔ {contact.station2} + + {contact.band && "#{contact.band} MHz"}{contact.mode}{contact.distance_km && "#{contact.distance_km} km"}
+
+ <% end %> +
+
""" end diff --git a/test/microwaveprop/radio_test.exs b/test/microwaveprop/radio_test.exs index c1569cdb..d1f912ef 100644 --- a/test/microwaveprop/radio_test.exs +++ b/test/microwaveprop/radio_test.exs @@ -498,4 +498,59 @@ defmodule Microwaveprop.RadioTest do assert Radio.list_contacts_for_user(user) == [] end end + + describe "list_contacts_involving_callsign/1" do + test "returns contacts where the callsign is station1" do + match = create_contact(%{station1: "W5ISP", station2: "K5OTR"}) + _other = create_contact(%{station1: "W5OTH", station2: "K5OTR"}) + + assert [found] = Radio.list_contacts_involving_callsign("W5ISP") + assert found.id == match.id + end + + test "returns contacts where the callsign is station2" do + match = create_contact(%{station1: "W5OTH", station2: "W5ISP"}) + _other = create_contact(%{station1: "W5OTH", station2: "K5OTR"}) + + assert [found] = Radio.list_contacts_involving_callsign("W5ISP") + assert found.id == match.id + end + + test "matching is case-insensitive" do + match = create_contact(%{station1: "W5ISP", station2: "K5OTR"}) + + assert [found] = Radio.list_contacts_involving_callsign("w5isp") + assert found.id == match.id + end + + test "orders newest first" do + _old = + create_contact(%{ + station1: "W5ISP", + station2: "K5A", + qso_timestamp: ~U[2020-01-01 00:00:00Z] + }) + + new = + create_contact(%{ + station1: "K5B", + station2: "W5ISP", + qso_timestamp: ~U[2026-06-15 12:00:00Z] + }) + + assert [first, _] = Radio.list_contacts_involving_callsign("W5ISP") + assert first.id == new.id + end + + test "returns empty list when no contacts match" do + create_contact(%{station1: "W5OTH", station2: "K5OTR"}) + + assert Radio.list_contacts_involving_callsign("W5ISP") == [] + end + + test "returns empty list for blank or nil callsign" do + assert Radio.list_contacts_involving_callsign("") == [] + assert Radio.list_contacts_involving_callsign(nil) == [] + end + end end