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 6b000f8d455..8ba425972d5 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 85% rename from apps/sim/app/api/chat/[subdomain]/route.test.ts rename to apps/sim/app/api/chat/[identifier]/route.test.ts index bce1e005f96..47c0bff36af 100644 --- a/apps/sim/app/api/chat/[subdomain]/route.test.ts +++ b/apps/sim/app/api/chat/[identifier]/route.test.ts @@ -1,12 +1,12 @@ /** - * Tests for chat subdomain API route + * Tests for chat identifier API route * * @vitest-environment node */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { createMockRequest } from '@/app/api/__test-utils__/utils' -describe('Chat Subdomain API Route', () => { +describe('Chat Identifier API Route', () => { const createMockStream = () => { return new ReadableStream({ start(controller) { @@ -134,11 +134,11 @@ describe('Chat Subdomain API Route', () => { }) describe('GET endpoint', () => { - it('should return chat info for a valid subdomain', async () => { + it('should return chat info for a valid identifier', 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 }) @@ -152,7 +152,7 @@ describe('Chat Subdomain API Route', () => { expect(data.customizations).toHaveProperty('welcomeMessage', 'Welcome to the test chat') }) - it('should return 404 for non-existent subdomain', async () => { + it('should return 404 for non-existent identifier', async () => { vi.doMock('@sim/db', () => { const mockLimit = vi.fn().mockReturnValue([]) const mockWhere = vi.fn().mockReturnValue({ limit: mockLimit }) @@ -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 d667b2e85d1..9551e991319 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/manage/[id]/route.test.ts similarity index 85% rename from apps/sim/app/api/chat/edit/[id]/route.test.ts rename to apps/sim/app/api/chat/manage/[id]/route.test.ts index 445ed4abfda..84e297507d9 100644 --- a/apps/sim/app/api/chat/edit/[id]/route.test.ts +++ b/apps/sim/app/api/chat/manage/[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', () => ({ @@ -93,8 +93,8 @@ describe('Chat Edit API Route', () => { getSession: vi.fn().mockResolvedValue(null), })) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123') - const { GET } = await import('@/app/api/chat/edit/[id]/route') + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123') + const { GET } = await import('@/app/api/chat/manage/[id]/route') const response = await GET(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(401) @@ -110,8 +110,8 @@ describe('Chat Edit API Route', () => { mockCheckChatAccess.mockResolvedValue({ hasAccess: false }) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123') - const { GET } = await import('@/app/api/chat/edit/[id]/route') + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123') + const { GET } = await import('@/app/api/chat/manage/[id]/route') const response = await GET(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(404) @@ -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', @@ -137,18 +137,18 @@ describe('Chat Edit API Route', () => { mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat }) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123') - const { GET } = await import('@/app/api/chat/edit/[id]/route') + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123') + const { GET } = await import('@/app/api/chat/manage/[id]/route') const response = await GET(req, { params: Promise.resolve({ id: 'chat-123' }) }) 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, }) }) @@ -160,11 +160,11 @@ describe('Chat Edit API Route', () => { getSession: vi.fn().mockResolvedValue(null), })) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'PATCH', body: JSON.stringify({ title: 'Updated Chat' }), }) - const { PATCH } = await import('@/app/api/chat/edit/[id]/route') + const { PATCH } = await import('@/app/api/chat/manage/[id]/route') const response = await PATCH(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(401) @@ -180,11 +180,11 @@ describe('Chat Edit API Route', () => { mockCheckChatAccess.mockResolvedValue({ hasAccess: false }) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'PATCH', body: JSON.stringify({ title: 'Updated Chat' }), }) - const { PATCH } = await import('@/app/api/chat/edit/[id]/route') + const { PATCH } = await import('@/app/api/chat/manage/[id]/route') const response = await PATCH(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(404) @@ -201,30 +201,30 @@ describe('Chat Edit API Route', () => { const mockChat = { id: 'chat-123', - subdomain: 'test-chat', + identifier: 'test-chat', title: 'Test Chat', authType: 'public', } mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat }) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'PATCH', body: JSON.stringify({ title: 'Updated Chat', description: 'Updated description' }), }) - const { PATCH } = await import('@/app/api/chat/edit/[id]/route') + const { PATCH } = await import('@/app/api/chat/manage/[id]/route') const response = await PATCH(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(200) 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', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/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 { PATCH } = await import('@/app/api/chat/manage/[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, @@ -269,11 +269,11 @@ describe('Chat Edit API Route', () => { mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat }) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'PATCH', body: JSON.stringify({ authType: 'password' }), // No password provided }) - const { PATCH } = await import('@/app/api/chat/edit/[id]/route') + const { PATCH } = await import('@/app/api/chat/manage/[id]/route') const response = await PATCH(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(400) @@ -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', } @@ -300,11 +300,11 @@ describe('Chat Edit API Route', () => { // User doesn't own chat but has workspace admin access mockCheckChatAccess.mockResolvedValue({ hasAccess: true, chat: mockChat }) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'PATCH', body: JSON.stringify({ title: 'Admin Updated Chat' }), }) - const { PATCH } = await import('@/app/api/chat/edit/[id]/route') + const { PATCH } = await import('@/app/api/chat/manage/[id]/route') const response = await PATCH(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(200) @@ -318,10 +318,10 @@ describe('Chat Edit API Route', () => { getSession: vi.fn().mockResolvedValue(null), })) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'DELETE', }) - const { DELETE } = await import('@/app/api/chat/edit/[id]/route') + const { DELETE } = await import('@/app/api/chat/manage/[id]/route') const response = await DELETE(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(401) @@ -337,10 +337,10 @@ describe('Chat Edit API Route', () => { mockCheckChatAccess.mockResolvedValue({ hasAccess: false }) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'DELETE', }) - const { DELETE } = await import('@/app/api/chat/edit/[id]/route') + const { DELETE } = await import('@/app/api/chat/manage/[id]/route') const response = await DELETE(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(404) @@ -358,10 +358,10 @@ describe('Chat Edit API Route', () => { mockCheckChatAccess.mockResolvedValue({ hasAccess: true }) mockWhere.mockResolvedValue(undefined) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'DELETE', }) - const { DELETE } = await import('@/app/api/chat/edit/[id]/route') + const { DELETE } = await import('@/app/api/chat/manage/[id]/route') const response = await DELETE(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(200) @@ -382,10 +382,10 @@ describe('Chat Edit API Route', () => { mockCheckChatAccess.mockResolvedValue({ hasAccess: true }) mockWhere.mockResolvedValue(undefined) - const req = new NextRequest('http://localhost:3000/api/chat/edit/chat-123', { + const req = new NextRequest('http://localhost:3000/api/chat/manage/chat-123', { method: 'DELETE', }) - const { DELETE } = await import('@/app/api/chat/edit/[id]/route') + const { DELETE } = await import('@/app/api/chat/manage/[id]/route') const response = await DELETE(req, { params: Promise.resolve({ id: 'chat-123' }) }) expect(response.status).toBe(200) diff --git a/apps/sim/app/api/chat/edit/[id]/route.ts b/apps/sim/app/api/chat/manage/[id]/route.ts similarity index 77% rename from apps/sim/app/api/chat/edit/[id]/route.ts rename to apps/sim/app/api/chat/manage/[id]/route.ts index 2a56fe9559b..65d0583ac0e 100644 --- a/apps/sim/app/api/chat/edit/[id]/route.ts +++ b/apps/sim/app/api/chat/manage/[id]/route.ts @@ -15,13 +15,12 @@ export const dynamic = 'force-dynamic' 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(), @@ -59,19 +58,17 @@ export async function GET(_request: NextRequest, { params }: { params: Promise<{ return createErrorResponse('Unauthorized', 401) } - // Check if user has access to view this chat const { hasAccess, chat: chatRecord } = await checkChatAccess(chatId, session.user.id) if (!hasAccess || !chatRecord) { return createErrorResponse('Chat not found or access denied', 404) } - // Create a new result object without the password const { password, ...safeData } = chatRecord const baseDomain = getEmailDomain() const protocol = isDev ? 'http' : 'https' - const chatUrl = `${protocol}://${chatRecord.subdomain}.${baseDomain}` + const chatUrl = `${protocol}://${baseDomain}/chat/${chatRecord.identifier}` const result = { ...safeData, @@ -105,19 +102,17 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< try { const validatedData = chatUpdateSchema.parse(body) - // Check if user has access to edit this chat const { hasAccess, chat: existingChatRecord } = await checkChatAccess(chatId, session.user.id) if (!hasAccess || !existingChatRecord) { return createErrorResponse('Chat not found or access denied', 404) } - const existingChat = [existingChatRecord] // Keep array format for compatibility + const existingChat = [existingChatRecord] - // Extract validated data const { workflowId, - subdomain, + identifier, title, description, customizations, @@ -127,77 +122,62 @@ 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 + 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) } } - // Handle password update let encryptedPassword - // Only encrypt and update password if one is provided if (password) { const { encrypted } = await encryptSecret(password) encryptedPassword = encrypted logger.info('Password provided, will be updated') } else if (authType === 'password' && !password) { - // If switching to password auth but no password provided, - // check if there's an existing password if (existingChat[0].authType !== 'password' || !existingChat[0].password) { - // If there's no existing password to reuse, return an error return createErrorResponse('Password is required when using password protection', 400) } logger.info('Keeping existing password') } - // Prepare update data const updateData: any = { updatedAt: new Date(), } - // 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 - // Handle auth type update if (authType) { updateData.authType = authType - // Reset auth-specific fields when changing auth types if (authType === 'public') { updateData.password = null updateData.allowedEmails = [] } else if (authType === 'password') { updateData.allowedEmails = [] - // Password handled separately } else if (authType === 'email') { updateData.password = null - // Emails handled separately } } - // Always update password if provided (not just when changing auth type) if (encryptedPassword) { updateData.password = encryptedPassword } - // Always update allowed emails if provided if (allowedEmails) { updateData.allowedEmails = allowedEmails } - // Handle output fields if (outputConfigs) { updateData.outputConfigs = outputConfigs } @@ -210,14 +190,13 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise< outputConfigsCount: updateData.outputConfigs ? updateData.outputConfigs.length : undefined, }) - // 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`) @@ -256,14 +235,12 @@ export async function DELETE( return createErrorResponse('Unauthorized', 401) } - // Check if user has access to delete this chat const { hasAccess } = await checkChatAccess(chatId, session.user.id) if (!hasAccess) { return createErrorResponse('Chat not found or access denied', 404) } - // Delete the chat deployment await db.delete(chat).where(eq(chat.id, chatId)) logger.info(`Chat "${chatId}" deleted successfully`) diff --git a/apps/sim/app/api/chat/route.test.ts b/apps/sim/app/api/chat/route.test.ts index 030871a92e7..7661f7b576e 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 333643e9cba..c546691eccd 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 4a20e1784d9..00000000000 --- 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 2bafb4fca4d..00000000000 --- 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/chat/utils.test.ts b/apps/sim/app/api/chat/utils.test.ts index c200765852e..c1e5b68dff2 100644 --- a/apps/sim/app/api/chat/utils.test.ts +++ b/apps/sim/app/api/chat/utils.test.ts @@ -43,8 +43,6 @@ vi.mock('@/lib/utils', () => ({ describe('Chat API Utils', () => { beforeEach(() => { - vi.resetModules() - vi.doMock('@/lib/logs/console/logger', () => ({ createLogger: vi.fn().mockReturnValue({ info: vi.fn(), @@ -61,6 +59,11 @@ describe('Chat API Utils', () => { NODE_ENV: 'development', }, }) + + vi.doMock('@/lib/environment', () => ({ + isDev: true, + isHosted: false, + })) }) afterEach(() => { @@ -71,30 +74,30 @@ describe('Chat API Utils', () => { it('should encrypt and validate auth tokens', async () => { const { encryptAuthToken, validateAuthToken } = await import('@/app/api/chat/utils') - const subdomainId = 'test-subdomain-id' + const chatId = 'test-chat-id' const type = 'password' - const token = encryptAuthToken(subdomainId, type) + const token = encryptAuthToken(chatId, type) expect(typeof token).toBe('string') expect(token.length).toBeGreaterThan(0) - const isValid = validateAuthToken(token, subdomainId) + const isValid = validateAuthToken(token, chatId) expect(isValid).toBe(true) - const isInvalidSubdomain = validateAuthToken(token, 'wrong-subdomain-id') - expect(isInvalidSubdomain).toBe(false) + const isInvalidChat = validateAuthToken(token, 'wrong-chat-id') + expect(isInvalidChat).toBe(false) }) it('should reject expired tokens', async () => { const { validateAuthToken } = await import('@/app/api/chat/utils') - const subdomainId = 'test-subdomain-id' + const chatId = 'test-chat-id' // Create an expired token by directly constructing it with an old timestamp const expiredToken = Buffer.from( - `${subdomainId}:password:${Date.now() - 25 * 60 * 60 * 1000}` + `${chatId}:password:${Date.now() - 25 * 60 * 60 * 1000}` ).toString('base64') - const isValid = validateAuthToken(expiredToken, subdomainId) + const isValid = validateAuthToken(expiredToken, chatId) expect(isValid).toBe(false) }) }) @@ -110,13 +113,13 @@ describe('Chat API Utils', () => { }, } as unknown as NextResponse - const subdomainId = 'test-subdomain-id' + const chatId = 'test-chat-id' const type = 'password' - setChatAuthCookie(mockResponse, subdomainId, type) + setChatAuthCookie(mockResponse, chatId, type) expect(mockSet).toHaveBeenCalledWith({ - name: `chat_auth_${subdomainId}`, + name: `chat_auth_${chatId}`, value: expect.any(String), httpOnly: true, secure: false, // Development mode @@ -134,7 +137,7 @@ describe('Chat API Utils', () => { const mockRequest = { headers: { - get: vi.fn().mockReturnValue('http://test.localhost:3000'), + get: vi.fn().mockReturnValue('http://localhost:3000'), }, } as any @@ -148,7 +151,7 @@ describe('Chat API Utils', () => { expect(mockResponse.headers.set).toHaveBeenCalledWith( 'Access-Control-Allow-Origin', - 'http://test.localhost:3000' + 'http://localhost:3000' ) expect(mockResponse.headers.set).toHaveBeenCalledWith( 'Access-Control-Allow-Credentials', @@ -169,7 +172,7 @@ describe('Chat API Utils', () => { const mockRequest = { headers: { - get: vi.fn().mockReturnValue('http://test.localhost:3000'), + get: vi.fn().mockReturnValue('http://localhost:3000'), }, } as any diff --git a/apps/sim/app/api/chat/utils.ts b/apps/sim/app/api/chat/utils.ts index 7b3165706b2..5fada4cc98b 100644 --- a/apps/sim/app/api/chat/utils.ts +++ b/apps/sim/app/api/chat/utils.ts @@ -11,7 +11,6 @@ import { LoggingSession } from '@/lib/logs/execution/logging-session' import { buildTraceSpans } from '@/lib/logs/execution/trace-spans/trace-spans' import { hasAdminPermission } from '@/lib/permissions/utils' import { processStreamingBlockLogs } from '@/lib/tokenization' -import { getEmailDomain } from '@/lib/urls/utils' import { decryptSecret, generateRequestId } from '@/lib/utils' import { TriggerUtils } from '@/lib/workflows/triggers' import { CHAT_ERROR_MESSAGES } from '@/app/chat/constants' @@ -102,17 +101,17 @@ export async function checkChatAccess( return { hasAccess: false } } -export const encryptAuthToken = (subdomainId: string, type: string): string => { - return Buffer.from(`${subdomainId}:${type}:${Date.now()}`).toString('base64') +export const encryptAuthToken = (chatId: string, type: string): string => { + return Buffer.from(`${chatId}:${type}:${Date.now()}`).toString('base64') } -export const validateAuthToken = (token: string, subdomainId: string): boolean => { +export const validateAuthToken = (token: string, chatId: string): boolean => { try { const decoded = Buffer.from(token, 'base64').toString() const [storedId, _type, timestamp] = decoded.split(':') - // Check if token is for this subdomain - if (storedId !== subdomainId) { + // Check if token is for this chat + if (storedId !== chatId) { return false } @@ -132,22 +131,16 @@ export const validateAuthToken = (token: string, subdomainId: string): boolean = } // Set cookie helper function -export const setChatAuthCookie = ( - response: NextResponse, - subdomainId: string, - type: string -): void => { - const token = encryptAuthToken(subdomainId, type) +export const setChatAuthCookie = (response: NextResponse, chatId: string, type: string): void => { + const token = encryptAuthToken(chatId, type) // Set cookie with HttpOnly and secure flags response.cookies.set({ - name: `chat_auth_${subdomainId}`, + name: `chat_auth_${chatId}`, value: token, httpOnly: true, secure: !isDev, sameSite: 'lax', path: '/', - // Using subdomain for the domain in production - domain: isDev ? undefined : `.${getEmailDomain()}`, maxAge: 60 * 60 * 24, // 24 hours }) } diff --git a/apps/sim/app/api/chat/validate/route.ts b/apps/sim/app/api/chat/validate/route.ts new file mode 100644 index 00000000000..779ee941174 --- /dev/null +++ b/apps/sim/app/api/chat/validate/route.ts @@ -0,0 +1,49 @@ +import { db } from '@sim/db' +import { chat } from '@sim/db/schema' +import { eq } from 'drizzle-orm' +import type { NextRequest } from 'next/server' +import { createLogger } from '@/lib/logs/console/logger' +import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' + +const logger = createLogger('ChatValidateAPI') + +/** + * GET endpoint to validate chat identifier availability + */ +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const identifier = searchParams.get('identifier') + + if (!identifier) { + return createErrorResponse('Identifier parameter is required', 400) + } + + if (!/^[a-z0-9-]+$/.test(identifier)) { + return createSuccessResponse({ + available: false, + error: 'Identifier can only contain lowercase letters, numbers, and hyphens', + }) + } + + const existingChat = await db + .select({ id: chat.id }) + .from(chat) + .where(eq(chat.identifier, identifier)) + .limit(1) + + const isAvailable = existingChat.length === 0 + + logger.debug( + `Identifier "${identifier}" availability check: ${isAvailable ? 'available' : 'taken'}` + ) + + return createSuccessResponse({ + available: isAvailable, + error: isAvailable ? null : 'This identifier is already in use', + }) + } catch (error: any) { + logger.error('Error validating chat identifier:', error) + return createErrorResponse(error.message || 'Failed to validate identifier', 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 d6d98dfbe32..6bd8ccbc4c3 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.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 d6f9ce704b7..85321217cec 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]/chat.css b/apps/sim/app/chat/[subdomain]/chat.css deleted file mode 100644 index c0459dcc1c5..00000000000 --- a/apps/sim/app/chat/[subdomain]/chat.css +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Chat Subdomain Light Mode Overrides - * - * This file overrides dark mode utility classes to force light mode appearance - * in the chat subdomain. It uses CSS variables defined in globals.css. - * - * The layout.tsx already applies the 'light' class which sets all the light - * theme CSS variables from globals.css, so we don't need to redefine them here. - */ - -/* Background Color Overrides */ -.chat-light-wrapper :is(.dark\:bg-black) { - background-color: hsl(var(--secondary)); -} - -.chat-light-wrapper :is(.dark\:bg-gray-900) { - background-color: hsl(var(--background)); -} - -.chat-light-wrapper :is(.dark\:bg-gray-800) { - background-color: hsl(var(--secondary)); -} - -.chat-light-wrapper :is(.dark\:bg-gray-700) { - background-color: hsl(var(--accent)); -} - -.chat-light-wrapper :is(.dark\:bg-gray-600) { - background-color: hsl(var(--muted)); -} - -.chat-light-wrapper :is(.dark\:bg-gray-300) { - background-color: hsl(var(--primary)); -} - -/* Text Color Overrides */ -.chat-light-wrapper :is(.dark\:text-gray-100) { - color: hsl(var(--primary)); -} - -.chat-light-wrapper :is(.dark\:text-gray-200) { - color: hsl(var(--foreground)); -} - -.chat-light-wrapper :is(.dark\:text-gray-300) { - color: hsl(var(--muted-foreground)); -} - -.chat-light-wrapper :is(.dark\:text-gray-400) { - color: hsl(var(--muted-foreground)); -} - -.chat-light-wrapper :is(.dark\:text-neutral-600) { - color: hsl(var(--muted-foreground)); -} - -.chat-light-wrapper :is(.dark\:text-blue-400) { - color: var(--brand-accent-hex); -} - -/* Border Color Overrides */ -.chat-light-wrapper :is(.dark\:border-gray-700) { - border-color: hsl(var(--border)); -} - -.chat-light-wrapper :is(.dark\:border-gray-800) { - border-color: hsl(var(--border)); -} - -.chat-light-wrapper :is(.dark\:border-gray-600) { - border-color: hsl(var(--border)); -} - -.chat-light-wrapper :is(.dark\:divide-gray-700) > * + * { - border-color: hsl(var(--border)); -} - -/* Hover State Overrides */ -.chat-light-wrapper :is(.dark\:hover\:bg-gray-800\/60:hover) { - background-color: hsl(var(--card-hover)); -} - -/* Code Block Overrides */ -.chat-light-wrapper pre:is(.dark\:bg-black) { - background-color: hsl(var(--workflow-dots)); -} - -.chat-light-wrapper code:is(.dark\:bg-gray-700) { - background-color: hsl(var(--accent)); -} - -.chat-light-wrapper code:is(.dark\:text-gray-200) { - color: hsl(var(--foreground)); -} - -/* Special Components */ -/* Tooltip overrides - keep tooltips dark with light text for consistency */ -.chat-light-wrapper [data-radix-tooltip-content] { - background-color: hsl(0 0% 3.9%) !important; - color: hsl(0 0% 98%) !important; -} - -/* Force light color scheme */ -.chat-light-wrapper { - color-scheme: light !important; -} diff --git a/apps/sim/app/chat/[subdomain]/layout.tsx b/apps/sim/app/chat/[subdomain]/layout.tsx deleted file mode 100644 index de843b8d4a1..00000000000 --- a/apps/sim/app/chat/[subdomain]/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -'use client' - -import { ThemeProvider } from 'next-themes' -import './chat.css' - -export default function ChatLayout({ children }: { children: React.ReactNode }) { - return ( - -
- {children} -
-
- ) -} diff --git a/apps/sim/app/chat/[subdomain]/page.tsx b/apps/sim/app/chat/[subdomain]/page.tsx deleted file mode 100644 index 7a005a4dd51..00000000000 --- 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 4bbf3f96ab9..5010ea5ccda 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 cf0c2f8dc3f..74035b8cc78 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 deleted file mode 100644 index fedddf40f2a..00000000000 --- a/apps/sim/app/conditional-theme-provider.tsx +++ /dev/null @@ -1,37 +0,0 @@ -'use client' - -import { usePathname } from 'next/navigation' -import type { ThemeProviderProps } from 'next-themes' -import { ThemeProvider as NextThemesProvider } from 'next-themes' - -export function ConditionalThemeProvider({ children, ...props }: ThemeProviderProps) { - const pathname = usePathname() - - // Force light mode for certain pages - const forcedTheme = - pathname === '/' || - pathname === '/homepage' || - pathname.startsWith('/login') || - pathname.startsWith('/signup') || - pathname.startsWith('/terms') || - pathname.startsWith('/privacy') || - pathname.startsWith('/invite') || - pathname.startsWith('/verify') || - pathname.startsWith('/changelog') - ? 'light' - : undefined - - return ( - - {children} - - ) -} diff --git a/apps/sim/app/layout.tsx b/apps/sim/app/layout.tsx index e85465f9276..50bbd857b37 100644 --- a/apps/sim/app/layout.tsx +++ b/apps/sim/app/layout.tsx @@ -9,7 +9,7 @@ import { createLogger } from '@/lib/logs/console/logger' import '@/app/globals.css' import { SessionProvider } from '@/lib/session/session-context' -import { ConditionalThemeProvider } from '@/app/conditional-theme-provider' +import { ThemeProvider } from '@/app/theme-provider' import { ZoomPrevention } from '@/app/zoom-prevention' const logger = createLogger('RootLayout') @@ -91,7 +91,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - + @@ -103,7 +103,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) )} - + ) diff --git a/apps/sim/app/theme-provider.tsx b/apps/sim/app/theme-provider.tsx index d5a7fd33821..a247c696e6b 100644 --- a/apps/sim/app/theme-provider.tsx +++ b/apps/sim/app/theme-provider.tsx @@ -1,9 +1,27 @@ 'use client' +import { usePathname } from 'next/navigation' import type { ThemeProviderProps } from 'next-themes' import { ThemeProvider as NextThemesProvider } from 'next-themes' export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + const pathname = usePathname() + + // Force light mode for certain pages + const forcedTheme = + pathname === '/' || + pathname === '/homepage' || + pathname.startsWith('/login') || + pathname.startsWith('/signup') || + pathname.startsWith('/terms') || + pathname.startsWith('/privacy') || + pathname.startsWith('/invite') || + pathname.startsWith('/verify') || + pathname.startsWith('/changelog') || + pathname.startsWith('/chat') + ? 'light' + : undefined + return ( {children} 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 64071a56291..0a70a2f1490 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' || @@ -125,14 +125,14 @@ export function ChatDeploy({ const data = await response.json() if (data.isDeployed && data.deployment) { - const detailResponse = await fetch(`/api/chat/edit/${data.deployment.id}`) + const detailResponse = await fetch(`/api/chat/manage/${data.deployment.id}`) if (detailResponse.ok) { const chatDetail = await detailResponse.json() 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) } @@ -217,7 +217,7 @@ export function ChatDeploy({ try { setIsDeleting(true) - const response = await fetch(`/api/chat/edit/${existingChat.id}`, { + const response = await fetch(`/api/chat/manage/${existingChat.id}`, { method: 'DELETE', }) @@ -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 693f2dbe299..cfbeb4d4163 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 b9d25a3b7cb..8e37e4330a3 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 identifier from path-based URL format (e.g., sim.ai/chat/identifier) + const pathParts = url.pathname.split('/') + const identifierPart = pathParts[2] || '' // /chat/identifier + + 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} + {identifierPart} -
- {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 5dd12da3a2a..2d03c0c9034 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: { @@ -95,7 +95,7 @@ export function useChatDeployment() { chatSchema.parse(payload) // Determine endpoint and method - const endpoint = existingChatId ? `/api/chat/edit/${existingChatId}` : '/api/chat' + const endpoint = existingChatId ? `/api/chat/manage/${existingChatId}` : '/api/chat' const method = existingChatId ? 'PATCH' : 'POST' const response = await fetch(endpoint, { @@ -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 417437744bc..753662453f5 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-subdomain-validation.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-identifier-validation.ts similarity index 55% rename from apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-subdomain-validation.ts rename to apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/hooks/use-identifier-validation.ts index 931072f83bb..3c9b675fbcc 100644 --- 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-identifier-validation.ts @@ -1,8 +1,8 @@ import { useEffect, useRef, useState } from 'react' -export function useSubdomainValidation( - subdomain: string, - originalSubdomain?: string, +export function useIdentifierValidation( + identifier: string, + originalIdentifier?: string, isEditingExisting?: boolean ) { const [isChecking, setIsChecking] = useState(false) @@ -12,71 +12,72 @@ export function useSubdomainValidation( const timeoutRef = useRef(null) useEffect(() => { - // Clear previous timeout if (timeoutRef.current) { clearTimeout(timeoutRef.current) } - // Reset states immediately when subdomain changes + // Reset states immediately when identifier changes setError(null) setIsValid(false) setIsChecking(false) // Skip validation if empty - if (!subdomain.trim()) { + if (!identifier.trim()) { return } // Skip validation if same as original (existing deployment) - if (originalSubdomain && subdomain === originalSubdomain) { + if (originalIdentifier && identifier === originalIdentifier) { setIsValid(true) return } - // If we're editing an existing deployment but originalSubdomain isn't available yet, + // 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 && !originalSubdomain) { + if (isEditingExisting && !originalIdentifier) { setIsValid(true) return } - // Validate format first - if (!/^[a-z0-9-]+$/.test(subdomain)) { - setError('Subdomain can only contain lowercase letters, numbers, and hyphens') + // Validate format first - client-side validation + if (!/^[a-z0-9-]+$/.test(identifier)) { + setError('Identifier can only contain lowercase letters, numbers, and hyphens') return } - // Debounce API call + // Check availability with server setIsChecking(true) timeoutRef.current = setTimeout(async () => { try { const response = await fetch( - `/api/chat/subdomains/validate?subdomain=${encodeURIComponent(subdomain)}` + `/api/chat/validate?identifier=${encodeURIComponent(identifier)}` ) const data = await response.json() - if (!response.ok || !data.available) { - setError(data.error || 'This subdomain is already in use') + if (!response.ok) { + setError('Error checking identifier availability') + setIsValid(false) + } else if (!data.available) { + setError(data.error || 'This identifier is already in use') setIsValid(false) } else { setError(null) setIsValid(true) } } catch (error) { - setError('Error checking subdomain availability') + setError('Error checking identifier availability') setIsValid(false) } finally { setIsChecking(false) } }, 500) - // Cleanup function return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current) } } - }, [subdomain, originalSubdomain, isEditingExisting]) + }, [identifier, originalIdentifier, isEditingExisting]) return { isChecking, error, isValid } } diff --git a/apps/sim/components/ui/progress.tsx b/apps/sim/components/ui/progress.tsx index 56a6f56741f..28ef70b5475 100644 --- a/apps/sim/components/ui/progress.tsx +++ b/apps/sim/components/ui/progress.tsx @@ -16,7 +16,10 @@ const Progress = React.forwardRef diff --git a/apps/sim/middleware.ts b/apps/sim/middleware.ts index a211e091216..52ef03444fe 100644 --- a/apps/sim/middleware.ts +++ b/apps/sim/middleware.ts @@ -1,9 +1,8 @@ import { getSessionCookie } from 'better-auth/cookies' import { type NextRequest, NextResponse } from 'next/server' -import { isDev, isHosted } from './lib/environment' +import { isHosted } from './lib/environment' import { createLogger } from './lib/logs/console/logger' import { generateRuntimeCSP } from './lib/security/csp' -import { getBaseDomain } from './lib/urls/utils' const logger = createLogger('Middleware') @@ -13,143 +12,98 @@ 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 -] - -const BASE_DOMAIN = getBaseDomain() - -export async function middleware(request: NextRequest) { - // Check for active session - const sessionCookie = getSessionCookie(request) - const hasActiveSession = !!sessionCookie - +] as const + +/** + * Handles authentication-based redirects for root paths + */ +function handleRootPathRedirects( + request: NextRequest, + hasActiveSession: boolean +): 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)) + if (url.pathname !== '/' && url.pathname !== '/homepage') { + 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)) - } - - // For root path, redirect authenticated users to workspace - if (hasActiveSession && url.pathname === '/') { + 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)) } - // Handle login page - 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() + // Hosted: Allow access to /homepage route even for authenticated users + if (url.pathname === '/homepage') { + return NextResponse.rewrite(new URL('/', request.url)) } - // Handle protected routes that require authentication - if (url.pathname.startsWith('/workspace')) { - if (!hasActiveSession) { - return NextResponse.redirect(new URL('/login', request.url)) - } + // For root path, redirect authenticated users to workspace + if (hasActiveSession && url.pathname === '/') { + return NextResponse.redirect(new URL('/workspace', request.url)) + } - // Email verification is enforced by Better Auth (server-side). No cookie gating here. - return NextResponse.next() + 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 } - // 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() + 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 } - // 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)) - } + 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 +112,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,10 +128,44 @@ export async function middleware(request: NextRequest) { }) } + return null +} + +export async function middleware(request: NextRequest) { + const url = request.nextUrl + + const sessionCookie = getSessionCookie(request) + const hasActiveSession = !!sessionCookie + + const redirect = handleRootPathRedirects(request, hasActiveSession) + if (redirect) return redirect + + if (url.pathname === '/login' || url.pathname === '/signup') { + if (hasActiveSession) { + return NextResponse.redirect(new URL('/workspace', request.url)) + } + return NextResponse.next() + } + + if (url.pathname.startsWith('/workspace')) { + if (!hasActiveSession) { + return NextResponse.redirect(new URL('/login', request.url)) + } + return NextResponse.next() + } + + const invitationRedirect = handleInvitationRedirects(request, hasActiveSession) + if (invitationRedirect) return invitationRedirect + + const workspaceInvitationRedirect = handleWorkspaceInvitationAPI(request, hasActiveSession) + if (workspaceInvitationRedirect) return workspaceInvitationRedirect + + const securityBlock = handleSecurityFiltering(request) + if (securityBlock) return securityBlock + const response = NextResponse.next() response.headers.set('Vary', 'User-Agent') - // Generate runtime CSP for main application routes that need dynamic environment variables if ( url.pathname.startsWith('/workspace') || url.pathname.startsWith('/chat') || @@ -188,7 +177,6 @@ export async function middleware(request: NextRequest) { return response } -// Update matcher to include invitation routes and root path export const config = { matcher: [ '/', // Root path for self-hosted redirect logic diff --git a/apps/sim/stores/settings/general/store.ts b/apps/sim/stores/settings/general/store.ts index 49dfa853ec8..dc51c1d4a32 100644 --- a/apps/sim/stores/settings/general/store.ts +++ b/apps/sim/stores/settings/general/store.ts @@ -194,16 +194,9 @@ export const useGeneralStore = create()( // If parsing fails, continue to load from DB } } - // Skip loading if on a subdomain or chat path - if ( - typeof window !== 'undefined' && - (window.location.pathname.startsWith('/chat/') || - (window.location.hostname !== 'sim.ai' && - window.location.hostname !== 'localhost' && - window.location.hostname !== '127.0.0.1' && - !window.location.hostname.startsWith('www.'))) - ) { - logger.debug('Skipping settings load - on chat or subdomain page') + // Skip loading if on a chat path + if (typeof window !== 'undefined' && window.location.pathname.startsWith('/chat/')) { + logger.debug('Skipping settings load - on chat page') return } @@ -258,15 +251,8 @@ export const useGeneralStore = create()( }, updateSetting: async (key, value) => { - if ( - typeof window !== 'undefined' && - (window.location.pathname.startsWith('/chat/') || - (window.location.hostname !== 'sim.ai' && - window.location.hostname !== 'localhost' && - window.location.hostname !== '127.0.0.1' && - !window.location.hostname.startsWith('www.'))) - ) { - logger.debug(`Skipping setting update for ${key} on chat or subdomain page`) + if (typeof window !== 'undefined' && window.location.pathname.startsWith('/chat/')) { + logger.debug(`Skipping setting update for ${key} on chat page`) return } 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 00000000000..bd69c55702d --- /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 00000000000..161fcc8083f --- /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 e1aba478e20..4b6a0d7c815 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 34bf07520ca..e3701252c21 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), } } )