Merge branch 'fix/privacy-notification' into 'develop'
[akkoma] / lib / pleroma / notification.ex
index 3084bac3b47f2ac420b85bd8a0cd6bd89fbb6f04..98289af08016e218008e2b27eb5dbd2f3c2dca18 100644 (file)
@@ -5,9 +5,8 @@
 defmodule Pleroma.Notification do
   use Ecto.Schema
 
-  alias Ecto.Multi
   alias Pleroma.Activity
-  alias Pleroma.Marker
+  alias Pleroma.FollowingRelationship
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Pagination
@@ -40,17 +39,6 @@ defmodule Pleroma.Notification do
     |> cast(attrs, [:seen])
   end
 
-  @spec last_read_query(User.t()) :: Ecto.Queryable.t()
-  def last_read_query(user) do
-    from(q in Pleroma.Notification,
-      where: q.user_id == ^user.id,
-      where: q.seen == true,
-      select: type(q.id, :string),
-      limit: 1,
-      order_by: [desc: :id]
-    )
-  end
-
   defp for_user_query_ap_id_opts(user, opts) do
     ap_id_relationships =
       [:block] ++
@@ -94,15 +82,13 @@ defmodule Pleroma.Notification do
     |> exclude_visibility(opts)
   end
 
+  # Excludes blocked users and non-followed domain-blocked users
   defp exclude_blocked(query, user, opts) do
     blocked_ap_ids = opts[:blocked_users_ap_ids] || User.blocked_users_ap_ids(user)
 
     query
     |> where([n, a], a.actor not in ^blocked_ap_ids)
-    |> where(
-      [n, a],
-      fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
-    )
+    |> FollowingRelationship.keep_following_or_not_domain_blocked(user)
   end
 
   defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
@@ -199,23 +185,25 @@ defmodule Pleroma.Notification do
     |> Repo.all()
   end
 
-  def set_read_up_to(%{id: user_id} = user, id) do
+  def set_read_up_to(%{id: user_id} = _user, id) do
     query =
       from(
         n in Notification,
         where: n.user_id == ^user_id,
         where: n.id <= ^id,
         where: n.seen == false,
+        update: [
+          set: [
+            seen: true,
+            updated_at: ^NaiveDateTime.utc_now()
+          ]
+        ],
         # Ideally we would preload object and activities here
         # but Ecto does not support preloads in update_all
         select: n.id
       )
 
-    {:ok, %{ids: {_, notification_ids}}} =
-      Multi.new()
-      |> Multi.update_all(:ids, query, set: [seen: true, updated_at: NaiveDateTime.utc_now()])
-      |> Marker.multi_set_last_read_id(user, "notifications")
-      |> Repo.transaction()
+    {_, notification_ids} = Repo.update_all(query, [])
 
     Notification
     |> where([n], n.id in ^notification_ids)
@@ -232,18 +220,11 @@ defmodule Pleroma.Notification do
     |> Repo.all()
   end
 
-  @spec read_one(User.t(), String.t()) ::
-          {:ok, Notification.t()} | {:error, Ecto.Changeset.t()} | nil
   def read_one(%User{} = user, notification_id) do
     with {:ok, %Notification{} = notification} <- get(user, notification_id) do
-      Multi.new()
-      |> Multi.update(:update, changeset(notification, %{seen: true}))
-      |> Marker.multi_set_last_read_id(user, "notifications")
-      |> Repo.transaction()
-      |> case do
-        {:ok, %{update: notification}} -> {:ok, notification}
-        {:error, :update, changeset, _} -> {:error, changeset}
-      end
+      notification
+      |> changeset(%{seen: true})
+      |> Repo.update()
     end
   end
 
@@ -280,6 +261,16 @@ defmodule Pleroma.Notification do
     |> Repo.delete_all()
   end
 
+  def dismiss(%Pleroma.Activity{} = activity) do
+    Notification
+    |> where([n], n.activity_id == ^activity.id)
+    |> Repo.delete_all()
+    |> case do
+      {_, notifications} -> {:ok, notifications}
+      _ -> {:error, "Cannot dismiss notification"}
+    end
+  end
+
   def dismiss(%{id: user_id} = _user, id) do
     notification = Repo.get(Notification, id)
 
