logo

pleroma

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

filter.ex (6169B)


  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.Filter do
  5. use Ecto.Schema
  6. import Ecto.Changeset
  7. import Ecto.Query
  8. alias Pleroma.Repo
  9. alias Pleroma.User
  10. @type t() :: %__MODULE__{}
  11. @type format() :: :postgres | :re
  12. schema "filters" do
  13. belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
  14. field(:filter_id, :integer)
  15. field(:hide, :boolean, default: false)
  16. field(:whole_word, :boolean, default: true)
  17. field(:phrase, :string)
  18. field(:context, {:array, :string})
  19. field(:expires_at, :naive_datetime)
  20. timestamps()
  21. end
  22. @spec get(integer() | String.t(), User.t()) :: t() | nil
  23. def get(id, %{id: user_id} = _user) do
  24. query =
  25. from(
  26. f in __MODULE__,
  27. where: f.filter_id == ^id,
  28. where: f.user_id == ^user_id
  29. )
  30. Repo.one(query)
  31. end
  32. @spec get_active(Ecto.Query.t() | module()) :: Ecto.Query.t()
  33. def get_active(query) do
  34. from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now())
  35. end
  36. @spec get_irreversible(Ecto.Query.t()) :: Ecto.Query.t()
  37. def get_irreversible(query) do
  38. from(f in query, where: f.hide)
  39. end
  40. @spec get_filters(Ecto.Query.t() | module(), User.t()) :: [t()]
  41. def get_filters(query \\ __MODULE__, %User{id: user_id}) do
  42. query =
  43. from(
  44. f in query,
  45. where: f.user_id == ^user_id,
  46. order_by: [desc: :id]
  47. )
  48. Repo.all(query)
  49. end
  50. @spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
  51. def create(attrs \\ %{}) do
  52. Repo.transaction(fn -> create_with_expiration(attrs) end)
  53. end
  54. defp create_with_expiration(attrs) do
  55. with {:ok, filter} <- do_create(attrs),
  56. {:ok, _} <- maybe_add_expiration_job(filter) do
  57. filter
  58. else
  59. {:error, error} -> Repo.rollback(error)
  60. end
  61. end
  62. defp do_create(attrs) do
  63. %__MODULE__{}
  64. |> cast(attrs, [:phrase, :context, :hide, :expires_at, :whole_word, :user_id, :filter_id])
  65. |> maybe_add_filter_id()
  66. |> validate_required([:phrase, :context, :user_id, :filter_id])
  67. |> maybe_add_expires_at(attrs)
  68. |> Repo.insert()
  69. end
  70. defp maybe_add_filter_id(%{changes: %{filter_id: _}} = changeset), do: changeset
  71. defp maybe_add_filter_id(%{changes: %{user_id: user_id}} = changeset) do
  72. # If filter_id wasn't given, use the max filter_id for this user plus 1.
  73. # XXX This could result in a race condition if a user tries to add two
  74. # different filters for their account from two different clients at the
  75. # same time, but that should be unlikely.
  76. max_id_query =
  77. from(
  78. f in __MODULE__,
  79. where: f.user_id == ^user_id,
  80. select: max(f.filter_id)
  81. )
  82. filter_id =
  83. case Repo.one(max_id_query) do
  84. # Start allocating from 1
  85. nil ->
  86. 1
  87. max_id ->
  88. max_id + 1
  89. end
  90. change(changeset, filter_id: filter_id)
  91. end
  92. # don't override expires_at, if passed expires_at and expires_in
  93. defp maybe_add_expires_at(%{changes: %{expires_at: %NaiveDateTime{} = _}} = changeset, _) do
  94. changeset
  95. end
  96. defp maybe_add_expires_at(changeset, %{expires_in: expires_in})
  97. when is_integer(expires_in) and expires_in > 0 do
  98. expires_at =
  99. NaiveDateTime.utc_now()
  100. |> NaiveDateTime.add(expires_in)
  101. |> NaiveDateTime.truncate(:second)
  102. change(changeset, expires_at: expires_at)
  103. end
  104. defp maybe_add_expires_at(changeset, %{expires_in: nil}) do
  105. change(changeset, expires_at: nil)
  106. end
  107. defp maybe_add_expires_at(changeset, _), do: changeset
  108. defp maybe_add_expiration_job(%{expires_at: %NaiveDateTime{} = expires_at} = filter) do
  109. Pleroma.Workers.PurgeExpiredFilter.enqueue(%{
  110. filter_id: filter.id,
  111. expires_at: DateTime.from_naive!(expires_at, "Etc/UTC")
  112. })
  113. end
  114. defp maybe_add_expiration_job(_), do: {:ok, nil}
  115. @spec delete(t()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
  116. def delete(%__MODULE__{} = filter) do
  117. Repo.transaction(fn -> delete_with_expiration(filter) end)
  118. end
  119. defp delete_with_expiration(filter) do
  120. with {:ok, _} <- maybe_delete_old_expiration_job(filter, nil),
  121. {:ok, filter} <- Repo.delete(filter) do
  122. filter
  123. else
  124. {:error, error} -> Repo.rollback(error)
  125. end
  126. end
  127. @spec update(t(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
  128. def update(%__MODULE__{} = filter, params) do
  129. Repo.transaction(fn -> update_with_expiration(filter, params) end)
  130. end
  131. defp update_with_expiration(filter, params) do
  132. with {:ok, updated} <- do_update(filter, params),
  133. {:ok, _} <- maybe_delete_old_expiration_job(filter, updated),
  134. {:ok, _} <-
  135. maybe_add_expiration_job(updated) do
  136. updated
  137. else
  138. {:error, error} -> Repo.rollback(error)
  139. end
  140. end
  141. defp do_update(filter, params) do
  142. filter
  143. |> cast(params, [:phrase, :context, :hide, :expires_at, :whole_word])
  144. |> validate_required([:phrase, :context])
  145. |> maybe_add_expires_at(params)
  146. |> Repo.update()
  147. end
  148. defp maybe_delete_old_expiration_job(%{expires_at: nil}, _), do: {:ok, nil}
  149. defp maybe_delete_old_expiration_job(%{expires_at: expires_at}, %{expires_at: expires_at}) do
  150. {:ok, nil}
  151. end
  152. defp maybe_delete_old_expiration_job(%{id: id}, _) do
  153. with %Oban.Job{} = job <- Pleroma.Workers.PurgeExpiredFilter.get_expiration(id) do
  154. Repo.delete(job)
  155. else
  156. nil -> {:ok, nil}
  157. end
  158. end
  159. @spec compose_regex(User.t() | [t()], format()) :: String.t() | Regex.t() | nil
  160. def compose_regex(user_or_filters, format \\ :postgres)
  161. def compose_regex(%User{} = user, format) do
  162. __MODULE__
  163. |> get_active()
  164. |> get_irreversible()
  165. |> get_filters(user)
  166. |> compose_regex(format)
  167. end
  168. def compose_regex([_ | _] = filters, format) do
  169. phrases =
  170. filters
  171. |> Enum.map(& &1.phrase)
  172. |> Enum.join("|")
  173. case format do
  174. :postgres ->
  175. "\\y(#{phrases})\\y"
  176. :re ->
  177. ~r/\b#{phrases}\b/i
  178. end
  179. end
  180. def compose_regex(_, _), do: nil
  181. end