Merge branch 'develop' into issue/1276
[akkoma] / lib / pleroma / notification.ex
index d339fdf64d02ad7cccf736bd7b4f31d352898eee..e2b75054e767b6ac302a852817eecf0c6a61ed71 100644 (file)
@@ -23,6 +23,8 @@ defmodule Pleroma.Notification do
 
   @type t :: %__MODULE__{}
 
+  @include_muted_option :with_muted
+
   schema "notifications" do
     field(:seen, :boolean, default: false)
     belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
@@ -36,13 +38,50 @@ defmodule Pleroma.Notification do
     |> cast(attrs, [:seen])
   end
 
-  def for_user_query(user, opts \\ []) do
+  @spec unread_count_query(User.t()) :: Ecto.Queryable.t()
+  def unread_count_query(user) do
+    from(q in Pleroma.Notification,
+      where: q.user_id == ^user.id,
+      where: q.seen == false
+    )
+  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_relations =
+      [:block] ++
+        if opts[@include_muted_option], do: [], else: [:notification_mute]
+
+    preloaded_ap_ids = User.outgoing_relations_ap_ids(user, ap_id_relations)
+
+    exclude_blocked_opts = Map.merge(%{blocked_users_ap_ids: preloaded_ap_ids[:block]}, opts)
+
+    exclude_notification_muted_opts =
+      Map.merge(%{notification_muted_users_ap_ids: preloaded_ap_ids[:notification_mute]}, opts)
+
+    {exclude_blocked_opts, exclude_notification_muted_opts}
+  end
+
+  def for_user_query(user, opts \\ %{}) do
+    {exclude_blocked_opts, exclude_notification_muted_opts} =
+      for_user_query_ap_id_opts(user, opts)
+
     Notification
     |> where(user_id: ^user.id)
     |> where(
       [n, a],
       fragment(
-        "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
+        "? not in (SELECT ap_id FROM users WHERE deactivated = 'true')",
         a.actor
       )
     )
@@ -56,43 +95,75 @@ defmodule Pleroma.Notification do
         )
     )
     |> preload([n, a, o], activity: {a, object: o})
-    |> exclude_muted(user, opts)
-    |> exclude_blocked(user)
+    |> exclude_notification_muted(user, exclude_notification_muted_opts)
+    |> exclude_blocked(user, exclude_blocked_opts)
     |> exclude_visibility(opts)
+    |> exclude_move(opts)
   end
 
-  defp exclude_blocked(query, user) do
+  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 ^user.info.blocks)
+    |> where([n, a], a.actor not in ^blocked_ap_ids)
     |> where(
       [n, a],
-      fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
+      fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.domain_blocks
     )
   end
 
-  defp exclude_muted(query, _, %{with_muted: true}) do
+  defp exclude_notification_muted(query, _, %{@include_muted_option => true}) do
     query
   end
 
-  defp exclude_muted(query, user, _opts) do
+  defp exclude_notification_muted(query, user, opts) do
+    notification_muted_ap_ids =
+      opts[:notification_muted_users_ap_ids] || User.notification_muted_users_ap_ids(user)
+
     query
-    |> where([n, a], a.actor not in ^user.info.muted_notifications)
+    |> where([n, a], a.actor not in ^notification_muted_ap_ids)
     |> join(:left, [n, a], tm in Pleroma.ThreadMute,
       on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
     )
     |> where([n, a, o, tm], is_nil(tm.user_id))
   end
 
+  defp exclude_move(query, %{with_move: true}) do
+    query
+  end
+
+  defp exclude_move(query, _opts) do
+    where(query, [n, a], fragment("?->>'type' != 'Move'", a.data))
+  end
+
   @valid_visibilities ~w[direct unlisted public private]
 
   defp exclude_visibility(query, %{exclude_visibilities: visibility})
        when is_list(visibility) do
     if Enum.all?(visibility, &(&1 in @valid_visibilities)) do
       query
