refactor: DRY extract_bearer, reuse validation helpers, add query limits

- Extract shared extract_bearer/1 from api/monitor_auth.ex → api/auth.ex
- Refactor admin_changeset to pipeline through validate_callsign/validate_name/validate_email
- Add limit: 500 to list_beacons/0 and candidate_rover_locations/1
- Combine get_mission_with_paths preloads into single round-trip
This commit is contained in:
Graham McInitre 2026-07-21 10:06:12 -05:00
parent d3a7cfe733
commit d30b6d3022
5 changed files with 20 additions and 24 deletions

View file

@ -82,15 +82,9 @@ defmodule Microwaveprop.Accounts.User do
user
|> cast(attrs, [:callsign, :name, :email, :is_admin])
|> update_change(:callsign, fn cs -> cs && cs |> String.trim() |> String.upcase() end)
|> validate_required([:callsign, :name, :email])
|> validate_format(:callsign, ~r/^[A-Z0-9]{3,10}$/, message: "must be 3-10 letters and digits")
|> validate_length(:name, min: 1, max: 100)
|> validate_format(:email, ~r/^[^@,;\s]+@[^@,;\s]+$/, message: "must have the @ sign and no spaces")
|> validate_length(:email, max: 160)
|> unsafe_validate_unique(:callsign, Microwaveprop.Repo)
|> unique_constraint(:callsign)
|> unsafe_validate_unique(:email, Microwaveprop.Repo)
|> unique_constraint(:email)
|> validate_callsign([])
|> validate_name()
|> validate_email([])
end
defp validate_callsign(changeset, opts) do

View file

@ -35,7 +35,8 @@ defmodule Microwaveprop.Beacons do
Repo.all(
from b in Beacon,
where: b.approved == true,
order_by: [desc: b.inserted_at]
order_by: [desc: b.inserted_at],
limit: 500
)
end

View file

@ -45,9 +45,14 @@ defmodule Microwaveprop.RoverPlanning do
@spec get_mission_with_paths(Ecto.UUID.t()) :: Mission.t() | nil | [%{atom() => any()}]
def get_mission_with_paths(id) do
case get_mission(id) do
nil -> nil
mission -> Repo.preload(mission, paths: [:rover_location, :station])
case Ecto.UUID.cast(id) do
{:ok, uuid} ->
Mission
|> Repo.get(uuid)
|> Repo.preload([:user, stations: from(s in Station, order_by: s.position), paths: [:rover_location, :station]])
:error ->
nil
end
end
@ -245,11 +250,11 @@ defmodule Microwaveprop.RoverPlanning do
"""
@spec candidate_rover_locations(Mission.t()) :: [Location.t()]
def candidate_rover_locations(%Mission{only_known_good: true}) do
Repo.all(from(l in Location, where: l.status == :good, order_by: [desc: l.inserted_at]))
Repo.all(from(l in Location, where: l.status == :good, order_by: [desc: l.inserted_at], limit: 500))
end
def candidate_rover_locations(%Mission{only_known_good: false}) do
Repo.all(from(l in Location, order_by: [desc: l.inserted_at]))
Repo.all(from(l in Location, order_by: [desc: l.inserted_at], limit: 500))
end
defp insert_pending_paths_and_jobs([], _mission), do: :ok

View file

@ -68,7 +68,9 @@ defmodule MicrowavepropWeb.Api.Auth do
end
end
defp extract_bearer(conn) do
@doc false
@spec extract_bearer(Plug.Conn.t()) :: {:ok, String.t()} | {:error, :missing_token | :invalid_authorization_header}
def extract_bearer(conn) do
case get_req_header(conn, "authorization") do
["Bearer " <> token] when byte_size(token) > 0 -> {:ok, token}
["bearer " <> token] when byte_size(token) > 0 -> {:ok, token}

View file

@ -22,6 +22,7 @@ defmodule MicrowavepropWeb.Api.MonitorAuth do
import Plug.Conn
alias Microwaveprop.BeaconMonitors
alias MicrowavepropWeb.Api.Auth
alias MicrowavepropWeb.Api.ErrorJSON
@impl true
@ -44,14 +45,7 @@ defmodule MicrowavepropWeb.Api.MonitorAuth do
end
end
defp extract_bearer(conn) do
case get_req_header(conn, "authorization") do
["Bearer " <> token] when byte_size(token) > 0 -> {:ok, token}
["bearer " <> token] when byte_size(token) > 0 -> {:ok, token}
[] -> {:error, :missing_token}
_ -> {:error, :invalid_authorization_header}
end
end
defp extract_bearer(conn), do: Auth.extract_bearer(conn)
defp assign_monitor(conn, monitor) do
# The rate-limiter buckets on `current_api_token.id`. Map the