aprs.me/lib/aprsme_web/router.ex
Graham McIntire a796f8a3a9
Add Weather API controller with parameter validation
Implements GET /api/v1/weather/nearby endpoint with comprehensive parameter
validation and error handling. The controller validates required parameters
(lat, lon, radius) and optional parameters (hours, limit) with appropriate
range checking. Integrates with PreparedQueries for efficient database access
and WeatherJSON for response serialization.

Key features:
- Required params: lat (-90 to 90), lon (-180 to 180), radius (0-1000 miles)
- Optional params: hours (1-168, default 6), limit (1-100, default 50)
- Proper error responses via FallbackController (400 for bad requests, 422 for validation)
- Full test coverage (16 tests) including edge cases and error scenarios

Also updates FallbackController to handle {:error, status, message} tuples
and fixes WeatherJSON to support both field name formats (lat/lon and latitude/longitude)
for compatibility with existing tests and actual query results.
2026-03-22 11:27:45 -05:00

135 lines
4.8 KiB
Elixir

defmodule AprsmeWeb.Router do
use AprsmeWeb, :router
use ErrorTracker.Web, :router
import AprsmeWeb.UserAuth
import Phoenix.LiveDashboard.Router
alias AprsmeWeb.Plugs.IPGeolocation
alias AprsmeWeb.Plugs.RateLimiter
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {AprsmeWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers, %{
"content-security-policy" =>
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.sentry-cdn.com https://unpkg.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: https: http: blob:; font-src 'self' data:; connect-src 'self' wss: https://*.ingest.sentry.io https://*.sentry.io https://nominatim.openstreetmap.org https://tile.openstreetmap.org https://*.tile.openstreetmap.org https://*.tile.openstreetmap.de https://*.basemaps.cartocdn.com; media-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; frame-src 'self'; manifest-src 'self'; worker-src 'self' blob:"
}
plug :fetch_current_user
plug AprsmeWeb.Plugs.SetLocale
plug IPGeolocation
plug RateLimiter, scale: 60_000, limit: 200
end
pipeline :public_api do
plug :accepts, ["json"]
plug RateLimiter, scale: 60_000, limit: 100
end
pipeline :accepts_json do
plug :accepts, ["json"]
end
pipeline :api do
plug :accepts, ["json"]
plug RateLimiter, scale: 60_000, limit: 100
plug AprsmeWeb.Plugs.ApiCSRF
end
scope "/", AprsmeWeb do
pipe_through [:browser, :require_authenticated_user]
live_dashboard "/dashboard", metrics: AprsmeWeb.Telemetry
error_tracker_dashboard("/errors")
end
scope "/", AprsmeWeb do
pipe_through [:browser]
delete "/users/log_out", UserSessionController, :delete
live_session :current_user,
on_mount: [{AprsmeWeb.UserAuth, :mount_current_user}, {AprsmeWeb.LocaleHook, :set_locale}] do
live "/users/confirm/:token", UserConfirmationLive, :edit
live "/users/confirm", UserConfirmationInstructionsLive, :new
end
end
# Health/readiness routes — no rate limiting to avoid false K8s probe failures
scope "/", AprsmeWeb do
pipe_through :accepts_json
get "/ready", PageController, :ready
get "/status.json", PageController, :status_json
end
## Authentication routes
scope "/", AprsmeWeb do
pipe_through [:browser, :redirect_if_user_is_authenticated]
live_session :redirect_if_user_is_authenticated,
on_mount: [{AprsmeWeb.UserAuth, :redirect_if_user_is_authenticated}, {AprsmeWeb.LocaleHook, :set_locale}] do
live "/users/register", UserRegistrationLive, :new
live "/users/log_in", UserLoginLive, :new
live "/users/reset_password", UserForgotPasswordLive, :new
live "/users/reset_password/:token", UserResetPasswordLive, :edit
end
post "/users/log_in", UserSessionController, :create
end
scope "/", AprsmeWeb do
pipe_through [:browser, :require_authenticated_user]
live_session :require_authenticated_user,
on_mount: [{AprsmeWeb.UserAuth, :ensure_authenticated}, {AprsmeWeb.LocaleHook, :set_locale}] do
live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
end
end
scope "/", AprsmeWeb do
pipe_through :browser
live_session :regular_pages,
on_mount: [{AprsmeWeb.UserAuth, :mount_current_user}, {AprsmeWeb.LocaleHook, :set_locale}] do
live "/status", StatusLive.Index, :index
live "/packets", PacketsLive.Index, :index
live "/packets/:callsign", PacketsLive.CallsignView, :index
live "/badpackets", BadPacketsLive.Index, :index
live "/weather/:callsign", WeatherLive.CallsignView, :index
live "/about", AboutLive, :index
live "/api", ApiDocsLive, :index
live "/info/:callsign", InfoLive.Show, :show
live "/", MapLive.Index, :index
live "/:callsign", MapLive.Index, :index
end
end
# API v1 routes
scope "/api/v1", AprsmeWeb.Api.V1, as: :api_v1 do
pipe_through :api
get "/callsign/:callsign", CallsignController, :show
get "/weather/nearby", WeatherController, :nearby
end
# Enable LiveDashboard and Swoosh mailbox preview in development
if Application.compile_env(:aprsme, :dev_routes) do
# If you want to use the LiveDashboard in production, you should put
# it behind authentication and allow only admins to access it.
# If your application does not have an admins-only section yet,
# you can use Plug.BasicAuth to set up some basic authentication
# as long as you are also using SSL (which you should anyway).
scope "/dev" do
pipe_through :browser
forward "/mailbox", Plug.Swoosh.MailboxPreview
end
end
end