lib/openagents/agents/agent_box_grant.ex

58e6347eeb72 · 1 KB

defmodule OpenAgents.Agents.AgentBoxGrant do
  @moduledoc "A revocable human grant of scoped computer control to an agent."

  use Ecto.Schema
  import Ecto.Changeset

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

  schema "agent_box_control_grants" do
    belongs_to :agent, OpenAgents.Agents.Agent
    belongs_to :user, OpenAgents.Accounts.User
    belongs_to :granted_by, OpenAgents.Accounts.User
    field :target_kind, :string, default: "box"
    field :scope, :string, default: "box:control"
    field :granted_at, :utc_datetime_usec
    field :revoked_at, :utc_datetime_usec
    timestamps()
  end

  def changeset(grant, attrs) do
    grant
    |> cast(attrs, [:target_kind, :scope, :granted_at, :revoked_at])
    |> put_programmatic(attrs, :agent_id)
    |> put_programmatic(attrs, :user_id)
    |> put_programmatic(attrs, :granted_by_id)
    |> validate_required([
      :agent_id,
      :user_id,
      :granted_by_id,
      :target_kind,
      :scope,
      :granted_at
    ])
    |> validate_inclusion(:target_kind, ["box", "computer"])
    |> validate_control_scope()
    |> foreign_key_constraint(:agent_id)
    |> foreign_key_constraint(:user_id)
    |> foreign_key_constraint(:granted_by_id)
  end

  defp validate_control_scope(changeset) do
    target_kind = get_field(changeset, :target_kind)
    scope = get_field(changeset, :scope)

    if (target_kind == "box" and scope == "box:control") or
         (target_kind == "computer" and scope == "computer:control") do
      changeset
    else
      add_error(changeset, :scope, "does not match target kind")
    end
  end

  defp put_programmatic(changeset, attrs, field) do
    case Map.fetch(attrs, field) do
      {:ok, value} -> put_change(changeset, field, value)
      :error -> changeset
    end
  end
end