defmodule MicrowavepropWeb.SubmitLive do @moduledoc false use MicrowavepropWeb, :live_view alias Microwaveprop.Radio alias Microwaveprop.Radio.Qso alias Microwaveprop.Workers.QsoWeatherEnqueueWorker require Logger @band_options [ {"1296 MHz", "1296"}, {"2304 MHz", "2304"}, {"3456 MHz", "3456"}, {"5760 MHz", "5760"}, {"10 GHz", "10000"}, {"24 GHz", "24000"}, {"47 GHz", "47000"}, {"76 GHz", "75000"} ] @mode_options ~w(CW SSB FM FT8 FT4) @impl true def mount(_params, _session, socket) do changeset = Radio.change_qso(%Qso{}) {:ok, assign(socket, page_title: "Submit QSO", form: to_form(changeset), band_options: @band_options, mode_options: @mode_options, submitted_at: nil )} end @impl true def handle_event("validate", %{"qso" => qso_params}, socket) do changeset = %Qso{} |> Radio.change_qso(qso_params) |> Map.put(:action, :validate) {:noreply, assign(socket, form: to_form(changeset))} end # Minimum milliseconds between submissions per LiveView session. # For broader IP-based rate limiting, use a reverse proxy (nginx limit_req, Cloudflare). @submission_cooldown_ms 30_000 def handle_event("save", %{"qso" => qso_params}, socket) do if recently_submitted?(socket) do {:noreply, put_flash(socket, :error, "Please wait before submitting another QSO.")} else case Radio.create_qso(qso_params) do {:ok, qso} -> case Oban.insert(QsoWeatherEnqueueWorker.new(%{})) do {:ok, _job} -> :ok {:error, reason} -> Logger.warning("Failed to enqueue weather processing: #{inspect(reason)}") end {:noreply, socket |> assign(submitted_at: System.monotonic_time(:millisecond)) |> put_flash(:info, "QSO submitted successfully!") |> push_navigate(to: ~p"/qsos/#{qso.id}")} {:error, changeset} -> {:noreply, assign(socket, form: to_form(changeset))} end end end defp recently_submitted?(socket) do case socket.assigns.submitted_at do nil -> false ts -> System.monotonic_time(:millisecond) - ts < @submission_cooldown_ms end end @impl true def render(assigns) do ~H""" <.header> Submit QSO <:subtitle>Submit a microwave QSO for propagation analysis <.form for={@form} id="qso-form" phx-change="validate" phx-submit="save" class="space-y-4">
<.input field={@form[:station1]} type="text" label="Station 1" placeholder="W5XD" /> <.input field={@form[:grid1]} type="text" label="Grid 1" placeholder="EM12kp" phx-debounce="blur" /> <.input field={@form[:station2]} type="text" label="Station 2" placeholder="K5TR" /> <.input field={@form[:grid2]} type="text" label="Grid 2" placeholder="EM00cd" phx-debounce="blur" />

Be as specific as possible with grid squares (6 characters preferred, e.g. EM12kp).

<.input field={@form[:band]} type="select" label="Band" prompt="Select band" options={@band_options} /> <.input field={@form[:mode]} type="select" label="Mode" prompt="Select mode" options={@mode_options} /> <.input field={@form[:qso_timestamp]} type="datetime-local" label="Timestamp (UTC)" />
<.input field={@form[:submitter_email]} type="email" label="Your Email" placeholder="you@example.com" />
<.button phx-disable-with="Submitting..." class="btn-primary">Submit QSO
""" end end