Further preloading (more endpoints), refactoring, tests.

This commit is contained in:
Ivan Tashkinov 2020-03-25 20:33:34 +03:00
parent be5e2c4dbb
commit 460e41585c
9 changed files with 179 additions and 125 deletions

View file

@ -151,4 +151,10 @@ defmodule Pleroma.FollowingRelationship do
) )
|> Repo.all() |> Repo.all()
end 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

@ -218,7 +218,10 @@ defmodule Pleroma.User do
end end
end end
@doc "Dumps id to SQL-compatible format" @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 def binary_id(source_id) when is_binary(source_id) do
with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do with {:ok, dumped_id} <- FlakeId.Ecto.CompatType.dump(source_id) do
dumped_id dumped_id

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
@ -124,6 +125,25 @@ defmodule Pleroma.UserRelationship do
end end
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

@ -5,20 +5,23 @@
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.UserRelationship
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.MediaProxy alias Pleroma.Web.MediaProxy
defp find_following_rel(following_relationships, follower, following) do
Enum.find(following_relationships, fn
fr -> fr.follower_id == follower.id and fr.following_id == following.id
end)
end
def render("index.json", %{users: users} = opts) do def render("index.json", %{users: users} = opts) do
relationships_opt =
if Map.has_key?(opts, :relationships) do
opts[:relationships]
else
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)
@ -53,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
follow_state = follow_state =
if following_relationships do if following_relationships do
user_to_target_following_relation = user_to_target_following_relation =
find_following_rel(following_relationships, reading_user, target) FollowingRelationship.find(following_relationships, reading_user, target)
User.get_follow_state(reading_user, target, user_to_target_following_relation) User.get_follow_state(reading_user, target, user_to_target_following_relation)
else else
@ -62,7 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
followed_by = followed_by =
if following_relationships do if following_relationships do
case find_following_rel(following_relationships, target, reading_user) do case FollowingRelationship.find(following_relationships, target, reading_user) do
%{state: "accept"} -> true %{state: "accept"} -> true
_ -> false _ -> false
end end
@ -70,7 +73,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
User.following?(target, reading_user) User.following?(target, reading_user)
end end
# NOTE: adjust StatusView.relationships_opts/2 if adding new relation-related flags # 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",
@ -129,11 +132,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
} }
end end
def render("relationships.json", %{user: user, targets: targets}) do def render("relationships.json", %{user: user, targets: targets} = opts) do
relationships_opts = StatusView.relationships_opts(user, targets) relationships_opt =
opts = %{as: :target, user: user, relationships: relationships_opts} if Map.has_key?(opts, :relationships) do
opts[:relationships]
else
UserRelationship.view_relationships_option(user, targets)
end
render_many(targets, AccountView, "relationship.json", opts) 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

View file

