defmodule MicrowavepropWeb.ApiCatalogController do @moduledoc """ RFC 9727 API catalog + companion OpenAPI spec for automated API discovery. `/api/contacts/map` is currently the only public JSON endpoint. The catalog lists it with link relations for `service-desc` (OpenAPI) and `status` (health check). If more endpoints are added, extend `linkset_entries/1` and the OpenAPI `paths` map. """ use MicrowavepropWeb, :controller @openapi_type "application/vnd.oai.openapi+json;version=3.0" @spec catalog(Plug.Conn.t(), map()) :: Plug.Conn.t() def catalog(conn, _params) do body = Jason.encode!(%{linkset: linkset_entries(conn)}) conn |> put_resp_content_type("application/linkset+json") |> put_resp_header("cache-control", "public, max-age=3600") |> send_resp(200, body) end @spec openapi(Plug.Conn.t(), map()) :: Plug.Conn.t() def openapi(conn, _params) do body = Jason.encode!(openapi_spec(conn)) conn |> put_resp_content_type("application/json") |> put_resp_header("cache-control", "public, max-age=3600") |> send_resp(200, body) end defp linkset_entries(conn) do [ %{ anchor: url(conn, ~p"/api/contacts/map"), "service-desc": [%{href: url(conn, ~p"/openapi.json"), type: @openapi_type}], status: [%{href: url(conn, ~p"/health")}] } ] end defp openapi_spec(conn) do %{ openapi: "3.0.3", info: %{ title: "Microwaveprop Public API", description: "Public JSON endpoints for the NTMS microwave propagation prediction service.", version: "1.0.0" }, servers: [%{url: url(conn, ~p"/")}], paths: %{ "/api/contacts/map" => %{ get: %{ summary: "All recorded amateur-radio contacts as compact tuples", description: "Returns every contact in the calibration dataset as an array of fixed-shape tuples. " <> "Response is pre-gzipped when the client sends Accept-Encoding: gzip. " <> "Tuple shape: [lat1, lon1, lat2, lon2, band_mhz, callsign1, callsign2, mode, distance_km, qso_timestamp, id].", responses: %{ "200" => %{ description: "Array of contact tuples", content: %{ "application/json" => %{ schema: %{ type: "array", items: %{ type: "array", minItems: 11, maxItems: 11 } } } } } } } } } } end end