lib/openagents_web/controllers/agent_front_door_controller.ex

main at 58e6347eeb72 · 1 KB

defmodule OpenAgentsWeb.AgentFrontDoorController do
  @moduledoc """
  Serves the agent front door in its two representations.

  `/agents.md` is the address episode 230 gave for standing instructions to
  agents, and `/agents.json` is the same contract as data. Both come from
  `OpenAgentsWeb.ContributionContract`, so a reader who follows the Markdown
  and a client that parses the JSON are following one document.

  The origin the request arrived on is the base URL both representations
  describe, so a staging deployment tells the truth about staging rather than
  advertising production.
  """

  use OpenAgentsWeb, :controller

  alias OpenAgentsWeb.ContributionContract

  def markdown(conn, _params) do
    conn
    |> send_contract("text/markdown; charset=utf-8", ContributionContract.markdown(base(conn)))
  end

  def json(conn, _params) do
    body = conn |> base() |> ContributionContract.document() |> Jason.encode!()

    send_contract(conn, "application/json; charset=utf-8", body)
  end

  defp send_contract(conn, content_type, body) do
    conn
    |> put_resp_content_type_string(content_type)
    |> put_resp_header("cache-control", "public, max-age=300")
    |> put_resp_header("etag", ~s("#{sha256(body)}"))
    |> send_resp(:ok, body)
  end

  # `put_resp_content_type/2` appends its own charset and rejects a type that
  # already carries one, so the header is set directly.
  defp put_resp_content_type_string(conn, content_type),
    do: put_resp_header(conn, "content-type", content_type)

  defp base(conn), do: conn.assigns[:url_base] || OpenAgentsWeb.Endpoint.url()

  defp sha256(body), do: :sha256 |> :crypto.hash(body) |> Base.encode16(case: :lower)
end