From 7d10b63dc551c2814027fa9d58f8bca33badaf0c Mon Sep 17 00:00:00 2001 From: Owen Date: Mon, 17 Aug 2026 16:50:51 -0400 Subject: [PATCH] Add v1 migrations --- server/setup/scriptsPg/1.22.0.ts | 365 +++++++++++++++++++++++++++ server/setup/scriptsSqlite/1.22.0.ts | 364 ++++++++++++++++++++++++++ 2 files changed, 729 insertions(+) diff --git a/server/setup/scriptsPg/1.22.0.ts b/server/setup/scriptsPg/1.22.0.ts index 6b8f19b6c..e6ddcf750 100644 --- a/server/setup/scriptsPg/1.22.0.ts +++ b/server/setup/scriptsPg/1.22.0.ts @@ -20,6 +20,371 @@ export default async function migration() { await db.execute(sql` `); + await db.execute(sql` + CREATE TABLE "aiBudgetBreachEvents" ( + "id" serial PRIMARY KEY NOT NULL, + "orgId" varchar NOT NULL, + "budgetId" integer NOT NULL, + "enforcement" varchar NOT NULL, + "unit" varchar NOT NULL, + "period" varchar NOT NULL, + "amount" real NOT NULL, + "usageAmount" real NOT NULL, + "blocked" boolean NOT NULL, + "requestUserId" varchar, + "createdAt" bigint NOT NULL + ); + `); + await db.execute(sql` + CREATE TABLE "aiBudgets" ( + "budgetId" serial PRIMARY KEY NOT NULL, + "orgId" varchar NOT NULL, + "providerId" integer, + "modelId" integer, + "resourceId" integer, + "siteResourceId" integer, + "roleId" integer, + "virtualApiKeyId" varchar, + "amount" real NOT NULL, + "unit" varchar NOT NULL, + "period" varchar DEFAULT 'monthly' NOT NULL, + "enforcement" varchar DEFAULT 'hard' NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + "createdAt" bigint NOT NULL, + "updatedAt" bigint NOT NULL, + CONSTRAINT "ai_budget_provider_uniq" UNIQUE("providerId","unit","period"), + CONSTRAINT "ai_budget_model_uniq" UNIQUE("modelId","unit","period"), + CONSTRAINT "ai_budget_resource_uniq" UNIQUE("resourceId","unit","period"), + CONSTRAINT "ai_budget_site_resource_uniq" UNIQUE("siteResourceId","unit","period"), + CONSTRAINT "ai_budget_role_uniq" UNIQUE("roleId","unit","period"), + CONSTRAINT "ai_budget_virtual_api_key_uniq" UNIQUE("virtualApiKeyId","unit","period") + ); + `); + await db.execute(sql` + CREATE TABLE "aiModels" ( + "modelId" serial PRIMARY KEY NOT NULL, + "providerId" integer NOT NULL, + "modelKey" varchar NOT NULL, + "name" varchar NOT NULL, + "listType" varchar DEFAULT 'allow' NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + "createdAt" bigint NOT NULL, + "updatedAt" bigint NOT NULL, + CONSTRAINT "ai_model_provider_key_uniq" UNIQUE("providerId","modelKey") + ); + `); + await db.execute(sql` + CREATE TABLE "aiProviders" ( + "providerId" serial PRIMARY KEY NOT NULL, + "orgId" varchar NOT NULL, + "name" varchar NOT NULL, + "niceId" varchar NOT NULL, + "type" varchar NOT NULL, + "upstreamUrl" text, + "apiKey" text, + "apiKeyLastChars" varchar, + "authType" varchar NOT NULL, + "routingMode" varchar DEFAULT 'url' NOT NULL, + "capabilities" text DEFAULT '[]' NOT NULL, + "headers" text, + "skipTlsVerification" boolean DEFAULT false NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + "createdAt" bigint NOT NULL, + "updatedAt" bigint NOT NULL + ); + `); + await db.execute(sql` + CREATE TABLE "aiSessionLog" ( + "id" serial PRIMARY KEY NOT NULL, + "sessionId" varchar NOT NULL, + "orgId" varchar, + "providerId" integer, + "capability" varchar NOT NULL, + "resourceId" integer, + "siteResourceId" integer, + "userId" varchar, + "virtualApiKeyId" varchar, + "requestedModel" varchar, + "isStream" boolean DEFAULT false NOT NULL, + "requestBody" text, + "responseBody" text, + "normalizedRequest" text, + "normalizedResponse" text, + "truncated" boolean DEFAULT false NOT NULL, + "statusCode" integer, + "createdAt" bigint NOT NULL + ); + `); + await db.execute(sql` + CREATE TABLE "aiUsageRecords" ( + "id" serial PRIMARY KEY NOT NULL, + "orgId" varchar NOT NULL, + "providerId" integer, + "resourceId" integer, + "siteResourceId" integer, + "userId" varchar, + "virtualApiKeyId" varchar, + "sessionId" varchar, + "requestedModel" varchar NOT NULL, + "promptTokens" integer DEFAULT 0 NOT NULL, + "cacheReadTokens" integer DEFAULT 0 NOT NULL, + "cacheWriteTokens" integer DEFAULT 0 NOT NULL, + "completionTokens" integer DEFAULT 0 NOT NULL, + "reasoningTokens" integer DEFAULT 0 NOT NULL, + "totalTokens" integer DEFAULT 0 NOT NULL, + "costUsd" real, + "estimated" boolean DEFAULT false NOT NULL, + "createdAt" bigint NOT NULL + ); + `); + await db.execute(sql` + CREATE TABLE "resourceAiModels" ( + "resourceId" integer NOT NULL, + "modelId" integer NOT NULL, + "listType" varchar DEFAULT 'allow' NOT NULL, + CONSTRAINT "resourceAiModels_resourceId_modelId_pk" PRIMARY KEY("resourceId","modelId") + ); + `); + await db.execute(sql` + CREATE TABLE "resourceAiProviders" ( + "resourceId" integer NOT NULL, + "providerId" integer NOT NULL, + "accessMode" varchar DEFAULT 'inherit' NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + CONSTRAINT "resourceAiProviders_resourceId_providerId_pk" PRIMARY KEY("resourceId","providerId") + ); + `); + await db.execute(sql` + CREATE TABLE "siteResourceAiModels" ( + "siteResourceId" integer NOT NULL, + "modelId" integer NOT NULL, + "listType" varchar DEFAULT 'allow' NOT NULL, + CONSTRAINT "siteResourceAiModels_siteResourceId_modelId_pk" PRIMARY KEY("siteResourceId","modelId") + ); + `); + await db.execute(sql` + CREATE TABLE "siteResourceAiProviders" ( + "siteResourceId" integer NOT NULL, + "providerId" integer NOT NULL, + "accessMode" varchar DEFAULT 'inherit' NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + CONSTRAINT "siteResourceAiProviders_siteResourceId_providerId_pk" PRIMARY KEY("siteResourceId","providerId") + ); + `); + await db.execute(sql` + CREATE TABLE "virtualApiKeyResources" ( + "virtualApiKeyId" varchar NOT NULL, + "resourceId" integer NOT NULL, + CONSTRAINT "virtualApiKeyResources_virtualApiKeyId_resourceId_pk" PRIMARY KEY("virtualApiKeyId","resourceId") + ); + `); + await db.execute(sql` + CREATE TABLE "virtualApiKeys" ( + "virtualApiKeyId" varchar PRIMARY KEY NOT NULL, + "orgId" varchar NOT NULL, + "kind" varchar NOT NULL, + "userId" varchar, + "name" varchar, + "description" varchar, + "token" varchar NOT NULL, + "lastChars" varchar NOT NULL, + "allResources" boolean DEFAULT false NOT NULL, + "expiresAt" bigint, + "lastUsedAt" bigint, + "createdAt" bigint NOT NULL, + "createdByUserId" varchar + ); + `); + await db.execute( + sql`ALTER TABLE "clients" RENAME COLUMN "subnet" TO "exitNodeSubnet";` + ); + await db.execute( + sql`ALTER TABLE "roles" ALTER COLUMN "sshSudoMode" SET DEFAULT 'full';` + ); + await db.execute( + sql`ALTER TABLE "targets" ALTER COLUMN "resourceId" DROP NOT NULL;` + ); + await db.execute( + sql`ALTER TABLE "subscriptions" ADD COLUMN "override" boolean DEFAULT false;` + ); + await db.execute( + sql`ALTER TABLE "orgs" ADD COLUMN "settingsLogRetentionDaysAISessions" integer DEFAULT 7 NOT NULL;` + ); + await db.execute( + sql`ALTER TABLE "siteResources" ADD COLUMN "requiresExitNodeConnection" boolean DEFAULT false NOT NULL;` + ); + await db.execute( + sql`ALTER TABLE "sites" ADD COLUMN "exitNodeSubnet" text;` + ); + await db.execute( + sql`ALTER TABLE "targets" ADD COLUMN "providerId" integer;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgetBreachEvents" ADD CONSTRAINT "aiBudgetBreachEvents_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgetBreachEvents" ADD CONSTRAINT "aiBudgetBreachEvents_budgetId_aiBudgets_budgetId_fk" FOREIGN KEY ("budgetId") REFERENCES "public"."aiBudgets"("budgetId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgetBreachEvents" ADD CONSTRAINT "aiBudgetBreachEvents_requestUserId_user_id_fk" FOREIGN KEY ("requestUserId") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgets" ADD CONSTRAINT "aiBudgets_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgets" ADD CONSTRAINT "aiBudgets_providerId_aiProviders_providerId_fk" FOREIGN KEY ("providerId") REFERENCES "public"."aiProviders"("providerId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgets" ADD CONSTRAINT "aiBudgets_modelId_aiModels_modelId_fk" FOREIGN KEY ("modelId") REFERENCES "public"."aiModels"("modelId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgets" ADD CONSTRAINT "aiBudgets_resourceId_resources_resourceId_fk" FOREIGN KEY ("resourceId") REFERENCES "public"."resources"("resourceId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgets" ADD CONSTRAINT "aiBudgets_siteResourceId_siteResources_siteResourceId_fk" FOREIGN KEY ("siteResourceId") REFERENCES "public"."siteResources"("siteResourceId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgets" ADD CONSTRAINT "aiBudgets_roleId_roles_roleId_fk" FOREIGN KEY ("roleId") REFERENCES "public"."roles"("roleId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiBudgets" ADD CONSTRAINT "aiBudgets_virtualApiKeyId_virtualApiKeys_virtualApiKeyId_fk" FOREIGN KEY ("virtualApiKeyId") REFERENCES "public"."virtualApiKeys"("virtualApiKeyId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiModels" ADD CONSTRAINT "aiModels_providerId_aiProviders_providerId_fk" FOREIGN KEY ("providerId") REFERENCES "public"."aiProviders"("providerId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiProviders" ADD CONSTRAINT "aiProviders_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiSessionLog" ADD CONSTRAINT "aiSessionLog_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiSessionLog" ADD CONSTRAINT "aiSessionLog_providerId_aiProviders_providerId_fk" FOREIGN KEY ("providerId") REFERENCES "public"."aiProviders"("providerId") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiSessionLog" ADD CONSTRAINT "aiSessionLog_resourceId_resources_resourceId_fk" FOREIGN KEY ("resourceId") REFERENCES "public"."resources"("resourceId") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiSessionLog" ADD CONSTRAINT "aiSessionLog_siteResourceId_siteResources_siteResourceId_fk" FOREIGN KEY ("siteResourceId") REFERENCES "public"."siteResources"("siteResourceId") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiSessionLog" ADD CONSTRAINT "aiSessionLog_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiSessionLog" ADD CONSTRAINT "aiSessionLog_virtualApiKeyId_virtualApiKeys_virtualApiKeyId_fk" FOREIGN KEY ("virtualApiKeyId") REFERENCES "public"."virtualApiKeys"("virtualApiKeyId") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiUsageRecords" ADD CONSTRAINT "aiUsageRecords_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiUsageRecords" ADD CONSTRAINT "aiUsageRecords_providerId_aiProviders_providerId_fk" FOREIGN KEY ("providerId") REFERENCES "public"."aiProviders"("providerId") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiUsageRecords" ADD CONSTRAINT "aiUsageRecords_resourceId_resources_resourceId_fk" FOREIGN KEY ("resourceId") REFERENCES "public"."resources"("resourceId") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiUsageRecords" ADD CONSTRAINT "aiUsageRecords_siteResourceId_siteResources_siteResourceId_fk" FOREIGN KEY ("siteResourceId") REFERENCES "public"."siteResources"("siteResourceId") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiUsageRecords" ADD CONSTRAINT "aiUsageRecords_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "aiUsageRecords" ADD CONSTRAINT "aiUsageRecords_virtualApiKeyId_virtualApiKeys_virtualApiKeyId_fk" FOREIGN KEY ("virtualApiKeyId") REFERENCES "public"."virtualApiKeys"("virtualApiKeyId") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "resourceAiModels" ADD CONSTRAINT "resourceAiModels_resourceId_resources_resourceId_fk" FOREIGN KEY ("resourceId") REFERENCES "public"."resources"("resourceId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "resourceAiModels" ADD CONSTRAINT "resourceAiModels_modelId_aiModels_modelId_fk" FOREIGN KEY ("modelId") REFERENCES "public"."aiModels"("modelId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "resourceAiProviders" ADD CONSTRAINT "resourceAiProviders_resourceId_resources_resourceId_fk" FOREIGN KEY ("resourceId") REFERENCES "public"."resources"("resourceId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "resourceAiProviders" ADD CONSTRAINT "resourceAiProviders_providerId_aiProviders_providerId_fk" FOREIGN KEY ("providerId") REFERENCES "public"."aiProviders"("providerId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "siteResourceAiModels" ADD CONSTRAINT "siteResourceAiModels_siteResourceId_siteResources_siteResourceId_fk" FOREIGN KEY ("siteResourceId") REFERENCES "public"."siteResources"("siteResourceId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "siteResourceAiModels" ADD CONSTRAINT "siteResourceAiModels_modelId_aiModels_modelId_fk" FOREIGN KEY ("modelId") REFERENCES "public"."aiModels"("modelId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "siteResourceAiProviders" ADD CONSTRAINT "siteResourceAiProviders_siteResourceId_siteResources_siteResourceId_fk" FOREIGN KEY ("siteResourceId") REFERENCES "public"."siteResources"("siteResourceId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "siteResourceAiProviders" ADD CONSTRAINT "siteResourceAiProviders_providerId_aiProviders_providerId_fk" FOREIGN KEY ("providerId") REFERENCES "public"."aiProviders"("providerId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "virtualApiKeyResources" ADD CONSTRAINT "virtualApiKeyResources_virtualApiKeyId_virtualApiKeys_virtualApiKeyId_fk" FOREIGN KEY ("virtualApiKeyId") REFERENCES "public"."virtualApiKeys"("virtualApiKeyId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "virtualApiKeyResources" ADD CONSTRAINT "virtualApiKeyResources_resourceId_resources_resourceId_fk" FOREIGN KEY ("resourceId") REFERENCES "public"."resources"("resourceId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "virtualApiKeys" ADD CONSTRAINT "virtualApiKeys_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "virtualApiKeys" ADD CONSTRAINT "virtualApiKeys_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`ALTER TABLE "virtualApiKeys" ADD CONSTRAINT "virtualApiKeys_createdByUserId_user_id_fk" FOREIGN KEY ("createdByUserId") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_budget_breach_events_budget_created" ON "aiBudgetBreachEvents" USING btree ("budgetId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_aiProviders_orgId_niceId" ON "aiProviders" USING btree ("orgId","niceId");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_session_log_org_created" ON "aiSessionLog" USING btree ("orgId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_session_log_org_provider_created" ON "aiSessionLog" USING btree ("orgId","providerId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_session_log_org_resource_created" ON "aiSessionLog" USING btree ("orgId","resourceId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_session_log_org_site_resource_created" ON "aiSessionLog" USING btree ("orgId","siteResourceId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_session_log_org_user_created" ON "aiSessionLog" USING btree ("orgId","userId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_session_log_org_virtual_api_key_created" ON "aiSessionLog" USING btree ("orgId","virtualApiKeyId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_session_log_session" ON "aiSessionLog" USING btree ("sessionId");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_usage_records_org_provider_created" ON "aiUsageRecords" USING btree ("orgId","providerId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_usage_records_org_resource_created" ON "aiUsageRecords" USING btree ("orgId","resourceId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_usage_records_org_site_resource_created" ON "aiUsageRecords" USING btree ("orgId","siteResourceId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_usage_records_org_user_created" ON "aiUsageRecords" USING btree ("orgId","userId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_usage_records_org_virtual_api_key_created" ON "aiUsageRecords" USING btree ("orgId","virtualApiKeyId","createdAt");` + ); + await db.execute( + sql`CREATE INDEX "idx_ai_usage_records_session" ON "aiUsageRecords" USING btree ("sessionId");` + ); + await db.execute( + sql`CREATE UNIQUE INDEX "virtual_api_key_user_identity_uniq" ON "virtualApiKeys" USING btree ("orgId","userId") WHERE "virtualApiKeys"."kind" = 'user';` + ); + await db.execute( + sql`ALTER TABLE "targets" ADD CONSTRAINT "targets_providerId_aiProviders_providerId_fk" FOREIGN KEY ("providerId") REFERENCES "public"."aiProviders"("providerId") ON DELETE cascade ON UPDATE no action;` + ); + await db.execute( + sql`CREATE INDEX "idx_targets_providerid_siteid" ON "targets" USING btree ("providerId","siteId");` + ); + for (const actionId of actionsToGrant) { await db.execute(sql` INSERT INTO "roleActions" ("roleId", "actionId", "orgId") diff --git a/server/setup/scriptsSqlite/1.22.0.ts b/server/setup/scriptsSqlite/1.22.0.ts index 6ca6bd19d..16a124ef9 100644 --- a/server/setup/scriptsSqlite/1.22.0.ts +++ b/server/setup/scriptsSqlite/1.22.0.ts @@ -20,10 +20,374 @@ export default async function migration() { db.pragma("foreign_keys = OFF"); db.transaction(() => { + db.prepare( + `ALTER TABLE 'sites' RENAME COLUMN "subnet" TO "exitNodeSubnet";` + ).run(); + db.prepare( ` + CREATE TABLE 'aiBudgetBreachEvents' ( + 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'budgetId' integer NOT NULL, + 'enforcement' text NOT NULL, + 'unit' text NOT NULL, + 'period' text NOT NULL, + 'amount' real NOT NULL, + 'usageAmount' real NOT NULL, + 'blocked' integer NOT NULL, + 'requestUserId' text, + 'createdAt' integer NOT NULL, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('budgetId') REFERENCES 'aiBudgets'('budgetId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('requestUserId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE set null + ); ` ).run(); + db.prepare( + ` + CREATE INDEX 'idx_ai_budget_breach_events_budget_created' ON 'aiBudgetBreachEvents' ('budgetId','createdAt'); + ` + ).run(); + db.prepare( + ` + CREATE TABLE 'aiBudgets' ( + 'budgetId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'providerId' integer, + 'modelId' integer, + 'resourceId' integer, + 'siteResourceId' integer, + 'roleId' integer, + 'virtualApiKeyId' text, + 'amount' real NOT NULL, + 'unit' text NOT NULL, + 'period' text DEFAULT 'monthly' NOT NULL, + 'enforcement' text DEFAULT 'hard' NOT NULL, + 'enabled' integer DEFAULT true NOT NULL, + 'createdAt' integer NOT NULL, + 'updatedAt' integer NOT NULL, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('providerId') REFERENCES 'aiProviders'('providerId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('modelId') REFERENCES 'aiModels'('modelId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('siteResourceId') REFERENCES 'siteResources'('siteResourceId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('roleId') REFERENCES 'roles'('roleId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('virtualApiKeyId') REFERENCES 'virtualApiKeys'('virtualApiKeyId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + + db.prepare( + `CREATE UNIQUE INDEX 'ai_budget_provider_uniq' ON 'aiBudgets' ('providerId','unit','period');` + ).run(); + db.prepare( + `CREATE UNIQUE INDEX 'ai_budget_model_uniq' ON 'aiBudgets' ('modelId','unit','period');` + ).run(); + db.prepare( + `CREATE UNIQUE INDEX 'ai_budget_resource_uniq' ON 'aiBudgets' ('resourceId','unit','period');` + ).run(); + db.prepare( + `CREATE UNIQUE INDEX 'ai_budget_site_resource_uniq' ON 'aiBudgets' ('siteResourceId','unit','period');` + ).run(); + db.prepare( + `CREATE UNIQUE INDEX 'ai_budget_role_uniq' ON 'aiBudgets' ('roleId','unit','period');` + ).run(); + db.prepare( + `CREATE UNIQUE INDEX 'ai_budget_virtual_api_key_uniq' ON 'aiBudgets' ('virtualApiKeyId','unit','period');` + ).run(); + db.prepare( + ` + CREATE TABLE 'aiModels' ( + 'modelId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'providerId' integer NOT NULL, + 'modelKey' text NOT NULL, + 'name' text NOT NULL, + 'listType' text DEFAULT 'allow' NOT NULL, + 'enabled' integer DEFAULT true NOT NULL, + 'createdAt' integer NOT NULL, + 'updatedAt' integer NOT NULL, + FOREIGN KEY ('providerId') REFERENCES 'aiProviders'('providerId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + db.prepare( + ` + CREATE UNIQUE INDEX 'ai_model_provider_key_uniq' ON 'aiModels' ('providerId','modelKey'); + ` + ).run(); + db.prepare( + ` + CREATE TABLE 'aiProviders' ( + 'providerId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'name' text NOT NULL, + 'niceId' text NOT NULL, + 'type' text NOT NULL, + 'upstreamUrl' text, + 'apiKey' text, + 'apiKeyLastChars' text, + 'authType' text NOT NULL, + 'routingMode' text DEFAULT 'url' NOT NULL, + 'capabilities' text DEFAULT '[]' NOT NULL, + 'headers' text, + 'skipTlsVerification' integer DEFAULT false NOT NULL, + 'enabled' integer DEFAULT true NOT NULL, + 'createdAt' integer NOT NULL, + 'updatedAt' integer NOT NULL, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + db.prepare( + ` + CREATE INDEX 'idx_aiProviders_orgId_niceId' ON 'aiProviders' ('orgId','niceId'); + ` + ).run(); + db.prepare( + ` + CREATE TABLE 'aiSessionLog' ( + 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'sessionId' text NOT NULL, + 'orgId' text, + 'providerId' integer, + 'capability' text NOT NULL, + 'resourceId' integer, + 'siteResourceId' integer, + 'userId' text, + 'virtualApiKeyId' text, + 'requestedModel' text, + 'isStream' integer DEFAULT false NOT NULL, + 'requestBody' text, + 'responseBody' text, + 'normalizedRequest' text, + 'normalizedResponse' text, + 'truncated' integer DEFAULT false NOT NULL, + 'statusCode' integer, + 'createdAt' integer NOT NULL, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('providerId') REFERENCES 'aiProviders'('providerId') ON UPDATE no action ON DELETE set null, + FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE set null, + FOREIGN KEY ('siteResourceId') REFERENCES 'siteResources'('siteResourceId') ON UPDATE no action ON DELETE set null, + FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE set null, + FOREIGN KEY ('virtualApiKeyId') REFERENCES 'virtualApiKeys'('virtualApiKeyId') ON UPDATE no action ON DELETE set null + ); + ` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_session_log_org_created' ON 'aiSessionLog' ('orgId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_session_log_org_provider_created' ON 'aiSessionLog' ('orgId','providerId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_session_log_org_resource_created' ON 'aiSessionLog' ('orgId','resourceId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_session_log_org_site_resource_created' ON 'aiSessionLog' ('orgId','siteResourceId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_session_log_org_user_created' ON 'aiSessionLog' ('orgId','userId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_session_log_org_virtual_api_key_created' ON 'aiSessionLog' ('orgId','virtualApiKeyId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_session_log_session' ON 'aiSessionLog' ('sessionId');` + ).run(); + db.prepare( + ` + CREATE TABLE 'aiUsageRecords' ( + 'id' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'orgId' text NOT NULL, + 'providerId' integer, + 'resourceId' integer, + 'siteResourceId' integer, + 'userId' text, + 'virtualApiKeyId' text, + 'sessionId' text, + 'requestedModel' text NOT NULL, + 'promptTokens' integer DEFAULT 0 NOT NULL, + 'cacheReadTokens' integer DEFAULT 0 NOT NULL, + 'cacheWriteTokens' integer DEFAULT 0 NOT NULL, + 'completionTokens' integer DEFAULT 0 NOT NULL, + 'reasoningTokens' integer DEFAULT 0 NOT NULL, + 'totalTokens' integer DEFAULT 0 NOT NULL, + 'costUsd' real, + 'estimated' integer DEFAULT false NOT NULL, + 'createdAt' integer NOT NULL, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('providerId') REFERENCES 'aiProviders'('providerId') ON UPDATE no action ON DELETE set null, + FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE set null, + FOREIGN KEY ('siteResourceId') REFERENCES 'siteResources'('siteResourceId') ON UPDATE no action ON DELETE set null, + FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE set null, + FOREIGN KEY ('virtualApiKeyId') REFERENCES 'virtualApiKeys'('virtualApiKeyId') ON UPDATE no action ON DELETE set null + ); + ` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_usage_records_org_provider_created' ON 'aiUsageRecords' ('orgId','providerId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_usage_records_org_resource_created' ON 'aiUsageRecords' ('orgId','resourceId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_usage_records_org_site_resource_created' ON 'aiUsageRecords' ('orgId','siteResourceId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_usage_records_org_user_created' ON 'aiUsageRecords' ('orgId','userId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_usage_records_org_virtual_api_key_created' ON 'aiUsageRecords' ('orgId','virtualApiKeyId','createdAt');` + ).run(); + db.prepare( + `CREATE INDEX 'idx_ai_usage_records_session' ON 'aiUsageRecords' ('sessionId');` + ).run(); + db.prepare( + ` + CREATE TABLE 'resourceAiModels' ( + 'resourceId' integer NOT NULL, + 'modelId' integer NOT NULL, + 'listType' text DEFAULT 'allow' NOT NULL, + PRIMARY KEY('resourceId', 'modelId'), + FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('modelId') REFERENCES 'aiModels'('modelId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + db.prepare( + ` + CREATE TABLE 'resourceAiProviders' ( + 'resourceId' integer NOT NULL, + 'providerId' integer NOT NULL, + 'accessMode' text DEFAULT 'inherit' NOT NULL, + 'enabled' integer DEFAULT true NOT NULL, + PRIMARY KEY('resourceId', 'providerId'), + FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('providerId') REFERENCES 'aiProviders'('providerId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + db.prepare( + ` + CREATE TABLE 'siteResourceAiModels' ( + 'siteResourceId' integer NOT NULL, + 'modelId' integer NOT NULL, + 'listType' text DEFAULT 'allow' NOT NULL, + PRIMARY KEY('siteResourceId', 'modelId'), + FOREIGN KEY ('siteResourceId') REFERENCES 'siteResources'('siteResourceId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('modelId') REFERENCES 'aiModels'('modelId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + db.prepare( + ` + CREATE TABLE 'siteResourceAiProviders' ( + 'siteResourceId' integer NOT NULL, + 'providerId' integer NOT NULL, + 'accessMode' text DEFAULT 'inherit' NOT NULL, + 'enabled' integer DEFAULT true NOT NULL, + PRIMARY KEY('siteResourceId', 'providerId'), + FOREIGN KEY ('siteResourceId') REFERENCES 'siteResources'('siteResourceId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('providerId') REFERENCES 'aiProviders'('providerId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + db.prepare( + ` + CREATE TABLE 'virtualApiKeyResources' ( + 'virtualApiKeyId' text NOT NULL, + 'resourceId' integer NOT NULL, + PRIMARY KEY('virtualApiKeyId', 'resourceId'), + FOREIGN KEY ('virtualApiKeyId') REFERENCES 'virtualApiKeys'('virtualApiKeyId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + db.prepare( + ` + CREATE TABLE 'virtualApiKeys' ( + 'virtualApiKeyId' text PRIMARY KEY NOT NULL, + 'orgId' text NOT NULL, + 'kind' text NOT NULL, + 'userId' text, + 'name' text, + 'description' text, + 'token' text NOT NULL, + 'lastChars' text NOT NULL, + 'allResources' integer DEFAULT false NOT NULL, + 'expiresAt' integer, + 'lastUsedAt' integer, + 'createdAt' integer NOT NULL, + 'createdByUserId' text, + FOREIGN KEY ('orgId') REFERENCES 'orgs'('orgId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('userId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('createdByUserId') REFERENCES 'user'('id') ON UPDATE no action ON DELETE set null + ); + ` + ).run(); + db.prepare( + ` + CREATE UNIQUE INDEX 'virtual_api_key_user_identity_uniq' ON 'virtualApiKeys' ('orgId','userId') WHERE "virtualApiKeys"."kind" = 'user'; + ` + ).run(); + + db.prepare( + `ALTER TABLE 'roles' ADD COLUMN 'sshSudoModeNew' text DEFAULT 'full';` + ).run(); + db.prepare( + `UPDATE 'roles' SET "sshSudoModeNew" = "sshSudoMode";` + ).run(); + db.prepare(`ALTER TABLE 'roles' DROP COLUMN "sshSudoMode";`).run(); + db.prepare( + `ALTER TABLE 'roles' RENAME COLUMN "sshSudoModeNew" TO "sshSudoMode";` + ).run(); + + db.prepare( + ` + CREATE TABLE '__new_targets' ( + 'targetId' integer PRIMARY KEY AUTOINCREMENT NOT NULL, + 'resourceId' integer, + 'providerId' integer, + 'siteId' integer NOT NULL, + 'ip' text NOT NULL, + 'method' text, + 'port' integer NOT NULL, + 'internalPort' integer, + 'enabled' integer DEFAULT true NOT NULL, + 'path' text, + 'pathMatchType' text, + 'rewritePath' text, + 'rewritePathType' text, + 'priority' integer DEFAULT 100 NOT NULL, + 'mode' text DEFAULT 'http' NOT NULL, + 'authToken' text, + FOREIGN KEY ('resourceId') REFERENCES 'resources'('resourceId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('providerId') REFERENCES 'aiProviders'('providerId') ON UPDATE no action ON DELETE cascade, + FOREIGN KEY ('siteId') REFERENCES 'sites'('siteId') ON UPDATE no action ON DELETE cascade + ); + ` + ).run(); + + db.prepare( + `INSERT INTO '__new_targets'("targetId", "resourceId", "providerId", "siteId", "ip", "method", "port", "internalPort", "enabled", "path", "pathMatchType", "rewritePath", "rewritePathType", "priority", "mode", "authToken") SELECT "targetId", "resourceId", "providerId", "siteId", "ip", "method", "port", "internalPort", "enabled", "path", "pathMatchType", "rewritePath", "rewritePathType", "priority", "mode", "authToken" FROM 'targets';` + ).run(); + db.prepare(`DROP TABLE 'targets';`).run(); + db.prepare( + `ALTER TABLE '__new_targets' RENAME TO 'targets';` + ).run(); + db.prepare( + `ALTER TABLE 'subscriptions' ADD 'override' integer DEFAULT false;` + ).run(); + db.prepare( + `ALTER TABLE 'clients' ADD 'exitNodeSubnet' text;` + ).run(); + db.prepare( + `ALTER TABLE 'orgs' ADD 'settingsLogRetentionDaysAISessions' integer DEFAULT 7 NOT NULL;` + ).run(); + db.prepare( + `ALTER TABLE 'siteResources' ADD 'requiresExitNodeConnection' integer DEFAULT false NOT NULL;` + ).run(); const insertRoleAction = db.prepare(` INSERT INTO 'roleActions' ("roleId", "actionId", "orgId")