Merge remote-tracking branch 'upstream/develop' into admin-create-users
[akkoma] / lib / pleroma / user.ex
index 417d57d727c5b8025bd9afa12f6b9c87c56bfe00..6abcb7288f5f3237a80cb212409656430cc153ef 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.User do
 
   alias Comeonin.Pbkdf2
   alias Pleroma.Activity
+  alias Pleroma.Keys
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Registration
@@ -55,7 +56,7 @@ defmodule Pleroma.User do
     field(:last_refreshed_at, :naive_datetime_usec)
     has_many(:notifications, Notification)
     has_many(:registrations, Registration)
-    embeds_one(:info, Pleroma.User.Info)
+    embeds_one(:info, User.Info)
 
     timestamps()
   end
@@ -105,10 +106,8 @@ defmodule Pleroma.User do
   def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
 
   def user_info(%User{} = user) do
-    oneself = if user.local, do: 1, else: 0
-
     %{
-      following_count: length(user.following) - oneself,
+      following_count: following_count(user),
       note_count: user.info.note_count,
       follower_count: user.info.follower_count,
       locked: user.info.locked,
@@ -117,6 +116,20 @@ defmodule Pleroma.User do
     }
   end
 
+  def restrict_deactivated(query) do
+    from(u in query,
+      where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
+    )
+  end
+
+  def following_count(%User{following: []}), do: 0
+
+  def following_count(%User{} = user) do
+    user
+    |> get_friends_query()
+    |> Repo.aggregate(:count, :id)
+  end
+
   def remote_user_creation(params) do
     params =
       params
@@ -154,7 +167,7 @@ defmodule Pleroma.User do
 
   def update_changeset(struct, params \\ %{}) do
     struct
-    |> cast(params, [:bio, :name, :avatar])
+    |> cast(params, [:bio, :name, :avatar, :following])
     |> unique_constraint(:nickname)
     |> validate_format(:nickname, local_nickname_regex())
     |> validate_length(:bio, max: 5000)
@@ -221,7 +234,7 @@ defmodule Pleroma.User do
       |> validate_confirmation(:password)
       |> unique_constraint(:email)
       |> unique_constraint(:nickname)
-      |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
+      |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
       |> validate_format(:nickname, local_nickname_regex())
       |> validate_format(:email, @email_regex)
       |> validate_length(:bio, max: 1000)
@@ -255,7 +268,7 @@ defmodule Pleroma.User do
     candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames])
 
     autofollowed_users =
-      User.Query.build(%{nickname: candidates, local: true})
+      User.Query.build(%{nickname: candidates, local: true, deactivated: false})
       |> Repo.all()
 
     follow_all(user, autofollowed_users)
@@ -264,9 +277,15 @@ defmodule Pleroma.User do
   @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
   def register(%Ecto.Changeset{} = changeset) do
     with {:ok, user} <- Repo.insert(changeset),
-         {:ok, user} <- autofollow_users(user),
+         {:ok, user} <- post_register_action(user) do
+      {:ok, user}
+    end
+  end
+
+  def post_register_action(%User{} = user) do
+    with {:ok, user} <- autofollow_users(user),
          {:ok, user} <- set_cache(user),
-         {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
+         {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
          {:ok, _} <- try_send_confirmation_email(user) do
       {:ok, user}
     end
@@ -353,9 +372,7 @@ defmodule Pleroma.User do
   end
 
   def follow(%User{} = follower, %User{info: info} = followed) do
-    user_config = Application.get_env(:pleroma, :user)
-    deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
-
+    deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
     ap_followers = followed.follower_address
 
     cond do
@@ -550,7 +567,7 @@ defmodule Pleroma.User do
 
   @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
   def get_followers_query(%User{} = user, nil) do
-    User.Query.build(%{followers: user})
+    User.Query.build(%{followers: user, deactivated: false})
   end
 
   def get_followers_query(user, page) do
@@ -575,7 +592,7 @@ defmodule Pleroma.User do
 
   @spec get_friends_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
   def get_friends_query(%User{} = user, nil) do
-    User.Query.build(%{friends: user})
+    User.Query.build(%{friends: user, deactivated: false})
   end
 
   def get_friends_query(user, page) do
@@ -665,16 +682,16 @@ defmodule Pleroma.User do
 
     info_cng = User.Info.set_note_count(user.info, note_count)
 
-    cng =
-      change(user)
-      |> put_embed(:info, info_cng)
-
-    update_and_set_cache(cng)
+    user
+    |> change()
+    |> put_embed(:info, info_cng)
+    |> update_and_set_cache()
   end
 
   def update_follower_count(%User{} = user) do
     follower_count_query =
-      User.Query.build(%{followers: user}) |> select([u], %{count: count(u.id)})
+      User.Query.build(%{followers: user, deactivated: false})
+      |> select([u], %{count: count(u.id)})
 
     User
     |> where(id: ^user.id)
@@ -697,9 +714,21 @@ defmodule Pleroma.User do
     end
   end
 
+  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}
+    criteria = %{ap_id: ap_ids, deactivated: false}
     criteria = if local_only, do: Map.put(criteria, :local, true), else: criteria
 
     User.Query.build(criteria)
@@ -708,7 +737,7 @@ defmodule Pleroma.User do
 
   @spec get_recipients_from_activity(Activity.t()) :: [User.t()]
   def get_recipients_from_activity(%Activity{recipients: to}) do
-    User.Query.build(%{recipients_from_activity: to, local: true})
+    User.Query.build(%{recipients_from_activity: to, local: true, deactivated: false})
     |> Repo.all()
   end
 
@@ -735,7 +764,7 @@ defmodule Pleroma.User do
 
     from(s in subquery(boost_search_rank_query(distinct_query, for_user)),
       order_by: [desc: s.search_rank],
-      limit: 20
+      limit: 40
     )
   end
 
@@ -806,6 +835,7 @@ defmodule Pleroma.User do
           ^processed_query
         )
     )