@ -8,12 +8,13 @@ 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: reading_user}) do def render("index.json", %{notifications: notifications, for: reading_user} = opts) do
activities = Enum.map(notifications, & &1.activity) activities = Enum.map(notifications, & &1.activity)
parent_activities = parent_activities =
@ -30,21 +31,28 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
|> Activity.with_preloaded_object(:left) |> Activity.with_preloaded_object(:left)
|> Pleroma.Repo.all() |> Pleroma.Repo.all()
move_activities_targets = relationships_opt =
activities if Map.has_key?(opts, :relationships) do
|> Enum.filter(&(Activity.mastodon_notification_type(&1) == "move")) opts[:relationships]
|> Enum.map(&User.get_cached_by_ap_id(&1.data["target"])) else
move_activities_targets =
activities
|> Enum.filter(&(Activity.mastodon_notification_type(&1) == "move"))
|> Enum.map(&User.get_cached_by_ap_id(&1.data["target"]))
actors = actors =
activities activities
|> Enum.map(fn a -> User.get_cached_by_ap_id(a.data["actor"]) end) |> Enum.map(fn a -> User.get_cached_by_ap_id(a.data["actor"]) end)
|> Enum.filter(& &1) |> Enum.filter(& &1)
|> Kernel.++(move_activities_targets) |> Kernel.++(move_activities_targets)
UserRelationship.view_relationships_option(reading_user, actors)
end
opts = %{ opts = %{
for: reading_user, for: reading_user,
parent_activities: parent_activities, parent_activities: parent_activities,
relationships: StatusView.relationships_opts(reading_user, actors) relationships: relationships_opt
} }
safe_render_many(notifications, NotificationView, "show.json", opts) safe_render_many(notifications, NotificationView, "show.json", opts)
@ -85,27 +93,27 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
} }
} }
relationships_opts = %{relationships: opts[:relationships]} relationships_opt = %{relationships: opts[:relationships]}
case mastodon_type do case mastodon_type do
"mention" -> "mention" ->
put_status(response, activity, reading_user, relationships_opts) put_status(response, activity, reading_user, relationships_opt)
"favourite" -> "favourite" ->
put_status(response, parent_activity_fn.(), reading_user, relationships_opts) put_status(response, parent_activity_fn.(), reading_user, relationships_opt)
"reblog" -> "reblog" ->
put_status(response, parent_activity_fn.(), reading_user, relationships_opts) put_status(response, parent_activity_fn.(), reading_user, relationships_opt)
"move" -> "move" ->
put_target(response, activity, reading_user, relationships_opts) put_target(response, activity, reading_user, relationships_opt)
"follow" -> "follow" ->
response response
"pleroma:emoji_reaction" -> "pleroma:emoji_reaction" ->
response response
|> put_status(parent_activity_fn.(), reading_user, relationships_opts) |> put_status(parent_activity_fn.(), reading_user, relationships_opt)
|> put_emoji(activity) |> put_emoji(activity)
_ -> _ ->

View file

@ -9,7 +9,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.ActivityExpiration alias Pleroma.ActivityExpiration
alias Pleroma.FollowingRelationship
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
@ -72,24 +71,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
present?(user && user.ap_id in (object.data["announcements"] || [])) present?(user && user.ap_id in (object.data["announcements"] || []))
end end
def relationships_opts(_reading_user = nil, _actors) do
%{user_relationships: [], following_relationships: []}
end
def relationships_opts(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
def render("index.json", opts) do def render("index.json", opts) do
# To do: check AdminAPIControllerTest on the reasons behind nil activities in the list # To do: check AdminAPIControllerTest on the reasons behind nil activities in the list
activities = Enum.filter(opts.activities, & &1) activities = Enum.filter(opts.activities, & &1)
@ -105,13 +86,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|> Activity.with_set_thread_muted_field(opts[:for]) |> Activity.with_set_thread_muted_field(opts[:for])
|> Repo.all() |> Repo.all()
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"])) relationships_opt =
if Map.has_key?(opts, :relationships) do
opts[:relationships]
else
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
UserRelationship.view_relationships_option(opts[:for], actors)
end
opts = opts =
opts opts
|> Map.put(:replied_to_activities, replied_to_activities) |> Map.put(:replied_to_activities, replied_to_activities)
|> Map.put(:parent_activities, parent_activities) |> Map.put(:parent_activities, parent_activities)
|> Map.put(:relationships, relationships_opts(opts[:for], actors)) |> Map.put(:relationships, relationships_opt)
safe_render_many(activities, StatusView, "show.json", opts) safe_render_many(activities, StatusView, "show.json", opts)
end end

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}
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(
following: true, @blank_response,
followed_by: true, %{
blocking: false, following: true,
blocked_by: false, followed_by: true,
muting: true, muting: true,
muting_notifications: true, muting_notifications: true,
subscribing: true, subscribing: true,
requested: false, showing_reblogs: false,
domain_blocking: false, id: to_string(other_user.id)
showing_reblogs: false, }
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 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