From c68c05260172eaf60668e8c63d415869f6544515 Mon Sep 17 00:00:00 2001 From: waleed Date: Sat, 27 Sep 2025 11:37:28 -0700 Subject: [PATCH] improvement(chat): deployed chat no longer uses subdomains, uses sim.ai/chat/[identifier] --- .../otp/route.ts | 20 +- .../route.test.ts | 56 +- .../{[subdomain] => [identifier]}/route.ts | 34 +- apps/sim/app/api/chat/edit/[id]/route.test.ts | 28 +- apps/sim/app/api/chat/edit/[id]/route.ts | 28 +- apps/sim/app/api/chat/route.test.ts | 34 +- apps/sim/app/api/chat/route.ts | 30 +- .../chat/subdomains/validate/route.test.ts | 242 - .../app/api/chat/subdomains/validate/route.ts | 74 - .../api/workflows/[id]/chat/status/route.ts | 4 +- .../{[subdomain] => [identifier]}/chat.css | 0 .../{[subdomain] => [identifier]}/chat.tsx | 12 +- .../{[subdomain] => [identifier]}/layout.tsx | 0 apps/sim/app/chat/[identifier]/page.tsx | 6 + apps/sim/app/chat/[subdomain]/page.tsx | 6 - .../chat/components/auth/email/email-auth.tsx | 10 +- .../auth/password/password-auth.tsx | 6 +- apps/sim/app/conditional-theme-provider.tsx | 3 +- .../components/chat-deploy/chat-deploy.tsx | 32 +- ...bdomain-input.tsx => identifier-input.tsx} | 36 +- .../chat-deploy/components/success-view.tsx | 25 +- .../chat-deploy/hooks/use-chat-deployment.ts | 14 +- .../chat-deploy/hooks/use-chat-form.ts | 14 +- .../hooks/use-identifier-validation.ts | 46 + .../hooks/use-subdomain-validation.ts | 82 - apps/sim/middleware.ts | 319 +- .../0094_perpetual_the_watchers.sql | 3 + .../db/migrations/meta/0094_snapshot.json | 6818 +++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 6 +- 30 files changed, 7280 insertions(+), 715 deletions(-) rename apps/sim/app/api/chat/{[subdomain] => [identifier]}/otp/route.ts (93%) rename apps/sim/app/api/chat/{[subdomain] => [identifier]}/route.test.ts (87%) rename apps/sim/app/api/chat/{[subdomain] => [identifier]}/route.ts (86%) delete mode 100644 apps/sim/app/api/chat/subdomains/validate/route.test.ts delete mode 100644 apps/sim/app/api/chat/subdomains/validate/route.ts rename apps/sim/app/chat/{[subdomain] => [identifier]}/chat.css (100%) rename apps/sim/app/chat/{[subdomain] => [identifier]}/chat.tsx (98%) rename apps/sim/app/chat/{[subdomain] => [identifier]}/layout.tsx (100%) create mode 100644 apps/sim/app/chat/[identifier]/page.tsx delete mode 100644 apps/sim/app/chat/[subdomain]/page.tsx rename apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/{subdomain-input.tsx => identifier-input.tsx} (69%) create mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-identifier-validation.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-subdomain-validation.ts create mode 100644 packages/db/migrations/0094_perpetual_the_watchers.sql create mode 100644 packages/db/migrations/meta/0094_snapshot.json diff --git a/apps/sim/app/api/chat/[subdomain]/otp/route.ts b/apps/sim/app/api/chat/[identifier]/otp/route.ts similarity index 93% rename from apps/sim/app/api/chat/[subdomain]/otp/route.ts rename to apps/sim/app/api/chat/[identifier]/otp/route.ts index 6b000f8d4..8ba425972 100644 --- a/apps/sim/app/api/chat/[subdomain]/otp/route.ts +++ b/apps/sim/app/api/chat/[identifier]/otp/route.ts @@ -113,13 +113,13 @@ const otpVerifySchema = z.object({ // Send OTP endpoint export async function POST( request: NextRequest, - { params }: { params: Promise<{ subdomain: string }> } + { params }: { params: Promise<{ identifier: string }> } ) { - const { subdomain } = await params + const { identifier } = await params const requestId = generateRequestId() try { - logger.debug(`[${requestId}] Processing OTP request for subdomain: ${subdomain}`) + logger.debug(`[${requestId}] Processing OTP request for identifier: ${identifier}`) // Parse request body let body @@ -136,11 +136,11 @@ export async function POST( title: chat.title, }) .from(chat) - .where(eq(chat.subdomain, subdomain)) + .where(eq(chat.identifier, identifier)) .limit(1) if (deploymentResult.length === 0) { - logger.warn(`[${requestId}] Chat not found for subdomain: ${subdomain}`) + logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`) return addCorsHeaders(createErrorResponse('Chat not found', 404), request) } @@ -227,13 +227,13 @@ export async function POST( // Verify OTP endpoint export async function PUT( request: NextRequest, - { params }: { params: Promise<{ subdomain: string }> } + { params }: { params: Promise<{ identifier: string }> } ) { - const { subdomain } = await params + const { identifier } = await params const requestId = generateRequestId() try { - logger.debug(`[${requestId}] Verifying OTP for subdomain: ${subdomain}`) + logger.debug(`[${requestId}] Verifying OTP for identifier: ${identifier}`) // Parse request body let body @@ -248,11 +248,11 @@ export async function PUT( authType: chat.authType, }) .from(chat) - .where(eq(chat.subdomain, subdomain)) + .where(eq(chat.identifier, identifier)) .limit(1) if (deploymentResult.length === 0) { - logger.warn(`[${requestId}] Chat not found for subdomain: ${subdomain}`) + logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`) return addCorsHeaders(createErrorResponse('Chat not found', 404), request) } diff --git a/apps/sim/app/api/chat/[subdomain]/route.test.ts b/apps/sim/app/api/chat/[identifier]/route.test.ts similarity index 87% rename from apps/sim/app/api/chat/[subdomain]/route.test.ts rename to apps/sim/app/api/chat/[identifier]/route.test.ts index bce1e005f..6d6005eea 100644 --- a/apps/sim/app/api/chat/[subdomain]/route.test.ts +++ b/apps/sim/app/api/chat/[identifier]/route.test.ts @@ -136,9 +136,9 @@ describe('Chat Subdomain API Route', () => { describe('GET endpoint', () => { it('should return chat info for a valid subdomain', async () => { const req = createMockRequest('GET') - const params = Promise.resolve({ subdomain: 'test-chat' }) + const params = Promise.resolve({ identifier: 'test-chat' }) - const { GET } = await import('@/app/api/chat/[subdomain]/route') + const { GET } = await import('@/app/api/chat/[identifier]/route') const response = await GET(req, { params }) @@ -167,9 +167,9 @@ describe('Chat Subdomain API Route', () => { }) const req = createMockRequest('GET') - const params = Promise.resolve({ subdomain: 'nonexistent' }) + const params = Promise.resolve({ identifier: 'nonexistent' }) - const { GET } = await import('@/app/api/chat/[subdomain]/route') + const { GET } = await import('@/app/api/chat/[identifier]/route') const response = await GET(req, { params }) @@ -201,9 +201,9 @@ describe('Chat Subdomain API Route', () => { }) const req = createMockRequest('GET') - const params = Promise.resolve({ subdomain: 'inactive-chat' }) + const params = Promise.resolve({ identifier: 'inactive-chat' }) - const { GET } = await import('@/app/api/chat/[subdomain]/route') + const { GET } = await import('@/app/api/chat/[identifier]/route') const response = await GET(req, { params }) @@ -222,9 +222,9 @@ describe('Chat Subdomain API Route', () => { })) const req = createMockRequest('GET') - const params = Promise.resolve({ subdomain: 'password-protected-chat' }) + const params = Promise.resolve({ identifier: 'password-protected-chat' }) - const { GET } = await import('@/app/api/chat/[subdomain]/route') + const { GET } = await import('@/app/api/chat/[identifier]/route') const response = await GET(req, { params }) @@ -243,9 +243,9 @@ describe('Chat Subdomain API Route', () => { describe('POST endpoint', () => { it('should handle authentication requests without input', async () => { const req = createMockRequest('POST', { password: 'test-password' }) - const params = Promise.resolve({ subdomain: 'password-protected-chat' }) + const params = Promise.resolve({ identifier: 'password-protected-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') const response = await POST(req, { params }) @@ -259,9 +259,9 @@ describe('Chat Subdomain API Route', () => { it('should return 400 for requests without input', async () => { const req = createMockRequest('POST', {}) - const params = Promise.resolve({ subdomain: 'test-chat' }) + const params = Promise.resolve({ identifier: 'test-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') const response = await POST(req, { params }) @@ -280,9 +280,9 @@ describe('Chat Subdomain API Route', () => { })) const req = createMockRequest('POST', { input: 'Hello' }) - const params = Promise.resolve({ subdomain: 'protected-chat' }) + const params = Promise.resolve({ identifier: 'protected-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') const response = await POST(req, { params }) @@ -343,9 +343,9 @@ describe('Chat Subdomain API Route', () => { }) const req = createMockRequest('POST', { input: 'Hello' }) - const params = Promise.resolve({ subdomain: 'test-chat' }) + const params = Promise.resolve({ identifier: 'test-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') const response = await POST(req, { params }) @@ -358,9 +358,9 @@ describe('Chat Subdomain API Route', () => { it('should return streaming response for valid chat messages', async () => { const req = createMockRequest('POST', { input: 'Hello world', conversationId: 'conv-123' }) - const params = Promise.resolve({ subdomain: 'test-chat' }) + const params = Promise.resolve({ identifier: 'test-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') const response = await POST(req, { params }) @@ -375,9 +375,9 @@ describe('Chat Subdomain API Route', () => { it('should handle streaming response body correctly', async () => { const req = createMockRequest('POST', { input: 'Hello world' }) - const params = Promise.resolve({ subdomain: 'test-chat' }) + const params = Promise.resolve({ identifier: 'test-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') const response = await POST(req, { params }) @@ -405,9 +405,9 @@ describe('Chat Subdomain API Route', () => { }) const req = createMockRequest('POST', { input: 'Trigger error' }) - const params = Promise.resolve({ subdomain: 'test-chat' }) + const params = Promise.resolve({ identifier: 'test-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') const response = await POST(req, { params }) @@ -430,9 +430,9 @@ describe('Chat Subdomain API Route', () => { json: vi.fn().mockRejectedValue(new Error('Invalid JSON')), } as any - const params = Promise.resolve({ subdomain: 'test-chat' }) + const params = Promise.resolve({ identifier: 'test-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') const response = await POST(req, { params }) @@ -448,9 +448,9 @@ describe('Chat Subdomain API Route', () => { input: 'Hello world', conversationId: 'test-conversation-123', }) - const params = Promise.resolve({ subdomain: 'test-chat' }) + const params = Promise.resolve({ identifier: 'test-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') await POST(req, { params }) @@ -463,9 +463,9 @@ describe('Chat Subdomain API Route', () => { it('should handle missing conversationId gracefully', async () => { const req = createMockRequest('POST', { input: 'Hello world' }) - const params = Promise.resolve({ subdomain: 'test-chat' }) + const params = Promise.resolve({ identifier: 'test-chat' }) - const { POST } = await import('@/app/api/chat/[subdomain]/route') + const { POST } = await import('@/app/api/chat/[identifier]/route') await POST(req, { params }) diff --git a/apps/sim/app/api/chat/[subdomain]/route.ts b/apps/sim/app/api/chat/[identifier]/route.ts similarity index 86% rename from apps/sim/app/api/chat/[subdomain]/route.ts rename to apps/sim/app/api/chat/[identifier]/route.ts index d667b2e85..9551e9913 100644 --- a/apps/sim/app/api/chat/[subdomain]/route.ts +++ b/apps/sim/app/api/chat/[identifier]/route.ts @@ -13,18 +13,18 @@ import { } from '@/app/api/chat/utils' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' -const logger = createLogger('ChatSubdomainAPI') +const logger = createLogger('ChatIdentifierAPI') -// This endpoint handles chat interactions via the subdomain +// This endpoint handles chat interactions via the identifier export async function POST( request: NextRequest, - { params }: { params: Promise<{ subdomain: string }> } + { params }: { params: Promise<{ identifier: string }> } ) { - const { subdomain } = await params + const { identifier } = await params const requestId = generateRequestId() try { - logger.debug(`[${requestId}] Processing chat request for subdomain: ${subdomain}`) + logger.debug(`[${requestId}] Processing chat request for identifier: ${identifier}`) // Parse the request body once let parsedBody @@ -34,7 +34,7 @@ export async function POST( return addCorsHeaders(createErrorResponse('Invalid request body', 400), request) } - // Find the chat deployment for this subdomain + // Find the chat deployment for this identifier const deploymentResult = await db .select({ id: chat.id, @@ -47,11 +47,11 @@ export async function POST( outputConfigs: chat.outputConfigs, }) .from(chat) - .where(eq(chat.subdomain, subdomain)) + .where(eq(chat.identifier, identifier)) .limit(1) if (deploymentResult.length === 0) { - logger.warn(`[${requestId}] Chat not found for subdomain: ${subdomain}`) + logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`) return addCorsHeaders(createErrorResponse('Chat not found', 404), request) } @@ -59,7 +59,7 @@ export async function POST( // Check if the chat is active if (!deployment.isActive) { - logger.warn(`[${requestId}] Chat is not active: ${subdomain}`) + logger.warn(`[${requestId}] Chat is not active: ${identifier}`) return addCorsHeaders(createErrorResponse('This chat is currently unavailable', 403), request) } @@ -139,15 +139,15 @@ export async function POST( // This endpoint returns information about the chat export async function GET( request: NextRequest, - { params }: { params: Promise<{ subdomain: string }> } + { params }: { params: Promise<{ identifier: string }> } ) { - const { subdomain } = await params + const { identifier } = await params const requestId = generateRequestId() try { - logger.debug(`[${requestId}] Fetching chat info for subdomain: ${subdomain}`) + logger.debug(`[${requestId}] Fetching chat info for identifier: ${identifier}`) - // Find the chat deployment for this subdomain + // Find the chat deployment for this identifier const deploymentResult = await db .select({ id: chat.id, @@ -162,11 +162,11 @@ export async function GET( outputConfigs: chat.outputConfigs, }) .from(chat) - .where(eq(chat.subdomain, subdomain)) + .where(eq(chat.identifier, identifier)) .limit(1) if (deploymentResult.length === 0) { - logger.warn(`[${requestId}] Chat not found for subdomain: ${subdomain}`) + logger.warn(`[${requestId}] Chat not found for identifier: ${identifier}`) return addCorsHeaders(createErrorResponse('Chat not found', 404), request) } @@ -174,7 +174,7 @@ export async function GET( // Check if the chat is active if (!deployment.isActive) { - logger.warn(`[${requestId}] Chat is not active: ${subdomain}`) + logger.warn(`[${requestId}] Chat is not active: ${identifier}`) return addCorsHeaders(createErrorResponse('This chat is currently unavailable', 403), request) } @@ -205,7 +205,7 @@ export async function GET( const authResult = await validateChatAuth(requestId, deployment, request) if (!authResult.authorized) { logger.info( - `[${requestId}] Authentication required for chat: ${subdomain}, type: ${deployment.authType}` + `[${requestId}] Authentication required for chat: ${identifier}, type: ${deployment.authType}` ) return addCorsHeaders( createErrorResponse(authResult.error || 'Authentication required', 401), diff --git a/apps/sim/app/api/chat/edit/[id]/route.test.ts b/apps/sim/app/api/chat/edit/[id]/route.test.ts index 445ed4abf..baa71a02b 100644 --- a/apps/sim/app/api/chat/edit/[id]/route.test.ts +++ b/apps/sim/app/api/chat/edit/[id]/route.test.ts @@ -39,7 +39,7 @@ describe('Chat Edit API Route', () => { })) vi.doMock('@sim/db/schema', () => ({ - chat: { id: 'id', subdomain: 'subdomain', userId: 'userId' }, + chat: { id: 'id', identifier: 'identifier', userId: 'userId' }, })) vi.doMock('@/lib/logs/console/logger', () => ({ @@ -128,7 +128,7 @@ describe('Chat Edit API Route', () => { const mockChat = { id: 'chat-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', description: 'A test chat', password: 'encrypted-password', @@ -144,11 +144,11 @@ describe('Chat Edit API Route', () => { expect(response.status).toBe(200) expect(mockCreateSuccessResponse).toHaveBeenCalledWith({ id: 'chat-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', description: 'A test chat', customizations: { primaryColor: '#000000' }, - chatUrl: 'http://test-chat.localhost:3000', + chatUrl: 'http://localhost:3000/chat/test-chat', hasPassword: true, }) }) @@ -201,7 +201,7 @@ describe('Chat Edit API Route', () => { const mockChat = { id: 'chat-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', authType: 'public', } @@ -219,12 +219,12 @@ describe('Chat Edit API Route', () => { expect(mockUpdate).toHaveBeenCalled() expect(mockCreateSuccessResponse).toHaveBeenCalledWith({ id: 'chat-123', - chatUrl: 'http://test-chat.localhost:3000', + chatUrl: 'http://localhost:3000/chat/test-chat', message: 'Chat deployment updated successfully', }) }) - it('should handle subdomain conflicts', async () => { + it('should handle identifier conflicts', async () => { vi.doMock('@/lib/auth', () => ({ getSession: vi.fn().mockResolvedValue({ user: { id: 'user-id' }, @@ -233,23 +233,23 @@ describe('Chat Edit API Route', () => { const mockChat = { id: 'chat-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', } mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat }) - // Mock subdomain conflict - mockLimit.mockResolvedValueOnce([{ id: 'other-chat-id', subdomain: 'new-subdomain' }]) + // Mock identifier conflict + mockLimit.mockResolvedValueOnce([{ id: 'other-chat-id', identifier: 'new-identifier' }]) const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { method: 'PATCH', - body: JSON.stringify({ subdomain: 'new-subdomain' }), + body: JSON.stringify({ identifier: 'new-identifier' }), }) const { PATCH } = await import('@/app/api/chat/edit/[id]/route') const response = await PATCH(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(400) - expect(mockCreateErrorResponse).toHaveBeenCalledWith('Subdomain already in use', 400) + expect(mockCreateErrorResponse).toHaveBeenCalledWith('Identifier already in use', 400) }) it('should validate password requirement for password auth', async () => { @@ -261,7 +261,7 @@ describe('Chat Edit API Route', () => { const mockChat = { id: 'chat-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', authType: 'public', password: null, @@ -292,7 +292,7 @@ describe('Chat Edit API Route', () => { const mockChat = { id: 'chat-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', authType: 'public', } diff --git a/apps/sim/app/api/chat/edit/[id]/route.ts b/apps/sim/app/api/chat/edit/[id]/route.ts index 2a56fe955..17d65244b 100644 --- a/apps/sim/app/api/chat/edit/[id]/route.ts +++ b/apps/sim/app/api/chat/edit/[id]/route.ts @@ -18,10 +18,10 @@ const logger = createLogger('ChatDetailAPI') // Schema for updating an existing chat const chatUpdateSchema = z.object({ workflowId: z.string().min(1, 'Workflow ID is required').optional(), - subdomain: z + identifier: z .string() - .min(1, 'Subdomain is required') - .regex(/^[a-z0-9-]+$/, 'Subdomain can only contain lowercase letters, numbers, and hyphens') + .min(1, 'Identifier is required') + .regex(/^[a-z0-9-]+$/, 'Identifier can only contain lowercase letters, numbers, and hyphens') .optional(), title: z.string().min(1, 'Title is required').optional(), description: z.string().optional(), @@ -71,7 +71,7 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{ const baseDomain = getEmailDomain() const protocol = isDev ? 'http' : 'https' - const chatUrl = `${protocol}://${chatRecord.subdomain}.${baseDomain}` + const chatUrl = `${protocol}://${baseDomain}/chat/${chatRecord.identifier}` const result = { ...safeData, @@ -117,7 +117,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< // Extract validated data const { workflowId, - subdomain, + identifier, title, description, customizations, @@ -127,16 +127,16 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< outputConfigs, } = validatedData - // Check if subdomain is changing and if it's available - if (subdomain && subdomain !== existingChat[0].subdomain) { - const existingSubdomain = await db + // Check if identifier is changing and if it's available + if (identifier && identifier !== existingChat[0].identifier) { + const existingIdentifier = await db .select() .from(chat) - .where(eq(chat.subdomain, subdomain)) + .where(eq(chat.identifier, identifier)) .limit(1) - if (existingSubdomain.length > 0 && existingSubdomain[0].id !== chatId) { - return createErrorResponse('Subdomain already in use', 400) + if (existingIdentifier.length > 0 && existingIdentifier[0].id !== chatId) { + return createErrorResponse('Identifier already in use', 400) } } @@ -165,7 +165,7 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< // Only include fields that are provided if (workflowId) updateData.workflowId = workflowId - if (subdomain) updateData.subdomain = subdomain + if (identifier) updateData.identifier = identifier if (title) updateData.title = title if (description !== undefined) updateData.description = description if (customizations) updateData.customizations = customizations @@ -213,11 +213,11 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< // Update the chat deployment await db.update(chat).set(updateData).where(eq(chat.id, chatId)) - const updatedSubdomain = subdomain || existingChat[0].subdomain + const updatedIdentifier = identifier || existingChat[0].identifier const baseDomain = getEmailDomain() const protocol = isDev ? 'http' : 'https' - const chatUrl = `${protocol}://${updatedSubdomain}.${baseDomain}` + const chatUrl = `${protocol}://${baseDomain}/chat/${updatedIdentifier}` logger.info(`Chat "${chatId}" updated successfully`) diff --git a/apps/sim/app/api/chat/route.test.ts b/apps/sim/app/api/chat/route.test.ts index 030871a92..7661f7b57 100644 --- a/apps/sim/app/api/chat/route.test.ts +++ b/apps/sim/app/api/chat/route.test.ts @@ -37,7 +37,7 @@ describe('Chat API Route', () => { })) vi.doMock('@sim/db/schema', () => ({ - chat: { userId: 'userId', subdomain: 'subdomain' }, + chat: { userId: 'userId', identifier: 'identifier' }, workflow: { id: 'id', userId: 'userId', isDeployed: 'isDeployed' }, })) @@ -169,7 +169,7 @@ describe('Chat API Route', () => { expect(response.status).toBe(400) }) - it('should reject if subdomain already exists', async () => { + it('should reject if identifier already exists', async () => { vi.doMock('@/lib/auth', () => ({ getSession: vi.fn().mockResolvedValue({ user: { id: 'user-id' }, @@ -178,7 +178,7 @@ describe('Chat API Route', () => { const validData = { workflowId: 'workflow-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', customizations: { primaryColor: '#000000', @@ -186,7 +186,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([{ id: 'existing-chat' }]) // Subdomain exists + mockLimit.mockResolvedValueOnce([{ id: 'existing-chat' }]) // Identifier exists const req = new NextRequest('http://localhost:3000/api/chat', { method: 'POST', @@ -196,7 +196,7 @@ describe('Chat API Route', () => { const response = await POST(req) expect(response.status).toBe(400) - expect(mockCreateErrorResponse).toHaveBeenCalledWith('Subdomain already in use', 400) + expect(mockCreateErrorResponse).toHaveBeenCalledWith('Identifier already in use', 400) }) it('should reject if workflow not found', async () => { @@ -208,7 +208,7 @@ describe('Chat API Route', () => { const validData = { workflowId: 'workflow-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', customizations: { primaryColor: '#000000', @@ -216,7 +216,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Subdomain is available + mockLimit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: false }) const req = new NextRequest('http://localhost:3000/api/chat', { @@ -254,7 +254,7 @@ describe('Chat API Route', () => { const validData = { workflowId: 'workflow-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', customizations: { primaryColor: '#000000', @@ -262,7 +262,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Subdomain is available + mockLimit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: true, workflow: { userId: 'user-id', workspaceId: null, isDeployed: true }, @@ -299,7 +299,7 @@ describe('Chat API Route', () => { const validData = { workflowId: 'workflow-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', customizations: { primaryColor: '#000000', @@ -307,7 +307,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Subdomain is available + mockLimit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: true, workflow: { userId: 'other-user-id', workspaceId: 'workspace-123', isDeployed: true }, @@ -334,7 +334,7 @@ describe('Chat API Route', () => { const validData = { workflowId: 'workflow-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', customizations: { primaryColor: '#000000', @@ -342,7 +342,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Subdomain is available + mockLimit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: false, }) @@ -371,7 +371,7 @@ describe('Chat API Route', () => { const validData = { workflowId: 'workflow-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', customizations: { primaryColor: '#000000', @@ -379,7 +379,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Subdomain is available + mockLimit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockRejectedValue(new Error('Permission check failed')) const req = new NextRequest('http://localhost:3000/api/chat', { @@ -402,7 +402,7 @@ describe('Chat API Route', () => { const validData = { workflowId: 'workflow-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', customizations: { primaryColor: '#000000', @@ -410,7 +410,7 @@ describe('Chat API Route', () => { }, } - mockLimit.mockResolvedValueOnce([]) // Subdomain is available + mockLimit.mockResolvedValueOnce([]) // Identifier is available mockCheckWorkflowAccessForChatCreation.mockResolvedValue({ hasAccess: true, workflow: { userId: 'user-id', workspaceId: null, isDeployed: false }, diff --git a/apps/sim/app/api/chat/route.ts b/apps/sim/app/api/chat/route.ts index 333643e9c..c546691ec 100644 --- a/apps/sim/app/api/chat/route.ts +++ b/apps/sim/app/api/chat/route.ts @@ -16,10 +16,10 @@ const logger = createLogger('ChatAPI') const chatSchema = z.object({ workflowId: z.string().min(1, 'Workflow ID is required'), - subdomain: z + identifier: z .string() - .min(1, 'Subdomain is required') - .regex(/^[a-z0-9-]+$/, 'Subdomain can only contain lowercase letters, numbers, and hyphens'), + .min(1, 'Identifier is required') + .regex(/^[a-z0-9-]+$/, 'Identifier can only contain lowercase letters, numbers, and hyphens'), title: z.string().min(1, 'Title is required'), description: z.string().optional(), customizations: z.object({ @@ -76,7 +76,7 @@ export async function POST(request: NextRequest) { // Extract validated data const { workflowId, - subdomain, + identifier, title, description = '', customizations, @@ -98,15 +98,15 @@ export async function POST(request: NextRequest) { ) } - // Check if subdomain is available - const existingSubdomain = await db + // Check if identifier is available + const existingIdentifier = await db .select() .from(chat) - .where(eq(chat.subdomain, subdomain)) + .where(eq(chat.identifier, identifier)) .limit(1) - if (existingSubdomain.length > 0) { - return createErrorResponse('Subdomain already in use', 400) + if (existingIdentifier.length > 0) { + return createErrorResponse('Identifier already in use', 400) } // Check if user has permission to create chat for this workflow @@ -137,7 +137,7 @@ export async function POST(request: NextRequest) { // Log the values we're inserting logger.info('Creating chat deployment with values:', { workflowId, - subdomain, + identifier, title, authType, hasPassword: !!encryptedPassword, @@ -156,7 +156,7 @@ export async function POST(request: NextRequest) { id, workflowId, userId: session.user.id, - subdomain, + identifier, title, description: description || '', customizations: mergedCustomizations, @@ -170,7 +170,7 @@ export async function POST(request: NextRequest) { }) // Return successful response with chat URL - // Generate chat URL based on the configured base URL + // Generate chat URL using path-based routing instead of subdomains const baseUrl = env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' let chatUrl: string @@ -180,7 +180,7 @@ export async function POST(request: NextRequest) { if (host.startsWith('www.')) { host = host.substring(4) } - chatUrl = `${url.protocol}//${subdomain}.${host}` + chatUrl = `${url.protocol}//${host}/chat/${identifier}` } catch (error) { logger.warn('Failed to parse baseUrl, falling back to defaults:', { baseUrl, @@ -188,9 +188,9 @@ export async function POST(request: NextRequest) { }) // Fallback based on environment if (isDev) { - chatUrl = `http://${subdomain}.localhost:3000` + chatUrl = `http://localhost:3000/chat/${identifier}` } else { - chatUrl = `https://${subdomain}.sim.ai` + chatUrl = `https://sim.ai/chat/${identifier}` } } diff --git a/apps/sim/app/api/chat/subdomains/validate/route.test.ts b/apps/sim/app/api/chat/subdomains/validate/route.test.ts deleted file mode 100644 index 4a20e1784..000000000 --- a/apps/sim/app/api/chat/subdomains/validate/route.test.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { NextRequest } from 'next/server' -/** - * Tests for subdomain validation API route - * - * @vitest-environment node - */ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -describe('Subdomain Validation API Route', () => { - // Mock database responses - const mockSelect = vi.fn() - const mockFrom = vi.fn() - const mockWhere = vi.fn() - const mockLimit = vi.fn() - - // Mock success and error responses - const mockCreateSuccessResponse = vi.fn() - const mockCreateErrorResponse = vi.fn() - const mockNextResponseJson = vi.fn() - - beforeEach(() => { - vi.resetModules() - - // Set up database query chain - mockSelect.mockReturnValue({ from: mockFrom }) - mockFrom.mockReturnValue({ where: mockWhere }) - mockWhere.mockReturnValue({ limit: mockLimit }) - - // Mock the database - vi.doMock('@sim/db', () => ({ - db: { - select: mockSelect, - }, - })) - - // Mock the schema - vi.doMock('@sim/db/schema', () => ({ - chat: { - subdomain: 'subdomain', - }, - })) - - // Mock the logger - vi.doMock('@/lib/logs/console/logger', () => ({ - createLogger: vi.fn().mockReturnValue({ - info: vi.fn(), - error: vi.fn(), - warn: vi.fn(), - debug: vi.fn(), - }), - })) - - // Mock the response utilities - vi.doMock('@/app/api/workflows/utils', () => ({ - createSuccessResponse: mockCreateSuccessResponse.mockImplementation((data) => { - return new Response(JSON.stringify(data), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }) - }), - createErrorResponse: mockCreateErrorResponse.mockImplementation((message, status = 500) => { - return new Response(JSON.stringify({ error: message }), { - status, - headers: { 'Content-Type': 'application/json' }, - }) - }), - })) - - // Mock the NextResponse json method - mockNextResponseJson.mockImplementation((data, options) => { - return new Response(JSON.stringify(data), { - status: options?.status || 200, - headers: { 'Content-Type': 'application/json' }, - }) - }) - - vi.doMock('next/server', () => ({ - NextRequest: vi.fn(), - NextResponse: { - json: mockNextResponseJson, - }, - })) - }) - - afterEach(() => { - vi.clearAllMocks() - }) - - it('should return 401 when user is not authenticated', async () => { - vi.doMock('@/lib/auth', () => ({ - getSession: vi.fn().mockResolvedValue(null), - })) - - const req = new NextRequest('http://localhost:3000/api/chat/subdomains/validate?subdomain=test') - - const { GET } = await import('@/app/api/chat/subdomains/validate/route') - - const response = await GET(req) - - expect(response.status).toBe(401) - expect(mockCreateErrorResponse).toHaveBeenCalledWith('Unauthorized', 401) - }) - - it('should return 400 when subdomain parameter is missing', async () => { - vi.doMock('@/lib/auth', () => ({ - getSession: vi.fn().mockResolvedValue({ - user: { id: 'user-id' }, - }), - })) - - const req = new NextRequest('http://localhost:3000/api/chat/subdomains/validate') - - const { GET } = await import('@/app/api/chat/subdomains/validate/route') - - const response = await GET(req) - - expect(response.status).toBe(400) - expect(mockCreateErrorResponse).toHaveBeenCalledWith('Missing subdomain parameter', 400) - }) - - it('should return 400 when subdomain format is invalid', async () => { - vi.doMock('@/lib/auth', () => ({ - getSession: vi.fn().mockResolvedValue({ - user: { id: 'user-id' }, - }), - })) - - const req = new NextRequest( - 'http://localhost:3000/api/chat/subdomains/validate?subdomain=Invalid_Subdomain!' - ) - - const { GET } = await import('@/app/api/chat/subdomains/validate/route') - - const response = await GET(req) - const data = await response.json() - - expect(response.status).toBe(400) - expect(data).toHaveProperty('available', false) - expect(data).toHaveProperty('error', 'Invalid subdomain format') - expect(mockNextResponseJson).toHaveBeenCalledWith( - { available: false, error: 'Invalid subdomain format' }, - { status: 400 } - ) - }) - - it('should return available=true when subdomain is valid and not in use', async () => { - vi.doMock('@/lib/auth', () => ({ - getSession: vi.fn().mockResolvedValue({ - user: { id: 'user-id' }, - }), - })) - - mockLimit.mockResolvedValue([]) - - const req = new NextRequest( - 'http://localhost:3000/api/chat/subdomains/validate?subdomain=available-subdomain' - ) - - const { GET } = await import('@/app/api/chat/subdomains/validate/route') - - const response = await GET(req) - - expect(response.status).toBe(200) - expect(mockCreateSuccessResponse).toHaveBeenCalledWith({ - available: true, - subdomain: 'available-subdomain', - }) - }) - - it('should return available=false when subdomain is reserved', async () => { - vi.doMock('@/lib/auth', () => ({ - getSession: vi.fn().mockResolvedValue({ - user: { id: 'user-id' }, - }), - })) - - const req = new NextRequest( - 'http://localhost:3000/api/chat/subdomains/validate?subdomain=telemetry' - ) - - const { GET } = await import('@/app/api/chat/subdomains/validate/route') - - const response = await GET(req) - const data = await response.json() - - expect(response.status).toBe(400) - expect(data).toHaveProperty('available', false) - expect(data).toHaveProperty('error', 'This subdomain is reserved') - expect(mockNextResponseJson).toHaveBeenCalledWith( - { available: false, error: 'This subdomain is reserved' }, - { status: 400 } - ) - }) - - it('should return available=false when subdomain is already in use', async () => { - vi.doMock('@/lib/auth', () => ({ - getSession: vi.fn().mockResolvedValue({ - user: { id: 'user-id' }, - }), - })) - - mockLimit.mockResolvedValue([{ id: 'existing-chat-id' }]) - - const req = new NextRequest( - 'http://localhost:3000/api/chat/subdomains/validate?subdomain=used-subdomain' - ) - - const { GET } = await import('@/app/api/chat/subdomains/validate/route') - - const response = await GET(req) - - expect(response.status).toBe(200) - expect(mockCreateSuccessResponse).toHaveBeenCalledWith({ - available: false, - subdomain: 'used-subdomain', - }) - }) - - it('should return 500 when database query fails', async () => { - vi.doMock('@/lib/auth', () => ({ - getSession: vi.fn().mockResolvedValue({ - user: { id: 'user-id' }, - }), - })) - - mockLimit.mockRejectedValue(new Error('Database error')) - - const req = new NextRequest( - 'http://localhost:3000/api/chat/subdomains/validate?subdomain=error-subdomain' - ) - - const { GET } = await import('@/app/api/chat/subdomains/validate/route') - - const response = await GET(req) - - expect(response.status).toBe(500) - expect(mockCreateErrorResponse).toHaveBeenCalledWith( - 'Failed to check subdomain availability', - 500 - ) - }) -}) diff --git a/apps/sim/app/api/chat/subdomains/validate/route.ts b/apps/sim/app/api/chat/subdomains/validate/route.ts deleted file mode 100644 index 2bafb4fca..000000000 --- a/apps/sim/app/api/chat/subdomains/validate/route.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { db } from '@sim/db' -import { chat } from '@sim/db/schema' -import { eq } from 'drizzle-orm' -import { NextResponse } from 'next/server' -import { getSession } from '@/lib/auth' -import { createLogger } from '@/lib/logs/console/logger' -import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' - -const logger = createLogger('SubdomainValidateAPI') - -export async function GET(request: Request) { - const session = await getSession() - if (!session || !session.user) { - return createErrorResponse('Unauthorized', 401) - } - - try { - const { searchParams } = new URL(request.url) - const subdomain = searchParams.get('subdomain') - - if (!subdomain) { - return createErrorResponse('Missing subdomain parameter', 400) - } - - if (!/^[a-z0-9-]+$/.test(subdomain)) { - return NextResponse.json( - { - available: false, - error: 'Invalid subdomain format', - }, - { status: 400 } - ) - } - - const reservedSubdomains = [ - 'telemetry', - 'docs', - 'api', - 'admin', - 'www', - 'app', - 'auth', - 'blog', - 'help', - 'support', - 'admin', - 'qa', - 'agent', - ] - if (reservedSubdomains.includes(subdomain)) { - return NextResponse.json( - { - available: false, - error: 'This subdomain is reserved', - }, - { status: 400 } - ) - } - - const existingDeployment = await db - .select() - .from(chat) - .where(eq(chat.subdomain, subdomain)) - .limit(1) - - return createSuccessResponse({ - available: existingDeployment.length === 0, - subdomain, - }) - } catch (error) { - logger.error('Error checking subdomain availability:', error) - return createErrorResponse('Failed to check subdomain availability', 500) - } -} diff --git a/apps/sim/app/api/workflows/[id]/chat/status/route.ts b/apps/sim/app/api/workflows/[id]/chat/status/route.ts index d6d98dfbe..6bd8ccbc4 100644 --- a/apps/sim/app/api/workflows/[id]/chat/status/route.ts +++ b/apps/sim/app/api/workflows/[id]/chat/status/route.ts @@ -21,7 +21,7 @@ export async function GET(_request: Request, { params }: { params: Promise<{ id: const deploymentResults = await db .select({ id: chat.id, - subdomain: chat.subdomain, + identifier: chat.identifier, isActive: chat.isActive, }) .from(chat) @@ -33,7 +33,7 @@ export async function GET(_request: Request, { params }: { params: Promise<{ id: deploymentResults.length > 0 ? { id: deploymentResults[0].id, - subdomain: deploymentResults[0].subdomain, + identifier: deploymentResults[0].identifier, } : null diff --git a/apps/sim/app/chat/[subdomain]/chat.css b/apps/sim/app/chat/[identifier]/chat.css similarity index 100% rename from apps/sim/app/chat/[subdomain]/chat.css rename to apps/sim/app/chat/[identifier]/chat.css diff --git a/apps/sim/app/chat/[subdomain]/chat.tsx b/apps/sim/app/chat/[identifier]/chat.tsx similarity index 98% rename from apps/sim/app/chat/[subdomain]/chat.tsx rename to apps/sim/app/chat/[identifier]/chat.tsx index d6f9ce704..85321217c 100644 --- a/apps/sim/app/chat/[subdomain]/chat.tsx +++ b/apps/sim/app/chat/[identifier]/chat.tsx @@ -92,7 +92,7 @@ function throttle any>(func: T, delay: number): T }) as T } -export default function ChatClient({ subdomain }: { subdomain: string }) { +export default function ChatClient({ identifier }: { identifier: string }) { const [messages, setMessages] = useState([]) const [inputValue, setInputValue] = useState('') const [isLoading, setIsLoading] = useState(false) @@ -190,7 +190,7 @@ export default function ChatClient({ subdomain }: { subdomain: string }) { const fetchChatConfig = async () => { try { - const response = await fetch(`/api/chat/${subdomain}`, { + const response = await fetch(`/api/chat/${identifier}`, { credentials: 'same-origin', headers: { 'X-Requested-With': 'XMLHttpRequest', @@ -251,7 +251,7 @@ export default function ChatClient({ subdomain }: { subdomain: string }) { .catch((err) => { logger.error('Failed to fetch GitHub stars:', err) }) - }, [subdomain]) + }, [identifier]) const refreshChat = () => { fetchChatConfig() @@ -309,7 +309,7 @@ export default function ChatClient({ subdomain }: { subdomain: string }) { logger.info('API payload:', payload) - const response = await fetch(`/api/chat/${subdomain}`, { + const response = await fetch(`/api/chat/${identifier}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -433,7 +433,7 @@ export default function ChatClient({ subdomain }: { subdomain: string }) { if (authRequired === 'password') { return ( }) { + const { identifier } = await params + return +} diff --git a/apps/sim/app/chat/[subdomain]/page.tsx b/apps/sim/app/chat/[subdomain]/page.tsx deleted file mode 100644 index 7a005a4dd..000000000 --- a/apps/sim/app/chat/[subdomain]/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import ChatClient from '@/app/chat/[subdomain]/chat' - -export default async function ChatPage({ params }: { params: Promise<{ subdomain: string }> }) { - const { subdomain } = await params - return -} diff --git a/apps/sim/app/chat/components/auth/email/email-auth.tsx b/apps/sim/app/chat/components/auth/email/email-auth.tsx index 4bbf3f96a..5010ea5cc 100644 --- a/apps/sim/app/chat/components/auth/email/email-auth.tsx +++ b/apps/sim/app/chat/components/auth/email/email-auth.tsx @@ -16,7 +16,7 @@ import { soehne } from '@/app/fonts/soehne/soehne' const logger = createLogger('EmailAuth') interface EmailAuthProps { - subdomain: string + identifier: string onAuthSuccess: () => void title?: string primaryColor?: string @@ -39,7 +39,7 @@ const validateEmailField = (emailValue: string): string[] => { } export default function EmailAuth({ - subdomain, + identifier, onAuthSuccess, title = 'chat', primaryColor = 'var(--brand-primary-hover-hex)', @@ -133,7 +133,7 @@ export default function EmailAuth({ setIsSendingOtp(true) try { - const response = await fetch(`/api/chat/${subdomain}/otp`, { + const response = await fetch(`/api/chat/${identifier}/otp`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -170,7 +170,7 @@ export default function EmailAuth({ setIsVerifyingOtp(true) try { - const response = await fetch(`/api/chat/${subdomain}/otp`, { + const response = await fetch(`/api/chat/${identifier}/otp`, { method: 'PUT', headers: { 'Content-Type': 'application/json', @@ -201,7 +201,7 @@ export default function EmailAuth({ setCountdown(30) try { - const response = await fetch(`/api/chat/${subdomain}/otp`, { + const response = await fetch(`/api/chat/${identifier}/otp`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/apps/sim/app/chat/components/auth/password/password-auth.tsx b/apps/sim/app/chat/components/auth/password/password-auth.tsx index cf0c2f8dc..74035b8cc 100644 --- a/apps/sim/app/chat/components/auth/password/password-auth.tsx +++ b/apps/sim/app/chat/components/auth/password/password-auth.tsx @@ -14,14 +14,14 @@ import { soehne } from '@/app/fonts/soehne/soehne' const logger = createLogger('PasswordAuth') interface PasswordAuthProps { - subdomain: string + identifier: string onAuthSuccess: () => void title?: string primaryColor?: string } export default function PasswordAuth({ - subdomain, + identifier, onAuthSuccess, title = 'chat', primaryColor = 'var(--brand-primary-hover-hex)', @@ -94,7 +94,7 @@ export default function PasswordAuth({ try { const payload = { password } - const response = await fetch(`/api/chat/${subdomain}`, { + const response = await fetch(`/api/chat/${identifier}`, { method: 'POST', credentials: 'same-origin', headers: { diff --git a/apps/sim/app/conditional-theme-provider.tsx b/apps/sim/app/conditional-theme-provider.tsx index fedddf40f..47a53d1fe 100644 --- a/apps/sim/app/conditional-theme-provider.tsx +++ b/apps/sim/app/conditional-theme-provider.tsx @@ -17,7 +17,8 @@ export function ConditionalThemeProvider({ children, ...props }: ThemeProviderPr pathname.startsWith('/privacy') || pathname.startsWith('/invite') || pathname.startsWith('/verify') || - pathname.startsWith('/changelog') + pathname.startsWith('/changelog') || + pathname.startsWith('/chat') ? 'light' : undefined diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy.tsx index 64071a562..18ab89782 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy.tsx @@ -24,7 +24,7 @@ import { import { createLogger } from '@/lib/logs/console/logger' import { getEmailDomain } from '@/lib/urls/utils' import { AuthSelector } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/auth-selector' -import { SubdomainInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/subdomain-input' +import { IdentifierInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/identifier-input' import { SuccessView } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/success-view' import { useChatDeployment } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-deployment' import { useChatForm } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-form' @@ -50,7 +50,7 @@ interface ChatDeployProps { interface ExistingChat { id: string - subdomain: string + identifier: string title: string description: string authType: 'public' | 'password' | 'email' @@ -96,9 +96,9 @@ export function ChatDeploy({ const { formData, errors, updateField, setError, validateForm, setFormData } = useChatForm() const { deployedUrl, deployChat } = useChatDeployment() const formRef = useRef(null) - const [isSubdomainValid, setIsSubdomainValid] = useState(false) + const [isIdentifierValid, setIsIdentifierValid] = useState(false) const isFormValid = - isSubdomainValid && + isIdentifierValid && Boolean(formData.title.trim()) && formData.selectedOutputBlocks.length > 0 && (formData.authType !== 'password' || @@ -132,7 +132,7 @@ export function ChatDeploy({ setExistingChat(chatDetail) setFormData({ - subdomain: chatDetail.subdomain || '', + identifier: chatDetail.identifier || '', title: chatDetail.title || '', description: chatDetail.description || '', authType: chatDetail.authType || 'public', @@ -185,8 +185,8 @@ export function ChatDeploy({ return } - if (!isSubdomainValid && formData.subdomain !== existingChat?.subdomain) { - setError('subdomain', 'Please wait for subdomain validation to complete') + if (!isIdentifierValid && formData.identifier !== existingChat?.identifier) { + setError('identifier', 'Please wait for identifier validation to complete') setChatSubmitting(false) return } @@ -201,8 +201,8 @@ export function ChatDeploy({ // This ensures existingChat is available when switching back to edit mode await fetchExistingChat() } catch (error: any) { - if (error.message?.includes('subdomain')) { - setError('subdomain', error.message) + if (error.message?.includes('identifier')) { + setError('identifier', error.message) } else { setError('general', error.message) } @@ -267,7 +267,7 @@ export function ChatDeploy({ This will permanently delete your chat deployment at{' '} - {existingChat?.subdomain}.{getEmailDomain()} + {getEmailDomain()}/chat/{existingChat?.identifier} . @@ -316,12 +316,12 @@ export function ChatDeploy({ )}
- updateField('subdomain', value)} - originalSubdomain={existingChat?.subdomain || undefined} + updateField('identifier', value)} + originalIdentifier={existingChat?.identifier || undefined} disabled={chatSubmitting} - onValidationChange={setIsSubdomainValid} + onValidationChange={setIsIdentifierValid} isEditingExisting={!!existingChat} />
@@ -445,7 +445,7 @@ export function ChatDeploy({ This will permanently delete your chat deployment at{' '} - {existingChat?.subdomain}.{getEmailDomain()} + {getEmailDomain()}/chat/{existingChat?.identifier} . diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/subdomain-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/identifier-input.tsx similarity index 69% rename from apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/subdomain-input.tsx rename to apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/identifier-input.tsx index 693f2dbe2..cfbeb4d41 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/subdomain-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/identifier-input.tsx @@ -2,33 +2,33 @@ import { useEffect } from 'react' import { Input, Label } from '@/components/ui' import { getEmailDomain } from '@/lib/urls/utils' import { cn } from '@/lib/utils' -import { useSubdomainValidation } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-subdomain-validation' +import { useIdentifierValidation } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-identifier-validation' -interface SubdomainInputProps { +interface IdentifierInputProps { value: string onChange: (value: string) => void - originalSubdomain?: string + originalIdentifier?: string disabled?: boolean onValidationChange?: (isValid: boolean) => void isEditingExisting?: boolean } -const getDomainSuffix = (() => { - const suffix = `.${getEmailDomain()}` - return () => suffix +const getDomainPrefix = (() => { + const prefix = `${getEmailDomain()}/chat/` + return () => prefix })() -export function SubdomainInput({ +export function IdentifierInput({ value, onChange, - originalSubdomain, + originalIdentifier, disabled = false, onValidationChange, isEditingExisting = false, -}: SubdomainInputProps) { - const { isChecking, error, isValid } = useSubdomainValidation( +}: IdentifierInputProps) { + const { isChecking, error, isValid } = useIdentifierValidation( value, - originalSubdomain, + originalIdentifier, isEditingExisting ) @@ -44,20 +44,23 @@ export function SubdomainInput({ return (
-
{error &&

{error}

}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/success-view.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/success-view.tsx index b9d25a3b7..b7b9c7706 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/success-view.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/components/success-view.tsx @@ -3,7 +3,7 @@ import { getBaseDomain, getEmailDomain } from '@/lib/urls/utils' interface ExistingChat { id: string - subdomain: string + identifier: string title: string description: string authType: 'public' | 'password' | 'email' @@ -27,21 +27,20 @@ export function SuccessView({ deployedUrl, existingChat, onDelete, onUpdate }: S const hostname = url.hostname const isDevelopmentUrl = hostname.includes('localhost') - let domainSuffix + // Extract subdomain from path-based URL format (e.g., sim.ai/chat/subdomain) + const pathParts = url.pathname.split('/') + const subdomainPart = pathParts[2] || '' // /chat/subdomain + + let domainPrefix if (isDevelopmentUrl) { const baseDomain = getBaseDomain() const baseHost = baseDomain.split(':')[0] const port = url.port || (baseDomain.includes(':') ? baseDomain.split(':')[1] : '3000') - domainSuffix = `.${baseHost}:${port}` + domainPrefix = `${baseHost}:${port}/chat/` } else { - domainSuffix = `.${getEmailDomain()}` + domainPrefix = `${getEmailDomain()}/chat/` } - const baseDomainForSplit = getEmailDomain() - const subdomainPart = isDevelopmentUrl - ? hostname.split('.')[0] - : hostname.split(`.${baseDomainForSplit}`)[0] - return (
@@ -49,17 +48,17 @@ export function SuccessView({ deployedUrl, existingChat, onDelete, onUpdate }: S Chat {existingChat ? 'Update' : 'Deployment'} Successful
+
+ {domainPrefix} +
{subdomainPart} -
- {domainSuffix} -

Your chat is now live at{' '} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-deployment.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-deployment.ts index 5dd12da3a..16bd6e9c5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-deployment.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-deployment.ts @@ -14,10 +14,10 @@ export interface ChatDeploymentState { const chatSchema = z.object({ workflowId: z.string().min(1, 'Workflow ID is required'), - subdomain: z + identifier: z .string() - .min(1, 'Subdomain is required') - .regex(/^[a-z0-9-]+$/, 'Subdomain can only contain lowercase letters, numbers, and hyphens'), + .min(1, 'Identifier is required') + .regex(/^[a-z0-9-]+$/, 'Identifier can only contain lowercase letters, numbers, and hyphens'), title: z.string().min(1, 'Title is required'), description: z.string().optional(), customizations: z.object({ @@ -75,7 +75,7 @@ export function useChatDeployment() { // Create request payload const payload = { workflowId, - subdomain: formData.subdomain.trim(), + identifier: formData.identifier.trim(), title: formData.title.trim(), description: formData.description.trim(), customizations: { @@ -107,9 +107,9 @@ export function useChatDeployment() { const result = await response.json() if (!response.ok) { - // Handle subdomain conflict specifically - if (result.error === 'Subdomain already in use') { - throw new Error('This subdomain is already in use') + // Handle identifier conflict specifically + if (result.error === 'Identifier already in use') { + throw new Error('This identifier is already in use') } throw new Error(result.error || `Failed to ${existingChatId ? 'update' : 'deploy'} chat`) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-form.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-form.ts index 417437744..753662453 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-form.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-chat-form.ts @@ -3,7 +3,7 @@ import { useCallback, useState } from 'react' export type AuthType = 'public' | 'password' | 'email' export interface ChatFormData { - subdomain: string + identifier: string title: string description: string authType: AuthType @@ -14,7 +14,7 @@ export interface ChatFormData { } export interface ChatFormErrors { - subdomain?: string + identifier?: string title?: string password?: string emails?: string @@ -23,7 +23,7 @@ export interface ChatFormErrors { } const initialFormData: ChatFormData = { - subdomain: '', + identifier: '', title: '', description: '', authType: 'public', @@ -67,10 +67,10 @@ export function useChatForm(initialData?: Partial) { const validateForm = useCallback((): boolean => { const newErrors: ChatFormErrors = {} - if (!formData.subdomain.trim()) { - newErrors.subdomain = 'Subdomain is required' - } else if (!/^[a-z0-9-]+$/.test(formData.subdomain)) { - newErrors.subdomain = 'Subdomain can only contain lowercase letters, numbers, and hyphens' + if (!formData.identifier.trim()) { + newErrors.identifier = 'Identifier is required' + } else if (!/^[a-z0-9-]+$/.test(formData.identifier)) { + newErrors.identifier = 'Identifier can only contain lowercase letters, numbers, and hyphens' } if (!formData.title.trim()) { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-identifier-validation.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-identifier-validation.ts new file mode 100644 index 000000000..257d9a6f4 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-identifier-validation.ts @@ -0,0 +1,46 @@ +import { useEffect, useState } from 'react' + +export function useIdentifierValidation( + identifier: string, + originalIdentifier?: string, + isEditingExisting?: boolean +) { + const [error, setError] = useState(null) + const [isValid, setIsValid] = useState(false) + + useEffect(() => { + // Reset states immediately when identifier changes + setError(null) + setIsValid(false) + + // Skip validation if empty + if (!identifier.trim()) { + return + } + + // Skip validation if same as original (existing deployment) + if (originalIdentifier && identifier === originalIdentifier) { + setIsValid(true) + return + } + + // If we're editing an existing deployment but originalIdentifier isn't available yet, + // assume it's valid and wait for the data to load + if (isEditingExisting && !originalIdentifier) { + setIsValid(true) + return + } + + // Validate format - only client-side validation needed now + if (!/^[a-z0-9-]+$/.test(identifier)) { + setError('Identifier can only contain lowercase letters, numbers, and hyphens') + return + } + + // If format is valid, mark as valid + setIsValid(true) + }, [identifier, originalIdentifier, isEditingExisting]) + + // No longer need isChecking since we're not doing async validation + return { isChecking: false, error, isValid } +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-subdomain-validation.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-subdomain-validation.ts deleted file mode 100644 index 931072f83..000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-subdomain-validation.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { useEffect, useRef, useState } from 'react' - -export function useSubdomainValidation( - subdomain: string, - originalSubdomain?: string, - isEditingExisting?: boolean -) { - const [isChecking, setIsChecking] = useState(false) - const [error, setError] = useState(null) - const [isValid, setIsValid] = useState(false) - - const timeoutRef = useRef(null) - - useEffect(() => { - // Clear previous timeout - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - } - - // Reset states immediately when subdomain changes - setError(null) - setIsValid(false) - setIsChecking(false) - - // Skip validation if empty - if (!subdomain.trim()) { - return - } - - // Skip validation if same as original (existing deployment) - if (originalSubdomain && subdomain === originalSubdomain) { - setIsValid(true) - return - } - - // If we're editing an existing deployment but originalSubdomain isn't available yet, - // assume it's valid and wait for the data to load - if (isEditingExisting && !originalSubdomain) { - setIsValid(true) - return - } - - // Validate format first - if (!/^[a-z0-9-]+$/.test(subdomain)) { - setError('Subdomain can only contain lowercase letters, numbers, and hyphens') - return - } - - // Debounce API call - setIsChecking(true) - timeoutRef.current = setTimeout(async () => { - try { - const response = await fetch( - `/api/chat/subdomains/validate?subdomain=${encodeURIComponent(subdomain)}` - ) - const data = await response.json() - - if (!response.ok || !data.available) { - setError(data.error || 'This subdomain is already in use') - setIsValid(false) - } else { - setError(null) - setIsValid(true) - } - } catch (error) { - setError('Error checking subdomain availability') - setIsValid(false) - } finally { - setIsChecking(false) - } - }, 500) - - // Cleanup function - return () => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current) - } - } - }, [subdomain, originalSubdomain, isEditingExisting]) - - return { isChecking, error, isValid } -} diff --git a/apps/sim/middleware.ts b/apps/sim/middleware.ts index a211e0912..eb9ddb489 100644 --- a/apps/sim/middleware.ts +++ b/apps/sim/middleware.ts @@ -13,143 +13,178 @@ const SUSPICIOUS_UA_PATTERNS = [ /<\s*script/i, // Potential XSS payloads /^\(\)\s*{/, // Command execution attempt /\b(sqlmap|nikto|gobuster|dirb|nmap)\b/i, // Known scanning tools -] +] as const const BASE_DOMAIN = getBaseDomain() -export async function middleware(request: NextRequest) { - // Check for active session - const sessionCookie = getSessionCookie(request) - const hasActiveSession = !!sessionCookie +const OPERATIONAL_SUBDOMAINS = new Set([ + 'telemetry', + 'docs', + 'api', + 'admin', + 'www', + 'app', + 'auth', + 'blog', + 'help', + 'support', + 'qa', + 'agent', + 'staging', +]) +interface SubdomainAnalysis { + isCustomDomain: boolean + subdomain: string | null +} + +/** + * Analyzes the hostname to determine if it's a custom subdomain and extracts the subdomain part + */ +function analyzeSubdomain(hostname: string): SubdomainAnalysis { + // Standard check for non-base domains + if (hostname === BASE_DOMAIN || hostname.startsWith('www.')) { + return { isCustomDomain: false, subdomain: null } + } + + // Extract root domain from BASE_DOMAIN (e.g., "sim.ai" from "staging.sim.ai") + const baseParts = BASE_DOMAIN.split('.') + const rootDomain = isDev + ? 'localhost' + : baseParts.length >= 2 + ? baseParts + .slice(-2) + .join('.') // Last 2 parts: ["sim", "ai"] -> "sim.ai" + : BASE_DOMAIN + + // Check if hostname is under the same root domain + if (!hostname.includes(rootDomain)) { + return { isCustomDomain: false, subdomain: null } + } + + // For nested subdomain environments: handle cases like myapp.staging.example.com + const hostParts = hostname.split('.') + const basePartCount = BASE_DOMAIN.split('.').length + + // If hostname has more parts than base domain, it's a nested subdomain + const isCustomDomain = hostParts.length > basePartCount || hostname !== BASE_DOMAIN + + return { + isCustomDomain, + subdomain: isCustomDomain ? hostname.split('.')[0] : null, + } +} + +/** + * Handles chat subdomain redirects for backward compatibility + */ +function handleChatSubdomainRedirect(request: NextRequest, subdomain: string): NextResponse | null { const url = request.nextUrl - const hostname = request.headers.get('host') || '' - // Extract subdomain - handle nested subdomains for any domain - const isCustomDomain = (() => { - // Standard check for non-base domains - if (hostname === BASE_DOMAIN || hostname.startsWith('www.')) { - return false - } - - // Extract root domain from BASE_DOMAIN (e.g., "sim.ai" from "staging.sim.ai") - const baseParts = BASE_DOMAIN.split('.') - const rootDomain = isDev - ? 'localhost' - : baseParts.length >= 2 - ? baseParts - .slice(-2) - .join('.') // Last 2 parts: ["simstudio", "ai"] -> "sim.ai" - : BASE_DOMAIN - - // Check if hostname is under the same root domain - if (!hostname.includes(rootDomain)) { - return false - } - - // For nested subdomain environments: handle cases like myapp.staging.example.com - const hostParts = hostname.split('.') - const basePartCount = BASE_DOMAIN.split('.').length - - // If hostname has more parts than base domain, it's a nested subdomain - if (hostParts.length > basePartCount) { - return true - } - - // For single-level subdomains: regular subdomain logic - return hostname !== BASE_DOMAIN - })() - - const subdomain = isCustomDomain ? hostname.split('.')[0] : null - - // Handle chat subdomains - if (subdomain && isCustomDomain) { - if (url.pathname.startsWith('/api/chat/') || url.pathname.startsWith('/api/proxy/')) { - return NextResponse.next() - } - - // Rewrite to the chat page but preserve the URL in browser - return NextResponse.rewrite(new URL(`/chat/${subdomain}${url.pathname}`, request.url)) + // Skip redirect for API endpoints + if (url.pathname.startsWith('/api/chat/') || url.pathname.startsWith('/api/proxy/')) { + return null } - // Handle root path redirects based on session status and hosting type - // Only apply redirects to the main domain, not subdomains - if (!isCustomDomain && (url.pathname === '/' || url.pathname === '/homepage')) { - if (!isHosted) { - // Self-hosted: Always redirect based on session - if (hasActiveSession) { - return NextResponse.redirect(new URL('/workspace', request.url)) - } - return NextResponse.redirect(new URL('/login', request.url)) - } - // Hosted: Allow access to /homepage route even for authenticated users - if (url.pathname === '/homepage') { - return NextResponse.rewrite(new URL('/', request.url)) - } + // Build redirect URL + const baseDomain = isDev ? `localhost:${url.port || '3000'}` : getBaseDomain() + const protocol = isDev ? 'http' : 'https' + const redirectUrl = `${protocol}://${baseDomain}/chat/${subdomain}${url.pathname}${url.search}` - // For root path, redirect authenticated users to workspace - if (hasActiveSession && url.pathname === '/') { - return NextResponse.redirect(new URL('/workspace', request.url)) - } + logger.info(`Redirecting subdomain request from ${request.headers.get('host')} to ${redirectUrl}`) + return NextResponse.redirect(redirectUrl, 301) // Permanent redirect +} + +/** + * Handles authentication-based redirects for root paths + */ +function handleRootPathRedirects( + request: NextRequest, + hasActiveSession: boolean +): NextResponse | null { + const url = request.nextUrl + + if (url.pathname !== '/' && url.pathname !== '/homepage') { + return null } - // Handle login page - redirect authenticated users to workspace - if (url.pathname === '/login' || url.pathname === '/signup') { + if (!isHosted) { + // Self-hosted: Always redirect based on session if (hasActiveSession) { return NextResponse.redirect(new URL('/workspace', request.url)) } - return NextResponse.next() + return NextResponse.redirect(new URL('/login', request.url)) } - // Handle protected routes that require authentication - if (url.pathname.startsWith('/workspace')) { - if (!hasActiveSession) { - return NextResponse.redirect(new URL('/login', request.url)) + // Hosted: Allow access to /homepage route even for authenticated users + if (url.pathname === '/homepage') { + return NextResponse.rewrite(new URL('/', request.url)) + } + + // For root path, redirect authenticated users to workspace + if (hasActiveSession && url.pathname === '/') { + return NextResponse.redirect(new URL('/workspace', request.url)) + } + + return null +} + +/** + * Handles invitation link redirects for unauthenticated users + */ +function handleInvitationRedirects( + request: NextRequest, + hasActiveSession: boolean +): NextResponse | null { + if (!request.nextUrl.pathname.startsWith('/invite/')) { + return null + } + + if ( + !hasActiveSession && + !request.nextUrl.pathname.endsWith('/login') && + !request.nextUrl.pathname.endsWith('/signup') && + !request.nextUrl.search.includes('callbackUrl') + ) { + const token = request.nextUrl.searchParams.get('token') + const inviteId = request.nextUrl.pathname.split('/').pop() + const callbackParam = encodeURIComponent(`/invite/${inviteId}${token ? `?token=${token}` : ''}`) + return NextResponse.redirect( + new URL(`/login?callbackUrl=${callbackParam}&invite_flow=true`, request.url) + ) + } + return NextResponse.next() +} + +/** + * Handles workspace invitation API endpoint access + */ +function handleWorkspaceInvitationAPI( + request: NextRequest, + hasActiveSession: boolean +): NextResponse | null { + if (!request.nextUrl.pathname.startsWith('/api/workspaces/invitations')) { + return null + } + + if (request.nextUrl.pathname.includes('/accept') && !hasActiveSession) { + const token = request.nextUrl.searchParams.get('token') + if (token) { + return NextResponse.redirect(new URL(`/invite/${token}?token=${token}`, request.url)) } - - // Email verification is enforced by Better Auth (server-side). No cookie gating here. - return NextResponse.next() - } - - // Allow access to invitation links - if (request.nextUrl.pathname.startsWith('/invite/')) { - if ( - !hasActiveSession && - !request.nextUrl.pathname.endsWith('/login') && - !request.nextUrl.pathname.endsWith('/signup') && - !request.nextUrl.search.includes('callbackUrl') - ) { - const token = request.nextUrl.searchParams.get('token') - const inviteId = request.nextUrl.pathname.split('/').pop() - const callbackParam = encodeURIComponent( - `/invite/${inviteId}${token ? `?token=${token}` : ''}` - ) - return NextResponse.redirect( - new URL(`/login?callbackUrl=${callbackParam}&invite_flow=true`, request.url) - ) - } - return NextResponse.next() - } - - // Allow access to workspace invitation API endpoint - if (request.nextUrl.pathname.startsWith('/api/workspaces/invitations')) { - if (request.nextUrl.pathname.includes('/accept') && !hasActiveSession) { - const token = request.nextUrl.searchParams.get('token') - if (token) { - return NextResponse.redirect(new URL(`/invite/${token}?token=${token}`, request.url)) - } - } - return NextResponse.next() } + return NextResponse.next() +} +/** + * Handles security filtering for suspicious user agents + */ +function handleSecurityFiltering(request: NextRequest): NextResponse | null { const userAgent = request.headers.get('user-agent') || '' - - // Check if this is a webhook endpoint that should be exempt from User-Agent validation - const isWebhookEndpoint = url.pathname.startsWith('/api/webhooks/trigger/') - + const isWebhookEndpoint = request.nextUrl.pathname.startsWith('/api/webhooks/trigger/') const isSuspicious = SUSPICIOUS_UA_PATTERNS.some((pattern) => pattern.test(userAgent)) - // Block suspicious requests, but exempt webhook endpoints from User-Agent validation only + // Block suspicious requests, but exempt webhook endpoints from User-Agent validation if (isSuspicious && !isWebhookEndpoint) { logger.warn('Blocked suspicious request', { userAgent, @@ -158,6 +193,7 @@ export async function middleware(request: NextRequest) { method: request.method, pattern: SUSPICIOUS_UA_PATTERNS.find((pattern) => pattern.test(userAgent))?.toString(), }) + return new NextResponse(null, { status: 403, statusText: 'Forbidden', @@ -173,6 +209,59 @@ export async function middleware(request: NextRequest) { }) } + return null +} + +export async function middleware(request: NextRequest) { + const url = request.nextUrl + const hostname = request.headers.get('host') || '' + + const sessionCookie = getSessionCookie(request) + const hasActiveSession = !!sessionCookie + + const { isCustomDomain, subdomain } = analyzeSubdomain(hostname) + + // Handle chat subdomains - redirect to path-based URLs for backward compatibility + if (subdomain && isCustomDomain && !OPERATIONAL_SUBDOMAINS.has(subdomain)) { + const redirect = handleChatSubdomainRedirect(request, subdomain) + if (redirect) return redirect + } + + // Handle root path redirects based on session status and hosting type + if (!isCustomDomain) { + const redirect = handleRootPathRedirects(request, hasActiveSession) + if (redirect) return redirect + } + + // Handle login/signup pages - redirect authenticated users to workspace + if (url.pathname === '/login' || url.pathname === '/signup') { + if (hasActiveSession) { + return NextResponse.redirect(new URL('/workspace', request.url)) + } + return NextResponse.next() + } + + // Handle protected routes that require authentication + if (url.pathname.startsWith('/workspace')) { + if (!hasActiveSession) { + return NextResponse.redirect(new URL('/login', request.url)) + } + // Email verification is enforced by Better Auth (server-side). No cookie gating here. + return NextResponse.next() + } + + // Handle invitation links + const invitationRedirect = handleInvitationRedirects(request, hasActiveSession) + if (invitationRedirect) return invitationRedirect + + // Handle workspace invitation API endpoints + const workspaceInvitationRedirect = handleWorkspaceInvitationAPI(request, hasActiveSession) + if (workspaceInvitationRedirect) return workspaceInvitationRedirect + + // Handle security filtering for suspicious requests + const securityBlock = handleSecurityFiltering(request) + if (securityBlock) return securityBlock + const response = NextResponse.next() response.headers.set('Vary', 'User-Agent') diff --git a/packages/db/migrations/0094_perpetual_the_watchers.sql b/packages/db/migrations/0094_perpetual_the_watchers.sql new file mode 100644 index 000000000..bd69c5570 --- /dev/null +++ b/packages/db/migrations/0094_perpetual_the_watchers.sql @@ -0,0 +1,3 @@ +ALTER TABLE "chat" RENAME COLUMN "subdomain" TO "identifier";--> statement-breakpoint +DROP INDEX "subdomain_idx";--> statement-breakpoint +CREATE UNIQUE INDEX "identifier_idx" ON "chat" USING btree ("identifier"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0094_snapshot.json b/packages/db/migrations/meta/0094_snapshot.json new file mode 100644 index 000000000..161fcc808 --- /dev/null +++ b/packages/db/migrations/meta/0094_snapshot.json @@ -0,0 +1,6818 @@ +{ + "id": "c8cce6b4-fe75-4302-88c3-09b6ecaf387a", + "prevId": "27714900-d193-45b2-b063-a4ff70522467", + "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 + }, + "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 + }, + "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.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'" + }, + "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'" + }, + "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 + }, + "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_state": { + "name": "deployed_state", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned_api_key_id": { + "name": "pinned_api_key_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "collaborators": { + "name": "collaborators", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "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": "'{}'" + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "marketplace_data": { + "name": "marketplace_data", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "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" + }, + "workflow_pinned_api_key_id_api_key_id_fk": { + "name": "workflow_pinned_api_key_id_api_key_id_fk", + "tableFrom": "workflow", + "tableTo": "api_key", + "columnsFrom": ["pinned_api_key_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 + }, + "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_source_block_idx": { + "name": "workflow_edges_source_block_idx", + "columns": [ + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_target_block_idx": { + "name": "workflow_edges_target_block_idx", + "columns": [ + { + "expression": "target_block_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_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 + }, + "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" + } + }, + "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_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 e1aba478e..4b6a0d7c8 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -652,6 +652,13 @@ "when": 1758751182653, "tag": "0093_medical_sentinel", "breakpoints": true + }, + { + "idx": 94, + "version": "7", + "when": 1758998206326, + "tag": "0094_perpetual_the_watchers", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 34bf07520..e3701252c 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -633,7 +633,7 @@ export const chat = pgTable( userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), - subdomain: text('subdomain').notNull(), + identifier: text('identifier').notNull(), title: text('title').notNull(), description: text('description'), isActive: boolean('is_active').notNull().default(true), @@ -652,8 +652,8 @@ export const chat = pgTable( }, (table) => { return { - // Ensure subdomains are unique - subdomainIdx: uniqueIndex('subdomain_idx').on(table.subdomain), + // Ensure identifiers are unique + identifierIdx: uniqueIndex('identifier_idx').on(table.identifier), } } )