From d87be2ec96912b147ad8fb6b17c1ee00d7d30a7f Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 15:59:04 +0300 Subject: [PATCH 1/5] Don't embed the first page in inboxes/outboxes and refactor the views to follow View/Controller pattern Note that I mentioned the change in 1.1 section because I intend to backport this, if this is not needed I will move it back to Unreleased. --- CHANGELOG.md | 1 + lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- .../activity_pub/activity_pub_controller.ex | 70 ++++++++++++++-- .../web/activity_pub/views/user_view.ex | 83 +++---------------- .../activity_pub_controller_test.exs | 6 +- .../web/activity_pub/views/user_view_test.exs | 16 +++- 6 files changed, 90 insertions(+), 88 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 649fbc0be..2d6ddd5d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Improve digest email template – Pagination: (optional) return `total` alongside with `items` when paginating - Add `rel="ugc"` to all links in statuses, to prevent SEO spam +- ActivityPub: The first page in inboxes/outboxes is no longer embedded. ### Fixed - Following from Osada diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1cf8b6151..a97afa665 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -834,7 +834,7 @@ defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do defp restrict_muted_reblogs(query, _), do: query - defp exclude_poll_votes(query, %{"include_poll_votes" => "true"}), do: query + defp exclude_poll_votes(query, %{"include_poll_votes" => true}), do: query defp exclude_poll_votes(query, _) do if has_named_binding?(query, :object) do diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 9eb86106f..c3e7edf57 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -231,13 +231,42 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d end end - def outbox(conn, %{"nickname" => nickname} = params) do + def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) + when page? in [true, "true"] do + with %User{} = user <- User.get_cached_by_nickname(nickname), + {:ok, user} <- User.ensure_keys_present(user), + activities <- + (if params["max_id"] do + ActivityPub.fetch_user_activities(user, nil, %{ + "max_id" => params["max_id"], + # This is a hack because postgres generates inefficient queries when filtering by 'Answer', + # poll votes will be hidden by the visibility filter in this case anyway + "include_poll_votes" => true, + "limit" => 10 + }) + else + ActivityPub.fetch_user_activities(user, nil, %{ + "limit" => 10, + "include_poll_votes" => true + }) + end) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection_page.json", %{ + activities: activities, + iri: "#{user.ap_id}/outbox" + }) + end + end + + def outbox(conn, %{"nickname" => nickname}) do with %User{} = user <- User.get_cached_by_nickname(nickname), {:ok, user} <- User.ensure_keys_present(user) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) - |> render("outbox.json", %{user: user, max_id: params["max_id"]}) + |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"}) end end @@ -315,12 +344,37 @@ def whoami(_conn, _params), do: {:error, :not_found} def read_inbox( %{assigns: %{user: %{nickname: nickname} = user}} = conn, - %{"nickname" => nickname} = params - ) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("inbox.json", user: user, max_id: params["max_id"]) + %{"nickname" => nickname, "page" => page?} = params + ) + when page? in [true, "true"] do + with activities <- + (if params["max_id"] do + ActivityPub.fetch_activities([user.ap_id | user.following], %{ + "max_id" => params["max_id"], + "limit" => 10 + }) + else + ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10}) + end) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection_page.json", %{ + activities: activities, + iri: "#{user.ap_id}/inbox" + }) + end + end + + def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ + "nickname" => nickname + }) do + with {:ok, user} <- User.ensure_keys_present(user) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"}) + end end def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 352d856fa..5dbb5992f 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do alias Pleroma.Keys alias Pleroma.Repo alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Endpoint @@ -210,20 +209,16 @@ def render("followers.json", %{user: user} = opts) do |> Map.merge(Utils.make_json_ld_header()) end - def render("outbox.json", %{user: user, max_id: max_qid}) do - params = %{ - "limit" => "10" + def render("activity_collection.json", %{iri: iri}) do + %{ + "id" => iri, + "type" => "OrderedCollection", + "first" => "#{iri}?page=true" } + |> Map.merge(Utils.make_json_ld_header()) + end - params = - if max_qid != nil do - Map.put(params, "max_id", max_qid) - else - params - end - - activities = ActivityPub.fetch_user_activities(user, nil, params) - + def render("activity_collection_page.json", %{activities: activities, iri: iri}) do # this is sorted chronologically, so first activity is the newest (max) {max_id, min_id, collection} = if length(activities) > 0 do @@ -243,71 +238,15 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do } end - iri = "#{user.ap_id}/outbox" - page = %{ - "id" => "#{iri}?max_id=#{max_id}", + "id" => "#{iri}?max_id=#{max_id}&page=true", "type" => "OrderedCollectionPage", "partOf" => iri, "orderedItems" => collection, - "next" => "#{iri}?max_id=#{min_id}" + "next" => "#{iri}?max_id=#{min_id}&page=true" } - if max_qid == nil do - %{ - "id" => iri, - "type" => "OrderedCollection", - "first" => page - } - |> Map.merge(Utils.make_json_ld_header()) - else - page |> Map.merge(Utils.make_json_ld_header()) - end - end - - def render("inbox.json", %{user: user, max_id: max_qid}) do - params = %{ - "limit" => "10" - } - - params = - if max_qid != nil do - Map.put(params, "max_id", max_qid) - else - params - end - - activities = ActivityPub.fetch_activities([user.ap_id | user.following], params) - - min_id = Enum.at(Enum.reverse(activities), 0).id - max_id = Enum.at(activities, 0).id - - collection = - Enum.map(activities, fn act -> - {:ok, data} = Transmogrifier.prepare_outgoing(act.data) - data - end) - - iri = "#{user.ap_id}/inbox" - - page = %{ - "id" => "#{iri}?max_id=#{max_id}", - "type" => "OrderedCollectionPage", - "partOf" => iri, - "orderedItems" => collection, - "next" => "#{iri}?max_id=#{min_id}" - } - - if max_qid == nil do - %{ - "id" => iri, - "type" => "OrderedCollection", - "first" => page - } - |> Map.merge(Utils.make_json_ld_header()) - else - page |> Map.merge(Utils.make_json_ld_header()) - end + page |> Map.merge(Utils.make_json_ld_header()) end def collection(collection, iri, page, show_items \\ true, total \\ nil) do diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 9e8e420ec..ab52044ae 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -479,7 +479,7 @@ test "it returns a note activity in a collection", %{conn: conn} do conn |> assign(:user, user) |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}/inbox") + |> get("/users/#{user.nickname}/inbox?page=true") assert response(conn, 200) =~ note_object.data["content"] end @@ -567,7 +567,7 @@ test "it returns a note activity in a collection", %{conn: conn} do conn = conn |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}/outbox") + |> get("/users/#{user.nickname}/outbox?page=true") assert response(conn, 200) =~ note_object.data["content"] end @@ -579,7 +579,7 @@ test "it returns an announce activity in a collection", %{conn: conn} do conn = conn |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}/outbox") + |> get("/users/#{user.nickname}/outbox?page=true") assert response(conn, 200) =~ announce_activity.data["object"] end diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs index 78b0408ee..3155749aa 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -159,7 +159,7 @@ test "sets correct totalItems when follows are hidden but the follow counter is end end - test "outbox paginates correctly" do + test "activity collection page aginates correctly" do user = insert(:user) posts = @@ -171,13 +171,21 @@ test "outbox paginates correctly" do # outbox sorts chronologically, newest first, with ten per page posts = Enum.reverse(posts) - %{"first" => %{"next" => next_url}} = - UserView.render("outbox.json", %{user: user, max_id: nil}) + %{"next" => next_url} = + UserView.render("activity_collection_page.json", %{ + iri: "#{user.ap_id}/outbox", + activities: Enum.take(posts, 10) + }) next_id = Enum.at(posts, 9).id assert next_url =~ next_id - %{"next" => next_url} = UserView.render("outbox.json", %{user: user, max_id: next_id}) + %{"next" => next_url} = + UserView.render("activity_collection_page.json", %{ + iri: "#{user.ap_id}/outbox", + activities: Enum.take(Enum.drop(posts, 10), 10) + }) + next_id = Enum.at(posts, 19).id assert next_url =~ next_id end From 1ddd403339655674ca634a876151c4346c87c515 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 13:20:48 +0000 Subject: [PATCH 2/5] Apply suggestion to lib/pleroma/web/activity_pub/activity_pub_controller.ex --- .../activity_pub/activity_pub_controller.ex | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index c3e7edf57..aa1620009 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -234,22 +234,23 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) when page? in [true, "true"] do with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- User.ensure_keys_present(user), - activities <- - (if params["max_id"] do - ActivityPub.fetch_user_activities(user, nil, %{ - "max_id" => params["max_id"], - # This is a hack because postgres generates inefficient queries when filtering by 'Answer', - # poll votes will be hidden by the visibility filter in this case anyway - "include_poll_votes" => true, - "limit" => 10 - }) - else - ActivityPub.fetch_user_activities(user, nil, %{ - "limit" => 10, - "include_poll_votes" => true - }) - end) do + {:ok, user} <- User.ensure_keys_present(user) do + activities = + if params["max_id"] do + ActivityPub.fetch_user_activities(user, nil, %{ + "max_id" => params["max_id"], + # This is a hack because postgres generates inefficient queries when filtering by 'Answer', + # poll votes will be hidden by the visibility filter in this case anyway + "include_poll_votes" => true, + "limit" => 10 + }) + else + ActivityPub.fetch_user_activities(user, nil, %{ + "limit" => 10, + "include_poll_votes" => true + }) + end + conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) From c7d8ccd0c417aab59253a446ed0ffc973448536e Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 16:26:47 +0300 Subject: [PATCH 3/5] Remove useless with clause --- .../activity_pub/activity_pub_controller.ex | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index aa1620009..60abe1e1d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -348,23 +348,23 @@ def read_inbox( %{"nickname" => nickname, "page" => page?} = params ) when page? in [true, "true"] do - with activities <- - (if params["max_id"] do - ActivityPub.fetch_activities([user.ap_id | user.following], %{ - "max_id" => params["max_id"], - "limit" => 10 - }) - else - ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10}) - end) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("activity_collection_page.json", %{ - activities: activities, - iri: "#{user.ap_id}/inbox" - }) - end + activities = + if params["max_id"] do + ActivityPub.fetch_activities([user.ap_id | user.following], %{ + "max_id" => params["max_id"], + "limit" => 10 + }) + else + ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10}) + end + + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection_page.json", %{ + activities: activities, + iri: "#{user.ap_id}/inbox" + }) end def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ From f2880d7d29836b16ae6825fbda85c21496fc42b5 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 16:36:46 +0300 Subject: [PATCH 4/5] Credo considered harmful --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 60abe1e1d..8112f6642 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -239,8 +239,8 @@ def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) if params["max_id"] do ActivityPub.fetch_user_activities(user, nil, %{ "max_id" => params["max_id"], - # This is a hack because postgres generates inefficient queries when filtering by 'Answer', - # poll votes will be hidden by the visibility filter in this case anyway + # This is a hack because postgres generates inefficient queries when filtering by + # 'Answer', poll votes will be hidden by the visibility filter in this case anyway "include_poll_votes" => true, "limit" => 10 }) From f92d7d52c20e951d31f0dedc16bde3aeb6687374 Mon Sep 17 00:00:00 2001 From: rinpatch Date: Wed, 25 Sep 2019 13:38:45 +0000 Subject: [PATCH 5/5] Apply suggestion to lib/pleroma/web/activity_pub/views/user_view.ex --- lib/pleroma/web/activity_pub/views/user_view.ex | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 5dbb5992f..4e37be5db 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -238,15 +238,14 @@ def render("activity_collection_page.json", %{activities: activities, iri: iri}) } end - page = %{ + %{ "id" => "#{iri}?max_id=#{max_id}&page=true", "type" => "OrderedCollectionPage", "partOf" => iri, "orderedItems" => collection, "next" => "#{iri}?max_id=#{min_id}&page=true" } - - page |> Map.merge(Utils.make_json_ld_header()) + |> Map.merge(Utils.make_json_ld_header()) end def collection(collection, iri, page, show_items \\ true, total \\ nil) do