diff --git a/apps/docs/content/docs/de/sdks/python.mdx b/apps/docs/content/docs/de/sdks/python.mdx index e8cd48d08..cbe7ad995 100644 --- a/apps/docs/content/docs/de/sdks/python.mdx +++ b/apps/docs/content/docs/de/sdks/python.mdx @@ -284,7 +284,6 @@ class AsyncExecutionResult: class WorkflowStatus: is_deployed: bool deployed_at: Optional[str] = None - is_published: bool = False needs_redeployment: bool = False ``` diff --git a/apps/docs/content/docs/de/sdks/typescript.mdx b/apps/docs/content/docs/de/sdks/typescript.mdx index 6e92c7edc..fed552b84 100644 --- a/apps/docs/content/docs/de/sdks/typescript.mdx +++ b/apps/docs/content/docs/de/sdks/typescript.mdx @@ -301,7 +301,6 @@ interface AsyncExecutionResult { interface WorkflowStatus { isDeployed: boolean; deployedAt?: string; - isPublished: boolean; needsRedeployment: boolean; } ``` diff --git a/apps/docs/content/docs/en/sdks/python.mdx b/apps/docs/content/docs/en/sdks/python.mdx index 0f932e530..3ab1cdee7 100644 --- a/apps/docs/content/docs/en/sdks/python.mdx +++ b/apps/docs/content/docs/en/sdks/python.mdx @@ -283,7 +283,6 @@ class AsyncExecutionResult: class WorkflowStatus: is_deployed: bool deployed_at: Optional[str] = None - is_published: bool = False needs_redeployment: bool = False ``` diff --git a/apps/docs/content/docs/en/sdks/typescript.mdx b/apps/docs/content/docs/en/sdks/typescript.mdx index ded437e06..6364384c8 100644 --- a/apps/docs/content/docs/en/sdks/typescript.mdx +++ b/apps/docs/content/docs/en/sdks/typescript.mdx @@ -294,7 +294,6 @@ interface AsyncExecutionResult { interface WorkflowStatus { isDeployed: boolean; deployedAt?: string; - isPublished: boolean; needsRedeployment: boolean; } ``` diff --git a/apps/docs/content/docs/es/sdks/python.mdx b/apps/docs/content/docs/es/sdks/python.mdx index a10e9c24d..cff0a2468 100644 --- a/apps/docs/content/docs/es/sdks/python.mdx +++ b/apps/docs/content/docs/es/sdks/python.mdx @@ -284,7 +284,6 @@ class AsyncExecutionResult: class WorkflowStatus: is_deployed: bool deployed_at: Optional[str] = None - is_published: bool = False needs_redeployment: bool = False ``` diff --git a/apps/docs/content/docs/es/sdks/typescript.mdx b/apps/docs/content/docs/es/sdks/typescript.mdx index c18c14194..58c3578c2 100644 --- a/apps/docs/content/docs/es/sdks/typescript.mdx +++ b/apps/docs/content/docs/es/sdks/typescript.mdx @@ -301,7 +301,6 @@ interface AsyncExecutionResult { interface WorkflowStatus { isDeployed: boolean; deployedAt?: string; - isPublished: boolean; needsRedeployment: boolean; } ``` diff --git a/apps/docs/content/docs/fr/sdks/python.mdx b/apps/docs/content/docs/fr/sdks/python.mdx index 08e743be7..268bc7657 100644 --- a/apps/docs/content/docs/fr/sdks/python.mdx +++ b/apps/docs/content/docs/fr/sdks/python.mdx @@ -284,7 +284,6 @@ class AsyncExecutionResult: class WorkflowStatus: is_deployed: bool deployed_at: Optional[str] = None - is_published: bool = False needs_redeployment: bool = False ``` diff --git a/apps/docs/content/docs/fr/sdks/typescript.mdx b/apps/docs/content/docs/fr/sdks/typescript.mdx index 5e18426f2..0c6e98781 100644 --- a/apps/docs/content/docs/fr/sdks/typescript.mdx +++ b/apps/docs/content/docs/fr/sdks/typescript.mdx @@ -301,7 +301,6 @@ interface AsyncExecutionResult { interface WorkflowStatus { isDeployed: boolean; deployedAt?: string; - isPublished: boolean; needsRedeployment: boolean; } ``` diff --git a/apps/docs/content/docs/ja/sdks/python.mdx b/apps/docs/content/docs/ja/sdks/python.mdx index a62f9f11b..de4467f8a 100644 --- a/apps/docs/content/docs/ja/sdks/python.mdx +++ b/apps/docs/content/docs/ja/sdks/python.mdx @@ -284,7 +284,6 @@ class AsyncExecutionResult: class WorkflowStatus: is_deployed: bool deployed_at: Optional[str] = None - is_published: bool = False needs_redeployment: bool = False ``` diff --git a/apps/docs/content/docs/ja/sdks/typescript.mdx b/apps/docs/content/docs/ja/sdks/typescript.mdx index 91fa16cfd..a224c7663 100644 --- a/apps/docs/content/docs/ja/sdks/typescript.mdx +++ b/apps/docs/content/docs/ja/sdks/typescript.mdx @@ -301,7 +301,6 @@ interface AsyncExecutionResult { interface WorkflowStatus { isDeployed: boolean; deployedAt?: string; - isPublished: boolean; needsRedeployment: boolean; } ``` diff --git a/apps/docs/content/docs/zh/sdks/python.mdx b/apps/docs/content/docs/zh/sdks/python.mdx index 3af55f8d5..c44973c86 100644 --- a/apps/docs/content/docs/zh/sdks/python.mdx +++ b/apps/docs/content/docs/zh/sdks/python.mdx @@ -284,7 +284,6 @@ class AsyncExecutionResult: class WorkflowStatus: is_deployed: bool deployed_at: Optional[str] = None - is_published: bool = False needs_redeployment: bool = False ``` diff --git a/apps/docs/content/docs/zh/sdks/typescript.mdx b/apps/docs/content/docs/zh/sdks/typescript.mdx index bbb36feee..0f038db92 100644 --- a/apps/docs/content/docs/zh/sdks/typescript.mdx +++ b/apps/docs/content/docs/zh/sdks/typescript.mdx @@ -301,7 +301,6 @@ interface AsyncExecutionResult { interface WorkflowStatus { isDeployed: boolean; deployedAt?: string; - isPublished: boolean; needsRedeployment: boolean; } ``` diff --git a/apps/sim/app/api/billing/portal/route.ts b/apps/sim/app/api/billing/portal/route.ts index 017fbb8bd..959a83cd7 100644 --- a/apps/sim/app/api/billing/portal/route.ts +++ b/apps/sim/app/api/billing/portal/route.ts @@ -1,6 +1,6 @@ import { db } from '@sim/db' import { subscription as subscriptionTable, user } from '@sim/db/schema' -import { and, eq } from 'drizzle-orm' +import { and, eq, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' import { requireStripeClient } from '@/lib/billing/stripe-client' @@ -38,7 +38,10 @@ export async function POST(request: NextRequest) { .where( and( eq(subscriptionTable.referenceId, organizationId), - eq(subscriptionTable.status, 'active') + or( + eq(subscriptionTable.status, 'active'), + eq(subscriptionTable.cancelAtPeriodEnd, true) + ) ) ) .limit(1) diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts index 9a827c9ec..18e2fa0d6 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts @@ -109,17 +109,17 @@ describe('Webhook Trigger API Route', () => { globalMockData.workflows.push({ id: 'test-workflow-id', userId: 'test-user-id', - pinnedApiKeyId: 'test-pinned-api-key-id', + workspaceId: 'test-workspace-id', }) - vi.doMock('@/lib/api-key/service', async () => { - const actual = await vi.importActual('@/lib/api-key/service') + vi.doMock('@/lib/workspaces/utils', async () => { + const actual = await vi.importActual('@/lib/workspaces/utils') return { ...(actual as Record), - getApiKeyOwnerUserId: vi + getWorkspaceBilledAccountUserId: vi .fn() - .mockImplementation(async (pinnedApiKeyId: string | null | undefined) => - pinnedApiKeyId ? 'test-user-id' : null + .mockImplementation(async (workspaceId: string | null | undefined) => + workspaceId ? 'test-user-id' : null ), } }) @@ -240,7 +240,7 @@ describe('Webhook Trigger API Route', () => { globalMockData.workflows.push({ id: 'test-workflow-id', userId: 'test-user-id', - pinnedApiKeyId: 'test-pinned-api-key-id', + workspaceId: 'test-workspace-id', }) const req = createMockRequest('POST', { event: 'test', id: 'test-123' }) @@ -272,7 +272,7 @@ describe('Webhook Trigger API Route', () => { globalMockData.workflows.push({ id: 'test-workflow-id', userId: 'test-user-id', - pinnedApiKeyId: 'test-pinned-api-key-id', + workspaceId: 'test-workspace-id', }) const headers = { @@ -307,7 +307,7 @@ describe('Webhook Trigger API Route', () => { globalMockData.workflows.push({ id: 'test-workflow-id', userId: 'test-user-id', - pinnedApiKeyId: 'test-pinned-api-key-id', + workspaceId: 'test-workspace-id', }) const headers = { @@ -338,7 +338,7 @@ describe('Webhook Trigger API Route', () => { globalMockData.workflows.push({ id: 'test-workflow-id', userId: 'test-user-id', - pinnedApiKeyId: 'test-pinned-api-key-id', + workspaceId: 'test-workspace-id', }) vi.doMock('@trigger.dev/sdk', () => ({ @@ -388,7 +388,7 @@ describe('Webhook Trigger API Route', () => { globalMockData.workflows.push({ id: 'test-workflow-id', userId: 'test-user-id', - pinnedApiKeyId: 'test-pinned-api-key-id', + workspaceId: 'test-workspace-id', }) vi.doMock('@trigger.dev/sdk', () => ({ diff --git a/apps/sim/app/api/workflows/[id]/deploy/route.ts b/apps/sim/app/api/workflows/[id]/deploy/route.ts index e93f9f6dd..e3a4e2323 100644 --- a/apps/sim/app/api/workflows/[id]/deploy/route.ts +++ b/apps/sim/app/api/workflows/[id]/deploy/route.ts @@ -1,6 +1,6 @@ -import { apiKey, db, workflow, workflowDeploymentVersion } from '@sim/db' +import { db, workflow, workflowDeploymentVersion } from '@sim/db' import { and, desc, eq } from 'drizzle-orm' -import { type NextRequest, NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' import { generateRequestId } from '@/lib/utils' import { deployWorkflow } from '@/lib/workflows/db-helpers' @@ -38,35 +38,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ }) } - let keyInfo: { name: string; type: 'personal' | 'workspace' } | null = null - - if (workflowData.pinnedApiKeyId) { - const pinnedKey = await db - .select({ key: apiKey.key, name: apiKey.name, type: apiKey.type }) - .from(apiKey) - .where(eq(apiKey.id, workflowData.pinnedApiKeyId)) - .limit(1) - - if (pinnedKey.length > 0) { - keyInfo = { name: pinnedKey[0].name, type: pinnedKey[0].type as 'personal' | 'workspace' } - } - } else { - const userApiKey = await db - .select({ - key: apiKey.key, - name: apiKey.name, - type: apiKey.type, - }) - .from(apiKey) - .where(and(eq(apiKey.userId, workflowData.userId), eq(apiKey.type, 'personal'))) - .orderBy(desc(apiKey.lastUsed), desc(apiKey.createdAt)) - .limit(1) - - if (userApiKey.length > 0) { - keyInfo = { name: userApiKey[0].name, type: userApiKey[0].type as 'personal' | 'workspace' } - } - } - let needsRedeployment = false const [active] = await db .select({ state: workflowDeploymentVersion.state }) @@ -97,7 +68,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ logger.info(`[${requestId}] Successfully retrieved deployment info: ${id}`) - const responseApiKeyInfo = keyInfo ? `${keyInfo.name} (${keyInfo.type})` : 'No API key found' + const responseApiKeyInfo = workflowData.workspaceId ? 'Workspace API keys' : 'Personal API keys' return createSuccessResponse({ apiKey: responseApiKeyInfo, @@ -127,101 +98,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ return createErrorResponse(error.message, error.status) } - const userId = workflowData!.userId - - let providedApiKey: string | null = null - try { - const parsed = await request.json() - if (parsed && typeof parsed.apiKey === 'string' && parsed.apiKey.trim().length > 0) { - providedApiKey = parsed.apiKey.trim() - } - } catch (_err) {} - - logger.debug(`[${requestId}] Validating API key for deployment`) - - let keyInfo: { name: string; type: 'personal' | 'workspace' } | null = null - let matchedKey: { - id: string - key: string - name: string - type: 'personal' | 'workspace' - } | null = null - - // Use provided API key, or fall back to existing pinned API key for redeployment - const apiKeyToUse = providedApiKey || workflowData!.pinnedApiKeyId - - if (!apiKeyToUse) { - return NextResponse.json( - { error: 'API key is required. Please create or select an API key before deploying.' }, - { status: 400 } - ) - } - - let isValidKey = false - - const currentUserId = session?.user?.id - - if (currentUserId) { - const [personalKey] = await db - .select({ - id: apiKey.id, - key: apiKey.key, - name: apiKey.name, - expiresAt: apiKey.expiresAt, - }) - .from(apiKey) - .where( - and( - eq(apiKey.id, apiKeyToUse), - eq(apiKey.userId, currentUserId), - eq(apiKey.type, 'personal') - ) - ) - .limit(1) - - if (personalKey) { - if (!personalKey.expiresAt || personalKey.expiresAt >= new Date()) { - matchedKey = { ...personalKey, type: 'personal' } - isValidKey = true - keyInfo = { name: personalKey.name, type: 'personal' } - } - } - } - - if (!isValidKey) { - if (workflowData!.workspaceId) { - const [workspaceKey] = await db - .select({ - id: apiKey.id, - key: apiKey.key, - name: apiKey.name, - expiresAt: apiKey.expiresAt, - }) - .from(apiKey) - .where( - and( - eq(apiKey.id, apiKeyToUse), - eq(apiKey.workspaceId, workflowData!.workspaceId), - eq(apiKey.type, 'workspace') - ) - ) - .limit(1) - - if (workspaceKey) { - if (!workspaceKey.expiresAt || workspaceKey.expiresAt >= new Date()) { - matchedKey = { ...workspaceKey, type: 'workspace' } - isValidKey = true - keyInfo = { name: workspaceKey.name, type: 'workspace' } - } - } - } - } - - if (!isValidKey) { - logger.warn(`[${requestId}] Invalid API key ID provided for workflow deployment: ${id}`) - return createErrorResponse('Invalid API key provided', 400) - } - // Attribution: this route is UI-only; require session user as actor const actorUserId: string | null = session?.user?.id ?? null if (!actorUserId) { @@ -232,8 +108,6 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ const deployResult = await deployWorkflow({ workflowId: id, deployedBy: actorUserId, - pinnedApiKeyId: matchedKey?.id, - includeDeployedState: true, workflowName: workflowData!.name, }) @@ -243,20 +117,11 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ const deployedAt = deployResult.deployedAt! - if (matchedKey) { - try { - await db - .update(apiKey) - .set({ lastUsed: new Date(), updatedAt: new Date() }) - .where(eq(apiKey.id, matchedKey.id)) - } catch (e) { - logger.warn(`[${requestId}] Failed to update lastUsed for api key`) - } - } - logger.info(`[${requestId}] Workflow deployed successfully: ${id}`) - const responseApiKeyInfo = keyInfo ? `${keyInfo.name} (${keyInfo.type})` : 'Default key' + const responseApiKeyInfo = workflowData!.workspaceId + ? 'Workspace API keys' + : 'Personal API keys' return createSuccessResponse({ apiKey: responseApiKeyInfo, @@ -298,7 +163,7 @@ export async function DELETE( await tx .update(workflow) - .set({ isDeployed: false, deployedAt: null, deployedState: null, pinnedApiKeyId: null }) + .set({ isDeployed: false, deployedAt: null }) .where(eq(workflow.id, id)) }) diff --git a/apps/sim/app/api/workflows/[id]/deployments/[version]/activate/route.ts b/apps/sim/app/api/workflows/[id]/deployments/[version]/activate/route.ts index c9b636a87..ceb51049d 100644 --- a/apps/sim/app/api/workflows/[id]/deployments/[version]/activate/route.ts +++ b/apps/sim/app/api/workflows/[id]/deployments/[version]/activate/route.ts @@ -1,4 +1,4 @@ -import { apiKey, db, workflow, workflowDeploymentVersion } from '@sim/db' +import { db, workflow, workflowDeploymentVersion } from '@sim/db' import { and, eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { createLogger } from '@/lib/logs/console/logger' @@ -19,11 +19,7 @@ export async function POST( const { id, version } = await params try { - const { - error, - session, - workflow: workflowData, - } = await validateWorkflowPermissions(id, requestId, 'admin') + const { error } = await validateWorkflowPermissions(id, requestId, 'admin') if (error) { return createErrorResponse(error.message, error.status) } @@ -33,52 +29,6 @@ export async function POST( return createErrorResponse('Invalid version', 400) } - let providedApiKey: string | null = null - try { - const parsed = await request.json() - if (parsed && typeof parsed.apiKey === 'string' && parsed.apiKey.trim().length > 0) { - providedApiKey = parsed.apiKey.trim() - } - } catch (_err) {} - - let pinnedApiKeyId: string | null = null - if (providedApiKey) { - const currentUserId = session?.user?.id - if (currentUserId) { - const [personalKey] = await db - .select({ id: apiKey.id }) - .from(apiKey) - .where( - and( - eq(apiKey.id, providedApiKey), - eq(apiKey.userId, currentUserId), - eq(apiKey.type, 'personal') - ) - ) - .limit(1) - - if (personalKey) { - pinnedApiKeyId = personalKey.id - } else if (workflowData!.workspaceId) { - const [workspaceKey] = await db - .select({ id: apiKey.id }) - .from(apiKey) - .where( - and( - eq(apiKey.id, providedApiKey), - eq(apiKey.workspaceId, workflowData!.workspaceId), - eq(apiKey.type, 'workspace') - ) - ) - .limit(1) - - if (workspaceKey) { - pinnedApiKeyId = workspaceKey.id - } - } - } - } - const now = new Date() await db.transaction(async (tx) => { @@ -112,10 +62,6 @@ export async function POST( deployedAt: now, } - if (pinnedApiKeyId) { - updateData.pinnedApiKeyId = pinnedApiKeyId - } - await tx.update(workflow).set(updateData).where(eq(workflow.id, id)) }) diff --git a/apps/sim/app/api/workflows/[id]/duplicate/route.ts b/apps/sim/app/api/workflows/[id]/duplicate/route.ts index 9daec4b63..12d5a0603 100644 --- a/apps/sim/app/api/workflows/[id]/duplicate/route.ts +++ b/apps/sim/app/api/workflows/[id]/duplicate/route.ts @@ -96,7 +96,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: createdAt: now, updatedAt: now, isDeployed: false, - collaborators: [], runCount: 0, // Duplicate variables with new IDs and new workflowId variables: (() => { @@ -112,8 +111,6 @@ export async function POST(req: NextRequest, { params }: { params: Promise<{ id: } return remapped })(), - isPublished: false, - marketplaceData: null, }) // Copy all blocks from source workflow with new IDs diff --git a/apps/sim/app/api/workflows/[id]/status/route.ts b/apps/sim/app/api/workflows/[id]/status/route.ts index c89af4ed6..d9d655774 100644 --- a/apps/sim/app/api/workflows/[id]/status/route.ts +++ b/apps/sim/app/api/workflows/[id]/status/route.ts @@ -62,7 +62,6 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ return createSuccessResponse({ isDeployed: validation.workflow.isDeployed, deployedAt: validation.workflow.deployedAt, - isPublished: validation.workflow.isPublished, needsRedeployment, }) } catch (error) { diff --git a/apps/sim/app/api/workflows/middleware.ts b/apps/sim/app/api/workflows/middleware.ts index 8ac40caed..f23510ba1 100644 --- a/apps/sim/app/api/workflows/middleware.ts +++ b/apps/sim/app/api/workflows/middleware.ts @@ -1,6 +1,9 @@ import type { NextRequest } from 'next/server' -import { authenticateApiKey } from '@/lib/api-key/auth' -import { authenticateApiKeyFromHeader, updateApiKeyLastUsed } from '@/lib/api-key/service' +import { + type ApiKeyAuthResult, + authenticateApiKeyFromHeader, + updateApiKeyLastUsed, +} from '@/lib/api-key/service' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' import { getWorkflowById } from '@/lib/workflows/utils' @@ -60,50 +63,39 @@ export async function validateWorkflowAccess( } } - // If a pinned key exists, only accept that specific key - if (workflow.pinnedApiKey?.key) { - const isValidPinnedKey = await authenticateApiKey(apiKeyHeader, workflow.pinnedApiKey.key) - if (!isValidPinnedKey) { - return { - error: { - message: 'Unauthorized: Invalid API key', - status: 401, - }, - } + let validResult: ApiKeyAuthResult | null = null + + if (workflow.workspaceId) { + const workspaceResult = await authenticateApiKeyFromHeader(apiKeyHeader, { + workspaceId: workflow.workspaceId as string, + keyTypes: ['workspace', 'personal'], + }) + + if (workspaceResult.success) { + validResult = workspaceResult } } else { - // Try personal keys first const personalResult = await authenticateApiKeyFromHeader(apiKeyHeader, { userId: workflow.userId as string, keyTypes: ['personal'], }) - let validResult = null if (personalResult.success) { validResult = personalResult - } else if (workflow.workspaceId) { - // Try workspace keys - const workspaceResult = await authenticateApiKeyFromHeader(apiKeyHeader, { - workspaceId: workflow.workspaceId as string, - keyTypes: ['workspace'], - }) - - if (workspaceResult.success) { - validResult = workspaceResult - } } + } - // If no valid key found, reject - if (!validResult) { - return { - error: { - message: 'Unauthorized: Invalid API key', - status: 401, - }, - } + if (!validResult) { + return { + error: { + message: 'Unauthorized: Invalid API key', + status: 401, + }, } + } - await updateApiKeyLastUsed(validResult.keyId!) + if (validResult.keyId) { + await updateApiKeyLastUsed(validResult.keyId) } } return { workflow } diff --git a/apps/sim/app/api/workflows/route.ts b/apps/sim/app/api/workflows/route.ts index b541fa7ae..5664c2bc2 100644 --- a/apps/sim/app/api/workflows/route.ts +++ b/apps/sim/app/api/workflows/route.ts @@ -143,11 +143,8 @@ export async function POST(req: NextRequest) { createdAt: now, updatedAt: now, isDeployed: false, - collaborators: [], runCount: 0, variables: {}, - isPublished: false, - marketplaceData: null, }) logger.info(`[${requestId}] Successfully created empty workflow ${workflowId}`) diff --git a/apps/sim/app/api/workspaces/[id]/api-keys/route.ts b/apps/sim/app/api/workspaces/[id]/api-keys/route.ts index 8b5b190e0..f6e82a658 100644 --- a/apps/sim/app/api/workspaces/[id]/api-keys/route.ts +++ b/apps/sim/app/api/workspaces/[id]/api-keys/route.ts @@ -94,10 +94,27 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ const userId = session.user.id const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId) - if (!permission || (permission !== 'admin' && permission !== 'write')) { + if (permission !== 'admin') { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) } + const workspaceRows = await db + .select({ billedAccountUserId: workspace.billedAccountUserId }) + .from(workspace) + .where(eq(workspace.id, workspaceId)) + .limit(1) + + if (!workspaceRows.length) { + return NextResponse.json({ error: 'Workspace not found' }, { status: 404 }) + } + + if (workspaceRows[0].billedAccountUserId !== userId) { + return NextResponse.json( + { error: 'Only the workspace billing account can create workspace API keys' }, + { status: 403 } + ) + } + const body = await request.json() const { name } = CreateKeySchema.parse(body) @@ -181,10 +198,27 @@ export async function DELETE( const userId = session.user.id const permission = await getUserEntityPermissions(userId, 'workspace', workspaceId) - if (!permission || (permission !== 'admin' && permission !== 'write')) { + if (permission !== 'admin') { return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) } + const workspaceRows = await db + .select({ billedAccountUserId: workspace.billedAccountUserId }) + .from(workspace) + .where(eq(workspace.id, workspaceId)) + .limit(1) + + if (!workspaceRows.length) { + return NextResponse.json({ error: 'Workspace not found' }, { status: 404 }) + } + + if (workspaceRows[0].billedAccountUserId !== userId) { + return NextResponse.json( + { error: 'Only the workspace billing account can delete workspace API keys' }, + { status: 403 } + ) + } + const body = await request.json() const { keys } = DeleteKeysSchema.parse(body) diff --git a/apps/sim/app/api/workspaces/[id]/permissions/route.ts b/apps/sim/app/api/workspaces/[id]/permissions/route.ts index 531ea7475..0a3145d52 100644 --- a/apps/sim/app/api/workspaces/[id]/permissions/route.ts +++ b/apps/sim/app/api/workspaces/[id]/permissions/route.ts @@ -1,6 +1,6 @@ import crypto from 'crypto' import { db } from '@sim/db' -import { permissions, type permissionTypeEnum } from '@sim/db/schema' +import { permissions, type permissionTypeEnum, workspace } from '@sim/db/schema' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' @@ -94,6 +94,18 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< const body: UpdatePermissionsRequest = await request.json() + const workspaceRow = await db + .select({ billedAccountUserId: workspace.billedAccountUserId }) + .from(workspace) + .where(eq(workspace.id, workspaceId)) + .limit(1) + + if (!workspaceRow.length) { + return NextResponse.json({ error: 'Workspace not found' }, { status: 404 }) + } + + const billedAccountUserId = workspaceRow[0].billedAccountUserId + const selfUpdate = body.updates.find((update) => update.userId === session.user.id) if (selfUpdate && selfUpdate.permissions !== 'admin') { return NextResponse.json( @@ -102,6 +114,18 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< ) } + if ( + billedAccountUserId && + body.updates.some( + (update) => update.userId === billedAccountUserId && update.permissions !== 'admin' + ) + ) { + return NextResponse.json( + { error: 'Workspace billing account must retain admin permissions' }, + { status: 400 } + ) + } + await db.transaction(async (tx) => { for (const update of body.updates) { await tx diff --git a/apps/sim/app/api/workspaces/[id]/route.ts b/apps/sim/app/api/workspaces/[id]/route.ts index 10389e8fc..18bd87161 100644 --- a/apps/sim/app/api/workspaces/[id]/route.ts +++ b/apps/sim/app/api/workspaces/[id]/route.ts @@ -100,22 +100,95 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< } try { - const { name } = await request.json() + const body = await request.json() + const { + name, + billedAccountUserId, + allowPersonalApiKeys, + }: { + name?: string + billedAccountUserId?: string + allowPersonalApiKeys?: boolean + } = body ?? {} - if (!name) { - return NextResponse.json({ error: 'Name is required' }, { status: 400 }) + if ( + name === undefined && + billedAccountUserId === undefined && + allowPersonalApiKeys === undefined + ) { + return NextResponse.json({ error: 'No updates provided' }, { status: 400 }) } - // Update workspace - await db - .update(workspace) - .set({ - name, - updatedAt: new Date(), - }) + const existingWorkspace = await db + .select() + .from(workspace) .where(eq(workspace.id, workspaceId)) + .then((rows) => rows[0]) + + if (!existingWorkspace) { + return NextResponse.json({ error: 'Workspace not found' }, { status: 404 }) + } + + const updateData: Record = {} + + if (name !== undefined) { + const trimmedName = name.trim() + if (!trimmedName) { + return NextResponse.json({ error: 'Name is required' }, { status: 400 }) + } + updateData.name = trimmedName + } + + if (allowPersonalApiKeys !== undefined) { + updateData.allowPersonalApiKeys = Boolean(allowPersonalApiKeys) + } + + if (billedAccountUserId !== undefined) { + const candidateId = billedAccountUserId?.trim() + + if (!candidateId) { + return NextResponse.json({ error: 'billedAccountUserId is required' }, { status: 400 }) + } + + const isOwner = candidateId === existingWorkspace.ownerId + + let hasAdminAccess = isOwner + + if (!hasAdminAccess) { + const adminPermission = await db + .select({ id: permissions.id }) + .from(permissions) + .where( + and( + eq(permissions.entityType, 'workspace'), + eq(permissions.entityId, workspaceId), + eq(permissions.userId, candidateId), + eq(permissions.permissionType, 'admin') + ) + ) + .limit(1) + + hasAdminAccess = adminPermission.length > 0 + } + + if (!hasAdminAccess) { + return NextResponse.json( + { error: 'Billed account must be a workspace admin' }, + { status: 400 } + ) + } + + updateData.billedAccountUserId = candidateId + } + + if (Object.keys(updateData).length === 0) { + return NextResponse.json({ error: 'No valid updates provided' }, { status: 400 }) + } + + updateData.updatedAt = new Date() + + await db.update(workspace).set(updateData).where(eq(workspace.id, workspaceId)) - // Get updated workspace const updatedWorkspace = await db .select() .from(workspace) diff --git a/apps/sim/app/api/workspaces/members/[id]/route.ts b/apps/sim/app/api/workspaces/members/[id]/route.ts index 68f1e4b5d..26a3f32f5 100644 --- a/apps/sim/app/api/workspaces/members/[id]/route.ts +++ b/apps/sim/app/api/workspaces/members/[id]/route.ts @@ -1,5 +1,5 @@ import { db } from '@sim/db' -import { permissions } from '@sim/db/schema' +import { permissions, workspace } from '@sim/db/schema' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { getSession } from '@/lib/auth' @@ -23,6 +23,23 @@ export async function DELETE(req: NextRequest, { params }: { params: Promise<{ i return NextResponse.json({ error: 'Workspace ID is required' }, { status: 400 }) } + const workspaceRow = await db + .select({ billedAccountUserId: workspace.billedAccountUserId }) + .from(workspace) + .where(eq(workspace.id, workspaceId)) + .limit(1) + + if (!workspaceRow.length) { + return NextResponse.json({ error: 'Workspace not found' }, { status: 404 }) + } + + if (workspaceRow[0].billedAccountUserId === userId) { + return NextResponse.json( + { error: 'Cannot remove the workspace billing account. Please reassign billing first.' }, + { status: 400 } + ) + } + // Check if the user to be removed actually has permissions for this workspace const userPermission = await db .select() diff --git a/apps/sim/app/api/workspaces/route.ts b/apps/sim/app/api/workspaces/route.ts index 736256d1e..2e74a0190 100644 --- a/apps/sim/app/api/workspaces/route.ts +++ b/apps/sim/app/api/workspaces/route.ts @@ -95,6 +95,8 @@ async function createWorkspace(userId: string, name: string) { id: workspaceId, name, ownerId: userId, + billedAccountUserId: userId, + allowPersonalApiKeys: true, createdAt: now, updatedAt: now, }) @@ -124,11 +126,8 @@ async function createWorkspace(userId: string, name: string) { createdAt: now, updatedAt: now, isDeployed: false, - collaborators: [], runCount: 0, variables: {}, - isPublished: false, - marketplaceData: null, }) // No blocks are inserted - empty canvas @@ -147,6 +146,8 @@ async function createWorkspace(userId: string, name: string) { id: workspaceId, name, ownerId: userId, + billedAccountUserId: userId, + allowPersonalApiKeys: true, createdAt: now, updatedAt: now, role: 'owner', diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/api-key-selector/api-key-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/api-key-selector/api-key-selector.tsx deleted file mode 100644 index 185d45a7f..000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/api-key-selector/api-key-selector.tsx +++ /dev/null @@ -1,463 +0,0 @@ -'use client' - -import { useEffect, useState } from 'react' -import { Check, Copy, Info, Loader2, Plus } from 'lucide-react' -import { useParams } from 'next/navigation' -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - Button, - Input, - Label, - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectLabel, - SelectTrigger, - SelectValue, - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@/components/ui' -import { createLogger } from '@/lib/logs/console/logger' -import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' - -const logger = createLogger('ApiKeySelector') - -export interface ApiKey { - id: string - name: string - key: string - displayKey?: string - lastUsed?: string - createdAt: string - expiresAt?: string - createdBy?: string -} - -interface ApiKeysData { - workspace: ApiKey[] - personal: ApiKey[] -} - -interface ApiKeySelectorProps { - value: string - onChange: (keyId: string) => void - disabled?: boolean - apiKeys?: ApiKey[] - onApiKeyCreated?: () => void - showLabel?: boolean - label?: string - isDeployed?: boolean - deployedApiKeyDisplay?: string -} - -export function ApiKeySelector({ - value, - onChange, - disabled = false, - apiKeys = [], - onApiKeyCreated, - showLabel = true, - label = 'API Key', - isDeployed = false, - deployedApiKeyDisplay, -}: ApiKeySelectorProps) { - const params = useParams() - const workspaceId = (params?.workspaceId as string) || '' - const userPermissions = useUserPermissionsContext() - const canCreateWorkspaceKeys = userPermissions.canEdit || userPermissions.canAdmin - - const [apiKeysData, setApiKeysData] = useState(null) - const [isCreatingKey, setIsCreatingKey] = useState(false) - const [newKeyName, setNewKeyName] = useState('') - const [keyType, setKeyType] = useState<'personal' | 'workspace'>('personal') - const [newKey, setNewKey] = useState(null) - const [showNewKeyDialog, setShowNewKeyDialog] = useState(false) - const [copySuccess, setCopySuccess] = useState(false) - const [isSubmittingCreate, setIsSubmittingCreate] = useState(false) - const [keysLoaded, setKeysLoaded] = useState(false) - const [createError, setCreateError] = useState(null) - const [justCreatedKeyId, setJustCreatedKeyId] = useState(null) - - useEffect(() => { - fetchApiKeys() - }, [workspaceId]) - - const fetchApiKeys = async () => { - try { - setKeysLoaded(false) - const [workspaceRes, personalRes] = await Promise.all([ - fetch(`/api/workspaces/${workspaceId}/api-keys`), - fetch('/api/users/me/api-keys'), - ]) - - const workspaceData = workspaceRes.ok ? await workspaceRes.json() : { keys: [] } - const personalData = personalRes.ok ? await personalRes.json() : { keys: [] } - - setApiKeysData({ - workspace: workspaceData.keys || [], - personal: personalData.keys || [], - }) - setKeysLoaded(true) - } catch (error) { - logger.error('Error fetching API keys:', { error }) - setKeysLoaded(true) - } - } - - const handleCreateKey = async () => { - if (!newKeyName.trim()) { - setCreateError('Please enter a name for the API key') - return - } - - try { - setIsSubmittingCreate(true) - setCreateError(null) - - const endpoint = - keyType === 'workspace' - ? `/api/workspaces/${workspaceId}/api-keys` - : '/api/users/me/api-keys' - - const response = await fetch(endpoint, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: newKeyName }), - }) - - if (!response.ok) { - const error = await response.json() - throw new Error(error.error || 'Failed to create API key') - } - - const data = await response.json() - setNewKey(data.key) - setJustCreatedKeyId(data.key.id) - setShowNewKeyDialog(true) - setIsCreatingKey(false) - setNewKeyName('') - - // Refresh API keys - await fetchApiKeys() - onApiKeyCreated?.() - } catch (error: any) { - setCreateError(error.message || 'Failed to create API key') - } finally { - setIsSubmittingCreate(false) - } - } - - const handleCopyKey = async () => { - if (newKey?.key) { - await navigator.clipboard.writeText(newKey.key) - setCopySuccess(true) - setTimeout(() => setCopySuccess(false), 2000) - } - } - - if (isDeployed && deployedApiKeyDisplay) { - return ( -
- {showLabel && ( -
- - - - - - - -

Owner is billed for usage

-
-
-
-
- )} -
-
-
-              {(() => {
-                const match = deployedApiKeyDisplay.match(/^(.*?)\s+\(([^)]+)\)$/)
-                if (match) {
-                  return match[1].trim()
-                }
-                return deployedApiKeyDisplay
-              })()}
-            
- {(() => { - const match = deployedApiKeyDisplay.match(/^(.*?)\s+\(([^)]+)\)$/) - if (match) { - const type = match[2] - return ( -
- - {type} - -
- ) - } - return null - })()} -
-
-
- ) - } - - return ( - <> -
- {showLabel && ( -
-
- - - - - - - -

Key Owner is Billed

-
-
-
-
- {!disabled && ( - - )} -
- )} - -
- - {/* Create Key Dialog */} - - - - Create new API key - - {keyType === 'workspace' - ? "This key will have access to all workflows in this workspace. Make sure to copy it after creation as you won't be able to see it again." - : "This key will have access to your personal workflows. Make sure to copy it after creation as you won't be able to see it again."} - - - -
- {canCreateWorkspaceKeys && ( -
-

API Key Type

-
- - -
-
- )} - -
- - { - setNewKeyName(e.target.value) - if (createError) setCreateError(null) - }} - disabled={isSubmittingCreate} - /> - {createError &&

{createError}

} -
-
- - - { - setNewKeyName('') - setCreateError(null) - }} - > - Cancel - - { - e.preventDefault() - handleCreateKey() - }} - > - {isSubmittingCreate ? ( - <> - - Creating... - - ) : ( - 'Create' - )} - - -
-
- - {/* New Key Dialog */} - { - setShowNewKeyDialog(open) - if (!open) { - setNewKey(null) - setCopySuccess(false) - if (justCreatedKeyId) { - onChange(justCreatedKeyId) - setJustCreatedKeyId(null) - } - } - }} - > - - - Your API key has been created - - This is the only time you will see your API key.{' '} - Copy it now and store it securely. - - - - {newKey && ( -
-
- - {newKey.key} - -
- -
- )} -
-
- - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deploy-form/deploy-form.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deploy-form/deploy-form.tsx deleted file mode 100644 index 98408d731..000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deploy-form/deploy-form.tsx +++ /dev/null @@ -1,94 +0,0 @@ -'use client' - -import { useEffect } from 'react' -import { zodResolver } from '@hookform/resolvers/zod' -import { useForm } from 'react-hook-form' -import { z } from 'zod' -import { Form, FormField, FormItem, FormMessage } from '@/components/ui/form' -import { createLogger } from '@/lib/logs/console/logger' -import { - type ApiKey, - ApiKeySelector, -} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/api-key-selector/api-key-selector' - -const logger = createLogger('DeployForm') - -// Form schema for API key selection or creation -const deployFormSchema = z.object({ - apiKey: z.string().min(1, 'Please select an API key'), - newKeyName: z.string().optional(), -}) - -type DeployFormValues = z.infer - -interface DeployFormProps { - apiKeys: ApiKey[] - selectedApiKeyId: string - onApiKeyChange: (keyId: string) => void - onSubmit: (data: DeployFormValues) => void - onApiKeyCreated?: () => void - formId?: string - isDeployed?: boolean - deployedApiKeyDisplay?: string -} - -export function DeployForm({ - apiKeys, - selectedApiKeyId, - onApiKeyChange, - onSubmit, - onApiKeyCreated, - formId, - isDeployed = false, - deployedApiKeyDisplay, -}: DeployFormProps) { - const form = useForm({ - resolver: zodResolver(deployFormSchema), - defaultValues: { - apiKey: selectedApiKeyId || (apiKeys.length > 0 ? apiKeys[0].id : ''), - newKeyName: '', - }, - }) - - useEffect(() => { - if (selectedApiKeyId) { - form.setValue('apiKey', selectedApiKeyId) - } - }, [selectedApiKeyId, form]) - - return ( -
- { - e.preventDefault() - onSubmit(form.getValues()) - }} - className='space-y-6' - > - ( - - { - field.onChange(keyId) - onApiKeyChange(keyId) - }} - apiKeys={apiKeys} - onApiKeyCreated={onApiKeyCreated} - showLabel={true} - label='Select API Key' - isDeployed={isDeployed} - deployedApiKeyDisplay={deployedApiKeyDisplay} - /> - - - )} - /> - - - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx index 26cbdc338..d49a11e8b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/deployment-info.tsx @@ -17,7 +17,6 @@ import { } from '@/components/ui' import { ApiEndpoint, - ApiKey, DeployStatus, ExampleCommand, } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/deployment-info/components' @@ -114,7 +113,6 @@ export function DeploymentInfo({
- void @@ -41,15 +37,6 @@ interface DeployModalProps { refetchDeployedState: () => Promise } -interface ApiKey { - id: string - name: string - key: string - lastUsed?: string - createdAt: string - expiresAt?: string -} - interface WorkflowDeploymentInfo { isDeployed: boolean deployedAt?: string @@ -59,12 +46,7 @@ interface WorkflowDeploymentInfo { needsRedeployment: boolean } -interface DeployFormValues { - apiKey: string - newKeyName?: string -} - -type TabView = 'general' | 'api' | 'versions' | 'chat' +type TabView = 'api' | 'versions' | 'chat' export function DeployModal({ open, @@ -85,9 +67,11 @@ export function DeployModal({ const [isUndeploying, setIsUndeploying] = useState(false) const [deploymentInfo, setDeploymentInfo] = useState(null) const [isLoading, setIsLoading] = useState(false) - const [apiKeys, setApiKeys] = useState([]) - const [activeTab, setActiveTab] = useState('general') - const [selectedApiKeyId, setSelectedApiKeyId] = useState('') + const workflowMetadata = useWorkflowRegistry((state) => + workflowId ? state.workflows[workflowId] : undefined + ) + const workflowWorkspaceId = workflowMetadata?.workspaceId ?? null + const [activeTab, setActiveTab] = useState('api') const [chatSubmitting, setChatSubmitting] = useState(false) const [apiDeployError, setApiDeployError] = useState(null) const [chatExists, setChatExists] = useState(false) @@ -116,6 +100,16 @@ export function DeployModal({ } }, [editingVersion]) + const getApiKeyLabel = (value?: string | null) => { + if (value && value.trim().length > 0) { + return value + } + return workflowWorkspaceId ? 'Workspace API keys' : 'Personal API keys' + } + + const getApiHeaderPlaceholder = () => + workflowWorkspaceId ? 'YOUR_WORKSPACE_API_KEY' : 'YOUR_PERSONAL_API_KEY' + const getInputFormatExample = (includeStreaming = false) => { let inputFormatExample = '' try { @@ -209,21 +203,6 @@ export function DeployModal({ return inputFormatExample } - const fetchApiKeys = async () => { - if (!open) return - - try { - const response = await fetch('/api/users/me/api-keys') - - if (response.ok) { - const data = await response.json() - setApiKeys(data.keys || []) - } - } catch (error) { - logger.error('Error fetching API keys:', { error }) - } - } - const fetchChatDeploymentInfo = async () => { if (!open || !workflowId) return @@ -252,39 +231,19 @@ export function DeployModal({ useEffect(() => { if (open) { setIsLoading(true) - fetchApiKeys() fetchChatDeploymentInfo() setActiveTab('api') setVersionToActivate(null) } else { - setSelectedApiKeyId('') setVersionToActivate(null) } }, [open, workflowId]) - useEffect(() => { - if (apiKeys.length === 0) return - - if (deploymentInfo?.apiKey) { - const matchingKey = apiKeys.find((k) => k.key === deploymentInfo.apiKey) - if (matchingKey) { - setSelectedApiKeyId(matchingKey.id) - return - } - } - - if (!selectedApiKeyId) { - setSelectedApiKeyId(apiKeys[0].id) - } - }, [deploymentInfo, apiKeys]) - useEffect(() => { async function fetchDeploymentInfo() { if (!open || !workflowId || !isDeployed) { setDeploymentInfo(null) - if (!open) { - setIsLoading(false) - } + setIsLoading(false) return } @@ -305,13 +264,14 @@ export function DeployModal({ const data = await response.json() const endpoint = `${getEnv('NEXT_PUBLIC_APP_URL')}/api/workflows/${workflowId}/execute` const inputFormatExample = getInputFormatExample(selectedStreamingOutputs.length > 0) + const placeholderKey = workflowWorkspaceId ? 'YOUR_WORKSPACE_API_KEY' : 'YOUR_API_KEY' setDeploymentInfo({ isDeployed: data.isDeployed, deployedAt: data.deployedAt, - apiKey: data.apiKey, + apiKey: data.apiKey || placeholderKey, endpoint, - exampleCommand: `curl -X POST -H "X-API-Key: ${data.apiKey}" -H "Content-Type: application/json"${inputFormatExample} ${endpoint}`, + exampleCommand: `curl -X POST -H "X-API-Key: ${placeholderKey}" -H "Content-Type: application/json"${inputFormatExample} ${endpoint}`, needsRedeployment, }) } catch (error) { @@ -324,14 +284,12 @@ export function DeployModal({ fetchDeploymentInfo() }, [open, workflowId, isDeployed, needsRedeployment, deploymentInfo?.isDeployed]) - const onDeploy = async (data: DeployFormValues) => { + const onDeploy = async () => { setApiDeployError(null) try { setIsSubmitting(true) - const apiKeyToUse = data.apiKey || selectedApiKeyId - let deployEndpoint = `/api/workflows/${workflowId}/deploy` if (versionToActivate !== null) { deployEndpoint = `/api/workflows/${workflowId}/deployments/${versionToActivate}/activate` @@ -343,7 +301,6 @@ export function DeployModal({ 'Content-Type': 'application/json', }, body: JSON.stringify({ - apiKey: apiKeyToUse, deployChatEnabled: false, }), }) @@ -358,14 +315,9 @@ export function DeployModal({ const isActivating = versionToActivate !== null const isDeployedStatus = isActivating ? true : (responseData.isDeployed ?? false) const deployedAtTime = responseData.deployedAt ? new Date(responseData.deployedAt) : undefined - const apiKeyFromResponse = responseData.apiKey || apiKeyToUse + const apiKeyLabel = getApiKeyLabel(responseData.apiKey) - setDeploymentStatus(workflowId, isDeployedStatus, deployedAtTime, apiKeyFromResponse) - - const matchingKey = apiKeys.find((k) => k.key === apiKeyFromResponse || k.id === apiKeyToUse) - if (matchingKey) { - setSelectedApiKeyId(matchingKey.id) - } + setDeploymentStatus(workflowId, isDeployedStatus, deployedAtTime, apiKeyLabel) const isActivatingVersion = versionToActivate !== null setNeedsRedeployment(isActivatingVersion) @@ -381,13 +333,14 @@ export function DeployModal({ const deploymentData = await deploymentInfoResponse.json() const apiEndpoint = `${getEnv('NEXT_PUBLIC_APP_URL')}/api/workflows/${workflowId}/execute` const inputFormatExample = getInputFormatExample(selectedStreamingOutputs.length > 0) + const placeholderKey = getApiHeaderPlaceholder() setDeploymentInfo({ isDeployed: deploymentData.isDeployed, deployedAt: deploymentData.deployedAt, - apiKey: deploymentData.apiKey, + apiKey: getApiKeyLabel(deploymentData.apiKey), endpoint: apiEndpoint, - exampleCommand: `curl -X POST -H "X-API-Key: ${deploymentData.apiKey}" -H "Content-Type: application/json"${inputFormatExample} ${apiEndpoint}`, + exampleCommand: `curl -X POST -H "X-API-Key: ${placeholderKey}" -H "Content-Type: application/json"${inputFormatExample} ${apiEndpoint}`, needsRedeployment: isActivatingVersion, }) } @@ -543,7 +496,7 @@ export function DeployModal({ workflowId, newDeployStatus, deployedAt ? new Date(deployedAt) : undefined, - apiKey + getApiKeyLabel(apiKey) ) setNeedsRedeployment(false) @@ -573,7 +526,7 @@ export function DeployModal({ const isActivating = versionToActivate !== null - setDeploymentStatus(workflowId, true, new Date()) + setDeploymentStatus(workflowId, true, new Date(), getApiKeyLabel()) const deploymentInfoResponse = await fetch(`/api/workflows/${workflowId}/deploy`) if (deploymentInfoResponse.ok) { @@ -581,12 +534,14 @@ export function DeployModal({ const apiEndpoint = `${getEnv('NEXT_PUBLIC_APP_URL')}/api/workflows/${workflowId}/execute` const inputFormatExample = getInputFormatExample(selectedStreamingOutputs.length > 0) + const placeholderKey = getApiHeaderPlaceholder() + setDeploymentInfo({ isDeployed: deploymentData.isDeployed, deployedAt: deploymentData.deployedAt, - apiKey: deploymentData.apiKey, + apiKey: getApiKeyLabel(deploymentData.apiKey), endpoint: apiEndpoint, - exampleCommand: `curl -X POST -H "X-API-Key: ${deploymentData.apiKey}" -H "Content-Type: application/json"${inputFormatExample} ${apiEndpoint}`, + exampleCommand: `curl -X POST -H "X-API-Key: ${placeholderKey}" -H "Content-Type: application/json"${inputFormatExample} ${apiEndpoint}`, needsRedeployment: isActivating, }) } @@ -679,72 +634,67 @@ export function DeployModal({
{activeTab === 'api' && ( - <> - {versionToActivate !== null ? ( - <> - {apiDeployError && ( -
-
API Deployment Error
-
{apiDeployError}
-
- )} - -
- -
- - ) : isDeployed ? ( - <> - - - ) : ( - <> - {apiDeployError && ( -
-
API Deployment Error
-
{apiDeployError}
-
- )} - -
- -
- +
+ {apiDeployError && ( +
+
API Deployment Error
+
{apiDeployError}
+
)} - + + {versionToActivate !== null ? ( +
+
+ {`Deploy version ${ + versions.find((v) => v.version === versionToActivate)?.name || + `v${versionToActivate}` + } to production.`} +
+
+ + +
+
+ ) : ( + + )} +
)} {activeTab === 'versions' && ( @@ -915,57 +865,23 @@ export function DeployModal({ )} {activeTab === 'chat' && ( - <> - setVersionToActivate(null)} - /> - + setVersionToActivate(null)} + /> )}
- {activeTab === 'api' && (versionToActivate !== null || !isDeployed) && ( -
- - - -
- )} - {activeTab === 'chat' && (
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/api-keys/api-keys.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/api-keys/api-keys.tsx index 27cbeeefb..d5591d21a 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/api-keys/api-keys.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/api-keys/api-keys.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect, useMemo, useRef, useState } from 'react' -import { Check, Copy, Plus, Search } from 'lucide-react' +import { Check, Copy, Info, Plus, Search } from 'lucide-react' import { useParams } from 'next/navigation' import { AlertDialog, @@ -17,6 +17,8 @@ import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Skeleton } from '@/components/ui/skeleton' +import { Switch } from '@/components/ui/switch' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { useSession } from '@/lib/auth-client' import { createLogger } from '@/lib/logs/console/logger' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' @@ -58,7 +60,7 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { const params = useParams() const workspaceId = (params?.workspaceId as string) || '' const userPermissions = useUserPermissionsContext() - const canManageWorkspaceKeys = userPermissions.canEdit || userPermissions.canAdmin + const canManageWorkspaceKeys = userPermissions.canAdmin // State for both workspace and personal keys const [workspaceKeys, setWorkspaceKeys] = useState([]) @@ -79,6 +81,18 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { const [shouldScrollToBottom, setShouldScrollToBottom] = useState(false) const [createError, setCreateError] = useState(null) + const [billedAccountUserId, setBilledAccountUserId] = useState(null) + const [allowPersonalApiKeys, setAllowPersonalApiKeys] = useState(true) + const [workspaceAdmins, setWorkspaceAdmins] = useState< + Array<{ userId: string; name: string; email: string; permissionType: string }> + >([]) + const [workspaceSettingsLoading, setWorkspaceSettingsLoading] = useState(true) + const [workspaceSettingsUpdating, setWorkspaceSettingsUpdating] = useState(false) + + const defaultKeyType = allowPersonalApiKeys ? 'personal' : 'workspace' + const createButtonDisabled = + workspaceSettingsLoading || (!allowPersonalApiKeys && !canManageWorkspaceKeys) + const scrollContainerRef = useRef(null) const filteredWorkspaceKeys = useMemo(() => { @@ -147,6 +161,75 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { } } + const fetchWorkspaceSettings = async () => { + if (!workspaceId) return + + setWorkspaceSettingsLoading(true) + try { + const [workspaceResponse, permissionsResponse] = await Promise.all([ + fetch(`/api/workspaces/${workspaceId}`), + fetch(`/api/workspaces/${workspaceId}/permissions`), + ]) + + if (workspaceResponse.ok) { + const data = await workspaceResponse.json() + const workspaceData = data.workspace ?? {} + setBilledAccountUserId(workspaceData.billedAccountUserId ?? null) + setAllowPersonalApiKeys( + workspaceData.allowPersonalApiKeys === undefined + ? true + : Boolean(workspaceData.allowPersonalApiKeys) + ) + } else { + logger.error('Failed to fetch workspace details', { status: workspaceResponse.status }) + } + + if (permissionsResponse.ok) { + const data = await permissionsResponse.json() + const users = Array.isArray(data.users) ? data.users : [] + const admins = users.filter((user: any) => user.permissionType === 'admin') + setWorkspaceAdmins(admins) + } else { + logger.error('Failed to fetch workspace permissions', { + status: permissionsResponse.status, + }) + } + } catch (error) { + logger.error('Error fetching workspace settings:', { error }) + } finally { + setWorkspaceSettingsLoading(false) + } + } + + const updateWorkspaceSettings = async (updates: { + billedAccountUserId?: string + allowPersonalApiKeys?: boolean + }) => { + if (!workspaceId) return + setWorkspaceSettingsUpdating(true) + try { + const response = await fetch(`/api/workspaces/${workspaceId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(updates), + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.error || 'Failed to update workspace settings') + } + + await fetchWorkspaceSettings() + } catch (error) { + logger.error('Error updating workspace settings:', { error }) + throw error + } finally { + setWorkspaceSettingsUpdating(false) + } + } + const handleCreateKey = async () => { if (!userId || !newKeyName.trim()) return @@ -281,6 +364,18 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { } }, [registerCloseHandler]) + useEffect(() => { + if (workspaceId) { + fetchWorkspaceSettings() + } + }, [workspaceId]) + + useEffect(() => { + if (!allowPersonalApiKeys && keyType === 'personal') { + setKeyType('workspace') + } + }, [allowPersonalApiKeys, keyType]) + useEffect(() => { if (shouldScrollToBottom && scrollContainerRef.current) { scrollContainerRef.current.scrollTo({ @@ -303,7 +398,7 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { return (
{/* Fixed Header */} -
+
{/* Search Input */} {isLoading ? ( @@ -338,6 +433,50 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) {
) : ( <> + {/* Allow Personal API Keys Toggle */} + {!searchTerm.trim() && ( + +
+
+ + Allow personal API keys + + + + + + + Allow collaborators to create and use their own keys with billing charged + to them. + + +
+ {workspaceSettingsLoading ? ( + + ) : ( + { + const previous = allowPersonalApiKeys + setAllowPersonalApiKeys(checked) + try { + await updateWorkspaceSettings({ allowPersonalApiKeys: checked }) + } catch (error) { + setAllowPersonalApiKeys(previous) + } + }} + /> + )} +
+
+ )} + {/* Workspace section */} {!searchTerm.trim() ? (
@@ -468,27 +607,26 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { {/* Footer */}
-
+
{isLoading ? ( - <> - -
- + ) : ( - <> - - + )}
@@ -518,7 +656,8 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { setKeyType('personal') if (createError) setCreateError(null) }} - className='h-8 data-[variant=outline]:border-border data-[variant=outline]:bg-background data-[variant=outline]:text-foreground data-[variant=outline]:hover:bg-muted dark:data-[variant=outline]:border-border dark:data-[variant=outline]:bg-background dark:data-[variant=outline]:text-foreground dark:data-[variant=outline]:hover:bg-muted/80' + disabled={!allowPersonalApiKeys} + className='h-8 data-[variant=outline]:border-border data-[variant=outline]:bg-background data-[variant=outline]:text-foreground data-[variant=outline]:hover:bg-muted dark:data-[variant=outline]:border-border dark:data-[variant=outline]:bg-background dark:data-[variant=outline]:text-foreground dark:data-[variant=outline]:hover:bg-muted/80 disabled:opacity-60 disabled:cursor-not-allowed' > Personal @@ -560,7 +699,7 @@ export function ApiKeys({ onOpenChange, registerCloseHandler }: ApiKeysProps) { className='h-9 w-full rounded-[8px] border-border bg-background text-foreground hover:bg-muted dark:border-border dark:bg-background dark:text-foreground dark:hover:bg-muted/80' onClick={() => { setNewKeyName('') - setKeyType('personal') + setKeyType(defaultKeyType) }} > Cancel diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx index 1f5ea569a..fd81cec55 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/components/cancel-subscription/cancel-subscription.tsx @@ -12,7 +12,6 @@ import { AlertDialogTitle, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { useSession, useSubscription } from '@/lib/auth-client' import { createLogger } from '@/lib/logs/console/logger' import { getBaseUrl } from '@/lib/urls/utils' @@ -30,6 +29,7 @@ interface CancelSubscriptionProps { } subscriptionData?: { periodEnd?: Date | null + cancelAtPeriodEnd?: boolean } } @@ -127,35 +127,48 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub const subscriptionStatus = getSubscriptionStatus() const activeOrgId = activeOrganization?.id - // For team/enterprise plans, get the subscription ID from organization store - if ((subscriptionStatus.isTeam || subscriptionStatus.isEnterprise) && activeOrgId) { - const orgSubscription = useOrganizationStore.getState().subscriptionData - - if (orgSubscription?.id && orgSubscription?.cancelAtPeriodEnd) { - // Restore the organization subscription - if (!betterAuthSubscription.restore) { - throw new Error('Subscription restore not available') - } - - const result = await betterAuthSubscription.restore({ - referenceId: activeOrgId, - subscriptionId: orgSubscription.id, - }) - logger.info('Organization subscription restored successfully', result) + if (isCancelAtPeriodEnd) { + if (!betterAuthSubscription.restore) { + throw new Error('Subscription restore not available') } + + let referenceId: string + let subscriptionId: string | undefined + + if ((subscriptionStatus.isTeam || subscriptionStatus.isEnterprise) && activeOrgId) { + const orgSubscription = useOrganizationStore.getState().subscriptionData + referenceId = activeOrgId + subscriptionId = orgSubscription?.id + } else { + // For personal subscriptions, use user ID and let better-auth find the subscription + referenceId = session.user.id + subscriptionId = undefined + } + + logger.info('Restoring subscription', { referenceId, subscriptionId }) + + // Build restore params - only include subscriptionId if we have one (team/enterprise) + const restoreParams: any = { referenceId } + if (subscriptionId) { + restoreParams.subscriptionId = subscriptionId + } + + const result = await betterAuthSubscription.restore(restoreParams) + + logger.info('Subscription restored successfully', result) } - // Refresh state and close await refresh() if (activeOrgId) { await loadOrganizationSubscription(activeOrgId) await refreshOrganization().catch(() => {}) } + setIsDialogOpen(false) } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Failed to keep subscription' + const errorMessage = error instanceof Error ? error.message : 'Failed to restore subscription' setError(errorMessage) - logger.error('Failed to keep subscription', { error }) + logger.error('Failed to restore subscription', { error }) } finally { setIsLoading(false) } @@ -190,19 +203,15 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub const periodEndDate = getPeriodEndDate() // Check if subscription is set to cancel at period end - const isCancelAtPeriodEnd = (() => { - const subscriptionStatus = getSubscriptionStatus() - if (subscriptionStatus.isTeam || subscriptionStatus.isEnterprise) { - return useOrganizationStore.getState().subscriptionData?.cancelAtPeriodEnd === true - } - return false - })() + const isCancelAtPeriodEnd = subscriptionData?.cancelAtPeriodEnd === true return ( <>
- Manage Subscription + + {isCancelAtPeriodEnd ? 'Restore Subscription' : 'Manage Subscription'} + {isCancelAtPeriodEnd && (

You'll keep access until {formatDate(periodEndDate)} @@ -217,10 +226,12 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub 'h-8 rounded-[8px] font-medium text-xs transition-all duration-200', error ? 'border-red-500 text-red-500 dark:border-red-500 dark:text-red-500' - : 'text-muted-foreground hover:border-red-500 hover:bg-red-500 hover:text-white dark:hover:border-red-500 dark:hover:bg-red-500' + : isCancelAtPeriodEnd + ? 'text-muted-foreground hover:border-green-500 hover:bg-green-500 hover:text-white dark:hover:border-green-500 dark:hover:bg-green-500' + : 'text-muted-foreground hover:border-red-500 hover:bg-red-500 hover:text-white dark:hover:border-red-500 dark:hover:bg-red-500' )} > - {error ? 'Error' : 'Manage'} + {error ? 'Error' : isCancelAtPeriodEnd ? 'Restore' : 'Manage'}

@@ -228,11 +239,11 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub - {isCancelAtPeriodEnd ? 'Manage' : 'Cancel'} {subscription.plan} subscription? + {isCancelAtPeriodEnd ? 'Restore' : 'Cancel'} {subscription.plan} subscription? {isCancelAtPeriodEnd - ? 'Your subscription is set to cancel at the end of the billing period. You can reactivate it or manage other settings.' + ? 'Your subscription is set to cancel at the end of the billing period. Would you like to keep your subscription active?' : `You'll be redirected to Stripe to manage your subscription. You'll keep access until ${formatDate( periodEndDate )}, then downgrade to free plan.`}{' '} @@ -260,38 +271,23 @@ export function CancelSubscription({ subscription, subscriptionData }: CancelSub setIsDialogOpen(false) : handleKeep} disabled={isLoading} > - Keep Subscription + {isCancelAtPeriodEnd ? 'Cancel' : 'Keep Subscription'} {(() => { const subscriptionStatus = getSubscriptionStatus() - if ( - subscriptionStatus.isPaid && - (activeOrganization?.id - ? useOrganizationStore.getState().subscriptionData?.cancelAtPeriodEnd - : false) - ) { + if (subscriptionStatus.isPaid && isCancelAtPeriodEnd) { return ( - - - -
- - Continue - -
-
- -

Subscription will be cancelled at end of billing period

-
-
-
+ + {isLoading ? 'Restoring...' : 'Restore Subscription'} + ) } return ( diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx index df87763d2..c919f3ae7 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/subscription/subscription.tsx @@ -1,10 +1,22 @@ 'use client' import { useCallback, useEffect, useRef, useState } from 'react' +import { useParams } from 'next/navigation' import { Skeleton, Switch } from '@/components/ui' +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' import { useSession } from '@/lib/auth-client' +import { createLogger } from '@/lib/logs/console/logger' import { useSubscriptionUpgrade } from '@/lib/subscription/upgrade' import { getBaseUrl } from '@/lib/urls/utils' import { cn } from '@/lib/utils' +import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { UsageHeader } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/shared/usage-header' import { CancelSubscription, @@ -175,6 +187,11 @@ const formatPlanName = (plan: string): string => plan.charAt(0).toUpperCase() + export function Subscription({ onOpenChange }: SubscriptionProps) { const { data: session } = useSession() const { handleUpgrade } = useSubscriptionUpgrade() + const params = useParams() + const workspaceId = (params?.workspaceId as string) || '' + const userPermissions = useUserPermissionsContext() + const canManageWorkspaceKeys = userPermissions.canAdmin + const logger = createLogger('Subscription') const { isLoading, @@ -191,6 +208,14 @@ export function Subscription({ onOpenChange }: SubscriptionProps) { const [upgradeError, setUpgradeError] = useState<'pro' | 'team' | null>(null) const usageLimitRef = useRef(null) + // Workspace billing state + const [billedAccountUserId, setBilledAccountUserId] = useState(null) + const [workspaceAdmins, setWorkspaceAdmins] = useState< + Array<{ userId: string; name: string; email: string; permissionType: string }> + >([]) + const [workspaceSettingsLoading, setWorkspaceSettingsLoading] = useState(true) + const [workspaceSettingsUpdating, setWorkspaceSettingsUpdating] = useState(false) + // Get real subscription data from store const subscription = getSubscriptionStatus() const usage = getUsage() @@ -203,6 +228,77 @@ export function Subscription({ onOpenChange }: SubscriptionProps) { } }, [activeOrgId, subscription.isTeam, subscription.isEnterprise, loadOrganizationBillingData]) + // Fetch workspace billing settings + const fetchWorkspaceSettings = useCallback(async () => { + if (!workspaceId) return + + setWorkspaceSettingsLoading(true) + try { + const [workspaceResponse, permissionsResponse] = await Promise.all([ + fetch(`/api/workspaces/${workspaceId}`), + fetch(`/api/workspaces/${workspaceId}/permissions`), + ]) + + if (workspaceResponse.ok) { + const data = await workspaceResponse.json() + const workspaceData = data.workspace ?? {} + setBilledAccountUserId(workspaceData.billedAccountUserId ?? null) + } else { + logger.error('Failed to fetch workspace details', { status: workspaceResponse.status }) + } + + if (permissionsResponse.ok) { + const data = await permissionsResponse.json() + const users = Array.isArray(data.users) ? data.users : [] + const admins = users.filter((user: any) => user.permissionType === 'admin') + setWorkspaceAdmins(admins) + } else { + logger.error('Failed to fetch workspace permissions', { + status: permissionsResponse.status, + }) + } + } catch (error) { + logger.error('Error fetching workspace settings:', { error }) + } finally { + setWorkspaceSettingsLoading(false) + } + }, [workspaceId, logger]) + + const updateWorkspaceSettings = async (updates: { billedAccountUserId?: string }) => { + if (!workspaceId) return + setWorkspaceSettingsUpdating(true) + try { + const response = await fetch(`/api/workspaces/${workspaceId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(updates), + }) + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.error || 'Failed to update workspace settings') + } + + await fetchWorkspaceSettings() + } catch (error) { + logger.error('Error updating workspace settings:', { error }) + throw error + } finally { + setWorkspaceSettingsUpdating(false) + } + } + + useEffect(() => { + if (workspaceId) { + fetchWorkspaceSettings() + } else { + setWorkspaceSettingsLoading(false) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [workspaceId]) + // Auto-clear upgrade error useEffect(() => { if (upgradeError) { @@ -540,10 +636,56 @@ export function Subscription({ onOpenChange }: SubscriptionProps) { }} subscriptionData={{ periodEnd: subscriptionData?.periodEnd || null, + cancelAtPeriodEnd: subscriptionData?.cancelAtPeriodEnd, }} />
)} + + {/* Workspace API Billing Settings */} + {canManageWorkspaceKeys && ( +
+ Billed Account for Workspace + {workspaceSettingsLoading ? ( + + ) : workspaceAdmins.length === 0 ? ( +
+ No admin members available +
+ ) : ( + + )} +
+ )}
) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/workflow-list.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/workflow-list.tsx index dcf1e8286..527270dfe 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/workflow-list.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/workflow-list.tsx @@ -40,15 +40,10 @@ function WorkflowItem({ workflow, active, isMarketplace }: WorkflowItemProps) { interface WorkflowListProps { regularWorkflows: WorkflowMetadata[] - marketplaceWorkflows: WorkflowMetadata[] isLoading?: boolean } -export function WorkflowList({ - regularWorkflows, - marketplaceWorkflows, - isLoading = false, -}: WorkflowListProps) { +export function WorkflowList({ regularWorkflows, isLoading = false }: WorkflowListProps) { const pathname = usePathname() const params = useParams() const workspaceId = params.workspaceId as string @@ -70,11 +65,7 @@ export function WorkflowList({ }, []) // Only show empty state when not loading and user is logged in - const showEmptyState = - !isLoading && - session?.user && - regularWorkflows.length === 0 && - marketplaceWorkflows.length === 0 + const showEmptyState = !isLoading && session?.user && regularWorkflows.length === 0 return (
@@ -91,29 +82,6 @@ export function WorkflowList({ active={pathname === `/workspace/${workspaceId}/w/${workflow.id}`} /> ))} - - {/* Marketplace Temp Workflows (if any) */} - {marketplaceWorkflows.length > 0 && ( -
-

Marketplace

- {marketplaceWorkflows.map((workflow) => ( - - ))} -
- )} - - {/* Empty state */} - {showEmptyState && ( -
- No workflows in {workspaceId ? 'this workspace' : 'your account'}. Create one to get - started. -
- )} )}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx index 50101af34..d4746d2b1 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/sidebar.tsx @@ -679,46 +679,34 @@ export function Sidebar() { const [showSearchModal, setShowSearchModal] = useState(false) const [showSubscriptionModal, setShowSubscriptionModal] = useState(false) - // Separate regular workflows from temporary marketplace workflows - const { regularWorkflows, tempWorkflows } = useMemo(() => { + // Get workflows for the current workspace + const regularWorkflows = useMemo(() => { + if (isLoading) return [] + const regular: WorkflowMetadata[] = [] - const temp: WorkflowMetadata[] = [] - - if (!isLoading) { - Object.values(workflows).forEach((workflow) => { - if (workflow.workspaceId === workspaceId || !workflow.workspaceId) { - if (workflow.marketplaceData?.status === 'temp') { - temp.push(workflow) - } else { - regular.push(workflow) - } - } - }) - - // Sort by creation date (newest first) for stable ordering - const sortByCreatedAt = (a: WorkflowMetadata, b: WorkflowMetadata) => { - return b.createdAt.getTime() - a.createdAt.getTime() + Object.values(workflows).forEach((workflow) => { + if (workflow.workspaceId === workspaceId || !workflow.workspaceId) { + regular.push(workflow) } + }) - regular.sort(sortByCreatedAt) - temp.sort(sortByCreatedAt) - } + // Sort by creation date (newest first) for stable ordering + regular.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()) - return { regularWorkflows: regular, tempWorkflows: temp } + return regular }, [workflows, isLoading, workspaceId]) // Prepare workflows for search modal const searchWorkflows = useMemo(() => { if (isLoading) return [] - const allWorkflows = [...regularWorkflows, ...tempWorkflows] - return allWorkflows.map((workflow) => ({ + return regularWorkflows.map((workflow) => ({ id: workflow.id, name: workflow.name, href: `/workspace/${workspaceId}/w/${workflow.id}`, isCurrent: workflow.id === workflowId, })) - }, [regularWorkflows, tempWorkflows, workspaceId, workflowId, isLoading]) + }, [regularWorkflows, workspaceId, workflowId, isLoading]) // Prepare workspaces for search modal (include all workspaces) const searchWorkspaces = useMemo(() => { @@ -942,7 +930,6 @@ export function Sidebar() {
diff --git a/apps/sim/background/schedule-execution.ts b/apps/sim/background/schedule-execution.ts index b64567dce..184970ff2 100644 --- a/apps/sim/background/schedule-execution.ts +++ b/apps/sim/background/schedule-execution.ts @@ -3,7 +3,6 @@ import { task } from '@trigger.dev/sdk' import { Cron } from 'croner' import { eq } from 'drizzle-orm' import { v4 as uuidv4 } from 'uuid' -import { getApiKeyOwnerUserId } from '@/lib/api-key/service' import { checkServerSideUsageLimits } from '@/lib/billing' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { getPersonalAndWorkspaceEnv } from '@/lib/environment/utils' @@ -19,6 +18,7 @@ import { import { decryptSecret } from '@/lib/utils' import { blockExistsInDeployment, loadDeployedWorkflowState } from '@/lib/workflows/db-helpers' import { updateWorkflowRunCounts } from '@/lib/workflows/utils' +import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils' import { Executor } from '@/executor' import { Serializer } from '@/serializer' import { RateLimiter } from '@/services/queue' @@ -91,11 +91,19 @@ export async function executeScheduleJob(payload: ScheduleExecutionPayload) { return } - const actorUserId = await getApiKeyOwnerUserId(workflowRecord.pinnedApiKeyId) + let actorUserId: string | null = null + + if (workflowRecord.workspaceId) { + actorUserId = await getWorkspaceBilledAccountUserId(workflowRecord.workspaceId) + } + + if (!actorUserId) { + actorUserId = workflowRecord.userId ?? null + } if (!actorUserId) { logger.warn( - `[${requestId}] Skipping schedule ${payload.scheduleId}: pinned API key required to attribute usage.` + `[${requestId}] Skipping schedule ${payload.scheduleId}: unable to resolve billed account.` ) return } diff --git a/apps/sim/lib/api-key/service.ts b/apps/sim/lib/api-key/service.ts index 2869b19b8..b68bacd2e 100644 --- a/apps/sim/lib/api-key/service.ts +++ b/apps/sim/lib/api-key/service.ts @@ -1,11 +1,13 @@ import { createCipheriv, createDecipheriv, randomBytes } from 'crypto' import { db } from '@sim/db' -import { apiKey as apiKeyTable, workspace } from '@sim/db/schema' +import { apiKey as apiKeyTable } from '@sim/db/schema' import { and, eq } from 'drizzle-orm' import { nanoid } from 'nanoid' import { authenticateApiKey } from '@/lib/api-key/auth' import { env } from '@/lib/env' import { createLogger } from '@/lib/logs/console/logger' +import { getUserEntityPermissions } from '@/lib/permissions/utils' +import { getWorkspaceBillingSettings } from '@/lib/workspaces/utils' const logger = createLogger('ApiKeyService') @@ -36,6 +38,18 @@ export async function authenticateApiKeyFromHeader( } try { + let workspaceSettings: { + billedAccountUserId: string | null + allowPersonalApiKeys: boolean + } | null = null + + if (options.workspaceId) { + workspaceSettings = await getWorkspaceBillingSettings(options.workspaceId) + if (!workspaceSettings) { + return { success: false, error: 'Workspace not found' } + } + } + // Build query based on options let query = db .select({ @@ -48,11 +62,6 @@ export async function authenticateApiKeyFromHeader( }) .from(apiKeyTable) - // Add workspace join if needed for workspace keys - if (options.workspaceId || options.keyTypes?.includes('workspace')) { - query = query.leftJoin(workspace, eq(apiKeyTable.workspaceId, workspace.id)) as any - } - // Apply filters const conditions = [] @@ -60,10 +69,6 @@ export async function authenticateApiKeyFromHeader( conditions.push(eq(apiKeyTable.userId, options.userId)) } - if (options.workspaceId) { - conditions.push(eq(apiKeyTable.workspaceId, options.workspaceId)) - } - if (options.keyTypes?.length) { if (options.keyTypes.length === 1) { conditions.push(eq(apiKeyTable.type, options.keyTypes[0])) @@ -78,11 +83,27 @@ export async function authenticateApiKeyFromHeader( const keyRecords = await query - // Filter by keyTypes in memory if multiple types specified - const filteredRecords = - options.keyTypes?.length && options.keyTypes.length > 1 - ? keyRecords.filter((record) => options.keyTypes!.includes(record.type as any)) - : keyRecords + const filteredRecords = keyRecords.filter((record) => { + const keyType = record.type as 'personal' | 'workspace' + + if (options.keyTypes?.length && !options.keyTypes.includes(keyType)) { + return false + } + + if (options.workspaceId) { + if (keyType === 'workspace') { + return record.workspaceId === options.workspaceId + } + + if (keyType === 'personal') { + return workspaceSettings?.allowPersonalApiKeys ?? false + } + } + + return true + }) + + const permissionCache = new Map() // Authenticate each key for (const storedKey of filteredRecords) { @@ -91,6 +112,29 @@ export async function authenticateApiKeyFromHeader( continue } + if (options.workspaceId && (storedKey.type as 'personal' | 'workspace') === 'personal') { + if (!workspaceSettings?.allowPersonalApiKeys) { + continue + } + + if (!storedKey.userId) { + continue + } + + if (!permissionCache.has(storedKey.userId)) { + const permission = await getUserEntityPermissions( + storedKey.userId, + 'workspace', + options.workspaceId + ) + permissionCache.set(storedKey.userId, permission !== null) + } + + if (!permissionCache.get(storedKey.userId)) { + continue + } + } + try { const isValid = await authenticateApiKey(apiKeyHeader, storedKey.key) if (isValid) { @@ -99,7 +143,7 @@ export async function authenticateApiKeyFromHeader( userId: storedKey.userId, keyId: storedKey.id, keyType: storedKey.type as 'personal' | 'workspace', - workspaceId: storedKey.workspaceId || undefined, + workspaceId: storedKey.workspaceId || options.workspaceId || undefined, } } } catch (error) { @@ -125,27 +169,6 @@ export async function updateApiKeyLastUsed(keyId: string): Promise { } } -/** - * Given a pinned API key ID, resolve the owning userId (actor). - * Returns null if not found. - */ -export async function getApiKeyOwnerUserId( - pinnedApiKeyId: string | null | undefined -): Promise { - if (!pinnedApiKeyId) return null - try { - const rows = await db - .select({ userId: apiKeyTable.userId }) - .from(apiKeyTable) - .where(eq(apiKeyTable.id, pinnedApiKeyId)) - .limit(1) - return rows[0]?.userId ?? null - } catch (error) { - logger.error('Error resolving API key owner', { error, pinnedApiKeyId }) - return null - } -} - /** * Get the API encryption key from the environment * @returns The API encryption key diff --git a/apps/sim/lib/billing/core/billing.ts b/apps/sim/lib/billing/core/billing.ts index 9e729ae06..4fdc1eb2c 100644 --- a/apps/sim/lib/billing/core/billing.ts +++ b/apps/sim/lib/billing/core/billing.ts @@ -220,6 +220,7 @@ export async function getSimplifiedBillingSummary( metadata: any stripeSubscriptionId: string | null periodEnd: Date | string | null + cancelAtPeriodEnd?: boolean // Usage details usage: { current: number @@ -341,6 +342,7 @@ export async function getSimplifiedBillingSummary( metadata: subscription.metadata || null, stripeSubscriptionId: subscription.stripeSubscriptionId || null, periodEnd: subscription.periodEnd || null, + cancelAtPeriodEnd: subscription.cancelAtPeriodEnd || undefined, // Usage details usage: { current: usageData.currentUsage, @@ -463,6 +465,7 @@ export async function getSimplifiedBillingSummary( metadata: subscription?.metadata || null, stripeSubscriptionId: subscription?.stripeSubscriptionId || null, periodEnd: subscription?.periodEnd || null, + cancelAtPeriodEnd: subscription?.cancelAtPeriodEnd || undefined, // Usage details usage: { current: currentUsage, @@ -524,5 +527,14 @@ function getDefaultBillingSummary(type: 'individual' | 'organization') { daysRemaining: 0, copilotCost: 0, }, + ...(type === 'organization' && { + organizationData: { + seatCount: 0, + memberCount: 0, + totalBasePrice: 0, + totalCurrentUsage: 0, + totalOverage: 0, + }, + }), } } diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 530175616..f189e4a33 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -2,7 +2,6 @@ import { db, webhook, workflow } from '@sim/db' import { tasks } from '@trigger.dev/sdk' import { and, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { getApiKeyOwnerUserId } from '@/lib/api-key/service' import { checkServerSideUsageLimits } from '@/lib/billing' import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription' import { env, isTruthy } from '@/lib/env' @@ -13,6 +12,7 @@ import { validateMicrosoftTeamsSignature, verifyProviderWebhook, } from '@/lib/webhooks/utils' +import { getWorkspaceBilledAccountUserId } from '@/lib/workspaces/utils' import { executeWebhookJob } from '@/background/webhook-execution' import { RateLimiter } from '@/services/queue' @@ -26,6 +26,20 @@ export interface WebhookProcessorOptions { executionTarget?: 'deployed' | 'live' } +async function resolveWorkflowActorUserId(foundWorkflow: { + workspaceId?: string | null + userId?: string | null +}): Promise { + if (foundWorkflow?.workspaceId) { + const billedAccount = await getWorkspaceBilledAccountUserId(foundWorkflow.workspaceId) + if (billedAccount) { + return billedAccount + } + } + + return foundWorkflow?.userId ?? null +} + export async function parseWebhookBody( request: NextRequest, requestId: string @@ -269,11 +283,11 @@ export async function checkRateLimits( requestId: string ): Promise { try { - const actorUserId = await getApiKeyOwnerUserId(foundWorkflow.pinnedApiKeyId) + const actorUserId = await resolveWorkflowActorUserId(foundWorkflow) if (!actorUserId) { - logger.warn(`[${requestId}] Webhook requires pinned API key to attribute usage`) - return NextResponse.json({ message: 'Pinned API key required' }, { status: 200 }) + logger.warn(`[${requestId}] Webhook requires a workspace billing account to attribute usage`) + return NextResponse.json({ message: 'Workspace billing account required' }, { status: 200 }) } const userSubscription = await getHighestPrioritySubscription(actorUserId) @@ -327,11 +341,11 @@ export async function checkUsageLimits( } try { - const actorUserId = await getApiKeyOwnerUserId(foundWorkflow.pinnedApiKeyId) + const actorUserId = await resolveWorkflowActorUserId(foundWorkflow) if (!actorUserId) { - logger.warn(`[${requestId}] Webhook requires pinned API key to attribute usage`) - return NextResponse.json({ message: 'Pinned API key required' }, { status: 200 }) + logger.warn(`[${requestId}] Webhook requires a workspace billing account to attribute usage`) + return NextResponse.json({ message: 'Workspace billing account required' }, { status: 200 }) } const usageCheck = await checkServerSideUsageLimits(actorUserId) @@ -376,10 +390,12 @@ export async function queueWebhookExecution( options: WebhookProcessorOptions ): Promise { try { - const actorUserId = await getApiKeyOwnerUserId(foundWorkflow.pinnedApiKeyId) + const actorUserId = await resolveWorkflowActorUserId(foundWorkflow) if (!actorUserId) { - logger.warn(`[${options.requestId}] Webhook requires pinned API key to attribute usage`) - return NextResponse.json({ message: 'Pinned API key required' }, { status: 200 }) + logger.warn( + `[${options.requestId}] Webhook requires a workspace billing account to attribute usage` + ) + return NextResponse.json({ message: 'Workspace billing account required' }, { status: 200 }) } const headers = Object.fromEntries(request.headers.entries()) diff --git a/apps/sim/lib/workflows/db-helpers.ts b/apps/sim/lib/workflows/db-helpers.ts index 6b5d0a077..c47bb6fce 100644 --- a/apps/sim/lib/workflows/db-helpers.ts +++ b/apps/sim/lib/workflows/db-helpers.ts @@ -369,8 +369,6 @@ export async function migrateWorkflowToNormalizedTables( export async function deployWorkflow(params: { workflowId: string deployedBy: string // User ID of the person deploying - pinnedApiKeyId?: string - includeDeployedState?: boolean workflowName?: string }): Promise<{ success: boolean @@ -379,13 +377,7 @@ export async function deployWorkflow(params: { currentState?: any error?: string }> { - const { - workflowId, - deployedBy, - pinnedApiKeyId, - includeDeployedState = false, - workflowName, - } = params + const { workflowId, deployedBy, workflowName } = params try { const normalizedData = await loadWorkflowFromNormalizedTables(workflowId) @@ -435,14 +427,6 @@ export async function deployWorkflow(params: { deployedAt: now, } - if (includeDeployedState) { - updateData.deployedState = currentState - } - - if (pinnedApiKeyId) { - updateData.pinnedApiKeyId = pinnedApiKeyId - } - await tx.update(workflow).set(updateData).where(eq(workflow.id, workflowId)) return nextVersion diff --git a/apps/sim/lib/workflows/utils.ts b/apps/sim/lib/workflows/utils.ts index a4b3aa729..a64e3355d 100644 --- a/apps/sim/lib/workflows/utils.ts +++ b/apps/sim/lib/workflows/utils.ts @@ -1,5 +1,5 @@ import { db } from '@sim/db' -import { apiKey, permissions, workflow as workflowTable, workspace } from '@sim/db/schema' +import { permissions, workflow as workflowTable, workspace } from '@sim/db/schema' import type { InferSelectModel } from 'drizzle-orm' import { and, eq } from 'drizzle-orm' import { NextResponse } from 'next/server' @@ -12,86 +12,12 @@ import type { WorkflowState } from '@/stores/workflows/workflow/types' const logger = createLogger('WorkflowUtils') -const WORKFLOW_BASE_SELECTION = { - id: workflowTable.id, - userId: workflowTable.userId, - workspaceId: workflowTable.workspaceId, - folderId: workflowTable.folderId, - name: workflowTable.name, - description: workflowTable.description, - color: workflowTable.color, - lastSynced: workflowTable.lastSynced, - createdAt: workflowTable.createdAt, - updatedAt: workflowTable.updatedAt, - isDeployed: workflowTable.isDeployed, - deployedState: workflowTable.deployedState, - deployedAt: workflowTable.deployedAt, - pinnedApiKeyId: workflowTable.pinnedApiKeyId, - collaborators: workflowTable.collaborators, - runCount: workflowTable.runCount, - lastRunAt: workflowTable.lastRunAt, - variables: workflowTable.variables, - isPublished: workflowTable.isPublished, - marketplaceData: workflowTable.marketplaceData, - pinnedApiKeyKey: apiKey.key, - pinnedApiKeyName: apiKey.name, - pinnedApiKeyType: apiKey.type, - pinnedApiKeyWorkspaceId: apiKey.workspaceId, -} - type WorkflowSelection = InferSelectModel -type ApiKeySelection = InferSelectModel - -type WorkflowRow = WorkflowSelection & { - pinnedApiKeyKey: ApiKeySelection['key'] | null - pinnedApiKeyName: ApiKeySelection['name'] | null - pinnedApiKeyType: ApiKeySelection['type'] | null - pinnedApiKeyWorkspaceId: ApiKeySelection['workspaceId'] | null -} - -type WorkflowWithPinnedKey = WorkflowSelection & { - pinnedApiKey: Pick | null -} - -function mapWorkflowRow(row: WorkflowRow | undefined): WorkflowWithPinnedKey | undefined { - if (!row) { - return undefined - } - - const { - pinnedApiKeyKey, - pinnedApiKeyName, - pinnedApiKeyType, - pinnedApiKeyWorkspaceId, - ...workflowWithoutDerived - } = row - - const pinnedApiKey = - workflowWithoutDerived.pinnedApiKeyId && pinnedApiKeyKey && pinnedApiKeyName && pinnedApiKeyType - ? { - id: workflowWithoutDerived.pinnedApiKeyId, - name: pinnedApiKeyName, - key: pinnedApiKeyKey, - type: pinnedApiKeyType, - workspaceId: pinnedApiKeyWorkspaceId, - } - : null - - return { - ...workflowWithoutDerived, - pinnedApiKey, - } -} export async function getWorkflowById(id: string) { - const rows = await db - .select(WORKFLOW_BASE_SELECTION) - .from(workflowTable) - .leftJoin(apiKey, eq(workflowTable.pinnedApiKeyId, apiKey.id)) - .where(eq(workflowTable.id, id)) - .limit(1) + const rows = await db.select().from(workflowTable).where(eq(workflowTable.id, id)).limit(1) - return mapWorkflowRow(rows[0] as WorkflowRow | undefined) + return rows[0] } type WorkflowRecord = ReturnType extends Promise @@ -110,55 +36,50 @@ export async function getWorkflowAccessContext( workflowId: string, userId?: string ): Promise { - const rows = await db - .select({ - ...WORKFLOW_BASE_SELECTION, - workspaceOwnerId: workspace.ownerId, - workspacePermission: permissions.permissionType, - }) - .from(workflowTable) - .leftJoin(apiKey, eq(workflowTable.pinnedApiKeyId, apiKey.id)) - .leftJoin(workspace, eq(workspace.id, workflowTable.workspaceId)) - .leftJoin( - permissions, - and( - eq(permissions.entityType, 'workspace'), - eq(permissions.entityId, workflowTable.workspaceId), - userId ? eq(permissions.userId, userId) : eq(permissions.userId, '' as unknown as string) - ) - ) - .where(eq(workflowTable.id, workflowId)) - .limit(1) - - const row = rows[0] as - | (WorkflowRow & { - workspaceOwnerId: string | null - workspacePermission: PermissionType | null - }) - | undefined - - if (!row) { - return null - } - - const workflow = mapWorkflowRow(row as WorkflowRow) + const workflow = await getWorkflowById(workflowId) if (!workflow) { return null } - const resolvedWorkspaceOwner = row.workspaceOwnerId ?? null - const resolvedWorkspacePermission = row.workspacePermission ?? null + let workspaceOwnerId: string | null = null + let workspacePermission: PermissionType | null = null + + if (workflow.workspaceId) { + const [workspaceRow] = await db + .select({ ownerId: workspace.ownerId }) + .from(workspace) + .where(eq(workspace.id, workflow.workspaceId)) + .limit(1) + + workspaceOwnerId = workspaceRow?.ownerId ?? null + + if (userId) { + const [permissionRow] = await db + .select({ permissionType: permissions.permissionType }) + .from(permissions) + .where( + and( + eq(permissions.userId, userId), + eq(permissions.entityType, 'workspace'), + eq(permissions.entityId, workflow.workspaceId) + ) + ) + .limit(1) + + workspacePermission = permissionRow?.permissionType ?? null + } + } const resolvedUserId = userId ?? null const isOwner = resolvedUserId ? workflow.userId === resolvedUserId : false - const isWorkspaceOwner = resolvedUserId ? resolvedWorkspaceOwner === resolvedUserId : false + const isWorkspaceOwner = resolvedUserId ? workspaceOwnerId === resolvedUserId : false return { workflow, - workspaceOwnerId: resolvedWorkspaceOwner, - workspacePermission: resolvedWorkspacePermission, + workspaceOwnerId, + workspacePermission, isOwner, isWorkspaceOwner, } diff --git a/apps/sim/lib/workspaces/utils.ts b/apps/sim/lib/workspaces/utils.ts new file mode 100644 index 000000000..5b5c2e76d --- /dev/null +++ b/apps/sim/lib/workspaces/utils.ts @@ -0,0 +1,39 @@ +import { db } from '@sim/db' +import { workspace as workspaceTable } from '@sim/db/schema' +import { eq } from 'drizzle-orm' + +interface WorkspaceBillingSettings { + billedAccountUserId: string | null + allowPersonalApiKeys: boolean +} + +export async function getWorkspaceBillingSettings( + workspaceId: string +): Promise { + if (!workspaceId) { + return null + } + + const rows = await db + .select({ + billedAccountUserId: workspaceTable.billedAccountUserId, + allowPersonalApiKeys: workspaceTable.allowPersonalApiKeys, + }) + .from(workspaceTable) + .where(eq(workspaceTable.id, workspaceId)) + .limit(1) + + if (!rows.length) { + return null + } + + return { + billedAccountUserId: rows[0].billedAccountUserId ?? null, + allowPersonalApiKeys: rows[0].allowPersonalApiKeys ?? false, + } +} + +export async function getWorkspaceBilledAccountUserId(workspaceId: string): Promise { + const settings = await getWorkspaceBillingSettings(workspaceId) + return settings?.billedAccountUserId ?? null +} diff --git a/apps/sim/stores/subscription/types.ts b/apps/sim/stores/subscription/types.ts index 1a9239929..36fff7671 100644 --- a/apps/sim/stores/subscription/types.ts +++ b/apps/sim/stores/subscription/types.ts @@ -31,6 +31,7 @@ export interface SubscriptionData { metadata: any | null stripeSubscriptionId: string | null periodEnd: Date | null + cancelAtPeriodEnd?: boolean usage: UsageData billingBlocked?: boolean } diff --git a/apps/sim/stores/workflows/index.ts b/apps/sim/stores/workflows/index.ts index 8ea2de0ab..733fdda79 100644 --- a/apps/sim/stores/workflows/index.ts +++ b/apps/sim/stores/workflows/index.ts @@ -50,7 +50,6 @@ export function getWorkflowWithValues(workflowId: string) { name: metadata.name, description: metadata.description, color: metadata.color || '#3972F6', - marketplaceData: metadata.marketplaceData || null, workspaceId: metadata.workspaceId, folderId: metadata.folderId, state: { @@ -126,7 +125,6 @@ export function getAllWorkflowsWithValues() { name: metadata.name, description: metadata.description, color: metadata.color || '#3972F6', - marketplaceData: metadata.marketplaceData || null, folderId: metadata.folderId, state: { blocks: mergedBlocks, @@ -136,7 +134,6 @@ export function getAllWorkflowsWithValues() { lastSaved: workflowState.lastSaved, isDeployed: workflowState.isDeployed, deployedAt: workflowState.deployedAt, - marketplaceData: metadata.marketplaceData || null, }, // Include API key if available apiKey, diff --git a/apps/sim/stores/workflows/registry/store.ts b/apps/sim/stores/workflows/registry/store.ts index e83dd0bd4..f0cd3a749 100644 --- a/apps/sim/stores/workflows/registry/store.ts +++ b/apps/sim/stores/workflows/registry/store.ts @@ -92,7 +92,6 @@ async function fetchWorkflowsFromDB(workspaceId?: string): Promise { color, variables, createdAt, - marketplaceData, workspaceId, folderId, isDeployed, @@ -108,7 +107,6 @@ async function fetchWorkflowsFromDB(workspaceId?: string): Promise { color: color || '#3972F6', lastModified: createdAt ? new Date(createdAt) : new Date(), createdAt: createdAt ? new Date(createdAt) : new Date(), - marketplaceData: marketplaceData || null, workspaceId, folderId: folderId || null, } @@ -438,7 +436,6 @@ export const useWorkflowRegistry = create()( deployedAt: workflowData.deployedAt ? new Date(workflowData.deployedAt) : undefined, apiKey: workflowData.apiKey, lastSaved: Date.now(), - marketplaceData: workflowData.marketplaceData || null, deploymentStatuses: {}, } } else { @@ -513,7 +510,7 @@ export const useWorkflowRegistry = create()( body: JSON.stringify({ name: options.name || generateCreativeWorkflowName(), description: options.description || 'New workflow', - color: options.marketplaceId ? '#808080' : getNextWorkflowColor(), + color: getNextWorkflowColor(), workspaceId, folderId: options.folderId || null, }), @@ -537,17 +534,10 @@ export const useWorkflowRegistry = create()( createdAt: new Date(), description: createdWorkflow.description, color: createdWorkflow.color, - marketplaceData: options.marketplaceId - ? { id: options.marketplaceId, status: 'temp' as const } - : undefined, workspaceId, folderId: createdWorkflow.folderId, } - if (options.marketplaceId && options.marketplaceState) { - logger.info(`Created workflow from marketplace: ${options.marketplaceId}`) - } - // Add workflow to registry with server-generated ID set((state) => ({ workflows: { @@ -557,26 +547,16 @@ export const useWorkflowRegistry = create()( error: null, })) - // Initialize subblock values if this is a marketplace import - if (options.marketplaceId && options.marketplaceState?.blocks) { - useSubBlockStore - .getState() - .initializeFromWorkflow(serverWorkflowId, options.marketplaceState.blocks) - } - // Initialize subblock values to ensure they're available for sync - if (!options.marketplaceId) { - // For non-marketplace workflows, initialize empty subblock values - const subblockValues: Record> = {} + const subblockValues: Record> = {} - // Update the subblock store with the initial values - useSubBlockStore.setState((state) => ({ - workflowValues: { - ...state.workflowValues, - [serverWorkflowId]: subblockValues, - }, - })) - } + // Update the subblock store with the initial values + useSubBlockStore.setState((state) => ({ + workflowValues: { + ...state.workflowValues, + [serverWorkflowId]: subblockValues, + }, + })) // Don't set as active workflow here - let the navigation/URL change handle that // This prevents race conditions and flickering @@ -594,107 +574,6 @@ export const useWorkflowRegistry = create()( } }, - /** - * Creates a new workflow from a marketplace workflow - */ - createMarketplaceWorkflow: async ( - marketplaceId: string, - state: any, - metadata: Partial - ) => { - const id = crypto.randomUUID() - - // Generate workflow metadata with marketplace properties - const newWorkflow: WorkflowMetadata = { - id, - name: metadata.name || generateCreativeWorkflowName(), - lastModified: new Date(), - createdAt: new Date(), - description: metadata.description || 'Imported from marketplace', - color: metadata.color || getNextWorkflowColor(), - marketplaceData: { id: marketplaceId, status: 'temp' as const }, - } - - // Prepare workflow state based on the marketplace workflow state - const initialState = { - blocks: state.blocks || {}, - edges: state.edges || [], - loops: state.loops || {}, - parallels: state.parallels || {}, - isDeployed: false, - deployedAt: undefined, - lastSaved: Date.now(), - } - - // Add workflow to registry - set((state) => ({ - workflows: { - ...state.workflows, - [id]: newWorkflow, - }, - error: null, - })) - - // Initialize subblock values from state blocks - if (state.blocks) { - useSubBlockStore.getState().initializeFromWorkflow(id, state.blocks) - } - - // Set as active workflow and update store - set({ activeWorkflowId: id }) - useWorkflowStore.setState(initialState) - - // Immediately persist the marketplace workflow to the database - const persistWorkflow = async () => { - try { - const workflowData = { - [id]: { - id, - name: newWorkflow.name, - description: newWorkflow.description, - color: newWorkflow.color, - state: initialState, - marketplaceData: newWorkflow.marketplaceData, - workspaceId: newWorkflow.workspaceId, - folderId: newWorkflow.folderId, - }, - } - - const response = await fetch('/api/workflows', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - workflows: workflowData, - workspaceId: newWorkflow.workspaceId, - }), - }) - - if (!response.ok) { - throw new Error(`Failed to persist workflow: ${response.statusText}`) - } - - logger.info(`Successfully persisted marketplace workflow ${id} to database`) - } catch (error) { - logger.error(`Failed to persist marketplace workflow ${id}:`, error) - } - } - - // Persist synchronously to ensure workflow exists before Socket.IO operations - try { - await persistWorkflow() - } catch (error) { - logger.error( - `Critical: Failed to persist marketplace workflow ${id}, Socket.IO operations may fail:`, - error - ) - // Don't throw - allow workflow creation to continue in memory - } - - logger.info(`Created marketplace workflow ${id} imported from ${marketplaceId}`) - - return id - }, - /** * Duplicates an existing workflow */ diff --git a/apps/sim/stores/workflows/registry/types.ts b/apps/sim/stores/workflows/registry/types.ts index d3f9bc0a7..ea2539f4d 100644 --- a/apps/sim/stores/workflows/registry/types.ts +++ b/apps/sim/stores/workflows/registry/types.ts @@ -1,8 +1,3 @@ -export interface MarketplaceData { - id: string // Marketplace entry ID to track original marketplace source - status: 'owner' | 'temp' -} - export interface DeploymentStatus { isDeployed: boolean deployedAt?: Date @@ -17,7 +12,6 @@ export interface WorkflowMetadata { createdAt: Date description?: string color: string - marketplaceData?: MarketplaceData | null workspaceId?: string folderId?: string | null } @@ -39,18 +33,11 @@ export interface WorkflowRegistryActions { updateWorkflow: (id: string, metadata: Partial) => Promise createWorkflow: (options?: { isInitial?: boolean - marketplaceId?: string - marketplaceState?: any name?: string description?: string workspaceId?: string folderId?: string | null }) => Promise - createMarketplaceWorkflow: ( - marketplaceId: string, - state: any, - metadata: Partial - ) => Promise duplicateWorkflow: (sourceId: string) => Promise getWorkflowDeploymentStatus: (workflowId: string | null) => DeploymentStatus | null setDeploymentStatus: ( diff --git a/packages/db/migrations/0104_orange_shinobi_shaw.sql b/packages/db/migrations/0104_orange_shinobi_shaw.sql new file mode 100644 index 000000000..46abd21a5 --- /dev/null +++ b/packages/db/migrations/0104_orange_shinobi_shaw.sql @@ -0,0 +1,12 @@ +ALTER TABLE "workflow" DROP CONSTRAINT "workflow_pinned_api_key_id_api_key_id_fk"; +--> statement-breakpoint +ALTER TABLE "workspace" ADD COLUMN "billed_account_user_id" text;--> statement-breakpoint +ALTER TABLE "workspace" ADD COLUMN "allow_personal_api_keys" boolean DEFAULT true NOT NULL;--> statement-breakpoint +UPDATE "workspace" SET "billed_account_user_id" = "owner_id" WHERE "billed_account_user_id" IS NULL;--> statement-breakpoint +ALTER TABLE "workspace" ALTER COLUMN "billed_account_user_id" SET NOT NULL;--> statement-breakpoint +ALTER TABLE "workspace" ADD CONSTRAINT "workspace_billed_account_user_id_user_id_fk" FOREIGN KEY ("billed_account_user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "workflow" DROP COLUMN "deployed_state";--> statement-breakpoint +ALTER TABLE "workflow" DROP COLUMN "pinned_api_key_id";--> statement-breakpoint +ALTER TABLE "workflow" DROP COLUMN "collaborators";--> statement-breakpoint +ALTER TABLE "workflow" DROP COLUMN "is_published";--> statement-breakpoint +ALTER TABLE "workflow" DROP COLUMN "marketplace_data"; \ No newline at end of file diff --git a/packages/db/migrations/meta/0104_snapshot.json b/packages/db/migrations/meta/0104_snapshot.json new file mode 100644 index 000000000..bae1a35e3 --- /dev/null +++ b/packages/db/migrations/meta/0104_snapshot.json @@ -0,0 +1,7245 @@ +{ + "id": "9c485cb2-eb48-4fe8-8e60-f39509b1401c", + "prevId": "a2087ca3-02f6-4390-bbc5-6d791d7a9200", + "version": "7", + "dialect": "postgresql", + "tables": { + "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 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_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.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "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()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "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": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "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": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "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": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "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": {}, + "foreignKeys": { + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "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": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_uploaded_at_idx": { + "name": "doc_kb_uploaded_at_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag1_idx": { + "name": "doc_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag2_idx": { + "name": "doc_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag3_idx": { + "name": "doc_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag4_idx": { + "name": "doc_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag5_idx": { + "name": "doc_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag6_idx": { + "name": "doc_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag7_idx": { + "name": "doc_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "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": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_tag1_idx": { + "name": "emb_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag2_idx": { + "name": "emb_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag3_idx": { + "name": "emb_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag4_idx": { + "name": "emb_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag5_idx": { + "name": "emb_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag6_idx": { + "name": "emb_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag7_idx": { + "name": "emb_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_not_null_check": { + "name": "embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "namespace": { + "name": "namespace", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_namespace_unique": { + "name": "idempotency_key_namespace_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "namespace", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idempotency_key_namespace_idx": { + "name": "idempotency_key_namespace_idx", + "columns": [ + { + "expression": "namespace", + "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 + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "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()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "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" + }, + "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" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_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 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_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": { + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "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": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.marketplace": { + "name": "marketplace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "author_id": { + "name": "author_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "author_name": { + "name": "author_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "category": { + "name": "category", + "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": {}, + "foreignKeys": { + "marketplace_workflow_id_workflow_id_fk": { + "name": "marketplace_workflow_id_workflow_id_fk", + "tableFrom": "marketplace", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "marketplace_author_id_user_id_fk": { + "name": "marketplace_author_id_user_id_fk", + "tableFrom": "marketplace", + "tableTo": "user", + "columnsFrom": ["author_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_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": { + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_idx": { + "name": "mcp_servers_workspace_deleted_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_idx": { + "name": "member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "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" + }, + "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" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "json", + "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()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workflow_idx": { + "name": "memory_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workflow_key_idx": { + "name": "memory_workflow_key_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workflow_id_workflow_id_fk": { + "name": "memory_workflow_id_workflow_id_fk", + "tableFrom": "memory", + "tableTo": "workflow", + "columnsFrom": ["workflow_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 + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "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": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "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": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_idx": { + "name": "permissions_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "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 + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_token_idx": { + "name": "session_token_idx", + "columns": [ + { + "expression": "token", + "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" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_fill_env_vars": { + "name": "auto_fill_env_vars", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_pan": { + "name": "auto_pan", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "console_expanded_by_default": { + "name": "console_expanded_by_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_floating_controls": { + "name": "show_floating_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "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 + }, + "domain": { + "name": "domain", + "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": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.template_stars": { + "name": "template_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_stars_user_id_idx": { + "name": "template_stars_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_id_idx": { + "name": "template_stars_template_id_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_idx": { + "name": "template_stars_user_template_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_user_idx": { + "name": "template_stars_template_user_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_starred_at_idx": { + "name": "template_stars_starred_at_idx", + "columns": [ + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_starred_at_idx": { + "name": "template_stars_template_starred_at_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_unique": { + "name": "template_stars_user_template_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_stars_user_id_user_id_fk": { + "name": "template_stars_user_id_user_id_fk", + "tableFrom": "template_stars", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "template_stars_template_id_templates_id_fk": { + "name": "template_stars_template_id_templates_id_fk", + "tableFrom": "template_stars", + "tableTo": "templates", + "columnsFrom": ["template_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_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 + }, + "author": { + "name": "author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'FileText'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "jsonb", + "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": { + "templates_workflow_id_idx": { + "name": "templates_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_user_id_idx": { + "name": "templates_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_category_idx": { + "name": "templates_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_views_idx": { + "name": "templates_views_idx", + "columns": [ + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_stars_idx": { + "name": "templates_stars_idx", + "columns": [ + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_category_views_idx": { + "name": "templates_category_views_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_category_stars_idx": { + "name": "templates_category_stars_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_user_category_idx": { + "name": "templates_user_category_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_created_at_idx": { + "name": "templates_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_updated_at_idx": { + "name": "templates_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "templates_workflow_id_workflow_id_fk": { + "name": "templates_workflow_id_workflow_id_fk", + "tableFrom": "templates", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "templates_user_id_user_id_fk": { + "name": "templates_user_id_user_id_fk", + "tableFrom": "templates", + "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 + }, + "image": { + "name": "image", + "type": "text", + "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 + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_rate_limits": { + "name": "user_rate_limits", + "schema": "", + "columns": { + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "sync_api_requests": { + "name": "sync_api_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "async_api_requests": { + "name": "async_api_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "api_endpoint_requests": { + "name": "api_endpoint_requests", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "window_start": { + "name": "window_start", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_rate_limited": { + "name": "is_rate_limited", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "rate_limit_reset_at": { + "name": "rate_limit_reset_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'10'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "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": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "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.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "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": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "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": { + "path_idx": { + "name": "path_idx", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_block_id_workflow_blocks_id_fk": { + "name": "webhook_block_id_workflow_blocks_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_blocks", + "columnsFrom": ["block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_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 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "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": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_workflow_type_idx": { + "name": "workflow_blocks_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "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": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_id_idx": { + "name": "workflow_deployment_version_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_idx": { + "name": "workflow_execution_logs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_folder": { + "name": "workflow_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#6B7280'" + }, + "is_expanded": { + "name": "is_expanded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "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": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_log_webhook": { + "name": "workflow_log_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "include_final_output": { + "name": "include_final_output", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_trace_spans": { + "name": "include_trace_spans", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_rate_limits": { + "name": "include_rate_limits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_usage_data": { + "name": "include_usage_data", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "level_filter": { + "name": "level_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['info', 'error']::text[]" + }, + "trigger_filter": { + "name": "trigger_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['api', 'webhook', 'schedule', 'manual', 'chat']::text[]" + }, + "active": { + "name": "active", + "type": "boolean", + "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": { + "workflow_log_webhook_workflow_id_idx": { + "name": "workflow_log_webhook_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_log_webhook_active_idx": { + "name": "workflow_log_webhook_active_idx", + "columns": [ + { + "expression": "active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_log_webhook_workflow_id_workflow_id_fk": { + "name": "workflow_log_webhook_workflow_id_workflow_id_fk", + "tableFrom": "workflow_log_webhook", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_log_webhook_delivery": { + "name": "workflow_log_webhook_delivery", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "webhook_delivery_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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 + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "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": { + "workflow_log_webhook_delivery_subscription_id_idx": { + "name": "workflow_log_webhook_delivery_subscription_id_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_log_webhook_delivery_execution_id_idx": { + "name": "workflow_log_webhook_delivery_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_log_webhook_delivery_status_idx": { + "name": "workflow_log_webhook_delivery_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_log_webhook_delivery_next_attempt_idx": { + "name": "workflow_log_webhook_delivery_next_attempt_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_log_webhook_delivery_subscription_id_workflow_log_webhook_id_fk": { + "name": "workflow_log_webhook_delivery_subscription_id_workflow_log_webhook_id_fk", + "tableFrom": "workflow_log_webhook_delivery", + "tableTo": "workflow_log_webhook", + "columnsFrom": ["subscription_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_log_webhook_delivery_workflow_id_workflow_id_fk": { + "name": "workflow_log_webhook_delivery_workflow_id_workflow_id_fk", + "tableFrom": "workflow_log_webhook_delivery", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_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": { + "workflow_schedule_workflow_block_unique": { + "name": "workflow_schedule_workflow_block_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_block_id_workflow_blocks_id_fk": { + "name": "workflow_schedule_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_blocks", + "columnsFrom": ["block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "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": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "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": {}, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "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": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_files_key_unique": { + "name": "workspace_files_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_invitation": { + "name": "workspace_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "workspace_invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permissions": { + "name": "permissions", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'admin'" + }, + "org_invitation_id": { + "name": "org_invitation_id", + "type": "text", + "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()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_invitation_workspace_id_workspace_id_fk": { + "name": "workspace_invitation_workspace_id_workspace_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_invitation_inviter_id_user_id_fk": { + "name": "workspace_invitation_inviter_id_user_id_fk", + "tableFrom": "workspace_invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_invitation_token_unique": { + "name": "workspace_invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.webhook_delivery_status": { + "name": "webhook_delivery_status", + "schema": "public", + "values": ["pending", "in_progress", "success", "failed"] + }, + "public.workspace_invitation_status": { + "name": "workspace_invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 0dd9953a1..35579e61a 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -722,6 +722,13 @@ "when": 1761845605676, "tag": "0103_careful_harpoon", "breakpoints": true + }, + { + "idx": 104, + "version": "7", + "when": 1761848118406, + "tag": "0104_orange_shinobi_shaw", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 37a4723db..91552f9c6 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -145,15 +145,10 @@ export const workflow = pgTable( createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), isDeployed: boolean('is_deployed').notNull().default(false), - deployedState: json('deployed_state'), deployedAt: timestamp('deployed_at'), - pinnedApiKeyId: text('pinned_api_key_id').references(() => apiKey.id, { onDelete: 'set null' }), - collaborators: json('collaborators').notNull().default('[]'), runCount: integer('run_count').notNull().default(0), lastRunAt: timestamp('last_run_at'), variables: json('variables').default('{}'), - isPublished: boolean('is_published').notNull().default(false), - marketplaceData: json('marketplace_data'), }, (table) => ({ userIdIdx: index('workflow_user_id_idx').on(table.userId), @@ -727,6 +722,10 @@ export const workspace = pgTable('workspace', { ownerId: text('owner_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), + billedAccountUserId: text('billed_account_user_id') + .notNull() + .references(() => user.id, { onDelete: 'no action' }), + allowPersonalApiKeys: boolean('allow_personal_api_keys').notNull().default(true), createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at').notNull().defaultNow(), }) diff --git a/packages/python-sdk/README.md b/packages/python-sdk/README.md index 0b35b7ade..efcf91df7 100644 --- a/packages/python-sdk/README.md +++ b/packages/python-sdk/README.md @@ -158,7 +158,6 @@ class WorkflowExecutionResult: class WorkflowStatus: is_deployed: bool deployed_at: Optional[str] = None - is_published: bool = False needs_redeployment: bool = False ``` diff --git a/packages/python-sdk/examples/basic_usage.py b/packages/python-sdk/examples/basic_usage.py index 1ddd25b70..b4c93dec6 100644 --- a/packages/python-sdk/examples/basic_usage.py +++ b/packages/python-sdk/examples/basic_usage.py @@ -77,7 +77,6 @@ def status_example(): status = client.get_workflow_status("your-workflow-id") print(f"Status: {{\n" f" deployed: {status.is_deployed},\n" - f" published: {status.is_published},\n" f" needs_redeployment: {status.needs_redeployment},\n" f" deployed_at: {status.deployed_at}\n" f"}}") diff --git a/packages/python-sdk/simstudio/__init__.py b/packages/python-sdk/simstudio/__init__.py index f57626e36..d20b7d7e9 100644 --- a/packages/python-sdk/simstudio/__init__.py +++ b/packages/python-sdk/simstudio/__init__.py @@ -42,7 +42,6 @@ class WorkflowStatus: """Status of a workflow.""" is_deployed: bool deployed_at: Optional[str] = None - is_published: bool = False needs_redeployment: bool = False @@ -296,7 +295,6 @@ class SimStudioClient: return WorkflowStatus( is_deployed=status_data.get('isDeployed', False), deployed_at=status_data.get('deployedAt'), - is_published=status_data.get('isPublished', False), needs_redeployment=status_data.get('needsRedeployment', False) ) diff --git a/packages/python-sdk/tests/test_client.py b/packages/python-sdk/tests/test_client.py index b117b5c3a..faa3176a9 100644 --- a/packages/python-sdk/tests/test_client.py +++ b/packages/python-sdk/tests/test_client.py @@ -79,12 +79,10 @@ def test_workflow_status(): status = WorkflowStatus( is_deployed=True, deployed_at="2023-01-01T00:00:00Z", - is_published=False, needs_redeployment=False ) assert status.is_deployed is True assert status.deployed_at == "2023-01-01T00:00:00Z" - assert status.is_published is False assert status.needs_redeployment is False diff --git a/packages/ts-sdk/README.md b/packages/ts-sdk/README.md index 78ffca265..04582aeda 100644 --- a/packages/ts-sdk/README.md +++ b/packages/ts-sdk/README.md @@ -157,7 +157,6 @@ interface WorkflowExecutionResult { interface WorkflowStatus { isDeployed: boolean; deployedAt?: string; - isPublished: boolean; needsRedeployment: boolean; } ``` diff --git a/packages/ts-sdk/examples/basic-usage.ts b/packages/ts-sdk/examples/basic-usage.ts index c5b01471c..f89b0d0d9 100644 --- a/packages/ts-sdk/examples/basic-usage.ts +++ b/packages/ts-sdk/examples/basic-usage.ts @@ -82,7 +82,6 @@ async function statusExample() { const status = await client.getWorkflowStatus('your-workflow-id') console.log('Status:', { deployed: status.isDeployed, - published: status.isPublished, needsRedeployment: status.needsRedeployment, deployedAt: status.deployedAt, }) diff --git a/packages/ts-sdk/src/index.test.ts b/packages/ts-sdk/src/index.test.ts index bde140647..e8ebbddad 100644 --- a/packages/ts-sdk/src/index.test.ts +++ b/packages/ts-sdk/src/index.test.ts @@ -72,7 +72,6 @@ describe('SimStudioClient', () => { json: vi.fn().mockResolvedValue({ isDeployed: true, deployedAt: '2023-01-01T00:00:00Z', - isPublished: false, needsRedeployment: false, }), } @@ -89,7 +88,6 @@ describe('SimStudioClient', () => { json: vi.fn().mockResolvedValue({ isDeployed: false, deployedAt: null, - isPublished: false, needsRedeployment: true, }), } diff --git a/packages/ts-sdk/src/index.ts b/packages/ts-sdk/src/index.ts index 559f04cfe..2152b9917 100644 --- a/packages/ts-sdk/src/index.ts +++ b/packages/ts-sdk/src/index.ts @@ -22,7 +22,6 @@ export interface WorkflowExecutionResult { export interface WorkflowStatus { isDeployed: boolean deployedAt?: string - isPublished: boolean needsRedeployment: boolean }