Merge branch 'fix/subscriber-notifications' into 'develop'
[akkoma] / lib / pleroma / user.ex
index d03810d1ad551e07ccbba54760dd53a2893c3ed9..a3f6add28cb13f033ecba47fc604733a75155771 100644 (file)
@@ -52,6 +52,7 @@ defmodule Pleroma.User do
     field(:avatar, :map)
     field(:local, :boolean, default: true)
     field(:follower_address, :string)
+    field(:following_address, :string)
     field(:search_rank, :float, virtual: true)
     field(:search_type, :integer, virtual: true)
     field(:tags, {:array, :string}, default: [])
@@ -107,6 +108,10 @@ defmodule Pleroma.User do
   def ap_followers(%User{follower_address: fa}) when is_binary(fa), do: fa
   def ap_followers(%User{} = user), do: "#{ap_id(user)}/followers"
 
+  @spec ap_following(User.t()) :: Sring.t()
+  def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
+  def ap_following(%User{} = user), do: "#{ap_id(user)}/following"
+
   def user_info(%User{} = user, args \\ %{}) do
     following_count =
       if args[:following_count], do: args[:following_count], else: following_count(user)
@@ -128,6 +133,7 @@ defmodule Pleroma.User do
     Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
   end
 
+  @spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
   def restrict_deactivated(query) do
     from(u in query,
       where: not fragment("? \\? 'deactivated' AND ?->'deactivated' @> 'true'", u.info, u.info)
@@ -162,9 +168,10 @@ defmodule Pleroma.User do
 
     if changes.valid? do
       case info_cng.changes[:source_data] do
-        %{"followers" => followers} ->
+        %{"followers" => followers, "following" => following} ->
           changes
           |> put_change(:follower_address, followers)
+          |> put_change(:following_address, following)
 
         _ ->
           followers = User.ap_followers(%User{nickname: changes.changes[:nickname]})
@@ -196,7 +203,14 @@ defmodule Pleroma.User do
       |> User.Info.user_upgrade(params[:info])
 
     struct
-    |> cast(params, [:bio, :name, :follower_address, :avatar, :last_refreshed_at])
+    |> cast(params, [
+      :bio,
+      :name,
+      :follower_address,
+      :following_address,
+      :avatar,
+      :last_refreshed_at
+    ])
     |> unique_constraint(:nickname)
     |> validate_format(:nickname, local_nickname_regex())
     |> validate_length(:bio, max: 5000)
@@ -735,10 +749,13 @@ defmodule Pleroma.User do
     |> Repo.all()
   end
 
-  def mute(muter, %User{ap_id: ap_id}) do
+  @spec mute(User.t(), User.t(), boolean()) :: {:ok, User.t()} | {:error, String.t()}
+  def mute(muter, %User{ap_id: ap_id}, notifications? \\ true) do
+    info = muter.info
+
     info_cng =
-      muter.info
-      |> User.Info.add_to_mutes(ap_id)
+      User.Info.add_to_mutes(info, ap_id)
+      |> User.Info.add_to_muted_notifications(info, ap_id, notifications?)
 
     cng =
       change(muter)
@@ -748,9 +765,11 @@ defmodule Pleroma.User do
   end
 
   def unmute(muter, %{ap_id: ap_id}) do
+    info = muter.info
+
     info_cng =
-      muter.info
-      |> User.Info.remove_from_mutes(ap_id)
+      User.Info.remove_from_mutes(info, ap_id)
+      |> User.Info.remove_from_muted_notifications(info, ap_id)
 
     cng =
       change(muter)
@@ -846,12 +865,21 @@ defmodule Pleroma.User do
   def mutes?(nil, _), do: false
   def mutes?(user, %{ap_id: ap_id}), do: Enum.member?(user.info.mutes, ap_id)
 
+  @spec muted_notifications?(User.t() | nil, User.t() | map()) :: boolean()
+  def muted_notifications?(nil, _), do: false
+
+  def muted_notifications?(user, %{ap_id: ap_id}),
+    do: Enum.member?(user.info.muted_notifications, ap_id)
+
   def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
     blocks = info.blocks
-    domain_blocks = info.domain_blocks
+
+    domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(info.domain_blocks)
+
     %{host: host} = URI.parse(ap_id)
 
-    Enum.member?(blocks, ap_id) || Enum.any?(domain_blocks, &(&1 == host))
+    Enum.member?(blocks, ap_id) ||
+      Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
   end
 
   def subscribed_to?(user, %{ap_id: ap_id}) do
@@ -937,6 +965,8 @@ defmodule Pleroma.User do
 
   @spec perform(atom(), User.t()) :: {:ok, User.t()}
   def perform(:delete, %User{} = user) do
+    {:ok, _user} = ActivityPub.delete(user)
+
     # Remove all relationships
     {:ok, followers} = User.get_followers(user)
 
@@ -953,8 +983,8 @@ defmodule Pleroma.User do
     end)
 
     delete_user_activities(user)
