Add a TODO note
[akkoma] / lib / pleroma / notification.ex
index bc691dce37d01db11533eb010513b6857a356768..3084bac3b47f2ac420b85bd8a0cd6bd89fbb6f04 100644 (file)
@@ -5,7 +5,9 @@
 defmodule Pleroma.Notification do
   use Ecto.Schema
 
+  alias Ecto.Multi
   alias Pleroma.Activity
+  alias Pleroma.Marker
   alias Pleroma.Notification
   alias Pleroma.Object
   alias Pleroma.Pagination
@@ -38,6 +40,17 @@ 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] ++
@@ -79,7 +92,6 @@ defmodule Pleroma.Notification do
     |> 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, opts) do
@@ -109,14 +121,6 @@ defmodule Pleroma.Notification do
     |> 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})
@@ -195,25 +199,23 @@ 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
       )
 
-    {_, notification_ids} = Repo.update_all(query, [])
+    {: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
     |> where([n], n.id in ^notification_ids)
@@ -230,11 +232,18 @@ 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
-      notification
-      |> changeset(%{seen: true})
-      |> Repo.update()
+      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
     end
   end
 
@@ -316,8 +325,11 @@ 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
-      notification = %Notification{user_id: user.id, activity: activity}
-      {:ok, notification} = Repo.insert(notification)
+      {: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()
 
       if do_send do
         Streamer.stream(["user", "user:notification"], notification)
@@ -331,6 +343,8 @@ defmodule Pleroma.Notification do
   @doc """
   Returns a tuple with 2 elements:
     {enabled notification receivers, currently disabled receivers (blocking / [thread] muting)}
+
+  NOTE: might be called for FAKE Activities, see ActivityPub.Utils.get_notified_from_object/1
   """
   def get_notified_from_activity(activity, local_only \\ true)
 
@@ -347,7 +361,7 @@ defmodule Pleroma.Notification do
     # Since even subscribers and followers can mute / thread-mute, filtering all above AP IDs
     notification_enabled_ap_ids =
       potential_receiver_ap_ids
-      |> exclude_relation_restricting_ap_ids(activity)
+      |> exclude_relationship_restricted_ap_ids(activity)
       |> exclude_thread_muter_ap_ids(activity)
 
     potential_receivers =
@@ -364,10 +378,10 @@ defmodule Pleroma.Notification do
   def get_notified_from_activity(_, _local_only), do: {[], []}
 
   @doc "Filters out AP IDs of users basing on their relationships with activity actor user"
-  def exclude_relation_restricting_ap_ids([], _activity), do: []
+  def exclude_relationship_restricted_ap_ids([], _activity), do: []
 
-  def exclude_relation_restricting_ap_ids(ap_ids, %Activity{} = activity) do
-    relation_restricted_ap_ids =
+  def exclude_relationship_restricted_ap_ids(ap_ids, %Activity{} = activity) do
+    relationship_restricted_ap_ids =
       activity
       |> Activity.user_actor()
       |> User.incoming_relationships_ungrouped_ap_ids([
@@ -375,7 +389,7 @@ defmodule Pleroma.Notification do
         :notification_mute
       ])
 
-    Enum.uniq(ap_ids) -- relation_restricted_ap_ids
+    Enum.uniq(ap_ids) -- relationship_restricted_ap_ids
   end
 
   @doc "Filters out AP IDs of users who mute activity thread"