+      |> join(:left, [n, a], mutated_activity in Pleroma.Activity,
+        on:
+          fragment("?->>'context'", a.data) ==
+            fragment("?->>'context'", mutated_activity.data) and
+            fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and
+            fragment("?->>'type'", mutated_activity.data) == "Create",
+        as: :mutated_activity
+      )
       |> where(
-        [n, a],
+        [n, a, mutated_activity: mutated_activity],
         not fragment(
-          "activity_visibility(?, ?, ?) = ANY (?)",
+          """
+          CASE WHEN (?->>'type') = 'Like' or (?->>'type') = 'Announce'
+            THEN (activity_visibility(?, ?, ?) = ANY (?))
+            ELSE (activity_visibility(?, ?, ?) = ANY (?)) END
+          """,
+          a.data,
+          a.data,
+          mutated_activity.actor,
+          mutated_activity.recipients,
+          mutated_activity.data,
+          ^visibility,
           a.actor,
           a.recipients,
           a.data,
@@ -107,17 +178,7 @@ defmodule Pleroma.Notification do
 
   defp exclude_visibility(query, %{exclude_visibilities: visibility})
        when visibility in @valid_visibilities do
-    query
-    |> where(
-      [n, a],
-      not fragment(
-        "activity_visibility(?, ?, ?) = (?)",
-        a.actor,
-        a.recipients,
-        a.data,
-        ^visibility
-      )
-    )
+    exclude_visibility(query, [visibility])
   end
 
   defp exclude_visibility(query, %{exclude_visibilities: visibility})
@@ -261,11 +322,11 @@ defmodule Pleroma.Notification do
     end
   end
 
-  def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
-      when type in ["Like", "Announce", "Follow"] do
+  def create_notifications(%Activity{data: %{"type" => type}} = activity)
+      when type in ["Like", "Announce", "Follow", "Move", "EmojiReaction"] do
     notifications =
       activity
-      |> get_notified_from_activity
+      |> get_notified_from_activity()
       |> Enum.map(&create_notification(activity, &1))
 
     {:ok, notifications}
@@ -292,19 +353,15 @@ defmodule Pleroma.Notification do
 
   def get_notified_from_activity(activity, local_only \\ true)
 
-  def get_notified_from_activity(
-        %Activity{data: %{"to" => _, "type" => type} = _data} = activity,
-        local_only
-      )
-      when type in ["Create", "Like", "Announce", "Follow"] do
-    recipients =
-      []
-      |> Utils.maybe_notify_to_recipients(activity)
-      |> Utils.maybe_notify_mentioned_recipients(activity)
-      |> Utils.maybe_notify_subscribers(activity)
-      |> Enum.uniq()
-
-    User.get_users_from_set(recipients, local_only)
+  def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only)
+      when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReaction"] do
+    []
+    |> Utils.maybe_notify_to_recipients(activity)
+    |> Utils.maybe_notify_mentioned_recipients(activity)
+    |> Utils.maybe_notify_subscribers(activity)
+    |> Utils.maybe_notify_followers(activity)
+    |> Enum.uniq()
+    |> User.get_users_from_set(local_only)
   end
 
   def get_notified_from_activity(_, _local_only), do: []
@@ -330,7 +387,7 @@ defmodule Pleroma.Notification do
   def skip?(
         :followers,
         activity,
-        %{info: %{notification_settings: %{"followers" => false}}} = user
+        %{notification_settings: %{followers: false}} = user
       ) do
     actor = activity.data["actor"]
     follower = User.get_cached_by_ap_id(actor)
@@ -340,14 +397,14 @@ defmodule Pleroma.Notification do
   def skip?(
         :non_followers,
         activity,
-        %{info: %{notification_settings: %{"non_followers" => false}}} = user
+        %{notification_settings: %{non_followers: false}} = user
       ) do
     actor = activity.data["actor"]
     follower = User.get_cached_by_ap_id(actor)
     !User.following?(follower, user)
   end
 
-  def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
+  def skip?(:follows, activity, %{notification_settings: %{follows: false}} = user) do
     actor = activity.data["actor"]
     followed = User.get_cached_by_ap_id(actor)
     User.following?(user, followed)
@@ -356,7 +413,7 @@ defmodule Pleroma.Notification do
   def skip?(
         :non_follows,
         activity,
-        %{info: %{notification_settings: %{"non_follows" => false}}} = user
+        %{notification_settings: %{non_follows: false}} = user
       ) do
     actor = activity.data["actor"]
     followed = User.get_cached_by_ap_id(actor)