Merge branch 'relations-preloading-for-statuses-rendering' into 'develop'

Performance improvements (timeline / statuses / notifications / accounts rendering)

See merge request pleroma/pleroma!2323
This commit is contained in:
lain 2020-03-27 19:14:46 +00:00
commit 4e81b4b190
14 changed files with 575 additions and 139 deletions

View file

@ -35,6 +35,13 @@ defmodule Pleroma.Activity.Queries do
from(a in query, where: a.actor == ^ap_id) from(a in query, where: a.actor == ^ap_id)
end end
def find_by_object_ap_id(activities, object_ap_id) do
Enum.find(
activities,
&(object_ap_id in [is_map(&1.data["object"]) && &1.data["object"]["id"], &1.data["object"]])
)
end
@spec by_object_id(query, String.t() | [String.t()]) :: query @spec by_object_id(query, String.t() | [String.t()]) :: query
def by_object_id(query \\ Activity, object_id) def by_object_id(query \\ Activity, object_id)

View file

@ -129,21 +129,18 @@ defmodule Pleroma.Conversation.Participation do
end end
def restrict_recipients(query, user, %{"recipients" => user_ids}) do def restrict_recipients(query, user, %{"recipients" => user_ids}) do
user_ids = user_binary_ids =
[user.id | user_ids] [user.id | user_ids]
|> Enum.uniq() |> Enum.uniq()
|> Enum.reduce([], fn user_id, acc -> |> User.binary_id()
{:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id)
[user_id | acc]
end)
conversation_subquery = conversation_subquery =
__MODULE__ __MODULE__
|> group_by([p], p.conversation_id) |> group_by([p], p.conversation_id)
|> having( |> having(
[p], [p],
count(p.user_id) == ^length(user_ids) and count(p.user_id) == ^length(user_binary_ids) and
fragment("array_agg(?) @> ?", p.user_id, ^user_ids) fragment("array_agg(?) @> ?", p.user_id, ^user_binary_ids)
) )
|> select([p], %{id: p.conversation_id}) |> select([p], %{id: p.conversation_id})

View file

@ -129,4 +129,32 @@ defmodule Pleroma.FollowingRelationship do
move_following(origin, target) move_following(origin, target)
end end
end end
def all_between_user_sets(
source_users,
target_users
)
when is_list(source_users) and is_list(target_users) do
source_user_ids = User.binary_id(source_users)
target_user_ids = User.binary_id(target_users)
__MODULE__
|> where(
fragment(
"(follower_id = ANY(?) AND following_id = ANY(?)) OR \
(follower_id = ANY(?) AND following_id = ANY(?))",
^source_user_ids,
^target_user_ids,
^target_user_ids,
^source_user_ids
)
)
|> Repo.all()
end
def find(following_relationships, follower, following) do
Enum.find(following_relationships, fn
fr -> fr.follower_id == follower.id and fr.following_id == following.id
end)
end
end end

View file

@ -25,10 +25,10 @@ defmodule Pleroma.ThreadMute do
end end
def query(user_id, context) do def query(user_id, context) do
{:ok, user_id} = FlakeId.Ecto.CompatType.dump(user_id) user_binary_id = User.binary_id(user_id)
ThreadMute ThreadMute
|> where(user_id: ^user_id) |> where(user_id: ^user_binary_id)
|> where(context: ^context) |> where(context: ^context)
end end
@ -68,8 +68,8 @@ defmodule Pleroma.ThreadMute do
|> Repo.delete_all() |> Repo.delete_all()
end end
def check_muted(user_id, context) do def exists?(user_id, context) do
query(user_id, context) query(user_id, context)
|> Repo.all() |> Repo.exists?()
end end
end end

View file

