From 7c03d2627f7da617282c9eade17dc22e537d6643 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 2 Oct 2019 11:12:34 -0400 Subject: [PATCH] Add AP followers endpoint --- lib/clacks/actor.ex | 4 +- .../controllers/actor_controller.ex | 76 +++++++++++++++++-- .../controllers/web_finger_controller.ex | 2 +- lib/clacks_web/router.ex | 2 + 4 files changed, 75 insertions(+), 9 deletions(-) diff --git a/lib/clacks/actor.ex b/lib/clacks/actor.ex index 897d4da..4e772a9 100644 --- a/lib/clacks/actor.ex +++ b/lib/clacks/actor.ex @@ -27,8 +27,8 @@ defmodule Clacks.Actor do |> validate_required([:ap_id, :nickname, :local, :data]) end - @spec get_by_nickanme(nickname :: String.t()) :: t() | nil - def get_by_nickanme(nickname) do + @spec get_by_nickname(nickname :: String.t()) :: t() | nil + def get_by_nickname(nickname) do Repo.one(from a in __MODULE__, where: a.nickname == ^nickname) end diff --git a/lib/clacks_web/controllers/actor_controller.ex b/lib/clacks_web/controllers/actor_controller.ex index c9cca06..b0a47fd 100644 --- a/lib/clacks_web/controllers/actor_controller.ex +++ b/lib/clacks_web/controllers/actor_controller.ex @@ -1,10 +1,28 @@ defmodule ClacksWeb.ActorController do use ClacksWeb, :controller - alias Clacks.{Repo, Actor} + alias Clacks.Actor import Ecto.Query - def get(conn, %{"nickname" => nickname}) do - case Actor.get_by_nickanme(nickname) do + @context "https://www.w3.org/ns/activitystreams" + + plug :get_actor + + defp get_actor(%Plug.Conn{path_params: %{"nickname" => nickname}} = conn, _opts) do + case Actor.get_by_nickname(nickname) do + nil -> + conn + |> put_status(404) + |> halt() + + actor -> + assign(conn, :actor, actor) + end + end + + defp get_actor(conn, _opts), do: conn + + def get(conn, _params) do + case conn.assigns[:actor] do %Actor{local: true, data: data} -> conn |> put_resp_header("content-type", "application/activity+json") @@ -13,10 +31,56 @@ defmodule ClacksWeb.ActorController do %Actor{local: false, ap_id: ap_id} -> conn |> redirect(external: ap_id) + end + end - _ -> - conn - |> put_status(404) + 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 + + 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 diff --git a/lib/clacks_web/controllers/web_finger_controller.ex b/lib/clacks_web/controllers/web_finger_controller.ex index ebd817a..a284b21 100644 --- a/lib/clacks_web/controllers/web_finger_controller.ex +++ b/lib/clacks_web/controllers/web_finger_controller.ex @@ -6,7 +6,7 @@ defmodule ClacksWeb.WebFingerController do def get(conn, %{"resource" => resource}) do with [_, nickname] <- Regex.run(@acct_regex, resource), - %Actor{local: true} = actor <- Actor.get_by_nickanme(nickname) do + %Actor{local: true} = actor <- Actor.get_by_nickname(nickname) do url = Application.get_env(:clacks, ClacksWeb.Endpoint)[:url] host = url[:host] port = url[:port] diff --git a/lib/clacks_web/router.ex b/lib/clacks_web/router.ex index f45547d..55c9724 100644 --- a/lib/clacks_web/router.ex +++ b/lib/clacks_web/router.ex @@ -21,7 +21,9 @@ defmodule ClacksWeb.Router do pipe_through :activitypub get "/objects/:id", ObjectsController, :get + get "/users/:nickname", ActorController, :get + get "/users/:nickname/followers", ActorController, :followers post "/inbox", InboxController, :shared post "/users/:nickname/inbox", InboxController, :user_specific