| defmodule Plausible.Shield.PageRule do |
| @moduledoc """ |
| Schema for Pages block list |
| """ |
| use Ecto.Schema |
| import Ecto.Changeset |
|
|
| @type t() :: %__MODULE__{} |
|
|
| @primary_key {:id, :binary_id, autogenerate: true} |
| schema "shield_rules_page" do |
| belongs_to :site, Plausible.Site |
| field :page_path, :string |
| field :page_path_pattern, Plausible.Ecto.Types.CompiledRegex |
| field :action, Ecto.Enum, values: [:deny, :allow], default: :deny |
| field :added_by, :string |
|
|
| |
| field :from_cache?, :boolean, virtual: true, default: false |
| timestamps() |
| end |
|
|
| def changeset(rule \\ %__MODULE__{}, attrs) do |
| rule |
| |> cast(attrs, [:page_path]) |
| |> validate_required([:page_path]) |
| |> validate_length(:page_path, max: 250) |
| |> validate_change(:page_path, fn :page_path, p -> |
| if not String.starts_with?(p, "/") do |
| [page_path: "must start with /"] |
| else |
| [] |
| end |
| end) |
| |> store_regex() |
| |> unique_constraint(:page_path_pattern, |
| name: :shield_rules_page_site_id_page_path_pattern_index, |
| error_key: :page_path, |
| message: "rule already exists" |
| ) |
| end |
|
|
| defp store_regex(changeset) do |
| case get_field(changeset, :page_path) do |
| "/" <> _ = page_path -> |
| regex = |
| page_path |
| |> Regex.escape() |
| |> String.replace("\\*\\*", ".*") |
| |> String.replace("\\*", ".*") |
|
|
| regex = "^#{regex}$" |
|
|
| verify_valid_regex(changeset, regex) |
|
|
| _ -> |
| changeset |
| end |
| end |
|
|
| defp verify_valid_regex(changeset, regex) do |
| case Regex.compile(regex) do |
| {:ok, _} -> |
| put_change(changeset, :page_path_pattern, regex) |
|
|
| {:error, _} -> |
| add_error(changeset, :page_path, "could not compile regular expression") |
| end |
| end |
| end |
|
|