commit: 792d4737787394153fcabf21e45a3f983ef6d1d9
parent 2620b89cb5302cb88816d5c8001b540864996680
Author: Haelwenn <contact+git.pleroma.social@hacktivis.me>
Date: Thu, 8 Jan 2026 10:25:45 +0000
Merge branch 'paginate-follow-requests' into 'develop'
Paginate follow requests (adapted from Akkoma)
See merge request pleroma/pleroma!4420
Diffstat:
9 files changed, 102 insertions(+), 19 deletions(-)
diff --git a/changelog.d/paginate-follow-requests.change b/changelog.d/paginate-follow-requests.change
@@ -0,0 +1 @@
+Paginate follow requests
diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex
@@ -147,24 +147,22 @@ 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 get_outgoing_follow_requests(%User{id: id}) do
+ def get_outgoing_follow_requests_query(%User{id: id}) do
__MODULE__
- |> join(:inner, [r], f in assoc(r, :following))
+ |> join(:inner, [r], f in assoc(r, :following), as: :following)
|> where([r], r.state == ^:follow_pending)
|> where([r], r.follower_id == ^id)
- |> where([r, f], f.is_active == true)
- |> select([r, f], f)
- |> Repo.all()
+ |> where([r, following: f], f.is_active == true)
+ |> select([r, following: 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
@@ -287,8 +287,14 @@ 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_outgoing_follow_requests(user), to: FollowingRelationship
+ defdelegate get_follow_requests_query(user), to: FollowingRelationship
+ defdelegate get_outgoing_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
@@ -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/api_spec/operations/pleroma_follow_request_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex
@@ -18,6 +18,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaFollowRequestOperation do
summary: "Retrieve outgoing follow requests",
security: [%{"oAuth" => ["read:follows", "follow"]}],
operationId: "PleromaFollowRequestController.outgoing",
+ parameters: pagination_params(),
responses: %{
200 =>
Operation.response("Array of Account", "application/json", %Schema{
@@ -28,4 +29,22 @@ defmodule Pleroma.Web.ApiSpec.PleromaFollowRequestOperation do
}
}
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
@@ -5,6 +5,10 @@
defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
use Pleroma.Web, :controller
+ import Pleroma.Web.ControllerHelper,
+ only: [add_link_headers: 2]
+
+ alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
@@ -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
@@ -360,8 +360,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
%User{id: user_id}
) do
count =
- User.get_follow_requests(user)
- |> length()
+ user
+ |> User.get_follow_requests_query()
+ |> Pleroma.Repo.aggregate(:count)
data
|> Kernel.put_in([:follow_requests_count], count)
diff --git a/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex b/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex
@@ -5,6 +5,10 @@
defmodule Pleroma.Web.PleromaAPI.FollowRequestController do
use Pleroma.Web, :controller
+ import Pleroma.Web.ControllerHelper,
+ only: [add_link_headers: 2]
+
+ alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.Web.Plugs.OAuthScopesPlug
@@ -17,11 +21,15 @@ defmodule Pleroma.Web.PleromaAPI.FollowRequestController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaFollowRequestOperation
@doc "GET /api/v1/pleroma/outgoing_follow_requests"
- def outgoing(%{assigns: %{user: follower}} = conn, _params) do
- follow_requests = User.get_outgoing_follow_requests(follower)
+ def outgoing(%{assigns: %{user: follower}} = conn, params) do
+ follow_requests =
+ follower
+ |> User.get_outgoing_follow_requests_query()
+ |> Pagination.fetch_paginated(params, :keyset, :following)
conn
|> put_view(Pleroma.Web.MastodonAPI.FollowRequestView)
+ |> add_link_headers(follow_requests)
|> render("index.json", for: follower, users: follow_requests, as: :user)
end
end
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
@@ -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{<(?<next_link>.*)>; 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)