activitypub: implement following/followers endpoints
authorWilliam Pitcock <nenolod@dereferenced.org>
Wed, 21 Mar 2018 17:23:27 +0000 (17:23 +0000)
committerWilliam Pitcock <nenolod@dereferenced.org>
Thu, 22 Mar 2018 05:26:39 +0000 (00:26 -0500)
lib/pleroma/web/activity_pub/activity_pub_controller.ex
lib/pleroma/web/activity_pub/utils.ex
lib/pleroma/web/activity_pub/views/user_view.ex
lib/pleroma/web/router.ex

index b9a70a33f8d16e9434d57f223cca1971379d3dd2..419c3307dfe23ecec72a2168c39f1f34927932b2 100644 (file)
@@ -27,6 +27,44 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
     end
   end
 
+  def following(conn, %{"nickname" => nickname, "page" => page}) do
+    with %User{} = user <- User.get_cached_by_nickname(nickname),
+         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+      {page, _} = Integer.parse(page)
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(UserView.render("following.json", %{user: user, page: page}))
+    end
+  end
+
+  def following(conn, %{"nickname" => nickname}) do
+    with %User{} = user <- User.get_cached_by_nickname(nickname),
+         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(UserView.render("following.json", %{user: user}))
+    end
+  end
+
+  def followers(conn, %{"nickname" => nickname, "page" => page}) do
+    with %User{} = user <- User.get_cached_by_nickname(nickname),
+         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+      {page, _} = Integer.parse(page)
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(UserView.render("followers.json", %{user: user, page: page}))
+    end
+  end
+
+  def followers(conn, %{"nickname" => nickname}) do
+    with %User{} = user <- User.get_cached_by_nickname(nickname),
+         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
+      conn
+      |> put_resp_header("content-type", "application/activity+json")
+      |> json(UserView.render("followers.json", %{user: user}))
+    end
+  end
+
   # TODO: Ensure that this inbox is a recipient of the message
   def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
     Federator.enqueue(:incoming_ap_doc, params)
index cda10628387f066bedb69881aff5737a9266835f..a25b27aabde0b3035c57693aaeadf4ca1962bcf3 100644 (file)
@@ -5,6 +5,26 @@ defmodule Pleroma.Web.ActivityPub.Utils do
   alias Ecto.{Changeset, UUID}
   import Ecto.Query
 
+  def make_json_ld_header do
+    %{
+      "@context" => [
+        "https://www.w3.org/ns/activitystreams",
+        "https://w3id.org/security/v1",
+        %{
+          "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
+          "sensitive" => "as:sensitive",
+          "Hashtag" => "as:Hashtag",
+          "ostatus" => "http://ostatus.org#",
+          "atomUri" => "ostatus:atomUri",
+          "inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
+          "conversation" => "ostatus:conversation",
+          "toot" => "http://joinmastodon.org/ns#",
+          "Emoji" => "toot:Emoji"
+        }
+      ]
+    }
+  end
+
   def make_date do
     DateTime.utc_now() |> DateTime.to_iso8601
   end
index 179636884aeb6e5c1a3431de71558e8361a4c761..7069c248c9df2c9b62f616ecfb9ffb9f3f864929 100644 (file)
@@ -3,6 +3,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
   alias Pleroma.Web.Salmon
   alias Pleroma.Web.WebFinger
   alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.Utils
 
   def render("user.json", %{user: user}) do
     {:ok, user} = WebFinger.ensure_keys_present(user)
@@ -10,21 +11,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
     public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
     public_key = :public_key.pem_encode([public_key])
     %{
-      "@context" => [
-        "https://www.w3.org/ns/activitystreams",
-        "https://w3id.org/security/v1",
-        %{
-          "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
-          "sensitive" => "as:sensitive",
-          "Hashtag" => "as:Hashtag",
-          "ostatus" => "http://ostatus.org#",
-          "atomUri" => "ostatus:atomUri",
-          "inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
-          "conversation" => "ostatus:conversation",
-          "toot" => "http://joinmastodon.org/ns#",
-          "Emoji" => "toot:Emoji"
-        }
-      ],
       "id" => user.ap_id,
       "type" => "Person",
       "following" => "#{user.ap_id}/following",
@@ -53,5 +39,56 @@ defmodule Pleroma.Web.ActivityPub.UserView do
         "url" => User.banner_url(user)
       }
     }
+    |> Map.merge(Utils.make_json_ld_header())
+  end
+
+  def collection(collection, iri, page) do
+    offset = (page - 1) * 10
+    items = Enum.slice(collection, offset, 10)
+    items = Enum.map(items, fn (user) -> user.ap_id end)
+    map = %{
+      "id" => "#{iri}?page=#{page}",
+      "type" => "OrderedCollectionPage",
+      "partOf" => iri,
+      "totalItems" => length(collection),
+      "orderedItems" => items
+    }
+    if offset < length(collection) do
+      Map.put(map, "next", "#{iri}?page=#{page+1}")
+    end
+  end
+
+  def render("following.json", %{user: user, page: page}) do
+    {:ok, following} = User.get_friends(user)
+    collection(following, "#{user.ap_id}/following", page)
+    |> Map.merge(Utils.make_json_ld_header())
+  end
+
+  def render("following.json", %{user: user}) do
+    {:ok, following} = User.get_friends(user)
+    %{
+      "id" => "#{user.ap_id}/following",
+      "type" => "OrderedCollection",
+      "totalItems" => length(following),
+      "first" => collection(following, "#{user.ap_id}/following", 1)
+    }
+    |> Map.merge(Utils.make_json_ld_header())
+  end
+
+  def render("followers.json", %{user: user, page: page}) do
+    {:ok, followers} = User.get_followers(user)
+    collection(followers, "#{user.ap_id}/followers", page)
+    |> Map.merge(Utils.make_json_ld_header())
+  end
+
+  def render("followers.json", %{user: user}) do
+    {:ok, followers} = User.get_followers(user)
+    %{
+      "id" => "#{user.ap_id}/following",
+      "type" => "OrderedCollection",
+      "totalItems" => length(followers),
+      "first" => collection(followers, "#{user.ap_id}/followers", 1)
+    }
+    |> Map.merge(Utils.make_json_ld_header())
   end
 end
index 3e9a8ba7b020e52454e0d45778d6b7d682b5003b..e071a469ed5411dbdf75814927d1f3941657b8a2 100644 (file)
@@ -249,6 +249,14 @@ defmodule Pleroma.Web.Router do
     plug Pleroma.Web.Plugs.HTTPSignaturePlug
   end
 
+  scope "/", Pleroma.Web.ActivityPub do
+    # XXX: not really ostatus
+    pipe_through :ostatus
+
+    get "/users/:nickname/followers", ActivityPubController, :followers
+    get "/users/:nickname/following", ActivityPubController, :following
+  end
+
   if @federating do
     scope "/", Pleroma.Web.ActivityPub do
       pipe_through :activitypub