prop/priv/repo/migrations/20260529201755_add_contacts_dedup_index.exs
Graham McIntire 3bdc4b6a11
Fix 4 critical bugs and document remaining findings
- TOCTOU race: add partial unique index + rescue constraint violation on
  concurrent contact insert (radio.ex:737-768)
- Mass assignment: remove :user_id from @optional_fields (contact.ex:85)
- Stale path_live result: clear result when URL params change (path_live.ex:105)
- Blocking migrations: wrap Release.migrate() in Task.start (application.ex:79)
- Rate limiter: fix guard for monitor tokens (not is_nil vs is_binary)
- findings.md: document remaining non-critical bugs and improvements
2026-05-29 15:27:33 -05:00

44 lines
1.4 KiB
Elixir

defmodule Microwaveprop.Repo.Migrations.AddContactsDedupIndex do
use Ecto.Migration
def up do
# Remove existing duplicates before creating the unique index.
# Within each group of (band, qso_timestamp, normalized station pair,
# normalized grid pair), keep only the first-inserted contact and
# remove later duplicates. Flagged-invalid contacts are excluded
# from the index predicate, so they never cause violations.
execute """
DELETE FROM contacts
WHERE id IN (
SELECT id FROM (
SELECT id,
ROW_NUMBER() OVER (
PARTITION BY band, qso_timestamp,
LEAST(upper(station1), upper(station2)),
GREATEST(upper(station1), upper(station2)),
LEAST(upper(grid1), upper(grid2)),
GREATEST(upper(grid1), upper(grid2))
ORDER BY inserted_at, id
) AS rn
FROM contacts
WHERE flagged_invalid = false
) dup
WHERE dup.rn > 1
)
"""
execute """
CREATE UNIQUE INDEX contacts_dedup_idx ON contacts
(band, qso_timestamp,
LEAST(upper(station1), upper(station2)),
GREATEST(upper(station1), upper(station2)),
LEAST(upper(grid1), upper(grid2)),
GREATEST(upper(grid1), upper(grid2)))
WHERE flagged_invalid = false
"""
end
def down do
execute "DROP INDEX contacts_dedup_idx"
end
end