diff --git a/lib/aprsme_web/controllers/page_controller.ex b/lib/aprsme_web/controllers/page_controller.ex
index ee2d644..446be6c 100644
--- a/lib/aprsme_web/controllers/page_controller.ex
+++ b/lib/aprsme_web/controllers/page_controller.ex
@@ -60,6 +60,57 @@ defmodule AprsmeWeb.PageController do
end
end
+ @sitemap_paths ~w(/ /about /api /status /packets)
+
+ def sitemap(conn, _params) do
+ base_url = AprsmeWeb.Endpoint.url()
+
+ urls =
+ Enum.map(@sitemap_paths, fn path ->
+ " #{base_url}#{path}"
+ end)
+
+ xml = """
+
+
+ #{Enum.join(urls, "\n")}
+
+ """
+
+ conn
+ |> put_resp_content_type("application/xml")
+ |> send_resp(200, xml)
+ end
+
+ def api_catalog(conn, _params) do
+ base_url = AprsmeWeb.Endpoint.url()
+ ws_url = String.replace(base_url, ~r/^http/, "ws")
+
+ body = %{
+ linkset: [
+ %{
+ anchor: "#{base_url}/api/v1",
+ "service-doc": [%{href: "#{base_url}/api", type: "text/html"}],
+ status: [%{href: "#{base_url}/status.json", type: "application/json"}]
+ },
+ %{
+ anchor: "#{ws_url}/mobile/websocket",
+ "service-doc": [
+ %{
+ href: "https://github.com/gmcintire/aprs.me/blob/main/docs/mobile-api.md",
+ type: "text/markdown"
+ }
+ ],
+ status: [%{href: "#{base_url}/status.json", type: "application/json"}]
+ }
+ ]
+ }
+
+ conn
+ |> put_resp_content_type("application/linkset+json")
+ |> send_resp(200, Jason.encode!(body))
+ end
+
def ready(conn, _params) do
# Simple readiness check without database dependency
# This is faster for startup health checks
diff --git a/lib/aprsme_web/plugs/agent_discovery_links.ex b/lib/aprsme_web/plugs/agent_discovery_links.ex
new file mode 100644
index 0000000..0056d13
--- /dev/null
+++ b/lib/aprsme_web/plugs/agent_discovery_links.ex
@@ -0,0 +1,17 @@
+defmodule AprsmeWeb.Plugs.AgentDiscoveryLinks do
+ @moduledoc false
+
+ @behaviour Plug
+
+ import Plug.Conn
+
+ @link_header ~s(; rel="api-catalog", ; rel="service-doc")
+
+ @impl true
+ def init(opts), do: opts
+
+ @impl true
+ def call(conn, _opts) do
+ put_resp_header(conn, "link", @link_header)
+ end
+end
diff --git a/lib/aprsme_web/router.ex b/lib/aprsme_web/router.ex
index 79b2c3e..011eba5 100644
--- a/lib/aprsme_web/router.ex
+++ b/lib/aprsme_web/router.ex
@@ -29,6 +29,7 @@ defmodule AprsmeWeb.Router do
plug AprsmeWeb.Plugs.SetLocale
plug IPGeolocation
plug RateLimiter, scale: 60_000, limit: 200
+ plug AprsmeWeb.Plugs.AgentDiscoveryLinks
end
pipeline :public_api do
@@ -71,6 +72,12 @@ defmodule AprsmeWeb.Router do
get "/status.json", PageController, :status_json
end
+ # Agent/crawler discovery endpoints
+ scope "/", AprsmeWeb do
+ get "/sitemap.xml", PageController, :sitemap
+ get "/.well-known/api-catalog", PageController, :api_catalog
+ end
+
## Authentication routes
scope "/", AprsmeWeb do
diff --git a/priv/static/robots.txt b/priv/static/robots.txt
index 26e06b5..8360d08 100644
--- a/priv/static/robots.txt
+++ b/priv/static/robots.txt
@@ -3,3 +3,5 @@
# To ban all spiders from the entire site uncomment the next two lines:
# User-agent: *
# Disallow: /
+
+Sitemap: https://aprs.me/sitemap.xml