@@ -303,7 +294,7 @@ defmodule Pleroma.Notification do
   end
 
   def create_notifications(%Activity{data: %{"type" => type}} = activity)
-      when type in ["Like", "Announce", "Follow", "Move", "EmojiReact"] do
+      when type in ["Follow", "Like", "Announce", "Move", "EmojiReact"] do
     do_create_notifications(activity)
   end
 
@@ -325,11 +316,8 @@ defmodule Pleroma.Notification do
   # TODO move to sql, too.
   def create_notification(%Activity{} = activity, %User{} = user, do_send \\ true) do
     unless skip?(activity, user) do
-      {:ok, %{notification: notification}} =
-        Multi.new()
-        |> Multi.insert(:notification, %Notification{user_id: user.id, activity: activity})
-        |> Marker.multi_set_last_read_id(user, "notifications")
-        |> Repo.transaction()
+      notification = %Notification{user_id: user.id, activity: activity}
+      {:ok, notification} = Repo.insert(notification)
 
       if do_send do
         Streamer.stream(["user", "user:notification"], notification)
@@ -342,10 +330,11 @@ defmodule Pleroma.Notification do
 
   @doc """
   Returns a tuple with 2 elements:
-    {enabled notification receivers, currently disabled receivers (blocking / [thread] muting)}
+    {notification-enabled receivers, currently disabled receivers (blocking / [thread] muting)}
 
   NOTE: might be called for FAKE Activities, see ActivityPub.Utils.get_notified_from_object/1
   """
+  @spec get_notified_from_activity(Activity.t(), boolean()) :: {list(User.t()), list(User.t())}
   def get_notified_from_activity(activity, local_only \\ true)
 
   def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
@@ -358,17 +347,14 @@ defmodule Pleroma.Notification do
       |> Utils.maybe_notify_followers(activity)
       |> Enum.uniq()
 
-    # Since even subscribers and followers can mute / thread-mute, filtering all above AP IDs
+    potential_receivers = User.get_users_from_set(potential_receiver_ap_ids, local_only)
+
     notification_enabled_ap_ids =
       potential_receiver_ap_ids
+      |> exclude_domain_blocker_ap_ids(activity, potential_receivers)
       |> exclude_relationship_restricted_ap_ids(activity)
       |> exclude_thread_muter_ap_ids(activity)
 
-    potential_receivers =
-      potential_receiver_ap_ids
-      |> Enum.uniq()
-      |> User.get_users_from_set(local_only)
-
     notification_enabled_users =
       Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end)
 
@@ -377,6 +363,38 @@ defmodule Pleroma.Notification do
 
   def get_notified_from_activity(_, _local_only), do: {[], []}
 
+  @doc "Filters out AP IDs domain-blocking and not following the activity's actor"
+  def exclude_domain_blocker_ap_ids(ap_ids, activity, preloaded_users \\ [])
+
+  def exclude_domain_blocker_ap_ids([], _activity, _preloaded_users), do: []
+
+  def exclude_domain_blocker_ap_ids(ap_ids, %Activity{} = activity, preloaded_users) do
+    activity_actor_domain = activity.actor && URI.parse(activity.actor).host
+
+    users =
+      ap_ids
+      |> Enum.map(fn ap_id ->
+        Enum.find(preloaded_users, &(&1.ap_id == ap_id)) ||
+          User.get_cached_by_ap_id(ap_id)
+      end)
+      |> Enum.filter(& &1)
+
+    domain_blocker_ap_ids = for u <- users, activity_actor_domain in u.domain_blocks, do: u.ap_id
+
+    domain_blocker_follower_ap_ids =
+      if Enum.any?(domain_blocker_ap_ids) do
+        activity
+        |> Activity.user_actor()
+        |> FollowingRelationship.followers_ap_ids(domain_blocker_ap_ids)
+      else
+        []
+      end
+
+    ap_ids
+    |> Kernel.--(domain_blocker_ap_ids)
+    |> Kernel.++(domain_blocker_follower_ap_ids)
+  end
+
   @doc "Filters out AP IDs of users basing on their relationships with activity actor user"
   def exclude_relationship_restricted_ap_ids([], _activity), do: []