Harden /submit route for production
- Defensive grid-to-coordinate resolution in create_qso (case instead of crash on match failure) - Log warning when Oban weather enqueue fails instead of silently dropping - Add 30s per-session submission cooldown to prevent rapid-fire submissions
This commit is contained in:
parent
4468fb3b61
commit
fe3c8812a0
2 changed files with 48 additions and 19 deletions
|
|
@ -137,17 +137,21 @@ defmodule Microwaveprop.Radio do
|
||||||
grid1 = Ecto.Changeset.get_change(changeset, :grid1)
|
grid1 = Ecto.Changeset.get_change(changeset, :grid1)
|
||||||
grid2 = Ecto.Changeset.get_change(changeset, :grid2)
|
grid2 = Ecto.Changeset.get_change(changeset, :grid2)
|
||||||
|
|
||||||
{:ok, {lat1, lon1}} = Maidenhead.to_latlon(grid1)
|
case {Maidenhead.to_latlon(grid1), Maidenhead.to_latlon(grid2)} do
|
||||||
{:ok, {lat2, lon2}} = Maidenhead.to_latlon(grid2)
|
{{:ok, {lat1, lon1}}, {:ok, {lat2, lon2}}} ->
|
||||||
|
distance = lat1 |> haversine_km(lon1, lat2, lon2) |> round() |> Decimal.new()
|
||||||
|
|
||||||
distance = lat1 |> haversine_km(lon1, lat2, lon2) |> round() |> Decimal.new()
|
changeset
|
||||||
|
|> Ecto.Changeset.put_change(:pos1, %{"lat" => lat1, "lon" => lon1})
|
||||||
|
|> Ecto.Changeset.put_change(:pos2, %{"lat" => lat2, "lon" => lon2})
|
||||||
|
|> Ecto.Changeset.put_change(:distance_km, distance)
|
||||||
|
|> Ecto.Changeset.put_change(:user_submitted, true)
|
||||||
|
|> Repo.insert()
|
||||||
|
|
||||||
changeset
|
_ ->
|
||||||
|> Ecto.Changeset.put_change(:pos1, %{"lat" => lat1, "lon" => lon1})
|
changeset = Ecto.Changeset.add_error(changeset, :grid1, "could not resolve grid to coordinates")
|
||||||
|> Ecto.Changeset.put_change(:pos2, %{"lat" => lat2, "lon" => lon2})
|
{:error, %{changeset | action: :insert}}
|
||||||
|> Ecto.Changeset.put_change(:distance_km, distance)
|
end
|
||||||
|> Ecto.Changeset.put_change(:user_submitted, true)
|
|
||||||
|> Repo.insert()
|
|
||||||
else
|
else
|
||||||
{:error, %{changeset | action: :insert}}
|
{:error, %{changeset | action: :insert}}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ defmodule MicrowavepropWeb.SubmitLive do
|
||||||
alias Microwaveprop.Radio.Qso
|
alias Microwaveprop.Radio.Qso
|
||||||
alias Microwaveprop.Workers.QsoWeatherEnqueueWorker
|
alias Microwaveprop.Workers.QsoWeatherEnqueueWorker
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
@band_options [
|
@band_options [
|
||||||
{"1296 MHz", "1296"},
|
{"1296 MHz", "1296"},
|
||||||
{"2304 MHz", "2304"},
|
{"2304 MHz", "2304"},
|
||||||
|
|
@ -28,7 +30,8 @@ defmodule MicrowavepropWeb.SubmitLive do
|
||||||
page_title: "Submit QSO",
|
page_title: "Submit QSO",
|
||||||
form: to_form(changeset),
|
form: to_form(changeset),
|
||||||
band_options: @band_options,
|
band_options: @band_options,
|
||||||
mode_options: @mode_options
|
mode_options: @mode_options,
|
||||||
|
submitted_at: nil
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -42,18 +45,40 @@ defmodule MicrowavepropWeb.SubmitLive do
|
||||||
{:noreply, assign(socket, form: to_form(changeset))}
|
{:noreply, assign(socket, form: to_form(changeset))}
|
||||||
end
|
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
|
def handle_event("save", %{"qso" => qso_params}, socket) do
|
||||||
case Radio.create_qso(qso_params) do
|
if recently_submitted?(socket) do
|
||||||
{:ok, qso} ->
|
{:noreply, put_flash(socket, :error, "Please wait before submitting another QSO.")}
|
||||||
Oban.insert(QsoWeatherEnqueueWorker.new(%{}))
|
else
|
||||||
|
case Radio.create_qso(qso_params) do
|
||||||
|
{:ok, qso} ->
|
||||||
|
case Oban.insert(QsoWeatherEnqueueWorker.new(%{})) do
|
||||||
|
{:ok, _job} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
{:noreply,
|
{:error, reason} ->
|
||||||
socket
|
Logger.warning("Failed to enqueue weather processing: #{inspect(reason)}")
|
||||||
|> put_flash(:info, "QSO submitted successfully!")
|
end
|
||||||
|> push_navigate(to: ~p"/qsos/#{qso.id}")}
|
|
||||||
|
|
||||||
{:error, changeset} ->
|
{:noreply,
|
||||||
{:noreply, assign(socket, form: to_form(changeset))}
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue