feat: add Accept: text/markdown content negotiation for agent compatibility

Intercepts requests with Accept: text/markdown at endpoint level (before
the router's :accepts plug rejects them) and returns hardcoded markdown
representations for /, /privacy, and /terms.
This commit is contained in:
Graham McIntire 2026-04-17 14:14:31 -05:00
parent 42480144da
commit ca041fe5aa
3 changed files with 76 additions and 0 deletions

View file

@ -117,5 +117,6 @@ defmodule ToweropsWeb.Endpoint do
plug Phoenix.Ecto.SQL.Sandbox
end
plug ToweropsWeb.Plugs.MarkdownNegotiation
plug ToweropsWeb.Router
end

View file

@ -0,0 +1,61 @@
defmodule ToweropsWeb.Plugs.MarkdownNegotiation do
@moduledoc false
import Plug.Conn
@markdown_content %{
"/" => """
# TowerOps
Network monitoring and alerting platform built for WISP and ISP operators.
See the business impact of every network event.
## Features
- Real-time network monitoring
- Subscriber impact analysis
- Revenue impact tracking
- Equipment management
- Multi-site support
## Get Started
- [Register](/users/register)
- [Log In](/users/log_in)
- [API Documentation](/docs/api)
- [Privacy Policy](/privacy)
- [Terms of Service](/terms)
""",
"/privacy" =>
"# Privacy Policy\n\nVisit [towerops.net/privacy](https://towerops.net/privacy) for the full privacy policy.\n",
"/terms" =>
"# Terms of Service\n\nVisit [towerops.net/terms](https://towerops.net/terms) for the full terms of service.\n"
}
def init(opts), do: opts
def call(conn, _opts) do
if accepts_markdown?(conn) do
case Map.get(@markdown_content, conn.request_path) do
nil -> conn
markdown -> send_markdown(conn, markdown)
end
else
conn
end
end
defp accepts_markdown?(conn) do
conn
|> get_req_header("accept")
|> Enum.any?(&String.contains?(&1, "text/markdown"))
end
defp send_markdown(conn, content) do
conn
|> put_resp_content_type("text/markdown")
|> send_resp(200, content)
|> halt()
end
end

View file

@ -65,4 +65,18 @@ defmodule ToweropsWeb.PageControllerTest do
assert response =~ "Terms of Service"
end
describe "GET / with Accept: text/markdown" do
test "returns markdown content when markdown is requested", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "text/markdown")
|> get(~p"/")
assert response(conn, 200)
assert get_resp_header(conn, "content-type") == ["text/markdown; charset=utf-8"]
body = response(conn, 200)
assert body =~ "# TowerOps"
end
end
end