mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2024-12-22 16:16:34 +00:00
WIP account endorsements
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
4f249b2397
commit
0f90fd5805
11 changed files with 107 additions and 51 deletions
|
@ -258,7 +258,8 @@ config :pleroma, :instance,
|
||||||
show_reactions: true,
|
show_reactions: true,
|
||||||
password_reset_token_validity: 60 * 60 * 24,
|
password_reset_token_validity: 60 * 60 * 24,
|
||||||
profile_directory: true,
|
profile_directory: true,
|
||||||
privileged_staff: false
|
privileged_staff: false,
|
||||||
|
max_endorsed_users: 20
|
||||||
|
|
||||||
config :pleroma, :welcome,
|
config :pleroma, :welcome,
|
||||||
direct_message: [
|
direct_message: [
|
||||||
|
|
|
@ -742,6 +742,16 @@ config :pleroma, :config_description, [
|
||||||
3
|
3
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
%{
|
||||||
|
key: :max_endorsed_users,
|
||||||
|
type: :integer,
|
||||||
|
description: "The maximum number of recommended accounts. 0 will disable the feature.",
|
||||||
|
suggestions: [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
3
|
||||||
|
]
|
||||||
|
},
|
||||||
%{
|
%{
|
||||||
key: :autofollowed_nicknames,
|
key: :autofollowed_nicknames,
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
|
|
|
@ -377,12 +377,6 @@ Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer feat
|
||||||
|
|
||||||
- `GET /api/v1/identity_proofs`: Returns an empty array, `[]`
|
- `GET /api/v1/identity_proofs`: Returns an empty array, `[]`
|
||||||
|
|
||||||
### Endorsements
|
|
||||||
|
|
||||||
*Added in Mastodon 2.5.0*
|
|
||||||
|
|
||||||
- `GET /api/v1/endorsements`: Returns an empty array, `[]`
|
|
||||||
|
|
||||||
### Featured tags
|
### Featured tags
|
||||||
|
|
||||||
*Added in Mastodon 3.0.0*
|
*Added in Mastodon 3.0.0*
|
||||||
|
|
|
@ -94,8 +94,7 @@ defmodule Pleroma.Pagination do
|
||||||
offset: :integer,
|
offset: :integer,
|
||||||
limit: :integer,
|
limit: :integer,
|
||||||
skip_extra_order: :boolean,
|
skip_extra_order: :boolean,
|
||||||
skip_order: :boolean,
|
skip_order: :boolean
|
||||||
shuffle: :boolean,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
|
||||||
|
@ -114,10 +113,6 @@ defmodule Pleroma.Pagination do
|
||||||
where(query, [{q, table_position(query, table_binding)}], q.id < ^max_id)
|
where(query, [{q, table_position(query, table_binding)}], q.id < ^max_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict(query, :order, %{shuffle: true}, _) do
|
|
||||||
order_by(query, [u], fragment("RANDOM()"))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp restrict(query, :order, %{skip_order: true}, _), do: query
|
defp restrict(query, :order, %{skip_order: true}, _), do: query
|
||||||
|
|
||||||
defp restrict(%{order_bys: [_ | _]} = query, :order, %{skip_extra_order: true}, _), do: query
|
defp restrict(%{order_bys: [_ | _]} = query, :order, %{skip_extra_order: true}, _), do: query
|
||||||
|
|
|
@ -82,7 +82,7 @@ defmodule Pleroma.User do
|
||||||
endorsement: [
|
endorsement: [
|
||||||
endorser_endorsements: :endorsed_users,
|
endorser_endorsements: :endorsed_users,
|
||||||
endorsee_endorsements: :endorser_users
|
endorsee_endorsements: :endorser_users
|
||||||
],
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||||
|
@ -1522,12 +1522,22 @@ defmodule Pleroma.User do
|
||||||
end
|
end
|
||||||
|
|
||||||
def endorse(%User{} = endorser, %User{} = target) do
|
def endorse(%User{} = endorser, %User{} = target) do
|
||||||
if not following?(endorser, target) do
|
with max_endorsed_users <- Pleroma.Config.get([:instance, :max_endorsed_users], 0),
|
||||||
|
endorsed_users <-
|
||||||
|
User.endorsed_users_relation(endorser)
|
||||||
|
|> Pleroma.Repo.all() do
|
||||||
|
cond do
|
||||||
|
Enum.count(endorsed_users) >= max_endorsed_users ->
|
||||||
|
{:error, "You have already pinned the maximum number of users"}
|
||||||
|
|
||||||
|
not following?(endorser, target) ->
|
||||||
{:error, "Could not endorse: You are not following #{target.nickname}"}
|
{:error, "Could not endorse: You are not following #{target.nickname}"}
|
||||||
else
|
|
||||||
|
true ->
|
||||||
UserRelationship.create_endorsement(endorser, target)
|
UserRelationship.create_endorsement(endorser, target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def endorse(%User{} = endorser, %{ap_id: ap_id}) do
|
def endorse(%User{} = endorser, %{ap_id: ap_id}) do
|
||||||
with %User{} = endorsed <- get_cached_by_ap_id(ap_id) do
|
with %User{} = endorsed <- get_cached_by_ap_id(ap_id) do
|
||||||
|
|
|
@ -343,7 +343,15 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
||||||
description: "Addds the given account to endorsed accounts list.",
|
description: "Addds the given account to endorsed accounts list.",
|
||||||
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
|
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Relationship", "application/json", AccountRelationship)
|
200 => Operation.response("Relationship", "application/json", AccountRelationship),
|
||||||
|
400 =>
|
||||||
|
Operation.response("Bad Request", "application/json", %Schema{
|
||||||
|
allOf: [ApiError],
|
||||||
|
title: "Unprocessable Entity",
|
||||||
|
example: %{
|
||||||
|
"error" => "You have already pinned the maximum number of users"
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -453,10 +461,10 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
|
||||||
tags: ["Retrieve account information"],
|
tags: ["Retrieve account information"],
|
||||||
summary: "Endorsements",
|
summary: "Endorsements",
|
||||||
operationId: "AccountController.endorsements",
|
operationId: "AccountController.endorsements",
|
||||||
description: "Not implemented",
|
description: "Returns endorsed accounts",
|
||||||
security: [%{"oAuth" => ["read:accounts"]}],
|
security: [%{"oAuth" => ["read:accounts"]}],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => empty_array_response()
|
200 => Operation.response("Array of Accounts", "application/json", array_of_accounts())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
||||||
alias OpenApiSpex.Operation
|
alias OpenApiSpex.Operation
|
||||||
|
alias Pleroma.Web.ApiSpec.AccountOperation
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
|
alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
|
||||||
alias Pleroma.Web.ApiSpec.AccountOperation
|
|
||||||
alias Pleroma.Web.ApiSpec.StatusOperation
|
alias Pleroma.Web.ApiSpec.StatusOperation
|
||||||
|
|
||||||
import Pleroma.Web.ApiSpec.Helpers
|
import Pleroma.Web.ApiSpec.Helpers
|
||||||
|
@ -69,17 +69,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
||||||
summary: "Endorsements",
|
summary: "Endorsements",
|
||||||
description: "Returns endorsed accounts",
|
description: "Returns endorsed accounts",
|
||||||
operationId: "PleromaAPI.AccountController.endorsements",
|
operationId: "PleromaAPI.AccountController.endorsements",
|
||||||
parameters:
|
parameters: [with_relationships_param(), id_param()],
|
||||||
[
|
|
||||||
Operation.parameter(
|
|
||||||
:shuffle,
|
|
||||||
:query,
|
|
||||||
:boolean,
|
|
||||||
"Show endorsed accounts in random order"
|
|
||||||
),
|
|
||||||
id_param()
|
|
||||||
] ++ pagination_params(),
|
|
||||||
security: [%{"oAuth" => ["read:account"]}],
|
|
||||||
responses: %{
|
responses: %{
|
||||||
200 =>
|
200 =>
|
||||||
Operation.response(
|
Operation.response(
|
||||||
|
@ -87,7 +77,6 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
|
||||||
"application/json",
|
"application/json",
|
||||||
AccountOperation.array_of_accounts()
|
AccountOperation.array_of_accounts()
|
||||||
),
|
),
|
||||||
403 => Operation.response("Forbidden", "application/json", ApiError),
|
|
||||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,8 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
def unfollow(follower, unfollowed) do
|
def unfollow(follower, unfollowed) do
|
||||||
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed),
|
||||||
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
|
{:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed),
|
||||||
{:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
|
{:ok, _subscription} <- User.unsubscribe(follower, unfollowed),
|
||||||
|
{:ok, _endorsement} <- User.unendorse(follower, unfollowed) do
|
||||||
{:ok, follower}
|
{:ok, follower}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -84,7 +84,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
|
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
|
||||||
|
|
||||||
@relationship_actions [:follow, :unfollow]
|
@relationship_actions [:follow, :unfollow]
|
||||||
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock endorse unendorse endorse unendorse)a
|
@needs_account ~W(
|
||||||
|
followers following lists follow unfollow mute unmute block unblock note endorse unendorse
|
||||||
|
)a
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
RateLimiter,
|
RateLimiter,
|
||||||
|
@ -450,16 +452,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "POST /api/v1/accounts/:id/mute"
|
@doc "POST /api/v1/accounts/:id/pin"
|
||||||
def endorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
|
def endorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
|
||||||
with {:ok, _user_relationships} <- User.endorse(endorser, endorsed) do
|
with {:ok, _user_relationships} <- User.endorse(endorser, endorsed) do
|
||||||
render(conn, "relationship.json", user: endorser, target: endorsed)
|
render(conn, "relationship.json", user: endorser, target: endorsed)
|
||||||
else
|
else
|
||||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
{:error, message} -> json_response(conn, :bad_request, %{error: message})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "POST /api/v1/accounts/:id/unmute"
|
@doc "POST /api/v1/accounts/:id/unpin"
|
||||||
def unendorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
|
def unendorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
|
||||||
with {:ok, _user_relationships} <- User.unendorse(endorser, endorsed) do
|
with {:ok, _user_relationships} <- User.unendorse(endorser, endorsed) do
|
||||||
render(conn, "relationship.json", user: endorser, target: endorsed)
|
render(conn, "relationship.json", user: endorser, target: endorsed)
|
||||||
|
|
|
@ -53,7 +53,10 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
||||||
|
|
||||||
plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
|
plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
|
||||||
|
|
||||||
plug(:assign_account_by_id when action in [:favourites, :endorsements, :subscribe, :unsubscribe])
|
plug(
|
||||||
|
:assign_account_by_id
|
||||||
|
when action in [:favourites, :endorsements, :subscribe, :unsubscribe]
|
||||||
|
)
|
||||||
|
|
||||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaAccountOperation
|
||||||
|
|
||||||
|
@ -106,7 +109,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
||||||
users =
|
users =
|
||||||
user
|
user
|
||||||
|> User.endorsed_users_relation(_restrict_deactivated = true)
|
|> User.endorsed_users_relation(_restrict_deactivated = true)
|
||||||
|> fetch_paginated_endorsements(params)
|
|> Pleroma.Repo.all()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(users)
|
|> add_link_headers(users)
|
||||||
|
@ -118,16 +121,6 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fetch_paginated_endorsements(user, %{shuffle: true} = params) do
|
|
||||||
user
|
|
||||||
|> Pleroma.Pagination.fetch_paginated(Map.put(params, :shuffle, true))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp fetch_paginated_endorsements(user, params) do
|
|
||||||
user
|
|
||||||
|> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
|
@doc "POST /api/v1/pleroma/accounts/:id/subscribe"
|
||||||
def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
|
def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
|
||||||
with {:ok, _subscription} <- User.subscribe(user, subscription_target) do
|
with {:ok, _subscription} <- User.subscribe(user, subscription_target) do
|
||||||
|
|
|
@ -1838,4 +1838,57 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
|
||||||
|> get("/api/v1/accounts/relationships?id=#{other_user.id}")
|
|> get("/api/v1/accounts/relationships?id=#{other_user.id}")
|
||||||
|> json_response_and_validate_schema(200)
|
|> json_response_and_validate_schema(200)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "account endorsements" do
|
||||||
|
setup do: oauth_access(["read:accounts", "write:accounts", "write:follows"])
|
||||||
|
|
||||||
|
setup do: clear_config([:instance, :max_endorsed_users], 1)
|
||||||
|
|
||||||
|
test "pin account", %{user: user, conn: conn} do
|
||||||
|
%{id: id1} = insert(:user)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/accounts/#{id1}/follow")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert %{"id" => ^id1, "endorsed" => true} =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/accounts/#{id1}/pin")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert [%{"id" => ^id1}] =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> get("/api/v1/endorsements")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "max pinned accounts", %{user: user, conn: conn} do
|
||||||
|
%{id: id1} = insert(:user)
|
||||||
|
%{id: id2} = insert(:user)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/accounts/#{id1}/follow")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/accounts/#{id2}/follow")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/accounts/#{id1}/pin")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert %{"error" => "You have already pinned the maximum number of users"} =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/v1/accounts/#{id2}/pin")
|
||||||
|
|> json_response_and_validate_schema(400)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue