Add cloudhost-platform Helm chart and registry ingress manifests.
Deploy backend, frontend, PostgreSQL, and Redis on Kubernetes with optional Ingress/TLS, SQL migration hooks, and public registry exposure at repo.3fase.ir. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
-- Temporary external access grants (Redis, RabbitMQ, database)
|
||||
CREATE TYPE service_access_target AS ENUM (
|
||||
'database',
|
||||
'redis',
|
||||
'rabbitmq_amqp',
|
||||
'rabbitmq_management'
|
||||
);
|
||||
|
||||
CREATE TYPE service_access_grant_status AS ENUM (
|
||||
'active',
|
||||
'expired',
|
||||
'revoked'
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS service_access_grants (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"applicationId" UUID NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
|
||||
"userId" UUID NOT NULL,
|
||||
"clusterId" UUID NOT NULL,
|
||||
namespace VARCHAR(255) NOT NULL,
|
||||
target service_access_target NOT NULL,
|
||||
"nodePort" INTEGER NOT NULL,
|
||||
"targetPort" INTEGER NOT NULL,
|
||||
host VARCHAR(255) NOT NULL,
|
||||
"k8sServiceName" VARCHAR(255) NOT NULL,
|
||||
status service_access_grant_status NOT NULL DEFAULT 'active',
|
||||
"expiresAt" TIMESTAMPTZ NOT NULL,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_service_access_grants_app_target_status
|
||||
ON service_access_grants ("applicationId", target, status);
|
||||
|
||||
INSERT INTO platform_settings (id, key, value, description, "createdAt", "updatedAt")
|
||||
SELECT gen_random_uuid(), 'access_max_duration_minutes', '240',
|
||||
'Maximum duration (minutes) for temporary external service access',
|
||||
NOW(), NOW()
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1 FROM platform_settings WHERE key = 'access_max_duration_minutes'
|
||||
);
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Add DOCKED lifecycle status and dock metadata (run if TypeORM sync is disabled)
|
||||
ALTER TYPE applications_lifecyclestatus_enum ADD VALUE IF NOT EXISTS 'docked';
|
||||
|
||||
ALTER TABLE applications
|
||||
ADD COLUMN IF NOT EXISTS "dockedAt" TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS "dockSnapshotId" VARCHAR;
|
||||
@@ -0,0 +1,24 @@
|
||||
CREATE TABLE IF NOT EXISTS resource_credits (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"userId" UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
"sourceAppName" VARCHAR,
|
||||
runtime VARCHAR NOT NULL,
|
||||
"databaseType" VARCHAR NOT NULL,
|
||||
"cpuLimit" VARCHAR NOT NULL,
|
||||
"memoryLimit" VARCHAR NOT NULL,
|
||||
replicas INT NOT NULL DEFAULT 1,
|
||||
"dbStorageSize" VARCHAR,
|
||||
"appStorageSize" VARCHAR,
|
||||
"enableRedis" BOOLEAN NOT NULL DEFAULT false,
|
||||
"enableRabbitmq" BOOLEAN NOT NULL DEFAULT false,
|
||||
"enableElasticsearch" BOOLEAN NOT NULL DEFAULT false,
|
||||
"billingCycle" VARCHAR,
|
||||
"expiresAt" TIMESTAMPTZ NOT NULL,
|
||||
"consumedAt" TIMESTAMPTZ,
|
||||
"appliedApplicationId" VARCHAR,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_resource_credits_user_active
|
||||
ON resource_credits ("userId", "expiresAt")
|
||||
WHERE "consumedAt" IS NULL;
|
||||
@@ -0,0 +1,117 @@
|
||||
-- Usage-based pricing catalog (replaces per-cycle service_plans + scattered platform_settings)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pricing_rates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
runtime VARCHAR NOT NULL,
|
||||
resource_type VARCHAR NOT NULL,
|
||||
hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (runtime, resource_type)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS addon_rates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
resource_type VARCHAR NOT NULL UNIQUE,
|
||||
hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Seed addon rows
|
||||
INSERT INTO addon_rates (resource_type, hourly_price, monthly_price, yearly_price)
|
||||
VALUES
|
||||
('redis_addon', 0, 0, 0),
|
||||
('rabbitmq_addon', 0, 0, 0),
|
||||
('elasticsearch_addon', 0, 0, 0),
|
||||
('custom_domain_addon', 0, 0, 0)
|
||||
ON CONFLICT (resource_type) DO NOTHING;
|
||||
|
||||
-- Migrate runtime rates from service_plans + pricing_rules
|
||||
INSERT INTO pricing_rates (runtime, resource_type, hourly_price, monthly_price, yearly_price, is_active)
|
||||
SELECT
|
||||
sp.runtime,
|
||||
pr.resource_type,
|
||||
COALESCE(MAX(CASE WHEN sp."billingCycle" = 'hourly' THEN pr.unit_price END), 0),
|
||||
COALESCE(MAX(CASE WHEN sp."billingCycle" = 'monthly' THEN pr.unit_price END), 0),
|
||||
COALESCE(MAX(CASE WHEN sp."billingCycle" = 'yearly' THEN pr.unit_price END), 0),
|
||||
BOOL_OR(sp."isActive")
|
||||
FROM pricing_rules pr
|
||||
JOIN service_plans sp ON sp.id = pr."planId"
|
||||
WHERE pr.resource_type IN (
|
||||
'base_fee', 'cpu_per_core', 'memory_per_gb', 'storage_per_gb', 'database_addon'
|
||||
)
|
||||
GROUP BY sp.runtime, pr.resource_type
|
||||
ON CONFLICT (runtime, resource_type) DO UPDATE SET
|
||||
hourly_price = EXCLUDED.hourly_price,
|
||||
monthly_price = EXCLUDED.monthly_price,
|
||||
yearly_price = EXCLUDED.yearly_price,
|
||||
is_active = EXCLUDED.is_active,
|
||||
"updatedAt" = NOW();
|
||||
|
||||
-- Optional services from platform_settings JSON
|
||||
UPDATE addon_rates ar SET
|
||||
hourly_price = COALESCE((s.parsed->'redis'->>'hourly')::decimal, 0),
|
||||
monthly_price = COALESCE((s.parsed->'redis'->>'monthly')::decimal, 0),
|
||||
yearly_price = COALESCE((s.parsed->'redis'->>'yearly')::decimal, 0)
|
||||
FROM (
|
||||
SELECT value::jsonb AS parsed
|
||||
FROM platform_settings
|
||||
WHERE key = 'optional_services_pricing_toman'
|
||||
LIMIT 1
|
||||
) s
|
||||
WHERE ar.resource_type = 'redis_addon' AND s.parsed IS NOT NULL;
|
||||
|
||||
UPDATE addon_rates ar SET
|
||||
hourly_price = COALESCE((s.parsed->'rabbitmq'->>'hourly')::decimal, 0),
|
||||
monthly_price = COALESCE((s.parsed->'rabbitmq'->>'monthly')::decimal, 0),
|
||||
yearly_price = COALESCE((s.parsed->'rabbitmq'->>'yearly')::decimal, 0)
|
||||
FROM (
|
||||
SELECT value::jsonb AS parsed
|
||||
FROM platform_settings
|
||||
WHERE key = 'optional_services_pricing_toman'
|
||||
LIMIT 1
|
||||
) s
|
||||
WHERE ar.resource_type = 'rabbitmq_addon' AND s.parsed IS NOT NULL;
|
||||
|
||||
UPDATE addon_rates ar SET
|
||||
hourly_price = COALESCE((s.parsed->'elasticsearch'->>'hourly')::decimal, 0),
|
||||
monthly_price = COALESCE((s.parsed->'elasticsearch'->>'monthly')::decimal, 0),
|
||||
yearly_price = COALESCE((s.parsed->'elasticsearch'->>'yearly')::decimal, 0)
|
||||
FROM (
|
||||
SELECT value::jsonb AS parsed
|
||||
FROM platform_settings
|
||||
WHERE key = 'optional_services_pricing_toman'
|
||||
LIMIT 1
|
||||
) s
|
||||
WHERE ar.resource_type = 'elasticsearch_addon' AND s.parsed IS NOT NULL;
|
||||
|
||||
UPDATE addon_rates ar SET
|
||||
monthly_price = COALESCE(s.price, 0)
|
||||
FROM (
|
||||
SELECT value::decimal AS price
|
||||
FROM platform_settings
|
||||
WHERE key = 'custom_domain_monthly_price_toman'
|
||||
LIMIT 1
|
||||
) s
|
||||
WHERE ar.resource_type = 'custom_domain_addon' AND s.price IS NOT NULL;
|
||||
|
||||
-- Default runtime rows for nodejs, laravel, wordpress
|
||||
INSERT INTO pricing_rates (runtime, resource_type, hourly_price, monthly_price, yearly_price)
|
||||
SELECT r.runtime, t.resource_type, 0, 0, 0
|
||||
FROM (VALUES ('nodejs'), ('laravel'), ('wordpress')) AS r(runtime)
|
||||
CROSS JOIN (
|
||||
VALUES
|
||||
('base_fee'),
|
||||
('cpu_per_core'),
|
||||
('memory_per_gb'),
|
||||
('storage_per_gb'),
|
||||
('database_addon')
|
||||
) AS t(resource_type)
|
||||
ON CONFLICT (runtime, resource_type) DO NOTHING;
|
||||
@@ -0,0 +1,24 @@
|
||||
-- Ensure pricing_rates rows exist for every AppRuntime enum value
|
||||
|
||||
INSERT INTO pricing_rates (runtime, resource_type, hourly_price, monthly_price, yearly_price)
|
||||
SELECT r.runtime, t.resource_type, 0, 0, 0
|
||||
FROM (
|
||||
VALUES
|
||||
('nodejs'),
|
||||
('laravel'),
|
||||
('wordpress'),
|
||||
('go'),
|
||||
('php'),
|
||||
('python'),
|
||||
('django'),
|
||||
('dotnet')
|
||||
) AS r(runtime)
|
||||
CROSS JOIN (
|
||||
VALUES
|
||||
('base_fee'),
|
||||
('cpu_per_core'),
|
||||
('memory_per_gb'),
|
||||
('storage_per_gb'),
|
||||
('database_addon')
|
||||
) AS t(resource_type)
|
||||
ON CONFLICT (runtime, resource_type) DO NOTHING;
|
||||
@@ -0,0 +1,27 @@
|
||||
-- Per-addon consumption footprint and optional resource unit prices (0 = use app runtime rates)
|
||||
|
||||
ALTER TABLE addon_rates
|
||||
ADD COLUMN IF NOT EXISTS cpu_limit VARCHAR,
|
||||
ADD COLUMN IF NOT EXISTS memory_limit VARCHAR,
|
||||
ADD COLUMN IF NOT EXISTS storage_gi DECIMAL(10, 2) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS log_shipper_cpu_limit VARCHAR,
|
||||
ADD COLUMN IF NOT EXISTS log_shipper_memory_limit VARCHAR,
|
||||
ADD COLUMN IF NOT EXISTS cpu_hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS cpu_monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS cpu_yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS memory_hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS memory_monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS memory_yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS storage_hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS storage_monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS storage_yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0;
|
||||
|
||||
UPDATE addon_rates SET cpu_limit = '200m', memory_limit = '256Mi', storage_gi = 1
|
||||
WHERE resource_type = 'redis_addon' AND cpu_limit IS NULL;
|
||||
|
||||
UPDATE addon_rates SET cpu_limit = '500m', memory_limit = '512Mi', storage_gi = 2
|
||||
WHERE resource_type = 'rabbitmq_addon' AND cpu_limit IS NULL;
|
||||
|
||||
UPDATE addon_rates SET cpu_limit = '50m', memory_limit = '64Mi', storage_gi = 0,
|
||||
log_shipper_cpu_limit = '50m', log_shipper_memory_limit = '64Mi'
|
||||
WHERE resource_type = 'elasticsearch_addon' AND cpu_limit IS NULL;
|
||||
@@ -0,0 +1,98 @@
|
||||
-- Optional services: resource profile + pricing matrix (same model as application runtimes)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS optional_service_profiles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
service VARCHAR NOT NULL UNIQUE,
|
||||
cpu_limit VARCHAR NOT NULL DEFAULT '200m',
|
||||
memory_limit VARCHAR NOT NULL DEFAULT '256Mi',
|
||||
storage_gi DECIMAL(10, 2) NOT NULL DEFAULT 0,
|
||||
log_shipper_cpu_limit VARCHAR,
|
||||
log_shipper_memory_limit VARCHAR,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS optional_service_rates (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
service VARCHAR NOT NULL,
|
||||
resource_type VARCHAR NOT NULL,
|
||||
hourly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
monthly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
yearly_price DECIMAL(12, 2) NOT NULL DEFAULT 0,
|
||||
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (service, resource_type)
|
||||
);
|
||||
|
||||
INSERT INTO optional_service_profiles (service, cpu_limit, memory_limit, storage_gi, log_shipper_cpu_limit, log_shipper_memory_limit)
|
||||
VALUES
|
||||
('redis', '200m', '256Mi', 1, NULL, NULL),
|
||||
('rabbitmq', '500m', '512Mi', 2, NULL, NULL),
|
||||
('elasticsearch', '50m', '64Mi', 0, '50m', '64Mi')
|
||||
ON CONFLICT (service) DO NOTHING;
|
||||
|
||||
UPDATE optional_service_profiles SET
|
||||
log_shipper_cpu_limit = '50m',
|
||||
log_shipper_memory_limit = '64Mi'
|
||||
WHERE service = 'elasticsearch' AND log_shipper_cpu_limit IS NULL;
|
||||
|
||||
INSERT INTO optional_service_rates (service, resource_type, hourly_price, monthly_price, yearly_price)
|
||||
SELECT s.service, t.resource_type, 0, 0, 0
|
||||
FROM (VALUES ('redis'), ('rabbitmq'), ('elasticsearch')) AS s(service)
|
||||
CROSS JOIN (
|
||||
VALUES ('base_fee'), ('cpu_per_core'), ('memory_per_gb'), ('storage_per_gb')
|
||||
) AS t(resource_type)
|
||||
ON CONFLICT (service, resource_type) DO NOTHING;
|
||||
|
||||
-- Migrate legacy per-resource addon prices into optional_service_rates (if columns exist)
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'addon_rates' AND column_name = 'cpu_hourly_price'
|
||||
) THEN
|
||||
UPDATE optional_service_rates osr SET
|
||||
hourly_price = COALESCE(ar.cpu_hourly_price, 0),
|
||||
monthly_price = COALESCE(ar.cpu_monthly_price, 0),
|
||||
yearly_price = COALESCE(ar.cpu_yearly_price, 0)
|
||||
FROM addon_rates ar
|
||||
WHERE ar.resource_type = 'redis_addon' AND osr.service = 'redis' AND osr.resource_type = 'cpu_per_core'
|
||||
AND (ar.cpu_hourly_price > 0 OR ar.cpu_monthly_price > 0);
|
||||
|
||||
UPDATE optional_service_rates osr SET
|
||||
hourly_price = COALESCE(ar.memory_hourly_price, 0),
|
||||
monthly_price = COALESCE(ar.memory_monthly_price, 0),
|
||||
yearly_price = COALESCE(ar.memory_yearly_price, 0)
|
||||
FROM addon_rates ar
|
||||
WHERE ar.resource_type = 'redis_addon' AND osr.service = 'redis' AND osr.resource_type = 'memory_per_gb'
|
||||
AND (ar.memory_hourly_price > 0 OR ar.memory_monthly_price > 0);
|
||||
|
||||
UPDATE optional_service_rates osr SET
|
||||
hourly_price = COALESCE(ar.storage_hourly_price, 0),
|
||||
monthly_price = COALESCE(ar.storage_monthly_price, 0),
|
||||
yearly_price = COALESCE(ar.storage_yearly_price, 0)
|
||||
FROM addon_rates ar
|
||||
WHERE ar.resource_type = 'redis_addon' AND osr.service = 'redis' AND osr.resource_type = 'storage_per_gb'
|
||||
AND (ar.storage_hourly_price > 0 OR ar.storage_monthly_price > 0);
|
||||
|
||||
UPDATE optional_service_profiles osp SET
|
||||
cpu_limit = COALESCE(ar.cpu_limit, osp.cpu_limit),
|
||||
memory_limit = COALESCE(ar.memory_limit, osp.memory_limit),
|
||||
storage_gi = COALESCE(ar.storage_gi, osp.storage_gi)
|
||||
FROM addon_rates ar WHERE ar.resource_type = 'redis_addon' AND osp.service = 'redis';
|
||||
|
||||
UPDATE optional_service_profiles osp SET
|
||||
cpu_limit = COALESCE(ar.cpu_limit, osp.cpu_limit),
|
||||
memory_limit = COALESCE(ar.memory_limit, osp.memory_limit),
|
||||
storage_gi = COALESCE(ar.storage_gi, osp.storage_gi)
|
||||
FROM addon_rates ar WHERE ar.resource_type = 'rabbitmq_addon' AND osp.service = 'rabbitmq';
|
||||
|
||||
UPDATE optional_service_profiles osp SET
|
||||
cpu_limit = COALESCE(ar.cpu_limit, osp.cpu_limit),
|
||||
memory_limit = COALESCE(ar.memory_limit, osp.memory_limit),
|
||||
log_shipper_cpu_limit = COALESCE(ar.log_shipper_cpu_limit, osp.log_shipper_cpu_limit),
|
||||
log_shipper_memory_limit = COALESCE(ar.log_shipper_memory_limit, osp.log_shipper_memory_limit)
|
||||
FROM addon_rates ar WHERE ar.resource_type = 'elasticsearch_addon' AND osp.service = 'elasticsearch';
|
||||
END IF;
|
||||
END $$;
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
-- Per-deploy optional service resource limits (user-chosen, like application CPU/RAM/storage)
|
||||
|
||||
ALTER TABLE applications
|
||||
ADD COLUMN IF NOT EXISTS optional_service_resources JSONB;
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Deploy-wizard default requests (TypeORM camelCase column names)
|
||||
|
||||
ALTER TABLE optional_service_profiles
|
||||
ADD COLUMN IF NOT EXISTS "cpuRequest" VARCHAR,
|
||||
ADD COLUMN IF NOT EXISTS "memoryRequest" VARCHAR;
|
||||
|
||||
UPDATE optional_service_profiles SET "cpuRequest" = '50m', "memoryRequest" = '64Mi'
|
||||
WHERE service = 'redis' AND "cpuRequest" IS NULL;
|
||||
|
||||
UPDATE optional_service_profiles SET "cpuRequest" = '100m', "memoryRequest" = '256Mi'
|
||||
WHERE service = 'rabbitmq' AND "cpuRequest" IS NULL;
|
||||
|
||||
UPDATE optional_service_profiles SET "cpuRequest" = '50m', "memoryRequest" = '64Mi'
|
||||
WHERE service = 'elasticsearch' AND "cpuRequest" IS NULL;
|
||||
@@ -0,0 +1,52 @@
|
||||
CREATE TABLE IF NOT EXISTS invoices (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"invoiceNumber" VARCHAR NOT NULL UNIQUE,
|
||||
"userId" UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
"applicationId" UUID REFERENCES applications(id) ON DELETE SET NULL,
|
||||
reason VARCHAR NOT NULL DEFAULT 'manual',
|
||||
status VARCHAR NOT NULL DEFAULT 'issued',
|
||||
"paymentMethod" VARCHAR,
|
||||
subtotal DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||
total DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||
"paidAmount" DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||
"dueAmount" DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||
"dueDate" TIMESTAMPTZ,
|
||||
"paidAt" TIMESTAMPTZ,
|
||||
"gatewayTrackingCode" VARCHAR,
|
||||
"gatewayReference" VARCHAR,
|
||||
"adminNote" VARCHAR,
|
||||
"statusReason" VARCHAR,
|
||||
metadata JSONB,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS invoice_lines (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"invoiceId" UUID NOT NULL REFERENCES invoices(id) ON DELETE CASCADE,
|
||||
label VARCHAR NOT NULL,
|
||||
description VARCHAR,
|
||||
quantity INT NOT NULL DEFAULT 1,
|
||||
"unitAmount" DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||
amount DECIMAL(14, 2) NOT NULL DEFAULT 0,
|
||||
metadata JSONB,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
ALTER TABLE wallet_transactions
|
||||
ADD COLUMN IF NOT EXISTS "invoiceId" UUID REFERENCES invoices(id) ON DELETE SET NULL;
|
||||
|
||||
ALTER TABLE wallet_transactions
|
||||
ADD COLUMN IF NOT EXISTS "gatewayTrackingCode" VARCHAR;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_invoices_user_status_created
|
||||
ON invoices ("userId", status, "createdAt" DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_invoices_application
|
||||
ON invoices ("applicationId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_invoice_lines_invoice
|
||||
ON invoice_lines ("invoiceId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_wallet_transactions_invoice
|
||||
ON wallet_transactions ("invoiceId");
|
||||
@@ -0,0 +1,10 @@
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_type
|
||||
WHERE typname = 'deployments_status_enum'
|
||||
) THEN
|
||||
ALTER TYPE deployments_status_enum ADD VALUE IF NOT EXISTS 'cancelled';
|
||||
END IF;
|
||||
END $$;
|
||||
@@ -0,0 +1,113 @@
|
||||
-- Cluster pool allocation, health snapshots, and audit logs.
|
||||
|
||||
ALTER TABLE clusters
|
||||
ADD COLUMN IF NOT EXISTS weight INTEGER NOT NULL DEFAULT 1,
|
||||
ADD COLUMN IF NOT EXISTS tags JSONB NOT NULL DEFAULT '[]'::jsonb,
|
||||
ADD COLUMN IF NOT EXISTS "healthStatus" VARCHAR NOT NULL DEFAULT 'unknown',
|
||||
ADD COLUMN IF NOT EXISTS "lastHealthCheckedAt" TIMESTAMPTZ,
|
||||
ADD COLUMN IF NOT EXISTS "healthMessage" VARCHAR,
|
||||
ADD COLUMN IF NOT EXISTS "availableResources" JSONB;
|
||||
|
||||
ALTER TABLE cluster_pools
|
||||
ADD COLUMN IF NOT EXISTS "isDefault" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 100;
|
||||
|
||||
ALTER TABLE cluster_pools
|
||||
ALTER COLUMN strategy SET DEFAULT 'weighted-resource';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_clusters_status_health
|
||||
ON clusters(status, "healthStatus");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_clusters_tags
|
||||
ON clusters USING GIN(tags);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cluster_pools_default_priority
|
||||
ON cluster_pools("isDefault", priority)
|
||||
WHERE "isActive" = TRUE;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cluster_health (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"clusterId" UUID NOT NULL REFERENCES clusters(id) ON DELETE CASCADE,
|
||||
status VARCHAR NOT NULL DEFAULT 'unknown',
|
||||
"readyNodes" INTEGER NOT NULL DEFAULT 0,
|
||||
"nodeCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"cpuAllocatable" VARCHAR,
|
||||
"memoryAllocatable" VARCHAR,
|
||||
"podCount" INTEGER NOT NULL DEFAULT 0,
|
||||
"appCount" INTEGER NOT NULL DEFAULT 0,
|
||||
message VARCHAR,
|
||||
resources JSONB,
|
||||
"checkedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cluster_health_cluster_checked
|
||||
ON cluster_health("clusterId", "checkedAt" DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cluster_allocation_logs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"applicationId" UUID REFERENCES applications(id) ON DELETE SET NULL,
|
||||
"userId" UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
"poolId" UUID REFERENCES cluster_pools(id) ON DELETE SET NULL,
|
||||
"selectedClusterId" UUID REFERENCES clusters(id) ON DELETE SET NULL,
|
||||
strategy VARCHAR NOT NULL DEFAULT 'weighted-resource',
|
||||
"estimatedRequest" JSONB,
|
||||
"candidateScores" JSONB,
|
||||
"rejectionReasons" JSONB,
|
||||
status VARCHAR NOT NULL DEFAULT 'success',
|
||||
message VARCHAR,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cluster_allocation_logs_user_created
|
||||
ON cluster_allocation_logs("userId", "createdAt" DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cluster_allocation_logs_app
|
||||
ON cluster_allocation_logs("applicationId");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cluster_allocation_logs_cluster_created
|
||||
ON cluster_allocation_logs("selectedClusterId", "createdAt" DESC);
|
||||
|
||||
ALTER TABLE applications
|
||||
ADD COLUMN IF NOT EXISTS "clusterId" UUID,
|
||||
ADD COLUMN IF NOT EXISTS "poolId" UUID;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'applications' AND column_name = 'clusterId' AND data_type <> 'uuid'
|
||||
) THEN
|
||||
ALTER TABLE applications
|
||||
ALTER COLUMN "clusterId" TYPE UUID USING NULLIF("clusterId", '')::uuid;
|
||||
END IF;
|
||||
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'applications' AND column_name = 'poolId' AND data_type <> 'uuid'
|
||||
) THEN
|
||||
ALTER TABLE applications
|
||||
ALTER COLUMN "poolId" TYPE UUID USING NULLIF("poolId", '')::uuid;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'fk_applications_cluster'
|
||||
) THEN
|
||||
ALTER TABLE applications
|
||||
ADD CONSTRAINT fk_applications_cluster FOREIGN KEY ("clusterId")
|
||||
REFERENCES clusters(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'fk_applications_pool'
|
||||
) THEN
|
||||
ALTER TABLE applications
|
||||
ADD CONSTRAINT fk_applications_pool FOREIGN KEY ("poolId")
|
||||
REFERENCES cluster_pools(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_applications_cluster_id ON applications("clusterId");
|
||||
CREATE INDEX IF NOT EXISTS idx_applications_pool_id ON applications("poolId");
|
||||
@@ -0,0 +1,36 @@
|
||||
CREATE TABLE IF NOT EXISTS application_migration_jobs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"applicationId" UUID NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
|
||||
"requestedBy" UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
"sourceClusterId" UUID NOT NULL REFERENCES clusters(id) ON DELETE RESTRICT,
|
||||
"targetClusterId" UUID NOT NULL REFERENCES clusters(id) ON DELETE RESTRICT,
|
||||
status VARCHAR NOT NULL DEFAULT 'queued',
|
||||
attempts INTEGER NOT NULL DEFAULT 0,
|
||||
"maxAttempts" INTEGER NOT NULL DEFAULT 3,
|
||||
"currentStep" VARCHAR,
|
||||
"errorMessage" VARCHAR,
|
||||
metadata JSONB,
|
||||
"startedAt" TIMESTAMPTZ,
|
||||
"completedAt" TIMESTAMPTZ,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_application_migration_jobs_app_created
|
||||
ON application_migration_jobs("applicationId", "createdAt" DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_application_migration_jobs_status
|
||||
ON application_migration_jobs(status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS application_migration_events (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"migrationId" UUID NOT NULL REFERENCES application_migration_jobs(id) ON DELETE CASCADE,
|
||||
step VARCHAR NOT NULL,
|
||||
level VARCHAR NOT NULL DEFAULT 'info',
|
||||
message VARCHAR NOT NULL,
|
||||
metadata JSONB,
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_application_migration_events_migration_created
|
||||
ON application_migration_events("migrationId", "createdAt" ASC);
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Standalone managed services (database / redis / rabbitmq) vs full applications
|
||||
ALTER TABLE applications
|
||||
ADD COLUMN IF NOT EXISTS product_type VARCHAR(32) NOT NULL DEFAULT 'application';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_applications_user_product_type
|
||||
ON applications (user_id, product_type);
|
||||
|
||||
ALTER TABLE resource_credits
|
||||
ADD COLUMN IF NOT EXISTS product_type VARCHAR(32) NOT NULL DEFAULT 'application';
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE service_access_grants
|
||||
ADD COLUMN IF NOT EXISTS persistent BOOLEAN NOT NULL DEFAULT false;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Snapshot creation progress (0–100) for UI feedback during DB dumps
|
||||
ALTER TABLE snapshots ADD COLUMN IF NOT EXISTS progress INT NOT NULL DEFAULT 0;
|
||||
Reference in New Issue
Block a user