-
-    {:ok, _user} = Repo.delete(user)
+    invalidate_cache(user)
+    Repo.delete(user)
   end
 
   @spec perform(atom(), User.t()) :: {:ok, User.t()}
@@ -1010,42 +1040,20 @@ defmodule Pleroma.User do
     )
   end
 
-  @spec sync_follow_counter() :: :ok
-  def sync_follow_counter,
-    do: PleromaJobQueue.enqueue(:background, __MODULE__, [:sync_follow_counters])
-
-  @spec perform(:sync_follow_counters) :: :ok
-  def perform(:sync_follow_counters) do
-    {:ok, _pid} = Agent.start_link(fn -> %{} end, name: :domain_errors)
-    config = Pleroma.Config.get([:instance, :external_user_synchronization])
-
-    :ok = sync_follow_counters(config)
-    Agent.stop(:domain_errors)
-  end
-
-  @spec sync_follow_counters(keyword()) :: :ok
-  def sync_follow_counters(opts \\ []) do
-    users = external_users(opts)
-
-    if length(users) > 0 do
-      errors = Agent.get(:domain_errors, fn state -> state end)
-      {last, updated_errors} = User.Synchronization.call(users, errors, opts)
-      Agent.update(:domain_errors, fn _state -> updated_errors end)
-      sync_follow_counters(max_id: last.id, limit: opts[:limit])
-    else
-      :ok
-    end
+  @spec external_users_query() :: Ecto.Query.t()
+  def external_users_query do
+    User.Query.build(%{
+      external: true,
+      active: true,
+      order_by: :id
+    })
   end
 
   @spec external_users(keyword()) :: [User.t()]
   def external_users(opts \\ []) do
     query =
-      User.Query.build(%{
-        external: true,
-        active: true,
-        order_by: :id,
-        select: [:id, :ap_id, :info]
-      })
+      external_users_query()
+      |> select([u], struct(u, [:id, :ap_id, :info]))
 
     query =
       if opts[:max_id],
@@ -1152,19 +1160,18 @@ defmodule Pleroma.User do
     end
   end
 
-  def get_or_create_instance_user do
-    relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay"
-
-    if user = get_cached_by_ap_id(relay_uri) do
+  @doc "Creates an internal service actor by URI if missing.  Optionally takes nickname for addressing."
+  def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
+    if user = get_cached_by_ap_id(uri) do
       user
     else
       changes =
         %User{info: %User.Info{}}
         |> cast(%{}, [:ap_id, :nickname, :local])
-        |> put_change(:ap_id, relay_uri)
-        |> put_change(:nickname, nil)
+        |> put_change(:ap_id, uri)
+        |> put_change(:nickname, nickname)
         |> put_change(:local, true)
-        |> put_change(:follower_address, relay_uri <> "/followers")
+        |> put_change(:follower_address, uri <> "/followers")
 
       {:ok, user} = Repo.insert(changes)
       user
@@ -1185,10 +1192,12 @@ defmodule Pleroma.User do
   end
 
   # OStatus Magic Key
-  def public_key_from_info(%{magic_key: magic_key}) do
+  def public_key_from_info(%{magic_key: magic_key}) when not is_nil(magic_key) do
     {:ok, Pleroma.Web.Salmon.decode_key(magic_key)}
   end
 
+  def public_key_from_info(_), do: {:error, "not found key"}
+
   def get_public_key_for_ap_id(ap_id) do
     with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
          {:ok, public_key} <- public_key_from_info(user.info) do
@@ -1205,7 +1214,7 @@ defmodule Pleroma.User do
     data
     |> Map.put(:name, blank?(data[:name]) || data[:nickname])
     |> remote_user_creation()
-    |> Repo.insert(on_conflict: :replace_all, conflict_target: :nickname)
+    |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname)
     |> set_cache()
   end
 
@@ -1374,23 +1383,16 @@ defmodule Pleroma.User do
     }
   end
 
-  def ensure_keys_present(user) do
-    info = user.info
-
+  def ensure_keys_present(%User{info: info} = user) do
     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)
+      user
+      |> Ecto.Changeset.change()
+      |> Ecto.Changeset.put_embed(:info, User.Info.set_keys(info, pem))
+      |> update_and_set_cache()
     end
   end
 
@@ -1411,4 +1413,8 @@ defmodule Pleroma.User do
   end
 
   defp put_password_hash(changeset), do: changeset
+
+  def is_internal_user?(%User{nickname: nil}), do: true
+  def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
+  def is_internal_user?(_), do: false
 end