Phase 3: deployment model + subscriptions
cloud_deployments — the billable unit (one app instance). A tenant has 1..N deployments; cloud_resources.deployment_id ties resources to one. Fields: tenant_id, slug (unique per tenant), display_name, region, state, llm_mode, billing_action_suspended (operator override), template_code/version (nullable — formal templates land in phase 4). Lifecycle state machine in ArcadiaCloud.Deployments — states trial / active / past_due / paused / suspended / cancelled / archived. Every transition is validated against an explicit @transitions map and recorded in cloud_deployment_events. create_deployment defaults to `active` (trial is wired but no flow enters it yet). subscriptions — one per deployment, binds it to a plan_version. status active/paused/cancelled, current period dates, trial_ends_at. subscription_addons — addons attached to a subscription with price + qty SNAPSHOTTED at attach time, so a later catalog price change can't retroactively reprice an existing subscriber. ArcadiaCloud.Subscriptions context: create_subscription (period defaults to current calendar month), attach_addon (snapshots from the live Addon), change_plan_version (migrate to a new version — price changes / up-down grades), get_subscription_for_deployment. API (platform_admin sees all tenants; others scoped to own tenant_id): - GET/POST /api/v1/deployments - GET /api/v1/deployments/:id (with subscription + events) - POST /api/v1/deployments/:id/transition - POST /api/v1/deployments/:id/subscribe (plan_code + optional addons) Smoke verified: created a deployment, transitioned active->paused (events logged with actor), rejected an invalid paused->archived transition (422), subscribed to Studio with the storage_50gb addon — addon price snapshotted at 750c/qty 50; show returns deployment + subscription + event history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
88
priv/repo/migrations/20260520150000_create_deployments.exs
Normal file
88
priv/repo/migrations/20260520150000_create_deployments.exs
Normal file
@@ -0,0 +1,88 @@
|
||||
defmodule ArcadiaCloud.Repo.Migrations.CreateDeployments do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
# A deployment is the billable unit — one app instance. A tenant has
|
||||
# 1..N deployments; resources tag to a deployment via
|
||||
# cloud_resources.deployment_id (already migrated).
|
||||
create table(:cloud_deployments, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :tenant_id, :string, null: false
|
||||
add :cloud_project_id, references(:cloud_projects, type: :binary_id, on_delete: :nilify_all)
|
||||
add :slug, :string, null: false
|
||||
add :display_name, :string
|
||||
# template_code/version nullable — phase 3 pilot deployments are
|
||||
# provisioned by hand; formal templates land in phase 4.
|
||||
add :template_code, :string
|
||||
add :template_version, :string
|
||||
add :region, :string
|
||||
add :state, :string, null: false, default: "active"
|
||||
add :state_since, :utc_datetime, null: false
|
||||
add :llm_mode, :string, null: false, default: "none"
|
||||
# operator override: keep this deployment running even when billing
|
||||
# rules would otherwise suspend it.
|
||||
add :billing_action_suspended, :boolean, null: false, default: false
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
create unique_index(:cloud_deployments, [:tenant_id, :slug])
|
||||
create index(:cloud_deployments, [:tenant_id])
|
||||
create index(:cloud_deployments, [:state])
|
||||
|
||||
create table(:cloud_deployment_events, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :deployment_id,
|
||||
references(:cloud_deployments, type: :binary_id, on_delete: :delete_all),
|
||||
null: false
|
||||
add :from_state, :string
|
||||
add :to_state, :string, null: false
|
||||
add :reason, :string
|
||||
add :actor, :string
|
||||
add :notes, :text
|
||||
add :occurred_at, :utc_datetime, null: false
|
||||
end
|
||||
|
||||
create index(:cloud_deployment_events, [:deployment_id, :occurred_at])
|
||||
|
||||
# One subscription per deployment — binds it to a plan version.
|
||||
create table(:subscriptions, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :deployment_id,
|
||||
references(:cloud_deployments, type: :binary_id, on_delete: :delete_all),
|
||||
null: false
|
||||
add :plan_version_id,
|
||||
references(:plan_versions, type: :binary_id, on_delete: :restrict),
|
||||
null: false
|
||||
add :status, :string, null: false, default: "active"
|
||||
add :current_period_start, :date, null: false
|
||||
add :current_period_end, :date, null: false
|
||||
add :trial_ends_at, :date
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
create unique_index(:subscriptions, [:deployment_id])
|
||||
create index(:subscriptions, [:plan_version_id])
|
||||
create index(:subscriptions, [:status])
|
||||
|
||||
# Addons attached to a subscription. Price/qty are SNAPSHOTTED at
|
||||
# attach time so later catalog changes don't retroactively reprice.
|
||||
create table(:subscription_addons, primary_key: false) do
|
||||
add :id, :binary_id, primary_key: true
|
||||
add :subscription_id,
|
||||
references(:subscriptions, type: :binary_id, on_delete: :delete_all),
|
||||
null: false
|
||||
add :addon_id, references(:addons, type: :binary_id, on_delete: :restrict), null: false
|
||||
add :resource_kind, :string, null: false
|
||||
add :qty, :decimal, null: false
|
||||
add :price_cents, :integer, null: false
|
||||
add :currency, :string, null: false, default: "AUD"
|
||||
add :attached_at, :utc_datetime, null: false
|
||||
|
||||
timestamps(type: :utc_datetime)
|
||||
end
|
||||
|
||||
create index(:subscription_addons, [:subscription_id])
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user