From 25c703401bd0dc5e71d7d2d75e4cc8d576936d29 Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 11 Apr 2026 17:30:37 -0500 Subject: [PATCH] Link to existing contact on duplicate submission Return the conflicting contact from create_contact so the UI can redirect to it with a flash message instead of just showing a form error. --- lib/microwaveprop/radio.ex | 26 +++++++++---------- lib/microwaveprop_web/live/submit_live.ex | 9 +++++++ .../radio/contact_submission_test.exs | 12 ++++----- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/microwaveprop/radio.ex b/lib/microwaveprop/radio.ex index dc6fbb3d..fa9c9bac 100644 --- a/lib/microwaveprop/radio.ex +++ b/lib/microwaveprop/radio.ex @@ -338,11 +338,8 @@ defmodule Microwaveprop.Radio do grid1 = Ecto.Changeset.get_change(changeset, :grid1) grid2 = Ecto.Changeset.get_change(changeset, :grid2) - if duplicate_contact_exists?(changeset) do - changeset = - Ecto.Changeset.add_error(changeset, :station1, "a matching contact already exists within the last hour") - - {:error, %{changeset | action: :insert}} + if existing = find_duplicate_contact(changeset) do + {:error, :duplicate, existing} else case {Maidenhead.to_latlon(grid1), Maidenhead.to_latlon(grid2)} do {{:ok, {lat1, lon1}}, {:ok, {lat2, lon2}}} -> @@ -365,7 +362,7 @@ defmodule Microwaveprop.Radio do end end - defp duplicate_contact_exists?(changeset) do + defp find_duplicate_contact(changeset) do s1 = Ecto.Changeset.get_field(changeset, :station1) |> to_string() |> String.upcase() s2 = Ecto.Changeset.get_field(changeset, :station2) |> to_string() |> String.upcase() g1 = Ecto.Changeset.get_field(changeset, :grid1) |> to_string() |> String.upcase() @@ -377,8 +374,8 @@ defmodule Microwaveprop.Radio do min_ts = DateTime.add(ts, -@dedup_window_seconds, :second) max_ts = DateTime.add(ts, @dedup_window_seconds, :second) - # Query for contacts with same band in the time window, then check - # station/grid pair match (order-independent) in Elixir + expected_pair = Enum.sort([{s1, g1}, {s2, g2}]) + from(c in Contact, where: c.band == ^band_decimal and @@ -387,11 +384,14 @@ defmodule Microwaveprop.Radio do c.flagged_invalid == false ) |> Repo.all() - |> Enum.any?(fn c -> - a = {String.upcase(c.station1), String.upcase(c.grid1)} - b = {String.upcase(c.station2), String.upcase(c.grid2)} - pair = Enum.sort([a, b]) - pair == Enum.sort([{s1, g1}, {s2, g2}]) + |> Enum.find(fn c -> + pair = + Enum.sort([ + {String.upcase(c.station1), String.upcase(c.grid1)}, + {String.upcase(c.station2), String.upcase(c.grid2)} + ]) + + pair == expected_pair end) end diff --git a/lib/microwaveprop_web/live/submit_live.ex b/lib/microwaveprop_web/live/submit_live.ex index 87b0b8b2..1cb5efde 100644 --- a/lib/microwaveprop_web/live/submit_live.ex +++ b/lib/microwaveprop_web/live/submit_live.ex @@ -80,6 +80,15 @@ defmodule MicrowavepropWeb.SubmitLive do |> put_flash(:info, "Contact submitted successfully!") |> push_navigate(to: ~p"/contacts/#{contact.id}")} + {:error, :duplicate, existing} -> + {:noreply, + socket + |> put_flash( + :error, + "A matching contact already exists: #{existing.station1} ↔ #{existing.station2} on #{existing.band} MHz" + ) + |> push_navigate(to: ~p"/contacts/#{existing.id}")} + {:error, changeset} -> {:noreply, assign(socket, form: to_form(changeset))} end diff --git a/test/microwaveprop/radio/contact_submission_test.exs b/test/microwaveprop/radio/contact_submission_test.exs index 54db87b4..de1d01c5 100644 --- a/test/microwaveprop/radio/contact_submission_test.exs +++ b/test/microwaveprop/radio/contact_submission_test.exs @@ -96,11 +96,10 @@ defmodule Microwaveprop.Radio.ContactSubmissionTest do describe "create_contact/1 deduplication" do alias Microwaveprop.Radio - test "rejects duplicate contact within 1-hour window" do - assert {:ok, _contact} = Radio.create_contact(@valid_attrs) + test "rejects duplicate and returns the existing contact" do + assert {:ok, original} = Radio.create_contact(@valid_attrs) - assert {:error, changeset} = Radio.create_contact(@valid_attrs) - assert "a matching contact already exists within the last hour" in errors_on(changeset).station1 + assert {:error, :duplicate, ^original} = Radio.create_contact(@valid_attrs) end test "allows same stations on a different band" do @@ -121,11 +120,10 @@ defmodule Microwaveprop.Radio.ContactSubmissionTest do end test "detects duplicates regardless of station order" do - assert {:ok, _} = Radio.create_contact(@valid_attrs) + assert {:ok, original} = Radio.create_contact(@valid_attrs) swapped = %{@valid_attrs | station1: "K5TR", grid1: "EM00", station2: "W5XD", grid2: "EM12"} - assert {:error, changeset} = Radio.create_contact(swapped) - assert "a matching contact already exists within the last hour" in errors_on(changeset).station1 + assert {:error, :duplicate, ^original} = Radio.create_contact(swapped) end end end