defmodule ClacksWeb.ActorController do use ClacksWeb, :controller alias Clacks.{Actor, User} @context "https://www.w3.org/ns/activitystreams" plug :get_actor defp get_actor(%Plug.Conn{path_params: %{"username" => username}} = conn, _opts) do case User.get_by_username(username) do nil -> conn |> put_status(404) |> halt() %User{actor: actor} -> assign(conn, :actor, actor) end end defp get_actor(conn, _opts), do: conn def get(%Plug.Conn{assigns: %{format: "activity+json"}} = conn, _params) do actor = conn.assigns[:actor] conn |> put_resp_header("content-type", "application/activity+json") |> json(actor.data) end def get(%Plug.Conn{assigns: %{format: "html"}} = conn, _params) do conn |> put_view(ClacksWeb.FrontendView) |> ClacksWeb.FrontendController.call(:profile) end def followers(conn, %{"page" => page}) do {page, _} = Integer.parse(page) followers = conn.assigns[:actor].followers data = collection_page(conn, followers, page) |> Map.put("@context", @context) conn |> put_resp_header("content-type", "application/activity+json") |> json(data) end def followers(conn, _params) do %Actor{followers: followers} = conn.assigns[:actor] data = %{ "@context" => @context, "type" => "OrderedCollection", "id" => current_url(conn, %{}), "totalItems" => length(followers), "first" => collection_page(conn, followers, 1) } conn |> put_resp_header("content-type", "application/activity+json") |> json(data) end def following(conn, %{"page" => page}) do actor = conn.assigns[:actor] following = Actor.get_all_following(actor) |> Enum.map(fn actor -> actor.ap_id end) data = collection_page(conn, following, page) |> Map.put("@context", @context) conn |> put_resp_header("content-type", "application/activity+json") |> json(data) end def following(conn, _params) do actor = conn.assigns[:actor] following = Actor.get_all_following(actor) |> Enum.map(fn actor -> actor.ap_id end) data = %{ "@context" => @context, "type" => "OrderedCollection", "id" => current_url(conn, %{}), "totalItems" => length(following), "first" => collection_page(conn, following, 1) } conn |> put_resp_header("content-type", "application/activity+json") |> json(data) end defp collection_page(conn, collection, page) do chunks = Enum.chunk_every(collection, 20) # page is 1 indexed, so subtract 1 to get the current chunk current_chunk = Enum.at(chunks, page - 1) data = %{ "type" => "OrderedCollectionPage", "totalItems" => length(collection), "partOf" => current_url(conn, %{}), "id" => current_url(conn, %{page: page}), "orderedItems" => current_chunk || [] } if page < length(chunks) do Map.put(data, "next", current_url(conn, %{page: page + 1})) else data end end end