# Pleroma: A lightweight social networking server # Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Filter do use Ecto.Schema import Ecto.Changeset import Ecto.Query alias Pleroma.Repo alias Pleroma.User schema "filters" do belongs_to(:user, User, type: FlakeId.Ecto.CompatType) field(:filter_id, :integer) field(:hide, :boolean, default: false) field(:whole_word, :boolean, default: true) field(:phrase, :string) field(:context, {:array, :string}) field(:expires_at, :utc_datetime) timestamps() end def get(id, %{id: user_id} = _user) do query = from( f in Pleroma.Filter, where: f.filter_id == ^id, where: f.user_id == ^user_id ) Repo.one(query) end def get_active(query) do from(f in query, where: is_nil(f.expires_at) or f.expires_at > ^NaiveDateTime.utc_now()) end def get_irreversible(query) do from(f in query, where: f.hide) end def get_filters(query \\ __MODULE__, %User{id: user_id}) do query = from( f in query, where: f.user_id == ^user_id, order_by: [desc: :id] ) Repo.all(query) end def create(%Pleroma.Filter{user_id: user_id, filter_id: nil} = filter) do # If filter_id wasn't given, use the max filter_id for this user plus 1. # XXX This could result in a race condition if a user tries to add two # different filters for their account from two different clients at the # same time, but that should be unlikely. max_id_query = from( f in Pleroma.Filter, where: f.user_id == ^user_id, select: max(f.filter_id) ) filter_id = case Repo.one(max_id_query) do # Start allocating from 1 nil -> 1 max_id -> max_id + 1 end filter |> Map.put(:filter_id, filter_id) |> Repo.insert() end def create(%Pleroma.Filter{} = filter) do Repo.insert(filter) end def delete(%Pleroma.Filter{id: filter_key} = filter) when is_number(filter_key) do Repo.delete(filter) end def delete(%Pleroma.Filter{id: filter_key} = filter) when is_nil(filter_key) do %Pleroma.Filter{id: id} = get(filter.filter_id, %{id: filter.user_id}) filter |> Map.put(:id, id) |> Repo.delete() end def update(%Pleroma.Filter{} = filter, params) do filter |> cast(params, [:phrase, :context, :hide, :expires_at, :whole_word]) |> validate_required([:phrase, :context]) |> Repo.update() end def compose_regex(user_or_filters, format \\ :postgres) def compose_regex(%User{} = user, format) do __MODULE__ |> get_active() |> get_irreversible() |> get_filters(user) |> compose_regex(format) end def compose_regex([_ | _] = filters, format) do phrases = filters |> Enum.map(& &1.phrase) |> Enum.join("|") case format do :postgres -> "\\y(#{phrases})\\y" :re -> ~r/\b#{phrases}\b/i _ -> nil end end def compose_regex(_, _), do: nil end