+    |> restrict_deactivated()
   end
 
   defp trigram_search_subquery(term) do
@@ -824,6 +854,7 @@ defmodule Pleroma.User do
       },
       where: fragment("trim(? || ' ' || coalesce(?, '')) % ?", u.nickname, u.name, ^term)
     )
+    |> restrict_deactivated()
   end
 
   def mute(muter, %User{ap_id: ap_id}) do
@@ -956,19 +987,19 @@ defmodule Pleroma.User do
 
   @spec muted_users(User.t()) :: [User.t()]
   def muted_users(user) do
-    User.Query.build(%{ap_id: user.info.mutes})
+    User.Query.build(%{ap_id: user.info.mutes, deactivated: false})
     |> Repo.all()
   end
 
   @spec blocked_users(User.t()) :: [User.t()]
   def blocked_users(user) do
-    User.Query.build(%{ap_id: user.info.blocks})
+    User.Query.build(%{ap_id: user.info.blocks, deactivated: false})
     |> Repo.all()
   end
 
   @spec subscribers(User.t()) :: [User.t()]
   def subscribers(user) do
-    User.Query.build(%{ap_id: user.info.subscribers})
+    User.Query.build(%{ap_id: user.info.subscribers, deactivated: false})
     |> Repo.all()
   end
 
@@ -996,14 +1027,25 @@ defmodule Pleroma.User do
     update_and_set_cache(cng)
   end
 
+  def deactivate_async(user, status \\ true) do
+    PleromaJobQueue.enqueue(:background, __MODULE__, [:deactivate_async, user, status])
+  end
+
   def deactivate(%User{} = user, status \\ true) do
     info_cng = User.Info.set_activation_status(user.info, status)
 
-    cng =
-      change(user)
-      |> put_embed(:info, info_cng)
+    with {:ok, friends} <- User.get_friends(user),
+         {:ok, followers} <- User.get_followers(user),
+         {:ok, user} <-
+           user
+           |> change()
+           |> put_embed(:info, info_cng)
+           |> update_and_set_cache() do
+      Enum.each(followers, &invalidate_cache(&1))
+      Enum.each(friends, &update_follower_count(&1))
 
-    update_and_set_cache(cng)
+      {:ok, user}
+    end
   end
 
   def update_notification_settings(%User{} = user, settings \\ %{}) do
@@ -1047,6 +1089,8 @@ defmodule Pleroma.User do
     {:ok, user}
   end
 
+  def perform(:deactivate_async, user, status), do: deactivate(user, status)
+
   @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
   def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
       when is_list(blocked_identifiers) do
@@ -1105,7 +1149,6 @@ defmodule Pleroma.User do
     stream =
       ap_id
       |> Activity.query_by_actor()
-      |> Activity.with_preloaded_object()
       |> Repo.stream()
 
     Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
@@ -1344,11 +1387,64 @@ defmodule Pleroma.User do
 
   @spec all_superusers() :: [User.t()]
   def all_superusers do
-    User.Query.build(%{super_users: true, local: true})
+    User.Query.build(%{super_users: true, local: true, deactivated: false})
     |> Repo.all()
   end
 
   def showing_reblogs?(%User{} = user, %User{} = target) do
     target.ap_id not in user.info.muted_reblogs
   end
+
+  @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
+  def toggle_confirmation(%User{} = user) do
+    need_confirmation? = !user.info.confirmation_pending
+
+    info_changeset =
+      User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
+
+    user
+    |> change()
+    |> put_embed(:info, info_changeset)
+    |> update_and_set_cache()
+  end
+
+  def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
+    mascot
+  end
+
+  def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
+    # use instance-default
+    config = Pleroma.Config.get([:assets, :mascots])
+    default_mascot = Pleroma.Config.get([:assets, :default_mascot])
+    mascot = Keyword.get(config, default_mascot)
+
+    %{
+      "id" => "default-mascot",
+      "url" => mascot[:url],
+      "preview_url" => mascot[:url],
+      "pleroma" => %{
+        "mime_type" => mascot[:mime_type]
+      }
+    }
+  end
+
+  def ensure_keys_present(user) do
+    info = user.info
+
+    if info.keys do
+      {:ok, user}
+    else
+      {:ok, pem} = Keys.generate_rsa_pem()
+
+      info_cng =
+        info
+        |> User.Info.set_keys(pem)
+
+      cng =
+        Ecto.Changeset.change(user)
+        |> Ecto.Changeset.put_embed(:info, info_cng)
+
+      update_and_set_cache(cng)
+    end
+  end
 end