Account endorsements

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2021-11-29 12:43:29 +01:00
parent c97f99ccf2
commit a9b0027071
9 changed files with 179 additions and 18 deletions

View file

@ -9,7 +9,8 @@
mute: 2, mute: 2,
reblog_mute: 3, reblog_mute: 3,
notification_mute: 4, notification_mute: 4,
inverse_subscription: 5 inverse_subscription: 5,
endorsement: 6
) )
defenum(Pleroma.FollowingRelationship.State, defenum(Pleroma.FollowingRelationship.State,

View file

@ -78,7 +78,11 @@ defmodule Pleroma.User do
inverse_subscription: [ inverse_subscription: [
subscribee_subscriptions: :subscriber_users, subscribee_subscriptions: :subscriber_users,
subscriber_subscriptions: :subscribee_users subscriber_subscriptions: :subscribee_users
] ],
endorsement: [
endorser_endorsements: :endorsed_users,
endorsee_endorsements: :endorser_users
],
] ]
@cachex Pleroma.Config.get([:cachex, :provider], Cachex) @cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@ -168,25 +172,25 @@ defmodule Pleroma.User do
{incoming_relation, incoming_relation_source} {incoming_relation, incoming_relation_source}
]} <- @user_relationships_config do ]} <- @user_relationships_config do
# Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes, # Definitions of `has_many` relations: :blocker_blocks, :muter_mutes, :reblog_muter_mutes,
# :notification_muter_mutes, :subscribee_subscriptions # :notification_muter_mutes, :subscribee_subscriptions, :endorser_endorsements
has_many(outgoing_relation, UserRelationship, has_many(outgoing_relation, UserRelationship,
foreign_key: :source_id, foreign_key: :source_id,
where: [relationship_type: relationship_type] where: [relationship_type: relationship_type]
) )
# Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes, # Definitions of `has_many` relations: :blockee_blocks, :mutee_mutes, :reblog_mutee_mutes,
# :notification_mutee_mutes, :subscriber_subscriptions # :notification_mutee_mutes, :subscriber_subscriptions, :endorsee_endorsements
has_many(incoming_relation, UserRelationship, has_many(incoming_relation, UserRelationship,
foreign_key: :target_id, foreign_key: :target_id,
where: [relationship_type: relationship_type] where: [relationship_type: relationship_type]
) )
# Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users, # Definitions of `has_many` relations: :blocked_users, :muted_users, :reblog_muted_users,
# :notification_muted_users, :subscriber_users # :notification_muted_users, :subscriber_users, :endorsed_users
has_many(outgoing_relation_target, through: [outgoing_relation, :target]) has_many(outgoing_relation_target, through: [outgoing_relation, :target])
# Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users, # Definitions of `has_many` relations: :blocker_users, :muter_users, :reblog_muter_users,
# :notification_muter_users, :subscribee_users # :notification_muter_users, :subscribee_users, :endorser_users
has_many(incoming_relation_source, through: [incoming_relation, :source]) has_many(incoming_relation_source, through: [incoming_relation, :source])
end end
@ -214,7 +218,7 @@ defmodule Pleroma.User do
@user_relationships_config do @user_relationships_config do
# `def blocked_users_relation/2`, `def muted_users_relation/2`, # `def blocked_users_relation/2`, `def muted_users_relation/2`,
# `def reblog_muted_users_relation/2`, `def notification_muted_users/2`, # `def reblog_muted_users_relation/2`, `def notification_muted_users/2`,
# `def subscriber_users/2` # `def subscriber_users/2`, `def endorsed_users_relation/2`
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
target_users_query = assoc(user, unquote(outgoing_relation_target)) target_users_query = assoc(user, unquote(outgoing_relation_target))
@ -227,7 +231,7 @@ def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated?
end end
# `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`, # `def blocked_users/2`, `def muted_users/2`, `def reblog_muted_users/2`,
# `def notification_muted_users/2`, `def subscriber_users/2` # `def notification_muted_users/2`, `def subscriber_users/2`, `def endorsed_users/2`
def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
__MODULE__ __MODULE__
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [ |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
@ -238,7 +242,8 @@ def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
end end
# `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`, # `def blocked_users_ap_ids/2`, `def muted_users_ap_ids/2`, `def reblog_muted_users_ap_ids/2`,
# `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2` # `def notification_muted_users_ap_ids/2`, `def subscriber_users_ap_ids/2`,
# `def endorsed_users_ap_ids/2`
def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
__MODULE__ __MODULE__
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [ |> apply(unquote(:"#{outgoing_relation_target}_relation"), [
@ -1514,6 +1519,30 @@ def unblock(%User{} = blocker, %{ap_id: ap_id}) do
unblock(blocker, get_cached_by_ap_id(ap_id)) unblock(blocker, get_cached_by_ap_id(ap_id))
end end
def endorse(%User{} = endorser, %User{} = target) do
if not following?(endorser, target) do
{:error, "Could not endorse: You are not following #{target.nickname}"}
else
UserRelationship.create_endorsement(endorser, target)
end
end
def endorse(%User{} = endorser, %{ap_id: ap_id}) do
with %User{} = endorsed <- get_cached_by_ap_id(ap_id) do
endorse(endorser, endorsed)
end
end
def unendorse(%User{} = unendorser, %User{} = target) do
UserRelationship.delete_endorsement(unendorser, target)
end
def unendorse(%User{} = unendorser, %{ap_id: ap_id}) do
with %User{} = user <- get_cached_by_ap_id(ap_id) do
unendorse(unendorser, user)
end
end
def mutes?(nil, _), do: false def mutes?(nil, _), do: false
def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target) def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
@ -1559,6 +1588,10 @@ def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
end end
end end
def endorses?(%User{} = user, %User{} = target) do
UserRelationship.endorsement_exists?(user, target)
end
@doc """ @doc """
Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type. Returns map of outgoing (blocked, muted etc.) relationships' user AP IDs by relation type.
E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}` E.g. `outgoing_relationships_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`

View file

@ -24,17 +24,20 @@ defmodule Pleroma.UserRelationship do
for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
# `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`, # `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
# `def create_notification_mute/2`, `def create_inverse_subscription/2` # `def create_notification_mute/2`, `def create_inverse_subscription/2`,
# `def endorsement/2`
def unquote(:"create_#{relationship_type}")(source, target), def unquote(:"create_#{relationship_type}")(source, target),
do: create(unquote(relationship_type), source, target) do: create(unquote(relationship_type), source, target)
# `def delete_block/2`, `def delete_mute/2`, `def delete_reblog_mute/2`, # `def delete_block/2`, `def delete_mute/2`, `def delete_reblog_mute/2`,
# `def delete_notification_mute/2`, `def delete_inverse_subscription/2` # `def delete_notification_mute/2`, `def delete_inverse_subscription/2`,
# `def delete_endorsement/2`
def unquote(:"delete_#{relationship_type}")(source, target), def unquote(:"delete_#{relationship_type}")(source, target),
do: delete(unquote(relationship_type), source, target) do: delete(unquote(relationship_type), source, target)
# `def block_exists?/2`, `def mute_exists?/2`, `def reblog_mute_exists?/2`, # `def block_exists?/2`, `def mute_exists?/2`, `def reblog_mute_exists?/2`,
# `def notification_mute_exists?/2`, `def inverse_subscription_exists?/2` # `def notification_mute_exists?/2`, `def inverse_subscription_exists?/2`,
# `def inverse_endorsement?/2`
def unquote(:"#{relationship_type}_exists?")(source, target), def unquote(:"#{relationship_type}_exists?")(source, target),
do: exists?(unquote(relationship_type), source, target) do: exists?(unquote(relationship_type), source, target)
end end

View file

@ -328,6 +328,35 @@ def unblock_operation do
} }
end end
def endorse_operation do
%Operation{
tags: ["Account actions"],
summary: "Endorse",
operationId: "AccountController.endorse",
security: [%{"oAuth" => ["follow", "write:accounts"]}],
description:
"Addds the given account to endorsed accounts list.",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
def unendorse_operation do
%Operation{
tags: ["Account actions"],
summary: "Unendorse",
operationId: "AccountController.unendorse",
security: [%{"oAuth" => ["follow", "write:accounts"]}],
description: "Removes the given account from endorsed accounts list.",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship)
}
}
end
def follow_by_uri_operation do def follow_by_uri_operation do
%Operation{ %Operation{
tags: ["Account actions"], tags: ["Account actions"],

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do
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
@ -62,6 +63,27 @@ def favourites_operation do
} }
end end
def endorsements_operation do
%Operation{
tags: ["Retrieve account information"],
summary: "Endorsements",
description: "Returns endorsed accounts",
operationId: "PleromaAPI.AccountController.endorsements",
parameters: [id_param() | pagination_params()],
security: [%{"oAuth" => ["read:account"]}],
responses: %{
200 =>
Operation.response(
"Array of Accounts",
"application/json",
AccountOperation.array_of_accounts()
),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def subscribe_operation do def subscribe_operation do
%Operation{ %Operation{
tags: ["Account actions"], tags: ["Account actions"],

View file

@ -53,7 +53,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
when action in [:verify_credentials, :endorsements, :identity_proofs] when action in [:verify_credentials, :endorsements, :identity_proofs]
) )
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials) plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]} when action in [:update_credentials, :endorse, :unendorse]
)
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
@ -79,7 +82,7 @@ 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)a @needs_account ~W(followers following lists follow unfollow mute unmute block unblock endorse unendorse)a
plug( plug(
RateLimiter, RateLimiter,
@ -435,6 +438,24 @@ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
end end
end end
@doc "POST /api/v1/accounts/:id/mute"
def endorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
with {:ok, _user_relationships} <- User.endorse(endorser, endorsed) do
render(conn, "relationship.json", user: endorser, target: endorsed)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "POST /api/v1/accounts/:id/unmute"
def unendorse(%{assigns: %{user: endorser, account: endorsed}} = conn, _params) do
with {:ok, _user_relationships} <- User.unendorse(endorser, endorsed) do
render(conn, "relationship.json", user: endorser, target: endorsed)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "POST /api/v1/follows" @doc "POST /api/v1/follows"
def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do case User.get_cached_by_nickname(uri) do
@ -478,7 +499,21 @@ def blocks(%{assigns: %{user: user}} = conn, params) do
end end
@doc "GET /api/v1/endorsements" @doc "GET /api/v1/endorsements"
def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, params) def endorsements(%{assigns: %{user: user}} = conn, params) do
users =
user
|> User.endorsed_users_relation(_restrict_deactivated = true)
|> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
conn
|> add_link_headers(users)
|> render("index.json",
users: users,
for: user,
as: :user,
embed_relationships: embed_relationships?(params)
)
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)