@ -226,6 +226,24 @@ defmodule Pleroma.User do
end end
end end
@doc """
Dumps Flake Id to SQL-compatible format (16-byte UUID).
E.g. "9pQtDGXuq4p3VlcJEm" -> <<0, 0, 1, 110, 179, 218, 42, 92, 213, 41, 44, 227, 95, 213, 0, 0>>
"""
def binary_id(source_id) when is_binary(source_id) do
with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
dumped_id
else
_ -> source_id
end
end
def binary_id(source_ids) when is_list(source_ids) do
Enum.map(source_ids, &binary_id/1)
end
def binary_id(%User{} = user), do: binary_id(user.id)
@doc "Returns status account" @doc "Returns status account"
@spec account_status(User.t()) :: account_status() @spec account_status(User.t()) :: account_status()
def account_status(%User{deactivated: true}), do: :deactivated def account_status(%User{deactivated: true}), do: :deactivated
@ -753,7 +771,14 @@ defmodule Pleroma.User do
def get_follow_state(%User{} = follower, %User{} = following) do def get_follow_state(%User{} = follower, %User{} = following) do
following_relationship = FollowingRelationship.get(follower, following) following_relationship = FollowingRelationship.get(follower, following)
get_follow_state(follower, following, following_relationship)
end
def get_follow_state(
%User{} = follower,
%User{} = following,
following_relationship
) do
case {following_relationship, following.local} do case {following_relationship, following.local} do
{nil, false} -> {nil, false} ->
case Utils.fetch_latest_follow(follower, following) do case Utils.fetch_latest_follow(follower, following) do
@ -1747,8 +1772,12 @@ defmodule Pleroma.User do
|> Repo.all() |> Repo.all()
end end
def muting_reblogs?(%User{} = user, %User{} = target) do
UserRelationship.reblog_mute_exists?(user, target)
end
def showing_reblogs?(%User{} = user, %User{} = target) do def showing_reblogs?(%User{} = user, %User{} = target) do
not UserRelationship.reblog_mute_exists?(user, target) not muting_reblogs?(user, target)
end end
@doc """ @doc """

View file

