Merge branch 'refactor/following-relationships' into 'develop'
[akkoma] / lib / pleroma / user.ex
index 5d3f5572192966b7fff3d0c99a678e25b3058c2d..40171620ed3901ab60857b17e9490772dc627ac5 100644 (file)
@@ -13,6 +13,7 @@ defmodule Pleroma.User do
   alias Pleroma.Activity
   alias Pleroma.Conversation.Participation
   alias Pleroma.Delivery
+  alias Pleroma.FollowingRelationship
   alias Pleroma.Keys
   alias Pleroma.Notification
   alias Pleroma.Object
@@ -50,7 +51,6 @@ defmodule Pleroma.User do
     field(:password, :string, virtual: true)
     field(:password_confirmation, :string, virtual: true)
     field(:keys, :string)
-    field(:following, {:array, :string}, default: [])
     field(:ap_id, :string)
     field(:avatar, :map)
     field(:local, :boolean, default: true)
@@ -216,13 +216,7 @@ defmodule Pleroma.User do
     from(u in query, where: u.deactivated != ^true)
   end
 
-  def following_count(%User{following: []}), do: 0
-
-  def following_count(%User{} = user) do
-    user
-    |> get_friends_query()
-    |> Repo.aggregate(:count, :id)
-  end
+  defdelegate following_count(user), to: FollowingRelationship
 
   defp truncate_fields_param(params) do
     if Map.has_key?(params, :fields) do
@@ -309,7 +303,6 @@ defmodule Pleroma.User do
         :bio,
         :name,
         :avatar,
-        :following,
         :locked,
         :no_rich_text,
         :default_scope,
@@ -454,7 +447,6 @@ defmodule Pleroma.User do
     followers = ap_followers(%User{nickname: get_field(changeset, :nickname)})
 
     changeset
-    |> put_change(:following, [followers])
     |> put_change(:follower_address, followers)
   end
 
@@ -508,8 +500,8 @@ defmodule Pleroma.User do
   def needs_update?(_), do: true
 
   @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
-  def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true}) do
-    {:ok, follower}
+  def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
+    follow(follower, followed, "pending")
   end
 
   def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
@@ -527,37 +519,22 @@ defmodule Pleroma.User do
   @doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
   @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()}
   def follow_all(follower, followeds) do
-    followed_addresses =
-      followeds
-      |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
-      |> Enum.map(fn %{follower_address: fa} -> fa end)
-
-    q =
-      from(u in User,
-        where: u.id == ^follower.id,
-        update: [
-          set: [
-            following:
-              fragment(
-                "array(select distinct unnest (array_cat(?, ?)))",
-                u.following,
-                ^followed_addresses
-              )
-          ]
-        ],
-        select: u
-      )
+    followeds =
+      Enum.reject(followeds, fn followed ->
+        blocks?(follower, followed) || blocks?(followed, follower)
+      end)
 
-    {1, [follower]} = Repo.update_all(q, [])
+    Enum.each(followeds, &follow(follower, &1, "accept"))
 
     Enum.each(followeds, &update_follower_count/1)
 
     set_cache(follower)
   end
 
-  def follow(%User{} = follower, %User{} = followed) do
+  defdelegate following(user), to: FollowingRelationship
+
+  def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
     deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
-    ap_followers = followed.follower_address
 
     cond do
       followed.deactivated ->
@@ -567,14 +544,7 @@ defmodule Pleroma.User do
         {:error, "Could not follow user: #{followed.nickname} blocked you."}
 
       true ->
-        q =
-          from(u in User,
-            where: u.id == ^follower.id,
-            update: [push: [following: ^ap_followers]],
-            select: u
-          )
-
-        {1, [follower]} = Repo.update_all(q, [])
+        FollowingRelationship.follow(follower, followed, state)
 
         follower = maybe_update_following_count(follower)
 
@@ -585,17 +555,8 @@ defmodule Pleroma.User do
   end
 
   def unfollow(%User{} = follower, %User{} = followed) do
-    ap_followers = followed.follower_address
-
     if following?(follower, followed) and follower.ap_id != followed.ap_id do
-      q =
-        from(u in User,
-          where: u.id == ^follower.id,
-          update: [pull: [following: ^ap_followers]],
-          select: u
-        )
-
-      {1, [follower]} = Repo.update_all(q, [])
+      FollowingRelationship.unfollow(follower, followed)
 
       follower = maybe_update_following_count(follower)
 
@@ -609,10 +570,7 @@ defmodule Pleroma.User do
     end
   end
 
-  @spec following?(User.t(), User.t()) :: boolean
-  def following?(%User{} = follower, %User{} = followed) do
-    Enum.member?(follower.following, followed.follower_address)
-  end
+  defdelegate following?(follower, followed), to: FollowingRelationship
 
   def locked?(%User{} = user) do
     user.locked || false
@@ -834,16 +792,7 @@ defmodule Pleroma.User do
     |> Repo.all()
   end
 
-  @spec get_follow_requests(User.t()) :: {:ok, [User.t()]}
-  def get_follow_requests(%User{} = user) do
-    user
-    |> Activity.follow_requests_for_actor()
-    |> join(:inner, [a], u in User, on: a.actor == u.ap_id)
-    |> where([a, u], not fragment("? @> ?", u.following, ^[user.follower_address]))
-    |> group_by([a, u], u.id)
-    |> select([a, u], u)
-    |> Repo.all()
-  end
+  defdelegate get_follow_requests(user), to: FollowingRelationship
 
   def increase_note_count(%User{} = user) do
     User
@@ -995,18 +944,6 @@ defmodule Pleroma.User do
 
   def increment_unread_conversation_count(_, user), do: {:ok, user}
 
-  def remove_duplicated_following(%User{following: following} = user) do
-    uniq_following = Enum.uniq(following)
-
-    if length(following) == length(uniq_following) do
-      {:ok, user}
-    else
-      user
-      |> update_changeset(%{following: uniq_following})
-      |> update_and_set_cache()
-    end
-  end
-
   @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
   def get_users_from_set(ap_ids, local_only \\ true) do
     criteria = %{ap_id: ap_ids, deactivated: false}