View file

@ -156,7 +156,14 @@ def render(
target, target,
&User.muting_reblogs?(&1, &2) &User.muting_reblogs?(&1, &2)
), ),
endorsed: false endorsed:
UserRelationship.exists?(
user_relationships,
:endorsement,
target,
reading_user,
&User.endorses?(&2, &1)
),
} }
end end

View file

@ -6,7 +6,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, import Pleroma.Web.ControllerHelper,
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2] only: [
json_response: 3,
add_link_headers: 2,
embed_relationships?: 1,
assign_account_by_id: 2
]
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
@ -40,9 +45,15 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites %{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
) )
plug(
OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
when action == :endorsements
)
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, :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
@ -90,6 +101,23 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
) )
end end
@doc "GET /api/v1/pleroma/accounts/:id/endorsements"
def endorsements(%{assigns: %{user: for_user, account: user}} = conn, params) do
users =
user
|> User.endorsed_users_relation(_restrict_deactivated = true)
|> Pleroma.Pagination.fetch_paginated(Map.put(params, :skip_order, true))
conn
|> add_link_headers(users)
|> render("index.json",
for: for_user,
users: users,
as: :user,
embed_relationships: embed_relationships?(params)
)
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

View file

@ -411,6 +411,7 @@ defmodule Pleroma.Web.Router do
scope [] do scope [] do
pipe_through(:api) pipe_through(:api)
get("/accounts/:id/favourites", AccountController, :favourites) get("/accounts/:id/favourites", AccountController, :favourites)
get("/accounts/:id/endorsements", AccountController, :endorsements)
end end
scope [] do scope [] do
@ -456,6 +457,8 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/unblock", AccountController, :unblock) post("/accounts/:id/unblock", AccountController, :unblock)
post("/accounts/:id/mute", AccountController, :mute) post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute) post("/accounts/:id/unmute", AccountController, :unmute)
post("/accounts/:id/pin", AccountController, :endorse)
post("/accounts/:id/unpin", AccountController, :unendorse)
get("/conversations", ConversationController, :index) get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read) post("/conversations/:id/read", ConversationController, :mark_as_read)