prop/lib/microwaveprop_web/live/submit_live.ex
Graham McIntire 40d1fa03aa
QSO submission triggers enrichment directly, fix prod badarith crash
- Add enqueue_for_qso/1 to directly enqueue weather/HRRR/terrain/IEMRE
  jobs for a single user-submitted QSO (no cron, no bulk processing)
- Submit flow calls enqueue_for_qso instead of generic enqueue worker
- Add enrichment queues to prod config for on-demand processing
- Guard against HRRR fill values in store_hrrr_profiles (fixes badarith)
- Filter QSOs without pos2 in build_terrain_jobs
2026-03-31 12:29:39 -05:00

142 lines
4 KiB
Elixir

defmodule MicrowavepropWeb.SubmitLive do
@moduledoc false
use MicrowavepropWeb, :live_view
alias Microwaveprop.Radio
alias Microwaveprop.Radio.Qso
alias Microwaveprop.Workers.QsoWeatherEnqueueWorker
@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} ->
QsoWeatherEnqueueWorker.enqueue_for_qso(qso)
{: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"""
<Layouts.app flash={@flash}>
<.header>
Submit QSO
<:subtitle>Submit a microwave QSO for propagation analysis</:subtitle>
</.header>
<.form for={@form} id="qso-form" phx-change="validate" phx-submit="save" class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-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"
/>
</div>
<p class="text-sm text-base-content/60 -mt-2">
Be as specific as possible with grid squares (6 characters preferred, e.g. EM12kp).
</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<.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)" />
</div>
<.input
field={@form[:submitter_email]}
type="email"
label="Your Email"
placeholder="you@example.com"
/>
<div class="mt-6">
<.button phx-disable-with="Submitting..." class="btn-primary">Submit QSO</.button>
</div>
</.form>
</Layouts.app>
"""
end
end