@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do
import Ecto.Changeset import Ecto.Changeset
import Ecto.Query import Ecto.Query
alias Pleroma.FollowingRelationship
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserRelationship alias Pleroma.UserRelationship
@ -37,6 +38,10 @@ defmodule Pleroma.UserRelationship do
do: exists?(unquote(relationship_type), source, target) do: exists?(unquote(relationship_type), source, target)
end end
def user_relationship_types, do: Keyword.keys(user_relationship_mappings())
def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__()
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
user_relationship user_relationship
|> cast(params, [:relationship_type, :source_id, :target_id]) |> cast(params, [:relationship_type, :source_id, :target_id])
@ -75,6 +80,73 @@ defmodule Pleroma.UserRelationship do
end end
end end
def dictionary(
source_users,
target_users,
source_to_target_rel_types \\ nil,
target_to_source_rel_types \\ nil
)
when is_list(source_users) and is_list(target_users) do
source_user_ids = User.binary_id(source_users)
target_user_ids = User.binary_id(target_users)
get_rel_type_codes = fn rel_type -> user_relationship_mappings()[rel_type] end
source_to_target_rel_types =
Enum.map(source_to_target_rel_types || user_relationship_types(), &get_rel_type_codes.(&1))
target_to_source_rel_types =
Enum.map(target_to_source_rel_types || user_relationship_types(), &get_rel_type_codes.(&1))
__MODULE__
|> where(
fragment(
"(source_id = ANY(?) AND target_id = ANY(?) AND relationship_type = ANY(?)) OR \
(source_id = ANY(?) AND target_id = ANY(?) AND relationship_type = ANY(?))",
^source_user_ids,
^target_user_ids,
^source_to_target_rel_types,
^target_user_ids,
^source_user_ids,
^target_to_source_rel_types
)
)
|> select([ur], [ur.relationship_type, ur.source_id, ur.target_id])
|> Repo.all()
end
def exists?(dictionary, rel_type, source, target, func) do
cond do
is_nil(source) or is_nil(target) ->
false
dictionary ->
[rel_type, source.id, target.id] in dictionary
true ->
func.(source, target)
end
end
@doc ":relationships option for StatusView / AccountView / NotificationView"
def view_relationships_option(nil = _reading_user, _actors) do
%{user_relationships: [], following_relationships: []}
end
def view_relationships_option(%User{} = reading_user, actors) do
user_relationships =
UserRelationship.dictionary(
[reading_user],
actors,
[:block, :mute, :notification_mute, :reblog_mute],
[:block, :inverse_subscription]
)
following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
%{user_relationships: user_relationships, following_relationships: following_relationships}
end
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
changeset changeset
|> validate_change(:target_id, fn _, target_id -> |> validate_change(:target_id, fn _, target_id ->

View file

@ -358,7 +358,7 @@ defmodule Pleroma.Web.CommonAPI do
def thread_muted?(%{id: nil} = _user, _activity), do: false def thread_muted?(%{id: nil} = _user, _activity), do: false
def thread_muted?(user, activity) do def thread_muted?(user, activity) do
ThreadMute.check_muted(user.id, activity.data["context"]) != [] ThreadMute.exists?(user.id, activity.data["context"])
end end
def report(user, %{"account_id" => account_id} = data) do def report(user, %{"account_id" => account_id} = data) do

View file

@ -5,12 +5,28 @@
defmodule Pleroma.Web.MastodonAPI.AccountView do defmodule Pleroma.Web.MastodonAPI.AccountView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.FollowingRelationship
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
def render("index.json", %{users: users} = opts) do def render("index.json", %{users: users} = opts) do
relationships_opt =
cond do
Map.has_key?(opts, :relationships) ->
opts[:relationships]
is_nil(opts[:for]) ->
UserRelationship.view_relationships_option(nil, [])
true ->
UserRelationship.view_relationships_option(opts[:for], users)
end
opts = Map.put(opts, :relationships, relationships_opt)
users users
|> render_many(AccountView, "show.json", opts) |> render_many(AccountView, "show.json", opts)
|> Enum.filter(&Enum.any?/1) |> Enum.filter(&Enum.any?/1)
@ -35,27 +51,107 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
%{} %{}
end end
def render("relationship.json", %{user: %User{} = user, target: %User{} = target}) do def render(
follow_state = User.get_follow_state(user, target) "relationship.json",
%{user: %User{} = reading_user, target: %User{} = target} = opts
) do
user_relationships = get_in(opts, [:relationships, :user_relationships])
following_relationships = get_in(opts, [:relationships, :following_relationships])
follow_state =
if following_relationships do
user_to_target_following_relation =
FollowingRelationship.find(following_relationships, reading_user, target)
User.get_follow_state(reading_user, target, user_to_target_following_relation)
else
User.get_follow_state(reading_user, target)
end
followed_by =
if following_relationships do
case FollowingRelationship.find(following_relationships, target, reading_user) do
%{state: "accept"} -> true
_ -> false
end
else
User.following?(target, reading_user)
end
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
%{ %{
id: to_string(target.id), id: to_string(target.id),
following: follow_state == "accept", following: follow_state == "accept",
followed_by: User.following?(target, user), followed_by: followed_by,
blocking: User.blocks_user?(user, target), blocking:
blocked_by: User.blocks_user?(target, user), UserRelationship.exists?(
muting: User.mutes?(user, target), user_relationships,
muting_notifications: User.muted_notifications?(user, target), :block,
subscribing: User.subscribed_to?(user, target), reading_user,
target,
&User.blocks_user?(&1, &2)
),
blocked_by:
UserRelationship.exists?(
user_relationships,
:block,
target,
reading_user,
&User.blocks_user?(&1, &2)
),
muting:
UserRelationship.exists?(
user_relationships,
:mute,
reading_user,
target,
&User.mutes?(&1, &2)
),
muting_notifications:
UserRelationship.exists?(
user_relationships,
:notification_mute,
reading_user,
target,
&User.muted_notifications?(&1, &2)
),
subscribing:
UserRelationship.exists?(
user_relationships,
:inverse_subscription,
target,
reading_user,
&User.subscribed_to?(&2, &1)
),
requested: follow_state == "pending", requested: follow_state == "pending",
domain_blocking: User.blocks_domain?(user, target), domain_blocking: User.blocks_domain?(reading_user, target),
showing_reblogs: User.showing_reblogs?(user, target), showing_reblogs:
not UserRelationship.exists?(
user_relationships,
:reblog_mute,
reading_user,
target,
&User.muting_reblogs?(&1, &2)
),
endorsed: false endorsed: false
} }
end end
def render("relationships.json", %{user: user, targets: targets}) do def render("relationships.json", %{user: user, targets: targets} = opts) do
render_many(targets, AccountView, "relationship.json", user: user, as: :target) relationships_opt =
cond do
Map.has_key?(opts, :relationships) ->
opts[:relationships]
is_nil(opts[:for]) ->
UserRelationship.view_relationships_option(nil, [])
true ->
UserRelationship.view_relationships_option(user, targets)
end
render_opts = %{as: :target, user: user, relationships: relationships_opt}
render_many(targets, AccountView, "relationship.json", render_opts)
end end
defp do_render("show.json", %{user: user} = opts) do defp do_render("show.json", %{user: user} = opts) do
@ -93,7 +189,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
} }
end) end)
relationship = render("relationship.json", %{user: opts[:for], target: user}) relationship =
render("relationship.json", %{
user: opts[:for],
target: user,
relationships: opts[:relationships]
})
%{ %{
id: to_string(user.id), id: to_string(user.id),

View file

@ -8,24 +8,86 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
def render("index.json", %{notifications: notifications, for: user}) do def render("index.json", %{notifications: notifications, for: reading_user} = opts) do
safe_render_many(notifications, NotificationView, "show.json", %{for: user}) activities = Enum.map(notifications, & &1.activity)
parent_activities =
activities
|> Enum.filter(
&(Activity.mastodon_notification_type(&1) in [
"favourite",
"reblog",
"pleroma:emoji_reaction"
])
)
|> Enum.map(& &1.data["object"])
|> Activity.create_by_object_ap_id()
|> Activity.with_preloaded_object(:left)
|> Pleroma.Repo.all()
relationships_opt =
cond do
Map.has_key?(opts, :relationships) ->
opts[:relationships]
is_nil(opts[:for]) ->
UserRelationship.view_relationships_option(nil, [])
true ->
move_activities_targets =
activities
|> Enum.filter(&(Activity.mastodon_notification_type(&1) == "move"))
|> Enum.map(&User.get_cached_by_ap_id(&1.data["target"]))
actors =
activities
|> Enum.map(fn a -> User.get_cached_by_ap_id(a.data["actor"]) end)
|> Enum.filter(& &1)
|> Kernel.++(move_activities_targets)
UserRelationship.view_relationships_option(reading_user, actors)
end end
def render("show.json", %{ opts = %{
for: reading_user,
parent_activities: parent_activities,
relationships: relationships_opt
}
safe_render_many(notifications, NotificationView, "show.json", opts)
end
def render(
"show.json",
%{
notification: %Notification{activity: activity} = notification, notification: %Notification{activity: activity} = notification,
for: user for: reading_user
}) do } = opts
) do
actor = User.get_cached_by_ap_id(activity.data["actor"]) actor = User.get_cached_by_ap_id(activity.data["actor"])
parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
parent_activity_fn = fn ->
if opts[:parent_activities] do
Activity.Queries.find_by_object_ap_id(opts[:parent_activities], activity.data["object"])
else
Activity.get_create_by_object_ap_id(activity.data["object"])
end
end
mastodon_type = Activity.mastodon_notification_type(activity) mastodon_type = Activity.mastodon_notification_type(activity)
with %{id: _} = account <- AccountView.render("show.json", %{user: actor, for: user}) do with %{id: _} = account <-
AccountView.render("show.json", %{
user: actor,
for: reading_user,
relationships: opts[:relationships]
}) do
response = %{ response = %{
id: to_string(notification.id), id: to_string(notification.id),
type: mastodon_type, type: mastodon_type,
@ -36,24 +98,28 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
} }
} }
render_opts = %{relationships: opts[:relationships]}
case mastodon_type do case mastodon_type do
"mention" -> "mention" ->
put_status(response, activity, user) put_status(response, activity, reading_user, render_opts)
"favourite" -> "favourite" ->
put_status(response, parent_activity, user) put_status(response, parent_activity_fn.(), reading_user, render_opts)
"reblog" -> "reblog" ->
put_status(response, parent_activity, user) put_status(response, parent_activity_fn.(), reading_user, render_opts)
"move" -> "move" ->
put_target(response, activity, user) put_target(response, activity, reading_user, render_opts)
"follow" -> "follow" ->
response response
"pleroma:emoji_reaction" -> "pleroma:emoji_reaction" ->
put_status(response, parent_activity, user) |> put_emoji(activity) response
|> put_status(parent_activity_fn.(), reading_user, render_opts)
|> put_emoji(activity)
_ -> _ ->
nil nil
@ -64,16 +130,21 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
end end
defp put_emoji(response, activity) do defp put_emoji(response, activity) do
response Map.put(response, :emoji, activity.data["content"])
|> Map.put(:emoji, activity.data["content"])
end end
defp put_status(response, activity, user) do defp put_status(response, activity, reading_user, opts) do
Map.put(response, :status, StatusView.render("show.json", %{activity: activity, for: user})) status_render_opts = Map.merge(opts, %{activity: activity, for: reading_user})
status_render = StatusView.render("show.json", status_render_opts)
Map.put(response, :status, status_render)
end end
defp put_target(response, activity, user) do defp put_target(response, activity, reading_user, opts) do
target = User.get_cached_by_ap_id(activity.data["target"]) target_user = User.get_cached_by_ap_id(activity.data["target"])
Map.put(response, :target, AccountView.render("show.json", %{user: target, for: user})) target_render_opts = Map.merge(opts, %{user: target_user, for: reading_user})
target_render = AccountView.render("show.json", target_render_opts)
Map.put(response, :target, target_render)
end end
end end

