| defmodule Plausible.Shield.HostnameRule do |
| @moduledoc """ |
| Schema for Hostnames allow list |
| """ |
| use Ecto.Schema |
| import Ecto.Changeset |
|
|
| @type t() :: %__MODULE__{} |
|
|
| @primary_key {:id, :binary_id, autogenerate: true} |
| schema "shield_rules_hostname" do |
| belongs_to :site, Plausible.Site |
| field :hostname, :string |
| field :hostname_pattern, Plausible.Ecto.Types.CompiledRegex |
| field :action, Ecto.Enum, values: [:deny, :allow], default: :allow |
| field :added_by, :string |
|
|
| |
| field :from_cache?, :boolean, virtual: true, default: false |
| timestamps() |
| end |
|
|
| def changeset(rule \\ %__MODULE__{}, attrs) do |
| rule |
| |> cast(attrs, [:hostname]) |
| |> validate_required([:hostname]) |
| |> validate_length(:hostname, max: 250) |
| |> store_regex() |
| |> unique_constraint(:hostname_pattern, |
| name: :shield_rules_hostname_site_id_hostname_pattern_index, |
| error_key: :hostname, |
| message: "rule already exists" |
| ) |
| end |
|
|
| defp store_regex(changeset) do |
| case fetch_change(changeset, :hostname) do |
| {:ok, hostname} -> |
| hostname |
| |> build_regex() |
| |> verify_and_put_regex(changeset) |
|
|
| :error -> |
| changeset |
| end |
| end |
|
|
| defp build_regex(hostname) do |
| regex = |
| hostname |
| |> Regex.escape() |
| |> String.replace("\\*\\*", ".*") |
| |> String.replace("\\*", ".*") |
|
|
| "^#{regex}$" |
| end |
|
|
| defp verify_and_put_regex(regex, changeset) do |
| case Regex.compile(regex) do |
| {:ok, _} -> |
| put_change(changeset, :hostname_pattern, regex) |
|
|
| {:error, _} -> |
| add_error(changeset, :hostname, "could not compile regular expression") |
| end |
| end |
| end |
|
|