Merge branch 'familiar-followers' into 'develop'

Implement `/api/v1/accounts/familiar_followers`

See merge request pleroma/pleroma!4098
This commit is contained in:
lain 2024-05-19 12:05:55 +00:00
commit e8cd6662eb
8 changed files with 194 additions and 1 deletions

View File

@ -0,0 +1 @@
Implement `/api/v1/accounts/familiar_followers`

View File

@ -1404,6 +1404,40 @@ defmodule Pleroma.User do
|> Repo.all() |> Repo.all()
end end
@spec get_familiar_followers_query(User.t(), User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_familiar_followers_query(%User{} = user, %User{} = current_user, nil) do
friends =
get_friends_query(current_user)
|> where([u], not u.hide_follows)
|> select([u], u.id)
User.Query.build(%{is_active: true})
|> where([u], u.id not in ^[user.id, current_user.id])
|> join(:inner, [u], r in FollowingRelationship,
as: :followers_relationships,
on: r.following_id == ^user.id and r.follower_id == u.id
)
|> where([followers_relationships: r], r.state == ^:follow_accept)
|> where([followers_relationships: r], r.follower_id in subquery(friends))
end
def get_familiar_followers_query(%User{} = user, %User{} = current_user, page) do
user
|> get_familiar_followers_query(current_user, nil)
|> User.Query.paginate(page, 20)
end
@spec get_familiar_followers_query(User.t(), User.t()) :: Ecto.Query.t()
def get_familiar_followers_query(%User{} = user, %User{} = current_user),
do: get_familiar_followers_query(user, current_user, nil)
@spec get_familiar_followers(User.t(), User.t(), pos_integer() | nil) :: {:ok, list(User.t())}
def get_familiar_followers(%User{} = user, %User{} = current_user, page \\ nil) do
user
|> get_familiar_followers_query(current_user, page)
|> Repo.all()
end
def increase_note_count(%User{} = user) do def increase_note_count(%User{} = user) do
User User
|> where(id: ^user.id) |> where(id: ^user.id)

View File

@ -11,6 +11,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
alias Pleroma.Web.ApiSpec.Schemas.ActorType alias Pleroma.Web.ApiSpec.Schemas.ActorType
alias Pleroma.Web.ApiSpec.Schemas.ApiError alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.List alias Pleroma.Web.ApiSpec.Schemas.List
alias Pleroma.Web.ApiSpec.Schemas.Status alias Pleroma.Web.ApiSpec.Schemas.Status
alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
@ -513,6 +514,48 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
} }
end end
def familiar_followers_operation do
%Operation{
tags: ["Retrieve account information"],
summary: "Followers that you follow",
operationId: "AccountController.familiar_followers",
description:
"Obtain a list of all accounts that follow a given account, filtered for accounts you follow.",
security: [%{"oAuth" => ["read:follows"]}],
parameters: [
Operation.parameter(
:id,
:query,
%Schema{
oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
},
"Account IDs",
example: "123"
)
],
responses: %{
200 =>
Operation.response("Accounts", "application/json", %Schema{
title: "ArrayOfAccounts",
type: :array,
items: %Schema{
title: "Account",
type: :object,
properties: %{
id: FlakeID,
accounts: %Schema{
title: "ArrayOfAccounts",
type: :array,
items: Account,
example: [Account.schema().example]
}
}
}
})
}
}
end
defp create_request do defp create_request do
%Schema{ %Schema{
title: "AccountCreateRequest", title: "AccountCreateRequest",

View File

@ -72,7 +72,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
%{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock] %{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock]
) )
plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships) plug(
OAuthScopesPlug,
%{scopes: ["read:follows"]} when action in [:relationships, :familiar_followers]
)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
@ -629,6 +632,35 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
) )
end end
@doc "GET /api/v1/accounts/familiar_followers"
def familiar_followers(
%{assigns: %{user: user}, private: %{open_api_spex: %{params: %{id: id}}}} = conn,
_id
) do
users =
User.get_all_by_ids(List.wrap(id))
|> Enum.map(&%{id: &1.id, accounts: get_familiar_followers(&1, user)})
conn
|> render("familiar_followers.json",
for: user,
users: users,
as: :user
)
end
defp get_familiar_followers(%{id: id} = user, %{id: id}) do
User.get_familiar_followers(user, user)
end
defp get_familiar_followers(%{hide_followers: true}, _current_user) do
[]
end
defp get_familiar_followers(user, current_user) do
User.get_familiar_followers(user, current_user)
end
@doc "GET /api/v1/identity_proofs" @doc "GET /api/v1/identity_proofs"
def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params) def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params)
end end

