aboutsummaryrefslogtreecommitdiff
path: root/lib/pleroma/marker.ex
diff options
context:
space:
mode:
authordcc <dcc@logografos.com>2023-09-02 00:52:52 -0700
committerdcc <dcc@logografos.com>2023-09-02 00:52:52 -0700
commit3a4773c3c2bd0bbef244eb519b07208da9108e49 (patch)
tree973567a6f3abb37bfb0f785b1cad14ed55840ef5 /lib/pleroma/marker.ex
downloadanni-3a4773c3c2bd0bbef244eb519b07208da9108e49.tar.gz
anni-3a4773c3c2bd0bbef244eb519b07208da9108e49.tar.bz2
anni-3a4773c3c2bd0bbef244eb519b07208da9108e49.zip
First
Diffstat (limited to 'lib/pleroma/marker.ex')
-rw-r--r--lib/pleroma/marker.ex117
1 files changed, 117 insertions, 0 deletions
diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex
new file mode 100644
index 0000000..68b054e
--- /dev/null
+++ b/lib/pleroma/marker.ex
@@ -0,0 +1,117 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Marker do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+ import Ecto.Query
+
+ alias Ecto.Multi
+ alias Pleroma.Notification
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias __MODULE__
+
+ @timelines ["notifications"]
+ @type t :: %__MODULE__{}
+
+ schema "markers" do
+ field(:last_read_id, :string, default: "")
+ field(:timeline, :string, default: "")
+ field(:lock_version, :integer, default: 0)
+ field(:unread_count, :integer, default: 0, virtual: true)
+
+ belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
+ timestamps()
+ end
+
+ @doc "Gets markers by user and timeline."
+ @spec get_markers(User.t(), list(String)) :: list(t())
+ def get_markers(user, timelines \\ []) do
+ user
+ |> get_query(timelines)
+ |> unread_count_query()
+ |> Repo.all()
+ end
+
+ @spec upsert(User.t(), map()) :: {:ok | :error, any()}
+ def upsert(%User{} = user, attrs) do
+ attrs
+ |> Map.take(@timelines)
+ |> Enum.reduce(Multi.new(), fn {timeline, timeline_attrs}, multi ->
+ marker =
+ user
+ |> get_marker(timeline)
+ |> changeset(timeline_attrs)
+
+ Multi.insert(multi, timeline, marker,
+ returning: true,
+ on_conflict: {:replace, [:last_read_id]},
+ conflict_target: [:user_id, :timeline]
+ )
+ end)
+ |> Repo.transaction()
+ end
+
+ @spec multi_set_last_read_id(Multi.t(), User.t(), String.t()) :: Multi.t()
+ def multi_set_last_read_id(multi, %User{} = user, "notifications") do
+ multi
+ |> Multi.run(:counters, fn _repo, _changes ->
+ {:ok, %{last_read_id: Repo.one(Notification.last_read_query(user))}}
+ end)
+ |> Multi.insert(
+ :marker,
+ fn %{counters: attrs} ->
+ %Marker{timeline: "notifications", user_id: user.id}
+ |> struct(attrs)
+ |> Ecto.Changeset.change()
+ end,
+ returning: true,
+ on_conflict: {:replace, [:last_read_id]},
+ conflict_target: [:user_id, :timeline]
+ )
+ end
+
+ def multi_set_last_read_id(multi, _, _), do: multi
+
+ defp get_marker(user, timeline) do
+ case Repo.find_resource(get_query(user, timeline)) do
+ {:ok, marker} -> %__MODULE__{marker | user: user}
+ _ -> %__MODULE__{timeline: timeline, user_id: user.id}
+ end
+ end
+
+ @doc false
+ defp changeset(marker, attrs) do
+ marker
+ |> cast(attrs, [:last_read_id])
+ |> validate_required([:user_id, :timeline, :last_read_id])
+ |> validate_inclusion(:timeline, @timelines)
+ end
+
+ defp by_timeline(query, timeline) do
+ from(m in query, where: m.timeline in ^List.wrap(timeline))
+ end
+
+ defp by_user_id(query, id), do: from(m in query, where: m.user_id == ^id)
+
+ defp get_query(user, timelines) do
+ __MODULE__
+ |> by_user_id(user.id)
+ |> by_timeline(timelines)
+ end
+
+ defp unread_count_query(query) do
+ from(
+ q in query,
+ left_join: n in "notifications",
+ on: n.user_id == q.user_id and n.seen == false,
+ group_by: [:id],
+ select_merge: %{
+ unread_count: fragment("count(?)", n.id)
+ }
+ )
+ end
+end