defmodule MoundHuntersWeb.ApiController do @moduledoc """ API endpoints for sharing geometries. """ import Plug.Conn require Logger @doc """ Create a new shared geometry. POST /api/share Body: {"geojson": {...}} Returns: {"id": "abc12345"} """ def create_share(conn) do with {:ok, body, conn} <- read_body(conn), {:ok, params} <- Jason.decode(body), geojson when is_map(geojson) <- params["geojson"], geojson_str <- Jason.encode!(geojson), {:ok, %{id: id}} <- MoundHunters.Repo.create_geometry(geojson_str) do conn |> put_resp_content_type("application/json") |> put_resp_header("cache-control", "no-cache") |> send_resp(200, Jason.encode!(%{id: id})) else {:error, :invalid_json} -> send_error(conn, 400, "Invalid JSON") nil -> send_error(conn, 400, "Missing geojson field") {:error, reason} -> Logger.error("Failed to create geometry: #{inspect(reason)}") send_error(conn, 500, "Failed to create geometry") _ -> send_error(conn, 400, "Invalid request") end end @doc """ Get a shared geometry by ID. GET /api/share/:id Returns: {"geojson": {...}} """ def get_share(conn) do geometry_id = conn.path_params["id"] case MoundHunters.Repo.get_geometry(geometry_id) do {:ok, geometry} -> geojson = Jason.decode!(geometry.geojson) conn |> put_resp_content_type("application/json") |> put_resp_header("cache-control", "no-cache") |> send_resp(200, Jason.encode!(%{geojson: geojson})) {:error, :not_found} -> send_error(conn, 404, "Geometry not found") {:error, reason} -> Logger.error("Failed to get geometry: #{inspect(reason)}") send_error(conn, 500, "Failed to retrieve geometry") end end @doc """ Get tile metadata by ID. GET /api/meta/tile/:id Returns: tile metadata including bounds, status, availability flags """ def get_tile(conn) do tile_id = conn.path_params["id"] case MoundHunters.Repo.get_tile(tile_id) do {:ok, tile} -> conn |> put_resp_content_type("application/json") |> put_resp_header("cache-control", "public, max-age=60") |> send_resp(200, Jason.encode!(tile)) {:error, :not_found} -> send_error(conn, 404, "Tile not found") {:error, reason} -> Logger.error("Failed to get tile: #{inspect(reason)}") send_error(conn, 500, "Failed to retrieve tile") end end @doc """ Get tile metadata by coordinates. GET /api/meta/tile?lat=40.0&lng=-82.5 Returns: tile metadata for the tile containing the given coordinates """ def get_tile_by_coords(conn) do with lat_str when is_binary(lat_str) <- conn.query_params["lat"], lng_str when is_binary(lng_str) <- conn.query_params["lng"], {lat, _} <- Float.parse(lat_str), {lng, _} <- Float.parse(lng_str) do case MoundHunters.Repo.get_tile_at_coords(lat, lng) do {:ok, tile} -> conn |> put_resp_content_type("application/json") |> put_resp_header("cache-control", "public, max-age=60") |> send_resp(200, Jason.encode!(tile)) {:error, :not_found} -> send_error(conn, 404, "No tile found at coordinates") {:error, reason} -> Logger.error("Failed to get tile by coords: #{inspect(reason)}") send_error(conn, 500, "Failed to retrieve tile") end else nil -> send_error(conn, 400, "Missing lat or lng parameter") :error -> send_error(conn, 400, "Invalid lat or lng format") _ -> send_error(conn, 400, "Invalid request") end end defp send_error(conn, status, message) do conn |> put_private(:error_message, message) |> put_resp_content_type("application/json") |> send_resp(status, Jason.encode!(%{error: message})) end end