lib/chimeway/workflows/workflow_run.ex

defmodule Chimeway.Workflows.WorkflowRun do
  @moduledoc "Durable workflow run aggregate row anchored to one notification."

  use Ecto.Schema
  import Ecto.Changeset

  alias Chimeway.Notifications.Notification
  alias Chimeway.Workflows.{WorkflowDefinition, WorkflowStep, WorkflowTransition}

  @type t :: %__MODULE__{}

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

  @state_values [:active, :waiting, :completed, :stopped]

  schema "chimeway_workflow_runs" do
    belongs_to(:notification, Notification)
    belongs_to(:workflow_definition, WorkflowDefinition)
    belongs_to(:current_step, WorkflowStep)
    has_many(:transitions, WorkflowTransition)

    field(:state, Ecto.Enum, values: @state_values, default: :active)
    field(:started_at, :utc_datetime_usec)
    field(:last_transition_at, :utc_datetime_usec)
    field(:status_reason, :string)
    field(:status_context, :map, default: %{})
    field(:tenant_id, :string)
    field(:suspended_until, :utc_datetime_usec)
    field(:pending_signals, {:array, :string}, default: [])
    field(:terminal_reason, :string)

    timestamps(type: :utc_datetime_usec)
  end

  @required_fields ~w(
    notification_id
    workflow_definition_id
    current_step_id
    state
    started_at
    last_transition_at
    status_reason
    tenant_id
  )a

  @optional_fields ~w(
    status_context
    suspended_until
    pending_signals
    terminal_reason
  )a

  def changeset(workflow_run, attrs) do
    workflow_run
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
    |> validate_inclusion(:state, @state_values)
    |> validate_length(:tenant_id, min: 1)
    |> put_default_status_context()
    |> unique_constraint(:notification_id,
      name: :chimeway_workflow_runs_notification_id_index
    )
  end

  defp put_default_status_context(changeset) do
    case get_field(changeset, :status_context) do
      nil -> put_change(changeset, :status_context, %{})
      _context -> changeset
    end
  end
end