View file

@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
@ -71,10 +72,41 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end end
def render("index.json", opts) do def render("index.json", opts) do
replied_to_activities = get_replied_to_activities(opts.activities) # To do: check AdminAPIControllerTest on the reasons behind nil activities in the list
opts = Map.put(opts, :replied_to_activities, replied_to_activities) activities = Enum.filter(opts.activities, & &1)
replied_to_activities = get_replied_to_activities(activities)
safe_render_many(opts.activities, StatusView, "show.json", opts) parent_activities =
activities
|> Enum.filter(&(&1.data["type"] == "Announce" && &1.data["object"]))
|> Enum.map(&Object.normalize(&1).data["id"])
|> Activity.create_by_object_ap_id()
|> Activity.with_preloaded_object(:left)
|> Activity.with_preloaded_bookmark(opts[:for])
|> Activity.with_set_thread_muted_field(opts[:for])
|> Repo.all()
relationships_opt =
cond do
Map.has_key?(opts, :relationships) ->
opts[:relationships]
is_nil(opts[:for]) ->
UserRelationship.view_relationships_option(nil, [])
true ->
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
UserRelationship.view_relationships_option(opts[:for], actors)
end
opts =
opts
|> Map.put(:replied_to_activities, replied_to_activities)
|> Map.put(:parent_activities, parent_activities)
|> Map.put(:relationships, relationships_opt)
safe_render_many(activities, StatusView, "show.json", opts)
end end
def render( def render(
@ -85,17 +117,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
created_at = Utils.to_masto_date(activity.data["published"]) created_at = Utils.to_masto_date(activity.data["published"])
activity_object = Object.normalize(activity) activity_object = Object.normalize(activity)
reblogged_activity = reblogged_parent_activity =
if opts[:parent_activities] do
Activity.Queries.find_by_object_ap_id(
opts[:parent_activities],
activity_object.data["id"]
)
else
Activity.create_by_object_ap_id(activity_object.data["id"]) Activity.create_by_object_ap_id(activity_object.data["id"])
|> Activity.with_preloaded_bookmark(opts[:for]) |> Activity.with_preloaded_bookmark(opts[:for])
|> Activity.with_set_thread_muted_field(opts[:for]) |> Activity.with_set_thread_muted_field(opts[:for])
|> Repo.one() |> Repo.one()
end
reblogged = render("show.json", Map.put(opts, :activity, reblogged_activity)) reblog_rendering_opts = Map.put(opts, :activity, reblogged_parent_activity)
reblogged = render("show.json", reblog_rendering_opts)
favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || []) favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || [])
bookmarked = Activity.get_bookmark(reblogged_activity, opts[:for]) != nil bookmarked = Activity.get_bookmark(reblogged_parent_activity, opts[:for]) != nil
mentions = mentions =
activity.recipients activity.recipients
@ -107,7 +147,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
id: to_string(activity.id), id: to_string(activity.id),
uri: activity_object.data["id"], uri: activity_object.data["id"],
url: activity_object.data["id"], url: activity_object.data["id"],
account: AccountView.render("show.json", %{user: user, for: opts[:for]}), account:
AccountView.render("show.json", %{
user: user,
for: opts[:for],
relationships: opts[:relationships]
}),
in_reply_to_id: nil, in_reply_to_id: nil,
in_reply_to_account_id: nil, in_reply_to_account_id: nil,
reblog: reblogged, reblog: reblogged,
@ -116,7 +161,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogs_count: 0, reblogs_count: 0,
replies_count: 0, replies_count: 0,
favourites_count: 0, favourites_count: 0,
reblogged: reblogged?(reblogged_activity, opts[:for]), reblogged: reblogged?(reblogged_parent_activity, opts[:for]),
favourited: present?(favorited), favourited: present?(favorited),
bookmarked: present?(bookmarked), bookmarked: present?(bookmarked),
muted: false, muted: false,
@ -183,9 +228,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end end
thread_muted? = thread_muted? =
case activity.thread_muted? do cond do
thread_muted? when is_boolean(thread_muted?) -> thread_muted? is_nil(opts[:for]) -> false
nil -> (opts[:for] && CommonAPI.thread_muted?(opts[:for], activity)) || false is_boolean(activity.thread_muted?) -> activity.thread_muted?
true -> CommonAPI.thread_muted?(opts[:for], activity)
end end
attachment_data = object.data["attachment"] || [] attachment_data = object.data["attachment"] || []
@ -253,11 +299,26 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
_ -> [] _ -> []
end end
muted =
thread_muted? ||
UserRelationship.exists?(
get_in(opts, [:relationships, :user_relationships]),
:mute,
opts[:for],
user,
fn for_user, user -> User.mutes?(for_user, user) end
)
%{ %{
id: to_string(activity.id), id: to_string(activity.id),
uri: object.data["id"], uri: object.data["id"],
url: url, url: url,
account: AccountView.render("show.json", %{user: user, for: opts[:for]}), account:
AccountView.render("show.json", %{
user: user,
for: opts[:for],
relationships: opts[:relationships]
}),
in_reply_to_id: reply_to && to_string(reply_to.id), in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil, reblog: nil,
@ -270,7 +331,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogged: reblogged?(activity, opts[:for]), reblogged: reblogged?(activity, opts[:for]),
favourited: present?(favorited), favourited: present?(favorited),
bookmarked: present?(bookmarked), bookmarked: present?(bookmarked),
muted: thread_muted? || User.mutes?(opts[:for], user), muted: muted,
pinned: pinned?(activity, user), pinned: pinned?(activity, user),
sensitive: sensitive, sensitive: sensitive,
spoiler_text: summary, spoiler_text: summary,

View file

@ -21,9 +21,12 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
setup do: oauth_access(["read:statuses"]) setup do: oauth_access(["read:statuses"])
test "the home timeline", %{user: user, conn: conn} do test "the home timeline", %{user: user, conn: conn} do
following = insert(:user) following = insert(:user, nickname: "followed")
third_user = insert(:user, nickname: "repeated")
{:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) {:ok, _activity} = CommonAPI.post(following, %{"status" => "post"})
{:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"})
{:ok, _, _} = CommonAPI.repeat(activity.id, following)
ret_conn = get(conn, "/api/v1/timelines/home") ret_conn = get(conn, "/api/v1/timelines/home")
@ -31,9 +34,54 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
{:ok, _user} = User.follow(user, following) {:ok, _user} = User.follow(user, following)
conn = get(conn, "/api/v1/timelines/home") ret_conn = get(conn, "/api/v1/timelines/home")
assert [%{"content" => "test"}] = json_response(conn, :ok) assert [
%{
"reblog" => %{
"content" => "repeated post",
"account" => %{
"pleroma" => %{
"relationship" => %{"following" => false, "followed_by" => false}
}
}
},
"account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
},
%{
"content" => "post",
"account" => %{
"acct" => "followed",
"pleroma" => %{"relationship" => %{"following" => true}}
}
}
] = json_response(ret_conn, :ok)
{:ok, _user} = User.follow(third_user, user)
ret_conn = get(conn, "/api/v1/timelines/home")
assert [
%{
"reblog" => %{
"content" => "repeated post",
"account" => %{
"acct" => "repeated",
"pleroma" => %{
"relationship" => %{"following" => false, "followed_by" => true}
}
}
},
"account" => %{"pleroma" => %{"relationship" => %{"following" => true}}}
},
%{
"content" => "post",
"account" => %{
"acct" => "followed",
"pleroma" => %{"relationship" => %{"following" => true}}
}
}
] = json_response(ret_conn, :ok)
end end
test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do

View file

@ -4,8 +4,11 @@
defmodule Pleroma.Web.MastodonAPI.AccountViewTest do defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
@ -182,6 +185,29 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
end end
describe "relationship" do describe "relationship" do
defp test_relationship_rendering(user, other_user, expected_result) do
opts = %{user: user, target: other_user, relationships: nil}
assert expected_result == AccountView.render("relationship.json", opts)
relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
opts = Map.put(opts, :relationships, relationships_opt)
assert expected_result == AccountView.render("relationship.json", opts)
end
@blank_response %{
following: false,
followed_by: false,
blocking: false,
blocked_by: false,
muting: false,
muting_notifications: false,
subscribing: false,
requested: false,
domain_blocking: false,
showing_reblogs: true,
endorsed: false
}
test "represent a relationship for the following and followed user" do test "represent a relationship for the following and followed user" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)
@ -192,23 +218,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
{:ok, _user_relationships} = User.mute(user, other_user, true) {:ok, _user_relationships} = User.mute(user, other_user, true)
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user) {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
expected = %{ expected =
id: to_string(other_user.id), Map.merge(
@blank_response,
%{
following: true, following: true,
followed_by: true, followed_by: true,
blocking: false,
blocked_by: false,
muting: true, muting: true,
muting_notifications: true, muting_notifications: true,
subscribing: true, subscribing: true,
requested: false,
domain_blocking: false,
showing_reblogs: false, showing_reblogs: false,
endorsed: false id: to_string(other_user.id)
} }
)
assert expected == test_relationship_rendering(user, other_user, expected)
AccountView.render("relationship.json", %{user: user, target: other_user})
end end
test "represent a relationship for the blocking and blocked user" do test "represent a relationship for the blocking and blocked user" do
@ -220,23 +244,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
{:ok, _user_relationship} = User.block(user, other_user) {:ok, _user_relationship} = User.block(user, other_user)
{:ok, _user_relationship} = User.block(other_user, user) {:ok, _user_relationship} = User.block(other_user, user)
expected = %{ expected =
id: to_string(other_user.id), Map.merge(
following: false, @blank_response,
followed_by: false, %{following: false, blocking: true, blocked_by: true, id: to_string(other_user.id)}
blocking: true, )
blocked_by: true,
muting: false,
muting_notifications: false,
subscribing: false,
requested: false,
domain_blocking: false,
showing_reblogs: true,
endorsed: false
}
assert expected == test_relationship_rendering(user, other_user, expected)
AccountView.render("relationship.json", %{user: user, target: other_user})
end end
test "represent a relationship for the user blocking a domain" do test "represent a relationship for the user blocking a domain" do
@ -245,8 +259,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
{:ok, user} = User.block_domain(user, "bad.site") {:ok, user} = User.block_domain(user, "bad.site")
assert %{domain_blocking: true, blocking: false} = expected =
AccountView.render("relationship.json", %{user: user, target: other_user}) Map.merge(
@blank_response,
%{domain_blocking: true, blocking: false, id: to_string(other_user.id)}
)
test_relationship_rendering(user, other_user, expected)
end end
test "represent a relationship for the user with a pending follow request" do test "represent a relationship for the user with a pending follow request" do
@ -257,23 +276,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id) other_user = User.get_cached_by_id(other_user.id)
expected = %{ expected =
id: to_string(other_user.id), Map.merge(
following: false, @blank_response,
followed_by: false, %{requested: true, following: false, id: to_string(other_user.id)}
blocking: false, )
blocked_by: false,
muting: false,
muting_notifications: false,
subscribing: false,
requested: true,
domain_blocking: false,
showing_reblogs: true,
endorsed: false
}
assert expected == test_relationship_rendering(user, other_user, expected)
AccountView.render("relationship.json", %{user: user, target: other_user})
end end
end end

View file

@ -16,6 +16,21 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
import Pleroma.Factory import Pleroma.Factory
defp test_notifications_rendering(notifications, user, expected_result) do
result = NotificationView.render("index.json", %{notifications: notifications, for: user})
assert expected_result == result
result =
NotificationView.render("index.json", %{
notifications: notifications,
for: user,
relationships: nil
})
assert expected_result == result
end
test "Mention notification" do test "Mention notification" do
user = insert(:user) user = insert(:user)
mentioned_user = insert(:user) mentioned_user = insert(:user)
@ -32,10 +47,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
created_at: Utils.to_masto_date(notification.inserted_at) created_at: Utils.to_masto_date(notification.inserted_at)
} }
result = test_notifications_rendering([notification], mentioned_user, [expected])
NotificationView.render("index.json", %{notifications: [notification], for: mentioned_user})
assert [expected] == result
end end
test "Favourite notification" do test "Favourite notification" do
@ -55,9 +67,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
created_at: Utils.to_masto_date(notification.inserted_at) created_at: Utils.to_masto_date(notification.inserted_at)
} }
result = NotificationView.render("index.json", %{notifications: [notification], for: user}) test_notifications_rendering([notification], user, [expected])
assert [expected] == result
end end
test "Reblog notification" do test "Reblog notification" do
@ -77,9 +87,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
created_at: Utils.to_masto_date(notification.inserted_at) created_at: Utils.to_masto_date(notification.inserted_at)
} }
result = NotificationView.render("index.json", %{notifications: [notification], for: user}) test_notifications_rendering([notification], user, [expected])
assert [expected] == result
end end
test "Follow notification" do test "Follow notification" do
@ -96,16 +104,12 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
created_at: Utils.to_masto_date(notification.inserted_at) created_at: Utils.to_masto_date(notification.inserted_at)
} }
result = test_notifications_rendering([notification], followed, [expected])
NotificationView.render("index.json", %{notifications: [notification], for: followed})
assert [expected] == result
User.perform(:delete, follower) User.perform(:delete, follower)
notification = Notification |> Repo.one() |> Repo.preload(:activity) notification = Notification |> Repo.one() |> Repo.preload(:activity)
assert [] == test_notifications_rendering([notification], followed, [])
NotificationView.render("index.json", %{notifications: [notification], for: followed})
end end
test "Move notification" do test "Move notification" do
@ -131,8 +135,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
created_at: Utils.to_masto_date(notification.inserted_at) created_at: Utils.to_masto_date(notification.inserted_at)
} }
assert [expected] == test_notifications_rendering([notification], follower, [expected])
NotificationView.render("index.json", %{notifications: [notification], for: follower})
end end
test "EmojiReact notification" do test "EmojiReact notification" do
@ -158,7 +161,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
created_at: Utils.to_masto_date(notification.inserted_at) created_at: Utils.to_masto_date(notification.inserted_at)
} }
assert expected == test_notifications_rendering([notification], user, [expected])
NotificationView.render("show.json", %{notification: notification, for: user})
end end
end end

View file

@ -12,10 +12,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserRelationship
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
import Pleroma.Factory import Pleroma.Factory
import Tesla.Mock import Tesla.Mock
@ -212,12 +214,21 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
{:ok, _user_relationships} = 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})
relationships_opt = UserRelationship.view_relationships_option(user, [other_user])
opts = %{activity: activity}
status = StatusView.render("show.json", opts)
assert status.muted == false assert status.muted == false
status = StatusView.render("show.json", %{activity: activity, for: user}) status = StatusView.render("show.json", Map.put(opts, :relationships, relationships_opt))
assert status.muted == false
for_opts = %{activity: activity, for: user}
status = StatusView.render("show.json", for_opts)
assert status.muted == true
status = StatusView.render("show.json", Map.put(for_opts, :relationships, relationships_opt))
assert status.muted == true assert status.muted == true
end end