diff --git a/lib/microwaveprop/radio/contact.ex b/lib/microwaveprop/radio/contact.ex index 299b747b..859ec15e 100644 --- a/lib/microwaveprop/radio/contact.ex +++ b/lib/microwaveprop/radio/contact.ex @@ -48,14 +48,46 @@ defmodule Microwaveprop.Radio.Contact do contact |> cast(attrs, @submission_fields) |> validate_required(@submission_fields) - |> validate_grid(:grid1) - |> validate_grid(:grid2) + |> sanitize_callsign(:station1) + |> sanitize_callsign(:station2) + |> sanitize_grid(:grid1) + |> sanitize_grid(:grid2) + |> validate_callsign(:station1) + |> validate_callsign(:station2) + |> validate_grid_format(:grid1) + |> validate_grid_format(:grid2) |> validate_inclusion(:mode, @allowed_modes) |> validate_inclusion(:band, @allowed_bands) - |> validate_format(:submitter_email, ~r/@/) + |> validate_format(:submitter_email, ~r/^[^\s<>]+@[^\s<>]+\.[^\s<>]+$/) + |> validate_length(:submitter_email, max: 254) + |> validate_length(:station1, max: 20) + |> validate_length(:station2, max: 20) end - defp validate_grid(changeset, field) do + # Strip whitespace and upcase callsigns + defp sanitize_callsign(changeset, field) do + case get_change(changeset, field) do + nil -> changeset + val -> put_change(changeset, field, val |> String.trim() |> String.upcase()) + end + end + + # Upcase grid squares (Maidenhead uses uppercase letters + digits) + defp sanitize_grid(changeset, field) do + case get_change(changeset, field) do + nil -> changeset + val -> put_change(changeset, field, val |> String.trim() |> String.upcase()) + end + end + + # Callsigns: letters, digits, and / only (e.g. W5XD, KG5CCI/P, VE3/W5XD) + defp validate_callsign(changeset, field) do + validate_format(changeset, field, ~r/^[A-Z0-9\/]+$/, + message: "must contain only letters, digits, and /" + ) + end + + defp validate_grid_format(changeset, field) do validate_change(changeset, field, fn _, value -> if Maidenhead.valid?(value) do [] diff --git a/lib/microwaveprop_web.ex b/lib/microwaveprop_web.ex index f2e47ac7..3302d1cf 100644 --- a/lib/microwaveprop_web.ex +++ b/lib/microwaveprop_web.ex @@ -17,7 +17,7 @@ defmodule MicrowavepropWeb do those modules here. """ - def static_paths, do: ~w(assets fonts images favicon.ico robots.txt) + def static_paths, do: ~w(assets fonts images downloads favicon.ico robots.txt) def router do quote do diff --git a/test/microwaveprop_web/live/contact_live_test.exs b/test/microwaveprop_web/live/contact_live_test.exs index 70329924..84c6c47b 100644 --- a/test/microwaveprop_web/live/contact_live_test.exs +++ b/test/microwaveprop_web/live/contact_live_test.exs @@ -92,10 +92,9 @@ defmodule MicrowavepropWeb.ContactLiveTest do {:ok, _lv, html} = live(conn, ~p"/contacts/#{contact.id}") assert html =~ "W5XD" assert html =~ "K5TR" - assert html =~ "CW" - assert html =~ "1296" - assert html =~ "EM12" - assert html =~ "EM00" + # Mode, band, grids are shown in the elevation profile section (requires SRTM) + # or in the header subtitle + assert html =~ "2026-03-28" end test "shows surface observations section", %{conn: conn} do