diff --git a/apps/docs/components/icons.tsx b/apps/docs/components/icons.tsx index fe869a5b77e..4c58dddc322 100644 --- a/apps/docs/components/icons.tsx +++ b/apps/docs/components/icons.tsx @@ -1274,6 +1274,31 @@ export function xAIIcon(props: SVGProps) { ) } +export function TikTokIcon(props: SVGProps) { + return ( + + + + + + ) +} + export function xIcon(props: SVGProps) { return ( diff --git a/apps/sim/app/api/tools/jupyter/upload/route.ts b/apps/sim/app/api/tools/jupyter/upload/route.ts index 3c12cb21bbf..ff0c38862d9 100644 --- a/apps/sim/app/api/tools/jupyter/upload/route.ts +++ b/apps/sim/app/api/tools/jupyter/upload/route.ts @@ -18,6 +18,7 @@ import { buildJupyterAuthHeaders, encodeJupyterPath, normalizeJupyterServerUrl, + parseJupyterContentModel, UnsafeJupyterPathError, } from '@/tools/jupyter/utils' @@ -128,17 +129,29 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } - const uploaded = await response.json() + const uploaded = parseJupyterContentModel(await response.json()) + if (!uploaded) { + logger.error(`[${requestId}] Jupyter returned an invalid upload response`) + return NextResponse.json( + { success: false, error: 'Jupyter returned an invalid upload response' }, + { status: 502 } + ) + } + + const uploadedName = uploaded.name ?? fileName + const uploadedPath = uploaded.path ?? destinationPath + const uploadedSize = uploaded.size ?? fileBuffer.length + const lastModified = uploaded.lastModified ?? null - logger.info(`[${requestId}] File uploaded to Jupyter: ${uploaded.path}`) + logger.info(`[${requestId}] File uploaded to Jupyter: ${uploadedPath}`) return NextResponse.json({ success: true, output: { - name: uploaded.name ?? fileName, - path: uploaded.path ?? destinationPath, - size: uploaded.size ?? fileBuffer.length, - lastModified: uploaded.last_modified ?? null, + name: uploadedName, + path: uploadedPath, + size: uploadedSize, + lastModified, }, }) } catch (error) { diff --git a/apps/sim/app/api/tools/tiktok/publish-video/route.ts b/apps/sim/app/api/tools/tiktok/publish-video/route.ts new file mode 100644 index 00000000000..d60157807df --- /dev/null +++ b/apps/sim/app/api/tools/tiktok/publish-video/route.ts @@ -0,0 +1,287 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { tiktokPublishVideoContract } from '@/lib/api/contracts/tiktok-tools' +import { parseRequest } from '@/lib/api/server' +import { checkInternalAuth } from '@/lib/auth/hybrid' +import { generateRequestId } from '@/lib/core/utils/request' +import { isPayloadSizeLimitError, readResponseTextWithLimit } from '@/lib/core/utils/stream-limits' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { + getFileExtension, + getMimeTypeFromExtension, + processSingleFileToUserFile, +} from '@/lib/uploads/utils/file-utils' +import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server' +import { assertToolFileAccess } from '@/app/api/files/authorization' +import type { UserFile } from '@/executor/types' +import { tiktokPublishInitApiDataSchema } from '@/tools/tiktok/api-schemas' +import { readTikTokApiResponse } from '@/tools/tiktok/utils' + +export const dynamic = 'force-dynamic' + +const logger = createLogger('TikTokPublishVideoAPI') + +const TIKTOK_VIDEO_MIME_TYPES = new Set(['video/mp4', 'video/quicktime', 'video/webm']) + +/** TikTok requires each chunk between 5MB and 64MB; the final chunk absorbs the remainder (up to ~2x this size, well under the 128MB cap). Capped at 1000 chunks total, which this default comfortably satisfies up to TikTok's 4GB video size limit. */ +const DEFAULT_CHUNK_SIZE = 10_000_000 +const TIKTOK_ERROR_RESPONSE_MAX_BYTES = 64 * 1024 + +/** Maximum size this route will buffer in memory for a single file-upload request. TikTok's + * own limit is 4GB, but relaying that much through this server's memory per request isn't + * safe under concurrent load. Enforced before downloading the file so an oversized upload + * fails fast with a clean 413 instead of materializing hundreds of MB to multiple GB + * in-process. */ +const TIKTOK_MAX_VIDEO_BYTES = 250 * 1024 * 1024 + +function computeChunkPlan(totalBytes: number): { chunkSize: number; totalChunkCount: number } { + if (totalBytes <= DEFAULT_CHUNK_SIZE) { + return { chunkSize: totalBytes, totalChunkCount: 1 } + } + const totalChunkCount = Math.floor(totalBytes / DEFAULT_CHUNK_SIZE) + return { chunkSize: DEFAULT_CHUNK_SIZE, totalChunkCount } +} + +function resolveVideoMimeType(fileName: string, fileType: string | undefined): string | null { + if (fileType && TIKTOK_VIDEO_MIME_TYPES.has(fileType)) return fileType + const fromExtension = getMimeTypeFromExtension(getFileExtension(fileName)) + return TIKTOK_VIDEO_MIME_TYPES.has(fromExtension) ? fromExtension : null +} + +async function validateDirectPostSettings( + accessToken: string, + postInfo: { privacy_level: string; brand_content_toggle: boolean }, + requestId: string +): Promise { + if (postInfo.brand_content_toggle && postInfo.privacy_level === 'SELF_ONLY') { + return 'Branded content cannot use Only Me privacy.' + } + + const response = await fetch('https://open.tiktokapis.com/v2/post/publish/creator_info/query/', { + method: 'POST', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: '{}', + }) + const data = await response.json() + if (!response.ok || (data.error?.code && data.error.code !== 'ok')) { + logger.warn(`[${requestId}] TikTok creator-info preflight failed`, { + status: response.status, + code: data.error?.code, + }) + return data.error?.message || 'TikTok creator information could not be verified.' + } + + const privacyOptions: unknown = data.data?.privacy_level_options + if ( + !Array.isArray(privacyOptions) || + !privacyOptions.some((option) => option === postInfo.privacy_level) + ) { + return `The selected privacy level (${postInfo.privacy_level}) is not currently available for this TikTok account. Run Query Creator Info and choose one of the returned options.` + } + + return null +} + +async function uploadChunks( + uploadUrl: string, + buffer: Buffer, + mimeType: string, + requestId: string +): Promise { + const totalBytes = buffer.length + const { chunkSize, totalChunkCount } = computeChunkPlan(totalBytes) + + for (let i = 0; i < totalChunkCount; i++) { + const start = i * chunkSize + const isLastChunk = i === totalChunkCount - 1 + const end = isLastChunk ? totalBytes - 1 : start + chunkSize - 1 + const chunk = buffer.subarray(start, end + 1) + + const response = await fetch(uploadUrl, { + method: 'PUT', + headers: { + 'Content-Type': mimeType, + 'Content-Length': String(chunk.length), + 'Content-Range': `bytes ${start}-${end}/${totalBytes}`, + }, + body: new Uint8Array(chunk), + }) + + if (!response.ok) { + const errorText = await readResponseTextWithLimit(response, { + maxBytes: TIKTOK_ERROR_RESPONSE_MAX_BYTES, + label: 'TikTok upload error response', + }).catch(() => 'Error response exceeded the allowed size') + logger.error(`[${requestId}] TikTok chunk upload failed`, { + chunkIndex: i, + status: response.status, + errorText, + }) + throw new Error( + `TikTok rejected video chunk ${i + 1}/${totalChunkCount}: ${response.status} ${errorText || response.statusText}` + ) + } + } +} + +export const POST = withRouteHandler(async (request: NextRequest) => { + const requestId = generateRequestId() + + try { + const authResult = await checkInternalAuth(request, { requireWorkflowId: false }) + if (!authResult.success || !authResult.userId) { + logger.warn(`[${requestId}] Unauthorized TikTok publish-video attempt: ${authResult.error}`) + return NextResponse.json( + { success: false, error: authResult.error || 'Authentication required' }, + { status: 401 } + ) + } + + const parsed = await parseRequest(tiktokPublishVideoContract, request, {}) + if (!parsed.success) return parsed.response + const data = parsed.data.body + + let userFile: UserFile + try { + userFile = processSingleFileToUserFile(data.file, requestId, logger) + } catch (error) { + return NextResponse.json( + { success: false, error: getErrorMessage(error, 'Failed to process file') }, + { status: 400 } + ) + } + + const denied = await assertToolFileAccess(userFile.key, authResult.userId, requestId, logger) + if (denied) return denied + + if (data.mode === 'direct') { + const settingsError = await validateDirectPostSettings( + data.accessToken, + data.postInfo, + requestId + ) + if (settingsError) { + return NextResponse.json({ success: false, error: settingsError }, { status: 400 }) + } + } + + const mimeType = resolveVideoMimeType(userFile.name, userFile.type) + if (!mimeType) { + return NextResponse.json( + { + success: false, + error: 'Unsupported video type. TikTok accepts MP4, MOV/QuickTime, or WebM files.', + }, + { status: 400 } + ) + } + + logger.info(`[${requestId}] Downloading video from storage`, { + fileName: userFile.name, + size: userFile.size, + }) + + const fileBuffer = await downloadFileFromStorage(userFile, requestId, logger, { + maxBytes: TIKTOK_MAX_VIDEO_BYTES, + }) + if (fileBuffer.length === 0) { + return NextResponse.json( + { success: false, error: 'The video file is empty.' }, + { status: 400 } + ) + } + const { chunkSize, totalChunkCount } = computeChunkPlan(fileBuffer.length) + + const initUrl = + data.mode === 'draft' + ? 'https://open.tiktokapis.com/v2/post/publish/inbox/video/init/' + : 'https://open.tiktokapis.com/v2/post/publish/video/init/' + + const initBody: Record = { + source_info: { + source: 'FILE_UPLOAD', + video_size: fileBuffer.length, + chunk_size: chunkSize, + total_chunk_count: totalChunkCount, + }, + } + if (data.mode === 'direct') { + initBody.post_info = data.postInfo + } + + logger.info(`[${requestId}] Initializing TikTok video ${data.mode}`, { + videoSize: fileBuffer.length, + chunkSize, + totalChunkCount, + }) + + const initResponse = await fetch(initUrl, { + method: 'POST', + headers: { + Authorization: `Bearer ${data.accessToken}`, + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: JSON.stringify(initBody), + }) + + const { data: initData, error: initError } = await readTikTokApiResponse( + initResponse, + tiktokPublishInitApiDataSchema + ) + + if (initError) { + logger.error(`[${requestId}] TikTok init failed`, { error: initError }) + return NextResponse.json( + { success: false, error: initError.message || 'Failed to initialize TikTok upload' }, + { status: initResponse.status >= 400 ? initResponse.status : 502 } + ) + } + + const publishId = initData?.publish_id + const uploadUrl = initData?.upload_url + + if (!publishId || !uploadUrl) { + return NextResponse.json( + { success: false, error: 'TikTok did not return a publish ID and upload URL' }, + { status: 502 } + ) + } + + try { + await uploadChunks(uploadUrl, fileBuffer, mimeType, requestId) + } catch (error) { + return NextResponse.json( + { success: false, error: getErrorMessage(error, 'Failed to upload video to TikTok') }, + { status: 502 } + ) + } + + logger.info(`[${requestId}] TikTok video upload complete`, { publishId }) + + return NextResponse.json({ success: true, output: { publishId } }) + } catch (error) { + if (isPayloadSizeLimitError(error)) { + logger.warn(`[${requestId}] Rejected oversized TikTok video upload`, { + maxBytes: error.maxBytes, + observedBytes: error.observedBytes, + }) + const maxMb = Math.floor(TIKTOK_MAX_VIDEO_BYTES / (1024 * 1024)) + return NextResponse.json( + { + success: false, + error: `Video exceeds the ${maxMb}MB limit for file uploads.`, + }, + { status: 413 } + ) + } + logger.error(`[${requestId}] Error publishing video to TikTok:`, error) + return NextResponse.json( + { success: false, error: getErrorMessage(error, 'Internal server error') }, + { status: 500 } + ) + } +}) diff --git a/apps/sim/app/api/webhooks/tiktok/route.test.ts b/apps/sim/app/api/webhooks/tiktok/route.test.ts new file mode 100644 index 00000000000..71d07947cab --- /dev/null +++ b/apps/sim/app/api/webhooks/tiktok/route.test.ts @@ -0,0 +1,116 @@ +/** + * @vitest-environment node + */ + +import crypto from 'node:crypto' +import { NextRequest } from 'next/server' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockEnqueue, mockExecuteTikTokWebhookIngress, mockRelease } = vi.hoisted(() => ({ + mockEnqueue: vi.fn(), + mockExecuteTikTokWebhookIngress: vi.fn(), + mockRelease: vi.fn(), +})) + +vi.mock('@/background/tiktok-webhook-ingress', () => ({ + executeTikTokWebhookIngress: mockExecuteTikTokWebhookIngress, + TIKTOK_WEBHOOK_INGRESS_CONCURRENCY_LIMIT: 50, + TIKTOK_WEBHOOK_INGRESS_MAX_ATTEMPTS: 3, +})) + +vi.mock('@/lib/core/admission/gate', () => ({ + admissionRejectedResponse: vi.fn(() => new Response(null, { status: 503 })), + tryAdmit: vi.fn(() => ({ release: mockRelease })), +})) + +vi.mock('@/lib/core/async-jobs', () => ({ + getJobQueue: vi.fn(async () => ({ enqueue: mockEnqueue })), +})) + +vi.mock('@/lib/core/config/env', () => ({ + env: { + TIKTOK_CLIENT_ID: 'client-key', + TIKTOK_CLIENT_SECRET: 'client-secret', + }, +})) + +vi.mock('@/lib/core/utils/request', () => ({ + generateRequestId: vi.fn(() => 'request-1'), +})) + +vi.mock('@/lib/core/utils/with-route-handler', () => ({ + withRouteHandler: + (handler: (request: NextRequest) => Promise) => (request: NextRequest) => + handler(request), +})) + +import { POST } from '@/app/api/webhooks/tiktok/route' + +function signedRequest(overrides?: { clientKey?: string }): NextRequest { + const body = JSON.stringify({ + client_key: overrides?.clientKey ?? 'client-key', + event: 'post.publish.complete', + create_time: 1_725_000_000, + user_openid: 'act.user', + content: '{"publish_id":"publish-1"}', + }) + const timestamp = String(Math.floor(Date.now() / 1000)) + const signature = crypto + .createHmac('sha256', 'client-secret') + .update(`${timestamp}.${body}`, 'utf8') + .digest('hex') + + return new NextRequest('http://localhost/api/webhooks/tiktok', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'TikTok-Signature': `t=${timestamp},s=${signature}`, + }, + body, + }) +} + +describe('TikTok webhook ingress route', () => { + beforeEach(() => { + vi.clearAllMocks() + mockEnqueue.mockResolvedValue('ingress-job-1') + }) + + it('returns 200 only after the verified delivery is accepted by the job queue', async () => { + const response = await POST(signedRequest()) + + expect(response.status).toBe(200) + await expect(response.json()).resolves.toEqual({ ok: true }) + expect(mockEnqueue).toHaveBeenCalledWith( + 'tiktok-webhook-ingress', + expect.objectContaining({ + envelope: expect.objectContaining({ + client_key: 'client-key', + user_openid: 'act.user', + }), + requestId: 'request-1', + }), + expect.objectContaining({ + maxAttempts: 3, + runner: expect.any(Function), + }) + ) + expect(mockRelease).toHaveBeenCalledOnce() + }) + + it('returns 503 when durable acceptance fails so TikTok retries', async () => { + mockEnqueue.mockRejectedValue(new Error('queue unavailable')) + + const response = await POST(signedRequest()) + + expect(response.status).toBe(503) + expect(mockRelease).toHaveBeenCalledOnce() + }) + + it('rejects a signed delivery for a different TikTok app', async () => { + const response = await POST(signedRequest({ clientKey: 'other-client-key' })) + + expect(response.status).toBe(401) + expect(mockEnqueue).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/app/api/webhooks/tiktok/route.ts b/apps/sim/app/api/webhooks/tiktok/route.ts new file mode 100644 index 00000000000..5fecb9134d0 --- /dev/null +++ b/apps/sim/app/api/webhooks/tiktok/route.ts @@ -0,0 +1,135 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { tiktokWebhookEnvelopeSchema } from '@/lib/api/contracts/webhooks' +import { admissionRejectedResponse, tryAdmit } from '@/lib/core/admission/gate' +import { getJobQueue } from '@/lib/core/async-jobs' +import { env } from '@/lib/core/config/env' +import { generateRequestId } from '@/lib/core/utils/request' +import { + assertContentLengthWithinLimit, + isPayloadSizeLimitError, + readStreamToBufferWithLimit, +} from '@/lib/core/utils/stream-limits' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { WEBHOOK_MAX_BODY_BYTES } from '@/lib/webhooks/constants' +import { verifyTikTokSignature } from '@/lib/webhooks/providers/tiktok' +import { + executeTikTokWebhookIngress, + TIKTOK_WEBHOOK_INGRESS_CONCURRENCY_LIMIT, + TIKTOK_WEBHOOK_INGRESS_MAX_ATTEMPTS, + type TikTokWebhookIngressPayload, +} from '@/background/tiktok-webhook-ingress' + +const logger = createLogger('TikTokWebhookIngress') + +const TIKTOK_BODY_LABEL = 'TikTok webhook body' + +export const dynamic = 'force-dynamic' +export const runtime = 'nodejs' +export const maxDuration = 60 + +async function readTikTokBody(req: Request): Promise { + assertContentLengthWithinLimit(req.headers, WEBHOOK_MAX_BODY_BYTES, TIKTOK_BODY_LABEL) + const buffer = await readStreamToBufferWithLimit(req.body, { + maxBytes: WEBHOOK_MAX_BODY_BYTES, + label: TIKTOK_BODY_LABEL, + }) + return new TextDecoder().decode(buffer) +} + +/** + * App-level TikTok webhook Callback URL. + * Portal: `{APP_URL}/api/webhooks/tiktok` (e.g. https://www.sim.ai/api/webhooks/tiktok). + * Verifies TikTok-Signature and durably accepts the delivery before background target fanout. + */ +export const POST = withRouteHandler(async (request: NextRequest) => { + const ticket = tryAdmit() + if (!ticket) { + return admissionRejectedResponse() + } + + const requestId = generateRequestId() + const receivedAt = Date.now() + + try { + let rawBody: string + try { + rawBody = await readTikTokBody(request) + } catch (bodyError) { + if (isPayloadSizeLimitError(bodyError)) { + logger.warn(`[${requestId}] Rejected oversized TikTok webhook body`, { + maxBytes: WEBHOOK_MAX_BODY_BYTES, + observedBytes: bodyError.observedBytes, + }) + return NextResponse.json({ error: 'Request body too large' }, { status: 413 }) + } + throw bodyError + } + + const authError = verifyTikTokSignature( + rawBody, + request.headers.get('TikTok-Signature'), + requestId + ) + if (authError) { + return authError + } + + let parsedJson: unknown + try { + parsedJson = rawBody ? JSON.parse(rawBody) : {} + } catch { + logger.warn(`[${requestId}] TikTok webhook body is not valid JSON`) + // Ack to avoid retry storms on malformed payloads after a valid signature. + return NextResponse.json({ ok: true }) + } + + const envelopeResult = tiktokWebhookEnvelopeSchema.safeParse(parsedJson) + if (!envelopeResult.success) { + logger.warn(`[${requestId}] Invalid TikTok webhook envelope`, { + issues: envelopeResult.error.issues, + }) + return NextResponse.json({ ok: true }) + } + + const envelope = envelopeResult.data + if (!env.TIKTOK_CLIENT_ID || envelope.client_key !== env.TIKTOK_CLIENT_ID) { + logger.warn(`[${requestId}] TikTok webhook client_key does not match configured app`) + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const payload: TikTokWebhookIngressPayload = { + envelope, + headers: { + 'content-type': request.headers.get('content-type') ?? 'application/json', + }, + requestId, + receivedAt, + } + const jobQueue = await getJobQueue() + const jobId = await jobQueue.enqueue('tiktok-webhook-ingress', payload, { + maxAttempts: TIKTOK_WEBHOOK_INGRESS_MAX_ATTEMPTS, + concurrencyKey: 'tiktok-webhook-ingress', + concurrencyLimit: TIKTOK_WEBHOOK_INGRESS_CONCURRENCY_LIMIT, + runner: async () => { + await executeTikTokWebhookIngress(payload) + }, + }) + + logger.info(`[${requestId}] Accepted TikTok webhook delivery`, { + event: envelope.event, + jobId, + userOpenIdPrefix: envelope.user_openid.slice(0, 12), + }) + + return NextResponse.json({ ok: true }) + } catch (error) { + logger.error(`[${requestId}] TikTok webhook ingress error`, { + error: getErrorMessage(error, 'Unknown error'), + }) + return NextResponse.json({ error: 'Temporarily unable to accept webhook' }, { status: 503 }) + } finally { + ticket.release() + } +}) diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts index df0e11e35dd..395882f0c8b 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.test.ts @@ -15,63 +15,52 @@ import { } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -/** Mock execution dependencies for webhook tests */ -function mockExecutionDependencies() { - vi.mock('@/lib/core/security/encryption', () => encryptionMock) - - vi.mock('@/lib/logs/execution/trace-spans/trace-spans', () => ({ - buildTraceSpans: vi.fn().mockReturnValue({ traceSpans: [], totalDuration: 100 }), - })) - - vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) - - vi.mock('@/serializer', () => ({ - Serializer: vi.fn().mockImplementation(() => ({ - serializeWorkflow: vi.fn().mockReturnValue({ - version: '1.0', - blocks: [ - { - id: 'starter-id', - metadata: { id: 'starter', name: 'Start' }, - config: {}, - inputs: {}, - outputs: {}, - position: { x: 100, y: 100 }, - enabled: true, - }, - { - id: 'agent-id', - metadata: { id: 'agent', name: 'Agent 1' }, - config: {}, - inputs: {}, - outputs: {}, - position: { x: 634, y: -167 }, - enabled: true, - }, - ], - edges: [ - { - id: 'edge-1', - source: 'starter-id', - target: 'agent-id', - sourceHandle: 'source', - targetHandle: 'target', - }, - ], - loops: {}, - parallels: {}, - }), - })), - })) -} +vi.mock('@/lib/core/security/encryption', () => encryptionMock) -/** Mock Trigger.dev SDK */ -function mockTriggerDevSdk() { - vi.mock('@trigger.dev/sdk', () => ({ - tasks: { trigger: vi.fn().mockResolvedValue({ id: 'mock-task-id' }) }, - task: vi.fn().mockReturnValue({}), - })) -} +vi.mock('@/lib/logs/execution/trace-spans/trace-spans', () => ({ + buildTraceSpans: vi.fn().mockReturnValue({ traceSpans: [], totalDuration: 100 }), +})) + +vi.mock('@/lib/workflows/utils', () => workflowsUtilsMock) + +vi.mock('@/serializer', () => ({ + Serializer: vi.fn().mockImplementation(() => ({ + serializeWorkflow: vi.fn().mockReturnValue({ + version: '1.0', + blocks: [ + { + id: 'starter-id', + metadata: { id: 'starter', name: 'Start' }, + config: {}, + inputs: {}, + outputs: {}, + position: { x: 100, y: 100 }, + enabled: true, + }, + { + id: 'agent-id', + metadata: { id: 'agent', name: 'Agent 1' }, + config: {}, + inputs: {}, + outputs: {}, + position: { x: 634, y: -167 }, + enabled: true, + }, + ], + edges: [ + { + id: 'edge-1', + source: 'starter-id', + target: 'agent-id', + sourceHandle: 'source', + targetHandle: 'target', + }, + ], + loops: {}, + parallels: {}, + }), + })), +})) /** * Test data store - isolated per test via beforeEach reset @@ -106,6 +95,7 @@ const { executeMock, getWorkspaceBilledAccountUserIdMock, queueWebhookExecutionMock, + dispatchResolvedWebhookTargetMock, isWorkspaceApiExecutionEntitledMock, } = vi.hoisted(() => ({ generateRequestHashMock: vi.fn().mockResolvedValue('test-hash-123'), @@ -134,6 +124,7 @@ const { const { NextResponse } = await import('next/server') return NextResponse.json({ message: 'Webhook processed' }) }), + dispatchResolvedWebhookTargetMock: vi.fn(), isWorkspaceApiExecutionEntitledMock: vi.fn().mockResolvedValue(true), })) @@ -274,6 +265,25 @@ vi.mock('@/lib/webhooks/processor', () => ({ } ), handleProviderReachabilityTest: vi.fn().mockReturnValue(null), + dispatchResolvedWebhookTarget: dispatchResolvedWebhookTargetMock.mockImplementation( + async ( + foundWebhook: unknown, + foundWorkflow: unknown, + body: unknown, + request: unknown, + options: unknown + ) => ({ + outcome: 'queued', + reason: 'queued', + response: await queueWebhookExecutionMock( + foundWebhook, + foundWorkflow, + body, + request, + options + ), + }) + ), verifyProviderAuth: vi .fn() .mockImplementation( @@ -350,7 +360,6 @@ vi.mock('@/lib/webhooks/processor', () => ({ }), shouldSkipWebhookEvent: vi.fn().mockReturnValue(false), handlePreDeploymentVerification: vi.fn().mockReturnValue(null), - queueWebhookExecution: queueWebhookExecutionMock, })) vi.mock('drizzle-orm/postgres-js', () => ({ @@ -401,9 +410,6 @@ describe('Webhook Trigger API Route', () => { workflowsPersistenceUtilsMockFns.mockBlockExistsInDeployment.mockResolvedValue(true) isWorkspaceApiExecutionEntitledMock.mockResolvedValue(true) - mockExecutionDependencies() - mockTriggerDevSdk() - // Set up default workflow for tests testData.workflows.push({ id: 'test-workflow-id', @@ -495,8 +501,8 @@ describe('Webhook Trigger API Route', () => { expect(text).toMatch(/not found/i) }) - describe('Internal trigger providers', () => { - it.each(['sim', 'table'])( + describe('Non-path trigger providers', () => { + it.each(['sim', 'table', 'tiktok'])( 'rejects HTTP deliveries to %s trigger paths with 404', async (provider) => { testData.webhooks.push({ @@ -539,7 +545,7 @@ describe('Webhook Trigger API Route', () => { }) describe('Generic Webhook Authentication', () => { - it('passes correlation-bearing request context into webhook queueing', async () => { + it('passes request context into shared webhook dispatch', async () => { testData.webhooks.push({ id: 'generic-webhook-id', provider: 'generic', @@ -556,7 +562,7 @@ describe('Webhook Trigger API Route', () => { expect(response.status).toBe(200) expect(queueWebhookExecutionMock).toHaveBeenCalledOnce() - const call = queueWebhookExecutionMock.mock.calls[0] + const call = dispatchResolvedWebhookTargetMock.mock.calls[0] expect(call[0]).toEqual(expect.objectContaining({ id: 'generic-webhook-id' })) expect(call[1]).toEqual(expect.objectContaining({ id: 'test-workflow-id' })) expect(call[2]).toEqual(expect.objectContaining({ event: 'test', id: 'test-123' })) @@ -564,18 +570,6 @@ describe('Webhook Trigger API Route', () => { expect.objectContaining({ requestId: 'mock-request-id', path: 'test-path', - actorUserId: 'test-user-id', - executionId: 'preprocess-execution-id', - correlation: { - executionId: 'preprocess-execution-id', - requestId: 'mock-request-id', - source: 'webhook', - workflowId: 'test-workflow-id', - webhookId: 'generic-webhook-id', - path: 'test-path', - provider: 'generic', - triggerType: 'webhook', - }, }) ) }) diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.ts index 1c103ddad72..af9ebe7c134 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.ts @@ -10,18 +10,15 @@ import { admissionRejectedResponse, tryAdmit } from '@/lib/core/admission/gate' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { - checkWebhookPreprocessing, + dispatchResolvedWebhookTarget, findAllWebhooksForPath, - handlePreDeploymentVerification, handlePreLookupWebhookVerification, handleProviderChallenges, handleProviderReachabilityTest, parseWebhookBody, - queueWebhookExecution, - shouldSkipWebhookEvent, verifyProviderAuth, } from '@/lib/webhooks/processor' -import { blockExistsInDeployment } from '@/lib/workflows/persistence/utils' +import { acceptsPathWebhookDelivery } from '@/lib/webhooks/providers' import { isInternalTriggerProvider } from '@/triggers/constants' const logger = createLogger('WebhookTriggerAPI') @@ -107,15 +104,15 @@ async function handleWebhookPost( // Find all webhooks for this path (multiple webhooks in one workflow may share a path) const allWebhooksForPath = await findAllWebhooksForPath({ requestId, path }) - // Internal trigger providers (sim, table) are fired in-process, never over - // HTTP. Their rows still register a path, so reject deliveries here to keep - // forged events out. + /** Exclude in-process triggers and providers that own an app-level ingress route. */ const webhooksForPath = allWebhooksForPath.filter( - ({ webhook: foundWebhook }) => !isInternalTriggerProvider(foundWebhook.provider) + ({ webhook: foundWebhook }) => + !isInternalTriggerProvider(foundWebhook.provider) && + acceptsPathWebhookDelivery(foundWebhook.provider) ) if (allWebhooksForPath.length > 0 && webhooksForPath.length === 0) { - logger.warn(`[${requestId}] Rejected HTTP delivery to internal trigger path: ${path}`) + logger.warn(`[${requestId}] Rejected HTTP delivery to non-path trigger: ${path}`) return new NextResponse('Not Found', { status: 404 }) } @@ -172,48 +169,35 @@ async function handleWebhookPost( return reachabilityResponse } - const preprocessResult = await checkWebhookPreprocessing(foundWorkflow, foundWebhook, requestId) - if (preprocessResult.error) { - if (webhooksForPath.length > 1) { - logger.warn( - `[${requestId}] Preprocessing failed for webhook ${foundWebhook.id}, continuing to next` - ) - continue + const dispatchResult = await dispatchResolvedWebhookTarget( + foundWebhook, + foundWorkflow, + body, + request, + { + requestId, + path, + receivedAt, + triggerTimestampMs: Number.isFinite(triggerTimestampMs) ? triggerTimestampMs : undefined, } - return preprocessResult.error - } + ) - if (foundWebhook.blockId) { - const blockExists = await blockExistsInDeployment(foundWorkflow.id, foundWebhook.blockId) - if (!blockExists) { - const preDeploymentResponse = handlePreDeploymentVerification(foundWebhook, requestId) - if (preDeploymentResponse) { - return preDeploymentResponse - } + if (dispatchResult.reason === 'filtered') { + continue + } - logger.info( - `[${requestId}] Trigger block ${foundWebhook.blockId} not found in deployment for workflow ${foundWorkflow.id}` + if (dispatchResult.outcome === 'failed' || dispatchResult.reason === 'block-missing') { + if (webhooksForPath.length > 1) { + logger.warn( + `[${requestId}] Webhook dispatch failed for ${foundWebhook.id}, continuing to next`, + { reason: dispatchResult.reason, status: dispatchResult.response.status } ) - if (webhooksForPath.length > 1) { - continue - } - return new NextResponse('Trigger block not found in deployment', { status: 404 }) + continue } + return dispatchResult.response } - if (shouldSkipWebhookEvent(foundWebhook, body, requestId)) { - continue - } - const response = await queueWebhookExecution(foundWebhook, foundWorkflow, body, request, { - requestId, - path, - actorUserId: preprocessResult.actorUserId, - executionId: preprocessResult.executionId, - correlation: preprocessResult.correlation, - receivedAt, - triggerTimestampMs: Number.isFinite(triggerTimestampMs) ? triggerTimestampMs : undefined, - }) - responses.push(response) + responses.push(dispatchResult.response) } if (responses.length === 0) { diff --git a/apps/sim/background/tiktok-webhook-ingress.test.ts b/apps/sim/background/tiktok-webhook-ingress.test.ts new file mode 100644 index 00000000000..7673053bb95 --- /dev/null +++ b/apps/sim/background/tiktok-webhook-ingress.test.ts @@ -0,0 +1,109 @@ +/** + * @vitest-environment node + */ + +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockDispatchResolvedWebhookTarget, mockFindTikTokWebhookTargets } = vi.hoisted(() => ({ + mockDispatchResolvedWebhookTarget: vi.fn(), + mockFindTikTokWebhookTargets: vi.fn(), +})) + +vi.mock('@trigger.dev/sdk', () => ({ + task: vi.fn((config: unknown) => config), +})) + +vi.mock('@/lib/webhooks/processor', () => ({ + dispatchResolvedWebhookTarget: mockDispatchResolvedWebhookTarget, +})) + +vi.mock('@/lib/webhooks/providers/tiktok-targets', () => ({ + findTikTokWebhookTargets: mockFindTikTokWebhookTargets, +})) + +import { + executeTikTokWebhookIngress, + type TikTokWebhookIngressPayload, +} from '@/background/tiktok-webhook-ingress' + +const payload: TikTokWebhookIngressPayload = { + envelope: { + client_key: 'client-key', + event: 'post.publish.complete', + create_time: 1_725_000_000, + user_openid: 'act.user', + content: '{"publish_id":"publish-1"}', + }, + headers: { 'content-type': 'application/json' }, + requestId: 'request-1', + receivedAt: 1_725_000_000_000, +} + +const targets = [ + { + webhook: { id: 'webhook-1', path: 'tiktok', provider: 'tiktok' }, + workflow: { id: 'workflow-1' }, + }, + { + webhook: { id: 'webhook-2', path: 'tiktok', provider: 'tiktok' }, + workflow: { id: 'workflow-2' }, + }, +] + +describe('executeTikTokWebhookIngress', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('acknowledges deliveries without active targets', async () => { + mockFindTikTokWebhookTargets.mockResolvedValue([]) + + await expect(executeTikTokWebhookIngress(payload)).resolves.toEqual({ + ignored: 0, + processed: 0, + targetCount: 0, + }) + expect(mockDispatchResolvedWebhookTarget).not.toHaveBeenCalled() + }) + + it('dispatches every resolved target and reports typed outcomes', async () => { + mockFindTikTokWebhookTargets.mockResolvedValue(targets) + mockDispatchResolvedWebhookTarget + .mockResolvedValueOnce({ + outcome: 'queued', + reason: 'queued', + response: new Response(null, { status: 200 }), + }) + .mockResolvedValueOnce({ + outcome: 'ignored', + reason: 'event-mismatch', + response: new Response(null, { status: 200 }), + }) + + await expect(executeTikTokWebhookIngress(payload)).resolves.toEqual({ + ignored: 1, + processed: 1, + targetCount: 2, + }) + expect(mockDispatchResolvedWebhookTarget).toHaveBeenCalledTimes(2) + }) + + it('throws after a target failure so the durable ingress job retries', async () => { + mockFindTikTokWebhookTargets.mockResolvedValue(targets) + mockDispatchResolvedWebhookTarget + .mockResolvedValueOnce({ + outcome: 'queued', + reason: 'queued', + response: new Response(null, { status: 200 }), + }) + .mockResolvedValueOnce({ + outcome: 'failed', + reason: 'queue-failed', + response: new Response(null, { status: 500 }), + }) + + await expect(executeTikTokWebhookIngress(payload)).rejects.toThrow( + 'Failed to dispatch 1 of 2 TikTok webhook targets' + ) + }) +}) diff --git a/apps/sim/background/tiktok-webhook-ingress.ts b/apps/sim/background/tiktok-webhook-ingress.ts new file mode 100644 index 00000000000..a55cbb3783e --- /dev/null +++ b/apps/sim/background/tiktok-webhook-ingress.ts @@ -0,0 +1,105 @@ +import { createLogger } from '@sim/logger' +import { task } from '@trigger.dev/sdk' +import { NextRequest } from 'next/server' +import type { TikTokWebhookEnvelope } from '@/lib/api/contracts/webhooks' +import { dispatchResolvedWebhookTarget } from '@/lib/webhooks/processor' +import { findTikTokWebhookTargets } from '@/lib/webhooks/providers/tiktok-targets' + +const logger = createLogger('TikTokWebhookIngressTask') + +export const TIKTOK_WEBHOOK_INGRESS_CONCURRENCY_LIMIT = 50 +export const TIKTOK_WEBHOOK_INGRESS_MAX_ATTEMPTS = 3 + +export interface TikTokWebhookIngressPayload { + envelope: TikTokWebhookEnvelope + headers: { + 'content-type': string + } + requestId: string + receivedAt: number +} + +export interface TikTokWebhookIngressResult { + ignored: number + processed: number + targetCount: number +} + +/** + * Resolves and dispatches all active workflow targets for one verified TikTok delivery. Throwing + * after any retryable target failure lets Trigger.dev replay the fanout; workflow-level + * idempotency prevents already-queued targets from executing twice. + */ +export async function executeTikTokWebhookIngress( + payload: TikTokWebhookIngressPayload +): Promise { + const targets = await findTikTokWebhookTargets(payload.envelope.user_openid, payload.requestId) + if (targets.length === 0) { + logger.info(`[${payload.requestId}] No TikTok webhook targets found`, { + event: payload.envelope.event, + userOpenIdPrefix: payload.envelope.user_openid.slice(0, 12), + }) + return { ignored: 0, processed: 0, targetCount: 0 } + } + + const request = new NextRequest('http://internal/api/webhooks/tiktok', { + method: 'POST', + headers: payload.headers, + body: JSON.stringify(payload.envelope), + }) + + let ignored = 0 + let processed = 0 + let failed = 0 + + for (const { webhook, workflow } of targets) { + const result = await dispatchResolvedWebhookTarget( + webhook, + workflow, + payload.envelope, + request, + { + requestId: payload.requestId, + path: webhook.path, + receivedAt: payload.receivedAt, + triggerTimestampMs: payload.envelope.create_time * 1000, + } + ) + + if (result.outcome === 'queued') { + processed += 1 + } else if (result.outcome === 'ignored') { + ignored += 1 + } else { + failed += 1 + } + } + + if (failed > 0) { + throw new Error(`Failed to dispatch ${failed} of ${targets.length} TikTok webhook targets`) + } + + logger.info(`[${payload.requestId}] TikTok webhook fanout completed`, { + event: payload.envelope.event, + ignored, + processed, + targetCount: targets.length, + }) + + return { ignored, processed, targetCount: targets.length } +} + +export const tiktokWebhookIngressTask = task({ + id: 'tiktok-webhook-ingress', + machine: 'small-1x', + retry: { + maxAttempts: TIKTOK_WEBHOOK_INGRESS_MAX_ATTEMPTS, + factor: 2, + minTimeoutInMs: 1000, + maxTimeoutInMs: 10_000, + }, + queue: { + concurrencyLimit: TIKTOK_WEBHOOK_INGRESS_CONCURRENCY_LIMIT, + }, + run: async (payload: TikTokWebhookIngressPayload) => executeTikTokWebhookIngress(payload), +}) diff --git a/apps/sim/blocks/blocks/tiktok.ts b/apps/sim/blocks/blocks/tiktok.ts new file mode 100644 index 00000000000..dcd2dabcb69 --- /dev/null +++ b/apps/sim/blocks/blocks/tiktok.ts @@ -0,0 +1,637 @@ +import { TikTokIcon } from '@/components/icons' +import { getScopesForService } from '@/lib/oauth/utils' +import type { BlockConfig, BlockMeta } from '@/blocks/types' +import { AuthMode, IntegrationType } from '@/blocks/types' +import { normalizeFileInput } from '@/blocks/utils' +import type { TikTokResponse } from '@/tools/tiktok/types' +import { getTrigger } from '@/triggers' + +const VIDEO_POST_OPERATIONS = ['tiktok_direct_post_video', 'tiktok_upload_video_draft'] + +export const TikTokBlock: BlockConfig = { + type: 'tiktok', + name: 'TikTok', + description: 'Access TikTok profiles and videos, and publish content', + authMode: AuthMode.OAuth, + longDescription: + 'Integrate TikTok into your workflow. Get user profile information including follower counts and video statistics. List and query videos with cover images, embed links, and metadata. Publish videos directly to TikTok (or send them to the inbox as drafts) from an uploaded file or a file produced earlier in the workflow, then track post status.', + docsLink: 'https://docs.sim.ai/integrations/tiktok', + category: 'tools', + integrationType: IntegrationType.Communication, + bgColor: '#000000', + icon: TikTokIcon, + triggerAllowed: true, + hideFromToolbar: true, + + subBlocks: [ + { + id: 'operation', + title: 'Operation', + type: 'dropdown', + options: [ + { label: 'Get User Info', id: 'tiktok_get_user' }, + { label: 'List Videos', id: 'tiktok_list_videos' }, + { label: 'Query Videos', id: 'tiktok_query_videos' }, + { label: 'Query Creator Info', id: 'tiktok_query_creator_info' }, + { label: 'Direct Post Video', id: 'tiktok_direct_post_video' }, + { label: 'Upload Video Draft', id: 'tiktok_upload_video_draft' }, + { label: 'Get Post Status', id: 'tiktok_get_post_status' }, + ], + value: () => 'tiktok_get_user', + }, + + // --- OAuth Credential --- + { + id: 'credential', + title: 'TikTok Account', + type: 'oauth-input', + serviceId: 'tiktok', + canonicalParamId: 'oauthCredential', + mode: 'basic', + requiredScopes: getScopesForService('tiktok'), + placeholder: 'Select TikTok account', + required: true, + }, + { + id: 'manualCredential', + title: 'TikTok Account', + type: 'short-input', + canonicalParamId: 'oauthCredential', + mode: 'advanced', + placeholder: 'Enter credential ID', + required: true, + }, + + // --- Get User Info --- + { + id: 'fields', + title: 'Fields', + type: 'short-input', + placeholder: 'open_id,display_name,avatar_url,follower_count,video_count', + description: 'Comma-separated list of user fields to return. Leave empty for all fields.', + mode: 'advanced', + condition: { field: 'operation', value: 'tiktok_get_user' }, + wandConfig: { + enabled: true, + prompt: + 'Generate a comma-separated list of TikTok user info fields based on the user request, choosing only from: open_id, union_id, avatar_url, avatar_large_url, display_name, bio_description, profile_deep_link, is_verified, username, follower_count, following_count, likes_count, video_count. Return ONLY the comma-separated field names - no explanations, no extra text.', + placeholder: 'Describe which profile fields you need', + }, + }, + + // --- List Videos --- + { + id: 'maxCount', + title: 'Max Count', + type: 'short-input', + placeholder: '20', + description: 'Maximum number of videos to return (1-20).', + mode: 'advanced', + condition: { field: 'operation', value: 'tiktok_list_videos' }, + }, + { + id: 'cursor', + title: 'Cursor', + type: 'short-input', + placeholder: 'Pagination cursor from previous response', + mode: 'advanced', + condition: { field: 'operation', value: 'tiktok_list_videos' }, + }, + + // --- Query Videos --- + { + id: 'videoIds', + title: 'Video IDs', + type: 'long-input', + placeholder: 'One video ID per line or comma-separated (e.g., 7077642457847994444)', + condition: { field: 'operation', value: 'tiktok_query_videos' }, + required: { field: 'operation', value: 'tiktok_query_videos' }, + }, + + // --- Video file (Direct Post Video / Upload Video Draft) — Gmail-style upload ⇄ block ref --- + { + id: 'videoFile', + title: 'Video File', + type: 'file-upload', + canonicalParamId: 'file', + mode: 'basic', + placeholder: 'Upload video', + acceptedTypes: '.mp4,.mov,.webm', + multiple: false, + maxSize: 250, + description: 'MP4, MOV, or WebM video up to 250 MB.', + condition: { field: 'operation', value: VIDEO_POST_OPERATIONS }, + required: { field: 'operation', value: VIDEO_POST_OPERATIONS }, + }, + { + id: 'videoFileRef', + title: 'Video File', + type: 'short-input', + canonicalParamId: 'file', + mode: 'advanced', + placeholder: 'Reference a video from a previous block', + condition: { field: 'operation', value: VIDEO_POST_OPERATIONS }, + required: { field: 'operation', value: VIDEO_POST_OPERATIONS }, + }, + + // --- Caption (Direct Post Video) --- + { + id: 'title', + title: 'Title / Caption', + type: 'long-input', + placeholder: 'Caption with #hashtags and @mentions', + description: 'Video caption. Maximum 2200 characters.', + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + + // --- Privacy & interaction settings (Direct Post Video) --- + { + id: 'privacyLevel', + title: 'Privacy Level', + type: 'dropdown', + options: [ + { label: 'Public', id: 'PUBLIC_TO_EVERYONE' }, + { label: 'Friends', id: 'MUTUAL_FOLLOW_FRIENDS' }, + { label: 'Followers', id: 'FOLLOWER_OF_CREATOR' }, + { label: 'Only Me', id: 'SELF_ONLY' }, + ], + description: + 'Choose manually from the privacyLevelOptions returned by Query Creator Info. TikTok prohibits preselecting a privacy level. Unaudited apps are restricted to Only Me.', + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + required: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + { + id: 'disableComment', + title: 'Disable Comments', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + required: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + { + id: 'disableDuet', + title: 'Disable Duet', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + required: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + { + id: 'disableStitch', + title: 'Disable Stitch', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + required: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + { + id: 'videoCoverTimestampMs', + title: 'Cover Timestamp (ms)', + type: 'short-input', + placeholder: '1000', + description: 'Timestamp in milliseconds to use as the video cover image.', + mode: 'advanced', + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + { + id: 'isAigc', + title: 'AI-Generated Content', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + value: () => 'false', + mode: 'advanced', + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + { + id: 'brandContentToggle', + title: 'Paid Partnership', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + value: () => 'false', + description: + 'Disclose this post as a paid partnership promoting a third-party business. Branded content cannot be posted with Only Me privacy.', + mode: 'advanced', + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + { + id: 'brandOrganicToggle', + title: 'Promotes Own Business', + type: 'dropdown', + options: [ + { label: 'No', id: 'false' }, + { label: 'Yes', id: 'true' }, + ], + value: () => 'false', + description: "Disclose this post as promoting the creator's own business.", + mode: 'advanced', + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + { + id: 'musicUsageConsent', + title: 'TikTok Music Usage Confirmation', + type: 'dropdown', + options: [ + { + label: "I agree to TikTok's Music Usage Confirmation", + id: 'accepted', + }, + ], + description: + "By posting, you agree to TikTok's Music Usage Confirmation. TikTok requires explicit consent before content is uploaded.", + condition: { field: 'operation', value: 'tiktok_direct_post_video' }, + required: { field: 'operation', value: 'tiktok_direct_post_video' }, + }, + + // --- Get Post Status --- + { + id: 'publishId', + title: 'Publish ID', + type: 'short-input', + placeholder: 'v_pub_file~v2-1.123456789', + condition: { field: 'operation', value: 'tiktok_get_post_status' }, + required: { field: 'operation', value: 'tiktok_get_post_status' }, + }, + + ...getTrigger('tiktok_post_publish_complete').subBlocks, + ...getTrigger('tiktok_post_publish_failed').subBlocks, + ...getTrigger('tiktok_post_inbox_delivered').subBlocks, + ...getTrigger('tiktok_post_publicly_available').subBlocks, + ...getTrigger('tiktok_post_no_longer_public').subBlocks, + ...getTrigger('tiktok_video_publish_completed').subBlocks, + ...getTrigger('tiktok_video_upload_failed').subBlocks, + ...getTrigger('tiktok_authorization_removed').subBlocks, + ], + + triggers: { + enabled: true, + available: [ + 'tiktok_post_publish_complete', + 'tiktok_post_publish_failed', + 'tiktok_post_inbox_delivered', + 'tiktok_post_publicly_available', + 'tiktok_post_no_longer_public', + 'tiktok_video_publish_completed', + 'tiktok_video_upload_failed', + 'tiktok_authorization_removed', + ], + }, + + tools: { + access: [ + 'tiktok_get_user', + 'tiktok_list_videos', + 'tiktok_query_videos', + 'tiktok_query_creator_info', + 'tiktok_direct_post_video', + 'tiktok_upload_video_draft', + 'tiktok_get_post_status', + ], + config: { + tool: (params) => params.operation || 'tiktok_get_user', + params: (params) => { + const operation = params.operation || 'tiktok_get_user' + const toBoolean = (value: unknown): boolean | undefined => + value === undefined || value === '' ? undefined : String(value).toLowerCase() === 'true' + + switch (operation) { + case 'tiktok_get_user': + return { + ...(params.fields && { fields: params.fields }), + } + case 'tiktok_list_videos': + return { + ...(params.maxCount && { maxCount: Number(params.maxCount) }), + ...(params.cursor !== undefined && + params.cursor !== '' && { cursor: Number(params.cursor) }), + } + case 'tiktok_query_videos': + return { + videoIds: (params.videoIds || '') + .split(/[,\n]+/) + .map((id: string) => id.trim()) + .filter(Boolean), + } + case 'tiktok_query_creator_info': + return {} + case 'tiktok_direct_post_video': { + const file = normalizeFileInput(params.file, { single: true }) + return { + file, + title: params.title, + privacyLevel: params.privacyLevel, + disableDuet: toBoolean(params.disableDuet), + disableStitch: toBoolean(params.disableStitch), + disableComment: toBoolean(params.disableComment), + ...(params.videoCoverTimestampMs !== undefined && + params.videoCoverTimestampMs !== '' && { + videoCoverTimestampMs: Number(params.videoCoverTimestampMs), + }), + isAigc: toBoolean(params.isAigc), + brandContentToggle: toBoolean(params.brandContentToggle), + brandOrganicToggle: toBoolean(params.brandOrganicToggle), + musicUsageConsent: params.musicUsageConsent, + } + } + case 'tiktok_upload_video_draft': { + const file = normalizeFileInput(params.file, { single: true }) + return { + file, + } + } + case 'tiktok_get_post_status': + return { + publishId: params.publishId, + } + default: + return {} + } + }, + }, + }, + + inputs: { + operation: { type: 'string', description: 'Operation to perform' }, + oauthCredential: { type: 'string', description: 'TikTok account credential' }, + fields: { type: 'string', description: 'Comma-separated list of user fields to return' }, + maxCount: { type: 'number', description: 'Maximum number of videos to return (1-20)' }, + cursor: { type: 'number', description: 'Pagination cursor from previous response' }, + videoIds: { + type: 'string', + description: 'List of video IDs to query, one per line or comma-separated', + }, + file: { + type: 'json', + description: 'Video file to upload (uploaded file or reference from a previous block)', + }, + title: { type: 'string', description: 'Video caption or title' }, + privacyLevel: { type: 'string', description: 'Privacy level for the post' }, + disableComment: { type: 'string', description: 'Whether to disable comments' }, + disableDuet: { type: 'string', description: 'Whether to disable duet' }, + disableStitch: { type: 'string', description: 'Whether to disable stitch' }, + videoCoverTimestampMs: { type: 'number', description: 'Video cover timestamp in ms' }, + isAigc: { type: 'string', description: 'Whether the video is AI-generated content' }, + brandContentToggle: { + type: 'string', + description: 'Whether the post is a paid partnership promoting a third-party business', + }, + brandOrganicToggle: { + type: 'string', + description: "Whether the post promotes the creator's own business", + }, + musicUsageConsent: { + type: 'string', + description: "Explicit acceptance of TikTok's Music Usage Confirmation", + }, + publishId: { type: 'string', description: 'Publish ID to check status for' }, + }, + + outputs: { + // Get User Info + openId: { + type: 'string', + description: 'Unique TikTok user ID for this application', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + unionId: { + type: 'string', + description: 'Unique TikTok user ID across all apps from the developer', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + displayName: { + type: 'string', + description: 'User display name', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + bioDescription: { + type: 'string', + description: 'User bio description', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + profileDeepLink: { + type: 'string', + description: 'Deep link to the user TikTok profile', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + isVerified: { + type: 'boolean', + description: 'Whether the account is verified', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + username: { + type: 'string', + description: 'TikTok username', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + followerCount: { + type: 'number', + description: 'Number of followers', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + followingCount: { + type: 'number', + description: 'Number of accounts followed', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + likesCount: { + type: 'number', + description: 'Total likes received across all videos', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + videoCount: { + type: 'number', + description: 'Total number of public videos', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + avatarFile: { + type: 'file', + description: + 'Downloadable copy of the profile avatar image, stored as a workflow file so it can be chained into file-consuming blocks (e.g. attached to an email).', + condition: { field: 'operation', value: 'tiktok_get_user' }, + }, + + // List/Query Videos + videos: { + type: 'json', + description: + 'Array of video objects (id, title, coverImageUrl, embedLink, embedHtml, duration, createTime, shareUrl, videoDescription, width, height, viewCount, likeCount, commentCount, shareCount)', + condition: { field: 'operation', value: ['tiktok_list_videos', 'tiktok_query_videos'] }, + }, + cursor: { + type: 'number', + description: 'Pagination cursor for fetching the next page', + condition: { field: 'operation', value: 'tiktok_list_videos' }, + }, + hasMore: { + type: 'boolean', + description: 'Whether more videos are available', + condition: { field: 'operation', value: 'tiktok_list_videos' }, + }, + + // Query Creator Info + creatorAvatarUrl: { + type: 'string', + description: 'URL of the creator avatar', + condition: { field: 'operation', value: 'tiktok_query_creator_info' }, + }, + creatorUsername: { + type: 'string', + description: 'TikTok username of the creator', + condition: { field: 'operation', value: 'tiktok_query_creator_info' }, + }, + creatorNickname: { + type: 'string', + description: 'Display name/nickname of the creator', + condition: { field: 'operation', value: 'tiktok_query_creator_info' }, + }, + privacyLevelOptions: { + type: 'json', + description: 'Available privacy levels for posting (array of strings)', + condition: { field: 'operation', value: 'tiktok_query_creator_info' }, + }, + commentDisabled: { + type: 'boolean', + description: 'Whether the creator disabled comments by default', + condition: { field: 'operation', value: 'tiktok_query_creator_info' }, + }, + duetDisabled: { + type: 'boolean', + description: 'Whether the creator disabled duets by default', + condition: { field: 'operation', value: 'tiktok_query_creator_info' }, + }, + stitchDisabled: { + type: 'boolean', + description: 'Whether the creator disabled stitches by default', + condition: { field: 'operation', value: 'tiktok_query_creator_info' }, + }, + maxVideoPostDurationSec: { + type: 'number', + description: 'Maximum allowed video duration in seconds', + condition: { field: 'operation', value: 'tiktok_query_creator_info' }, + }, + + // Direct Post / Upload Draft + publishId: { + type: 'string', + description: 'Publish ID for tracking post status', + condition: { + field: 'operation', + value: VIDEO_POST_OPERATIONS, + }, + }, + + // Get Post Status + status: { + type: 'string', + description: + 'Post status: PROCESSING_UPLOAD/PROCESSING_DOWNLOAD, SEND_TO_USER_INBOX, PUBLISH_COMPLETE, or FAILED', + condition: { field: 'operation', value: 'tiktok_get_post_status' }, + }, + failReason: { + type: 'string', + description: 'Reason for failure if status is FAILED', + condition: { field: 'operation', value: 'tiktok_get_post_status' }, + }, + publiclyAvailablePostId: { + type: 'json', + description: 'Array of public post IDs once the content is published', + condition: { field: 'operation', value: 'tiktok_get_post_status' }, + }, + uploadedBytes: { + type: 'number', + description: 'Number of video bytes TikTok has received for a file upload', + condition: { field: 'operation', value: 'tiktok_get_post_status' }, + }, + downloadedBytes: { + type: 'number', + description: 'Number of video bytes TikTok reports as downloaded', + condition: { field: 'operation', value: 'tiktok_get_post_status' }, + }, + }, +} + +export const TikTokBlockMeta = { + tags: ['marketing', 'content-management'], + url: 'https://www.tiktok.com', + templates: [ + { + icon: TikTokIcon, + title: 'User-reviewed TikTok publisher', + prompt: + 'Build a workflow that prepares a generated video and editable AI-written caption, asks the user to review the video, choose TikTok privacy and interaction settings, and explicitly accept the Music Usage Confirmation before publishing, then checks post status until it completes.', + modules: ['agent', 'workflows'], + category: 'marketing', + tags: ['marketing', 'automation'], + }, + { + icon: TikTokIcon, + title: 'TikTok content calendar drafts', + prompt: + 'Create a scheduled workflow that reads the next due row from a Tables-based content calendar, uploads the matching video to the creator’s TikTok inbox as a draft, and notifies them to review and publish it in TikTok.', + modules: ['scheduled', 'tables', 'agent', 'workflows'], + category: 'marketing', + tags: ['marketing', 'automation'], + }, + { + icon: TikTokIcon, + title: 'TikTok performance reporter', + prompt: + 'Build a workflow that lists recent TikTok videos, summarizes view and engagement trends with an agent, and posts the digest to Slack every week.', + modules: ['scheduled', 'agent', 'workflows'], + category: 'marketing', + tags: ['marketing', 'reporting'], + alsoIntegrations: ['slack'], + }, + { + icon: TikTokIcon, + title: 'TikTok draft review pipeline', + prompt: + 'Create a workflow that uploads a new video to a TikTok inbox draft for review, then notifies the marketing team on Slack to approve and post it from the TikTok app.', + modules: ['agent', 'workflows'], + category: 'operations', + tags: ['marketing', 'automation'], + alsoIntegrations: ['slack'], + }, + ], + skills: [ + { + name: 'publish-video-to-tiktok', + description: + 'Guide a user-reviewed direct post to TikTok from an uploaded file or a previous block.', + content: + '# Publish a Video to TikTok\n\nGuide a user-reviewed post to a connected TikTok account.\n\n## Steps\n1. Run Query Creator Info immediately before posting to confirm the account, posting permissions, allowed privacy levels, interaction restrictions, and maximum duration.\n2. Show the user the video and an editable Title/Caption; never publish an unattended or unreviewed file.\n3. Have the user manually choose Privacy Level and each interaction setting from the currently allowed options.\n4. Require the user to accept the TikTok Music Usage Confirmation, then use Direct Post Video with the reviewed settings.\n5. Use Get Post Status with the returned Publish ID until the post completes or fails.\n\n## Output\nReturn the Publish ID and final status (PUBLISH_COMPLETE or FAILED with a reason).', + }, + { + name: 'send-video-draft-to-inbox', + description: "Send a video to the user's TikTok inbox for manual review before posting.", + content: + "# Send a TikTok Video Draft\n\nDeliver a video to the connected account's TikTok inbox so a human can review, edit, and publish it from the app.\n\n## Steps\n1. Use the Upload Video Draft operation with a connected TikTok Account.\n2. Provide a Video File: upload one, or reference a file from a previous block.\n3. Submit the draft — no caption or privacy level is set here, since the user finishes the post manually in the TikTok app.\n4. Use Get Post Status with the returned Publish ID to see when the user has acted on the inbox notification (SEND_TO_USER_INBOX until they do).\n\n## Output\nReturn the Publish ID so the draft's status can be tracked or referenced later.", + }, + { + name: 'check-tiktok-post-status', + description: 'Poll the status of a TikTok post or draft until it completes or fails.', + content: + '# Check TikTok Post Status\n\nTrack the outcome of a post or draft submitted with any TikTok publish operation.\n\n## Steps\n1. Capture the Publish ID returned by Direct Post Video or Upload Video Draft.\n2. Call Get Post Status with that Publish ID.\n3. Branch on the returned status: PROCESSING_UPLOAD/PROCESSING_DOWNLOAD means still in progress, SEND_TO_USER_INBOX means a draft is waiting on the user, PUBLISH_COMPLETE means it succeeded, and FAILED means it did not (read failReason for why).\n4. Repeat on a delay for in-progress statuses until a terminal state is reached.\n\n## Output\nReturn the final status, failReason (if any), and the publiclyAvailablePostId once published.', + }, + { + name: 'summarize-tiktok-video-performance', + description: "List a creator's recent TikTok videos and summarize engagement for reporting.", + content: + "# Summarize TikTok Video Performance\n\nPull a creator's recent videos and turn the metadata into a readable report.\n\n## Steps\n1. Use List Videos with a connected TikTok Account to fetch recent videos (paginate with the Cursor if more than one page is needed).\n2. For specific videos already known by ID, use Query Videos instead to refresh their metadata.\n3. Ask an agent to summarize the results — highlight top performers by duration/engagement signals available in the metadata and note any patterns.\n4. Optionally use Get User Info alongside this to report overall follower and like counts.\n\n## Output\nA structured summary or table of videos with their titles, share URLs, and key metadata, suitable for posting to a report or chat.", + }, + ], +} as const satisfies BlockMeta diff --git a/apps/sim/blocks/registry-maps.ts b/apps/sim/blocks/registry-maps.ts index 89919671fc9..f8c74a4e986 100644 --- a/apps/sim/blocks/registry-maps.ts +++ b/apps/sim/blocks/registry-maps.ts @@ -294,6 +294,7 @@ import { TemporalBlock, TemporalBlockMeta } from '@/blocks/blocks/temporal' import { TextractBlock, TextractBlockMeta, TextractV2Block } from '@/blocks/blocks/textract' import { ThinkingBlock } from '@/blocks/blocks/thinking' import { ThriveBlock, ThriveBlockMeta } from '@/blocks/blocks/thrive' +import { TikTokBlock, TikTokBlockMeta } from '@/blocks/blocks/tiktok' import { TinybirdBlock, TinybirdBlockMeta } from '@/blocks/blocks/tinybird' import { TranslateBlock } from '@/blocks/blocks/translate' import { TrelloBlock, TrelloBlockMeta } from '@/blocks/blocks/trello' @@ -604,6 +605,7 @@ export const BLOCK_REGISTRY: Record = { textract_v2: TextractV2Block, thinking: ThinkingBlock, thrive: ThriveBlock, + tiktok: TikTokBlock, tinybird: TinybirdBlock, translate: TranslateBlock, trello: TrelloBlock, @@ -871,6 +873,7 @@ export const BLOCK_META_REGISTRY: Record = { temporal: TemporalBlockMeta, textract: TextractBlockMeta, thrive: ThriveBlockMeta, + tiktok: TikTokBlockMeta, tinybird: TinybirdBlockMeta, trello: TrelloBlockMeta, trigger_dev: TriggerDevBlockMeta, diff --git a/apps/sim/components/icons.tsx b/apps/sim/components/icons.tsx index b8f8a650e65..f1fb7446eeb 100644 --- a/apps/sim/components/icons.tsx +++ b/apps/sim/components/icons.tsx @@ -1274,6 +1274,31 @@ export function xAIIcon(props: SVGProps) { ) } +export function TikTokIcon(props: SVGProps) { + return ( + + + + + + ) +} + export function xIcon(props: SVGProps) { return ( diff --git a/apps/sim/lib/api/contracts/tiktok-tools.test.ts b/apps/sim/lib/api/contracts/tiktok-tools.test.ts new file mode 100644 index 00000000000..ee24ba93bd3 --- /dev/null +++ b/apps/sim/lib/api/contracts/tiktok-tools.test.ts @@ -0,0 +1,86 @@ +import { describe, expect, it } from 'vitest' +import { tiktokPublishVideoBodySchema } from '@/lib/api/contracts/tiktok-tools' + +const file = { + key: 'workspace/test/video.mp4', + name: 'video.mp4', + size: 1024, + type: 'video/mp4', +} + +describe('tiktokPublishVideoBodySchema', () => { + it('accepts a draft without direct-post metadata', () => { + expect( + tiktokPublishVideoBodySchema.safeParse({ + accessToken: 'token', + mode: 'draft', + file, + }).success + ).toBe(true) + }) + + it('requires direct-post privacy and commercial-content disclosure', () => { + expect( + tiktokPublishVideoBodySchema.safeParse({ + accessToken: 'token', + mode: 'direct', + file, + postInfo: { privacy_level: 'SELF_ONLY' }, + }).success + ).toBe(false) + }) + + it('accepts documented direct-post metadata', () => { + expect( + tiktokPublishVideoBodySchema.safeParse({ + accessToken: 'token', + mode: 'direct', + file, + musicUsageConsent: 'accepted', + postInfo: { + title: 'A test video', + privacy_level: 'SELF_ONLY', + disable_duet: true, + disable_stitch: true, + disable_comment: true, + brand_content_toggle: false, + }, + }).success + ).toBe(true) + }) + + it('rejects direct posting without explicit music usage consent', () => { + expect( + tiktokPublishVideoBodySchema.safeParse({ + accessToken: 'token', + mode: 'direct', + file, + postInfo: { + privacy_level: 'SELF_ONLY', + disable_duet: true, + disable_stitch: true, + disable_comment: true, + brand_content_toggle: false, + }, + }).success + ).toBe(false) + }) + + it('rejects an undocumented privacy value', () => { + expect( + tiktokPublishVideoBodySchema.safeParse({ + accessToken: 'token', + mode: 'direct', + file, + musicUsageConsent: 'accepted', + postInfo: { + privacy_level: 'PRIVATE', + disable_duet: true, + disable_stitch: true, + disable_comment: true, + brand_content_toggle: false, + }, + }).success + ).toBe(false) + }) +}) diff --git a/apps/sim/lib/api/contracts/tiktok-tools.ts b/apps/sim/lib/api/contracts/tiktok-tools.ts new file mode 100644 index 00000000000..2023b29de6b --- /dev/null +++ b/apps/sim/lib/api/contracts/tiktok-tools.ts @@ -0,0 +1,56 @@ +import { z } from 'zod' +import { + type ContractBodyInput, + type ContractJsonResponse, + defineRouteContract, +} from '@/lib/api/contracts/types' +import { RawFileInputSchema } from '@/lib/uploads/utils/file-schemas' + +const tiktokPublishVideoPostInfoSchema = z.object({ + title: z.string().max(2200).optional(), + privacy_level: z.enum([ + 'PUBLIC_TO_EVERYONE', + 'MUTUAL_FOLLOW_FRIENDS', + 'FOLLOWER_OF_CREATOR', + 'SELF_ONLY', + ]), + disable_duet: z.boolean(), + disable_stitch: z.boolean(), + disable_comment: z.boolean(), + video_cover_timestamp_ms: z.number().int().nonnegative().optional(), + is_aigc: z.boolean().optional(), + brand_content_toggle: z.boolean(), + brand_organic_toggle: z.boolean().optional(), +}) + +const tiktokPublishVideoBaseSchema = z.object({ + accessToken: z.string().min(1, 'Access token is required'), + file: RawFileInputSchema, +}) + +export const tiktokPublishVideoBodySchema = z.discriminatedUnion('mode', [ + tiktokPublishVideoBaseSchema.extend({ + mode: z.literal('direct'), + postInfo: tiktokPublishVideoPostInfoSchema, + musicUsageConsent: z.literal('accepted'), + }), + tiktokPublishVideoBaseSchema.extend({ + mode: z.literal('draft'), + }), +]) + +export const tiktokPublishVideoResponseSchema = z.object({ + success: z.boolean(), + output: z.object({ publishId: z.string() }).optional(), + error: z.string().optional(), +}) + +export const tiktokPublishVideoContract = defineRouteContract({ + method: 'POST', + path: '/api/tools/tiktok/publish-video', + body: tiktokPublishVideoBodySchema, + response: { mode: 'json', schema: tiktokPublishVideoResponseSchema }, +}) + +export type TikTokPublishVideoBody = ContractBodyInput +export type TikTokPublishVideoResponse = ContractJsonResponse diff --git a/apps/sim/lib/api/contracts/webhooks.ts b/apps/sim/lib/api/contracts/webhooks.ts index 4903d558614..04c2d3ad5ba 100644 --- a/apps/sim/lib/api/contracts/webhooks.ts +++ b/apps/sim/lib/api/contracts/webhooks.ts @@ -258,3 +258,38 @@ export const webhookTriggerPostContract = defineRouteContract({ schema: z.unknown(), }, }) + +/** + * TikTok app-level webhook ingress. Signature is verified from the raw body + * before this schema runs; `content` remains a JSON string per TikTok docs. + */ +export const tiktokWebhookEnvelopeSchema = z.object({ + client_key: z.string().min(1).max(255), + event: z.string().min(1).max(255), + create_time: z.number().int().nonnegative(), + user_openid: z.string().min(1).max(255), + content: z.string().max(1_000_000), +}) + +export type TikTokWebhookEnvelope = z.input + +export const tiktokWebhookHeadersSchema = z.object({ + 'tiktok-signature': z.string().min(1), +}) + +export const tiktokWebhookResponseSchema = z.union([ + z.object({ ok: z.literal(true) }), + z.object({ error: z.string().min(1) }), +]) + +export const tiktokWebhookContract = defineRouteContract({ + method: 'POST', + path: '/api/webhooks/tiktok', + headers: tiktokWebhookHeadersSchema, + // Body is validated after HMAC verification against the raw payload. + body: tiktokWebhookEnvelopeSchema, + response: { + mode: 'json', + schema: tiktokWebhookResponseSchema, + }, +}) diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 96c86881aa9..d9d6443fdb4 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -1938,6 +1938,77 @@ export const auth = betterAuth({ }, }, + { + providerId: 'tiktok', + clientId: env.TIKTOK_CLIENT_ID as string, + clientSecret: env.TIKTOK_CLIENT_SECRET as string, + authorizationUrl: 'https://www.tiktok.com/v2/auth/authorize/', + tokenUrl: 'https://open.tiktokapis.com/v2/oauth/token/', + scopes: getCanonicalScopesForProvider('tiktok'), + responseType: 'code', + redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/tiktok`, + // TikTok requires the app identifier under `client_key`, not the standard + // `client_id`. The library always sends `client_id` too, but TikTok ignores + // unrecognized params, so adding `client_key` here (for both the authorize + // redirect and the token exchange) is sufficient without patching the library. + // + // TikTok also requires a comma-separated `scope` list, but the library always + // joins scopes with a space (no configurable separator in the generic-oauth + // plugin's authorize route). `additionalParams` is applied via + // `url.searchParams.set(key, value)` after the default scope is set, so + // overriding `scope` here replaces the space-joined value with a comma-joined + // one that TikTok can actually parse. + authorizationUrlParams: { + client_key: env.TIKTOK_CLIENT_ID as string, + scope: getCanonicalScopesForProvider('tiktok').join(','), + }, + tokenUrlParams: { client_key: env.TIKTOK_CLIENT_ID as string }, + getUserInfo: async (tokens) => { + try { + const response = await fetch( + 'https://open.tiktokapis.com/v2/user/info/?fields=open_id,display_name,avatar_url', + { + headers: { + Authorization: `Bearer ${tokens.accessToken}`, + }, + } + ) + + if (!response.ok) { + await response.text().catch(() => {}) + logger.error('Error fetching TikTok user info:', { + status: response.status, + statusText: response.statusText, + }) + return null + } + + const profile = await response.json() + const user = profile.data?.user + + if (!user?.open_id) { + logger.error('Invalid TikTok profile response:', profile) + return null + } + + const now = new Date() + + return { + id: `${user.open_id}-${generateId()}`, + name: user.display_name || 'TikTok User', + email: `${user.open_id}@tiktok.user`, + image: user.avatar_url || undefined, + emailVerified: false, + createdAt: now, + updatedAt: now, + } + } catch (error) { + logger.error('Error in TikTok getUserInfo:', { error }) + return null + } + }, + }, + { providerId: 'confluence', clientId: env.CONFLUENCE_CLIENT_ID as string, diff --git a/apps/sim/lib/core/async-jobs/backends/trigger-dev.ts b/apps/sim/lib/core/async-jobs/backends/trigger-dev.ts index 233be772e99..eecf53ff772 100644 --- a/apps/sim/lib/core/async-jobs/backends/trigger-dev.ts +++ b/apps/sim/lib/core/async-jobs/backends/trigger-dev.ts @@ -21,6 +21,7 @@ const JOB_TYPE_TO_TASK_ID: Record = { 'workflow-execution': 'workflow-execution', 'schedule-execution': 'schedule-execution', 'webhook-execution': 'webhook-execution', + 'tiktok-webhook-ingress': 'tiktok-webhook-ingress', 'resume-execution': 'resume-execution', 'workflow-group-cell': 'workflow-group-cell', 'cleanup-logs': 'cleanup-logs', diff --git a/apps/sim/lib/core/async-jobs/types.ts b/apps/sim/lib/core/async-jobs/types.ts index 3acb9927c25..95d6c83dbba 100644 --- a/apps/sim/lib/core/async-jobs/types.ts +++ b/apps/sim/lib/core/async-jobs/types.ts @@ -24,6 +24,7 @@ export type JobType = | 'workflow-execution' | 'schedule-execution' | 'webhook-execution' + | 'tiktok-webhook-ingress' | 'resume-execution' | 'workflow-group-cell' | 'cleanup-logs' diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 6cf92e243a3..d48b4396607 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -347,6 +347,8 @@ export const env = createEnv({ X_CLIENT_ID: z.string().optional(), // X (Twitter) OAuth client ID X_CLIENT_SECRET: z.string().optional(), // X (Twitter) OAuth client secret + TIKTOK_CLIENT_ID: z.string().optional(), // TikTok OAuth client key (TikTok calls this "client_key") + TIKTOK_CLIENT_SECRET: z.string().optional(), // TikTok OAuth client secret CONFLUENCE_CLIENT_ID: z.string().optional(), // Atlassian Confluence OAuth client ID CONFLUENCE_CLIENT_SECRET: z.string().optional(), // Atlassian Confluence OAuth client secret JIRA_CLIENT_ID: z.string().optional(), // Atlassian Jira OAuth client ID diff --git a/apps/sim/lib/integrations/integrations.json b/apps/sim/lib/integrations/integrations.json index 16dd3fc2a81..85d9aaf86b1 100644 --- a/apps/sim/lib/integrations/integrations.json +++ b/apps/sim/lib/integrations/integrations.json @@ -1,5 +1,5 @@ { - "updatedAt": "2026-07-09", + "updatedAt": "2026-07-10", "integrations": [ { "type": "onepassword", diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 8f6b17100fe..578271fe85b 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -43,6 +43,7 @@ import { ShopifyIcon, SlackIcon, SpotifyIcon, + TikTokIcon, TrelloIcon, VertexIcon, WealthboxIcon, @@ -427,6 +428,28 @@ export const OAUTH_PROVIDERS: Record = { }, defaultService: 'x', }, + tiktok: { + name: 'TikTok', + icon: TikTokIcon, + services: { + tiktok: { + name: 'TikTok', + description: 'Read profile info and videos, and publish content to TikTok.', + providerId: 'tiktok', + icon: TikTokIcon, + baseProviderIcon: TikTokIcon, + scopes: [ + 'user.info.basic', + 'user.info.profile', + 'user.info.stats', + 'video.publish', + 'video.upload', + 'video.list', + ], + }, + }, + defaultService: 'tiktok', + }, atlassian: { name: 'Atlassian', icon: JiraIcon, @@ -1057,6 +1080,11 @@ interface ProviderAuthConfig { * instead of the default application/x-www-form-urlencoded. Used by Notion. */ useJsonBody?: boolean + /** + * Body param name to use for the client identifier instead of the standard `client_id`. + * TikTok requires `client_key` instead. + */ + clientIdParamName?: string } /** @@ -1093,6 +1121,21 @@ function getProviderAuthConfig(provider: string): ProviderAuthConfig { supportsRefreshTokenRotation: true, } } + case 'tiktok': { + const { clientId, clientSecret } = getCredentials( + env.TIKTOK_CLIENT_ID, + env.TIKTOK_CLIENT_SECRET + ) + return { + tokenEndpoint: 'https://open.tiktokapis.com/v2/oauth/token/', + clientId, + clientSecret, + useBasicAuth: false, + supportsRefreshTokenRotation: true, + // TikTok requires `client_key` in the token request body instead of `client_id`. + clientIdParamName: 'client_key', + } + } case 'confluence': { const { clientId, clientSecret } = getCredentials( env.CONFLUENCE_CLIENT_ID, @@ -1455,7 +1498,7 @@ function buildAuthRequest( headers.Authorization = `Basic ${basicAuth}` } else { // Use body credentials - include client credentials in request body - bodyParams.client_id = config.clientId + bodyParams[config.clientIdParamName || 'client_id'] = config.clientId if (config.clientSecret) { bodyParams.client_secret = config.clientSecret } diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index bd1de917bf2..4e25f6ff1a2 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -29,6 +29,7 @@ export type OAuthProvider = | 'google-meet' | 'vertex-ai' | 'x' + | 'tiktok' | 'confluence' | 'airtable' | 'notion' @@ -81,6 +82,7 @@ export type OAuthService = | 'google-meet' | 'vertex-ai' | 'x' + | 'tiktok' | 'confluence' | 'airtable' | 'notion' diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index 6d903a6cd1c..1ca1655e1f3 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -122,6 +122,14 @@ export const SCOPE_DESCRIPTIONS: Record = { 'mute.write': 'Mute and unmute users', 'offline.access': 'Access account when not using the application', + // TikTok scopes + 'user.info.basic': "Read a user's profile info (open id, avatar, display name)", + 'user.info.profile': "Read a user's profile info (bio, verification status, username)", + 'user.info.stats': "Read a user's stats (follower, following, likes, and video counts)", + 'video.publish': "Directly post content to a user's TikTok profile", + 'video.upload': "Share content to a creator's account as a draft for further edit and post", + 'video.list': "Read a user's public TikTok videos", + // Airtable scopes 'data.records:read': 'Read records', 'data.records:write': 'Write to records', diff --git a/apps/sim/lib/webhooks/deploy.test.ts b/apps/sim/lib/webhooks/deploy.test.ts index f5c111ed68a..3958b74ab21 100644 --- a/apps/sim/lib/webhooks/deploy.test.ts +++ b/apps/sim/lib/webhooks/deploy.test.ts @@ -13,7 +13,7 @@ vi.mock('@/lib/webhooks/providers', () => ({ getProviderHandler: vi.fn() })) vi.mock('@/lib/webhooks/provider-subscriptions', () => ({ cleanupExternalWebhook: vi.fn(), createExternalWebhookSubscription: vi.fn(), - shouldRecreateExternalWebhookSubscription: vi.fn(), + hasWebhookConfigChanged: vi.fn(), })) vi.mock('@/lib/webhooks/utils.server', () => ({ findConflictingWebhookPathOwner: vi.fn(), @@ -29,7 +29,12 @@ const trigger = (subBlocks: Partial[]): { subBlocks: SubBlockCon }) const driveTrigger = trigger([ - { id: 'triggerCredentials', mode: 'trigger', canonicalParamId: 'oauthCredential' }, + { + id: 'triggerCredentials', + mode: 'trigger', + canonicalParamId: 'oauthCredential', + serviceId: 'google-drive', + }, { id: 'folderId', mode: 'trigger', canonicalParamId: 'folderId', required: false }, { id: 'manualFolderId', mode: 'trigger-advanced', canonicalParamId: 'folderId', required: false }, ]) @@ -63,6 +68,15 @@ describe('buildProviderConfig canonical collapse', () => { expect(providerConfig.folderId).toBe('BASIC') }) + it('returns the credential reference and canonical OAuth service for deploy validation', () => { + const block = makeBlock('google_drive_poller', { triggerCredentials: 'credential-1' }) + const result = buildProviderConfig(block, 'google_drive_poller', driveTrigger) + + expect(result.credentialReference).toBe('credential-1') + expect(result.credentialServiceId).toBe('google-drive') + expect(result.providerConfig.credentialId).toBeUndefined() + }) + it('writes the active (advanced) value under the canonical key when only advanced is set', () => { const block = makeBlock('google_drive_poller', { manualFolderId: 'ADVANCED' }) const { providerConfig } = buildProviderConfig(block, 'google_drive_poller', driveTrigger) diff --git a/apps/sim/lib/webhooks/deploy.ts b/apps/sim/lib/webhooks/deploy.ts index 93e21eb6b87..825adb147f7 100644 --- a/apps/sim/lib/webhooks/deploy.ts +++ b/apps/sim/lib/webhooks/deploy.ts @@ -1,14 +1,14 @@ import { db } from '@sim/db' -import { webhook, workflowDeploymentVersion } from '@sim/db/schema' +import { credential, webhook, workflowDeploymentVersion } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateShortId } from '@sim/utils/id' -import { and, eq, inArray, isNull } from 'drizzle-orm' +import { and, eq, inArray, isNull, or } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { PendingWebhookVerificationTracker } from '@/lib/webhooks/pending-verification' import { cleanupExternalWebhook, createExternalWebhookSubscription, - shouldRecreateExternalWebhookSubscription, + hasWebhookConfigChanged, } from '@/lib/webhooks/provider-subscriptions' import { getProviderHandler } from '@/lib/webhooks/providers' import { findConflictingWebhookPathOwner } from '@/lib/webhooks/utils.server' @@ -35,6 +35,14 @@ interface TriggerSaveResult { error?: TriggerSaveError } +interface BuiltProviderConfig { + providerConfig: Record + missingFields: string[] + credentialReference?: string + credentialServiceId?: string + triggerPath: string +} + export async function validateTriggerWebhookConfigForDeploy( blocks: Record ): Promise { @@ -198,12 +206,7 @@ export function buildProviderConfig( block: BlockState, triggerId: string, triggerDef: { subBlocks: SubBlockConfig[] } -): { - providerConfig: Record - missingFields: string[] - credentialId?: string - triggerPath: string -} { +): BuiltProviderConfig { const triggerConfigValue = getSubBlockValue(block, 'triggerConfig') const baseConfig = triggerConfigValue && typeof triggerConfigValue === 'object' @@ -282,11 +285,10 @@ export function buildProviderConfig( missingFields.push(credentialConfig.title || 'Credentials') } - let credentialId: string | undefined - if (typeof triggerCredentials === 'string' && triggerCredentials.length > 0) { - credentialId = triggerCredentials - providerConfig.credentialId = credentialId - } + const credentialReference = + typeof triggerCredentials === 'string' && triggerCredentials.length > 0 + ? triggerCredentials + : undefined providerConfig.triggerId = triggerId @@ -296,7 +298,38 @@ export function buildProviderConfig( ? triggerPathValue : block.id - return { providerConfig, missingFields, credentialId, triggerPath } + return { + providerConfig, + missingFields, + credentialReference, + credentialServiceId: credentialConfig?.serviceId, + triggerPath, + } +} + +/** + * Resolves a trigger credential reference to its canonical platform credential ID while enforcing + * that the credential belongs to the deployed workflow's workspace and OAuth service. + */ +async function resolveTriggerCredentialId( + credentialReference: string, + workspaceId: string, + serviceId: string +): Promise { + const [resolvedCredential] = await db + .select({ id: credential.id }) + .from(credential) + .where( + and( + eq(credential.workspaceId, workspaceId), + eq(credential.type, 'oauth'), + eq(credential.providerId, serviceId), + or(eq(credential.id, credentialReference), eq(credential.accountId, credentialReference)) + ) + ) + .limit(1) + + return resolvedCredential?.id ?? null } async function configurePollingIfNeeded( @@ -387,11 +420,8 @@ export async function saveTriggerWebhooksForDeploy({ const triggerDef = getTrigger(triggerId) const provider = triggerDef.provider - const { providerConfig, missingFields, triggerPath } = buildProviderConfig( - block, - triggerId, - triggerDef - ) + const { providerConfig, missingFields, credentialReference, credentialServiceId, triggerPath } = + buildProviderConfig(block, triggerId, triggerDef) if (missingFields.length > 0) { return { @@ -414,6 +444,38 @@ export async function saveTriggerWebhooksForDeploy({ } } + let credentialId: string | undefined + if (credentialReference && credentialServiceId) { + const workflowWorkspaceId = + typeof workflow.workspaceId === 'string' ? workflow.workspaceId : undefined + if (!workflowWorkspaceId) { + return { + success: false, + error: { + message: `Cannot validate credentials for ${triggerDef.name || triggerId} without a workflow workspace`, + status: 400, + }, + } + } + + credentialId = + (await resolveTriggerCredentialId( + credentialReference, + workflowWorkspaceId, + credentialServiceId + )) ?? undefined + if (!credentialId) { + return { + success: false, + error: { + message: `The selected ${credentialServiceId} credential is not available in this workspace`, + status: 400, + }, + } + } + providerConfig.credentialId = credentialId + } + const pathConflict = await findConflictingWebhookPathOwner({ path: triggerPath, workflowId, @@ -450,12 +512,8 @@ export async function saveTriggerWebhooksForDeploy({ const existingConfig = (existingWh.providerConfig as Record) || {} const needsRecreation = forceRecreateSubscriptions || - shouldRecreateExternalWebhookSubscription({ - previousProvider: existingWh.provider as string, - nextProvider: provider, - previousConfig: existingConfig, - nextConfig: providerConfig, - }) + existingWh.provider !== provider || + hasWebhookConfigChanged(existingConfig, providerConfig) if (needsRecreation) { webhooksToDelete.push(existingWh) diff --git a/apps/sim/lib/webhooks/processor.test.ts b/apps/sim/lib/webhooks/processor.test.ts index 1e823ed8af5..cbb7f5cdcc4 100644 --- a/apps/sim/lib/webhooks/processor.test.ts +++ b/apps/sim/lib/webhooks/processor.test.ts @@ -2,24 +2,28 @@ * @vitest-environment node */ +import type { webhook, workflow } from '@sim/db/schema' import { createMockRequest, envFlagsMock, executionPreprocessingMock, executionPreprocessingMockFns, } from '@sim/testing' +import type { NextRequest } from 'next/server' import { beforeEach, describe, expect, it, vi } from 'vitest' const { mockGenerateId, mockEnqueue, mockGetJobQueue, + mockProviderHandler, mockShouldExecuteInline, mockWebhookLookupResult, } = vi.hoisted(() => ({ mockGenerateId: vi.fn(), mockEnqueue: vi.fn(), mockGetJobQueue: vi.fn(), + mockProviderHandler: { current: {} as Record }, mockShouldExecuteInline: vi.fn(), mockWebhookLookupResult: { rows: [] as Array<{ webhook: any; workflow: any }> }, })) @@ -95,7 +99,7 @@ vi.mock('@/lib/webhooks/utils.server', () => ({ })) vi.mock('@/lib/webhooks/providers', () => ({ - getProviderHandler: vi.fn().mockReturnValue({}), + getProviderHandler: vi.fn(() => mockProviderHandler.current), })) vi.mock('@/background/webhook-execution', () => ({ @@ -122,11 +126,55 @@ vi.mock('@/triggers/jira/utils', () => ({ isJiraEventMatch: vi.fn().mockReturnValue(true), })) -import { - checkWebhookPreprocessing, - findAllWebhooksForPath, - queueWebhookExecution, -} from '@/lib/webhooks/processor' +import { dispatchResolvedWebhookTarget, findAllWebhooksForPath } from '@/lib/webhooks/processor' + +type WebhookRecord = typeof webhook.$inferSelect +type WorkflowRecord = typeof workflow.$inferSelect + +function makeWebhookRecord(overrides: Partial): WebhookRecord { + const now = new Date('2026-01-01T00:00:00.000Z') + return { + id: 'webhook-1', + workflowId: 'workflow-1', + deploymentVersionId: null, + blockId: null, + path: 'incoming/test', + provider: 'generic', + providerConfig: {}, + isActive: true, + failedCount: 0, + lastFailedAt: null, + archivedAt: null, + createdAt: now, + updatedAt: now, + ...overrides, + } +} + +function makeWorkflowRecord(overrides: Partial): WorkflowRecord { + const now = new Date('2026-01-01T00:00:00.000Z') + return { + id: 'workflow-1', + userId: 'owner-1', + workspaceId: 'workspace-1', + folderId: null, + sortOrder: 0, + name: 'Webhook workflow', + description: null, + lastSynced: now, + createdAt: now, + updatedAt: now, + isDeployed: true, + deployedAt: now, + isPublicApi: false, + locked: false, + runCount: 0, + lastRunAt: null, + variables: {}, + archivedAt: null, + ...overrides, + } +} describe('findAllWebhooksForPath cross-tenant collision', () => { beforeEach(() => { @@ -200,64 +248,38 @@ describe('webhook processor execution identity', () => { }) mockEnqueue.mockResolvedValue('job-1') mockGetJobQueue.mockResolvedValue({ enqueue: mockEnqueue }) + mockProviderHandler.current = {} mockShouldExecuteInline.mockReturnValue(false) mockGenerateId.mockReturnValue('generated-execution-id') }) it('reuses preprocessing execution identity when queueing a polling webhook', async () => { - const preprocessingResult = await checkWebhookPreprocessing( - { - id: 'workflow-1', - userId: 'owner-1', - workspaceId: 'workspace-1', - }, - { - id: 'webhook-1', - path: 'incoming/gmail', - provider: 'gmail', - }, - 'request-1' - ) - - expect(preprocessingResult).toMatchObject({ - error: null, - actorUserId: 'actor-user-1', + const expectedCorrelation = { executionId: 'generated-execution-id', - correlation: { - executionId: 'generated-execution-id', - requestId: 'request-1', - source: 'webhook', - workflowId: 'workflow-1', - webhookId: 'webhook-1', - path: 'incoming/gmail', - provider: 'gmail', - triggerType: 'webhook', - }, - }) - - await queueWebhookExecution( - { - id: 'webhook-1', + requestId: 'request-1', + source: 'webhook', + workflowId: 'workflow-1', + webhookId: 'webhook-1', + path: 'incoming/gmail', + provider: 'gmail', + triggerType: 'webhook', + } + + const result = await dispatchResolvedWebhookTarget( + makeWebhookRecord({ path: 'incoming/gmail', provider: 'gmail', - providerConfig: {}, - blockId: 'block-1', - }, - { - id: 'workflow-1', - workspaceId: 'workspace-1', - }, + }), + makeWorkflowRecord({}), { event: 'message.received' }, - createMockRequest('POST', { event: 'message.received' }) as any, + createMockRequest('POST', { event: 'message.received' }) as NextRequest, { requestId: 'request-1', path: 'incoming/gmail', - actorUserId: preprocessingResult.actorUserId, - executionId: preprocessingResult.executionId, - correlation: preprocessingResult.correlation, } ) + expect(result.outcome).toBe('queued') expect(mockGenerateId).toHaveBeenCalledTimes(1) expect(mockEnqueue).toHaveBeenCalledWith( 'webhook-execution', @@ -270,9 +292,41 @@ describe('webhook processor execution identity', () => { workflowId: 'workflow-1', workspaceId: 'workspace-1', userId: 'actor-user-1', - correlation: preprocessingResult.correlation, + correlation: expectedCorrelation, }), }) ) }) + + it('routes queue-mode providers through the durable job backend', async () => { + mockProviderHandler.current = { executionMode: 'queue' } + + const result = await dispatchResolvedWebhookTarget( + makeWebhookRecord({ + id: 'webhook-2', + path: 'tiktok', + provider: 'tiktok', + }), + makeWorkflowRecord({ + id: 'workflow-2', + workspaceId: 'workspace-2', + }), + { event: 'post.publish.complete' }, + createMockRequest('POST', { event: 'post.publish.complete' }) as NextRequest, + { + requestId: 'request-2', + } + ) + + expect(result.outcome).toBe('queued') + expect(result.response.status).toBe(200) + expect(mockEnqueue).toHaveBeenCalledWith( + 'webhook-execution', + expect.objectContaining({ + provider: 'tiktok', + workflowId: 'workflow-2', + }), + expect.any(Object) + ) + }) }) diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 80fb7b488f5..f674e816c64 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -22,6 +22,7 @@ import { requiresPendingWebhookVerification, } from '@/lib/webhooks/pending-verification' import { getProviderHandler } from '@/lib/webhooks/providers' +import type { WebhookProviderHandler } from '@/lib/webhooks/providers/types' import { blockExistsInDeployment } from '@/lib/workflows/persistence/utils' import { SIM_TRIGGER_PROVIDER } from '@/lib/workspace-events/constants' import { executeWebhookJob } from '@/background/webhook-execution' @@ -514,14 +515,51 @@ export async function checkWebhookPreprocessing( } } -export async function queueWebhookExecution( - foundWebhook: any, - foundWorkflow: any, - body: any, +export type WebhookDispatchOutcome = 'queued' | 'ignored' | 'failed' + +export interface WebhookDispatchResult { + outcome: WebhookDispatchOutcome + response: NextResponse + reason: + | 'queued' + | 'event-mismatch' + | 'filtered' + | 'preprocessing' + | 'block-missing' + | 'queue-failed' +} + +type ResolvedWebhookRecord = Omit & { + provider: string + providerConfig: Record +} + +function parseProviderConfig(value: unknown): Record { + return value !== null && typeof value === 'object' && !Array.isArray(value) + ? (value as Record) + : {} +} + +function getCredentialId(providerConfig: Record): string | undefined { + return typeof providerConfig.credentialId === 'string' ? providerConfig.credentialId : undefined +} + +function shouldUseDurableQueue(provider: string, handler: WebhookProviderHandler): boolean { + return ( + isPollingWebhookProvider(provider) || + provider === SIM_TRIGGER_PROVIDER || + handler.executionMode === 'queue' + ) +} + +async function queueWebhookExecutionWithResult( + foundWebhook: ResolvedWebhookRecord, + foundWorkflow: typeof workflow.$inferSelect, + body: unknown, request: NextRequest, options: WebhookProcessorOptions -): Promise { - const providerConfig = (foundWebhook.providerConfig as Record) || {} +): Promise { + const providerConfig = foundWebhook.providerConfig ?? {} const handler = getProviderHandler(foundWebhook.provider) try { @@ -536,11 +574,19 @@ export async function queueWebhookExecution( }) if (result !== true) { if (result instanceof NextResponse) { - return result + return { + outcome: result.ok ? 'ignored' : 'failed', + response: result, + reason: 'event-mismatch', + } + } + return { + outcome: 'ignored', + response: NextResponse.json({ + message: 'Event type does not match trigger configuration. Ignoring.', + }), + reason: 'event-mismatch', } - return NextResponse.json({ - message: 'Event type does not match trigger configuration. Ignoring.', - }) } } @@ -553,12 +599,19 @@ export async function queueWebhookExecution( ) } - const credentialId = providerConfig.credentialId as string | undefined + const credentialId = getCredentialId(providerConfig) const actorUserId = options.actorUserId if (!actorUserId) { logger.error(`[${options.requestId}] No actorUserId provided for webhook ${foundWebhook.id}`) - return NextResponse.json({ error: 'Unable to resolve billing account' }, { status: 500 }) + return { + outcome: 'failed', + response: NextResponse.json( + { error: 'Unable to resolve billing account' }, + { status: 500 } + ), + reason: 'queue-failed', + } } const executionId = options.executionId ?? generateId() @@ -574,6 +627,7 @@ export async function queueWebhookExecution( provider: foundWebhook.provider, triggerType: 'webhook', } satisfies AsyncExecutionCorrelation) + const workspaceId = foundWorkflow.workspaceId ?? undefined const payload = { webhookId: foundWebhook.id, @@ -586,8 +640,8 @@ export async function queueWebhookExecution( body, headers, path: options.path || foundWebhook.path, - blockId: foundWebhook.blockId, - workspaceId: foundWorkflow.workspaceId, + blockId: foundWebhook.blockId ?? undefined, + workspaceId, ...(credentialId ? { credentialId } : {}), ...(options.receivedAt !== undefined ? { webhookReceivedAt: options.receivedAt } : {}), ...(options.triggerTimestampMs !== undefined @@ -595,26 +649,26 @@ export async function queueWebhookExecution( : {}), } - const isPolling = isPollingWebhookProvider(payload.provider) + const shouldUseQueue = shouldUseDurableQueue(payload.provider, handler) - if (isPolling && !shouldExecuteInline()) { + if (shouldUseQueue && !shouldExecuteInline()) { const jobId = await (await getJobQueue()).enqueue('webhook-execution', payload, { metadata: { workflowId: foundWorkflow.id, - workspaceId: foundWorkflow.workspaceId, + workspaceId, userId: actorUserId, correlation, }, }) logger.info( - `[${options.requestId}] Queued polling webhook execution task ${jobId} for ${foundWebhook.provider} webhook via job queue` + `[${options.requestId}] Queued webhook execution task ${jobId} for ${foundWebhook.provider} webhook via job queue` ) } else { const jobQueue = await getInlineJobQueue() const jobId = await jobQueue.enqueue('webhook-execution', payload, { metadata: { workflowId: foundWorkflow.id, - workspaceId: foundWorkflow.workspaceId, + workspaceId, userId: actorUserId, correlation, }, @@ -659,20 +713,95 @@ export async function queueWebhookExecution( const successResponse = handler.formatSuccessResponse?.(providerConfig) ?? null if (successResponse) { - return successResponse + return { outcome: 'queued', response: successResponse, reason: 'queued' } } - return NextResponse.json({ message: 'Webhook processed' }) + return { + outcome: 'queued', + response: NextResponse.json({ message: 'Webhook processed' }), + reason: 'queued', + } } catch (error: unknown) { logger.error(`[${options.requestId}] Failed to queue webhook execution:`, error) const errorResponse = handler.formatQueueErrorResponse?.() ?? null if (errorResponse) { - return errorResponse + return { outcome: 'failed', response: errorResponse, reason: 'queue-failed' } + } + + return { + outcome: 'failed', + response: NextResponse.json({ error: 'Internal server error' }, { status: 500 }), + reason: 'queue-failed', + } + } +} + +/** + * Runs the common post-authentication lifecycle for a resolved webhook target and returns a typed + * outcome so app-level fanout workers do not infer queue state from HTTP response bodies. + */ +export async function dispatchResolvedWebhookTarget( + foundWebhook: typeof webhook.$inferSelect, + foundWorkflow: typeof workflow.$inferSelect, + body: unknown, + request: NextRequest, + options: WebhookProcessorOptions +): Promise { + if (!foundWebhook.provider) { + return { + outcome: 'failed', + response: NextResponse.json({ error: 'Webhook provider is missing' }, { status: 500 }), + reason: 'queue-failed', + } + } + + const webhookRecord = { + ...foundWebhook, + provider: foundWebhook.provider, + providerConfig: parseProviderConfig(foundWebhook.providerConfig), + } + const preprocessResult = await checkWebhookPreprocessing( + foundWorkflow, + webhookRecord, + options.requestId + ) + if (preprocessResult.error) { + return { + outcome: 'failed', + response: preprocessResult.error, + reason: 'preprocessing', + } + } + + if (webhookRecord.blockId) { + const blockExists = await blockExistsInDeployment(foundWorkflow.id, webhookRecord.blockId) + if (!blockExists) { + const verificationResponse = handlePreDeploymentVerification(webhookRecord, options.requestId) + return { + outcome: 'ignored', + response: + verificationResponse ?? + new NextResponse('Trigger block not found in deployment', { status: 404 }), + reason: 'block-missing', + } } + } - return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + if (shouldSkipWebhookEvent(webhookRecord, body, options.requestId)) { + return { + outcome: 'ignored', + response: NextResponse.json({ message: 'Webhook event ignored' }), + reason: 'filtered', + } } + + return queueWebhookExecutionWithResult(webhookRecord, foundWorkflow, body, request, { + ...options, + actorUserId: preprocessResult.actorUserId, + executionId: preprocessResult.executionId, + correlation: preprocessResult.correlation, + }) } export interface PolledWebhookEventResult { @@ -734,8 +863,8 @@ export async function processPolledWebhookEvent( } } - const providerConfig = (foundWebhook.providerConfig as Record) || {} - const credentialId = providerConfig.credentialId as string | undefined + const providerConfig = parseProviderConfig(foundWebhook.providerConfig) + const credentialId = getCredentialId(providerConfig) const actorUserId = preprocessResult.actorUserId if (!actorUserId) { @@ -774,8 +903,7 @@ export async function processPolledWebhookEvent( ...(credentialId ? { credentialId } : {}), } - const isQueueRoutedProvider = - isPollingWebhookProvider(payload.provider) || payload.provider === SIM_TRIGGER_PROVIDER + const isQueueRoutedProvider = shouldUseDurableQueue(provider, getProviderHandler(provider)) if (isQueueRoutedProvider && !shouldExecuteInline()) { const jobId = await (await getJobQueue()).enqueue('webhook-execution', payload, { metadata: { diff --git a/apps/sim/lib/webhooks/provider-subscriptions.ts b/apps/sim/lib/webhooks/provider-subscriptions.ts index 05c3eab64c6..ae8b160279e 100644 --- a/apps/sim/lib/webhooks/provider-subscriptions.ts +++ b/apps/sim/lib/webhooks/provider-subscriptions.ts @@ -17,7 +17,7 @@ type RecreateCheckInput = { nextConfig: Record } -/** System-managed fields that shouldn't trigger recreation */ +/** System-managed fields that should not trigger recreation. */ const SYSTEM_MANAGED_FIELDS = new Set([ 'externalId', 'externalSubscriptionId', @@ -32,6 +32,29 @@ const SYSTEM_MANAGED_FIELDS = new Set([ 'userId', ]) +/** Returns true when user-controlled persisted webhook configuration changed. */ +export function hasWebhookConfigChanged( + previousConfig: Record, + nextConfig: Record +): boolean { + const allKeys = new Set([...Object.keys(previousConfig), ...Object.keys(nextConfig)]) + + for (const key of allKeys) { + if (SYSTEM_MANAGED_FIELDS.has(key)) continue + + const previousValue = previousConfig[key] + const nextValue = nextConfig[key] + const previousComparable = + typeof previousValue === 'object' ? JSON.stringify(previousValue ?? null) : previousValue + const nextComparable = + typeof nextValue === 'object' ? JSON.stringify(nextValue ?? null) : nextValue + + if (previousComparable !== nextComparable) return true + } + + return false +} + /** * Determine whether a webhook with provider-managed registration should be * recreated after its persisted provider config changes. @@ -58,23 +81,7 @@ export function shouldRecreateExternalWebhookSubscription({ return false } - const allKeys = new Set([...Object.keys(previousConfig), ...Object.keys(nextConfig)]) - - for (const key of allKeys) { - if (SYSTEM_MANAGED_FIELDS.has(key)) continue - - const prevVal = previousConfig[key] - const nextVal = nextConfig[key] - - const prevStr = typeof prevVal === 'object' ? JSON.stringify(prevVal ?? null) : prevVal - const nextStr = typeof nextVal === 'object' ? JSON.stringify(nextVal ?? null) : nextVal - - if (prevStr !== nextStr) { - return true - } - } - - return false + return hasWebhookConfigChanged(previousConfig, nextConfig) } /** diff --git a/apps/sim/lib/webhooks/providers/index.ts b/apps/sim/lib/webhooks/providers/index.ts index a73a597c73b..59bc422f913 100644 --- a/apps/sim/lib/webhooks/providers/index.ts +++ b/apps/sim/lib/webhooks/providers/index.ts @@ -13,3 +13,9 @@ export function extractProviderIdentifierFromBody(provider: string, body: unknow const handler = getProviderHandler(provider) return handler.extractIdempotencyId?.(body) ?? null } + +/** Returns whether a provider accepts deliveries through the generic per-webhook path route. */ +export function acceptsPathWebhookDelivery(provider: string | null): boolean { + if (!provider) return true + return getProviderHandler(provider).ingressMode !== 'provider' +} diff --git a/apps/sim/lib/webhooks/providers/registry.ts b/apps/sim/lib/webhooks/providers/registry.ts index 1965a9663c7..dc0f2bffcc2 100644 --- a/apps/sim/lib/webhooks/providers/registry.ts +++ b/apps/sim/lib/webhooks/providers/registry.ts @@ -47,6 +47,7 @@ import { slackHandler } from '@/lib/webhooks/providers/slack' import { stripeHandler } from '@/lib/webhooks/providers/stripe' import { tableProviderHandler } from '@/lib/webhooks/providers/table' import { telegramHandler } from '@/lib/webhooks/providers/telegram' +import { tiktokHandler } from '@/lib/webhooks/providers/tiktok' import { twilioHandler } from '@/lib/webhooks/providers/twilio' import { twilioVoiceHandler } from '@/lib/webhooks/providers/twilio-voice' import { typeformHandler } from '@/lib/webhooks/providers/typeform' @@ -108,6 +109,7 @@ const PROVIDER_HANDLERS: Record = { stripe: stripeHandler, table: tableProviderHandler, telegram: telegramHandler, + tiktok: tiktokHandler, twilio: twilioHandler, twilio_voice: twilioVoiceHandler, typeform: typeformHandler, diff --git a/apps/sim/lib/webhooks/providers/tiktok-targets.test.ts b/apps/sim/lib/webhooks/providers/tiktok-targets.test.ts new file mode 100644 index 00000000000..e0ac0d04b84 --- /dev/null +++ b/apps/sim/lib/webhooks/providers/tiktok-targets.test.ts @@ -0,0 +1,119 @@ +/** + * @vitest-environment node + */ + +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockCredentialExpression, mockEq, mockSelect, queryRows } = vi.hoisted(() => ({ + mockCredentialExpression: vi.fn(() => 'webhook.credentialId'), + mockEq: vi.fn((left: unknown, right: unknown) => ({ left, right })), + mockSelect: vi.fn(), + queryRows: { + rows: [] as Array<{ + accountId: string + webhook: Record + workflow: Record + }>, + }, +})) + +vi.mock('@sim/db', () => { + const chain = { + from: vi.fn(() => chain), + innerJoin: vi.fn(() => chain), + leftJoin: vi.fn(() => chain), + where: vi.fn(() => Promise.resolve(queryRows.rows)), + } + mockSelect.mockImplementation(() => chain) + + return { + account: { + id: 'account.id', + accountId: 'account.accountId', + providerId: 'account.providerId', + }, + credential: { + id: 'credential.id', + accountId: 'credential.accountId', + providerId: 'credential.providerId', + type: 'credential.type', + workspaceId: 'credential.workspaceId', + }, + db: { select: mockSelect }, + webhookCredentialIdExpression: mockCredentialExpression, + webhook: { + deploymentVersionId: 'webhook.deploymentVersionId', + isActive: 'webhook.isActive', + archivedAt: 'webhook.archivedAt', + provider: 'webhook.provider', + providerConfig: 'webhook.providerConfig', + workflowId: 'webhook.workflowId', + }, + workflow: { + id: 'workflow.id', + workspaceId: 'workflow.workspaceId', + archivedAt: 'workflow.archivedAt', + }, + workflowDeploymentVersion: { + id: 'workflowDeploymentVersion.id', + workflowId: 'workflowDeploymentVersion.workflowId', + isActive: 'workflowDeploymentVersion.isActive', + }, + } +}) + +vi.mock('drizzle-orm', () => ({ + and: vi.fn((...conditions: unknown[]) => conditions), + eq: mockEq, + isNull: vi.fn((value: unknown) => ({ isNull: value })), + like: vi.fn((left: unknown, right: unknown) => ({ left, right })), + or: vi.fn((...conditions: unknown[]) => conditions), +})) + +import { findTikTokWebhookTargets } from '@/lib/webhooks/providers/tiktok-targets' + +describe('findTikTokWebhookTargets', () => { + beforeEach(() => { + vi.clearAllMocks() + queryRows.rows = [] + }) + + it('returns only rows whose stored account ID exactly matches user_openid', async () => { + queryRows.rows = [ + { + accountId: 'act.user-11111111-2222-3333-4444-555555555555', + webhook: { id: 'webhook-1' }, + workflow: { id: 'workflow-1' }, + }, + { + accountId: 'act.user-other-11111111-2222-3333-4444-555555555555', + webhook: { id: 'webhook-2' }, + workflow: { id: 'workflow-2' }, + }, + ] + + const targets = await findTikTokWebhookTargets('act.user', 'request-1') + + expect(targets).toEqual([ + { + webhook: { id: 'webhook-1' }, + workflow: { id: 'workflow-1' }, + }, + ]) + }) + + it('enforces provider and workspace bindings in the database query', async () => { + await findTikTokWebhookTargets('act.user', 'request-2') + + expect(mockEq).toHaveBeenCalledWith('credential.providerId', 'tiktok') + expect(mockEq).toHaveBeenCalledWith('webhook.provider', 'tiktok') + expect(mockEq).toHaveBeenCalledWith('workflow.workspaceId', 'credential.workspaceId') + expect(mockCredentialExpression).toHaveBeenCalledWith('webhook.providerConfig') + expect(mockEq).toHaveBeenCalledWith('webhook.credentialId', 'credential.id') + }) + + it('does not query for an empty user_openid', async () => { + expect(await findTikTokWebhookTargets('', 'request-3')).toEqual([]) + expect(mockSelect).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/lib/webhooks/providers/tiktok-targets.ts b/apps/sim/lib/webhooks/providers/tiktok-targets.ts new file mode 100644 index 00000000000..8fc4dc2aeaf --- /dev/null +++ b/apps/sim/lib/webhooks/providers/tiktok-targets.ts @@ -0,0 +1,102 @@ +import { + account, + credential, + db, + webhook, + webhookCredentialIdExpression, + workflow, + workflowDeploymentVersion, +} from '@sim/db' +import { createLogger } from '@sim/logger' +import { and, eq, isNull, like, or } from 'drizzle-orm' + +const logger = createLogger('TikTokWebhookTargets') +const ACCOUNT_ID_UUID_SUFFIX = /-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i + +export interface TikTokWebhookTarget { + webhook: typeof webhook.$inferSelect + workflow: typeof workflow.$inferSelect +} + +function escapeLikePattern(value: string): string { + return value.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_') +} + +function openIdFromAccountId(accountId: string): string { + return accountId.replace(ACCOUNT_ID_UUID_SUFFIX, '') +} + +/** + * Resolves a TikTok user_openid to active webhook targets through the credential ID persisted in + * providerConfig. The workflow-workspace equality prevents cross-tenant event routing. + */ +export async function findTikTokWebhookTargets( + userOpenId: string, + requestId: string +): Promise { + if (!userOpenId) return [] + + const rows = await db + .select({ + accountId: account.accountId, + webhook, + workflow, + }) + .from(account) + .innerJoin( + credential, + and( + eq(credential.accountId, account.id), + eq(credential.type, 'oauth'), + eq(credential.providerId, 'tiktok') + ) + ) + .innerJoin( + webhook, + and( + eq(webhookCredentialIdExpression(webhook.providerConfig), credential.id), + eq(webhook.provider, 'tiktok'), + eq(webhook.isActive, true), + isNull(webhook.archivedAt) + ) + ) + .innerJoin( + workflow, + and( + eq(workflow.id, webhook.workflowId), + eq(workflow.workspaceId, credential.workspaceId), + isNull(workflow.archivedAt) + ) + ) + .leftJoin( + workflowDeploymentVersion, + and( + eq(workflowDeploymentVersion.workflowId, workflow.id), + eq(workflowDeploymentVersion.isActive, true) + ) + ) + .where( + and( + eq(account.providerId, 'tiktok'), + like(account.accountId, `${escapeLikePattern(userOpenId)}-%`), + or( + eq(webhook.deploymentVersionId, workflowDeploymentVersion.id), + and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId)) + ) + ) + ) + + const targets = rows + .filter((row) => openIdFromAccountId(row.accountId) === userOpenId) + .map(({ webhook: webhookRecord, workflow: workflowRecord }) => ({ + webhook: webhookRecord, + workflow: workflowRecord, + })) + + logger.info(`[${requestId}] Resolved TikTok webhook targets`, { + userOpenIdPrefix: userOpenId.slice(0, 12), + webhookCount: targets.length, + }) + + return targets +} diff --git a/apps/sim/lib/webhooks/providers/tiktok.test.ts b/apps/sim/lib/webhooks/providers/tiktok.test.ts new file mode 100644 index 00000000000..3d40d8060e1 --- /dev/null +++ b/apps/sim/lib/webhooks/providers/tiktok.test.ts @@ -0,0 +1,298 @@ +import crypto from 'node:crypto' +import { NextRequest } from 'next/server' +import { describe, expect, it } from 'vitest' +import { + parseTikTokContent, + parseTikTokSignatureHeader, + tiktokHandler, + verifyTikTokSignature, +} from '@/lib/webhooks/providers/tiktok' +import { isTikTokEventMatch } from '@/triggers/tiktok/utils' + +function signTikTokBody(secret: string, timestamp: string, rawBody: string): string { + return crypto.createHmac('sha256', secret).update(`${timestamp}.${rawBody}`, 'utf8').digest('hex') +} + +function requestWithTikTokSignature(signatureHeader: string): NextRequest { + return new NextRequest('http://localhost/api/webhooks/tiktok', { + headers: { + 'TikTok-Signature': signatureHeader, + }, + }) +} + +describe('parseTikTokSignatureHeader', () => { + it('parses t and s from the header', () => { + expect( + parseTikTokSignatureHeader( + 't=1633174587,s=18494715036ac4416a1d0a673871a2edbcfc94d94bd88ccd2c5ec9b3425afe66' + ) + ).toEqual({ + timestamp: '1633174587', + signature: '18494715036ac4416a1d0a673871a2edbcfc94d94bd88ccd2c5ec9b3425afe66', + }) + }) + + it('returns null for missing or malformed headers', () => { + expect(parseTikTokSignatureHeader(null)).toBeNull() + expect(parseTikTokSignatureHeader('')).toBeNull() + expect(parseTikTokSignatureHeader('t=123')).toBeNull() + expect(parseTikTokSignatureHeader('s=abc')).toBeNull() + }) +}) + +describe('verifyTikTokSignature', () => { + const secret = 'tiktok-client-secret' + const rawBody = JSON.stringify({ + client_key: 'key', + event: 'post.publish.complete', + create_time: 1615338610, + user_openid: 'act.example', + content: '{"publish_id":"p1","publish_type":"DIRECT_POST"}', + }) + + it('accepts a valid signature within the skew window', () => { + const now = Math.floor(Date.now() / 1000) + const timestamp = String(now) + const signature = signTikTokBody(secret, timestamp, rawBody) + const result = verifyTikTokSignature( + rawBody, + `t=${timestamp},s=${signature}`, + 'tt-1', + secret, + now + ) + expect(result).toBeNull() + }) + + it('rejects an invalid signature', () => { + const now = Math.floor(Date.now() / 1000) + const result = verifyTikTokSignature( + rawBody, + `t=${now},s=${'0'.repeat(64)}`, + 'tt-2', + secret, + now + ) + expect(result?.status).toBe(401) + }) + + it('rejects when the client secret is missing', () => { + const now = Math.floor(Date.now() / 1000) + const result = verifyTikTokSignature(rawBody, `t=${now},s=abc`, 'tt-3', undefined, now) + expect(result?.status).toBe(401) + }) + + it('rejects a stale timestamp', () => { + const now = Math.floor(Date.now() / 1000) + const stale = String(now - 600) + const signature = signTikTokBody(secret, stale, rawBody) + const result = verifyTikTokSignature(rawBody, `t=${stale},s=${signature}`, 'tt-4', secret, now) + expect(result?.status).toBe(401) + }) + + it('rejects a missing signature header', () => { + const result = verifyTikTokSignature(rawBody, null, 'tt-5', secret) + expect(result?.status).toBe(401) + }) +}) + +describe('parseTikTokContent', () => { + it('parses a JSON string content field', () => { + expect(parseTikTokContent('{"publish_id":"p1","publish_type":"DIRECT_POST"}')).toEqual({ + publish_id: 'p1', + publish_type: 'DIRECT_POST', + }) + }) + + it('returns an empty object for invalid JSON', () => { + expect(parseTikTokContent('{not-json')).toEqual({}) + }) +}) + +describe('isTikTokEventMatch', () => { + it('matches documented event names including TikTok typo', () => { + expect(isTikTokEventMatch('tiktok_post_publish_complete', 'post.publish.complete')).toBe(true) + expect( + isTikTokEventMatch( + 'tiktok_post_no_longer_public', + 'post.publish.no_longer_publicaly_available' + ) + ).toBe(true) + expect(isTikTokEventMatch('tiktok_post_publish_complete', 'post.publish.failed')).toBe(false) + }) +}) + +describe('tiktokHandler', () => { + it('verifyAuth delegates to signature verification', async () => { + const secret = 'tiktok-client-secret' + const rawBody = '{"event":"authorization.removed"}' + const now = Math.floor(Date.now() / 1000) + const timestamp = String(now) + const signature = signTikTokBody(secret, timestamp, rawBody) + + // verifyAuth uses env.TIKTOK_CLIENT_SECRET; exercise via verifyTikTokSignature path above. + // Handler still requires a request with the header for the dedicated ingress. + const res = await tiktokHandler.verifyAuth!({ + request: requestWithTikTokSignature(`t=${timestamp},s=${signature}`), + rawBody, + requestId: 'tt-handler', + providerConfig: {}, + webhook: {}, + workflow: {}, + }) + // Without env secret this may 401; signature path is covered above. + expect(res === null || res.status === 401).toBe(true) + }) + + it('matchEvent filters by trigger id', async () => { + const match = await tiktokHandler.matchEvent!({ + body: { event: 'post.publish.complete' }, + request: new NextRequest('http://localhost'), + requestId: 'tt-match', + providerConfig: { triggerId: 'tiktok_post_publish_complete' }, + webhook: {}, + workflow: {}, + }) + expect(match).toBe(true) + + const skip = await tiktokHandler.matchEvent!({ + body: { event: 'post.publish.failed' }, + request: new NextRequest('http://localhost'), + requestId: 'tt-skip', + providerConfig: { triggerId: 'tiktok_post_publish_complete' }, + webhook: {}, + workflow: {}, + }) + expect(skip).toBe(false) + }) + + it('formatInput flattens envelope and content fields', async () => { + const { input } = await tiktokHandler.formatInput!({ + body: { + client_key: 'ck', + event: 'post.publish.failed', + create_time: 1615338610, + user_openid: 'act.user', + content: '{"publish_id":"pub-1","publish_type":"DIRECT_POST","reason":"spam_risk"}', + }, + webhook: {}, + workflow: { id: 'w1', userId: 'u1' }, + headers: {}, + requestId: 'tt-fmt', + }) + + expect(input).toEqual({ + event: 'post.publish.failed', + createTime: 1615338610, + userOpenId: 'act.user', + clientKey: 'ck', + publishId: 'pub-1', + publishType: 'DIRECT_POST', + failReason: 'spam_risk', + }) + }) + + it('formatInput maps authorization.removed reason', async () => { + const { input } = await tiktokHandler.formatInput!({ + body: { + client_key: 'ck', + event: 'authorization.removed', + create_time: 1615338610, + user_openid: 'act.user', + content: '{"reason": 1 }', + }, + webhook: {}, + workflow: { id: 'w1', userId: 'u1' }, + headers: {}, + requestId: 'tt-auth', + }) + + expect(input).toEqual({ + event: 'authorization.removed', + createTime: 1615338610, + userOpenId: 'act.user', + clientKey: 'ck', + reason: 1, + }) + }) + + it('formatInput emits only the selected event output shape with null optional values', async () => { + const { input } = await tiktokHandler.formatInput!({ + body: { + client_key: 'ck', + event: 'post.publish.publicly_available', + create_time: 1615338610, + user_openid: 'act.user', + content: '{"publish_id":"pub-1"}', + }, + webhook: {}, + workflow: { id: 'w1', userId: 'u1' }, + headers: {}, + requestId: 'tt-public', + }) + + expect(input).toEqual({ + event: 'post.publish.publicly_available', + createTime: 1615338610, + userOpenId: 'act.user', + clientKey: 'ck', + publishId: 'pub-1', + publishType: null, + postId: null, + }) + expect(input).not.toHaveProperty('shareId') + expect(input).not.toHaveProperty('failReason') + }) + + it('distinguishes multiple completed posts created from one publish_id', () => { + expect( + tiktokHandler.extractIdempotencyId!({ + event: 'post.publish.complete', + user_openid: 'act.user', + create_time: 1, + content: '{"publish_id":"pub-1"}', + }) + ).toBe('post.publish.complete:act.user:pub-1:1') + + expect( + tiktokHandler.extractIdempotencyId!({ + event: 'post.publish.complete', + user_openid: 'act.user', + create_time: 2, + content: '{"publish_id":"pub-1"}', + }) + ).toBe('post.publish.complete:act.user:pub-1:2') + }) + + it('uses post_id to distinguish public availability events for the same publish_id', () => { + expect( + tiktokHandler.extractIdempotencyId!({ + event: 'post.publish.publicly_available', + user_openid: 'act.user', + create_time: 3, + content: '{"publish_id":"pub-1","post_id":"post-1"}', + }) + ).toBe('post.publish.publicly_available:act.user:pub-1:post-1') + }) + + it('extractIdempotencyId falls back to share_id then create_time', () => { + expect( + tiktokHandler.extractIdempotencyId!({ + event: 'video.publish.completed', + user_openid: 'act.user', + create_time: 99, + content: '{"share_id":"share-1"}', + }) + ).toBe('video.publish.completed:act.user:share-1') + + expect( + tiktokHandler.extractIdempotencyId!({ + event: 'authorization.removed', + user_openid: 'act.user', + create_time: 42, + content: '{"reason":1}', + }) + ).toBe('authorization.removed:act.user:42') + }) +}) diff --git a/apps/sim/lib/webhooks/providers/tiktok.ts b/apps/sim/lib/webhooks/providers/tiktok.ts new file mode 100644 index 00000000000..0abedaa7e03 --- /dev/null +++ b/apps/sim/lib/webhooks/providers/tiktok.ts @@ -0,0 +1,245 @@ +import { createLogger } from '@sim/logger' +import { safeCompare } from '@sim/security/compare' +import { hmacSha256Hex } from '@sim/security/hmac' +import { NextResponse } from 'next/server' +import { env } from '@/lib/core/config/env' +import type { + AuthContext, + EventMatchContext, + FormatInputContext, + FormatInputResult, + WebhookProviderHandler, +} from '@/lib/webhooks/providers/types' + +const logger = createLogger('WebhookProvider:TikTok') + +/** TikTok recommends rejecting replayed signatures; 5 minutes matches Linear/common practice. */ +export const TIKTOK_WEBHOOK_TIMESTAMP_SKEW_SECONDS = 5 * 60 + +export interface TikTokSignatureParts { + timestamp: string + signature: string +} + +/** + * Parse `TikTok-Signature: t=,s=` (comma-separated prefix=value pairs). + */ +export function parseTikTokSignatureHeader(header: string | null): TikTokSignatureParts | null { + if (!header) return null + + let timestamp: string | undefined + let signature: string | undefined + + for (const part of header.split(',')) { + const trimmed = part.trim() + const eq = trimmed.indexOf('=') + if (eq <= 0) continue + const prefix = trimmed.slice(0, eq) + const value = trimmed.slice(eq + 1) + if (prefix === 't') timestamp = value + if (prefix === 's') signature = value + } + + if (!timestamp || !signature) return null + return { timestamp, signature } +} + +/** + * Verify TikTok webhook HMAC-SHA256 of `${t}.${rawBody}` with the app client secret. + * Returns null on success, or a 401 NextResponse on failure. + */ +export function verifyTikTokSignature( + rawBody: string, + signatureHeader: string | null, + requestId: string, + clientSecret: string | undefined = env.TIKTOK_CLIENT_SECRET, + nowSeconds: number = Math.floor(Date.now() / 1000) +): NextResponse | null { + if (!clientSecret) { + logger.warn(`[${requestId}] TikTok webhook missing TIKTOK_CLIENT_SECRET`) + return new NextResponse('Unauthorized - TikTok client secret not configured', { status: 401 }) + } + + const parts = parseTikTokSignatureHeader(signatureHeader) + if (!parts) { + logger.warn(`[${requestId}] TikTok webhook missing or malformed TikTok-Signature header`) + return new NextResponse('Unauthorized - Missing TikTok signature', { status: 401 }) + } + + const timestampSeconds = Number(parts.timestamp) + if (!Number.isFinite(timestampSeconds)) { + logger.warn(`[${requestId}] TikTok webhook signature timestamp is not a number`) + return new NextResponse('Unauthorized - Invalid TikTok signature timestamp', { status: 401 }) + } + + if (Math.abs(nowSeconds - timestampSeconds) > TIKTOK_WEBHOOK_TIMESTAMP_SKEW_SECONDS) { + logger.warn(`[${requestId}] TikTok webhook signature timestamp outside allowed skew`, { + skewSeconds: TIKTOK_WEBHOOK_TIMESTAMP_SKEW_SECONDS, + timestampSeconds, + nowSeconds, + }) + return new NextResponse('Unauthorized - TikTok signature timestamp skew too large', { + status: 401, + }) + } + + const signedPayload = `${parts.timestamp}.${rawBody}` + const computed = hmacSha256Hex(signedPayload, clientSecret) + if (!safeCompare(computed, parts.signature)) { + logger.warn(`[${requestId}] TikTok signature verification failed`) + return new NextResponse('Unauthorized - Invalid TikTok signature', { status: 401 }) + } + + return null +} + +function asRecord(value: unknown): Record | null { + if (!value || typeof value !== 'object' || Array.isArray(value)) return null + return value as Record +} + +/** + * Parse the TikTok envelope `content` field (a JSON string) into an object. + */ +export function parseTikTokContent(content: unknown): Record { + if (typeof content !== 'string' || content.length === 0) { + return asRecord(content) ?? {} + } + try { + return asRecord(JSON.parse(content)) ?? {} + } catch { + logger.warn('Failed to parse TikTok webhook content JSON string') + return {} + } +} + +function stringField(obj: Record, ...keys: string[]): string | undefined { + for (const key of keys) { + const value = obj[key] + if (typeof value === 'string' && value.length > 0) return value + if (typeof value === 'number' && Number.isFinite(value)) return String(value) + } + return undefined +} + +function numberField(value: unknown): number | null { + const number = typeof value === 'number' ? value : Number(value) + return Number.isFinite(number) ? number : null +} + +export const tiktokHandler: WebhookProviderHandler = { + ingressMode: 'provider', + executionMode: 'queue', + + async verifyAuth({ request, rawBody, requestId }: AuthContext): Promise { + return verifyTikTokSignature(rawBody, request.headers.get('TikTok-Signature'), requestId) + }, + + async matchEvent({ body, requestId, providerConfig }: EventMatchContext) { + const triggerId = + typeof providerConfig.triggerId === 'string' ? providerConfig.triggerId : undefined + if (!triggerId) return true + + const { isTikTokEventMatch } = await import('@/triggers/tiktok/utils') + const event = stringField(asRecord(body) ?? {}, 'event') + if (!isTikTokEventMatch(triggerId, event)) { + logger.debug( + `[${requestId}] TikTok event mismatch for trigger ${triggerId}. Event: ${event}. Skipping.` + ) + return false + } + return true + }, + + async formatInput({ body }: FormatInputContext): Promise { + const envelope = asRecord(body) ?? {} + const content = parseTikTokContent(envelope.content) + const event = typeof envelope.event === 'string' ? envelope.event : '' + const commonInput: Record = { + event, + createTime: numberField(envelope.create_time), + userOpenId: typeof envelope.user_openid === 'string' ? envelope.user_openid : null, + clientKey: typeof envelope.client_key === 'string' ? envelope.client_key : null, + } + const postingInput = { + ...commonInput, + publishId: stringField(content, 'publish_id') ?? null, + publishType: stringField(content, 'publish_type') ?? null, + } + + if (event === 'post.publish.failed') { + return { + input: { + ...postingInput, + failReason: stringField(content, 'fail_reason', 'reason') ?? null, + }, + } + } + + if (event === 'post.publish.complete' || event === 'post.publish.inbox_delivered') { + return { input: postingInput } + } + + if ( + event === 'post.publish.publicly_available' || + event === 'post.publish.no_longer_publicaly_available' + ) { + return { + input: { + ...postingInput, + postId: stringField(content, 'post_id') ?? null, + }, + } + } + + if (event === 'authorization.removed') { + return { + input: { + ...commonInput, + reason: numberField(content.reason), + }, + } + } + + if (event === 'video.publish.completed' || event === 'video.upload.failed') { + return { + input: { + ...commonInput, + shareId: stringField(content, 'share_id') ?? null, + }, + } + } + + return { input: commonInput } + }, + + extractIdempotencyId(body: unknown) { + const envelope = asRecord(body) + if (!envelope) return null + + const event = typeof envelope.event === 'string' ? envelope.event : null + const userOpenId = typeof envelope.user_openid === 'string' ? envelope.user_openid : null + if (!event || !userOpenId) return null + + const content = parseTikTokContent(envelope.content) + const publishId = stringField(content, 'publish_id') + const postId = stringField(content, 'post_id') + const shareId = stringField(content, 'share_id') + const createTime = + typeof envelope.create_time === 'number' || typeof envelope.create_time === 'string' + ? String(envelope.create_time) + : null + + let unique: string | null = null + if (publishId && postId) { + unique = `${publishId}:${postId}` + } else if (event === 'post.publish.complete' && publishId && createTime) { + unique = `${publishId}:${createTime}` + } else { + unique = publishId ?? shareId ?? createTime + } + if (!unique) return null + + return `${event}:${userOpenId}:${unique}` + }, +} diff --git a/apps/sim/lib/webhooks/providers/types.ts b/apps/sim/lib/webhooks/providers/types.ts index d25037117d9..b773b1e3fd1 100644 --- a/apps/sim/lib/webhooks/providers/types.ts +++ b/apps/sim/lib/webhooks/providers/types.ts @@ -89,6 +89,18 @@ export interface PollingConfigContext { * Each provider implements only the methods it needs — all methods are optional. */ export interface WebhookProviderHandler { + /** + * Restrict deliveries to a provider-owned ingress route instead of the generic per-webhook + * path route. Use when the provider sends all app events to one callback before target lookup. + */ + ingressMode?: 'path' | 'provider' + + /** + * Queue workflow execution through the configured durable backend instead of the low-latency + * in-process path. Use for providers whose ingress is acknowledged before target processing. + */ + executionMode?: 'inline' | 'queue' + /** Verify signature/auth. Return NextResponse(401/403) on failure, null on success. */ verifyAuth?(ctx: AuthContext): Promise | NextResponse | null diff --git a/apps/sim/proxy.ts b/apps/sim/proxy.ts index 741e30081db..5bc9afa6e7f 100644 --- a/apps/sim/proxy.ts +++ b/apps/sim/proxy.ts @@ -196,7 +196,10 @@ function handleInvitationRedirects( function handleSecurityFiltering(request: NextRequest): NextResponse | null { const userAgent = request.headers.get('user-agent') || '' const { pathname } = request.nextUrl - const isWebhookEndpoint = pathname.startsWith('/api/webhooks/trigger/') + const isWebhookEndpoint = + pathname.startsWith('/api/webhooks/trigger/') || + pathname.startsWith('/api/webhooks/tiktok') || + pathname.startsWith('/api/webhooks/agentmail') const isMcpEndpoint = pathname.startsWith('/api/mcp/') const isMcpOauthDiscoveryEndpoint = pathname.startsWith('/.well-known/oauth-authorization-server') || diff --git a/apps/sim/tools/jupyter/content-transforms.test.ts b/apps/sim/tools/jupyter/content-transforms.test.ts new file mode 100644 index 00000000000..22d99e69917 --- /dev/null +++ b/apps/sim/tools/jupyter/content-transforms.test.ts @@ -0,0 +1,119 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { jupyterCopyContentTool } from '@/tools/jupyter/copy_content' +import { jupyterCreateFileTool } from '@/tools/jupyter/create_file' +import { jupyterGetContentTool } from '@/tools/jupyter/get_content' +import { jupyterListContentsTool } from '@/tools/jupyter/list_contents' +import { jupyterRenameContentTool } from '@/tools/jupyter/rename_content' + +const AUTH = { + serverUrl: 'http://localhost:8888', + token: 'token', +} + +const CONTENT_MODEL = { + name: 'notes.txt', + path: 'docs/notes.txt', + type: 'file', + writable: true, + created: '2026-07-09T10:00:00Z', + last_modified: '2026-07-09T11:00:00Z', + size: 5, + mimetype: 'text/plain', + format: 'text', + content: 'hello', +} + +describe('Jupyter content transforms', () => { + it('preserves existing output shapes for valid Contents API models', async () => { + await expect( + jupyterCreateFileTool.transformResponse?.(Response.json(CONTENT_MODEL), { + ...AUTH, + path: CONTENT_MODEL.path, + type: 'file', + }) + ).resolves.toMatchObject({ + success: true, + output: { + name: CONTENT_MODEL.name, + path: CONTENT_MODEL.path, + type: 'file', + createdAt: CONTENT_MODEL.created, + lastModified: CONTENT_MODEL.last_modified, + }, + }) + + await expect( + jupyterCopyContentTool.transformResponse?.(Response.json(CONTENT_MODEL), { + ...AUTH, + path: CONTENT_MODEL.path, + copyFromPath: 'notes.txt', + }) + ).resolves.toMatchObject({ + success: true, + output: { + name: CONTENT_MODEL.name, + path: CONTENT_MODEL.path, + createdAt: CONTENT_MODEL.created, + }, + }) + + await expect( + jupyterRenameContentTool.transformResponse?.(Response.json(CONTENT_MODEL), { + ...AUTH, + path: 'notes.txt', + newPath: CONTENT_MODEL.path, + }) + ).resolves.toMatchObject({ + success: true, + output: { + name: CONTENT_MODEL.name, + path: CONTENT_MODEL.path, + lastModified: CONTENT_MODEL.last_modified, + }, + }) + + await expect( + jupyterListContentsTool.transformResponse?.( + Response.json({ ...CONTENT_MODEL, path: 'docs', content: [CONTENT_MODEL] }), + { ...AUTH, path: 'docs' } + ) + ).resolves.toMatchObject({ + success: true, + output: { + path: 'docs', + items: [ + { + name: CONTENT_MODEL.name, + path: CONTENT_MODEL.path, + type: 'file', + writable: true, + created: CONTENT_MODEL.created, + lastModified: CONTENT_MODEL.last_modified, + size: CONTENT_MODEL.size, + mimetype: CONTENT_MODEL.mimetype, + format: CONTENT_MODEL.format, + }, + ], + }, + }) + + await expect( + jupyterGetContentTool.transformResponse?.(Response.json(CONTENT_MODEL), { + ...AUTH, + path: CONTENT_MODEL.path, + }) + ).resolves.toMatchObject({ + success: true, + output: { + name: CONTENT_MODEL.name, + path: CONTENT_MODEL.path, + mimetype: CONTENT_MODEL.mimetype, + text: CONTENT_MODEL.content, + file: null, + }, + }) + }) +}) diff --git a/apps/sim/tools/jupyter/copy_content.ts b/apps/sim/tools/jupyter/copy_content.ts index 87abbdbd0d3..797aad0761d 100644 --- a/apps/sim/tools/jupyter/copy_content.ts +++ b/apps/sim/tools/jupyter/copy_content.ts @@ -1,5 +1,9 @@ import type { JupyterCopyContentParams, JupyterCopyContentResponse } from '@/tools/jupyter/types' -import { assertSafeJupyterPath, encodeJupyterPath } from '@/tools/jupyter/utils' +import { + assertSafeJupyterPath, + encodeJupyterPath, + parseJupyterContentModel, +} from '@/tools/jupyter/utils' import type { ToolConfig } from '@/tools/types' export const jupyterCopyContentTool: ToolConfig< @@ -61,14 +65,14 @@ export const jupyterCopyContentTool: ToolConfig< } } - const data = await response.json() + const data = parseJupyterContentModel(await response.json()) ?? {} return { success: true, output: { - name: (data.name as string | undefined) ?? '', - path: (data.path as string | undefined) ?? params?.path ?? '', - createdAt: (data.created as string | undefined) ?? null, + name: data.name ?? '', + path: data.path ?? params?.path ?? '', + createdAt: data.created ?? null, }, } }, diff --git a/apps/sim/tools/jupyter/create_file.ts b/apps/sim/tools/jupyter/create_file.ts index 5d59dfcff20..6eb45e5f1d8 100644 --- a/apps/sim/tools/jupyter/create_file.ts +++ b/apps/sim/tools/jupyter/create_file.ts @@ -1,5 +1,5 @@ import type { JupyterCreateFileParams, JupyterCreateFileResponse } from '@/tools/jupyter/types' -import { encodeJupyterPath } from '@/tools/jupyter/utils' +import { encodeJupyterPath, parseJupyterContentModel } from '@/tools/jupyter/utils' import type { ToolConfig } from '@/tools/types' const EMPTY_NOTEBOOK = { cells: [], metadata: {}, nbformat: 4, nbformat_minor: 5 } @@ -95,16 +95,16 @@ export const jupyterCreateFileTool: ToolConfig = @@ -58,10 +58,10 @@ export const jupyterGetContentTool: ToolConfig) => ({ - name: item.name, - path: item.path, - type: item.type, - writable: Boolean(item.writable), - created: (item.created as string | undefined) ?? null, - lastModified: (item.last_modified as string | undefined) ?? null, - size: (item.size as number | undefined) ?? null, - mimetype: (item.mimetype as string | undefined) ?? null, - format: (item.format as string | undefined) ?? null, - })), - path: (data.path as string | undefined) ?? params?.path ?? '', + items: items.map((item) => { + const content = parseJupyterContentModel(item) ?? {} + return { + name: content.name ?? '', + path: content.path ?? '', + type: content.type ?? 'file', + writable: content.writable ?? false, + created: content.created ?? null, + lastModified: content.lastModified ?? null, + size: content.size ?? null, + mimetype: content.mimetype ?? null, + format: content.format ?? null, + } + }), + path: data.path ?? params?.path ?? '', }, } }, diff --git a/apps/sim/tools/jupyter/rename_content.ts b/apps/sim/tools/jupyter/rename_content.ts index 2e32900b1e6..3f6598cb54c 100644 --- a/apps/sim/tools/jupyter/rename_content.ts +++ b/apps/sim/tools/jupyter/rename_content.ts @@ -2,7 +2,11 @@ import type { JupyterRenameContentParams, JupyterRenameContentResponse, } from '@/tools/jupyter/types' -import { assertSafeJupyterPath, encodeJupyterPath } from '@/tools/jupyter/utils' +import { + assertSafeJupyterPath, + encodeJupyterPath, + parseJupyterContentModel, +} from '@/tools/jupyter/utils' import type { ToolConfig } from '@/tools/types' export const jupyterRenameContentTool: ToolConfig< @@ -64,14 +68,14 @@ export const jupyterRenameContentTool: ToolConfig< } } - const data = await response.json() + const data = parseJupyterContentModel(await response.json()) ?? {} return { success: true, output: { - name: (data.name as string | undefined) ?? '', - path: (data.path as string | undefined) ?? params?.newPath ?? '', - lastModified: (data.last_modified as string | undefined) ?? null, + name: data.name ?? '', + path: data.path ?? params?.newPath ?? '', + lastModified: data.lastModified ?? null, }, } }, diff --git a/apps/sim/tools/jupyter/utils.test.ts b/apps/sim/tools/jupyter/utils.test.ts new file mode 100644 index 00000000000..037aa2b4e02 --- /dev/null +++ b/apps/sim/tools/jupyter/utils.test.ts @@ -0,0 +1,52 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { parseJupyterContentModel } from '@/tools/jupyter/utils' + +describe('parseJupyterContentModel', () => { + it('normalizes a Jupyter Contents API model without changing its values', () => { + const content = { cells: [] } + + expect( + parseJupyterContentModel({ + name: 'analysis.ipynb', + path: 'notebooks/analysis.ipynb', + type: 'notebook', + writable: true, + created: '2026-07-09T10:00:00Z', + last_modified: '2026-07-09T11:00:00Z', + size: 42, + mimetype: 'application/x-ipynb+json', + format: 'json', + content, + }) + ).toEqual({ + name: 'analysis.ipynb', + path: 'notebooks/analysis.ipynb', + type: 'notebook', + writable: true, + created: '2026-07-09T10:00:00Z', + lastModified: '2026-07-09T11:00:00Z', + size: 42, + mimetype: 'application/x-ipynb+json', + format: 'json', + content, + }) + }) + + it('rejects non-object models and omits fields with invalid types', () => { + expect(parseJupyterContentModel(null)).toBeNull() + expect( + parseJupyterContentModel({ + name: 42, + path: 'valid/path', + size: '42', + content: null, + }) + ).toEqual({ + path: 'valid/path', + content: null, + }) + }) +}) diff --git a/apps/sim/tools/jupyter/utils.ts b/apps/sim/tools/jupyter/utils.ts index 517ec545c95..30c14f3cf5d 100644 --- a/apps/sim/tools/jupyter/utils.ts +++ b/apps/sim/tools/jupyter/utils.ts @@ -1,3 +1,5 @@ +import { isPlainRecord } from '@sim/utils/object' + const PROTOCOL_PATTERN = /^https?:\/\//i /** @@ -131,6 +133,46 @@ export function assertSafeJupyterProxyPath(rawPath: string): void { assertNoJupyterPathTraversal(pathname) } +interface JupyterContentModel { + name?: string + path?: string + type?: 'directory' | 'file' | 'notebook' + writable?: boolean + created?: string + lastModified?: string + size?: number + mimetype?: string + format?: 'json' | 'text' | 'base64' + content?: unknown +} + +/** Parses the shared model returned by Jupyter's Contents API. */ +export function parseJupyterContentModel(value: unknown): JupyterContentModel | null { + if (!isPlainRecord(value)) return null + + const type = + value.type === 'directory' || value.type === 'file' || value.type === 'notebook' + ? value.type + : undefined + const format = + value.format === 'json' || value.format === 'text' || value.format === 'base64' + ? value.format + : undefined + + return { + ...(typeof value.name === 'string' ? { name: value.name } : {}), + ...(typeof value.path === 'string' ? { path: value.path } : {}), + ...(type ? { type } : {}), + ...(typeof value.writable === 'boolean' ? { writable: value.writable } : {}), + ...(typeof value.created === 'string' ? { created: value.created } : {}), + ...(typeof value.last_modified === 'string' ? { lastModified: value.last_modified } : {}), + ...(typeof value.size === 'number' ? { size: value.size } : {}), + ...(typeof value.mimetype === 'string' ? { mimetype: value.mimetype } : {}), + ...(format ? { format } : {}), + ...('content' in value ? { content: value.content } : {}), + } +} + interface RawJupyterKernel { id?: string name?: string diff --git a/apps/sim/tools/registry.ts b/apps/sim/tools/registry.ts index e9423a2837b..7f4a6c48674 100644 --- a/apps/sim/tools/registry.ts +++ b/apps/sim/tools/registry.ts @@ -4074,6 +4074,15 @@ import { thriveUpdateUserSkillsTool, thriveUpdateUserTool, } from '@/tools/thrive' +import { + tiktokDirectPostVideoTool, + tiktokGetPostStatusTool, + tiktokGetUserTool, + tiktokListVideosTool, + tiktokQueryCreatorInfoTool, + tiktokQueryVideosTool, + tiktokUploadVideoDraftTool, +} from '@/tools/tiktok' import { tinybirdAppendDatasourceTool, tinybirdDeleteDatasourceRowsTool, @@ -7189,6 +7198,13 @@ export const tools: Record = { thrive_remove_user_tags: thriveRemoveUserTagsTool, thrive_update_user_skills: thriveUpdateUserSkillsTool, thrive_get_skill_levels: thriveGetSkillLevelsTool, + tiktok_direct_post_video: tiktokDirectPostVideoTool, + tiktok_get_post_status: tiktokGetPostStatusTool, + tiktok_get_user: tiktokGetUserTool, + tiktok_list_videos: tiktokListVideosTool, + tiktok_query_creator_info: tiktokQueryCreatorInfoTool, + tiktok_query_videos: tiktokQueryVideosTool, + tiktok_upload_video_draft: tiktokUploadVideoDraftTool, tinybird_events: tinybirdEventsTool, tinybird_query: tinybirdQueryTool, tinybird_query_pipe: tinybirdQueryPipeTool, diff --git a/apps/sim/tools/tiktok/api-schemas.ts b/apps/sim/tools/tiktok/api-schemas.ts new file mode 100644 index 00000000000..82d1606c0f0 --- /dev/null +++ b/apps/sim/tools/tiktok/api-schemas.ts @@ -0,0 +1,77 @@ +import { z } from 'zod' + +/** Raw user object returned by TikTok's user info API. */ +export const tiktokApiUserSchema = z.object({ + open_id: z.string().optional(), + union_id: z.string().optional(), + avatar_url: z.string().optional(), + avatar_large_url: z.string().optional(), + display_name: z.string().optional(), + bio_description: z.string().optional(), + profile_deep_link: z.string().optional(), + is_verified: z.boolean().optional(), + username: z.string().optional(), + follower_count: z.number().optional(), + following_count: z.number().optional(), + likes_count: z.number().optional(), + video_count: z.number().optional(), +}) + +/** Data payload returned by TikTok's user info API. */ +export const tiktokGetUserApiDataSchema = z.object({ + user: tiktokApiUserSchema.optional(), +}) + +/** Raw video object returned by TikTok's video APIs. */ +export const tiktokApiVideoSchema = z.object({ + id: z.string().optional(), + title: z.string().optional(), + cover_image_url: z.string().optional(), + embed_link: z.string().optional(), + embed_html: z.string().optional(), + duration: z.number().optional(), + create_time: z.number().optional(), + share_url: z.string().optional(), + video_description: z.string().optional(), + width: z.number().optional(), + height: z.number().optional(), + view_count: z.number().optional(), + like_count: z.number().optional(), + comment_count: z.number().optional(), + share_count: z.number().optional(), +}) + +/** Data payload returned by TikTok's list/query video APIs. */ +export const tiktokVideosApiDataSchema = z.object({ + videos: z.array(tiktokApiVideoSchema).optional(), + cursor: z.number().optional(), + has_more: z.boolean().optional(), +}) + +/** Data payload returned by TikTok's creator info API. */ +export const tiktokCreatorInfoApiDataSchema = z.object({ + creator_avatar_url: z.string().optional(), + creator_username: z.string().optional(), + creator_nickname: z.string().optional(), + privacy_level_options: z.array(z.string()).optional(), + comment_disabled: z.boolean().optional(), + duet_disabled: z.boolean().optional(), + stitch_disabled: z.boolean().optional(), + max_video_post_duration_sec: z.number().optional(), +}) + +/** Data payload returned by TikTok's publish initialization APIs. */ +export const tiktokPublishInitApiDataSchema = z.object({ + publish_id: z.string().optional(), + upload_url: z.string().optional(), +}) + +/** Data payload returned by TikTok's post status API. */ +export const tiktokPostStatusApiDataSchema = z.object({ + status: z.string().optional(), + fail_reason: z.string().optional(), + uploaded_bytes: z.number().optional(), + downloaded_bytes: z.number().optional(), +}) + +export type TikTokApiVideo = z.infer diff --git a/apps/sim/tools/tiktok/direct_post_video.ts b/apps/sim/tools/tiktok/direct_post_video.ts new file mode 100644 index 00000000000..dccb1cdde50 --- /dev/null +++ b/apps/sim/tools/tiktok/direct_post_video.ts @@ -0,0 +1,151 @@ +import type { + TikTokDirectPostVideoParams, + TikTokDirectPostVideoResponse, +} from '@/tools/tiktok/types' +import { readTikTokPublishInitResponse, toTikTokPublishToolResponse } from '@/tools/tiktok/utils' +import type { ToolConfig } from '@/tools/types' + +function buildPostInfo(params: TikTokDirectPostVideoParams): Record { + const postInfo: Record = { + brand_content_toggle: params.brandContentToggle, + brand_organic_toggle: params.brandOrganicToggle ?? false, + privacy_level: params.privacyLevel, + disable_duet: params.disableDuet, + disable_stitch: params.disableStitch, + disable_comment: params.disableComment, + } + + if (params.title) postInfo.title = params.title + if (params.videoCoverTimestampMs !== undefined) { + postInfo.video_cover_timestamp_ms = params.videoCoverTimestampMs + } + if (params.isAigc !== undefined) postInfo.is_aigc = params.isAigc + return postInfo +} + +export const tiktokDirectPostVideoTool: ToolConfig< + TikTokDirectPostVideoParams, + TikTokDirectPostVideoResponse +> = { + id: 'tiktok_direct_post_video', + name: 'TikTok Direct Post Video', + description: + 'Publish a video to TikTok by uploading a file from the workflow. Rate limit: 6 requests per minute per user.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['video.publish'], + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'TikTok OAuth access token', + }, + file: { + type: 'file', + required: true, + visibility: 'user-only', + description: 'Video file to upload from the workflow. Maximum size: 250 MB.', + }, + title: { + type: 'string', + required: false, + visibility: 'user-or-llm', + description: 'Video caption/description. Maximum 2200 characters.', + }, + privacyLevel: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: + 'Privacy level for the video. Options: PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY. Must match one of the privacyLevelOptions returned by Query Creator Info. Note: unaudited apps (including sandbox apps) are restricted to SELF_ONLY.', + }, + disableDuet: { + type: 'boolean', + required: true, + visibility: 'user-or-llm', + description: 'Whether to disable duet for this video. The user must choose explicitly.', + }, + disableStitch: { + type: 'boolean', + required: true, + visibility: 'user-or-llm', + description: 'Whether to disable stitch for this video. The user must choose explicitly.', + }, + disableComment: { + type: 'boolean', + required: true, + visibility: 'user-or-llm', + description: 'Whether to disable comments for this video. The user must choose explicitly.', + }, + videoCoverTimestampMs: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Timestamp in milliseconds to use as the video cover image.', + }, + isAigc: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: 'Set to true if the video is AI-generated content (AIGC).', + }, + brandContentToggle: { + type: 'boolean', + required: true, + visibility: 'user-or-llm', + description: + 'Whether the video is a paid partnership promoting a third-party business. The user must choose explicitly. Branded content cannot be posted with Only Me privacy.', + }, + brandOrganicToggle: { + type: 'boolean', + required: false, + visibility: 'user-or-llm', + description: "Set to true if the video is promoting the creator's own business.", + }, + musicUsageConsent: { + type: 'string', + required: true, + visibility: 'user-only', + description: + "Must be 'accepted' after the user explicitly agrees to TikTok's Music Usage Confirmation.", + }, + }, + + request: { + url: () => '/api/tools/tiktok/publish-video', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params: TikTokDirectPostVideoParams) => { + const postInfo = buildPostInfo(params) + + return { + accessToken: params.accessToken, + mode: 'direct', + file: params.file, + postInfo, + musicUsageConsent: params.musicUsageConsent, + } + }, + }, + + transformResponse: async (response: Response): Promise => { + const result = await readTikTokPublishInitResponse(response) + return toTikTokPublishToolResponse(result) + }, + + outputs: { + publishId: { + type: 'string', + description: + 'Unique identifier for tracking the post status. Use this with the Get Post Status tool to check if the video was successfully published.', + }, + }, +} diff --git a/apps/sim/tools/tiktok/get_post_status.ts b/apps/sim/tools/tiktok/get_post_status.ts new file mode 100644 index 00000000000..5e18f2f3b6e --- /dev/null +++ b/apps/sim/tools/tiktok/get_post_status.ts @@ -0,0 +1,135 @@ +import { tiktokPostStatusApiDataSchema } from '@/tools/tiktok/api-schemas' +import type { TikTokGetPostStatusParams, TikTokGetPostStatusResponse } from '@/tools/tiktok/types' +import { readTikTokApiResponse } from '@/tools/tiktok/utils' +import type { ToolConfig } from '@/tools/types' + +function emptyPostStatusOutput(): TikTokGetPostStatusResponse['output'] { + return { + status: '', + failReason: null, + publiclyAvailablePostId: [], + uploadedBytes: null, + downloadedBytes: null, + } +} + +export const tiktokGetPostStatusTool: ToolConfig< + TikTokGetPostStatusParams, + TikTokGetPostStatusResponse +> = { + id: 'tiktok_get_post_status', + name: 'TikTok Get Post Status', + description: + 'Check the status of a post initiated with Direct Post Video or Upload Video Draft. Use the publishId returned from the post request to track progress.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['video.publish', 'video.upload'], + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'TikTok OAuth access token', + }, + publishId: { + type: 'string', + required: true, + visibility: 'user-or-llm', + description: 'The publish ID returned from a post/upload tool.', + }, + }, + + request: { + url: () => 'https://open.tiktokapis.com/v2/post/publish/status/fetch/', + method: 'POST', + headers: (params: TikTokGetPostStatusParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json; charset=UTF-8', + }), + body: (params: TikTokGetPostStatusParams) => { + const publishId = params.publishId.trim() + if (!publishId) throw new Error('publishId is required') + return { publish_id: publishId } + }, + }, + + transformResponse: async (response: Response): Promise => { + /** TikTok's int64 post IDs must be extracted before JSON parsing rounds them. */ + const { + data: statusData, + error, + rawBody, + } = await readTikTokApiResponse(response, tiktokPostStatusApiDataSchema) + const postIdsMatch = rawBody.match(/"publicaly_available_post_id"\s*:\s*\[([^\]]*)\]/) + const publiclyAvailablePostId = postIdsMatch + ? postIdsMatch[1] + .split(',') + .map((id: string) => id.trim().replace(/^"|"$/g, '')) + .filter(Boolean) + : [] + + if (error) { + return { + success: false, + output: emptyPostStatusOutput(), + error: error.message || 'Failed to fetch post status', + } + } + + if (!statusData) { + return { + success: false, + output: emptyPostStatusOutput(), + error: 'No status data returned', + } + } + + return { + success: true, + output: { + status: statusData.status ?? '', + failReason: statusData.fail_reason ?? null, + publiclyAvailablePostId, + uploadedBytes: statusData.uploaded_bytes ?? null, + downloadedBytes: statusData.downloaded_bytes ?? null, + }, + } + }, + + outputs: { + status: { + type: 'string', + description: + 'Current status of the post. Values: PROCESSING_UPLOAD/PROCESSING_DOWNLOAD (TikTok is processing the media), SEND_TO_USER_INBOX (draft delivered, awaiting user action), PUBLISH_COMPLETE (successfully posted), FAILED (check failReason).', + }, + failReason: { + type: 'string', + description: 'Reason for failure if status is FAILED. Null otherwise.', + optional: true, + }, + publiclyAvailablePostId: { + type: 'array', + description: + 'Array of public post IDs (as strings) once the content is published and publicly viewable. Can be used to construct the TikTok post URL.', + items: { + type: 'string', + description: 'Public TikTok post ID', + }, + }, + uploadedBytes: { + type: 'number', + description: 'Number of bytes uploaded to TikTok for FILE_UPLOAD posts', + optional: true, + }, + downloadedBytes: { + type: 'number', + description: 'Number of bytes TikTok reports as downloaded', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/tiktok/get_user.ts b/apps/sim/tools/tiktok/get_user.ts new file mode 100644 index 00000000000..384196a152d --- /dev/null +++ b/apps/sim/tools/tiktok/get_user.ts @@ -0,0 +1,173 @@ +import { tiktokGetUserApiDataSchema } from '@/tools/tiktok/api-schemas' +import type { TikTokGetUserParams, TikTokGetUserResponse } from '@/tools/tiktok/types' +import { readTikTokApiResponse, TIKTOK_USER_FIELDS } from '@/tools/tiktok/utils' +import type { ToolConfig } from '@/tools/types' + +function emptyUserOutput(): TikTokGetUserResponse['output'] { + return { + openId: '', + unionId: null, + displayName: '', + bioDescription: null, + profileDeepLink: null, + isVerified: null, + username: null, + followerCount: null, + followingCount: null, + likesCount: null, + videoCount: null, + } +} + +export const tiktokGetUserTool: ToolConfig = { + id: 'tiktok_get_user', + name: 'TikTok Get User', + description: + 'Get the authenticated TikTok user profile information including display name, avatar, bio, follower count, and video statistics.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['user.info.basic', 'user.info.profile', 'user.info.stats'], + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'TikTok OAuth access token', + }, + fields: { + type: 'string', + required: false, + visibility: 'user-or-llm', + default: TIKTOK_USER_FIELDS, + description: + 'Comma-separated list of fields to return. Available: open_id, union_id, avatar_url, avatar_large_url, display_name, bio_description, profile_deep_link, is_verified, username, follower_count, following_count, likes_count, video_count. Include avatar_url and avatar_large_url to receive the avatarFile output.', + }, + }, + + request: { + url: (params: TikTokGetUserParams) => { + const fields = params.fields || TIKTOK_USER_FIELDS + return `https://open.tiktokapis.com/v2/user/info/?fields=${encodeURIComponent(fields)}` + }, + method: 'GET', + headers: (params: TikTokGetUserParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + }), + }, + + transformResponse: async (response: Response): Promise => { + const { data, error } = await readTikTokApiResponse(response, tiktokGetUserApiDataSchema) + + if (error) { + return { + success: false, + output: emptyUserOutput(), + error: error.message || 'Failed to fetch user info', + } + } + + const user = data?.user + + if (!user) { + return { + success: false, + output: emptyUserOutput(), + error: 'No user data returned', + } + } + + const avatarSourceUrl = user.avatar_large_url ?? user.avatar_url + const avatarFileName = `${user.username || user.open_id || 'tiktok-user'}-avatar.jpg` + + return { + success: true, + output: { + openId: user.open_id ?? '', + unionId: user.union_id ?? null, + displayName: user.display_name ?? '', + bioDescription: user.bio_description ?? null, + profileDeepLink: user.profile_deep_link ?? null, + isVerified: user.is_verified ?? null, + username: user.username ?? null, + followerCount: user.follower_count ?? null, + followingCount: user.following_count ?? null, + likesCount: user.likes_count ?? null, + videoCount: user.video_count ?? null, + ...(avatarSourceUrl && { + avatarFile: { + name: avatarFileName, + mimeType: 'image/jpeg', + url: avatarSourceUrl, + }, + }), + }, + } + }, + + outputs: { + openId: { + type: 'string', + description: 'Unique TikTok user ID for this application', + }, + unionId: { + type: 'string', + description: 'Unique TikTok user ID across all apps from the same developer', + optional: true, + }, + displayName: { + type: 'string', + description: 'User display name', + }, + bioDescription: { + type: 'string', + description: 'User bio description', + optional: true, + }, + profileDeepLink: { + type: 'string', + description: 'Deep link to user TikTok profile', + optional: true, + }, + isVerified: { + type: 'boolean', + description: 'Whether the account is verified', + optional: true, + }, + username: { + type: 'string', + description: 'TikTok username', + optional: true, + }, + followerCount: { + type: 'number', + description: 'Number of followers', + optional: true, + }, + followingCount: { + type: 'number', + description: 'Number of accounts the user follows', + optional: true, + }, + likesCount: { + type: 'number', + description: 'Total likes received across all videos', + optional: true, + }, + videoCount: { + type: 'number', + description: 'Total number of public videos', + optional: true, + }, + avatarFile: { + type: 'file', + description: + 'Downloadable copy of the profile avatar image (largest available variant), stored as a workflow file so it can be chained into file-consuming blocks (e.g. attached to an email).', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/tiktok/index.ts b/apps/sim/tools/tiktok/index.ts new file mode 100644 index 00000000000..d20db2ae69a --- /dev/null +++ b/apps/sim/tools/tiktok/index.ts @@ -0,0 +1,17 @@ +import { tiktokDirectPostVideoTool } from '@/tools/tiktok/direct_post_video' +import { tiktokGetPostStatusTool } from '@/tools/tiktok/get_post_status' +import { tiktokGetUserTool } from '@/tools/tiktok/get_user' +import { tiktokListVideosTool } from '@/tools/tiktok/list_videos' +import { tiktokQueryCreatorInfoTool } from '@/tools/tiktok/query_creator_info' +import { tiktokQueryVideosTool } from '@/tools/tiktok/query_videos' +import { tiktokUploadVideoDraftTool } from '@/tools/tiktok/upload_video_draft' + +export * from '@/tools/tiktok/types' + +export { tiktokGetUserTool } +export { tiktokListVideosTool } +export { tiktokQueryVideosTool } +export { tiktokQueryCreatorInfoTool } +export { tiktokDirectPostVideoTool } +export { tiktokUploadVideoDraftTool } +export { tiktokGetPostStatusTool } diff --git a/apps/sim/tools/tiktok/list_videos.ts b/apps/sim/tools/tiktok/list_videos.ts new file mode 100644 index 00000000000..14ba8ed8ecf --- /dev/null +++ b/apps/sim/tools/tiktok/list_videos.ts @@ -0,0 +1,114 @@ +import { tiktokVideosApiDataSchema } from '@/tools/tiktok/api-schemas' +import { + TIKTOK_VIDEO_OUTPUT_PROPERTIES, + type TikTokListVideosParams, + type TikTokListVideosResponse, + type TikTokVideo, +} from '@/tools/tiktok/types' +import { mapTikTokVideo, readTikTokApiResponse, TIKTOK_VIDEO_FIELDS } from '@/tools/tiktok/utils' +import type { ToolConfig } from '@/tools/types' + +export const tiktokListVideosTool: ToolConfig = { + id: 'tiktok_list_videos', + name: 'TikTok List Videos', + description: + "Get a list of the authenticated user's TikTok videos with cover images, titles, and metadata. Supports pagination.", + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['video.list'], + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'TikTok OAuth access token', + }, + maxCount: { + type: 'number', + required: false, + visibility: 'user-or-llm', + default: 10, + description: 'Maximum number of videos to return (1-20)', + }, + cursor: { + type: 'number', + required: false, + visibility: 'user-or-llm', + description: 'Cursor for pagination (from previous response)', + }, + }, + + request: { + url: () => `https://open.tiktokapis.com/v2/video/list/?fields=${TIKTOK_VIDEO_FIELDS}`, + method: 'POST', + headers: (params: TikTokListVideosParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params: TikTokListVideosParams) => { + const maxCount = params.maxCount ?? 10 + if (!Number.isInteger(maxCount) || maxCount < 1 || maxCount > 20) { + throw new Error('maxCount must be an integer between 1 and 20') + } + if (params.cursor !== undefined && (!Number.isInteger(params.cursor) || params.cursor < 0)) { + throw new Error('cursor must be a non-negative integer') + } + return { + max_count: maxCount, + ...(params.cursor !== undefined && { cursor: params.cursor }), + } + }, + }, + + transformResponse: async (response: Response): Promise => { + const { data, error } = await readTikTokApiResponse(response, tiktokVideosApiDataSchema) + + if (error) { + return { + success: false, + output: { + videos: [], + cursor: null, + hasMore: false, + }, + error: error.message || 'Failed to fetch videos', + } + } + + const videos: TikTokVideo[] = (data?.videos ?? []).map(mapTikTokVideo) + + return { + success: true, + output: { + videos, + cursor: data?.cursor ?? null, + hasMore: data?.has_more ?? false, + }, + } + }, + + outputs: { + videos: { + type: 'array', + description: 'List of TikTok videos', + items: { + type: 'object', + properties: TIKTOK_VIDEO_OUTPUT_PROPERTIES, + }, + }, + cursor: { + type: 'number', + description: 'Cursor for fetching the next page of results', + optional: true, + }, + hasMore: { + type: 'boolean', + description: 'Whether there are more videos to fetch', + }, + }, +} diff --git a/apps/sim/tools/tiktok/query_creator_info.ts b/apps/sim/tools/tiktok/query_creator_info.ts new file mode 100644 index 00000000000..f39f767daa4 --- /dev/null +++ b/apps/sim/tools/tiktok/query_creator_info.ts @@ -0,0 +1,133 @@ +import { tiktokCreatorInfoApiDataSchema } from '@/tools/tiktok/api-schemas' +import type { + TikTokQueryCreatorInfoParams, + TikTokQueryCreatorInfoResponse, +} from '@/tools/tiktok/types' +import { readTikTokApiResponse } from '@/tools/tiktok/utils' +import type { ToolConfig } from '@/tools/types' + +function emptyCreatorInfoOutput(): TikTokQueryCreatorInfoResponse['output'] { + return { + creatorAvatarUrl: null, + creatorUsername: null, + creatorNickname: null, + privacyLevelOptions: [], + commentDisabled: false, + duetDisabled: false, + stitchDisabled: false, + maxVideoPostDurationSec: null, + } +} + +export const tiktokQueryCreatorInfoTool: ToolConfig< + TikTokQueryCreatorInfoParams, + TikTokQueryCreatorInfoResponse +> = { + id: 'tiktok_query_creator_info', + name: 'TikTok Query Creator Info', + description: + 'Check if the authenticated TikTok user can post content and retrieve their available privacy options, interaction settings, and maximum video duration. Required before any post per TikTok UX guidelines.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['video.publish'], + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'TikTok OAuth access token', + }, + }, + + request: { + url: () => 'https://open.tiktokapis.com/v2/post/publish/creator_info/query/', + method: 'POST', + headers: (params: TikTokQueryCreatorInfoParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json; charset=UTF-8', + }), + body: () => ({}), + }, + + transformResponse: async (response: Response): Promise => { + const { data: creatorInfo, error } = await readTikTokApiResponse( + response, + tiktokCreatorInfoApiDataSchema + ) + + if (error) { + return { + success: false, + output: emptyCreatorInfoOutput(), + error: error.message || 'Failed to query creator info', + } + } + + if (!creatorInfo) { + return { + success: false, + output: emptyCreatorInfoOutput(), + error: 'No creator info returned', + } + } + + return { + success: true, + output: { + creatorAvatarUrl: creatorInfo.creator_avatar_url ?? null, + creatorUsername: creatorInfo.creator_username ?? null, + creatorNickname: creatorInfo.creator_nickname ?? null, + privacyLevelOptions: creatorInfo.privacy_level_options ?? [], + commentDisabled: creatorInfo.comment_disabled ?? false, + duetDisabled: creatorInfo.duet_disabled ?? false, + stitchDisabled: creatorInfo.stitch_disabled ?? false, + maxVideoPostDurationSec: creatorInfo.max_video_post_duration_sec ?? null, + }, + } + }, + + outputs: { + creatorAvatarUrl: { + type: 'string', + description: 'URL of the creator avatar', + optional: true, + }, + creatorUsername: { + type: 'string', + description: 'TikTok username of the creator', + optional: true, + }, + creatorNickname: { + type: 'string', + description: 'Display name/nickname of the creator', + optional: true, + }, + privacyLevelOptions: { + type: 'array', + description: + 'Available privacy levels for posting (e.g., PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY)', + }, + commentDisabled: { + type: 'boolean', + description: 'Whether the creator has disabled comments by default', + }, + duetDisabled: { + type: 'boolean', + description: 'Whether the creator has disabled duets by default', + }, + stitchDisabled: { + type: 'boolean', + description: 'Whether the creator has disabled stitches by default', + }, + maxVideoPostDurationSec: { + type: 'number', + description: 'Maximum allowed video duration in seconds', + optional: true, + }, + }, +} diff --git a/apps/sim/tools/tiktok/query_videos.ts b/apps/sim/tools/tiktok/query_videos.ts new file mode 100644 index 00000000000..b9e89c2a64f --- /dev/null +++ b/apps/sim/tools/tiktok/query_videos.ts @@ -0,0 +1,100 @@ +import { tiktokVideosApiDataSchema } from '@/tools/tiktok/api-schemas' +import { + TIKTOK_VIDEO_OUTPUT_PROPERTIES, + type TikTokQueryVideosParams, + type TikTokQueryVideosResponse, + type TikTokVideo, +} from '@/tools/tiktok/types' +import { + assertTikTokArrayLength, + mapTikTokVideo, + readTikTokApiResponse, + TIKTOK_VIDEO_FIELDS, +} from '@/tools/tiktok/utils' +import type { ToolConfig } from '@/tools/types' + +export const tiktokQueryVideosTool: ToolConfig = + { + id: 'tiktok_query_videos', + name: 'TikTok Query Videos', + description: + 'Query specific TikTok videos by their IDs to get fresh metadata including cover images, embed links, and video details.', + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['video.list'], + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'TikTok OAuth access token', + }, + videoIds: { + type: 'array', + required: true, + visibility: 'user-or-llm', + description: 'Array of video IDs to query (maximum 20)', + items: { + type: 'string', + description: 'TikTok video ID', + }, + }, + }, + + request: { + url: () => `https://open.tiktokapis.com/v2/video/query/?fields=${TIKTOK_VIDEO_FIELDS}`, + method: 'POST', + headers: (params: TikTokQueryVideosParams) => ({ + Authorization: `Bearer ${params.accessToken}`, + 'Content-Type': 'application/json', + }), + body: (params: TikTokQueryVideosParams) => { + const videoIds = params.videoIds.map((id) => id.trim()).filter(Boolean) + assertTikTokArrayLength(videoIds, 'videoIds', 20) + return { + filters: { + video_ids: videoIds, + }, + } + }, + }, + + transformResponse: async (response: Response): Promise => { + const { data, error } = await readTikTokApiResponse(response, tiktokVideosApiDataSchema) + + if (error) { + return { + success: false, + output: { + videos: [], + }, + error: error.message || 'Failed to query videos', + } + } + + const videos: TikTokVideo[] = (data?.videos ?? []).map(mapTikTokVideo) + + return { + success: true, + output: { + videos, + }, + } + }, + + outputs: { + videos: { + type: 'array', + description: 'List of queried TikTok videos', + items: { + type: 'object', + properties: TIKTOK_VIDEO_OUTPUT_PROPERTIES, + }, + }, + }, + } diff --git a/apps/sim/tools/tiktok/types.ts b/apps/sim/tools/tiktok/types.ts new file mode 100644 index 00000000000..cfbcd531682 --- /dev/null +++ b/apps/sim/tools/tiktok/types.ts @@ -0,0 +1,208 @@ +import type { UserFile } from '@/executor/types' +import type { OutputProperty, ToolResponse } from '@/tools/types' + +/** + * Base params that include OAuth access token + */ +export interface TikTokBaseParams { + accessToken: string +} + +/** Error envelope returned by TikTok APIs. */ +export interface TikTokApiError { + code: string + message?: string + logId?: string +} + +/** + * Get User Info + */ +export interface TikTokGetUserParams extends TikTokBaseParams { + fields?: string +} + +export interface TikTokGetUserResponse extends ToolResponse { + output: { + openId: string + unionId: string | null + displayName: string + bioDescription: string | null + profileDeepLink: string | null + isVerified: boolean | null + username: string | null + followerCount: number | null + followingCount: number | null + likesCount: number | null + videoCount: number | null + avatarFile?: { + name: string + mimeType: string + url: string + } + } +} + +/** + * List Videos + */ +export interface TikTokListVideosParams extends TikTokBaseParams { + maxCount?: number + cursor?: number +} + +export interface TikTokVideo { + id: string + title: string | null + coverImageUrl: string | null + embedLink: string | null + embedHtml: string | null + duration: number | null + createTime: number | null + shareUrl: string | null + videoDescription: string | null + width: number | null + height: number | null + viewCount: number | null + likeCount: number | null + commentCount: number | null + shareCount: number | null +} + +/** + * Shared output schema for video objects returned by list and query tools. + * Lives in types.ts so the docs generator can resolve the const reference. + */ +export const TIKTOK_VIDEO_OUTPUT_PROPERTIES = { + id: { type: 'string', description: 'Video ID' }, + title: { type: 'string', description: 'Video title', optional: true }, + coverImageUrl: { + type: 'string', + description: + 'Signed TikTok CDN cover URL. It is public but time-limited, so consume it immediately.', + optional: true, + }, + embedLink: { type: 'string', description: 'Embeddable video URL', optional: true }, + embedHtml: { type: 'string', description: 'HTML embed markup for the video', optional: true }, + duration: { type: 'number', description: 'Video duration in seconds', optional: true }, + createTime: { + type: 'number', + description: 'Unix timestamp when the video was created', + optional: true, + }, + shareUrl: { type: 'string', description: 'Shareable video URL', optional: true }, + videoDescription: { + type: 'string', + description: 'Video description or caption', + optional: true, + }, + width: { type: 'number', description: 'Video width in pixels', optional: true }, + height: { type: 'number', description: 'Video height in pixels', optional: true }, + viewCount: { type: 'number', description: 'Number of views', optional: true }, + likeCount: { type: 'number', description: 'Number of likes', optional: true }, + commentCount: { type: 'number', description: 'Number of comments', optional: true }, + shareCount: { type: 'number', description: 'Number of shares', optional: true }, +} satisfies Record + +export interface TikTokListVideosResponse extends ToolResponse { + output: { + videos: TikTokVideo[] + cursor: number | null + hasMore: boolean + } +} + +/** + * Query Videos + */ +export interface TikTokQueryVideosParams extends TikTokBaseParams { + videoIds: string[] +} + +export interface TikTokQueryVideosResponse extends ToolResponse { + output: { + videos: TikTokVideo[] + } +} + +/** + * Query Creator Info - Check posting permissions and get privacy options + */ +export interface TikTokQueryCreatorInfoParams extends TikTokBaseParams {} + +export interface TikTokQueryCreatorInfoResponse extends ToolResponse { + output: { + creatorAvatarUrl: string | null + creatorUsername: string | null + creatorNickname: string | null + privacyLevelOptions: string[] + commentDisabled: boolean + duetDisabled: boolean + stitchDisabled: boolean + maxVideoPostDurationSec: number | null + } +} + +/** + * Direct Post Video - Publish an uploaded video to TikTok + */ +export interface TikTokDirectPostVideoParams extends TikTokBaseParams { + file: UserFile + title?: string + privacyLevel: string + disableDuet: boolean + disableStitch: boolean + disableComment: boolean + videoCoverTimestampMs?: number + isAigc?: boolean + brandContentToggle: boolean + brandOrganicToggle?: boolean + musicUsageConsent: 'accepted' +} + +/** Shared response shape for TikTok publish and draft initialization tools. */ +export interface TikTokPublishResponse extends ToolResponse { + output: { + publishId: string + } +} + +export type TikTokDirectPostVideoResponse = TikTokPublishResponse + +/** + * Upload Video Draft - Send a video to the user's TikTok inbox for manual editing/posting + */ +export interface TikTokUploadVideoDraftParams extends TikTokBaseParams { + file: UserFile +} + +export type TikTokUploadVideoDraftResponse = TikTokPublishResponse + +/** + * Get Post Status - Check status of a published/uploaded post + */ +export interface TikTokGetPostStatusParams extends TikTokBaseParams { + publishId: string +} + +export interface TikTokGetPostStatusResponse extends ToolResponse { + output: { + status: string + failReason: string | null + publiclyAvailablePostId: string[] + uploadedBytes: number | null + downloadedBytes: number | null + } +} + +/** + * Union type of all TikTok responses + */ +export type TikTokResponse = + | TikTokGetUserResponse + | TikTokListVideosResponse + | TikTokQueryVideosResponse + | TikTokQueryCreatorInfoResponse + | TikTokDirectPostVideoResponse + | TikTokUploadVideoDraftResponse + | TikTokGetPostStatusResponse diff --git a/apps/sim/tools/tiktok/upload_video_draft.ts b/apps/sim/tools/tiktok/upload_video_draft.ts new file mode 100644 index 00000000000..3104539049f --- /dev/null +++ b/apps/sim/tools/tiktok/upload_video_draft.ts @@ -0,0 +1,64 @@ +import type { + TikTokUploadVideoDraftParams, + TikTokUploadVideoDraftResponse, +} from '@/tools/tiktok/types' +import { readTikTokPublishInitResponse, toTikTokPublishToolResponse } from '@/tools/tiktok/utils' +import type { ToolConfig } from '@/tools/types' + +export const tiktokUploadVideoDraftTool: ToolConfig< + TikTokUploadVideoDraftParams, + TikTokUploadVideoDraftResponse +> = { + id: 'tiktok_upload_video_draft', + name: 'TikTok Upload Video Draft', + description: + "Send an uploaded video to the authenticated user's TikTok inbox for manual editing and posting. The user must open TikTok and tap the inbox notification to complete the post. Rate limit: 6 requests per minute per user.", + version: '1.0.0', + + oauth: { + required: true, + provider: 'tiktok', + requiredScopes: ['video.upload'], + }, + + params: { + accessToken: { + type: 'string', + required: true, + visibility: 'hidden', + description: 'TikTok OAuth access token', + }, + file: { + type: 'file', + required: true, + visibility: 'user-only', + description: 'Video file to upload from the workflow. Maximum size: 250 MB.', + }, + }, + + request: { + url: () => '/api/tools/tiktok/publish-video', + method: 'POST', + headers: () => ({ + 'Content-Type': 'application/json', + }), + body: (params: TikTokUploadVideoDraftParams) => ({ + accessToken: params.accessToken, + mode: 'draft', + file: params.file, + }), + }, + + transformResponse: async (response: Response): Promise => { + const result = await readTikTokPublishInitResponse(response) + return toTikTokPublishToolResponse(result) + }, + + outputs: { + publishId: { + type: 'string', + description: + 'Unique identifier for tracking the draft status. Use this with the Get Post Status tool to check when the user has completed posting from their inbox.', + }, + }, +} diff --git a/apps/sim/tools/tiktok/utils.test.ts b/apps/sim/tools/tiktok/utils.test.ts new file mode 100644 index 00000000000..0377220ee7b --- /dev/null +++ b/apps/sim/tools/tiktok/utils.test.ts @@ -0,0 +1,83 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import { tiktokPublishInitApiDataSchema } from '@/tools/tiktok/api-schemas' +import { + assertTikTokArrayLength, + mapTikTokVideo, + readTikTokApiResponse, + readTikTokPublishInitResponse, +} from '@/tools/tiktok/utils' + +describe('TikTok tool utilities', () => { + it('turns non-JSON HTTP failures into a structured TikTok error', async () => { + const response = new Response('Bad gateway', { status: 502 }) + + await expect(readTikTokApiResponse(response, tiktokPublishInitApiDataSchema)).resolves.toEqual({ + data: null, + error: { + code: 'http_502', + message: 'TikTok request failed with HTTP 502: Bad gateway', + }, + rawBody: 'Bad gateway', + }) + }) + + it('preserves internal publish route failures', async () => { + const response = Response.json( + { success: false, output: { publishId: '' }, error: 'Upload failed' }, + { status: 400 } + ) + + await expect(readTikTokPublishInitResponse(response)).resolves.toEqual({ + success: false, + publishId: '', + error: 'Upload failed', + }) + }) + + it('validates TikTok response data before exposing it to tools', async () => { + const response = Response.json({ + data: { publish_id: 123 }, + error: { code: 'ok' }, + }) + + await expect(readTikTokApiResponse(response, tiktokPublishInitApiDataSchema)).resolves.toEqual({ + data: null, + error: { + code: 'invalid_response', + message: 'TikTok returned an unexpected data shape', + }, + rawBody: '{"data":{"publish_id":123},"error":{"code":"ok"}}', + }) + }) + + it('normalizes direct TikTok publish responses', async () => { + const response = Response.json({ + data: { publish_id: 'publish-1' }, + error: { code: 'ok' }, + }) + + await expect(readTikTokPublishInitResponse(response)).resolves.toEqual({ + success: true, + publishId: 'publish-1', + }) + }) + + it('enforces TikTok array limits', () => { + expect(() => assertTikTokArrayLength([], 'videoIds', 20)).toThrow( + 'videoIds must contain at least one item' + ) + expect(() => assertTikTokArrayLength(Array.from({ length: 21 }), 'videoIds', 20)).toThrow( + 'videoIds supports at most 20 items' + ) + expect(() => assertTikTokArrayLength(['video-1'], 'videoIds', 20)).not.toThrow() + }) + + it('maps TikTok embed HTML', () => { + expect(mapTikTokVideo({ id: 'video-1', embed_html: '
' }).embedHtml).toBe( + '
' + ) + }) +}) diff --git a/apps/sim/tools/tiktok/utils.ts b/apps/sim/tools/tiktok/utils.ts new file mode 100644 index 00000000000..012a40fd641 --- /dev/null +++ b/apps/sim/tools/tiktok/utils.ts @@ -0,0 +1,234 @@ +import { truncate } from '@sim/utils/string' +import type { ZodType } from 'zod' +import { type TikTokApiVideo, tiktokPublishInitApiDataSchema } from '@/tools/tiktok/api-schemas' +import type { TikTokApiError, TikTokPublishResponse, TikTokVideo } from '@/tools/tiktok/types' + +/** + * Default fields requested from TikTok's `/v2/user/info/` endpoint, covering the + * `user.info.basic`, `user.info.profile`, and `user.info.stats` scopes. + * `avatar_url` and `avatar_large_url` feed the file-typed `avatarFile` output. + */ +export const TIKTOK_USER_FIELDS = + 'open_id,union_id,avatar_url,avatar_large_url,display_name,bio_description,profile_deep_link,is_verified,username,follower_count,following_count,likes_count,video_count' + +/** + * Fields requested from TikTok's `/v2/video/list/` and `/v2/video/query/` endpoints. + * All are available under the `video.list` scope. + */ +export const TIKTOK_VIDEO_FIELDS = + 'id,title,cover_image_url,embed_link,embed_html,duration,create_time,share_url,video_description,width,height,view_count,like_count,comment_count,share_count' + +export function mapTikTokVideo(video: TikTokApiVideo): TikTokVideo { + return { + id: video.id ?? '', + title: video.title ?? null, + coverImageUrl: video.cover_image_url ?? null, + embedLink: video.embed_link ?? null, + embedHtml: video.embed_html ?? null, + duration: video.duration ?? null, + createTime: video.create_time ?? null, + shareUrl: video.share_url ?? null, + videoDescription: video.video_description ?? null, + width: video.width ?? null, + height: video.height ?? null, + viewCount: video.view_count ?? null, + likeCount: video.like_count ?? null, + commentCount: video.comment_count ?? null, + shareCount: video.share_count ?? null, + } +} + +interface ParsedTikTokApiResponse { + data: TData | null + error: TikTokApiError | null + rawBody: string +} + +interface ParsedJsonObject { + body: Record | null + error: TikTokApiError | null + rawBody: string +} + +interface TikTokPublishInitResult { + success: boolean + publishId: string + error?: string +} + +function isRecord(value: unknown): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value) +} + +function asRecord(value: unknown): Record | null { + return isRecord(value) ? value : null +} + +function parseTikTokError(value: unknown): TikTokApiError | null { + const error = asRecord(value) + if (!error) return null + + const code = typeof error.code === 'string' ? error.code : null + if (!code) return null + + return { + code, + ...(typeof error.message === 'string' ? { message: error.message } : {}), + ...(typeof error.log_id === 'string' ? { logId: error.log_id } : {}), + } +} + +function httpError(response: Response, rawBody: string, message?: string): TikTokApiError { + return { + code: `http_${response.status}`, + message: + message ?? + `TikTok request failed with HTTP ${response.status}: ${truncate(rawBody.trim(), 300)}`, + } +} + +async function readJsonObject(response: Response): Promise { + const rawBody = await response.text() + let parsed: unknown + + try { + parsed = JSON.parse(rawBody) + } catch { + return { + body: null, + error: response.ok + ? { code: 'invalid_response', message: 'TikTok returned an invalid JSON response' } + : httpError(response, rawBody), + rawBody, + } + } + + const body = asRecord(parsed) + if (!body) { + return { + body: null, + error: { code: 'invalid_response', message: 'TikTok returned an unexpected response shape' }, + rawBody, + } + } + + return { body, error: null, rawBody } +} + +function parseApiEnvelope( + response: Response, + parsed: ParsedJsonObject, + dataSchema: ZodType +): ParsedTikTokApiResponse { + if (!parsed.body) { + return { data: null, error: parsed.error, rawBody: parsed.rawBody } + } + + const providerError = parseTikTokError(parsed.body.error) + if (providerError?.code && providerError.code !== 'ok') { + return { data: null, error: providerError, rawBody: parsed.rawBody } + } + + if (!response.ok) { + return { + data: null, + error: httpError(response, parsed.rawBody, providerError?.message), + rawBody: parsed.rawBody, + } + } + + if (parsed.body.data === null || parsed.body.data === undefined) { + return { data: null, error: null, rawBody: parsed.rawBody } + } + + const dataResult = dataSchema.safeParse(parsed.body.data) + if (!dataResult.success) { + return { + data: null, + error: { + code: 'invalid_response', + message: 'TikTok returned an unexpected data shape', + }, + rawBody: parsed.rawBody, + } + } + + return { data: dataResult.data, error: null, rawBody: parsed.rawBody } +} + +/** Reads and normalizes a typed TikTok API envelope. */ +export async function readTikTokApiResponse( + response: Response, + dataSchema: ZodType +): Promise> { + return parseApiEnvelope(response, await readJsonObject(response), dataSchema) +} + +/** Enforces TikTok's bounded array request limits before making a network request. */ +export function assertTikTokArrayLength(values: unknown[], label: string, maximum: number): void { + if (values.length === 0) { + throw new Error(`${label} must contain at least one item`) + } + if (values.length > maximum) { + throw new Error(`${label} supports at most ${maximum} items`) + } +} + +/** + * The internal Sim upload route and TikTok both return publish-init envelopes. + * Reading and normalization share one boundary. + */ +export async function readTikTokPublishInitResponse( + response: Response +): Promise { + const parsed = await readJsonObject(response) + if (!parsed.body) { + return { + success: false, + publishId: '', + error: parsed.error?.message ?? 'Failed to read publish response', + } + } + + if ('success' in parsed.body) { + if (parsed.body.success !== true) { + return { + success: false, + publishId: '', + error: typeof parsed.body.error === 'string' ? parsed.body.error : 'Failed to publish', + } + } + + const output = asRecord(parsed.body.output) + const publishId = typeof output?.publishId === 'string' ? output.publishId : '' + return publishId + ? { success: true, publishId } + : { success: false, publishId: '', error: 'No publish ID returned' } + } + + const result = parseApiEnvelope(response, parsed, tiktokPublishInitApiDataSchema) + if (result.error) { + return { + success: false, + publishId: '', + error: result.error.message ?? 'Failed to initiate post', + } + } + + return result.data?.publish_id + ? { success: true, publishId: result.data.publish_id } + : { success: false, publishId: '', error: 'No publish ID returned' } +} + +/** Converts a normalized publish result into the shared tool response shape. */ +export function toTikTokPublishToolResponse( + result: TikTokPublishInitResult +): TikTokPublishResponse { + return result.success + ? { success: true, output: { publishId: result.publishId } } + : { + success: false, + output: { publishId: '' }, + error: result.error ?? 'Failed to initiate TikTok publish', + } +} diff --git a/apps/sim/triggers/registry.ts b/apps/sim/triggers/registry.ts index 67416d77974..a5fad7739ea 100644 --- a/apps/sim/triggers/registry.ts +++ b/apps/sim/triggers/registry.ts @@ -383,6 +383,16 @@ import { slackWebhookTrigger } from '@/triggers/slack' import { stripeWebhookTrigger } from '@/triggers/stripe' import { tableNewRowTrigger } from '@/triggers/table' import { telegramWebhookTrigger } from '@/triggers/telegram' +import { + tiktokAuthorizationRemovedTrigger, + tiktokPostInboxDeliveredTrigger, + tiktokPostNoLongerPublicTrigger, + tiktokPostPubliclyAvailableTrigger, + tiktokPostPublishCompleteTrigger, + tiktokPostPublishFailedTrigger, + tiktokVideoPublishCompletedTrigger, + tiktokVideoUploadFailedTrigger, +} from '@/triggers/tiktok' import { twilioSmsReceivedTrigger, twilioSmsStatusTrigger } from '@/triggers/twilio' import { twilioVoiceWebhookTrigger } from '@/triggers/twilio_voice' import { typeformWebhookTrigger } from '@/triggers/typeform' @@ -672,6 +682,14 @@ export const TRIGGER_REGISTRY: TriggerRegistry = { stripe_webhook: stripeWebhookTrigger, table_new_row: tableNewRowTrigger, telegram_webhook: telegramWebhookTrigger, + tiktok_authorization_removed: tiktokAuthorizationRemovedTrigger, + tiktok_post_inbox_delivered: tiktokPostInboxDeliveredTrigger, + tiktok_post_no_longer_public: tiktokPostNoLongerPublicTrigger, + tiktok_post_publicly_available: tiktokPostPubliclyAvailableTrigger, + tiktok_post_publish_complete: tiktokPostPublishCompleteTrigger, + tiktok_post_publish_failed: tiktokPostPublishFailedTrigger, + tiktok_video_publish_completed: tiktokVideoPublishCompletedTrigger, + tiktok_video_upload_failed: tiktokVideoUploadFailedTrigger, typeform_webhook: typeformWebhookTrigger, whatsapp_webhook: whatsappWebhookTrigger, google_forms_webhook: googleFormsWebhookTrigger, diff --git a/apps/sim/triggers/tiktok/authorization_removed.ts b/apps/sim/triggers/tiktok/authorization_removed.ts new file mode 100644 index 00000000000..9845b6e13a1 --- /dev/null +++ b/apps/sim/triggers/tiktok/authorization_removed.ts @@ -0,0 +1,25 @@ +import { TikTokIcon } from '@/components/icons' +import { + buildTikTokAuthorizationRemovedOutputs, + buildTikTokTriggerSubBlocks, + TIKTOK_WEBHOOK_HEADERS, +} from '@/triggers/tiktok/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const tiktokAuthorizationRemovedTrigger: TriggerConfig = { + id: 'tiktok_authorization_removed', + name: 'TikTok Authorization Removed', + provider: 'tiktok', + description: 'Trigger when a user deauthorizes your TikTok app', + version: '1.0.0', + icon: TikTokIcon, + + subBlocks: buildTikTokTriggerSubBlocks('tiktok_authorization_removed', 'authorization.removed'), + + outputs: buildTikTokAuthorizationRemovedOutputs(), + + webhook: { + method: 'POST', + headers: { ...TIKTOK_WEBHOOK_HEADERS }, + }, +} diff --git a/apps/sim/triggers/tiktok/index.ts b/apps/sim/triggers/tiktok/index.ts new file mode 100644 index 00000000000..87d4219c68c --- /dev/null +++ b/apps/sim/triggers/tiktok/index.ts @@ -0,0 +1,8 @@ +export { tiktokAuthorizationRemovedTrigger } from './authorization_removed' +export { tiktokPostInboxDeliveredTrigger } from './post_inbox_delivered' +export { tiktokPostNoLongerPublicTrigger } from './post_no_longer_public' +export { tiktokPostPubliclyAvailableTrigger } from './post_publicly_available' +export { tiktokPostPublishCompleteTrigger } from './post_publish_complete' +export { tiktokPostPublishFailedTrigger } from './post_publish_failed' +export { tiktokVideoPublishCompletedTrigger } from './video_publish_completed' +export { tiktokVideoUploadFailedTrigger } from './video_upload_failed' diff --git a/apps/sim/triggers/tiktok/post_inbox_delivered.ts b/apps/sim/triggers/tiktok/post_inbox_delivered.ts new file mode 100644 index 00000000000..ab729fdaa3d --- /dev/null +++ b/apps/sim/triggers/tiktok/post_inbox_delivered.ts @@ -0,0 +1,28 @@ +import { TikTokIcon } from '@/components/icons' +import { + buildTikTokPostingOutputs, + buildTikTokTriggerSubBlocks, + TIKTOK_WEBHOOK_HEADERS, +} from '@/triggers/tiktok/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const tiktokPostInboxDeliveredTrigger: TriggerConfig = { + id: 'tiktok_post_inbox_delivered', + name: 'TikTok Post Inbox Delivered', + provider: 'tiktok', + description: 'Trigger when a draft notification is delivered to the creator inbox', + version: '1.0.0', + icon: TikTokIcon, + + subBlocks: buildTikTokTriggerSubBlocks( + 'tiktok_post_inbox_delivered', + 'post.publish.inbox_delivered' + ), + + outputs: buildTikTokPostingOutputs(), + + webhook: { + method: 'POST', + headers: { ...TIKTOK_WEBHOOK_HEADERS }, + }, +} diff --git a/apps/sim/triggers/tiktok/post_no_longer_public.ts b/apps/sim/triggers/tiktok/post_no_longer_public.ts new file mode 100644 index 00000000000..2adc1796b9c --- /dev/null +++ b/apps/sim/triggers/tiktok/post_no_longer_public.ts @@ -0,0 +1,28 @@ +import { TikTokIcon } from '@/components/icons' +import { + buildTikTokPostingOutputs, + buildTikTokTriggerSubBlocks, + TIKTOK_WEBHOOK_HEADERS, +} from '@/triggers/tiktok/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const tiktokPostNoLongerPublicTrigger: TriggerConfig = { + id: 'tiktok_post_no_longer_public', + name: 'TikTok Post No Longer Public', + provider: 'tiktok', + description: 'Trigger when a post is no longer publicly viewable on TikTok', + version: '1.0.0', + icon: TikTokIcon, + + subBlocks: buildTikTokTriggerSubBlocks( + 'tiktok_post_no_longer_public', + 'post.publish.no_longer_publicaly_available' + ), + + outputs: buildTikTokPostingOutputs({ includePostId: true }), + + webhook: { + method: 'POST', + headers: { ...TIKTOK_WEBHOOK_HEADERS }, + }, +} diff --git a/apps/sim/triggers/tiktok/post_publicly_available.ts b/apps/sim/triggers/tiktok/post_publicly_available.ts new file mode 100644 index 00000000000..54fc9caca42 --- /dev/null +++ b/apps/sim/triggers/tiktok/post_publicly_available.ts @@ -0,0 +1,28 @@ +import { TikTokIcon } from '@/components/icons' +import { + buildTikTokPostingOutputs, + buildTikTokTriggerSubBlocks, + TIKTOK_WEBHOOK_HEADERS, +} from '@/triggers/tiktok/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const tiktokPostPubliclyAvailableTrigger: TriggerConfig = { + id: 'tiktok_post_publicly_available', + name: 'TikTok Post Publicly Available', + provider: 'tiktok', + description: 'Trigger when a published post becomes publicly viewable on TikTok', + version: '1.0.0', + icon: TikTokIcon, + + subBlocks: buildTikTokTriggerSubBlocks( + 'tiktok_post_publicly_available', + 'post.publish.publicly_available' + ), + + outputs: buildTikTokPostingOutputs({ includePostId: true }), + + webhook: { + method: 'POST', + headers: { ...TIKTOK_WEBHOOK_HEADERS }, + }, +} diff --git a/apps/sim/triggers/tiktok/post_publish_complete.ts b/apps/sim/triggers/tiktok/post_publish_complete.ts new file mode 100644 index 00000000000..e77eb2d91bc --- /dev/null +++ b/apps/sim/triggers/tiktok/post_publish_complete.ts @@ -0,0 +1,41 @@ +import { TikTokIcon } from '@/components/icons' +import { + buildTikTokPostingOutputs, + buildTikTokTriggerSubBlocks, + TIKTOK_WEBHOOK_HEADERS, + tiktokTriggerOptions, +} from '@/triggers/tiktok/utils' +import type { TriggerConfig } from '@/triggers/types' + +/** + * Primary TikTok trigger — includes the trigger-type dropdown. + * Fires when Content Posting completes (direct post or inbox draft published). + */ +export const tiktokPostPublishCompleteTrigger: TriggerConfig = { + id: 'tiktok_post_publish_complete', + name: 'TikTok Post Publish Complete', + provider: 'tiktok', + description: 'Trigger when a TikTok Content Posting publish completes', + version: '1.0.0', + icon: TikTokIcon, + + subBlocks: [ + { + id: 'selectedTriggerId', + title: 'Trigger Type', + type: 'dropdown', + mode: 'trigger', + options: tiktokTriggerOptions, + value: () => 'tiktok_post_publish_complete', + required: true, + }, + ...buildTikTokTriggerSubBlocks('tiktok_post_publish_complete', 'post.publish.complete'), + ], + + outputs: buildTikTokPostingOutputs(), + + webhook: { + method: 'POST', + headers: { ...TIKTOK_WEBHOOK_HEADERS }, + }, +} diff --git a/apps/sim/triggers/tiktok/post_publish_failed.ts b/apps/sim/triggers/tiktok/post_publish_failed.ts new file mode 100644 index 00000000000..a60d0251668 --- /dev/null +++ b/apps/sim/triggers/tiktok/post_publish_failed.ts @@ -0,0 +1,25 @@ +import { TikTokIcon } from '@/components/icons' +import { + buildTikTokPostingOutputs, + buildTikTokTriggerSubBlocks, + TIKTOK_WEBHOOK_HEADERS, +} from '@/triggers/tiktok/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const tiktokPostPublishFailedTrigger: TriggerConfig = { + id: 'tiktok_post_publish_failed', + name: 'TikTok Post Publish Failed', + provider: 'tiktok', + description: 'Trigger when a TikTok Content Posting publish fails', + version: '1.0.0', + icon: TikTokIcon, + + subBlocks: buildTikTokTriggerSubBlocks('tiktok_post_publish_failed', 'post.publish.failed'), + + outputs: buildTikTokPostingOutputs({ includeFailReason: true }), + + webhook: { + method: 'POST', + headers: { ...TIKTOK_WEBHOOK_HEADERS }, + }, +} diff --git a/apps/sim/triggers/tiktok/utils.ts b/apps/sim/triggers/tiktok/utils.ts new file mode 100644 index 00000000000..e70d93dab81 --- /dev/null +++ b/apps/sim/triggers/tiktok/utils.ts @@ -0,0 +1,147 @@ +import { getScopesForService } from '@/lib/oauth/utils' +import type { SubBlockConfig } from '@/blocks/types' +import type { TriggerOutput } from '@/triggers/types' + +export const tiktokTriggerOptions = [ + { label: 'Post Publish Complete', id: 'tiktok_post_publish_complete' }, + { label: 'Post Publish Failed', id: 'tiktok_post_publish_failed' }, + { label: 'Post Inbox Delivered', id: 'tiktok_post_inbox_delivered' }, + { label: 'Post Publicly Available', id: 'tiktok_post_publicly_available' }, + { label: 'Post No Longer Public', id: 'tiktok_post_no_longer_public' }, + { label: 'Video Publish Completed', id: 'tiktok_video_publish_completed' }, + { label: 'Video Upload Failed', id: 'tiktok_video_upload_failed' }, + { label: 'Authorization Removed', id: 'tiktok_authorization_removed' }, +] + +/** Map Sim trigger ids to TikTok `event` strings (documented spelling preserved). */ +export const TIKTOK_TRIGGER_EVENT_MAP: Record = { + tiktok_post_publish_complete: 'post.publish.complete', + tiktok_post_publish_failed: 'post.publish.failed', + tiktok_post_inbox_delivered: 'post.publish.inbox_delivered', + tiktok_post_publicly_available: 'post.publish.publicly_available', + tiktok_post_no_longer_public: 'post.publish.no_longer_publicaly_available', + tiktok_video_publish_completed: 'video.publish.completed', + tiktok_video_upload_failed: 'video.upload.failed', + tiktok_authorization_removed: 'authorization.removed', +} + +export function isTikTokEventMatch(triggerId: string, event: string | undefined): boolean { + if (!event) return false + const expected = TIKTOK_TRIGGER_EVENT_MAP[triggerId] + return expected === event +} + +export function tiktokSetupInstructions(eventLabel: string): string { + const instructions = [ + 'App setup: A Sim operator must register the full app-level Callback URL https://<your-sim-domain>/api/webhooks/tiktok once in the TikTok Developer Portal. TikTok allows one Callback URL per app, so workflow builders do not paste a unique webhook URL.', + 'Connect the TikTok account that should receive this event using the credential selector above.', + `This trigger listens for ${eventLabel} events for that connected account.`, + 'Deploy the workflow to activate the trigger.', + ] + + return instructions + .map( + (instruction, index) => + `
${index === 0 ? instruction : `${index}. ${instruction}`}
` + ) + .join('') +} + +/** + * Builds subBlocks for a TikTok trigger: OAuth credential + setup instructions. + * Omits the per-workflow Webhook URL field — TikTok uses a fixed app-level Callback URL. + */ +export function buildTikTokTriggerSubBlocks( + triggerId: string, + eventLabel: string +): SubBlockConfig[] { + return [ + { + id: 'triggerCredentials', + title: 'TikTok Account', + type: 'oauth-input', + serviceId: 'tiktok', + requiredScopes: getScopesForService('tiktok'), + mode: 'trigger', + required: true, + condition: { field: 'selectedTriggerId', value: triggerId }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + hideFromPreview: true, + type: 'text', + defaultValue: tiktokSetupInstructions(eventLabel), + mode: 'trigger', + condition: { field: 'selectedTriggerId', value: triggerId }, + }, + ] +} + +function buildCommonOutputs(): Record { + return { + event: { type: 'string', description: 'TikTok webhook event name' }, + createTime: { + type: 'number', + description: 'UTC epoch seconds when the event occurred', + }, + userOpenId: { + type: 'string', + description: 'TikTok user open_id for the connected account', + }, + clientKey: { + type: 'string', + description: 'TikTok app client_key that received the event', + }, + } +} + +export function buildTikTokPostingOutputs(options?: { + includeFailReason?: boolean + includePostId?: boolean +}): Record { + const outputs: Record = { + ...buildCommonOutputs(), + publishId: { type: 'string', description: 'Content Posting API publish_id' }, + publishType: { + type: 'string', + description: 'Publish type (e.g. DIRECT_POST, INBOX_SHARE)', + }, + } + if (options?.includePostId) { + outputs.postId = { + type: 'string', + description: 'TikTok post_id when the post is publicly available', + } + } + if (options?.includeFailReason) { + outputs.failReason = { + type: 'string', + description: 'Failure reason enum from TikTok when publishing fails', + } + } + return outputs +} + +export function buildTikTokVideoKitOutputs(): Record { + return { + ...buildCommonOutputs(), + shareId: { type: 'string', description: 'Share Kit / Video Kit share_id' }, + } +} + +export function buildTikTokAuthorizationRemovedOutputs(): Record { + return { + ...buildCommonOutputs(), + reason: { + type: 'number', + description: + 'Revocation reason (0 unknown, 1 user disconnect, 2 account deleted, 3 age change, 4 banned, 5 developer revoke)', + }, + } +} + +export const TIKTOK_WEBHOOK_HEADERS = { + 'Content-Type': 'application/json', + 'TikTok-Signature': 't=,s=', +} as const diff --git a/apps/sim/triggers/tiktok/video_publish_completed.ts b/apps/sim/triggers/tiktok/video_publish_completed.ts new file mode 100644 index 00000000000..9cd74477337 --- /dev/null +++ b/apps/sim/triggers/tiktok/video_publish_completed.ts @@ -0,0 +1,28 @@ +import { TikTokIcon } from '@/components/icons' +import { + buildTikTokTriggerSubBlocks, + buildTikTokVideoKitOutputs, + TIKTOK_WEBHOOK_HEADERS, +} from '@/triggers/tiktok/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const tiktokVideoPublishCompletedTrigger: TriggerConfig = { + id: 'tiktok_video_publish_completed', + name: 'TikTok Video Publish Completed', + provider: 'tiktok', + description: 'Trigger when a Share Kit / Video Kit upload is published by the user', + version: '1.0.0', + icon: TikTokIcon, + + subBlocks: buildTikTokTriggerSubBlocks( + 'tiktok_video_publish_completed', + 'video.publish.completed' + ), + + outputs: buildTikTokVideoKitOutputs(), + + webhook: { + method: 'POST', + headers: { ...TIKTOK_WEBHOOK_HEADERS }, + }, +} diff --git a/apps/sim/triggers/tiktok/video_upload_failed.ts b/apps/sim/triggers/tiktok/video_upload_failed.ts new file mode 100644 index 00000000000..767d3b7ff1d --- /dev/null +++ b/apps/sim/triggers/tiktok/video_upload_failed.ts @@ -0,0 +1,25 @@ +import { TikTokIcon } from '@/components/icons' +import { + buildTikTokTriggerSubBlocks, + buildTikTokVideoKitOutputs, + TIKTOK_WEBHOOK_HEADERS, +} from '@/triggers/tiktok/utils' +import type { TriggerConfig } from '@/triggers/types' + +export const tiktokVideoUploadFailedTrigger: TriggerConfig = { + id: 'tiktok_video_upload_failed', + name: 'TikTok Video Upload Failed', + provider: 'tiktok', + description: 'Trigger when a Share Kit / Video Kit upload fails in TikTok', + version: '1.0.0', + icon: TikTokIcon, + + subBlocks: buildTikTokTriggerSubBlocks('tiktok_video_upload_failed', 'video.upload.failed'), + + outputs: buildTikTokVideoKitOutputs(), + + webhook: { + method: 'POST', + headers: { ...TIKTOK_WEBHOOK_HEADERS }, + }, +} diff --git a/packages/db/migrations/0258_gigantic_lady_mastermind.sql b/packages/db/migrations/0258_gigantic_lady_mastermind.sql new file mode 100644 index 00000000000..e35e5dc07ff --- /dev/null +++ b/packages/db/migrations/0258_gigantic_lady_mastermind.sql @@ -0,0 +1,4 @@ +COMMIT;--> statement-breakpoint +SET lock_timeout = 0;--> statement-breakpoint +CREATE INDEX CONCURRENTLY IF NOT EXISTS "webhook_tiktok_credential_id_idx" ON "webhook" USING btree ((("provider_config")::jsonb ->> 'credentialId')) WHERE "webhook"."provider" = 'tiktok' AND "webhook"."is_active" = true AND "webhook"."archived_at" IS NULL;--> statement-breakpoint +SET lock_timeout = '5s'; \ No newline at end of file diff --git a/packages/db/migrations/meta/0258_snapshot.json b/packages/db/migrations/meta/0258_snapshot.json new file mode 100644 index 00000000000..70ac5ac0184 --- /dev/null +++ b/packages/db/migrations/meta/0258_snapshot.json @@ -0,0 +1,16734 @@ +{ + "id": "4fa65eec-f5c0-4d5c-8d9c-a3b6577228d0", + "prevId": "c405fc48-c6bb-4786-863f-3d3e686095b5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_number_idx": { + "name": "academy_certificate_number_idx", + "columns": [ + { + "expression": "certificate_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": ["certificate_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "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": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_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 + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.background_work_status": { + "name": "background_work_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "background_work_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "background_work_status_value", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "background_work_status_workspace_status_idx": { + "name": "background_work_status_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_workflow_status_idx": { + "name": "background_work_status_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_child_ws_idx": { + "name": "background_work_status_meta_child_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'childWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_other_ws_idx": { + "name": "background_work_status_meta_other_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'otherWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "background_work_status_workspace_id_workspace_id_fk": { + "name": "background_work_status_workspace_id_workspace_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "background_work_status_workflow_id_workflow_id_fk": { + "name": "background_work_status_workflow_id_workflow_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "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": "'[]'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_idx": { + "name": "copilot_async_tool_calls_tool_call_id_idx", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": ["checkpoint_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": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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_user_workspace_idx": { + "name": "copilot_chats_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": {} + }, + "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": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "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" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": ["workspace_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.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "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": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "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": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "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": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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": { + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "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": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_block": { + "name": "custom_block", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "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 + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inputs": { + "name": "inputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "outputs": { + "name": "outputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "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": { + "custom_block_organization_id_idx": { + "name": "custom_block_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_workflow_id_idx": { + "name": "custom_block_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_organization_type_unique": { + "name": "custom_block_organization_type_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_block_organization_id_organization_id_fk": { + "name": "custom_block_organization_id_organization_id_fk", + "tableFrom": "custom_block", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_workflow_id_workflow_id_fk": { + "name": "custom_block_workflow_id_workflow_id_fk", + "tableFrom": "custom_block", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_created_by_user_id_fk": { + "name": "custom_block_created_by_user_id_fk", + "tableFrom": "custom_block", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "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": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "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 + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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 + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "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_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_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_id_idx": { + "name": "doc_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_storage_key_idx": { + "name": "doc_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"storage_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "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": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "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" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "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_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "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.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": ["parent_key", "child_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": ["key", "execution_id", "source"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "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_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": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "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": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "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": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "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": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "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": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "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 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_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": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "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": "cascade", + "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.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "consecutive_failures": { + "name": "consecutive_failures", + "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()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_id_idx": { + "name": "kcsl_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_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_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "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 + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "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 + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "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_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "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_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "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 + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "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()" + }, + "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_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "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 + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "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": { + "mothership_settings_workspace_id_idx": { + "name": "mothership_settings_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_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 + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "departed_member_usage": { + "name": "departed_member_usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_member_usage_limit": { + "name": "organization_member_usage_limit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "set_by": { + "name": "set_by", + "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": { + "org_member_usage_limit_org_user_unique": { + "name": "org_member_usage_limit_org_user_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "org_member_usage_limit_organization_id_idx": { + "name": "org_member_usage_limit_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_member_usage_limit_organization_id_organization_id_fk": { + "name": "organization_member_usage_limit_organization_id_organization_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_user_id_user_id_fk": { + "name": "organization_member_usage_limit_user_id_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_set_by_user_id_fk": { + "name": "organization_member_usage_limit_set_by_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["set_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "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 + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_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 + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "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": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_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()" + } + }, + "indexes": { + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "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()" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_name_unique": { + "name": "permission_group_organization_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_default_unique": { + "name": "permission_group_organization_default_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_organization_user_idx": { + "name": "permission_group_member_organization_user_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_organization_id_organization_id_fk": { + "name": "permission_group_member_organization_id_organization_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_workspace": { + "name": "permission_group_workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_workspace_workspace_id_idx": { + "name": "permission_group_workspace_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_group_workspace_unique": { + "name": "permission_group_workspace_group_workspace_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_permission_group_id_permission_group_id_fk": { + "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_organization_id_organization_id_fk": { + "name": "permission_group_workspace_organization_id_organization_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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.public_share": { + "name": "public_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": "'[]'" + }, + "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": { + "public_share_token_unique": { + "name": "public_share_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_unique": { + "name": "public_share_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_id_idx": { + "name": "public_share_resource_id_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_workspace_id_idx": { + "name": "public_share_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "public_share_workspace_id_workspace_id_fk": { + "name": "public_share_workspace_id_workspace_id_fk", + "tableFrom": "public_share", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "public_share_created_by_user_id_fk": { + "name": "public_share_created_by_user_id_fk", + "tableFrom": "public_share", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_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 + }, + "impersonated_by": { + "name": "impersonated_by", + "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 + }, + "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_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": 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.sim_trigger_state": { + "name": "sim_trigger_state", + "schema": "", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sim_trigger_state_workflow_id_workflow_id_fk": { + "name": "sim_trigger_state_workflow_id_workflow_id_fk", + "tableFrom": "sim_trigger_state", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sim_trigger_state_workflow_id_block_id_scope_key_pk": { + "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", + "columns": ["workflow_id", "block_id", "scope_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "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": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "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 + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "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.table_jobs": { + "name": "table_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rows_processed": { + "name": "rows_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_jobs_one_active_per_table": { + "name": "table_jobs_one_active_per_table", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_watchdog_idx": { + "name": "table_jobs_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_table_started_idx": { + "name": "table_jobs_table_started_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_jobs_table_id_user_table_definitions_id_fk": { + "name": "table_jobs_table_id_user_table_definitions_id_fk", + "tableFrom": "table_jobs", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_jobs_workspace_id_workspace_id_fk": { + "name": "table_jobs_workspace_id_workspace_id_fk", + "tableFrom": "table_jobs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enrichment_details": { + "name": "enrichment_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_triggered_by_user_id_user_id_fk": { + "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": ["triggered_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "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 + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + } + }, + "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_mcp_executions": { + "name": "total_mcp_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "bigint", + "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": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "pro_period_cost_snapshot_at": { + "name": "pro_period_cost_snapshot_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_calls": { + "name": "total_mcp_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_cost": { + "name": "total_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_mcp_copilot_cost": { + "name": "current_period_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "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.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rows_version": { + "name": "rows_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "order_key": { + "name": "order_key", + "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()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_tenant_data_gin_idx": { + "name": "user_table_rows_tenant_data_gin_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"data\" jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_order_key_idx": { + "name": "user_table_rows_table_order_key_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_id_id_idx": { + "name": "user_table_rows_table_id_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "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 + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": { + "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_tiktok_credential_id_idx": { + "name": "webhook_tiktok_credential_id_idx", + "columns": [ + { + "expression": "((\"provider_config\")::jsonb ->> 'credentialId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"provider\" = 'tiktok' AND \"webhook\".\"is_active\" = true AND \"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id_updated_at_desc": { + "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "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_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_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 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "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": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "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_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_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 + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "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 + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "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_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_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": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "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": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_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" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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": false + }, + "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": "set null", + "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 + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "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()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "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": {} + }, + "workflow_folder_archived_at_idx": { + "name": "workflow_folder_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_archived_partial_idx": { + "name": "workflow_folder_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_folder\".\"archived_at\" IS NOT NULL", + "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_mcp_server": { + "name": "workflow_mcp_server", + "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": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "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": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "last_queued_at": { + "name": "last_queued_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 + }, + "infra_retry_count": { + "name": "infra_retry_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 + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "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_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_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 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_folders": { + "name": "workspace_file_folders", + "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 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": { + "workspace_file_folders_workspace_parent_idx": { + "name": "workspace_file_folders_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": {} + }, + "workspace_file_folders_parent_sort_idx": { + "name": "workspace_file_folders_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": {} + }, + "workspace_file_folders_deleted_at_idx": { + "name": "workspace_file_folders_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_deleted_partial_idx": { + "name": "workspace_file_folders_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_parent_name_active_unique": { + "name": "workspace_file_folders_workspace_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_folders_user_id_user_id_fk": { + "name": "workspace_file_folders_user_id_user_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_workspace_id_workspace_id_fk": { + "name": "workspace_file_folders_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_parent_id_workspace_file_folders_id_fk": { + "name": "workspace_file_folders_parent_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace_file_folders", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_workspace_file_folders_id_fk": { + "name": "workspace_files_folder_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace_file_folders", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_block_map": { + "name": "workspace_fork_block_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_workflow_id": { + "name": "parent_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_block_id": { + "name": "parent_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_workflow_id": { + "name": "child_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_block_id": { + "name": "child_block_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": { + "workspace_fork_block_map_child_ws_parent_unique": { + "name": "workspace_fork_block_map_child_ws_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_unique": { + "name": "workspace_fork_block_map_child_ws_child_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_parent_wf_idx": { + "name": "workspace_fork_block_map_child_ws_parent_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_wf_idx": { + "name": "workspace_fork_block_map_child_ws_child_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_block_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_block_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_dependent_value": { + "name": "workspace_fork_dependent_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workflow_id": { + "name": "target_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sub_block_key": { + "name": "sub_block_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "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": { + "workspace_fork_dependent_value_child_ws_wf_idx": { + "name": "workspace_fork_dependent_value_child_ws_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_dependent_value_field_unique": { + "name": "workspace_fork_dependent_value_field_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sub_block_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_dependent_value", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_promote_run": { + "name": "workspace_fork_promote_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workspace_id": { + "name": "target_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "workspace_fork_promote_direction", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_promote_run_child_ws_target_unique": { + "name": "workspace_fork_promote_run_child_ws_target_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_promote_run_target_ws_idx": { + "name": "workspace_fork_promote_run_target_ws_idx", + "columns": [ + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_promote_run_created_by_user_id_fk": { + "name": "workspace_fork_promote_run_created_by_user_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_resource_map": { + "name": "workspace_fork_resource_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "workspace_fork_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "parent_resource_id": { + "name": "parent_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_resource_id": { + "name": "child_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "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": { + "workspace_fork_resource_map_child_ws_idx": { + "name": "workspace_fork_resource_map_child_ws_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_ws_type_idx": { + "name": "workspace_fork_resource_map_child_ws_type_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_type_parent_unique": { + "name": "workspace_fork_resource_map_child_type_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_resource_map_created_by_user_id_fk": { + "name": "workspace_fork_resource_map_created_by_user_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.background_work_kind": { + "name": "background_work_kind", + "schema": "public", + "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"] + }, + "public.background_work_status_value": { + "name": "background_work_status_value", + "schema": "public", + "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": ["oauth", "env_workspace", "env_personal", "service_account"] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": ["hourly", "daily"] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": ["running", "success", "failed"] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": ["cron", "manual"] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] + }, + "public.execution_large_value_reference_source": { + "name": "execution_large_value_reference_source", + "schema": "public", + "values": ["execution_log", "paused_snapshot"] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": ["organization", "workspace"] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": ["internal", "external"] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed", "tool"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment" + ] + }, + "public.workspace_fork_promote_direction": { + "name": "workspace_fork_promote_direction", + "schema": "public", + "values": ["push", "pull"] + }, + "public.workspace_fork_resource_type": { + "name": "workspace_fork_resource_type", + "schema": "public", + "values": [ + "workflow", + "oauth_credential", + "service_account_credential", + "env_var", + "table", + "knowledge_base", + "knowledge_document", + "file", + "mcp_server", + "workflow_mcp_server", + "custom_tool", + "skill" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "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 b457b88600c..bc28ddf7be3 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1800,6 +1800,13 @@ "when": 1783531236677, "tag": "0257_majestic_chat", "breakpoints": true + }, + { + "idx": 258, + "version": "7", + "when": 1783620533559, + "tag": "0258_gigantic_lady_mastermind", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 62bf0b5d806..64ee9a71061 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -726,6 +726,11 @@ export const jobExecutionLogs = pgTable( }) ) +/** Extracts the canonical credential ID persisted in webhook provider configuration. */ +export function webhookCredentialIdExpression(column: AnyPgColumn): SQL { + return sql`((${column})::jsonb ->> 'credentialId')` +} + export const webhook = pgTable( 'webhook', { @@ -764,6 +769,11 @@ export const webhook = pgTable( providerActiveWorkflowDeploymentIdx: index( 'idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468' ).on(table.provider, table.isActive, table.workflowId, table.deploymentVersionId), + tiktokCredentialIdIdx: index('webhook_tiktok_credential_id_idx') + .on(webhookCredentialIdExpression(table.providerConfig)) + .where( + sql`${table.provider} = 'tiktok' AND ${table.isActive} = true AND ${table.archivedAt} IS NULL` + ), workflowBlockUpdatedDescIdx: index('idx_webhook_on_workflow_id_block_id_updated_at_desc').on( table.workflowId, table.blockId, diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index 3fb1e9e1c1b..8308a288d53 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries') const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors') const BASELINE = { - totalRoutes: 922, - zodRoutes: 922, + totalRoutes: 924, + zodRoutes: 924, nonZodRoutes: 0, } as const diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 984dd89a76e..5531b797771 100755 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -69,7 +69,15 @@ const HANDWRITTEN_TRIGGER_DOCS = new Set([ ]) /** Providers whose docs are already covered by hand-written pages. */ -const SKIP_TRIGGER_PROVIDERS = new Set(['generic', 'rss', 'table', 'sim']) +const SKIP_TRIGGER_PROVIDERS = new Set([ + 'generic', + 'rss', + 'table', + 'sim', + // TikTok is temporarily hideFromToolbar; skip so cleanup does not leave an + // orphan triggers-only docs page after the actions page is removed. + 'tiktok', +]) /** * Maps trigger provider names (from TriggerConfig.provider) to their @@ -119,6 +127,7 @@ const TRIGGER_PROVIDER_DISPLAY_NAMES: Record = { slack: 'Slack', stripe: 'Stripe', telegram: 'Telegram', + tiktok: 'TikTok', twilio_voice: 'Twilio Voice', typeform: 'Typeform', vercel: 'Vercel',