lib/openagents/scv/driver_account.ex

main at 58e6347eeb72 · 3 KB

defmodule OpenAgents.SCV.DriverAccount do
  @moduledoc "Restricted metadata for one operator-connected SCV driver account."

  use Ecto.Schema
  import Ecto.Changeset

  alias OpenAgents.Accounts.User
  alias OpenAgents.SCV.DriverLoginAttempt

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  @timestamps_opts [type: :utc_datetime_usec]

  schema "scv_driver_accounts" do
    field :driver, :string, default: "codex_app_server"
    field :credential_kind, :string, default: "managed_chatgpt"
    field :label, :string
    field :status, :string, default: "pending"
    field :secret_ref, :string, redact: true
    field :credential_version, :integer
    field :account_email, :string
    field :plan_type, :string
    field :available_models, {:array, :string}, default: []
    field :reasoning_efforts, {:array, :string}, default: []
    field :last_verified_at, :utc_datetime_usec
    field :last_error_code, :string
    field :disconnected_at, :utc_datetime_usec

    belongs_to :operator, User
    has_many :login_attempts, DriverLoginAttempt, foreign_key: :account_id

    timestamps()
  end

  @type t :: %__MODULE__{}

  @doc false
  def create_changeset(account, attributes) do
    account
    |> cast(attributes, [:id, :operator_id, :label, :secret_ref])
    |> validate_required([:operator_id, :label, :secret_ref])
    |> validate_length(:label, min: 1, max: 80)
    |> validate_length(:secret_ref, min: 1, max: 512)
    |> put_change(:driver, "codex_app_server")
    |> put_change(:credential_kind, "managed_chatgpt")
    |> put_change(:status, "pending")
    |> foreign_key_constraint(:operator_id)
    |> unique_constraint(:secret_ref)
    |> check_constraint(:driver, name: :scv_driver_accounts_driver_check)
    |> check_constraint(:credential_kind, name: :scv_driver_accounts_credential_kind_check)
    |> check_constraint(:status, name: :scv_driver_accounts_status_check)
  end

  @doc false
  def ready_changeset(account, attributes) do
    account
    |> cast(attributes, [
      :credential_version,
      :account_email,
      :plan_type,
      :available_models,
      :reasoning_efforts,
      :last_verified_at
    ])
    |> validate_required([:credential_version, :plan_type, :last_verified_at])
    |> validate_length(:account_email, max: 320)
    |> validate_length(:plan_type, min: 1, max: 80)
    |> put_change(:status, "ready")
    |> put_change(:last_error_code, nil)
    |> check_constraint(:status, name: :scv_driver_accounts_status_check)
  end

  @doc false
  def failed_changeset(account, code) when is_binary(code) do
    account
    |> change(status: "failed", last_error_code: String.slice(code, 0, 80))
    |> check_constraint(:status, name: :scv_driver_accounts_status_check)
  end

  @doc false
  def retry_changeset(account, operator_id, label)
      when is_binary(operator_id) and is_binary(label) do
    account
    |> change(
      operator_id: operator_id,
      label: label,
      status: "pending",
      credential_version: nil,
      account_email: nil,
      plan_type: nil,
      available_models: [],
      reasoning_efforts: [],
      last_verified_at: nil,
      last_error_code: nil,
      disconnected_at: nil
    )
    |> validate_required([:operator_id, :label, :secret_ref])
    |> validate_length(:label, min: 1, max: 80)
    |> foreign_key_constraint(:operator_id)
    |> check_constraint(:status, name: :scv_driver_accounts_status_check)
  end
end