diff --git a/lib/pinchflat/sources/source.ex b/lib/pinchflat/sources/source.ex index 1cdebee..418c828 100644 --- a/lib/pinchflat/sources/source.ex +++ b/lib/pinchflat/sources/source.ex @@ -112,6 +112,7 @@ defmodule Pinchflat.Sources.Source do |> dynamic_default(:custom_name, fn cs -> get_field(cs, :collection_name) end) |> dynamic_default(:uuid, fn _ -> Ecto.UUID.generate() end) |> validate_required(required_fields) + |> validate_title_regex() |> validate_number(:retention_period_days, greater_than_or_equal_to: 0) # Ensures it ends with `.{{ ext }}` or `.%(ext)s` or similar (with a little wiggle room) |> validate_format(:output_path_template_override, MediaProfile.ext_regex(), message: "must end with .{{ ext }}") @@ -149,6 +150,15 @@ defmodule Pinchflat.Sources.Source do ~r<^(?:(?!youtube\.com/(watch|shorts|embed)|youtu\.be).)*$> end + defp validate_title_regex(%{changes: %{title_filter_regex: regex}} = changeset) when is_binary(regex) do + case Ecto.Adapters.SQL.query(Repo, "SELECT regexp_like('', ?)", [regex]) do + {:ok, _} -> changeset + _ -> add_error(changeset, :title_filter_regex, "is invalid") + end + end + + defp validate_title_regex(changeset), do: changeset + defimpl Jason.Encoder, for: Source do def encode(value, opts) do value diff --git a/test/pinchflat/sources_test.exs b/test/pinchflat/sources_test.exs index 22e4fc7..65f48e0 100644 --- a/test/pinchflat/sources_test.exs +++ b/test/pinchflat/sources_test.exs @@ -659,6 +659,28 @@ defmodule Pinchflat.SourcesTest do end end + describe "change_source/3 when testing regex validation" do + test "succeeds when a valid regex is provided" do + source = source_fixture() + + assert %{errors: []} = Sources.change_source(source, %{title_filter_regex: "(?i)^How to Bike$"}) + end + + test "succeeds when a regex is set back to nil" do + source = source_fixture(%{title_filter_regex: "(?i)^How to Bike$"}) + + assert %{errors: []} = Sources.change_source(source, %{title_filter_regex: nil}) + end + + test "fails when an invalid regex is provided" do + source = source_fixture() + + changeset = Sources.change_source(source, %{title_filter_regex: "*FOO"}) + + assert "is invalid" in errors_on(changeset).title_filter_regex + end + end + describe "change_source/3 when testing original_url validation" do test "succeeds when an original URL is valid" do source = source_fixture()