View File

@ -193,6 +193,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
render_many(targets, AccountView, "relationship.json", render_opts) render_many(targets, AccountView, "relationship.json", render_opts)
end end
def render("familiar_followers.json", %{users: users} = opts) do
opts =
opts
|> Map.merge(%{as: :user})
|> Map.delete(:users)
users
|> render_many(AccountView, "familiar_followers.json", opts)
end
def render("familiar_followers.json", %{user: %{id: id, accounts: accounts}} = opts) do
accounts =
accounts
|> render_many(AccountView, "show.json", opts)
|> Enum.filter(&Enum.any?/1)
%{id: id, accounts: accounts}
end
defp do_render("show.json", %{user: user} = opts) do defp do_render("show.json", %{user: user} = opts) do
self = opts[:for] == user self = opts[:for] == user

View File

@ -638,6 +638,7 @@ defmodule Pleroma.Web.Router do
patch("/accounts/update_credentials", AccountController, :update_credentials) patch("/accounts/update_credentials", AccountController, :update_credentials)
get("/accounts/relationships", AccountController, :relationships) get("/accounts/relationships", AccountController, :relationships)
get("/accounts/familiar_followers", AccountController, :familiar_followers)
get("/accounts/:id/lists", AccountController, :lists) get("/accounts/:id/lists", AccountController, :lists)
get("/accounts/:id/identity_proofs", AccountController, :identity_proofs) get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
get("/endorsements", AccountController, :endorsements) get("/endorsements", AccountController, :endorsements)

View File

@ -2894,6 +2894,20 @@ defmodule Pleroma.UserTest do
end end
end end
describe "get_familiar_followers/3" do
test "returns familiar followers for a pair of users" do
user1 = insert(:user)
%{id: id2} = user2 = insert(:user)
user3 = insert(:user)
_user4 = insert(:user)
User.follow(user1, user2)
User.follow(user2, user3)
assert [%{id: ^id2}] = User.get_familiar_followers(user3, user1)
end
end
describe "account endorsements" do describe "account endorsements" do
test "it pins people" do test "it pins people" do
user = insert(:user) user = insert(:user)

View File

@ -2172,6 +2172,55 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
end end
end end
describe "familiar followers" do
setup do: oauth_access(["read:follows"])
test "fetch user familiar followers", %{user: user, conn: conn} do
%{id: id1} = other_user1 = insert(:user)
%{id: id2} = other_user2 = insert(:user)
_ = insert(:user)
User.follow(user, other_user1)
User.follow(other_user1, other_user2)
assert [%{"accounts" => [%{"id" => ^id1}], "id" => ^id2}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/familiar_followers?id[]=#{id2}")
|> json_response_and_validate_schema(200)
end
test "returns empty array if followers are hidden", %{user: user, conn: conn} do
other_user1 = insert(:user, hide_follows: true)
%{id: id2} = other_user2 = insert(:user)
_ = insert(:user)
User.follow(user, other_user1)
User.follow(other_user1, other_user2)
assert [%{"accounts" => [], "id" => ^id2}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/familiar_followers?id[]=#{id2}")
|> json_response_and_validate_schema(200)
end
test "it respects hide_followers", %{user: user, conn: conn} do
other_user1 = insert(:user)
%{id: id2} = other_user2 = insert(:user, hide_followers: true)
_ = insert(:user)
User.follow(user, other_user1)
User.follow(other_user1, other_user2)
assert [%{"accounts" => [], "id" => ^id2}] =
conn
|> put_req_header("content-type", "application/json")
|> get("/api/v1/accounts/familiar_followers?id[]=#{id2}")
|> json_response_and_validate_schema(200)
end
end
describe "remove from followers" do describe "remove from followers" do
setup do: oauth_access(["follow"]) setup do: oauth_access(["follow"])