Preloading of follow relations for timeline/statuses rendering (performance improveme...
authorIvan Tashkinov <ivantashkinov@gmail.com>
Mon, 23 Mar 2020 09:01:11 +0000 (12:01 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Mon, 23 Mar 2020 09:01:11 +0000 (12:01 +0300)
lib/pleroma/following_relationship.ex
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 a6d28115137b32cb5ec30026f87461c20971930b..dd16961364ad20671b9b3477077eda5b35c990f7 100644 (file)
@@ -129,4 +129,30 @@ defmodule Pleroma.FollowingRelationship do
         move_following(origin, target)
     end
   end
+
+  def all_between_user_sets(
+        source_users,
+        target_users
+      )
+      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))
+
+    __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
 end
index daaa6d86ba015269d232567bbcbe25e9e5061142..eb72755a0b6f85a6dafeda8ad93370761a48e19b 100644 (file)
@@ -674,7 +674,14 @@ defmodule Pleroma.User do
 
   def get_follow_state(%User{} = follower, %User{} = following) do
     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
       {nil, false} ->
         case Utils.fetch_latest_follow(follower, following) do
index 167a3919c771a2101758808ffbf3b4fa6c7f018d..9423e3a42c71d7c8b3333216742a9c10c51a29b9 100644 (file)
@@ -116,6 +116,19 @@ defmodule Pleroma.UserRelationship do
     |> 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
+
   defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
     changeset
     |> validate_change(:target_id, fn _, target_id ->
index 15a579278d32b0f617f3743a580556d03e299fa0..2fe46158b305d1e8dce2922aca2134f61705fd04 100644 (file)
@@ -6,21 +6,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
   use Pleroma.Web, :view
 
   alias Pleroma.User
+  alias Pleroma.UserRelationship
   alias Pleroma.Web.CommonAPI.Utils
   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
+  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
@@ -53,21 +47,61 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
         %{user: %User{} = reading_user, target: %User{} = target} = opts
       ) do
     user_relationships = Map.get(opts, :user_relationships)
+    following_relationships = opts[:following_relationships]
+
+    follow_state =
+      if following_relationships do
+        user_to_target_following_relation =
+          find_following_rel(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
 
-    follow_state = User.get_follow_state(reading_user, target)
+    followed_by =
+      if following_relationships do
+        with %{state: "accept"} <-
+               find_following_rel(following_relationships, target, reading_user) do
+          true
+        else
+          _ -> false
+        end
+      else
+        User.following?(target, reading_user)
+      end
 
     # 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, reading_user),
+      followed_by: followed_by,
       blocking:
-        test_rel(user_relationships, :block, reading_user, target, &User.blocks_user?(&1, &2)),
+        UserRelationship.exists?(
+          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)),
+        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:
-        test_rel(
+        UserRelationship.exists?(
           user_relationships,
           :notification_mute,
           reading_user,
@@ -75,7 +109,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
           &User.muted_notifications?(&1, &2)
         ),
       subscribing:
-        test_rel(
+        UserRelationship.exists?(
           user_relationships,
           :inverse_subscription,
           target,
@@ -85,7 +119,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       requested: follow_state == "pending",
       domain_blocking: User.blocks_domain?(reading_user, target),
       showing_reblogs:
-        not test_rel(
+        not UserRelationship.exists?(
           user_relationships,
           :reblog_mute,
           reading_user,
@@ -139,7 +173,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
       render("relationship.json", %{
         user: opts[:for],
         target: user,
-        user_relationships: opts[:user_relationships]
+        user_relationships: opts[:user_relationships],
+        following_relationships: opts[:following_relationships]
       })
 
     %{
index e0c368ec9219915c097b622c94916086b6b98df0..55a5513f9c4259746ea89a1e66df98f5782ce7f2 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
   alias Pleroma.Activity
   alias Pleroma.ActivityExpiration
+  alias Pleroma.FollowingRelationship
   alias Pleroma.HTML
   alias Pleroma.Object
   alias Pleroma.Repo
@@ -71,22 +72,31 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     present?(user && user.ap_id in (object.data["announcements"] || []))
   end
 
-  defp user_relationships_opt(opts) do
+  defp relationships_opts(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)
+    {user_relationships, following_relationships} =
+      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
+        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, following_relationships}
+      else
+        {[], []}
+      end
+
+    %{user_relationships: user_relationships, following_relationships: following_relationships}
   end
 
   def render("index.json", opts) do
@@ -96,7 +106,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     opts =
       opts
       |> Map.put(:replied_to_activities, replied_to_activities)
-      |> Map.put(:user_relationships, user_relationships_opt(opts))
+      |> Map.merge(relationships_opts(opts))
 
     safe_render_many(activities, StatusView, "show.json", opts)
   end
@@ -135,7 +145,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         AccountView.render("show.json", %{
           user: user,
           for: opts[:for],
-          user_relationships: opts[:user_relationships]
+          user_relationships: opts[:user_relationships],
+          following_relationships: opts[:following_relationships]
         }),
       in_reply_to_id: nil,
       in_reply_to_account_id: nil,
@@ -286,7 +297,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
 
     muted =
       thread_muted? ||
-        Pleroma.Web.MastodonAPI.AccountView.test_rel(
+        UserRelationship.exists?(
           user_relationships_opt,
           :mute,
           opts[:for],
@@ -302,7 +313,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
         AccountView.render("show.json", %{
           user: user,
           for: opts[:for],
-          user_relationships: user_relationships_opt
+          user_relationships: user_relationships_opt,
+          following_relationships: opts[:following_relationships]
         }),
       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),