From 9c5dd1f821166f53e3b4ff449a292b83ad5de1e7 Mon Sep 17 00:00:00 2001 From: mhbdev Date: Sun, 26 Jul 2026 03:24:16 +0330 Subject: [PATCH] release: v0.1.151 - secure cron config and backup fixes --- CHANGELOG.md | 8 + .../content/docs/features/upstand-json.mdx | 10 +- apps/fumadocs/public/upstand.schema.json | 9 +- apps/server/.env.example | 2 +- apps/web/public/upstand.schema.json | 9 +- docker-compose.local.yml | 4 +- package.json | 2 +- packages/api/src/di/backups.ts | 11 +- .../api/src/routers/certificate.router.ts | 11 +- .../0065_secure_upstand_cron_config.sql | 2 + .../db/src/migrations/meta/0065_snapshot.json | 6524 +++++++++++++++++ packages/db/src/migrations/meta/_journal.json | 7 + packages/db/src/schema/schedule.ts | 2 + packages/domain/src/entities/schedule.ts | 20 + .../src/entities/upstand-config.test.ts | 40 + .../domain/src/entities/upstand-config.ts | 153 +- .../src/backup/backup-runtime.service.ts | 52 +- .../usecases/src/backup/backup-storage.ts | 15 +- .../backup/delete-backup-schedule.usecase.ts | 15 +- .../src/backup/execute-backup-run.usecase.ts | 10 +- .../src/backup/restore-backup-run.usecase.ts | 10 +- .../src/backup/verify-backup-run.usecase.ts | 16 +- .../src/deployment/deployment-worker.ts | 3 - .../src/schedule/general-scheduler.ts | 4 +- .../src/schedule/schedule.usecases.test.ts | 4 + .../src/schedule/schedule.usecases.ts | 2 + .../sync-upstand-config.usecase.test.ts | 4 + .../schedule/sync-upstand-config.usecase.ts | 8 + 28 files changed, 6861 insertions(+), 96 deletions(-) create mode 100644 packages/db/src/migrations/0065_secure_upstand_cron_config.sql create mode 100644 packages/db/src/migrations/meta/0065_snapshot.json diff --git a/CHANGELOG.md b/CHANGELOG.md index ab21a5f2..1fd4ad30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to Upstand are recorded here. Release tags use semantic vers ## Unreleased +## 0.1.151 - 2026-07-26 + +### Fixed & Enhanced + +- Propagated configured CA certificates through backup create, restore, verify, and deletion runtimes for S3-compatible destinations. +- Prevented deletion of certificates that are still assigned to S3 backup destinations. +- Centralized backup destination certificate enrichment and standardized the local development encryption-key example. + ## 0.1.150 - 2026-07-26 ### Added & Enhanced diff --git a/apps/fumadocs/content/docs/features/upstand-json.mdx b/apps/fumadocs/content/docs/features/upstand-json.mdx index 1e679032..1e8a8067 100644 --- a/apps/fumadocs/content/docs/features/upstand-json.mdx +++ b/apps/fumadocs/content/docs/features/upstand-json.mdx @@ -101,6 +101,12 @@ Define container resource limits, entrypoint commands, replica scaling, and rest ### C. Scheduled Cron Jobs (`crons`) +HTTP cron authentication is intentionally indirect: set the named variable in +the resource's encrypted Environment configuration, then reference only its +name with `secretEnvVar`. Upstand never accepts or stores a bearer-token value +from `upstand.json`; a missing or empty referenced variable causes the run to +fail instead of sending an unauthenticated request. + Declare recurring HTTP route cron jobs or in-container script schedules. | Property | Type | Default | Description | @@ -108,7 +114,7 @@ Declare recurring HTTP route cron jobs or in-container script schedules. | `schedule` | `string` | **(Required)** | Cron expression schedule (e.g. `0 0 * * *` for daily at midnight). | | `path` | `string` | — | HTTP route endpoint path for HTTP cron jobs (must start with `/`). | | `method` | `string` | `"GET"` | HTTP method (`GET` or `POST`). | -| `secret` | `string` | — | Authorization token sent as `Authorization: Bearer `. | +| `secretEnvVar` | `string` | — | Name of the encrypted resource environment variable containing the bearer token. Never put the secret value in `upstand.json`. | | `command` | `string` | — | In-container script command to execute. | | `shellType` | `string` | `"bash"` | Shell type (`"bash"` or `"sh"`). | | `timezone` | `string` | `"UTC"` | IANA timezone identifier (e.g. `"UTC"`, `"America/New_York"`). | @@ -120,6 +126,8 @@ Declare recurring HTTP route cron jobs or in-container script schedules. { "name": "Daily Cleanup", "path": "/api/cron/cleanup", + "method": "POST", + "secretEnvVar": "CRON_SECRET", "schedule": "0 0 * * *" }, { diff --git a/apps/fumadocs/public/upstand.schema.json b/apps/fumadocs/public/upstand.schema.json index d1ddc97c..78d8e04b 100644 --- a/apps/fumadocs/public/upstand.schema.json +++ b/apps/fumadocs/public/upstand.schema.json @@ -28,21 +28,25 @@ }, "buildPath": { "type": "string", + "pattern": "^(?![\\\\/])(?![A-Za-z]:[\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+", "default": ".", "description": "Relative directory path where build commands should execute." }, "dockerfilePath": { "type": "string", + "pattern": "^(?![\\\\/])(?![A-Za-z]:[\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+", "default": "Dockerfile", "description": "Relative path to the Dockerfile." }, "dockerContextPath": { "type": "string", + "pattern": "^(?![\\\\/])(?![A-Za-z]:[\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+", "default": ".", "description": "Relative path to the Docker build context." }, "publishDirectory": { "type": "string", + "pattern": "^(?![\\\\/])(?![A-Za-z]:[\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+", "description": "Relative directory where static artifacts are published." }, "dockerBuildStage": { @@ -109,9 +113,10 @@ "default": "GET", "description": "HTTP method for HTTP cron requests." }, - "secret": { + "secretEnvVar": { "type": "string", - "description": "Authorization secret token for HTTP cron requests." + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Name of the encrypted resource environment variable containing the HTTP Authorization bearer token." }, "command": { "type": "string", diff --git a/apps/server/.env.example b/apps/server/.env.example index 5c354124..b5e399e9 100644 --- a/apps/server/.env.example +++ b/apps/server/.env.example @@ -6,5 +6,5 @@ CORS_ORIGIN=http://localhost:3001 REDIS_URL=redis://localhost:6379 NODE_ENV=development IS_CLOUD=false -ENCRYPTION_KEY_V1=dev-encryption-key-32-chars-long +ENCRYPTION_KEY_V1=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI= UPSTAND_INSTANCE_OWNER_USER_ID= diff --git a/apps/web/public/upstand.schema.json b/apps/web/public/upstand.schema.json index d1ddc97c..78d8e04b 100644 --- a/apps/web/public/upstand.schema.json +++ b/apps/web/public/upstand.schema.json @@ -28,21 +28,25 @@ }, "buildPath": { "type": "string", + "pattern": "^(?![\\\\/])(?![A-Za-z]:[\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+", "default": ".", "description": "Relative directory path where build commands should execute." }, "dockerfilePath": { "type": "string", + "pattern": "^(?![\\\\/])(?![A-Za-z]:[\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+", "default": "Dockerfile", "description": "Relative path to the Dockerfile." }, "dockerContextPath": { "type": "string", + "pattern": "^(?![\\\\/])(?![A-Za-z]:[\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+", "default": ".", "description": "Relative path to the Docker build context." }, "publishDirectory": { "type": "string", + "pattern": "^(?![\\\\/])(?![A-Za-z]:[\\\\/])(?!.*(?:^|[\\\\/])\\.\\.(?:[\\\\/]|$)).+", "description": "Relative directory where static artifacts are published." }, "dockerBuildStage": { @@ -109,9 +113,10 @@ "default": "GET", "description": "HTTP method for HTTP cron requests." }, - "secret": { + "secretEnvVar": { "type": "string", - "description": "Authorization secret token for HTTP cron requests." + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$", + "description": "Name of the encrypted resource environment variable containing the HTTP Authorization bearer token." }, "command": { "type": "string", diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 24a71889..bdfa3f19 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -48,7 +48,7 @@ services: CORS_ORIGIN: http://localhost:3001 REDIS_URL: redis://redis:6379 DOCKER_NETWORK: upstand-network - ENCRYPTION_KEY_V1: ${ENCRYPTION_KEY_V1:-dev-encryption-key-32-chars-long} + ENCRYPTION_KEY_V1: ${ENCRYPTION_KEY_V1:-MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=} depends_on: postgres: condition: service_healthy @@ -86,7 +86,7 @@ services: BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:-upstand-local-development-secret-that-is-at-least-32-characters} REDIS_URL: redis://redis:6379 SCHEDULES_PORT: 3002 - ENCRYPTION_KEY_V1: ${ENCRYPTION_KEY_V1:-dev-encryption-key-32-chars-long} + ENCRYPTION_KEY_V1: ${ENCRYPTION_KEY_V1:-MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=} depends_on: postgres: condition: service_healthy diff --git a/package.json b/package.json index d7891b38..d15dba17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "upstand", - "version": "0.1.150", + "version": "0.1.151", "private": true, "workspaces": { "packages": [ diff --git a/packages/api/src/di/backups.ts b/packages/api/src/di/backups.ts index c7979721..ff46d925 100644 --- a/packages/api/src/di/backups.ts +++ b/packages/api/src/di/backups.ts @@ -184,7 +184,14 @@ export function registerBackups( const envs = parseResourceEnvironmentVariables( resourceSecret.envVars, ); - cronSecret = envs.CRON_SECRET || ""; + cronSecret = schedule.secretEnvVar + ? envs[schedule.secretEnvVar] || "" + : envs.CRON_SECRET || ""; + } + if (schedule.secretEnvVar && !cronSecret) { + throw new Error( + `Configured cron secret environment variable '${schedule.secretEnvVar}' is missing or empty`, + ); } const path = schedule.command.startsWith("/") @@ -203,7 +210,7 @@ export function registerBackups( const timeoutId = setTimeout(() => controller.abort(), 30_000); const response = await fetch(targetUrl, { - method: "GET", + method: schedule.httpMethod || "GET", headers, signal: controller.signal, }); diff --git a/packages/api/src/routers/certificate.router.ts b/packages/api/src/routers/certificate.router.ts index e5e69fbf..ad122c0b 100644 --- a/packages/api/src/routers/certificate.router.ts +++ b/packages/api/src/routers/certificate.router.ts @@ -157,16 +157,23 @@ export const certificateRouter = router({ return false; } }); + const s3Destinations = + await uow.s3DestinationRepository.findByOrganizationId( + existing.organizationId, + ); + const isS3DestinationInUse = s3Destinations.some( + (destination) => destination.certificateId === existing.id, + ); const settings = await uow.webServerSettingsRepository.findGlobal(); const isServerDomainInUse = settings?.certificateProvider === "custom" && settings?.certificateId === existing.id; - if (isInUse || isServerDomainInUse) { + if (isInUse || isServerDomainInUse || isS3DestinationInUse) { throw new TRPCError({ code: "CONFLICT", message: - "This certificate is currently assigned to a resource domain or server domain. Remove that assignment before deleting it.", + "This certificate is currently assigned to a resource domain, server domain, or S3 backup destination. Remove that assignment before deleting it.", }); } const result = await ctx.scope diff --git a/packages/db/src/migrations/0065_secure_upstand_cron_config.sql b/packages/db/src/migrations/0065_secure_upstand_cron_config.sql new file mode 100644 index 00000000..d4fa84ed --- /dev/null +++ b/packages/db/src/migrations/0065_secure_upstand_cron_config.sql @@ -0,0 +1,2 @@ +ALTER TABLE "schedule" ADD COLUMN "http_method" text;--> statement-breakpoint +ALTER TABLE "schedule" ADD COLUMN "secret_env_var" text; \ No newline at end of file diff --git a/packages/db/src/migrations/meta/0065_snapshot.json b/packages/db/src/migrations/meta/0065_snapshot.json new file mode 100644 index 00000000..25751ab4 --- /dev/null +++ b/packages/db/src/migrations/meta/0065_snapshot.json @@ -0,0 +1,6524 @@ +{ + "id": "057344b9-45f8-469c-a51a-cac4d094f51c", + "prevId": "ef6e434e-f9a8-4af9-bd9c-ee288a061dad", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.ai_approval": { + "name": "ai_approval", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "input": { + "name": "input", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_approval_tool_call_uidx": { + "name": "ai_approval_tool_call_uidx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_approval_org_idx": { + "name": "ai_approval_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_approval_run_id_ai_run_id_fk": { + "name": "ai_approval_run_id_ai_run_id_fk", + "tableFrom": "ai_approval", + "tableTo": "ai_run", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_approval_conversation_id_ai_conversation_id_fk": { + "name": "ai_approval_conversation_id_ai_conversation_id_fk", + "tableFrom": "ai_approval", + "tableTo": "ai_conversation", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_approval_organization_id_organization_id_fk": { + "name": "ai_approval_organization_id_organization_id_fk", + "tableFrom": "ai_approval", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_approval_decided_by_user_id_fk": { + "name": "ai_approval_decided_by_user_id_fk", + "tableFrom": "ai_approval", + "tableTo": "user", + "columnsFrom": ["decided_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_conversation": { + "name": "ai_conversation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'New UpGal conversation'" + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_conversation_org_idx": { + "name": "ai_conversation_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_conversation_org_user_idx": { + "name": "ai_conversation_org_user_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_conversation_organization_id_organization_id_fk": { + "name": "ai_conversation_organization_id_organization_id_fk", + "tableFrom": "ai_conversation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_conversation_user_id_user_id_fk": { + "name": "ai_conversation_user_id_user_id_fk", + "tableFrom": "ai_conversation", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_feature_assignment": { + "name": "ai_feature_assignment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_config_id": { + "name": "provider_config_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_feature_assignment_org_feature_uidx": { + "name": "ai_feature_assignment_org_feature_uidx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "feature", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ai_feature_assignment_org_idx": { + "name": "ai_feature_assignment_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_feature_assignment_organization_id_organization_id_fk": { + "name": "ai_feature_assignment_organization_id_organization_id_fk", + "tableFrom": "ai_feature_assignment", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_feature_assignment_provider_config_id_ai_provider_config_id_fk": { + "name": "ai_feature_assignment_provider_config_id_ai_provider_config_id_fk", + "tableFrom": "ai_feature_assignment", + "tableTo": "ai_provider_config", + "columnsFrom": ["provider_config_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_message": { + "name": "ai_message", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parts": { + "name": "parts", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_message_conversation_idx": { + "name": "ai_message_conversation_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_message_conversation_id_ai_conversation_id_fk": { + "name": "ai_message_conversation_id_ai_conversation_id_fk", + "tableFrom": "ai_message", + "tableTo": "ai_conversation", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_provider_config": { + "name": "ai_provider_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'Default'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "base_url": { + "name": "base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "temperature": { + "name": "temperature", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "reasoning_enabled": { + "name": "reasoning_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_output_tokens": { + "name": "max_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_key_ciphertext": { + "name": "api_key_ciphertext", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key_iv": { + "name": "api_key_iv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key_auth_tag": { + "name": "api_key_auth_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key_version": { + "name": "api_key_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "ai_provider_config_org_idx": { + "name": "ai_provider_config_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_provider_config_organization_id_organization_id_fk": { + "name": "ai_provider_config_organization_id_organization_id_fk", + "tableFrom": "ai_provider_config", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_run": { + "name": "ai_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "step_count": { + "name": "step_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ai_run_conversation_idx": { + "name": "ai_run_conversation_idx", + "columns": [ + { + "expression": "conversation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ai_run_conversation_id_ai_conversation_id_fk": { + "name": "ai_run_conversation_id_ai_conversation_id_fk", + "tableFrom": "ai_run", + "tableTo": "ai_conversation", + "columnsFrom": ["conversation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_run_organization_id_organization_id_fk": { + "name": "ai_run_organization_id_organization_id_fk", + "tableFrom": "ai_run", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "ai_run_user_id_user_id_fk": { + "name": "ai_run_user_id_user_id_fk", + "tableFrom": "ai_run", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ai_tavily_settings": { + "name": "ai_tavily_settings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "api_key_ciphertext": { + "name": "api_key_ciphertext", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key_iv": { + "name": "api_key_iv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key_auth_tag": { + "name": "api_key_auth_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_key_version": { + "name": "api_key_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "search_depth": { + "name": "search_depth", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'basic'" + }, + "include_answer": { + "name": "include_answer", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_results": { + "name": "max_results", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "enable_search": { + "name": "enable_search", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "enable_extract": { + "name": "enable_extract", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "enable_crawl": { + "name": "enable_crawl", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "enable_map": { + "name": "enable_map", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ai_tavily_settings_organization_id_organization_id_fk": { + "name": "ai_tavily_settings_organization_id_organization_id_fk", + "tableFrom": "ai_tavily_settings", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_role": { + "name": "actor_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "route": { + "name": "route", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_org_created_idx": { + "name": "audit_log_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_org_action_idx": { + "name": "audit_log_org_action_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_org_resource_idx": { + "name": "audit_log_org_resource_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_idx": { + "name": "audit_log_actor_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_organization_id_organization_id_fk": { + "name": "audit_log_organization_id_organization_id_fk", + "tableFrom": "audit_log", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.apikey": { + "name": "apikey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start": { + "name": "start", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refill_interval": { + "name": "refill_interval", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refill_amount": { + "name": "refill_amount", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_enabled": { + "name": "rate_limit_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "rate_limit_time_window": { + "name": "rate_limit_time_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3600000 + }, + "rate_limit_max": { + "name": "rate_limit_max", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1000 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_request": { + "name": "last_request", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "apikey_configId_idx": { + "name": "apikey_configId_idx", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikey_referenceId_idx": { + "name": "apikey_referenceId_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "apikey_key_idx": { + "name": "apikey_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_channel_id": { + "name": "email_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "invitation_organizationId_idx": { + "name": "invitation_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scim_active": { + "name": "scim_active", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "scim_external_id": { + "name": "scim_external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scim_display_name": { + "name": "scim_display_name", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "member_organizationId_idx": { + "name": "member_organizationId_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_userId_idx": { + "name": "member_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "organization_slug_uidx": { + "name": "organization_slug_uidx", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organization_slug_unique": { + "name": "organization_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain_verified": { + "name": "domain_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "sso_provider_provider_id_unique": { + "name": "sso_provider_provider_id_unique", + "nullsNotDistinct": false, + "columns": ["provider_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.two_factor": { + "name": "two_factor", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "failed_verification_count": { + "name": "failed_verification_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "locked_until": { + "name": "locked_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "twoFactor_secret_idx": { + "name": "twoFactor_secret_idx", + "columns": [ + { + "expression": "secret", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "twoFactor_userId_idx": { + "name": "twoFactor_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "managed": { + "name": "managed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backup_run": { + "name": "backup_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destination_id": { + "name": "destination_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "file_key": { + "name": "file_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "verification_status": { + "name": "verification_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "restore_tested_at": { + "name": "restore_tested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "recovery_point": { + "name": "recovery_point", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "backup_run_schedule_created_idx": { + "name": "backup_run_schedule_created_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backup_run_resource_created_idx": { + "name": "backup_run_resource_created_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backup_run_status_idx": { + "name": "backup_run_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backup_run_organization_idx": { + "name": "backup_run_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backup_run_destination_idx": { + "name": "backup_run_destination_idx", + "columns": [ + { + "expression": "destination_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "backup_run_schedule_id_backup_schedule_id_fk": { + "name": "backup_run_schedule_id_backup_schedule_id_fk", + "tableFrom": "backup_run", + "tableTo": "backup_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_run_resource_id_resource_id_fk": { + "name": "backup_run_resource_id_resource_id_fk", + "tableFrom": "backup_run", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_run_destination_id_s3_destination_id_fk": { + "name": "backup_run_destination_id_s3_destination_id_fk", + "tableFrom": "backup_run", + "tableTo": "s3_destination", + "columnsFrom": ["destination_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.backup_schedule": { + "name": "backup_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "destination_id": { + "name": "destination_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "retention_count": { + "name": "retention_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "database_name": { + "name": "database_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "database_engine": { + "name": "database_engine", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "volume_name": { + "name": "volume_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stop_service": { + "name": "stop_service", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "encrypted_configuration": { + "name": "encrypted_configuration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "point_in_time_recovery": { + "name": "point_in_time_recovery", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "restore_verification": { + "name": "restore_verification", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "replica_count": { + "name": "replica_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failover_enabled": { + "name": "failover_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "migration_command": { + "name": "migration_command", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "backup_schedule_resource_idx": { + "name": "backup_schedule_resource_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backup_schedule_enabled_idx": { + "name": "backup_schedule_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backup_schedule_organization_idx": { + "name": "backup_schedule_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "backup_schedule_destination_idx": { + "name": "backup_schedule_destination_idx", + "columns": [ + { + "expression": "destination_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "backup_schedule_resource_id_resource_id_fk": { + "name": "backup_schedule_resource_id_resource_id_fk", + "tableFrom": "backup_schedule", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "backup_schedule_destination_id_s3_destination_id_fk": { + "name": "backup_schedule_destination_id_s3_destination_id_fk", + "tableFrom": "backup_schedule", + "tableTo": "s3_destination", + "columnsFrom": ["destination_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.certificate": { + "name": "certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificate_pem": { + "name": "certificate_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_pem": { + "name": "private_key_pem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "certificate_organization_idx": { + "name": "certificate_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "certificate_organization_id_organization_id_fk": { + "name": "certificate_organization_id_organization_id_fk", + "tableFrom": "certificate", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_role": { + "name": "custom_role", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_role_organization_name_idx": { + "name": "custom_role_organization_name_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_role_organization_idx": { + "name": "custom_role_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_role_organization_id_organization_id_fk": { + "name": "custom_role_organization_id_organization_id_fk", + "tableFrom": "custom_role", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment": { + "name": "deployment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logs": { + "name": "logs", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_name": { + "name": "server_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_revision": { + "name": "source_revision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_token": { + "name": "execution_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "deployment_created_idx": { + "name": "deployment_created_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "deployment_resource_created_idx": { + "name": "deployment_resource_created_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "deployment_status_idx": { + "name": "deployment_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "deployment_server_status_idx": { + "name": "deployment_server_status_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "deployment_execution_lease_idx": { + "name": "deployment_execution_lease_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_resource_id_resource_id_fk": { + "name": "deployment_resource_id_resource_id_fk", + "tableFrom": "deployment", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docker_registry": { + "name": "docker_registry", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "image_prefix": { + "name": "image_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registry_url": { + "name": "registry_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docker_registry_organization_idx": { + "name": "docker_registry_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docker_registry_server_idx": { + "name": "docker_registry_server_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "docker_registry_organization_id_organization_id_fk": { + "name": "docker_registry_organization_id_organization_id_fk", + "tableFrom": "docker_registry", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_environment_id": { + "name": "parent_environment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inherits_variables": { + "name": "inherits_variables", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_protected": { + "name": "is_protected", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "resource_count": { + "name": "resource_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "environment_project_idx": { + "name": "environment_project_idx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_parent_idx": { + "name": "environment_parent_idx", + "columns": [ + { + "expression": "parent_environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "environment_project_slug_uidx": { + "name": "environment_project_slug_uidx", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "environment_project_id_project_id_fk": { + "name": "environment_project_id_project_id_fk", + "tableFrom": "environment", + "tableTo": "project", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.environment_secret": { + "name": "environment_secret", + "schema": "", + "columns": { + "environment_id": { + "name": "environment_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "env_vars": { + "name": "env_vars", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_secret_environment_id_environment_id_fk": { + "name": "environment_secret_environment_id_environment_id_fk", + "tableFrom": "environment_secret", + "tableTo": "environment", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.git_provider": { + "name": "git_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "git_provider_organization_idx": { + "name": "git_provider_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "git_provider_organization_id_organization_id_fk": { + "name": "git_provider_organization_id_organization_id_fk", + "tableFrom": "git_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.monitoring_settings": { + "name": "monitoring_settings", + "schema": "", + "columns": { + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cpu_threshold": { + "name": "cpu_threshold", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "memory_threshold": { + "name": "memory_threshold", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 90 + }, + "alert_email": { + "name": "alert_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_channel": { + "name": "notification_channel", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "events": { + "name": "events", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "encrypted_configuration": { + "name": "encrypted_configuration", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "configuration_summary": { + "name": "configuration_summary", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "notification_channel_organization_idx": { + "name": "notification_channel_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_channel_events_idx": { + "name": "notification_channel_events_idx", + "columns": [ + { + "expression": "events", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "notification_channel_organization_id_organization_id_fk": { + "name": "notification_channel_organization_id_organization_id_fk", + "tableFrom": "notification_channel", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_delivery": { + "name": "notification_delivery", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "notification_delivery_channel_idx": { + "name": "notification_delivery_channel_idx", + "columns": [ + { + "expression": "channel_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_delivery_organization_created_idx": { + "name": "notification_delivery_organization_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_delivery_status_idx": { + "name": "notification_delivery_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "notification_delivery_idempotency_uidx": { + "name": "notification_delivery_idempotency_uidx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_delivery_channel_id_notification_channel_id_fk": { + "name": "notification_delivery_channel_id_notification_channel_id_fk", + "tableFrom": "notification_delivery", + "tableTo": "notification_channel", + "columnsFrom": ["channel_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_delivery_organization_id_organization_id_fk": { + "name": "notification_delivery_organization_id_organization_id_fk", + "tableFrom": "notification_delivery", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox": { + "name": "outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "dead_lettered_at": { + "name": "dead_lettered_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "outbox_idempotency_uidx": { + "name": "outbox_idempotency_uidx", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_publication_idx": { + "name": "outbox_publication_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_aggregate_idx": { + "name": "outbox_aggregate_idx", + "columns": [ + { + "expression": "aggregate_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "aggregate_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_organization_idx": { + "name": "outbox_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.preview_deployment": { + "name": "preview_deployment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pull_request_id": { + "name": "pull_request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_name": { + "name": "app_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "preview_deployment_resource_idx": { + "name": "preview_deployment_resource_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "preview_deployment_status_idx": { + "name": "preview_deployment_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "preview_deployment_resource_id_resource_id_fk": { + "name": "preview_deployment_resource_id_resource_id_fk", + "tableFrom": "preview_deployment", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "preview_deployment_app_name_unique": { + "name": "preview_deployment_app_name_unique", + "nullsNotDistinct": false, + "columns": ["app_name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_organization_idx": { + "name": "project_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_organization_id_organization_id_fk": { + "name": "project_organization_id_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resource": { + "name": "resource", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "environment_id": { + "name": "environment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_name": { + "name": "app_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "db_type": { + "name": "db_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "compose_type": { + "name": "compose_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "docker_image": { + "name": "docker_image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_registry_id": { + "name": "build_registry_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rollback_active": { + "name": "rollback_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "rollback_registry_id": { + "name": "rollback_registry_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_port": { + "name": "external_port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "libsql_grpc_port": { + "name": "libsql_grpc_port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "libsql_admin_port": { + "name": "libsql_admin_port", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'push'" + }, + "tag_pattern": { + "name": "tag_pattern", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "webhook_token_hash": { + "name": "webhook_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "webhook_token_prefix": { + "name": "webhook_token_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_server_id": { + "name": "build_server_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_preview_deployments_active": { + "name": "is_preview_deployments_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "preview_limit": { + "name": "preview_limit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "preview_wildcard": { + "name": "preview_wildcard", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_https": { + "name": "preview_https", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "preview_port": { + "name": "preview_port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3000 + }, + "cron_jobs_enabled": { + "name": "cron_jobs_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_environment_idx": { + "name": "resource_environment_idx", + "columns": [ + { + "expression": "environment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_server_idx": { + "name": "resource_server_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_build_server_idx": { + "name": "resource_build_server_idx", + "columns": [ + { + "expression": "build_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_build_registry_idx": { + "name": "resource_build_registry_idx", + "columns": [ + { + "expression": "build_registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_rollback_registry_idx": { + "name": "resource_rollback_registry_idx", + "columns": [ + { + "expression": "rollback_registry_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_app_name_idx": { + "name": "resource_app_name_idx", + "columns": [ + { + "expression": "app_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_environment_id_environment_id_fk": { + "name": "resource_environment_id_environment_id_fk", + "tableFrom": "resource", + "tableTo": "environment", + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resource_build_registry_id_docker_registry_id_fk": { + "name": "resource_build_registry_id_docker_registry_id_fk", + "tableFrom": "resource", + "tableTo": "docker_registry", + "columnsFrom": ["build_registry_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "resource_rollback_registry_id_docker_registry_id_fk": { + "name": "resource_rollback_registry_id_docker_registry_id_fk", + "tableFrom": "resource", + "tableTo": "docker_registry", + "columnsFrom": ["rollback_registry_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "resource_build_server_id_server_id_fk": { + "name": "resource_build_server_id_server_id_fk", + "tableFrom": "resource", + "tableTo": "server", + "columnsFrom": ["build_server_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "resource_webhook_token_hash_unique": { + "name": "resource_webhook_token_hash_unique", + "nullsNotDistinct": false, + "columns": ["webhook_token_hash"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resource_configuration": { + "name": "resource_configuration", + "schema": "", + "columns": { + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "build_config": { + "name": "build_config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "advanced_config": { + "name": "advanced_config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "watch_paths": { + "name": "watch_paths", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domains": { + "name": "domains", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "resource_configuration_resource_id_resource_id_fk": { + "name": "resource_configuration_resource_id_resource_id_fk", + "tableFrom": "resource_configuration", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resource_runtime": { + "name": "resource_runtime", + "schema": "", + "columns": { + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "containers": { + "name": "containers", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'docker'" + } + }, + "indexes": {}, + "foreignKeys": { + "resource_runtime_resource_id_resource_id_fk": { + "name": "resource_runtime_resource_id_resource_id_fk", + "tableFrom": "resource_runtime", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resource_secret": { + "name": "resource_secret", + "schema": "", + "columns": { + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_secrets": { + "name": "build_secrets", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_vars": { + "name": "env_vars", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "resource_secret_resource_id_resource_id_fk": { + "name": "resource_secret_resource_id_resource_id_fk", + "tableFrom": "resource_secret", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.s3_destination": { + "name": "s3_destination", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_key_id": { + "name": "access_key_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_access_key": { + "name": "secret_access_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bucket": { + "name": "bucket", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "certificate_id": { + "name": "certificate_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "additional_flags": { + "name": "additional_flags", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "s3_destination_organization_idx": { + "name": "s3_destination_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "s3_destination_organization_id_organization_id_fk": { + "name": "s3_destination_organization_id_organization_id_fk", + "tableFrom": "s3_destination", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "s3_destination_certificate_id_certificate_id_fk": { + "name": "s3_destination_certificate_id_certificate_id_fk", + "tableFrom": "s3_destination", + "tableTo": "certificate", + "columnsFrom": ["certificate_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedule": { + "name": "schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "http_method": { + "name": "http_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_env_var": { + "name": "secret_env_var", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "job_type": { + "name": "job_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'command'" + }, + "service_name": { + "name": "service_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shell_type": { + "name": "shell_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'bash'" + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "backup_schedule_id": { + "name": "backup_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_run_status": { + "name": "last_run_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "schedule_resource_idx": { + "name": "schedule_resource_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "schedule_enabled_idx": { + "name": "schedule_enabled_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "schedule_backup_schedule_idx": { + "name": "schedule_backup_schedule_idx", + "columns": [ + { + "expression": "backup_schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "schedule_resource_id_resource_id_fk": { + "name": "schedule_resource_id_resource_id_fk", + "tableFrom": "schedule", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_backup_schedule_id_backup_schedule_id_fk": { + "name": "schedule_backup_schedule_id_backup_schedule_id_fk", + "tableFrom": "schedule", + "tableTo": "backup_schedule", + "columnsFrom": ["backup_schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schedule_log": { + "name": "schedule_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "executed_at": { + "name": "executed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "schedule_log_schedule_idx": { + "name": "schedule_log_schedule_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "schedule_log_resource_idx": { + "name": "schedule_log_resource_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "schedule_log_executed_idx": { + "name": "schedule_log_executed_idx", + "columns": [ + { + "expression": "executed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "schedule_log_schedule_id_schedule_id_fk": { + "name": "schedule_log_schedule_id_schedule_id_fk", + "tableFrom": "schedule_log", + "tableTo": "schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schedule_log_resource_id_resource_id_fk": { + "name": "schedule_log_resource_id_resource_id_fk", + "tableFrom": "schedule_log", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_provider": { + "name": "scim_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_provider_organization_provider_uidx": { + "name": "scim_provider_organization_provider_uidx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_provider_token_hash_uidx": { + "name": "scim_provider_token_hash_uidx", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_provider_organization_idx": { + "name": "scim_provider_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_provider_organization_id_organization_id_fk": { + "name": "scim_provider_organization_id_organization_id_fk", + "tableFrom": "scim_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_provider": { + "name": "secret_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_configuration": { + "name": "encrypted_configuration", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'true'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_provider_organization_idx": { + "name": "secret_provider_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_provider_organization_id_organization_id_fk": { + "name": "secret_provider_organization_id_organization_id_fk", + "tableFrom": "secret_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_rotation_schedule": { + "name": "secret_rotation_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "keys": { + "name": "keys", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "interval_hours": { + "name": "interval_hours", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "value_length": { + "name": "value_length", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 32 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_rotated_at": { + "name": "last_rotated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rotation_claimed_until": { + "name": "rotation_claimed_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_rotation_schedule_org_idx": { + "name": "secret_rotation_schedule_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_rotation_schedule_due_idx": { + "name": "secret_rotation_schedule_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_rotated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_rotation_schedule_scope_idx": { + "name": "secret_rotation_schedule_scope_idx", + "columns": [ + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_rotation_schedule_organization_id_organization_id_fk": { + "name": "secret_rotation_schedule_organization_id_organization_id_fk", + "tableFrom": "secret_rotation_schedule", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_version": { + "name": "secret_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "scope_type": { + "name": "scope_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_id": { + "name": "scope_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_secrets": { + "name": "build_secrets", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_vars": { + "name": "env_vars", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'local'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_version_scope_version_uidx": { + "name": "secret_version_scope_version_uidx", + "columns": [ + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_version_scope_idx": { + "name": "secret_version_scope_idx", + "columns": [ + { + "expression": "scope_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.server": { + "name": "server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "server_type": { + "name": "server_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ssh_key_id": { + "name": "ssh_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ssh_host_key_fingerprint": { + "name": "ssh_host_key_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "port": { + "name": "port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 22 + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'root'" + }, + "enable_docker_cleanup": { + "name": "enable_docker_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "setup_error": { + "name": "setup_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "server_organization_idx": { + "name": "server_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "server_ssh_key_idx": { + "name": "server_ssh_key_idx", + "columns": [ + { + "expression": "ssh_key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "server_organization_id_organization_id_fk": { + "name": "server_organization_id_organization_id_fk", + "tableFrom": "server", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "server_ssh_key_id_ssh_key_id_fk": { + "name": "server_ssh_key_id_ssh_key_id_fk", + "tableFrom": "server", + "tableTo": "ssh_key", + "columnsFrom": ["ssh_key_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.server_build_settings": { + "name": "server_build_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "hostname": { + "name": "hostname", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ip": { + "name": "ip", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "concurrency": { + "name": "concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ssh_key": { + "name": "ssh_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "algorithm": { + "name": "algorithm", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "fingerprint": { + "name": "fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_ciphertext": { + "name": "private_key_ciphertext", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_iv": { + "name": "private_key_iv", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_auth_tag": { + "name": "private_key_auth_tag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key_version": { + "name": "private_key_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "ssh_key_organization_idx": { + "name": "ssh_key_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "ssh_key_organization_id_organization_id_fk": { + "name": "ssh_key_organization_id_organization_id_fk", + "tableFrom": "ssh_key", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resource_tag": { + "name": "resource_tag", + "schema": "", + "columns": { + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_id": { + "name": "tag_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "resource_tag_uidx": { + "name": "resource_tag_uidx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_tag_resource_idx": { + "name": "resource_tag_resource_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resource_tag_tag_idx": { + "name": "resource_tag_tag_idx", + "columns": [ + { + "expression": "tag_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resource_tag_resource_id_resource_id_fk": { + "name": "resource_tag_resource_id_resource_id_fk", + "tableFrom": "resource_tag", + "tableTo": "resource", + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "resource_tag_tag_id_tag_id_fk": { + "name": "resource_tag_tag_id_tag_id_fk", + "tableFrom": "resource_tag", + "tableTo": "tag", + "columnsFrom": ["tag_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tag": { + "name": "tag", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#6366f1'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tag_organization_name_uidx": { + "name": "tag_organization_name_uidx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tag_organization_idx": { + "name": "tag_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tag_organization_id_organization_id_fk": { + "name": "tag_organization_id_organization_id_fk", + "tableFrom": "tag", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "tag_color_hex_check": { + "name": "tag_color_hex_check", + "value": "\"tag\".\"color\" ~ '^#[0-9a-fA-F]{6}$'" + } + }, + "isRLSEnabled": false + }, + "public.template": { + "name": "template", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "compose_file": { + "name": "compose_file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_organization_name_uidx": { + "name": "template_organization_name_uidx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_organization_idx": { + "name": "template_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_organization_id_organization_id_fk": { + "name": "template_organization_id_organization_id_fk", + "tableFrom": "template", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.web_server_settings": { + "name": "web_server_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_domain": { + "name": "server_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "https_enabled": { + "name": "https_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "certificate_provider": { + "name": "certificate_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'letsencrypt'" + }, + "certificate_id": { + "name": "certificate_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lets_encrypt_email": { + "name": "lets_encrypt_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cloudflare_api_token": { + "name": "cloudflare_api_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_port": { + "name": "http_port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 80 + }, + "https_port": { + "name": "https_port", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 443 + }, + "enable_http3": { + "name": "enable_http3", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "global_caddyfile": { + "name": "global_caddyfile", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "caddy_snippets": { + "name": "caddy_snippets", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "caddy_middlewares": { + "name": "caddy_middlewares", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "server_ip": { + "name": "server_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "daily_docker_cleanup": { + "name": "daily_docker_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "caddy_environment": { + "name": "caddy_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "caddy_ports": { + "name": "caddy_ports", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "caddy_dashboard_enabled": { + "name": "caddy_dashboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "access_logs_enabled": { + "name": "access_logs_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "access_log_cleanup_cron": { + "name": "access_log_cleanup_cron", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'0 3 * * *'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 685f0bf5..c556492c 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -456,6 +456,13 @@ "when": 1785019114876, "tag": "0064_striped_mandroid", "breakpoints": true + }, + { + "idx": 65, + "version": "7", + "when": 1785023246897, + "tag": "0065_secure_upstand_cron_config", + "breakpoints": true } ] } diff --git a/packages/db/src/schema/schedule.ts b/packages/db/src/schema/schedule.ts index 75ebca6b..cbf9e50f 100644 --- a/packages/db/src/schema/schedule.ts +++ b/packages/db/src/schema/schedule.ts @@ -20,6 +20,8 @@ export const schedule = pgTable( name: text("name").notNull(), description: text("description"), cronExpression: text("cron_expression").notNull(), + httpMethod: text("http_method"), + secretEnvVar: text("secret_env_var"), timezone: text("timezone").notNull().default("UTC"), jobType: text("job_type").notNull().default("command"), // "command" | "deployment" | "backup" | "cron" serviceName: text("service_name"), diff --git a/packages/domain/src/entities/schedule.ts b/packages/domain/src/entities/schedule.ts index ba125a8d..a5d57608 100644 --- a/packages/domain/src/entities/schedule.ts +++ b/packages/domain/src/entities/schedule.ts @@ -6,6 +6,8 @@ export interface Schedule { name: string; description: string | null; cronExpression: string; + httpMethod: "GET" | "POST" | null; + secretEnvVar: string | null; timezone: string; jobType: "command" | "deployment" | "backup" | "cron"; serviceName: string | null; @@ -26,6 +28,8 @@ export type CreateScheduleDTO = Omit< | "updatedAt" | "description" | "timezone" + | "httpMethod" + | "secretEnvVar" | "serviceName" | "shellType" | "source" @@ -36,6 +40,8 @@ export type CreateScheduleDTO = Omit< id?: string; description?: string | null; timezone?: string; + httpMethod?: "GET" | "POST" | null; + secretEnvVar?: string | null; serviceName?: string | null; shellType?: "bash" | "sh"; source?: "upstand.json" | "manual"; @@ -93,6 +99,13 @@ export const CreateScheduleInputSchema = z name: ScheduleNameSchema, description: z.string().trim().max(500).optional().nullable(), cronExpression: ScheduleCronSchema, + httpMethod: z.enum(["GET", "POST"]).optional().nullable(), + secretEnvVar: z + .string() + .trim() + .regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "Invalid secret environment variable") + .optional() + .nullable(), timezone: z.string().trim().default("UTC").optional(), jobType: ScheduleJobTypeSchema.default("command"), serviceName: z.string().trim().optional().nullable(), @@ -139,6 +152,13 @@ export const UpdateScheduleInputSchema = z.object({ name: ScheduleNameSchema.optional(), description: z.string().trim().max(500).nullable().optional(), cronExpression: ScheduleCronSchema.optional(), + httpMethod: z.enum(["GET", "POST"]).optional().nullable(), + secretEnvVar: z + .string() + .trim() + .regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "Invalid secret environment variable") + .optional() + .nullable(), timezone: z.string().trim().optional(), jobType: ScheduleJobTypeSchema.optional(), serviceName: z.string().trim().nullable().optional(), diff --git a/packages/domain/src/entities/upstand-config.test.ts b/packages/domain/src/entities/upstand-config.test.ts index 3c8ee01b..79061daf 100644 --- a/packages/domain/src/entities/upstand-config.test.ts +++ b/packages/domain/src/entities/upstand-config.test.ts @@ -108,4 +108,44 @@ describe("parseUpstandConfig", () => { const result = parseUpstandConfig("invalid json"); expect(result.success).toBe(false); }); + + it("rejects plaintext cron secrets and ambiguous cron targets", () => { + const plaintextSecret = parseUpstandConfig( + JSON.stringify({ + crons: [ + { + path: "/api/cron", + schedule: "0 * * * *", + secret: "do-not-store-me", + }, + ], + }), + ); + expect(plaintextSecret.success).toBe(false); + + const ambiguous = parseUpstandConfig({ + crons: [ + { + path: "/api/cron", + command: "echo nope", + schedule: "0 * * * *", + }, + ], + }); + expect(ambiguous.success).toBe(false); + }); + + it("rejects unsafe repository paths and conflicting runtime aliases", () => { + expect( + parseUpstandConfig({ + build: { buildPath: "../outside" }, + }).success, + ).toBe(false); + expect( + parseUpstandConfig({ + runtime: { replicas: 2 }, + resources: { replicas: 3 }, + }).success, + ).toBe(false); + }); }); diff --git a/packages/domain/src/entities/upstand-config.ts b/packages/domain/src/entities/upstand-config.ts index c1c5b4e6..127d2db3 100644 --- a/packages/domain/src/entities/upstand-config.ts +++ b/packages/domain/src/entities/upstand-config.ts @@ -1,5 +1,28 @@ import { z } from "zod"; +const RepositoryRelativePathSchema = z + .string() + .trim() + .min(1) + .max(512) + .refine( + (value) => + value !== "/" && + !value.startsWith("/") && + !value.startsWith("\\") && + !/^[A-Za-z]:[\\/]/.test(value) && + !value.split(/[\\/]+/).includes(".."), + "Path must be relative to the repository and must not contain '..'", + ); + +const SecretEnvironmentVariableSchema = z + .string() + .trim() + .regex( + /^[A-Za-z_][A-Za-z0-9_]*$/, + "Secret environment variable must be a valid environment variable name", + ); + function validateCronExpression(expr: string): boolean { if (!expr || typeof expr !== "string") return false; const parts = expr.trim().split(/\s+/); @@ -20,7 +43,9 @@ export const UpstandCronConfigSchema = z }) .optional(), method: z.enum(["GET", "POST"]).default("GET").optional(), - secret: z.string().trim().optional(), + // Never put the secret value in source control. The value is read from + // the encrypted resource environment at execution time. + secretEnvVar: SecretEnvironmentVariableSchema.optional(), // Command Schedule properties command: z.string().trim().min(1).optional(), @@ -46,58 +71,79 @@ export const UpstandCronConfigSchema = z "Cron item must specify either a 'path' (HTTP cron) or a 'command' (Script schedule)", }); } - }); + if (data.path && data.command) { + ctx.addIssue({ + code: "custom", + path: ["command"], + message: "Cron item cannot specify both 'path' and 'command'", + }); + } + if (data.secretEnvVar && !data.path) { + ctx.addIssue({ + code: "custom", + path: ["secretEnvVar"], + message: "secretEnvVar is only valid for HTTP cron items", + }); + } + }) + .strict(); export type UpstandCronConfig = z.infer; -export const UpstandBuildConfigSchema = z.object({ - type: z - .enum([ - "dockerfile", - "railpack", - "nixpacks", - "heroku-buildpacks", - "paketo-buildpacks", - "static", - ]) - .optional(), - buildPath: z.string().trim().min(1).optional(), - dockerfilePath: z.string().trim().min(1).optional(), - dockerContextPath: z.string().trim().min(1).optional(), - publishDirectory: z.string().trim().min(1).optional(), - dockerBuildStage: z.string().trim().min(1).optional(), - dockerBuildArgs: z.record(z.string(), z.string()).optional(), - dockerNoCache: z.boolean().optional(), - watchPaths: z - .union([z.string().trim(), z.array(z.string().trim())]) - .optional(), -}); +export const UpstandBuildConfigSchema = z + .object({ + type: z + .enum([ + "dockerfile", + "railpack", + "nixpacks", + "heroku-buildpacks", + "paketo-buildpacks", + "static", + ]) + .optional(), + buildPath: RepositoryRelativePathSchema.optional(), + dockerfilePath: RepositoryRelativePathSchema.optional(), + dockerContextPath: RepositoryRelativePathSchema.optional(), + publishDirectory: RepositoryRelativePathSchema.optional(), + dockerBuildStage: z.string().trim().min(1).optional(), + dockerBuildArgs: z.record(z.string(), z.string()).optional(), + dockerNoCache: z.boolean().optional(), + watchPaths: z + .union([z.string().trim(), z.array(z.string().trim())]) + .optional(), + }) + .strict(); export type UpstandBuildConfig = z.infer; -export const UpstandRuntimeConfigSchema = z.object({ - command: z.union([z.string().trim(), z.array(z.string().trim())]).optional(), - args: z.array(z.string()).optional(), - workingDir: z.string().trim().optional(), - cpuLimit: z.number().positive().max(1024).optional(), - cpuReservation: z.number().positive().max(1024).optional(), - memoryLimitMb: z.number().int().positive().max(1_048_576).optional(), - memoryReservationMb: z.number().int().positive().max(1_048_576).optional(), - replicas: z.number().int().min(0).max(1000).optional(), - restartPolicy: z - .object({ - condition: z.enum(["none", "on-failure", "any"]).optional(), - maxAttempts: z.number().int().min(0).max(1000).optional(), - delaySeconds: z.number().int().min(0).max(86400).optional(), - }) - .optional(), - updateConfig: z - .object({ - parallelism: z.number().int().min(0).max(1000).optional(), - order: z.enum(["stop-first", "start-first"]).optional(), - }) - .optional(), -}); +export const UpstandRuntimeConfigSchema = z + .object({ + command: z + .union([z.string().trim(), z.array(z.string().trim())]) + .optional(), + args: z.array(z.string()).optional(), + workingDir: z.string().trim().optional(), + cpuLimit: z.number().positive().max(1024).optional(), + cpuReservation: z.number().positive().max(1024).optional(), + memoryLimitMb: z.number().int().positive().max(1_048_576).optional(), + memoryReservationMb: z.number().int().positive().max(1_048_576).optional(), + replicas: z.number().int().min(0).max(1000).optional(), + restartPolicy: z + .object({ + condition: z.enum(["none", "on-failure", "any"]).optional(), + maxAttempts: z.number().int().min(0).max(1000).optional(), + delaySeconds: z.number().int().min(0).max(86400).optional(), + }) + .optional(), + updateConfig: z + .object({ + parallelism: z.number().int().min(0).max(1000).optional(), + order: z.enum(["stop-first", "start-first"]).optional(), + }) + .optional(), + }) + .strict(); export type UpstandRuntimeConfig = z.infer; @@ -107,9 +153,18 @@ export const UpstandConfigSchema = z build: UpstandBuildConfigSchema.optional(), runtime: UpstandRuntimeConfigSchema.optional(), resources: UpstandRuntimeConfigSchema.optional(), - crons: z.array(UpstandCronConfigSchema).optional(), + crons: z.array(UpstandCronConfigSchema).max(100).optional(), }) - .passthrough(); + .strict() + .superRefine((data, ctx) => { + if (data.runtime && data.resources) { + ctx.addIssue({ + code: "custom", + path: ["resources"], + message: "Use either 'runtime' or 'resources', not both", + }); + } + }); export type UpstandConfig = z.infer; diff --git a/packages/usecases/src/backup/backup-runtime.service.ts b/packages/usecases/src/backup/backup-runtime.service.ts index 2e36ab59..13051faf 100644 --- a/packages/usecases/src/backup/backup-runtime.service.ts +++ b/packages/usecases/src/backup/backup-runtime.service.ts @@ -5,7 +5,7 @@ import os from "node:os"; import path from "node:path"; import { Readable } from "node:stream"; import { promisify } from "node:util"; -import type { IUnitOfWork, S3Destination } from "@upstand/domain"; +import type { IUnitOfWork } from "@upstand/domain"; import { type BackupDatabaseEngine, type BackupSchedule, @@ -18,6 +18,7 @@ import { resolveDockerCliEnvironmentForServer } from "../resource/docker-client" import { parseResourceCredentials as parseCredentialDocument } from "../resource/resource-credentials"; import { parseResourceEnvironmentVariables } from "../resource/resource-environment"; import { + type BackupRuntimeDestination, normalizeBackupPrefix, pipeProcesses, rcloneRemote, @@ -202,9 +203,12 @@ export class BackupRuntimeService { async createBackup( schedule: BackupSchedule, resource: Resource, - destination: S3Destination, + destination: BackupRuntimeDestination, ): Promise { - const storage = toBackupStorageDestination(destination); + const storage = toBackupStorageDestination( + destination, + destination.caCertificatePem, + ); const resourcePath = `${resource.id}/${normalizeBackupPrefix(schedule.prefix)}`; const fileName = schedule.kind === "database" @@ -226,11 +230,14 @@ export class BackupRuntimeService { async restoreBackup( schedule: BackupSchedule, resource: Resource, - destination: S3Destination, + destination: BackupRuntimeDestination, fileKey: string, targetTime?: string, ): Promise { - const storage = toBackupStorageDestination(destination); + const storage = toBackupStorageDestination( + destination, + destination.caCertificatePem, + ); if (schedule.kind === "database") { await this.restoreDatabaseBackup( schedule, @@ -245,10 +252,13 @@ export class BackupRuntimeService { } async deleteBackup( - destination: S3Destination, + destination: BackupRuntimeDestination, fileKey: string, ): Promise { - const storage = toBackupStorageDestination(destination); + const storage = toBackupStorageDestination( + destination, + destination.caCertificatePem, + ); await runProcess("rclone", [ "deletefile", ...storage.rcloneFlags, @@ -258,11 +268,14 @@ export class BackupRuntimeService { async verifyBackup( schedule: BackupSchedule, - destination: S3Destination, + destination: BackupRuntimeDestination, fileKey: string, resource?: Resource, ): Promise { - const storage = toBackupStorageDestination(destination); + const storage = toBackupStorageDestination( + destination, + destination.caCertificatePem, + ); if (schedule.kind !== "database") { await runProcess("rclone", [ "size", @@ -806,9 +819,12 @@ export class BackupRuntimeService { async createWebServerBackup( schedule: BackupSchedule, - destination: S3Destination, + destination: BackupRuntimeDestination, ): Promise { - const storage = toBackupStorageDestination(destination); + const storage = toBackupStorageDestination( + destination, + destination.caCertificatePem, + ); const base = `web-server/${normalizeBackupPrefix(schedule.prefix)}${backupTimestamp()}-${randomSuffix()}/`; const postgresKey = `${base}control-plane.dump`; const volumeKeys = WEB_SERVER_BACKUP_VOLUMES.map( @@ -870,10 +886,13 @@ export class BackupRuntimeService { } async restoreWebServerBackup( - destination: S3Destination, + destination: BackupRuntimeDestination, manifestKey: string, ): Promise { - const storage = toBackupStorageDestination(destination); + const storage = toBackupStorageDestination( + destination, + destination.caCertificatePem, + ); const manifest = await this.readWebServerManifest(storage, manifestKey); const postgresContainer = await this.resolvePostgresContainer(); let caddyWasRunning = false; @@ -956,10 +975,13 @@ export class BackupRuntimeService { } async deleteWebServerBackup( - destination: S3Destination, + destination: BackupRuntimeDestination, manifestKey: string, ): Promise { - const storage = toBackupStorageDestination(destination); + const storage = toBackupStorageDestination( + destination, + destination.caCertificatePem, + ); let manifest: WebServerBackupManifest; try { manifest = await this.readWebServerManifest(storage, manifestKey); diff --git a/packages/usecases/src/backup/backup-storage.ts b/packages/usecases/src/backup/backup-storage.ts index 17b443cc..6753966c 100644 --- a/packages/usecases/src/backup/backup-storage.ts +++ b/packages/usecases/src/backup/backup-storage.ts @@ -11,6 +11,19 @@ export interface BackupStorageDestination { rcloneFlags: string[]; } +export type BackupRuntimeDestination = S3Destination & { + caCertificatePem?: string | null; +}; + +export function withBackupCaCertificate( + destination: S3Destination, + certificatePem?: string | null, +): BackupRuntimeDestination { + return certificatePem?.trim() + ? { ...destination, caCertificatePem: certificatePem } + : destination; +} + export function normalizeBackupPrefix(prefix: string): string { let normalized = prefix.trim(); while (normalized.startsWith("/")) normalized = normalized.slice(1); @@ -36,7 +49,7 @@ export function ensureCaCertificateFile(certificatePem: string): string { } export function toBackupStorageDestination( - destination: S3Destination, + destination: BackupRuntimeDestination, caCertificatePem?: string | null, ): BackupStorageDestination { const accessKeyId = decryptDestinationField(destination.accessKeyId); diff --git a/packages/usecases/src/backup/delete-backup-schedule.usecase.ts b/packages/usecases/src/backup/delete-backup-schedule.usecase.ts index 988dbbfb..a83f9100 100644 --- a/packages/usecases/src/backup/delete-backup-schedule.usecase.ts +++ b/packages/usecases/src/backup/delete-backup-schedule.usecase.ts @@ -1,6 +1,7 @@ import { type IUnitOfWork, ValidationError } from "@upstand/domain"; import { z } from "zod"; import { BackupRuntimeService } from "./backup-runtime.service"; +import { withBackupCaCertificate } from "./backup-storage"; export const DeleteBackupScheduleInputSchema = z.object({ id: z.string().min(1), @@ -23,6 +24,13 @@ export class DeleteBackupScheduleUseCase { schedule.destinationId, ); if (!destination) throw new ValidationError("Backup destination not found"); + const certificate = destination.certificateId + ? await this.uow.certificateRepository.findById(destination.certificateId) + : null; + const effectiveDestination = withBackupCaCertificate( + destination, + certificate?.certificatePem, + ); const runs = await this.uow.backupRunRepository.findByScheduleId( schedule.id, @@ -31,9 +39,12 @@ export class DeleteBackupScheduleUseCase { for (const run of runs) { if (!run.fileKey) continue; if (schedule.kind === "web-server") { - await this.runtime.deleteWebServerBackup(destination, run.fileKey); + await this.runtime.deleteWebServerBackup( + effectiveDestination, + run.fileKey, + ); } else { - await this.runtime.deleteBackup(destination, run.fileKey); + await this.runtime.deleteBackup(effectiveDestination, run.fileKey); } } return this.uow.transaction((tx) => diff --git a/packages/usecases/src/backup/execute-backup-run.usecase.ts b/packages/usecases/src/backup/execute-backup-run.usecase.ts index b25cd2bc..fe52e805 100644 --- a/packages/usecases/src/backup/execute-backup-run.usecase.ts +++ b/packages/usecases/src/backup/execute-backup-run.usecase.ts @@ -11,6 +11,7 @@ import { withBackupRuntime, } from "./backup-runtime.service"; import { resolveBackupOrganizationId } from "./backup-schedule.service"; +import { withBackupCaCertificate } from "./backup-storage"; export class ExecuteBackupRunUseCase { constructor( @@ -76,11 +77,10 @@ export class ExecuteBackupRunUseCase { const caCert = destination.certificateId ? await this.uow.certificateRepository.findById(destination.certificateId) : null; - const effectiveDestination = caCert - ? Object.assign({}, destination, { - caCertificatePem: caCert.certificatePem, - }) - : destination; + const effectiveDestination = withBackupCaCertificate( + destination, + caCert?.certificatePem, + ); try { const fileKey = diff --git a/packages/usecases/src/backup/restore-backup-run.usecase.ts b/packages/usecases/src/backup/restore-backup-run.usecase.ts index b3d46d65..5ff7331e 100644 --- a/packages/usecases/src/backup/restore-backup-run.usecase.ts +++ b/packages/usecases/src/backup/restore-backup-run.usecase.ts @@ -4,6 +4,7 @@ import { BackupRuntimeService, withBackupRuntime, } from "./backup-runtime.service"; +import { withBackupCaCertificate } from "./backup-storage"; export const RestoreBackupRunInputSchema = z.object({ runId: z.string().min(1), @@ -40,11 +41,10 @@ export class RestoreBackupRunUseCase { const caCert = destination.certificateId ? await this.uow.certificateRepository.findById(destination.certificateId) : null; - const effectiveDestination = caCert - ? Object.assign({}, destination, { - caCertificatePem: caCert.certificatePem, - }) - : destination; + const effectiveDestination = withBackupCaCertificate( + destination, + caCert?.certificatePem, + ); if (schedule.kind === "web-server") { await this.runtime.restoreWebServerBackup(effectiveDestination, fileKey); diff --git a/packages/usecases/src/backup/verify-backup-run.usecase.ts b/packages/usecases/src/backup/verify-backup-run.usecase.ts index 2c05602b..6f39d6de 100644 --- a/packages/usecases/src/backup/verify-backup-run.usecase.ts +++ b/packages/usecases/src/backup/verify-backup-run.usecase.ts @@ -3,6 +3,7 @@ import { BackupRuntimeService, withBackupRuntime, } from "./backup-runtime.service"; +import { withBackupCaCertificate } from "./backup-storage"; export class VerifyBackupRunUseCase { constructor( @@ -22,9 +23,20 @@ export class VerifyBackupRunUseCase { ); if (!schedule || !destination) throw new ValidationError("Backup configuration not found"); + const certificate = destination.certificateId + ? await this.uow.certificateRepository.findById(destination.certificateId) + : null; + const effectiveDestination = withBackupCaCertificate( + destination, + certificate?.certificatePem, + ); try { if (schedule.kind === "web-server") { - await this.runtime.verifyBackup(schedule, destination, run.fileKey); + await this.runtime.verifyBackup( + schedule, + effectiveDestination, + run.fileKey, + ); } else { const resource = run.resourceId ? await this.uow.resourceRepository.findById(run.resourceId) @@ -33,7 +45,7 @@ export class VerifyBackupRunUseCase { await withBackupRuntime(this.uow, resource, this.runtime, (runtime) => runtime.verifyBackup( schedule, - destination, + effectiveDestination, run.fileKey as string, resource, ), diff --git a/packages/usecases/src/deployment/deployment-worker.ts b/packages/usecases/src/deployment/deployment-worker.ts index 1b361be2..92ec0d7c 100644 --- a/packages/usecases/src/deployment/deployment-worker.ts +++ b/packages/usecases/src/deployment/deployment-worker.ts @@ -1066,12 +1066,9 @@ export class DeploymentWorker { async (clonePath: string) => { try { const upstandJsonPath = path.join(clonePath, "upstand.json"); - const vercelJsonPath = path.join(clonePath, "vercel.json"); let configContent: string | null = null; if (fs.existsSync(upstandJsonPath)) { configContent = fs.readFileSync(upstandJsonPath, "utf-8"); - } else if (fs.existsSync(vercelJsonPath)) { - configContent = fs.readFileSync(vercelJsonPath, "utf-8"); } if (configContent) { diff --git a/packages/usecases/src/schedule/general-scheduler.ts b/packages/usecases/src/schedule/general-scheduler.ts index d88eeb34..72dbf9c0 100644 --- a/packages/usecases/src/schedule/general-scheduler.ts +++ b/packages/usecases/src/schedule/general-scheduler.ts @@ -12,6 +12,8 @@ export interface CustomScheduleItem extends ScheduledJobItem { serviceName?: string | null; shellType?: string | null; backupScheduleId?: string | null; + httpMethod?: string | null; + secretEnvVar?: string | null; command: string; } @@ -31,7 +33,7 @@ export class GeneralScheduler extends BaseCronScheduler { buildSignature(schedule: CustomScheduleItem): string { const timezone = schedule.timezone || "UTC"; - return `${schedule.cronExpression}\0${timezone}\0${schedule.jobType ?? "command"}\0${schedule.serviceName ?? ""}\0${schedule.shellType ?? "bash"}\0${schedule.backupScheduleId ?? ""}\0${schedule.command}`; + return `${schedule.cronExpression}\0${timezone}\0${schedule.jobType ?? "command"}\0${schedule.serviceName ?? ""}\0${schedule.shellType ?? "bash"}\0${schedule.backupScheduleId ?? ""}\0${schedule.httpMethod ?? "GET"}\0${schedule.secretEnvVar ?? ""}\0${schedule.command}`; } /** Execute a persisted schedule immediately, even when it is disabled. */ diff --git a/packages/usecases/src/schedule/schedule.usecases.test.ts b/packages/usecases/src/schedule/schedule.usecases.test.ts index 3c85a1de..1fed8d95 100644 --- a/packages/usecases/src/schedule/schedule.usecases.test.ts +++ b/packages/usecases/src/schedule/schedule.usecases.test.ts @@ -45,6 +45,8 @@ function createUow(): { uow: IUnitOfWork; schedules: Map } { shellType: data.shellType ?? "bash", source: data.source ?? "manual", backupScheduleId: data.backupScheduleId ?? null, + httpMethod: data.httpMethod ?? null, + secretEnvVar: data.secretEnvVar ?? null, lastRunAt: data.lastRunAt ?? null, lastRunStatus: data.lastRunStatus ?? null, createdAt: new Date(), @@ -147,6 +149,8 @@ describe("resource schedules", () => { name: "Manual maintenance", description: null, cronExpression: "0 4 * * *", + httpMethod: null, + secretEnvVar: null, timezone: "UTC", jobType: "command", serviceName: null, diff --git a/packages/usecases/src/schedule/schedule.usecases.ts b/packages/usecases/src/schedule/schedule.usecases.ts index 415ea8c0..3d7903b4 100644 --- a/packages/usecases/src/schedule/schedule.usecases.ts +++ b/packages/usecases/src/schedule/schedule.usecases.ts @@ -68,6 +68,8 @@ export class CreateScheduleUseCase { name: parsed.name, description: parsed.description ?? null, cronExpression: parsed.cronExpression, + httpMethod: parsed.httpMethod ?? null, + secretEnvVar: parsed.secretEnvVar ?? null, timezone: parsed.timezone ?? "UTC", jobType: parsed.jobType, serviceName: parsed.serviceName ?? null, diff --git a/packages/usecases/src/schedule/sync-upstand-config.usecase.test.ts b/packages/usecases/src/schedule/sync-upstand-config.usecase.test.ts index 4b215e91..4614ead8 100644 --- a/packages/usecases/src/schedule/sync-upstand-config.usecase.test.ts +++ b/packages/usecases/src/schedule/sync-upstand-config.usecase.test.ts @@ -85,6 +85,8 @@ describe("SyncUpstandConfigUseCase", () => { crons: [ { path: "/api/new", + method: "POST", + secretEnvVar: "CRON_SECRET", schedule: "*/5 * * * *", }, ], @@ -101,6 +103,8 @@ describe("SyncUpstandConfigUseCase", () => { expect(deletedIds).not.toContain("sch-manual"); expect(created[0]?.command).toBe("/api/new"); expect(created[0]?.source).toBe("upstand.json"); + expect(created[0]?.httpMethod).toBe("POST"); + expect(created[0]?.secretEnvVar).toBe("CRON_SECRET"); expect(updatedResources).toHaveLength(1); expect(updatedResources[0]?.patch.watchPaths).toBe('["apps/web/**"]'); expect(updatedResources[0]?.patch.buildConfig).toContain( diff --git a/packages/usecases/src/schedule/sync-upstand-config.usecase.ts b/packages/usecases/src/schedule/sync-upstand-config.usecase.ts index 496a07ef..4a2051cb 100644 --- a/packages/usecases/src/schedule/sync-upstand-config.usecase.ts +++ b/packages/usecases/src/schedule/sync-upstand-config.usecase.ts @@ -177,6 +177,8 @@ export class SyncUpstandConfigUseCase { const shellType = item.shellType || "bash"; const serviceName = item.serviceName || null; const description = item.description || null; + const httpMethod = isHttpCron ? item.method || "GET" : null; + const secretEnvVar = isHttpCron ? item.secretEnvVar || null : null; // Attempt to find an existing upstand.json schedule matching this item const match = upstandSchedules.find((s) => { @@ -196,6 +198,8 @@ export class SyncUpstandConfigUseCase { matchedScheduleIds.add(match.id); const hasChanges = match.cronExpression !== item.schedule || + match.httpMethod !== httpMethod || + match.secretEnvVar !== secretEnvVar || match.name !== name || match.description !== description || match.timezone !== timezone || @@ -208,6 +212,8 @@ export class SyncUpstandConfigUseCase { name, description, cronExpression: item.schedule, + httpMethod, + secretEnvVar, timezone, jobType, serviceName, @@ -228,6 +234,8 @@ export class SyncUpstandConfigUseCase { name, description, cronExpression: item.schedule, + httpMethod, + secretEnvVar, timezone, jobType, serviceName,