Merge branch '1335-user-api-id-fields-relations' into 'develop'

[#1335] User: refactoring of AP ID fields into relations

Closes #1335

See merge request pleroma/pleroma!1963
This commit is contained in:
lain 2019-12-05 17:16:23 +00:00
commit 67d8df04a4
34 changed files with 765 additions and 265 deletions

View File

@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- OStatus: Extract RSS functionality - OStatus: Extract RSS functionality
- Deprecated `User.Info` embedded schema (fields moved to `User`) - Deprecated `User.Info` embedded schema (fields moved to `User`)
- Store status data inside Flag activity - Store status data inside Flag activity
- Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`).
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>

13
lib/pleroma/ecto_enums.ex Normal file
View File

@ -0,0 +1,13 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
import EctoEnum
defenum(UserRelationshipTypeEnum,
block: 1,
mute: 2,
reblog_mute: 3,
notification_mute: 4,
inverse_subscription: 5
)

View File

@ -21,6 +21,8 @@ defmodule Pleroma.Notification do
@type t :: %__MODULE__{} @type t :: %__MODULE__{}
@include_muted_option :with_muted
schema "notifications" do schema "notifications" do
field(:seen, :boolean, default: false) field(:seen, :boolean, default: false)
belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
@ -34,7 +36,25 @@ def changeset(%Notification{} = notification, attrs) do
|> cast(attrs, [:seen]) |> cast(attrs, [:seen])
end end
def for_user_query(user, opts \\ []) do defp for_user_query_ap_id_opts(user, opts) do
ap_id_relations =
[:block] ++
if opts[@include_muted_option], do: [], else: [:notification_mute]
preloaded_ap_ids = User.outgoing_relations_ap_ids(user, ap_id_relations)
exclude_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts)
exclude_notification_muted_opts =
Map.merge(%{notification_muted_users_ap_ids: preloaded_ap_ids[:notification_mute]}, opts)
{exclude_blocked_opts, exclude_notification_muted_opts}
end
def for_user_query(user, opts \\ %{}) do
{exclude_blocked_opts, exclude_notification_muted_opts} =
for_user_query_ap_id_opts(user, opts)
Notification Notification
|> where(user_id: ^user.id) |> where(user_id: ^user.id)
|> where( |> where(
@ -54,27 +74,32 @@ def for_user_query(user, opts \\ []) do
) )
) )
|> preload([n, a, o], activity: {a, object: o}) |> preload([n, a, o], activity: {a, object: o})
|> exclude_muted(user, opts) |> exclude_notification_muted(user, exclude_notification_muted_opts)
|> exclude_blocked(user) |> exclude_blocked(user, exclude_blocked_opts)
|> exclude_visibility(opts) |> exclude_visibility(opts)
end end
defp exclude_blocked(query, user) do defp exclude_blocked(query, user, opts) do
blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
query query
|> where([n, a], a.actor not in ^user.blocks) |> where([n, a], a.actor not in ^blocked_ap_ids)
|> where( |> where(
[n, a], [n, a],
fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
) )
end end
defp exclude_muted(query, _, %{with_muted: true}) do defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
query query
end end
defp exclude_muted(query, user, _opts) do defp exclude_notification_muted(query, user, opts) do
notification_muted_ap_ids =
opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user)
query query
|> where([n, a], a.actor not in ^user.muted_notifications) |> where([n, a], a.actor not in ^notification_muted_ap_ids)
|> join(:left, [n, a], tm in Pleroma.ThreadMute, |> join(:left, [n, a], tm in Pleroma.ThreadMute,
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data) on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
) )

View File

@ -7,6 +7,7 @@ defmodule Pleroma.User do
import Ecto.Changeset import Ecto.Changeset
import Ecto.Query import Ecto.Query
import Ecto, only: [assoc: 2]
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Ecto.Multi alias Ecto.Multi
@ -21,6 +22,7 @@ defmodule Pleroma.User do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.RepoStreamer alias Pleroma.RepoStreamer
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
@ -42,6 +44,32 @@ defmodule Pleroma.User do
@strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/ @strict_local_nickname_regex ~r/^[a-zA-Z\d]+$/
@extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/ @extended_local_nickname_regex ~r/^[a-zA-Z\d_-]+$/
# AP ID user relationships (blocks, mutes etc.)
# Format: [rel_type: [outgoing_rel: :outgoing_rel_target, incoming_rel: :incoming_rel_source]]
@user_relationships_config [
block: [
blocker_blocks: :blocked_users,
blockee_blocks: :blocker_users
],
mute: [
muter_mutes: :muted_users,
mutee_mutes: :muter_users
],
reblog_mute: [
reblog_muter_mutes: :reblog_muted_users,
reblog_mutee_mutes: :reblog_muter_users
],
notification_mute: [
notification_muter_mutes: :notification_muted_users,
notification_mutee_mutes: :notification_muter_users
],
# Note: `inverse_subscription` relationship is inverse: subscriber acts as relationship target
inverse_subscription: [
subscribee_subscriptions: :subscriber_users,
subscriber_subscriptions: :subscribee_users
]
]
schema "users" do schema "users" do
field(:bio, :string) field(:bio, :string)
field(:email, :string) field(:email, :string)
@ -61,7 +89,6 @@ defmodule Pleroma.User do
field(:tags, {:array, :string}, default: []) field(:tags, {:array, :string}, default: [])
field(:last_refreshed_at, :naive_datetime_usec) field(:last_refreshed_at, :naive_datetime_usec)
field(:last_digest_emailed_at, :naive_datetime) field(:last_digest_emailed_at, :naive_datetime)
field(:banner, :map, default: %{}) field(:banner, :map, default: %{})
field(:background, :map, default: %{}) field(:background, :map, default: %{})
field(:source_data, :map, default: %{}) field(:source_data, :map, default: %{})
@ -73,12 +100,7 @@ defmodule Pleroma.User do
field(:password_reset_pending, :boolean, default: false) field(:password_reset_pending, :boolean, default: false)
field(:confirmation_token, :string, default: nil) field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public") field(:default_scope, :string, default: "public")
field(:blocks, {:array, :string}, default: [])
field(:domain_blocks, {:array, :string}, default: []) field(:domain_blocks, {:array, :string}, default: [])
field(:mutes, {:array, :string}, default: [])
field(:muted_reblogs, {:array, :string}, default: [])
field(:muted_notifications, {:array, :string}, default: [])
field(:subscribers, {:array, :string}, default: [])
field(:deactivated, :boolean, default: false) field(:deactivated, :boolean, default: false)
field(:no_rich_text, :boolean, default: false) field(:no_rich_text, :boolean, default: false)
field(:ap_enabled, :boolean, default: false) field(:ap_enabled, :boolean, default: false)
@ -120,9 +142,82 @@ defmodule Pleroma.User do
has_many(:registrations, Registration) has_many(:registrations, Registration)
has_many(:deliveries, Delivery) has_many(:deliveries, Delivery)
has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id)
has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id)
for {relationship_type,
[
{outgoing_relation, outgoing_relation_target},
{incoming_relation, incoming_relation_source}
]} <- @user_relationships_config do
# Definitions of `has_many :blocker_blocks`, `has_many :muter_mutes` etc.
has_many(outgoing_relation, UserRelationship,
foreign_key: :source_id,
where: [relationship_type: relationship_type]
)
# Definitions of `has_many :blockee_blocks`, `has_many :mutee_mutes` etc.
has_many(incoming_relation, UserRelationship,
foreign_key: :target_id,
where: [relationship_type: relationship_type]
)
# Definitions of `has_many :blocked_users`, `has_many :muted_users` etc.
has_many(outgoing_relation_target, through: [outgoing_relation, :target])
# Definitions of `has_many :blocker_users`, `has_many :muter_users` etc.
has_many(incoming_relation_source, through: [incoming_relation, :source])
end
# `:blocks` is deprecated (replaced with `blocked_users` relation)
field(:blocks, {:array, :string}, default: [])
# `:mutes` is deprecated (replaced with `muted_users` relation)
field(:mutes, {:array, :string}, default: [])
# `:muted_reblogs` is deprecated (replaced with `reblog_muted_users` relation)
field(:muted_reblogs, {:array, :string}, default: [])
# `:muted_notifications` is deprecated (replaced with `notification_muted_users` relation)
field(:muted_notifications, {:array, :string}, default: [])
# `:subscribers` is deprecated (replaced with `subscriber_users` relation)
field(:subscribers, {:array, :string}, default: [])
timestamps() timestamps()
end end
for {_relationship_type, [{_outgoing_relation, outgoing_relation_target}, _]} <-
@user_relationships_config do
# Definitions of `blocked_users_relation/1`, `muted_users_relation/1`, etc.
def unquote(:"#{outgoing_relation_target}_relation")(user, restrict_deactivated? \\ false) do
target_users_query = assoc(user, unquote(outgoing_relation_target))
if restrict_deactivated? do
restrict_deactivated(target_users_query)
else
target_users_query
end
end
# Definitions of `blocked_users/1`, `muted_users/1`, etc.
def unquote(outgoing_relation_target)(user, restrict_deactivated? \\ false) do
__MODULE__
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
user,
restrict_deactivated?
])
|> Repo.all()
end
# Definitions of `blocked_users_ap_ids/1`, `muted_users_ap_ids/1`, etc.
def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \\ false) do
__MODULE__
|> apply(unquote(:"#{outgoing_relation_target}_relation"), [
user,
restrict_deactivated?
])
|> select([u], u.ap_id)
|> Repo.all()
end
end
@doc "Returns if the user should be allowed to authenticate" @doc "Returns if the user should be allowed to authenticate"
def auth_active?(%User{deactivated: true}), do: false def auth_active?(%User{deactivated: true}), do: false
@ -946,34 +1041,45 @@ def get_recipients_from_activity(%Activity{recipients: to}) do
|> Repo.all() |> Repo.all()
end end
@spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()} @spec mute(User.t(), User.t(), boolean()) ::
def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do {:ok, list(UserRelationship.t())} | {:error, String.t()}
add_to_mutes(muter, ap_id, notifications?) def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
add_to_mutes(muter, mutee, notifications?)
end end
def unmute(muter, %{ap_id: ap_id}) do def unmute(%User{} = muter, %User{} = mutee) do
remove_from_mutes(muter, ap_id) remove_from_mutes(muter, mutee)
end end
def subscribe(subscriber, %{ap_id: ap_id}) do def subscribe(%User{} = subscriber, %User{} = target) do
with %User{} = subscribed <- get_cached_by_ap_id(ap_id) do deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
if blocks?(subscribed, subscriber) and deny_follow_blocked do if blocks?(target, subscriber) and deny_follow_blocked do
{:error, "Could not subscribe: #{subscribed.nickname} is blocking you"} {:error, "Could not subscribe: #{target.nickname} is blocking you"}
else else
User.add_to_subscribers(subscribed, subscriber.ap_id) # Note: the relationship is inverse: subscriber acts as relationship target
end UserRelationship.create_inverse_subscription(target, subscriber)
end end
end end
def unsubscribe(unsubscriber, %{ap_id: ap_id}) do def subscribe(%User{} = subscriber, %{ap_id: ap_id}) do
with %User{} = subscribee <- get_cached_by_ap_id(ap_id) do
subscribe(subscriber, subscribee)
end
end
def unsubscribe(%User{} = unsubscriber, %User{} = target) do
# Note: the relationship is inverse: subscriber acts as relationship target
UserRelationship.delete_inverse_subscription(target, unsubscriber)
end
def unsubscribe(%User{} = unsubscriber, %{ap_id: ap_id}) do
with %User{} = user <- get_cached_by_ap_id(ap_id) do with %User{} = user <- get_cached_by_ap_id(ap_id) do
User.remove_from_subscribers(user, unsubscriber.ap_id) unsubscribe(unsubscriber, user)
end end
end end
def block(blocker, %User{ap_id: ap_id} = blocked) do def block(%User{} = blocker, %User{} = blocked) do
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
blocker = blocker =
if following?(blocker, blocked) do if following?(blocker, blocked) do
@ -990,50 +1096,53 @@ def block(blocker, %User{ap_id: ap_id} = blocked) do
nil -> blocked nil -> blocked
end end
blocker = unsubscribe(blocked, blocker)
if subscribed_to?(blocked, blocker) do
{:ok, blocker} = unsubscribe(blocked, blocker)
blocker
else
blocker
end
if following?(blocked, blocker), do: unfollow(blocked, blocker) if following?(blocked, blocker), do: unfollow(blocked, blocker)
{:ok, blocker} = update_follower_count(blocker) {:ok, blocker} = update_follower_count(blocker)
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked) {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
add_to_block(blocker, ap_id) add_to_block(blocker, blocked)
end end
# helper to handle the block given only an actor's AP id # helper to handle the block given only an actor's AP id
def block(blocker, %{ap_id: ap_id}) do def block(%User{} = blocker, %{ap_id: ap_id}) do
block(blocker, get_cached_by_ap_id(ap_id)) block(blocker, get_cached_by_ap_id(ap_id))
end end
def unblock(blocker, %{ap_id: ap_id}) do def unblock(%User{} = blocker, %User{} = blocked) do
remove_from_block(blocker, ap_id) remove_from_block(blocker, blocked)
end
# helper to handle the block given only an actor's AP id
def unblock(%User{} = blocker, %{ap_id: ap_id}) do
unblock(blocker, get_cached_by_ap_id(ap_id))
end end
def mutes?(nil, _), do: false def mutes?(nil, _), do: false
def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.mutes, ap_id) def mutes?(%User{} = user, %User{} = target), do: mutes_user?(user, target)
def mutes_user?(%User{} = user, %User{} = target) do
UserRelationship.mute_exists?(user, target)
end
@spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean() @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
def muted_notifications?(nil, _), do: false def muted_notifications?(nil, _), do: false
def muted_notifications?(user, %{ap_id: ap_id}), def muted_notifications?(%User{} = user, %User{} = target),
do: Enum.member?(user.muted_notifications, ap_id) do: UserRelationship.notification_mute_exists?(user, target)
def blocks?(%User{} = user, %User{} = target) do
blocks_ap_id?(user, target) || blocks_domain?(user, target)
end
def blocks?(nil, _), do: false def blocks?(nil, _), do: false
def blocks_ap_id?(%User{} = user, %User{} = target) do def blocks?(%User{} = user, %User{} = target) do
Enum.member?(user.blocks, target.ap_id) blocks_user?(user, target) || blocks_domain?(user, target)
end end
def blocks_ap_id?(_, _), do: false def blocks_user?(%User{} = user, %User{} = target) do
UserRelationship.block_exists?(user, target)
end
def blocks_user?(_, _), do: false
def blocks_domain?(%User{} = user, %User{} = target) do def blocks_domain?(%User{} = user, %User{} = target) do
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
@ -1043,28 +1152,41 @@ def blocks_domain?(%User{} = user, %User{} = target) do
def blocks_domain?(_, _), do: false def blocks_domain?(_, _), do: false
def subscribed_to?(user, %{ap_id: ap_id}) do def subscribed_to?(%User{} = user, %User{} = target) do
# Note: the relationship is inverse: subscriber acts as relationship target
UserRelationship.inverse_subscription_exists?(target, user)
end
def subscribed_to?(%User{} = user, %{ap_id: ap_id}) do
with %User{} = target <- get_cached_by_ap_id(ap_id) do with %User{} = target <- get_cached_by_ap_id(ap_id) do
Enum.member?(target.subscribers, user.ap_id) subscribed_to?(user, target)
end end
end end
@spec muted_users(User.t()) :: [User.t()] @doc """
def muted_users(user) do Returns map of outgoing (blocked, muted etc.) relations' user AP IDs by relation type.
User.Query.build(%{ap_id: user.mutes, deactivated: false}) E.g. `outgoing_relations_ap_ids(user, [:block])` -> `%{block: ["https://some.site/users/userapid"]}`
|> Repo.all() """
end @spec outgoing_relations_ap_ids(User.t(), list(atom())) :: %{atom() => list(String.t())}
def outgoing_relations_ap_ids(_, []), do: %{}
@spec blocked_users(User.t()) :: [User.t()] def outgoing_relations_ap_ids(%User{} = user, relationship_types)
def blocked_users(user) do when is_list(relationship_types) do
User.Query.build(%{ap_id: user.blocks, deactivated: false}) db_result =
|> Repo.all() user
end |> assoc(:outgoing_relationships)
|> join(:inner, [user_rel], u in assoc(user_rel, :target))
|> where([user_rel, u], user_rel.relationship_type in ^relationship_types)
|> select([user_rel, u], [user_rel.relationship_type, fragment("array_agg(?)", u.ap_id)])
|> group_by([user_rel, u], user_rel.relationship_type)
|> Repo.all()
|> Enum.into(%{}, fn [k, v] -> {k, v} end)
@spec subscribers(User.t()) :: [User.t()] Enum.into(
def subscribers(user) do relationship_types,
User.Query.build(%{ap_id: user.subscribers, deactivated: false}) %{},
|> Repo.all() fn rel_type -> {rel_type, db_result[rel_type] || []} end
)
end end
def deactivate_async(user, status \\ true) do def deactivate_async(user, status \\ true) do
@ -1171,7 +1293,7 @@ def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
blocked_identifiers, blocked_identifiers,
fn blocked_identifier -> fn blocked_identifier ->
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier), with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
{:ok, blocker} <- block(blocker, blocked), {:ok, _user_block} <- block(blocker, blocked),
{:ok, _} <- ActivityPub.block(blocker, blocked) do {:ok, _} <- ActivityPub.block(blocker, blocked) do
blocked blocked
else else
@ -1485,7 +1607,7 @@ def all_superusers do
end end
def showing_reblogs?(%User{} = user, %User{} = target) do def showing_reblogs?(%User{} = user, %User{} = target) do
target.ap_id not in user.muted_reblogs not UserRelationship.reblog_mute_exists?(user, target)
end end
@doc """ @doc """
@ -1808,23 +1930,6 @@ def update_email_notifications(user, settings) do
|> update_and_set_cache() |> update_and_set_cache()
end end
defp set_subscribers(user, subscribers) do
params = %{subscribers: subscribers}
user
|> cast(params, [:subscribers])
|> validate_required([:subscribers])
|> update_and_set_cache()
end
def add_to_subscribers(user, subscribed) do
set_subscribers(user, Enum.uniq([subscribed | user.subscribers]))
end
def remove_from_subscribers(user, subscribed) do
set_subscribers(user, List.delete(user.subscribers, subscribed))
end
defp set_domain_blocks(user, domain_blocks) do defp set_domain_blocks(user, domain_blocks) do
params = %{domain_blocks: domain_blocks} params = %{domain_blocks: domain_blocks}
@ -1842,81 +1947,35 @@ def unblock_domain(user, domain_blocked) do
set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked)) set_domain_blocks(user, List.delete(user.domain_blocks, domain_blocked))
end end
defp set_blocks(user, blocks) do @spec add_to_block(User.t(), User.t()) ::
params = %{blocks: blocks} {:ok, UserRelationship.t()} | {:error, Ecto.Changeset.t()}
defp add_to_block(%User{} = user, %User{} = blocked) do
user UserRelationship.create_block(user, blocked)
|> cast(params, [:blocks])
|> validate_required([:blocks])
|> update_and_set_cache()
end end
def add_to_block(user, blocked) do @spec add_to_block(User.t(), User.t()) ::
set_blocks(user, Enum.uniq([blocked | user.blocks])) {:ok, UserRelationship.t()} | {:ok, nil} | {:error, Ecto.Changeset.t()}
defp remove_from_block(%User{} = user, %User{} = blocked) do
UserRelationship.delete_block(user, blocked)
end end
def remove_from_block(user, blocked) do defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
set_blocks(user, List.delete(user.blocks, blocked)) with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
end {:ok, user_notification_mute} <-
(notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
defp set_mutes(user, mutes) do {:ok, nil} do
params = %{mutes: mutes} {:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
user
|> cast(params, [:mutes])
|> validate_required([:mutes])
|> update_and_set_cache()
end
def add_to_mutes(user, muted, notifications?) do
with {:ok, user} <- set_mutes(user, Enum.uniq([muted | user.mutes])) do
set_notification_mutes(
user,
Enum.uniq([muted | user.muted_notifications]),
notifications?
)
end end
end end
def remove_from_mutes(user, muted) do defp remove_from_mutes(user, %User{} = muted_user) do
with {:ok, user} <- set_mutes(user, List.delete(user.mutes, muted)) do with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
set_notification_mutes( {:ok, user_notification_mute} <-
user, UserRelationship.delete_notification_mute(user, muted_user) do
List.delete(user.muted_notifications, muted), {:ok, [user_mute, user_notification_mute]}
true
)
end end
end end
defp set_notification_mutes(user, _muted_notifications, false = _notifications?) do
{:ok, user}
end
defp set_notification_mutes(user, muted_notifications, true = _notifications?) do
params = %{muted_notifications: muted_notifications}
user
|> cast(params, [:muted_notifications])
|> validate_required([:muted_notifications])
|> update_and_set_cache()
end
def add_reblog_mute(user, ap_id) do
params = %{muted_reblogs: user.muted_reblogs ++ [ap_id]}
user
|> cast(params, [:muted_reblogs])
|> update_and_set_cache()
end
def remove_reblog_mute(user, ap_id) do
params = %{muted_reblogs: List.delete(user.muted_reblogs, ap_id)}
user
|> cast(params, [:muted_reblogs])
|> update_and_set_cache()
end
def set_invisible(user, invisible) do def set_invisible(user, invisible) do
params = %{invisible: invisible} params = %{invisible: invisible}

View File

@ -103,9 +103,13 @@ defp filter_invisible_users(query) do
from(q in query, where: q.invisible == false) from(q in query, where: q.invisible == false)
end end
defp filter_blocked_user(query, %User{blocks: blocks}) defp filter_blocked_user(query, %User{} = blocker) do
when length(blocks) > 0 do query
from(q in query, where: not (q.ap_id in ^blocks)) |> join(:left, [u], b in Pleroma.UserRelationship,
as: :blocks,
on: b.relationship_type == ^:block and b.source_id == ^blocker.id and u.id == b.target_id
)
|> where([blocks: b], is_nil(b.target_id))
end end
defp filter_blocked_user(query, _), do: query defp filter_blocked_user(query, _), do: query

View File

@ -0,0 +1,92 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UserRelationship do
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserRelationship
schema "user_relationships" do
belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
field(:relationship_type, UserRelationshipTypeEnum)
timestamps(updated_at: false)
end
for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
# Definitions of `create_block/2`, `create_mute/2` etc.
def unquote(:"create_#{relationship_type}")(source, target),
do: create(unquote(relationship_type), source, target)
# Definitions of `delete_block/2`, `delete_mute/2` etc.
def unquote(:"delete_#{relationship_type}")(source, target),
do: delete(unquote(relationship_type), source, target)
# Definitions of `block_exists?/2`, `mute_exists?/2` etc.
def unquote(:"#{relationship_type}_exists?")(source, target),
do: exists?(unquote(relationship_type), source, target)
end
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
user_relationship
|> cast(params, [:relationship_type, :source_id, :target_id])
|> validate_required([:relationship_type, :source_id, :target_id])
|> unique_constraint(:relationship_type,
name: :user_relationships_source_id_relationship_type_target_id_index
)
|> validate_not_self_relationship()
end
def exists?(relationship_type, %User{} = source, %User{} = target) do
UserRelationship
|> where(relationship_type: ^relationship_type, source_id: ^source.id, target_id: ^target.id)
|> Repo.exists?()
end
def create(relationship_type, %User{} = source, %User{} = target) do
%UserRelationship{}
|> changeset(%{
relationship_type: relationship_type,
source_id: source.id,
target_id: target.id
})
|> Repo.insert(
on_conflict: :replace_all_except_primary_key,
conflict_target: [:source_id, :relationship_type, :target_id]
)
end
def delete(relationship_type, %User{} = source, %User{} = target) do
attrs = %{relationship_type: relationship_type, source_id: source.id, target_id: target.id}
case Repo.get_by(UserRelationship, attrs) do
%UserRelationship{} = existing_record -> Repo.delete(existing_record)
nil -> {:ok, nil}
end
end
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
changeset
|> validate_change(:target_id, fn _, target_id ->
if target_id == get_field(changeset, :source_id) do
[target_id: "can't be equal to source_id"]
else
[]
end
end)
|> validate_change(:source_id, fn _, source_id ->
if source_id == get_field(changeset, :target_id) do
[source_id: "can't be equal to target_id"]
else
[]
end
end)
end
end

View File

@ -919,7 +919,7 @@ defp restrict_reblogs(query, _), do: query
defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query
defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
mutes = user.mutes mutes = opts["muted_users_ap_ids"] || User.muted_users_ap_ids(user)
query = query =
from([activity] in query, from([activity] in query,
@ -936,8 +936,8 @@ defp restrict_muted(query, %{"muting_user" => %User{} = user} = opts) do
defp restrict_muted(query, _), do: query defp restrict_muted(query, _), do: query
defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do defp restrict_blocked(query, %{"blocking_user" => %User{} = user} = opts) do
blocks = user.blocks || [] blocked_ap_ids = opts["blocked_users_ap_ids"] || User.blocked_users_ap_ids(user)
domain_blocks = user.domain_blocks || [] domain_blocks = user.domain_blocks || []
query = query =
@ -945,14 +945,14 @@ defp restrict_blocked(query, %{"blocking_user" => %User{} = user}) do
from( from(
[activity, object: o] in query, [activity, object: o] in query,
where: fragment("not (? = ANY(?))", activity.actor, ^blocks), where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
where: fragment("not (? && ?)", activity.recipients, ^blocks), where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids),
where: where:
fragment( fragment(
"not (?->>'type' = 'Announce' and ?->'to' \\?| ?)", "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)",
activity.data, activity.data,
activity.data, activity.data,
^blocks ^blocked_ap_ids
), ),
where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks), where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks),
where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks) where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)
@ -979,8 +979,8 @@ defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids})
defp restrict_pinned(query, _), do: query defp restrict_pinned(query, _), do: query
defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user}) do defp restrict_muted_reblogs(query, %{"muting_user" => %User{} = user} = opts) do
muted_reblogs = user.muted_reblogs || [] muted_reblogs = opts["reblog_muted_users_ap_ids"] || User.reblog_muted_users_ap_ids(user)
from( from(
activity in query, activity in query,
@ -1061,7 +1061,33 @@ defp maybe_order(query, %{order: :asc}) do
defp maybe_order(query, _), do: query defp maybe_order(query, _), do: query
defp fetch_activities_query_ap_ids_ops(opts) do
source_user = opts["muting_user"]
ap_id_relations = if source_user, do: [:mute, :reblog_mute], else: []
ap_id_relations =
ap_id_relations ++
if opts["blocking_user"] && opts["blocking_user"] == source_user do
[:block]
else
[]
end
preloaded_ap_ids = User.outgoing_relations_ap_ids(source_user, ap_id_relations)
restrict_blocked_opts = Map.merge(%{"blocked_users_ap_ids" => preloaded_ap_ids[:block]}, opts)
restrict_muted_opts = Map.merge(%{"muted_users_ap_ids" => preloaded_ap_ids[:mute]}, opts)
restrict_muted_reblogs_opts =
Map.merge(%{"reblog_muted_users_ap_ids" => preloaded_ap_ids[:reblog_mute]}, opts)
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts}
end
def fetch_activities_query(recipients, opts \\ %{}) do def fetch_activities_query(recipients, opts \\ %{}) do
{restrict_blocked_opts, restrict_muted_opts, restrict_muted_reblogs_opts} =
fetch_activities_query_ap_ids_ops(opts)
config = %{ config = %{
skip_thread_containment: Config.get([:instance, :skip_thread_containment]) skip_thread_containment: Config.get([:instance, :skip_thread_containment])
} }
@ -1081,15 +1107,15 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_type(opts) |> restrict_type(opts)
|> restrict_state(opts) |> restrict_state(opts)
|> restrict_favorited_by(opts) |> restrict_favorited_by(opts)
|> restrict_blocked(opts) |> restrict_blocked(restrict_blocked_opts)
|> restrict_muted(opts) |> restrict_muted(restrict_muted_opts)
|> restrict_media(opts) |> restrict_media(opts)
|> restrict_visibility(opts) |> restrict_visibility(opts)
|> restrict_thread_visibility(opts, config) |> restrict_thread_visibility(opts, config)
|> restrict_replies(opts) |> restrict_replies(opts)
|> restrict_reblogs(opts) |> restrict_reblogs(opts)
|> restrict_pinned(opts) |> restrict_pinned(opts)
|> restrict_muted_reblogs(opts) |> restrict_muted_reblogs(restrict_muted_reblogs_opts)
|> restrict_instance(opts) |> restrict_instance(opts)
|> Activity.restrict_deactivated_users() |> Activity.restrict_deactivated_users()
|> exclude_poll_votes(opts) |> exclude_poll_votes(opts)

View File

@ -10,6 +10,7 @@ defmodule Pleroma.Web.CommonAPI do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.ThreadMute alias Pleroma.ThreadMute
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
@ -32,7 +33,7 @@ def follow(follower, followed) 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, _unfollowed} <- User.unsubscribe(follower, unfollowed) do {:ok, _subscription} <- User.unsubscribe(follower, unfollowed) do
{:ok, follower} {:ok, follower}
end end
end end
@ -420,15 +421,11 @@ defp set_visibility(activity, %{"visibility" => visibility}) do
defp set_visibility(activity, _), do: {:ok, activity} defp set_visibility(activity, _), do: {:ok, activity}
def hide_reblogs(user, %{ap_id: ap_id} = _muted) do def hide_reblogs(%User{} = user, %User{} = target) do
if ap_id not in user.muted_reblogs do UserRelationship.create_reblog_mute(user, target)
User.add_reblog_mute(user, ap_id)
end
end end
def show_reblogs(user, %{ap_id: ap_id} = _muted) do def show_reblogs(%User{} = user, %User{} = target) do
if ap_id in user.muted_reblogs do UserRelationship.delete_reblog_mute(user, target)
User.remove_reblog_mute(user, ap_id)
end
end end
end end

View File

@ -494,7 +494,7 @@ def maybe_notify_subscribers(
with %User{} = user <- User.get_cached_by_ap_id(actor) do with %User{} = user <- User.get_cached_by_ap_id(actor) do
subscriber_ids = subscriber_ids =
user user
|> User.subscribers() |> User.subscriber_users()
|> Enum.filter(&Visibility.visible_for_user?(activity, &1)) |> Enum.filter(&Visibility.visible_for_user?(activity, &1))
|> Enum.map(& &1.ap_id) |> Enum.map(& &1.ap_id)

View File

@ -324,7 +324,7 @@ def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) d
def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
notifications? = params |> Map.get("notifications", true) |> truthy_param?() notifications? = params |> Map.get("notifications", true) |> truthy_param?()
with {:ok, muter} <- User.mute(muter, muted, notifications?) do with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do
render(conn, "relationship.json", user: muter, target: muted) render(conn, "relationship.json", user: muter, target: muted)
else else
{:error, message} -> json_response(conn, :forbidden, %{error: message}) {:error, message} -> json_response(conn, :forbidden, %{error: message})
@ -333,7 +333,7 @@ def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
@doc "POST /api/v1/accounts/:id/unmute" @doc "POST /api/v1/accounts/:id/unmute"
def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
with {:ok, muter} <- User.unmute(muter, muted) do with {:ok, _user_relationships} <- User.unmute(muter, muted) do
render(conn, "relationship.json", user: muter, target: muted) render(conn, "relationship.json", user: muter, target: muted)
else else
{:error, message} -> json_response(conn, :forbidden, %{error: message}) {:error, message} -> json_response(conn, :forbidden, %{error: message})
@ -342,7 +342,7 @@ def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
@doc "POST /api/v1/accounts/:id/block" @doc "POST /api/v1/accounts/:id/block"
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
with {:ok, blocker} <- User.block(blocker, blocked), with {:ok, _user_block} <- User.block(blocker, blocked),
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
render(conn, "relationship.json", user: blocker, target: blocked) render(conn, "relationship.json", user: blocker, target: blocked)
else else
@ -352,7 +352,7 @@ def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
@doc "POST /api/v1/accounts/:id/unblock" @doc "POST /api/v1/accounts/:id/unblock"
def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
with {:ok, blocker} <- User.unblock(blocker, blocked), with {:ok, _user_block} <- User.unblock(blocker, blocked),
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
render(conn, "relationship.json", user: blocker, target: blocked) render(conn, "relationship.json", user: blocker, target: blocked)
else else
@ -374,12 +374,14 @@ def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
@doc "GET /api/v1/mutes" @doc "GET /api/v1/mutes"
def mutes(%{assigns: %{user: user}} = conn, _) do def mutes(%{assigns: %{user: user}} = conn, _) do
render(conn, "index.json", users: User.muted_users(user), for: user, as: :user) users = User.muted_users(user, _restrict_deactivated = true)
render(conn, "index.json", users: users, for: user, as: :user)
end end
@doc "GET /api/v1/blocks" @doc "GET /api/v1/blocks"
def blocks(%{assigns: %{user: user}} = conn, _) do def blocks(%{assigns: %{user: user}} = conn, _) do
render(conn, "index.json", users: User.blocked_users(user), for: user, as: :user) users = User.blocked_users(user, _restrict_deactivated = true)
render(conn, "index.json", users: users, for: user, as: :user)
end end
@doc "GET /api/v1/endorsements" @doc "GET /api/v1/endorsements"

View File

@ -24,19 +24,16 @@ def follow(follower, followed, params \\ %{}) do
with {:ok, follower, _followed, _} <- result do with {:ok, follower, _followed, _} <- result do
options = cast_params(params) options = cast_params(params)
set_reblogs_visibility(options[:reblogs], result)
case reblogs_visibility(options[:reblogs], result) do {:ok, follower}
{:ok, follower} -> {:ok, follower}
_ -> {:ok, follower}
end
end end
end end
defp reblogs_visibility(false, {:ok, follower, followed, _}) do defp set_reblogs_visibility(false, {:ok, follower, followed, _}) do
CommonAPI.hide_reblogs(follower, followed) CommonAPI.hide_reblogs(follower, followed)
end end
defp reblogs_visibility(_, {:ok, follower, followed, _}) do defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do
CommonAPI.show_reblogs(follower, followed) CommonAPI.show_reblogs(follower, followed)
end end

View File

@ -50,8 +50,8 @@ def render("relationship.json", %{user: %User{} = user, target: %User{} = target
id: to_string(target.id), id: to_string(target.id),
following: User.following?(user, target), following: User.following?(user, target),
followed_by: User.following?(target, user), followed_by: User.following?(target, user),
blocking: User.blocks_ap_id?(user, target), blocking: User.blocks_user?(user, target),
blocked_by: User.blocks_ap_id?(target, user), blocked_by: User.blocks_user?(target, user),
muting: User.mutes?(user, target), muting: User.mutes?(user, target),
muting_notifications: User.muted_notifications?(user, target), muting_notifications: User.muted_notifications?(user, target),
subscribing: User.subscribed_to?(user, target), subscribing: User.subscribed_to?(user, target),

View File

@ -144,7 +144,7 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
@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_target} <- User.subscribe(user, subscription_target) do with {:ok, _subscription} <- User.subscribe(user, subscription_target) do
render(conn, "relationship.json", user: user, target: subscription_target) render(conn, "relationship.json", user: user, target: subscription_target)
else else
{:error, message} -> json_response(conn, :forbidden, %{error: message}) {:error, message} -> json_response(conn, :forbidden, %{error: message})
@ -153,7 +153,7 @@ def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _p
@doc "POST /api/v1/pleroma/accounts/:id/unsubscribe" @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe"
def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do with {:ok, _subscription} <- User.unsubscribe(user, subscription_target) do
render(conn, "relationship.json", user: user, target: subscription_target) render(conn, "relationship.json", user: user, target: subscription_target)
else else
{:error, message} -> json_response(conn, :forbidden, %{error: message}) {:error, message} -> json_response(conn, :forbidden, %{error: message})

View File

@ -129,16 +129,17 @@ defp do_stream(%{topic: topic, item: item}) do
end end
defp should_send?(%User{} = user, %Activity{} = item) do defp should_send?(%User{} = user, %Activity{} = item) do
blocks = user.blocks || [] %{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
mutes = user.mutes || [] User.outgoing_relations_ap_ids(user, [:block, :mute, :reblog_mute])
reblog_mutes = user.muted_reblogs || []
recipient_blocks = MapSet.new(blocks ++ mutes) recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids)
recipients = MapSet.new(item.recipients) recipients = MapSet.new(item.recipients)
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
with parent <- Object.normalize(item) || item, with parent <- Object.normalize(item) || item,
true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)), true <-
true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)), Enum.all?([blocked_ap_ids, muted_ap_ids, reblog_muted_ap_ids], &(item.actor not in &1)),
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
true <- MapSet.disjoint?(recipients, recipient_blocks), true <- MapSet.disjoint?(recipients, recipient_blocks),
%{host: item_host} <- URI.parse(item.actor), %{host: item_host} <- URI.parse(item.actor),
%{host: parent_host} <- URI.parse(parent.data["actor"]), %{host: parent_host} <- URI.parse(parent.data["actor"]),

View File

@ -100,6 +100,7 @@ defp deps do
{:plug_cowboy, "~> 2.0"}, {:plug_cowboy, "~> 2.0"},
{:phoenix_pubsub, "~> 1.1"}, {:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"}, {:phoenix_ecto, "~> 4.0"},
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.2"}, {:ecto_sql, "~> 3.2"},
{:postgrex, ">= 0.13.5"}, {:postgrex, ">= 0.13.5"},
{:oban, "~> 0.12.0"}, {:oban, "~> 0.12.0"},

View File

@ -24,6 +24,7 @@
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.2.2", "d10845bc147b9f61ef485cbf0973c0a337237199bd9bd30dd9542db00aadc26b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0 or ~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "ecto_sql": {:hex, :ecto_sql, "3.2.2", "d10845bc147b9f61ef485cbf0973c0a337237199bd9bd30dd9542db00aadc26b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0 or ~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"}, "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"},

View File

@ -0,0 +1,17 @@
defmodule Pleroma.Repo.Migrations.CreateUserRelationships do
use Ecto.Migration
def change do
create_if_not_exists table(:user_relationships) do
add(:source_id, references(:users, type: :uuid, on_delete: :delete_all))
add(:target_id, references(:users, type: :uuid, on_delete: :delete_all))
add(:relationship_type, :integer, null: false)
timestamps(updated_at: false)
end
create_if_not_exists(
unique_index(:user_relationships, [:source_id, :relationship_type, :target_id])
)
end
end

View File

@ -0,0 +1,68 @@
defmodule Pleroma.Repo.Migrations.DataMigrationPopulateUserRelationships do
use Ecto.Migration
alias Ecto.Adapters.SQL
alias Pleroma.Repo
require Logger
def up do
Enum.each(
[blocks: 1, mutes: 2, muted_reblogs: 3, muted_notifications: 4, subscribers: 5],
fn {field, relationship_type_code} ->
migrate(field, relationship_type_code)
if field == :subscribers do
drop_if_exists(index(:users, [:subscribers]))
end
end
)
end
def down, do: :noop
defp migrate(field, relationship_type_code) do
Logger.info("Processing users.#{field}...")
{:ok, %{rows: field_rows}} =
SQL.query(Repo, "SELECT id, #{field} FROM users WHERE #{field} != '{}'")
target_ap_ids =
Enum.flat_map(
field_rows,
fn [_, ap_ids] -> ap_ids end
)
|> Enum.uniq()
# Selecting ids of all targets at once in order to reduce the number of SELECT queries
{:ok, %{rows: target_ap_id_id}} =
SQL.query(Repo, "SELECT ap_id, id FROM users WHERE ap_id = ANY($1)", [target_ap_ids])
target_id_by_ap_id = Enum.into(target_ap_id_id, %{}, fn [k, v] -> {k, v} end)
Enum.each(
field_rows,
fn [source_id, target_ap_ids] ->
source_uuid = Ecto.UUID.cast!(source_id)
for target_ap_id <- target_ap_ids do
target_id = target_id_by_ap_id[target_ap_id]
with {:ok, target_uuid} <- target_id && Ecto.UUID.cast(target_id) do
execute("""
INSERT INTO user_relationships(
source_id, target_id, relationship_type, inserted_at
)
VALUES(
'#{source_uuid}'::uuid, '#{target_uuid}'::uuid, #{relationship_type_code}, now()
)
ON CONFLICT (source_id, relationship_type, target_id) DO NOTHING
""")
else
_ -> Logger.warn("Unresolved #{field} reference: (#{source_uuid}, #{target_id})")
end
end
end
)
end
end

View File

@ -252,7 +252,7 @@ test "when the user blocks a recipient, the existing conversations with them are
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 4 assert User.get_cached_by_id(blocker.id).unread_conversation_count == 4
{:ok, blocker} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
# The conversations with the blocked user are marked as read # The conversations with the blocked user are marked as read
assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] = assert [%{read: true}, %{read: true}, %{read: true}, %{read: false}] =
@ -274,7 +274,7 @@ test "the new conversation with the blocked user is not marked as unread " do
blocked = insert(:user) blocked = insert(:user)
third_user = insert(:user) third_user = insert(:user)
{:ok, blocker} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
# When the blocked user is the author # When the blocked user is the author
{:ok, _direct1} = {:ok, _direct1} =
@ -311,7 +311,7 @@ test "the conversation with the blocked user is not marked as unread on a reply"
"visibility" => "direct" "visibility" => "direct"
}) })
{:ok, blocker} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
assert [%{read: true}] = Participation.for_user(blocker) assert [%{read: true}] = Participation.for_user(blocker)
assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0 assert User.get_cached_by_id(blocker.id).unread_conversation_count == 0

View File

@ -93,7 +93,7 @@ test "it creates a notification for user if the user blocks the activity author"
activity = insert(:note_activity) activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"]) author = User.get_cached_by_ap_id(activity.data["actor"])
user = insert(:user) user = insert(:user)
{:ok, user} = User.block(user, author) {:ok, _user_relationship} = User.block(user, author)
assert Notification.create_notification(activity, user) assert Notification.create_notification(activity, user)
end end
@ -112,7 +112,7 @@ test "notification created if user is muted without notifications" do
muter = insert(:user) muter = insert(:user)
muted = insert(:user) muted = insert(:user)
{:ok, muter} = User.mute(muter, muted, false) {:ok, _user_relationships} = User.mute(muter, muted, false)
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"}) {:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
@ -665,7 +665,7 @@ test "move activity generates a notification" do
test "it returns notifications for muted user without notifications" do test "it returns notifications for muted user without notifications" do
user = insert(:user) user = insert(:user)
muted = insert(:user) muted = insert(:user)
{:ok, user} = User.mute(user, muted, false) {:ok, _user_relationships} = User.mute(user, muted, false)
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
@ -675,7 +675,7 @@ test "it returns notifications for muted user without notifications" do
test "it doesn't return notifications for muted user with notifications" do test "it doesn't return notifications for muted user with notifications" do
user = insert(:user) user = insert(:user)
muted = insert(:user) muted = insert(:user)
{:ok, user} = User.mute(user, muted) {:ok, _user_relationships} = User.mute(user, muted)
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
@ -685,7 +685,7 @@ test "it doesn't return notifications for muted user with notifications" do
test "it doesn't return notifications for blocked user" do test "it doesn't return notifications for blocked user" do
user = insert(:user) user = insert(:user)
blocked = insert(:user) blocked = insert(:user)
{:ok, user} = User.block(user, blocked) {:ok, _user_relationship} = User.block(user, blocked)
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
@ -715,7 +715,7 @@ test "it doesn't return notifications for muted thread" do
test "it returns notifications from a muted user when with_muted is set" do test "it returns notifications from a muted user when with_muted is set" do
user = insert(:user) user = insert(:user)
muted = insert(:user) muted = insert(:user)
{:ok, user} = User.mute(user, muted) {:ok, _user_relationships} = User.mute(user, muted)
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
@ -725,7 +725,7 @@ test "it returns notifications from a muted user when with_muted is set" do
test "it doesn't return notifications from a blocked user when with_muted is set" do test "it doesn't return notifications from a blocked user when with_muted is set" do
user = insert(:user) user = insert(:user)
blocked = insert(:user) blocked = insert(:user)
{:ok, user} = User.block(user, blocked) {:ok, _user_relationship} = User.block(user, blocked)
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"}) {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})

View File

@ -42,6 +42,18 @@ def user_factory do
} }
end end
def user_relationship_factory(attrs \\ %{}) do
source = attrs[:source] || insert(:user)
target = attrs[:target] || insert(:user)
relationship_type = attrs[:relationship_type] || :block
%Pleroma.UserRelationship{
source_id: source.id,
target_id: target.id,
relationship_type: relationship_type
}
end
def note_factory(attrs \\ %{}) do def note_factory(attrs \\ %{}) do
text = sequence(:text, &"This is :moominmamma: note #{&1}") text = sequence(:text, &"This is :moominmamma: note #{&1}")

View File

@ -0,0 +1,130 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UserRelationshipTest do
alias Pleroma.UserRelationship
use Pleroma.DataCase
import Pleroma.Factory
describe "*_exists?/2" do
setup do
{:ok, users: insert_list(2, :user)}
end
test "returns false if record doesn't exist", %{users: [user1, user2]} do
refute UserRelationship.block_exists?(user1, user2)
refute UserRelationship.mute_exists?(user1, user2)
refute UserRelationship.notification_mute_exists?(user1, user2)
refute UserRelationship.reblog_mute_exists?(user1, user2)
refute UserRelationship.inverse_subscription_exists?(user1, user2)
end
test "returns true if record exists", %{users: [user1, user2]} do
for relationship_type <- [
:block,
:mute,
:notification_mute,
:reblog_mute,
:inverse_subscription
] do
insert(:user_relationship,
source: user1,
target: user2,
relationship_type: relationship_type
)
end
assert UserRelationship.block_exists?(user1, user2)
assert UserRelationship.mute_exists?(user1, user2)
assert UserRelationship.notification_mute_exists?(user1, user2)
assert UserRelationship.reblog_mute_exists?(user1, user2)
assert UserRelationship.inverse_subscription_exists?(user1, user2)
end
end
describe "create_*/2" do
setup do
{:ok, users: insert_list(2, :user)}
end
test "creates user relationship record if it doesn't exist", %{users: [user1, user2]} do
for relationship_type <- [
:block,
:mute,
:notification_mute,
:reblog_mute,
:inverse_subscription
] do
insert(:user_relationship,
source: user1,
target: user2,
relationship_type: relationship_type
)
end
UserRelationship.create_block(user1, user2)
UserRelationship.create_mute(user1, user2)
UserRelationship.create_notification_mute(user1, user2)
UserRelationship.create_reblog_mute(user1, user2)
UserRelationship.create_inverse_subscription(user1, user2)
assert UserRelationship.block_exists?(user1, user2)
assert UserRelationship.mute_exists?(user1, user2)
assert UserRelationship.notification_mute_exists?(user1, user2)
assert UserRelationship.reblog_mute_exists?(user1, user2)
assert UserRelationship.inverse_subscription_exists?(user1, user2)
end
test "if record already exists, returns it", %{users: [user1, user2]} do
user_block = UserRelationship.create_block(user1, user2)
assert user_block == UserRelationship.create_block(user1, user2)
end
end
describe "delete_*/2" do
setup do
{:ok, users: insert_list(2, :user)}
end
test "deletes user relationship record if it exists", %{users: [user1, user2]} do
for relationship_type <- [
:block,
:mute,
:notification_mute,
:reblog_mute,
:inverse_subscription
] do
insert(:user_relationship,
source: user1,
target: user2,
relationship_type: relationship_type
)
end
assert {:ok, %UserRelationship{}} = UserRelationship.delete_block(user1, user2)
assert {:ok, %UserRelationship{}} = UserRelationship.delete_mute(user1, user2)
assert {:ok, %UserRelationship{}} = UserRelationship.delete_notification_mute(user1, user2)
assert {:ok, %UserRelationship{}} = UserRelationship.delete_reblog_mute(user1, user2)
assert {:ok, %UserRelationship{}} =
UserRelationship.delete_inverse_subscription(user1, user2)
refute UserRelationship.block_exists?(user1, user2)
refute UserRelationship.mute_exists?(user1, user2)
refute UserRelationship.notification_mute_exists?(user1, user2)
refute UserRelationship.reblog_mute_exists?(user1, user2)
refute UserRelationship.inverse_subscription_exists?(user1, user2)
end
test "if record does not exist, returns {:ok, nil}", %{users: [user1, user2]} do
assert {:ok, nil} = UserRelationship.delete_block(user1, user2)
assert {:ok, nil} = UserRelationship.delete_mute(user1, user2)
assert {:ok, nil} = UserRelationship.delete_notification_mute(user1, user2)
assert {:ok, nil} = UserRelationship.delete_reblog_mute(user1, user2)
assert {:ok, nil} = UserRelationship.delete_inverse_subscription(user1, user2)
end
end
end

View File

@ -44,6 +44,56 @@ test "returns invisible actor" do
end end
end end
describe "AP ID user relationships" do
setup do
{:ok, user: insert(:user)}
end
test "outgoing_relations_ap_ids/1", %{user: user} do
rel_types = [:block, :mute, :notification_mute, :reblog_mute, :inverse_subscription]
ap_ids_by_rel =
Enum.into(
rel_types,
%{},
fn rel_type ->
rel_records =
insert_list(2, :user_relationship, %{source: user, relationship_type: rel_type})
ap_ids = Enum.map(rel_records, fn rr -> Repo.preload(rr, :target).target.ap_id end)
{rel_type, Enum.sort(ap_ids)}
end
)
assert ap_ids_by_rel[:block] == Enum.sort(User.blocked_users_ap_ids(user))
assert ap_ids_by_rel[:block] == Enum.sort(Enum.map(User.blocked_users(user), & &1.ap_id))
assert ap_ids_by_rel[:mute] == Enum.sort(User.muted_users_ap_ids(user))
assert ap_ids_by_rel[:mute] == Enum.sort(Enum.map(User.muted_users(user), & &1.ap_id))
assert ap_ids_by_rel[:notification_mute] ==
Enum.sort(User.notification_muted_users_ap_ids(user))
assert ap_ids_by_rel[:notification_mute] ==
Enum.sort(Enum.map(User.notification_muted_users(user), & &1.ap_id))
assert ap_ids_by_rel[:reblog_mute] == Enum.sort(User.reblog_muted_users_ap_ids(user))
assert ap_ids_by_rel[:reblog_mute] ==
Enum.sort(Enum.map(User.reblog_muted_users(user), & &1.ap_id))
assert ap_ids_by_rel[:inverse_subscription] == Enum.sort(User.subscriber_users_ap_ids(user))
assert ap_ids_by_rel[:inverse_subscription] ==
Enum.sort(Enum.map(User.subscriber_users(user), & &1.ap_id))
outgoing_relations_ap_ids = User.outgoing_relations_ap_ids(user, rel_types)
assert ap_ids_by_rel ==
Enum.into(outgoing_relations_ap_ids, %{}, fn {k, v} -> {k, Enum.sort(v)} end)
end
end
describe "when tags are nil" do describe "when tags are nil" do
test "tagging a user" do test "tagging a user" do
user = insert(:user, %{tags: nil}) user = insert(:user, %{tags: nil})
@ -119,7 +169,7 @@ test "clears follow requests when requester is blocked" do
CommonAPI.follow(follower, followed) CommonAPI.follow(follower, followed)
assert [_activity] = User.get_follow_requests(followed) assert [_activity] = User.get_follow_requests(followed)
{:ok, _follower} = User.block(followed, follower) {:ok, _user_relationship} = User.block(followed, follower)
assert [] = User.get_follow_requests(followed) assert [] = User.get_follow_requests(followed)
end end
@ -132,8 +182,8 @@ test "follow_all follows mutliple users" do
not_followed = insert(:user) not_followed = insert(:user)
reverse_blocked = insert(:user) reverse_blocked = insert(:user)
{:ok, user} = User.block(user, blocked) {:ok, _user_relationship} = User.block(user, blocked)
{:ok, reverse_blocked} = User.block(reverse_blocked, user) {:ok, _user_relationship} = User.block(reverse_blocked, user)
{:ok, user} = User.follow(user, followed_zero) {:ok, user} = User.follow(user, followed_zero)
@ -186,7 +236,7 @@ test "can't follow a user who blocked us" do
blocker = insert(:user) blocker = insert(:user)
blockee = insert(:user) blockee = insert(:user)
{:ok, blocker} = User.block(blocker, blockee) {:ok, _user_relationship} = User.block(blocker, blockee)
{:error, _} = User.follow(blockee, blocker) {:error, _} = User.follow(blockee, blocker)
end end
@ -195,7 +245,7 @@ test "can't subscribe to a user who blocked us" do
blocker = insert(:user) blocker = insert(:user)
blocked = insert(:user) blocked = insert(:user)
{:ok, blocker} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
{:error, _} = User.subscribe(blocked, blocker) {:error, _} = User.subscribe(blocked, blocker)
end end
@ -678,7 +728,7 @@ test "it mutes people" do
refute User.mutes?(user, muted_user) refute User.mutes?(user, muted_user)
refute User.muted_notifications?(user, muted_user) refute User.muted_notifications?(user, muted_user)
{:ok, user} = User.mute(user, muted_user) {:ok, _user_relationships} = User.mute(user, muted_user)
assert User.mutes?(user, muted_user) assert User.mutes?(user, muted_user)
assert User.muted_notifications?(user, muted_user) assert User.muted_notifications?(user, muted_user)
@ -688,8 +738,8 @@ test "it unmutes users" do
user = insert(:user) user = insert(:user)
muted_user = insert(:user) muted_user = insert(:user)
{:ok, user} = User.mute(user, muted_user) {:ok, _user_relationships} = User.mute(user, muted_user)
{:ok, user} = User.unmute(user, muted_user) {:ok, _user_mute} = User.unmute(user, muted_user)
refute User.mutes?(user, muted_user) refute User.mutes?(user, muted_user)
refute User.muted_notifications?(user, muted_user) refute User.muted_notifications?(user, muted_user)
@ -702,7 +752,7 @@ test "it mutes user without notifications" do
refute User.mutes?(user, muted_user) refute User.mutes?(user, muted_user)
refute User.muted_notifications?(user, muted_user) refute User.muted_notifications?(user, muted_user)
{:ok, user} = User.mute(user, muted_user, false) {:ok, _user_relationships} = User.mute(user, muted_user, false)
assert User.mutes?(user, muted_user) assert User.mutes?(user, muted_user)
refute User.muted_notifications?(user, muted_user) refute User.muted_notifications?(user, muted_user)
@ -716,7 +766,7 @@ test "it blocks people" do
refute User.blocks?(user, blocked_user) refute User.blocks?(user, blocked_user)
{:ok, user} = User.block(user, blocked_user) {:ok, _user_relationship} = User.block(user, blocked_user)
assert User.blocks?(user, blocked_user) assert User.blocks?(user, blocked_user)
end end
@ -725,8 +775,8 @@ test "it unblocks users" do
user = insert(:user) user = insert(:user)
blocked_user = insert(:user) blocked_user = insert(:user)
{:ok, user} = User.block(user, blocked_user) {:ok, _user_relationship} = User.block(user, blocked_user)
{:ok, user} = User.unblock(user, blocked_user) {:ok, _user_block} = User.unblock(user, blocked_user)
refute User.blocks?(user, blocked_user) refute User.blocks?(user, blocked_user)
end end
@ -741,7 +791,7 @@ test "blocks tear down cyclical follow relationships" do
assert User.following?(blocker, blocked) assert User.following?(blocker, blocked)
assert User.following?(blocked, blocker) assert User.following?(blocked, blocker)
{:ok, blocker} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
blocked = User.get_cached_by_id(blocked.id) blocked = User.get_cached_by_id(blocked.id)
assert User.blocks?(blocker, blocked) assert User.blocks?(blocker, blocked)
@ -759,7 +809,7 @@ test "blocks tear down blocker->blocked follow relationships" do
assert User.following?(blocker, blocked) assert User.following?(blocker, blocked)
refute User.following?(blocked, blocker) refute User.following?(blocked, blocker)
{:ok, blocker} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
blocked = User.get_cached_by_id(blocked.id) blocked = User.get_cached_by_id(blocked.id)
assert User.blocks?(blocker, blocked) assert User.blocks?(blocker, blocked)
@ -777,7 +827,7 @@ test "blocks tear down blocked->blocker follow relationships" do
refute User.following?(blocker, blocked) refute User.following?(blocker, blocked)
assert User.following?(blocked, blocker) assert User.following?(blocked, blocker)
{:ok, blocker} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
blocked = User.get_cached_by_id(blocked.id) blocked = User.get_cached_by_id(blocked.id)
assert User.blocks?(blocker, blocked) assert User.blocks?(blocker, blocked)
@ -790,12 +840,12 @@ test "blocks tear down blocked->blocker subscription relationships" do
blocker = insert(:user) blocker = insert(:user)
blocked = insert(:user) blocked = insert(:user)
{:ok, blocker} = User.subscribe(blocked, blocker) {:ok, _subscription} = User.subscribe(blocked, blocker)
assert User.subscribed_to?(blocked, blocker) assert User.subscribed_to?(blocked, blocker)
refute User.subscribed_to?(blocker, blocked) refute User.subscribed_to?(blocker, blocked)
{:ok, blocker} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
assert User.blocks?(blocker, blocked) assert User.blocks?(blocker, blocked)
refute User.subscribed_to?(blocker, blocked) refute User.subscribed_to?(blocker, blocked)
@ -1324,7 +1374,8 @@ test "follower count is updated when a follower is blocked" do
{:ok, _follower2} = User.follow(follower2, user) {:ok, _follower2} = User.follow(follower2, user)
{:ok, _follower3} = User.follow(follower3, user) {:ok, _follower3} = User.follow(follower3, user)
{:ok, user} = User.block(user, follower) {:ok, _user_relationship} = User.block(user, follower)
user = refresh_record(user)
assert user.follower_count == 2 assert user.follower_count == 2
end end

View File

@ -487,7 +487,7 @@ test "retrieves activities that have a given context" do
activity_five = insert(:note_activity) activity_five = insert(:note_activity)
user = insert(:user) user = insert(:user)
{:ok, user} = User.block(user, %{ap_id: activity_five.data["actor"]}) {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})
activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user}) activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
assert activities == [activity_two, activity] assert activities == [activity_two, activity]
@ -500,7 +500,7 @@ test "doesn't return blocked activities" do
activity_three = insert(:note_activity) activity_three = insert(:note_activity)
user = insert(:user) user = insert(:user)
booster = insert(:user) booster = insert(:user)
{:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]}) {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})
activities = activities =
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true}) ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
@ -509,7 +509,7 @@ test "doesn't return blocked activities" do
assert Enum.member?(activities, activity_three) assert Enum.member?(activities, activity_three)
refute Enum.member?(activities, activity_one) refute Enum.member?(activities, activity_one)
{:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]}) {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
activities = activities =
ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true}) ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
@ -518,7 +518,7 @@ test "doesn't return blocked activities" do
assert Enum.member?(activities, activity_three) assert Enum.member?(activities, activity_three)
assert Enum.member?(activities, activity_one) assert Enum.member?(activities, activity_one)
{:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]}) {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster) {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
activity_three = Activity.get_by_id(activity_three.id) activity_three = Activity.get_by_id(activity_three.id)
@ -545,7 +545,7 @@ test "doesn't return transitive interactions concerning blocked users" do
blockee = insert(:user) blockee = insert(:user)
friend = insert(:user) friend = insert(:user)
{:ok, blocker} = User.block(blocker, blockee) {:ok, _user_relationship} = User.block(blocker, blockee)
{:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
@ -568,7 +568,7 @@ test "doesn't return announce activities concerning blocked users" do
blockee = insert(:user) blockee = insert(:user)
friend = insert(:user) friend = insert(:user)
{:ok, blocker} = User.block(blocker, blockee) {:ok, _user_relationship} = User.block(blocker, blockee)
{:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
@ -614,7 +614,9 @@ test "doesn't return muted activities" do
activity_three = insert(:note_activity) activity_three = insert(:note_activity)
user = insert(:user) user = insert(:user)
booster = insert(:user) booster = insert(:user)
{:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]})
activity_one_actor = User.get_by_ap_id(activity_one.data["actor"])
{:ok, _user_relationships} = User.mute(user, activity_one_actor)
activities = activities =
ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
@ -635,7 +637,7 @@ test "doesn't return muted activities" do
assert Enum.member?(activities, activity_three) assert Enum.member?(activities, activity_three)
assert Enum.member?(activities, activity_one) assert Enum.member?(activities, activity_one)
{:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]}) {:ok, _user_mute} = User.unmute(user, activity_one_actor)
activities = activities =
ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
@ -644,7 +646,8 @@ test "doesn't return muted activities" do
assert Enum.member?(activities, activity_three) assert Enum.member?(activities, activity_three)
assert Enum.member?(activities, activity_one) assert Enum.member?(activities, activity_one)
{:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]}) activity_three_actor = User.get_by_ap_id(activity_three.data["actor"])
{:ok, _user_relationships} = User.mute(user, activity_three_actor)
{:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster) {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
%Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
activity_three = Activity.get_by_id(activity_three.id) activity_three = Activity.get_by_id(activity_three.id)
@ -791,7 +794,7 @@ test "doesn't return reblogs for users for whom reblogs have been muted" do
activity = insert(:note_activity) activity = insert(:note_activity)
user = insert(:user) user = insert(:user)
booster = insert(:user) booster = insert(:user)
{:ok, user} = CommonAPI.hide_reblogs(user, booster) {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
{:ok, activity, _} = CommonAPI.repeat(activity.id, booster) {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
@ -804,8 +807,8 @@ test "returns reblogs for users for whom reblogs have not been muted" do
activity = insert(:note_activity) activity = insert(:note_activity)
user = insert(:user) user = insert(:user)
booster = insert(:user) booster = insert(:user)
{:ok, user} = CommonAPI.hide_reblogs(user, booster) {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)
{:ok, user} = CommonAPI.show_reblogs(user, booster) {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)
{:ok, activity, _} = CommonAPI.repeat(activity.id, booster) {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)

View File

@ -128,7 +128,7 @@ test "it rejects incoming follow requests from blocked users when deny_follow_bl
user = insert(:user) user = insert(:user)
{:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin") {:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin")
{:ok, user} = User.block(user, target) {:ok, _user_relationship} = User.block(user, target)
data = data =
File.read!("test/fixtures/mastodon-follow-activity.json") File.read!("test/fixtures/mastodon-follow-activity.json")

View File

@ -509,14 +509,14 @@ test "updates state of multiple reports" do
end end
test "add a reblog mute", %{muter: muter, muted: muted} do test "add a reblog mute", %{muter: muter, muted: muted} do
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted) {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
assert User.showing_reblogs?(muter, muted) == false assert User.showing_reblogs?(muter, muted) == false
end end
test "remove a reblog mute", %{muter: muter, muted: muted} do test "remove a reblog mute", %{muter: muter, muted: muted} do
{:ok, muter} = CommonAPI.hide_reblogs(muter, muted) {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
{:ok, muter} = CommonAPI.show_reblogs(muter, muted) {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
assert User.showing_reblogs?(muter, muted) == true assert User.showing_reblogs?(muter, muted) == true
end end
@ -526,7 +526,7 @@ test "remove a reblog mute", %{muter: muter, muted: muted} do
test "also unsubscribes a user" do test "also unsubscribes a user" do
[follower, followed] = insert_pair(:user) [follower, followed] = insert_pair(:user)
{:ok, follower, followed, _} = CommonAPI.follow(follower, followed) {:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
{:ok, followed} = User.subscribe(follower, followed) {:ok, _subscription} = User.subscribe(follower, followed)
assert User.subscribed_to?(follower, followed) assert User.subscribed_to?(follower, followed)

View File

@ -891,7 +891,7 @@ test "getting a list of mutes", %{conn: conn} do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.mute(user, other_user) {:ok, _user_relationships} = User.mute(user, other_user)
conn = conn =
conn conn
@ -906,7 +906,7 @@ test "getting a list of blocks", %{conn: conn} do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.block(user, other_user) {:ok, _user_relationship} = User.block(user, other_user)
conn = conn =
conn conn

View File

@ -289,7 +289,7 @@ test "doesn't see notifications after muting user with notifications", %{conn: c
assert length(json_response(conn, 200)) == 1 assert length(json_response(conn, 200)) == 1
{:ok, user} = User.mute(user, user2) {:ok, _user_relationships} = User.mute(user, user2)
conn = assign(build_conn(), :user, user) conn = assign(build_conn(), :user, user)
conn = get(conn, "/api/v1/notifications") conn = get(conn, "/api/v1/notifications")
@ -310,7 +310,7 @@ test "see notifications after muting user without notifications", %{conn: conn}
assert length(json_response(conn, 200)) == 1 assert length(json_response(conn, 200)) == 1
{:ok, user} = User.mute(user, user2, false) {:ok, _user_relationships} = User.mute(user, user2, false)
conn = assign(build_conn(), :user, user) conn = assign(build_conn(), :user, user)
conn = get(conn, "/api/v1/notifications") conn = get(conn, "/api/v1/notifications")
@ -333,7 +333,7 @@ test "see notifications after muting user with notifications and with_muted para
assert length(json_response(conn, 200)) == 1 assert length(json_response(conn, 200)) == 1
{:ok, user} = User.mute(user, user2) {:ok, _user_relationships} = User.mute(user, user2)
conn = assign(build_conn(), :user, user) conn = assign(build_conn(), :user, user)
conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"}) conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})

View File

@ -1108,7 +1108,7 @@ test "does not return users who have favorited the status but are blocked", %{
activity: activity activity: activity
} do } do
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.block(user, other_user) {:ok, _user_relationship} = User.block(user, other_user)
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user) {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
@ -1205,7 +1205,7 @@ test "does not return users who have reblogged the status but are blocked", %{
activity: activity activity: activity
} do } do
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.block(user, other_user) {:ok, _user_relationship} = User.block(user, other_user)
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user) {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)

View File

@ -194,7 +194,7 @@ test "doesn't include DMs from blocked users", %{conn: conn} do
blocker = insert(:user) blocker = insert(:user)
blocked = insert(:user) blocked = insert(:user)
user = insert(:user) user = insert(:user)
{:ok, blocker} = User.block(blocker, blocked) {:ok, _user_relationship} = User.block(blocker, blocked)
{:ok, _blocked_direct} = {:ok, _blocked_direct} =
CommonAPI.post(blocked, %{ CommonAPI.post(blocked, %{

View File

@ -190,9 +190,9 @@ test "represent a relationship for the following and followed user" do
{:ok, user} = User.follow(user, other_user) {:ok, user} = User.follow(user, other_user)
{:ok, other_user} = User.follow(other_user, user) {:ok, other_user} = User.follow(other_user, user)
{:ok, other_user} = User.subscribe(user, other_user) {:ok, _subscription} = User.subscribe(user, other_user)
{:ok, user} = User.mute(user, other_user, true) {:ok, _user_relationships} = User.mute(user, other_user, true)
{:ok, user} = CommonAPI.hide_reblogs(user, other_user) {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
expected = %{ expected = %{
id: to_string(other_user.id), id: to_string(other_user.id),
@ -218,9 +218,9 @@ test "represent a relationship for the blocking and blocked user" do
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.follow(user, other_user) {:ok, user} = User.follow(user, other_user)
{:ok, other_user} = User.subscribe(user, other_user) {:ok, _subscription} = User.subscribe(user, other_user)
{:ok, user} = User.block(user, other_user) {:ok, _user_relationship} = User.block(user, other_user)
{:ok, other_user} = User.block(other_user, user) {:ok, _user_relationship} = User.block(other_user, user)
expected = %{ expected = %{
id: to_string(other_user.id), id: to_string(other_user.id),
@ -291,7 +291,7 @@ test "represent an embedded relationship" do
other_user = insert(:user) other_user = insert(:user)
{:ok, other_user} = User.follow(other_user, user) {:ok, other_user} = User.follow(other_user, user)
{:ok, other_user} = User.block(other_user, user) {:ok, _user_relationship} = User.block(other_user, user)
{:ok, _} = User.follow(insert(:user), user) {:ok, _} = User.follow(insert(:user), user)
expected = %{ expected = %{

View File

@ -183,7 +183,7 @@ test "tells if the message is muted for some reason" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.mute(user, other_user) {:ok, _user_relationships} = User.mute(user, other_user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
status = StatusView.render("show.json", %{activity: activity}) status = StatusView.render("show.json", %{activity: activity})
@ -199,7 +199,7 @@ test "tells if the message is thread muted" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
{:ok, user} = User.mute(user, other_user) {:ok, _user_relationships} = User.mute(user, other_user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
status = StatusView.render("show.json", %{activity: activity, for: user}) status = StatusView.render("show.json", %{activity: activity, for: user})

View File

@ -59,7 +59,7 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl
user: user user: user
} do } do
blocked = insert(:user) blocked = insert(:user)
{:ok, user} = User.block(user, blocked) {:ok, _user_relationship} = User.block(user, blocked)
task = Task.async(fn -> refute_receive {:text, _}, 4_000 end) task = Task.async(fn -> refute_receive {:text, _}, 4_000 end)
@ -259,7 +259,7 @@ test "it sends message if recipients invalid and thread containment is enabled b
test "it doesn't send messages involving blocked users" do test "it doesn't send messages involving blocked users" do
user = insert(:user) user = insert(:user)
blocked_user = insert(:user) blocked_user = insert(:user)
{:ok, user} = User.block(user, blocked_user) {:ok, _user_relationship} = User.block(user, blocked_user)
task = task =
Task.async(fn -> Task.async(fn ->
@ -301,7 +301,7 @@ test "it doesn't send messages transitively involving blocked users" do
"public" => [fake_socket] "public" => [fake_socket]
} }
{:ok, blocker} = User.block(blocker, blockee) {:ok, _user_relationship} = User.block(blocker, blockee)
{:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"}) {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"})

View File

@ -387,7 +387,7 @@ test "returns error when user is blocked", %{conn: conn} do
user = insert(:user) user = insert(:user)
user2 = insert(:user) user2 = insert(:user)
{:ok, _user} = Pleroma.User.block(user2, user) {:ok, _user_block} = Pleroma.User.block(user2, user)
response = response =
conn conn
@ -485,7 +485,7 @@ test "returns error when user is blocked", %{conn: conn} do
Pleroma.Config.put([:user, :deny_follow_blocked], true) Pleroma.Config.put([:user, :deny_follow_blocked], true)
user = insert(:user) user = insert(:user)
user2 = insert(:user) user2 = insert(:user)
{:ok, _user} = Pleroma.User.block(user2, user) {:ok, _user_block} = Pleroma.User.block(user2, user)
response = response =
conn conn