From aeb68a0ad151ed07d2daa41eafe1c6c282e78144 Mon Sep 17 00:00:00 2001 From: floatingghost Date: Sat, 4 Feb 2023 20:51:17 +0000 Subject: [PATCH] paginate follow requests (#460) matches https://docs.joinmastodon.org/methods/follow_requests/#get mostly Co-authored-by: FloatingGhost Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/460 --- CHANGELOG.md | 1 + lib/pleroma/following_relationship.ex | 9 ++++---- lib/pleroma/user.ex | 8 ++++++- .../operations/follow_request_operation.ex | 19 ++++++++++++++++ .../controllers/follow_request_controller.ex | 15 ++++++++++--- .../web/mastodon_api/views/account_view.ex | 3 ++- .../block_validation_test.exs | 1 + test/pleroma/web/common_api_test.exs | 1 + .../follow_request_controller_test.exs | 22 +++++++++++++++++++ 9 files changed, 69 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0437033ee..e6effac78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Simplified HTTP signature processing - Rich media will now hard-exit after 5 seconds, to prevent timeline hangs - HTTP Content Security Policy is now far more strict to prevent any potential XSS/CSS leakages +- Follow requests are now paginated, matches mastodon API spec, so use the Link header to paginate. ### Fixed - /api/v1/accounts/lookup will now respect restrict\_unauthenticated diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index c489ccbbe..9e75458e5 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -155,14 +155,13 @@ defmodule Pleroma.FollowingRelationship do |> Repo.aggregate(:count, :id) end - def get_follow_requests(%User{id: id}) do + def get_follow_requests_query(%User{id: id}) do __MODULE__ - |> join(:inner, [r], f in assoc(r, :follower)) + |> join(:inner, [r], f in assoc(r, :follower), as: :follower) |> where([r], r.state == ^:follow_pending) |> where([r], r.following_id == ^id) - |> where([r, f], f.is_active == true) - |> select([r, f], f) - |> Repo.all() + |> where([r, follower: f], f.is_active == true) + |> select([r, follower: f], f) end def following?(%User{id: follower_id}, %User{id: followed_id}) do diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 1ddbd36a8..1572a895e 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -273,7 +273,13 @@ defmodule Pleroma.User do defdelegate following(user), to: FollowingRelationship defdelegate following?(follower, followed), to: FollowingRelationship defdelegate following_ap_ids(user), to: FollowingRelationship - defdelegate get_follow_requests(user), to: FollowingRelationship + defdelegate get_follow_requests_query(user), to: FollowingRelationship + + def get_follow_requests(user) do + get_follow_requests_query(user) + |> Repo.all() + end + defdelegate search(query, opts \\ []), to: User.Search @doc """ diff --git a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex index 784019699..d6f59191b 100644 --- a/lib/pleroma/web/api_spec/operations/follow_request_operation.ex +++ b/lib/pleroma/web/api_spec/operations/follow_request_operation.ex @@ -19,6 +19,7 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do summary: "Retrieve follow requests", security: [%{"oAuth" => ["read:follows", "follow"]}], operationId: "FollowRequestController.index", + parameters: pagination_params(), responses: %{ 200 => Operation.response("Array of Account", "application/json", %Schema{ @@ -62,4 +63,22 @@ defmodule Pleroma.Web.ApiSpec.FollowRequestOperation do required: true ) end + + defp pagination_params do + [ + Operation.parameter(:max_id, :query, :string, "Return items older than this ID"), + Operation.parameter( + :since_id, + :query, + :string, + "Return the oldest items newer than this ID" + ), + Operation.parameter( + :limit, + :query, + %Schema{type: :integer, default: 20}, + "Maximum number of items to return. Will be ignored if it's more than 40" + ) + ] + end end diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex index d915298f1..e534d0388 100644 --- a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -5,9 +5,13 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do use Pleroma.Web, :controller + import Pleroma.Web.ControllerHelper, + only: [add_link_headers: 2] + alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Pagination plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(:assign_follower when action != :index) @@ -24,10 +28,15 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FollowRequestOperation @doc "GET /api/v1/follow_requests" - def index(%{assigns: %{user: followed}} = conn, _params) do - follow_requests = User.get_follow_requests(followed) + def index(%{assigns: %{user: followed}} = conn, params) do + follow_requests = + followed + |> User.get_follow_requests_query() + |> Pagination.fetch_paginated(params, :keyset, :follower) - render(conn, "index.json", for: followed, users: follow_requests, as: :user) + conn + |> add_link_headers(follow_requests) + |> render("index.json", for: followed, users: follow_requests, as: :user) end @doc "POST /api/v1/follow_requests/:id/authorize" diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 653a50e20..190d6ebf2 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -334,7 +334,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do %User{id: user_id} ) do count = - User.get_follow_requests(user) + user + |> User.get_follow_requests() |> length() data diff --git a/test/pleroma/web/activity_pub/object_validators/block_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/block_validation_test.exs index ad6190892..5e6757760 100644 --- a/test/pleroma/web/activity_pub/object_validators/block_validation_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/block_validation_test.exs @@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidationTest do describe "blocks" do setup do + clear_config([:activitypub, :outgoing_blocks], true) user = insert(:user, local: false) blocked = insert(:user) diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 2b7a34be2..33709c8f3 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -71,6 +71,7 @@ defmodule Pleroma.Web.CommonAPITest do test "it blocks and federates", %{blocker: blocker, blocked: blocked} do clear_config([:instance, :federating], true) + clear_config([:activitypub, :outgoing_blocks], true) with_mock Pleroma.Web.Federator, publish: fn _ -> nil end do diff --git a/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs index 069ffb3d6..e3f59d886 100644 --- a/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -10,6 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do import Pleroma.Factory + defp extract_next_link_header(header) do + [_, next_link] = Regex.run(~r{<(?.*)>; rel="next"}, header) + next_link + end + describe "locked accounts" do setup do user = insert(:user, is_locked: true) @@ -31,6 +36,23 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do assert to_string(other_user.id) == relationship["id"] end + test "/api/v1/follow_requests paginates", %{user: user, conn: conn} do + for _ <- 1..21 do + other_user = insert(:user) + {:ok, _, _, _activity} = CommonAPI.follow(other_user, user) + {:ok, _, _} = User.follow(other_user, user, :follow_pending) + end + + conn = get(conn, "/api/v1/follow_requests") + assert length(json_response_and_validate_schema(conn, 200)) == 20 + assert [link_header] = get_resp_header(conn, "link") + assert link_header =~ "rel=\"next\"" + next_link = extract_next_link_header(link_header) + assert next_link =~ "/api/v1/follow_requests" + conn = get(conn, next_link) + assert length(json_response_and_validate_schema(conn, 200)) == 1 + end + test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do other_user = insert(:user)