marker.ex (3175B)
- # 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