diff --git a/changelog.d/outgoing-follow-requests.add b/changelog.d/outgoing-follow-requests.add new file mode 100644 index 000000000..a898bcf6e --- /dev/null +++ b/changelog.d/outgoing-follow-requests.add @@ -0,0 +1 @@ +Add /api/v1/pleroma/outgoing_follow_requests diff --git a/lib/pleroma/following_relationship.ex b/lib/pleroma/following_relationship.ex index 495488dfd..653feb32f 100644 --- a/lib/pleroma/following_relationship.ex +++ b/lib/pleroma/following_relationship.ex @@ -157,6 +157,16 @@ defmodule Pleroma.FollowingRelationship do |> Repo.all() end + def get_outgoing_follow_requests(%User{id: id}) do + __MODULE__ + |> join(:inner, [r], f in assoc(r, :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() + end + def following?(%User{id: follower_id}, %User{id: followed_id}) do __MODULE__ |> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 7a36ece77..5586ac6b1 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -280,6 +280,7 @@ defmodule Pleroma.User do 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 search(query, opts \\ []), to: User.Search @doc """ 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 new file mode 100644 index 000000000..ff0dbb8e8 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/pleroma_follow_request_operation.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.PleromaFollowRequestOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Follow requests"], + summary: "Retrieve outgoing follow requests", + security: [%{"oAuth" => ["read:follows", "follow"]}], + operationId: "PleromaFollowRequestController.index", + responses: %{ + 200 => + Operation.response("Array of Account", "application/json", %Schema{ + type: :array, + items: Account, + example: [Account.schema().example] + }) + } + } + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex b/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex new file mode 100644 index 000000000..8967c378f --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/follow_request_controller.ex @@ -0,0 +1,25 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.FollowRequestController do + use Pleroma.Web, :controller + + alias Pleroma.User + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + plug(OAuthScopesPlug, %{scopes: ["follow", "read:follows"]}) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaFollowRequestOperation + + @doc "GET /api/v1/pleroma/outgoing_follow_requests" + def index(%{assigns: %{user: follower}} = conn, _params) do + follow_requests = User.get_outgoing_follow_requests(follower) + + render(conn, "index.json", for: follower, users: follow_requests, as: :user) + end +end diff --git a/lib/pleroma/web/pleroma_api/views/follow_request_view.ex b/lib/pleroma/web/pleroma_api/views/follow_request_view.ex new file mode 100644 index 000000000..17edd1bcc --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/follow_request_view.ex @@ -0,0 +1,10 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.FollowRequestView do + use Pleroma.Web, :view + alias Pleroma.Web.MastodonAPI + + def render(view, opts), do: MastodonAPI.AccountView.render(view, opts) +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 0423ca9e2..d1088a808 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -602,6 +602,8 @@ defmodule Pleroma.Web.Router do post("/bookmark_folders", BookmarkFolderController, :create) patch("/bookmark_folders/:id", BookmarkFolderController, :update) delete("/bookmark_folders/:id", BookmarkFolderController, :delete) + + get("/outgoing_follow_requests", FollowRequestController, :index) end scope [] do diff --git a/test/pleroma/web/pleroma_api/controllers/follow_request_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/follow_request_controller_test.exs new file mode 100644 index 000000000..46109e35e --- /dev/null +++ b/test/pleroma/web/pleroma_api/controllers/follow_request_controller_test.exs @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.FollowRequestControllerTest do + use Pleroma.Web.ConnCase, async: true + + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "/api/v1/pleroma/outgoing_follow_requests works" do + %{conn: conn, user: user} = oauth_access(["read:follows"]) + + other_user1 = insert(:user) + other_user2 = insert(:user, is_locked: true) + _other_user3 = insert(:user) + + {:ok, _, _, _} = CommonAPI.follow(other_user1, user) + {:ok, _, _, _} = CommonAPI.follow(other_user2, user) + + conn = get(conn, "/api/v1/pleroma/outgoing_follow_requests") + + assert [relationship] = json_response_and_validate_schema(conn, 200) + assert to_string(other_user2.id) == relationship["id"] + end +end