3.6 KiB · text 5af55d9
defmodule GitGudWeb.Plugs.ApDispatcher do
@moduledoc """
Pipeline plug that diverts AP-shaped requests to the
`GitGudWeb.ApActorController` before the browser router can flip the
conn into a LiveView.
Recognised paths:
GET /orgs/:handle → org actor
GET /r/:owner/:name → repo actor
GET /@:handle → user actor
Plus the per-actor collections:
GET /<actor>/outbox|followers|following
POST /<actor>/inbox
Only fires when the Accept header asks for AP JSON. For browsers,
this is a no-op and the LiveView renders the HTML page.
"""
@behaviour Plug
import Plug.Conn
alias GitGudWeb.ApActorController
alias GitGudWeb.Plugs.ApContentNegotiation
@impl true
def init(opts), do: opts
@impl true
def call(conn, _opts) do
cond do
not ApContentNegotiation.ap_requested?(conn) ->
conn
conn.method == "GET" ->
dispatch_get(conn)
conn.method == "POST" ->
dispatch_post(conn)
true ->
conn
end
end
# ── GET dispatch ────────────────────────────────────────────────────
defp dispatch_get(%Plug.Conn{path_info: ["@" <> handle]} = conn),
do: call_action(conn, :user, %{"handle" => handle})
defp dispatch_get(%Plug.Conn{path_info: ["@" <> _handle, collection]} = conn)
when collection in ~w(outbox followers following inbox) do
call_collection(conn, collection)
end
defp dispatch_get(%Plug.Conn{path_info: ["orgs", handle]} = conn),
do: call_action(conn, :org, %{"handle" => handle})
defp dispatch_get(%Plug.Conn{path_info: ["orgs", _handle, collection]} = conn)
when collection in ~w(outbox followers following inbox),
do: call_collection(conn, collection)
defp dispatch_get(%Plug.Conn{path_info: ["orgs", handle, "repositories"]} = conn),
do: call_action(conn, :repositories, %{"handle" => handle})
defp dispatch_get(%Plug.Conn{path_info: ["r", owner, name]} = conn),
do: call_action(conn, :repo, %{"owner" => owner, "name" => name})
defp dispatch_get(%Plug.Conn{path_info: ["r", _owner, _name, collection]} = conn)
when collection in ~w(outbox followers following inbox),
do: call_collection(conn, collection)
defp dispatch_get(conn), do: conn
# ── POST dispatch ───────────────────────────────────────────────────
#
# Inbox writes are routed here so other controllers don't need to
# know about them.
defp dispatch_post(%Plug.Conn{path_info: path_info} = conn) do
if List.last(path_info) == "inbox" do
call_action(conn, :inbox, %{})
else
conn
end
end
defp dispatch_post(conn), do: conn
# ── invocation ──────────────────────────────────────────────────────
defp call_action(conn, action, params) do
conn
|> Phoenix.Controller.put_view(ApActorController)
|> Phoenix.Controller.put_format(:json)
|> then(&apply(ApActorController, action, [&1, Map.merge(&1.params, params)]))
|> halt()
end
defp call_collection(conn, "inbox"), do: call_action(conn, :inbox, %{})
defp call_collection(conn, "outbox"), do: call_action(conn, :outbox, %{})
defp call_collection(conn, "followers"), do: call_action(conn, :followers, %{})
defp call_collection(conn, "following"), do: call_action(conn, :following, %{})
end