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 %>
+
+
+
+
+ | When |
+ Stations |
+ Band |
+ Mode |
+ Distance |
+
+
+
+
+ |
+ {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