WIP: preloading of user relations for timeline/statuses rendering (performance improv...
authorIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 22 Mar 2020 18:51:44 +0000 (21:51 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Sun, 22 Mar 2020 18:51:44 +0000 (21:51 +0300)
lib/pleroma/user.ex
lib/pleroma/user_relationship.ex
lib/pleroma/web/mastodon_api/views/account_view.ex
lib/pleroma/web/mastodon_api/views/status_view.ex

index 12c2ad81502638f9afc8af5ff283922033a2abb0..daaa6d86ba015269d232567bbcbe25e9e5061142 100644 (file)
@@ -1642,8 +1642,12 @@ defmodule Pleroma.User do
     |> Repo.all()
   end
 
+  def muting_reblogs?(%User{} = user, %User{} = target) do
+    UserRelationship.reblog_mute_exists?(user, target)
+  end
+
   def showing_reblogs?(%User{} = user, %User{} = target) do
-    not UserRelationship.reblog_mute_exists?(user, target)
+    not muting_reblogs?(user, target)
   end
 
   @doc """
index 393947942304d0a4356e9599a9fc91bad1552b2a..167a3919c771a2101758808ffbf3b4fa6c7f018d 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do
   import Ecto.Changeset
   import Ecto.Query
 
+  alias FlakeId.Ecto.CompatType
   alias Pleroma.Repo
   alias Pleroma.User
   alias Pleroma.UserRelationship
@@ -34,6 +35,10 @@ defmodule Pleroma.UserRelationship do
       do: exists?(unquote(relationship_type), source, target)
   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
     user_relationship
     |> cast(params, [:relationship_type, :source_id, :target_id])
@@ -72,6 +77,45 @@ defmodule Pleroma.UserRelationship do
     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
+    get_bin_ids = fn user ->
+      with {:ok, bin_id} <- CompatType.dump(user.id), do: bin_id
+    end
+
+    source_user_ids = Enum.map(source_users, &get_bin_ids.(&1))
+    target_user_ids = Enum.map(target_users, &get_bin_ids.(&1))
+
+    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
+
   defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
     changeset
     |> validate_change(:target_id, fn _, target_id ->
index 4ebce73b45ee21047f3822e4bdea6b0cc8b1eac7..15a579278d32b0f617f3743a580556d03e299fa0 100644 (file)
@@ -10,6 +10,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
   alias Pleroma.Web.MastodonAPI.AccountView
   alias Pleroma.Web.MediaProxy
 
+  def test_rel(user_relationships, rel_type, source, target, func) do
+    cond do
+      is_nil(source) or is_nil(target) ->
+        false
+
+      user_relationships ->
+        [rel_type, source.id, target.id] in user_relationships
+
+      true ->
+        func.(source, target)
+    end
+  end
+
   def render("index.json", %{users: users} = opts) do
     users
     |> render_many(AccountView, "show.json", opts)
@@ -35,21 +48,50 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
     %{}
   end
 
-  def render("relationship.json", %{user: %User{} = user, target: %User{} = target}) do
-    follow_state = User.get_follow_state(user, target)
+  def render(
+        "relationship.json",
+        %{user: %User{} = reading_user, target: %User{} = target} = opts
+      ) do
+    user_relationships = Map.get(opts, :user_relationships)
+
+    follow_state = User.get_follow_state(reading_user, target)
 
+    # TODO: add a note on adjusting StatusView.user_relationships_opt/1 re: preloading of user relations
     %{
       id: to_string(target.id),
       following: follow_state == "accept",
-      followed_by: User.following?(target, user),
-      blocking: User.blocks_user?(user, target),
-      blocked_by: User.blocks_user?(target, user),
-      muting: User.mutes?(user, target),
-      muting_notifications: User.muted_notifications?(user, target),
-      subscribing: User.subscribed_to?(user, target),
+      followed_by: User.following?(target, reading_user),
+      blocking:
+        test_rel(user_relationships, :block, reading_user, target, &User.blocks_user?(&1, &2)),
+      blocked_by:
+        test_rel(user_relationships, :block, target, reading_user, &User.blocks_user?(&1, &2)),
+      muting: test_rel(user_relationships, :mute, reading_user, target, &User.mutes?(&1, &2)),
+      muting_notifications:
+        test_rel(
+          user_relationships,
+          :notification_mute,
+          reading_user,
+          target,
+          &User.muted_notifications?(&1, &2)
+        ),
+      subscribing:
+        test_rel(
+          user_relationships,
+          :inverse_subscription,
+          target,
+          reading_user,
+          &User.subscribed_to?(&2, &1)
+        ),
       requested: follow_state == "pending",
-      domain_blocking: User.blocks_domain?(user, target),
-      showing_reblogs: User.showing_reblogs?(user, target),
+      domain_blocking: User.blocks_domain?(reading_user, target),
+      showing_reblogs:
+        not test_rel(
+          user_relationships,
+          :reblog_mute,
+          reading_user,
+          target,
+          &User.muting_reblogs?(&1, &2)
+        ),
       endorsed: false
     }
   end
@@ -93,7 +135,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
         }
       end)
 
-    relationship = render("relationship.json", %{user: opts[:for], target: user})
+    relationship =
+      render("relationship.json", %{
+        user: opts[:for],
+        target: user,
+        user_relationships: opts[:user_relationships]
+      })
 
     %{
       id: to_string(user.id),
index f7469cdff69e6e7883cbf290395eebb52fe5bc3a..e0c368ec9219915c097b622c94916086b6b98df0 100644 (file)
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   alias Pleroma.Object
   alias Pleroma.Repo
   alias Pleroma.User
+  alias Pleroma.UserRelationship
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.CommonAPI.Utils
   alias Pleroma.Web.MastodonAPI.AccountView
@@ -70,11 +71,34 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     present?(user && user.ap_id in (object.data["announcements"] || []))
   end
 
+  defp user_relationships_opt(opts) do
+    reading_user = opts[:for]
+
+    if reading_user do
+      activities = opts[:activities]
+      actors = Enum.map(activities, fn a -> get_user(a.data["actor"]) end)
+
+      UserRelationship.dictionary(
+        [reading_user],
+        actors,
+        [:block, :mute, :notification_mute, :reblog_mute],
+        [:block, :inverse_subscription]
+      )
+    else
+      []
+    end
+  end
+
   def render("index.json", opts) do
-    replied_to_activities = get_replied_to_activities(opts.activities)
-    opts = Map.put(opts, :replied_to_activities, replied_to_activities)
+    activities = opts.activities
+    replied_to_activities = get_replied_to_activities(activities)
+
+    opts =
+      opts
+      |> Map.put(:replied_to_activities, replied_to_activities)
+      |> Map.put(:user_relationships, user_relationships_opt(opts))
 
-    safe_render_many(opts.activities, StatusView, "show.json", opts)
+    safe_render_many(activities, StatusView, "show.json", opts)
   end
 
   def render(
@@ -107,7 +131,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       id: to_string(activity.id),
       uri: 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],
+          user_relationships: opts[:user_relationships]
+        }),
       in_reply_to_id: nil,
       in_reply_to_account_id: nil,
       reblog: reblogged,
@@ -253,11 +282,28 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         _ -> []
       end
 
+    user_relationships_opt = opts[:user_relationships]
+
+    muted =
+      thread_muted? ||
+        Pleroma.Web.MastodonAPI.AccountView.test_rel(
+          user_relationships_opt,
+          :mute,
+          opts[:for],
+          user,
+          fn for_user, user -> User.mutes?(for_user, user) end
+        )
+
     %{
       id: to_string(activity.id),
       uri: object.data["id"],
       url: url,
-      account: AccountView.render("show.json", %{user: user, for: opts[:for]}),
+      account:
+        AccountView.render("show.json", %{
+          user: user,
+          for: opts[:for],
+          user_relationships: user_relationships_opt
+        }),
       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),
       reblog: nil,
@@ -270,7 +316,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       reblogged: reblogged?(activity, opts[:for]),
       favourited: present?(favorited),
       bookmarked: present?(bookmarked),
-      muted: thread_muted? || User.mutes?(opts[:for], user),
+      muted: muted,
       pinned: pinned?(activity, user),
       sensitive: sensitive,
       spoiler_text: summary,