Skip to main content

lib/rulestead/targeting/audience_reference_projection.ex

defmodule Rulestead.Targeting.AudienceReferenceProjection do
  @moduledoc false

  use Ecto.Schema

  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  schema "audience_reference_projection" do
    field(:environment_key, :string)
    field(:tenant_key, :string)
    field(:audience_key, :string)
    field(:flag_key, :string)
    field(:ruleset_version, :integer)
    field(:rule_key, :string)
    field(:rule_strategy, :string)
    field(:ruleset_status, :string)
    field(:rollout_context, :map, default: %{})
    field(:lifecycle_context, :map, default: %{})
    field(:visibility, :map, default: %{})
    field(:reference_count, :integer, default: 1)
    field(:hidden_reference_count, :integer, default: 0)

    timestamps(type: :utc_datetime_usec)
  end

  @type t :: %__MODULE__{}

  @spec changeset(t(), map()) :: Ecto.Changeset.t()
  def changeset(projection, attrs) do
    projection
    |> cast(attrs, [
      :environment_key,
      :tenant_key,
      :audience_key,
      :flag_key,
      :ruleset_version,
      :rule_key,
      :rule_strategy,
      :ruleset_status,
      :rollout_context,
      :lifecycle_context,
      :visibility,
      :reference_count,
      :hidden_reference_count
    ])
    |> update_change(:environment_key, &normalize_string/1)
    |> update_change(:tenant_key, &normalize_string/1)
    |> update_change(:audience_key, &normalize_string/1)
    |> update_change(:flag_key, &normalize_string/1)
    |> update_change(:rule_key, &normalize_string/1)
    |> update_change(:rule_strategy, &normalize_string/1)
    |> update_change(:ruleset_status, &normalize_string/1)
    |> update_change(:rollout_context, &normalize_map/1)
    |> update_change(:lifecycle_context, &normalize_map/1)
    |> update_change(:visibility, &normalize_map/1)
    |> update_change(:reference_count, &normalize_count/1)
    |> update_change(:hidden_reference_count, &normalize_count/1)
    |> validate_required([
      :environment_key,
      :tenant_key,
      :audience_key,
      :flag_key,
      :ruleset_version,
      :rule_key
    ])
    |> validate_number(:ruleset_version, greater_than: 0)
    |> validate_number(:reference_count, greater_than_or_equal_to: 0)
    |> validate_number(:hidden_reference_count, greater_than_or_equal_to: 0)
    |> unique_constraint(
      [:environment_key, :tenant_key, :flag_key, :ruleset_version, :rule_key, :audience_key],
      name: :audience_reference_projection_identity_index
    )
  end

  defp normalize_string(value) when is_binary(value) do
    case String.trim(value) do
      "" -> nil
      normalized -> normalized
    end
  end

  defp normalize_string(value) when is_atom(value),
    do: value |> Atom.to_string() |> normalize_string()

  defp normalize_string(value), do: value

  defp normalize_map(value) when is_map(value), do: value
  defp normalize_map(_value), do: %{}

  defp normalize_count(value) when is_integer(value) and value >= 0, do: value
  defp normalize_count(_value), do: 0
end