Files
MoundHunters/lib/mound_hunters_web/plugs/bounds_check.ex
2026-01-24 10:23:37 +01:00

76 lines
1.9 KiB
Elixir

defmodule MoundHuntersWeb.Plugs.BoundsCheck do
@moduledoc """
Plug to validate that coordinates are within Ohio boundaries.
Applies to tile requests but not to geometry sharing API.
"""
import Plug.Conn
require Logger
def init(opts), do: opts
def call(conn, _opts) do
# Skip bounds check for non-tile endpoints
if skip_bounds_check?(conn.request_path) do
conn
else
check_bounds(conn)
end
end
defp skip_bounds_check?(path) do
# Skip bounds check for geometry API and static files
String.starts_with?(path, "/api/share") or
String.starts_with?(path, "/static/") or
path == "/"
end
defp check_bounds(conn) do
cond do
# Check query params for tile request
conn.query_params["lat"] != nil and conn.query_params["lng"] != nil ->
check_query_params(conn)
# For tile file serving, we assume tiles in storage are valid
# (they were validated when created)
String.starts_with?(conn.request_path, "/tiles/") ->
conn
true ->
conn
end
end
defp check_query_params(conn) do
with {:ok, lat} <- parse_float(conn.query_params["lat"]),
{:ok, lng} <- parse_float(conn.query_params["lng"]),
:ok <- MoundHunters.Boundary.check_bounds(lat, lng) do
conn
else
{:error, :invalid_number} ->
send_error(conn, 400, "Invalid coordinate format")
{:error, reason} ->
send_error(conn, 400, reason)
end
end
defp parse_float(str) when is_binary(str) do
case Float.parse(str) do
{num, ""} -> {:ok, num}
_ -> {:error, :invalid_number}
end
end
defp parse_float(_), do: {:error, :invalid_number}
defp send_error(conn, status, message) do
Logger.warning("Bounds check failed: #{message}")
conn
|> put_private(:error_message, message)
|> put_resp_content_type("application/json")
|> send_resp(status, Jason.encode!(%{error: message}))
|> halt()
end
end