logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

marker.ex (3175B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
  3. # SPDX-License-Identifier: AGPL-3.0-only
  4. defmodule Pleroma.Marker do
  5. use Ecto.Schema
  6. import Ecto.Changeset
  7. import Ecto.Query
  8. alias Ecto.Multi
  9. alias Pleroma.Notification
  10. alias Pleroma.Repo
  11. alias Pleroma.User
  12. alias __MODULE__
  13. @timelines ["notifications"]
  14. @type t :: %__MODULE__{}
  15. schema "markers" do
  16. field(:last_read_id, :string, default: "")
  17. field(:timeline, :string, default: "")
  18. field(:lock_version, :integer, default: 0)
  19. field(:unread_count, :integer, default: 0, virtual: true)
  20. belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
  21. timestamps()
  22. end
  23. @doc "Gets markers by user and timeline."
  24. @spec get_markers(User.t(), list(String)) :: list(t())
  25. def get_markers(user, timelines \\ []) do
  26. user
  27. |> get_query(timelines)
  28. |> unread_count_query()
  29. |> Repo.all()
  30. end
  31. @spec upsert(User.t(), map()) :: {:ok | :error, any()}
  32. def upsert(%User{} = user, attrs) do
  33. attrs
  34. |> Map.take(@timelines)
  35. |> Enum.reduce(Multi.new(), fn {timeline, timeline_attrs}, multi ->
  36. marker =
  37. user
  38. |> get_marker(timeline)
  39. |> changeset(timeline_attrs)
  40. Multi.insert(multi, timeline, marker,
  41. returning: true,
  42. on_conflict: {:replace, [:last_read_id]},
  43. conflict_target: [:user_id, :timeline]
  44. )
  45. end)
  46. |> Repo.transaction()
  47. end
  48. @spec multi_set_last_read_id(Multi.t(), User.t(), String.t()) :: Multi.t()
  49. def multi_set_last_read_id(multi, %User{} = user, "notifications") do
  50. multi
  51. |> Multi.run(:counters, fn _repo, _changes ->
  52. {:ok, %{last_read_id: Repo.one(Notification.last_read_query(user))}}
  53. end)
  54. |> Multi.insert(
  55. :marker,
  56. fn %{counters: attrs} ->
  57. %Marker{timeline: "notifications", user_id: user.id}
  58. |> struct(attrs)
  59. |> Ecto.Changeset.change()
  60. end,
  61. returning: true,
  62. on_conflict: {:replace, [:last_read_id]},
  63. conflict_target: [:user_id, :timeline]
  64. )
  65. end
  66. def multi_set_last_read_id(multi, _, _), do: multi
  67. defp get_marker(user, timeline) do
  68. case Repo.find_resource(get_query(user, timeline)) do
  69. {:ok, marker} -> %__MODULE__{marker | user: user}
  70. _ -> %__MODULE__{timeline: timeline, user_id: user.id}
  71. end
  72. end
  73. @doc false
  74. defp changeset(marker, attrs) do
  75. marker
  76. |> cast(attrs, [:last_read_id])
  77. |> validate_required([:user_id, :timeline, :last_read_id])
  78. |> validate_inclusion(:timeline, @timelines)
  79. end
  80. defp by_timeline(query, timeline) do
  81. from(m in query, where: m.timeline in ^List.wrap(timeline))
  82. end
  83. defp by_user_id(query, id), do: from(m in query, where: m.user_id == ^id)
  84. defp get_query(user, timelines) do
  85. __MODULE__
  86. |> by_user_id(user.id)
  87. |> by_timeline(timelines)
  88. end
  89. defp unread_count_query(query) do
  90. from(
  91. q in query,
  92. left_join: n in "notifications",
  93. on: n.user_id == q.user_id and n.seen == false,
  94. group_by: [:id],
  95. select_merge: %{
  96. unread_count: fragment("count(?)", n.id)
  97. }
  98. )
  99. end
  100. end