Merge branch 'refactor/following-relationships' into 'develop'
[akkoma] / lib / pleroma / user.ex
index 00a1df133d57f7b6216d3c9bf384dbde16dda24b..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)
@@ -100,9 +100,10 @@ defmodule Pleroma.User do
     field(:mascot, :map, default: nil)
     field(:emoji, {:array, :map}, default: [])
     field(:pleroma_settings_store, :map, default: %{})
-    field(:fields, {:array, :map}, default: nil)
+    field(:fields, {:array, :map}, default: [])
     field(:raw_fields, {:array, :map}, default: [])
     field(:discoverable, :boolean, default: false)
+    field(:invisible, :boolean, default: false)
     field(:skip_thread_containment, :boolean, default: false)
 
     field(:notification_settings, :map,
@@ -142,7 +143,7 @@ defmodule Pleroma.User do
   def superuser?(%User{local: true, is_moderator: true}), do: true
   def superuser?(_), do: false
 
-  def invisible?(%User{info: %User.Info{invisible: true}}), do: true
+  def invisible?(%User{invisible: true}), do: true
   def invisible?(_), do: false
 
   def avatar_url(user, options \\ []) do
@@ -215,60 +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
-
-  @info_fields [
-    :banner,
-    :background,
-    :source_data,
-    :note_count,
-    :follower_count,
-    :following_count,
-    :locked,
-    :confirmation_pending,
-    :password_reset_pending,
-    :confirmation_token,
-    :default_scope,
-    :blocks,
-    :domain_blocks,
-    :mutes,
-    :muted_reblogs,
-    :muted_notifications,
-    :subscribers,
-    :deactivated,
-    :no_rich_text,
-    :ap_enabled,
-    :is_moderator,
-    :is_admin,
-    :show_role,
-    :settings,
-    :magic_key,
-    :uri,
-    :hide_followers_count,
-    :hide_follows_count,
-    :hide_followers,
-    :hide_follows,
-    :hide_favorites,
-    :unread_conversation_count,
-    :pinned_activities,
-    :email_notifications,
-    :mascot,
-    :emoji,
-    :pleroma_settings_store,
-    :fields,
-    :raw_fields,
-    :discoverable,
-    :skip_thread_containment,
-    :notification_settings
-  ]
-
-  def info_fields, do: @info_fields
+  defdelegate following_count(user), to: FollowingRelationship
 
   defp truncate_fields_param(params) do
     if Map.has_key?(params, :fields) do
@@ -321,7 +269,8 @@ defmodule Pleroma.User do
           :follower_count,
           :fields,
           :following_count,
-          :discoverable
+          :discoverable,
+          :invisible
         ]
       )
       |> validate_required([:name, :ap_id])
@@ -354,7 +303,6 @@ defmodule Pleroma.User do
         :bio,
         :name,
         :avatar,
-        :following,
         :locked,
         :no_rich_text,
         :default_scope,
@@ -499,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
 
@@ -553,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
@@ -572,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 ->
@@ -612,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)
 
@@ -630,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)
 
@@ -654,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
@@ -879,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
@@ -1016,7 +920,7 @@ defmodule Pleroma.User do
     end
   end
 
-  def set_unread_conversation_count(_), do: :noop
+  def set_unread_conversation_count(user), do: {:ok, user}
 
   def increment_unread_conversation_count(conversation, %User{local: true} = user) do
     unread_query =
@@ -1038,19 +942,7 @@ defmodule Pleroma.User do
     end
   end
 
-  def increment_unread_conversation_count(_, _), do: :noop
-
-  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
+  def increment_unread_conversation_count(_, user), do: {:ok, user}
 
   @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
   def get_users_from_set(ap_ids, local_only \\ true) do
@@ -1122,7 +1014,7 @@ defmodule Pleroma.User do
     if following?(blocked, blocker), do: unfollow(blocked, blocker)
 
     {:ok, blocker} = update_follower_count(blocker)
-
+    {:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
     add_to_block(blocker, ap_id)
   end
 
@@ -2021,4 +1913,13 @@ defmodule Pleroma.User do
     |> cast(params, [:muted_reblogs])
     |> update_and_set_cache()
   end
+
+  def set_invisible(user, invisible) do
+    params = %{invisible: invisible}
+
+    user
+    |> cast(params, [:invisible])
+    |> validate_required([:invisible])
+    |> update_and_set_cache()
+  end
 end