From 14815e927b6932fbbe38309ef1e2633bf09a182a Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Mon, 6 Jul 2026 18:33:27 -0700 Subject: [PATCH] chore(credential-sets): cleanup feature --- .claude/commands/add-trigger.md | 4 +- .claude/rules/sim-queries.md | 2 +- .claude/rules/sim-settings-pages.md | 4 +- .cursor/commands/add-trigger.md | 4 +- .../docs/content/docs/de/enterprise/index.mdx | 1 - apps/docs/content/docs/de/triggers/index.mdx | 4 - .../content/docs/en/integrations/index.mdx | 6 - .../docs/en/platform/enterprise/index.mdx | 1 - .../docs/content/docs/es/enterprise/index.mdx | 1 - apps/docs/content/docs/es/triggers/index.mdx | 4 - .../docs/content/docs/fr/enterprise/index.mdx | 1 - apps/docs/content/docs/fr/triggers/index.mdx | 4 - .../docs/content/docs/ja/enterprise/index.mdx | 1 - apps/docs/content/docs/ja/triggers/index.mdx | 26 - .../docs/content/docs/zh/enterprise/index.mdx | 1 - apps/docs/content/docs/zh/triggers/index.mdx | 4 - apps/sim/app/(auth)/signup/signup-form.tsx | 5 +- .../api/auth/oauth/disconnect/route.test.ts | 8 - .../app/api/auth/oauth/disconnect/route.ts | 46 +- apps/sim/app/api/auth/oauth/utils.ts | 90 +- .../[id]/invite/[invitationId]/route.ts | 183 - .../api/credential-sets/[id]/invite/route.ts | 306 - .../api/credential-sets/[id]/members/route.ts | 238 - .../sim/app/api/credential-sets/[id]/route.ts | 242 - .../api/credential-sets/invitations/route.ts | 54 - .../credential-sets/invite/[token]/route.ts | 253 - .../api/credential-sets/memberships/route.ts | 150 - apps/sim/app/api/credential-sets/route.ts | 216 - apps/sim/app/api/webhooks/[id]/route.ts | 75 +- apps/sim/app/api/webhooks/route.ts | 150 +- .../app/api/webhooks/trigger/[path]/route.ts | 9 +- .../credential-account/[token]/loading.tsx | 19 - .../app/credential-account/[token]/page.tsx | 275 - apps/sim/app/robots.ts | 1 - .../[workspaceId]/settings/[section]/page.tsx | 1 - .../settings/[section]/settings.tsx | 19 +- .../credential-sets/credential-sets.tsx | 818 - .../components/credential-sets/index.ts | 1 - .../settings-resource-row.tsx | 2 +- .../[workspaceId]/settings/navigation.ts | 15 - .../credential-selector.tsx | 139 +- apps/sim/blocks/types.ts | 2 - .../components/emails/invitations/index.ts | 1 - .../polling-group-invitation-email.tsx | 55 - apps/sim/components/emails/render.ts | 19 - apps/sim/components/emails/subjects.ts | 3 - apps/sim/executor/constants.ts | 12 - apps/sim/hooks/queries/credential-sets.ts | 318 - .../hooks/queries/oauth/oauth-credentials.ts | 34 +- .../queries/utils/fetch-credential-set.ts | 21 - apps/sim/hooks/use-webhook-management.ts | 1 - apps/sim/lib/api/contracts/credential-sets.ts | 344 - apps/sim/lib/api/contracts/index.ts | 1 - apps/sim/lib/api/contracts/webhooks.ts | 9 - apps/sim/lib/auth/auth.ts | 41 - .../sim/lib/billing/core/subscription.test.ts | 1 - apps/sim/lib/billing/core/subscription.ts | 107 - apps/sim/lib/billing/index.ts | 3 - .../sim/lib/compare/data/competitors/tines.ts | 2 +- apps/sim/lib/compare/data/feature-catalog.ts | 2 +- apps/sim/lib/core/config/env-flags.ts | 6 - apps/sim/lib/core/config/env.ts | 5 - .../core/security/input-validation.test.ts | 2 +- apps/sim/lib/core/telemetry.ts | 11 - apps/sim/lib/credential-sets/providers.ts | 27 - apps/sim/lib/webhooks/deploy.test.ts | 2 - apps/sim/lib/webhooks/deploy.ts | 226 +- apps/sim/lib/webhooks/polling/gmail.ts | 7 +- .../lib/webhooks/polling/google-calendar.ts | 7 +- apps/sim/lib/webhooks/polling/google-drive.ts | 7 +- .../sim/lib/webhooks/polling/google-sheets.ts | 7 +- apps/sim/lib/webhooks/polling/hubspot.ts | 2 +- apps/sim/lib/webhooks/polling/outlook.ts | 2 +- apps/sim/lib/webhooks/polling/utils.test.ts | 111 +- apps/sim/lib/webhooks/polling/utils.ts | 36 +- apps/sim/lib/webhooks/processor.test.ts | 4 +- apps/sim/lib/webhooks/processor.ts | 58 +- apps/sim/lib/webhooks/utils.server.ts | 371 +- .../comparison/format-description.test.ts | 5 - .../workflows/comparison/resolve-values.ts | 19 +- .../sim/lib/workflows/orchestration/deploy.ts | 7 +- .../fork/mapping/dependent-reconfigs.test.ts | 13 +- .../fork/mapping/dependent-reconfigs.ts | 3 +- .../fork/mapping/mapping-service.ts | 4 +- .../fork/remap/remap-references.test.ts | 45 - .../workspaces/fork/remap/remap-references.ts | 12 - apps/sim/triggers/gmail/poller.ts | 23 - apps/sim/triggers/google-calendar/poller.ts | 1 - apps/sim/triggers/google-drive/poller.ts | 1 - apps/sim/triggers/google-sheets/poller.ts | 1 - apps/sim/triggers/hubspot/poller.ts | 7 - apps/sim/triggers/outlook/poller.ts | 17 - helm/sim/values.yaml | 2 - packages/audit/src/types.ts | 12 - .../0255_remove_credential_sets.sql | 12 + .../db/migrations/meta/0255_snapshot.json | 16681 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 98 - packages/testing/src/mocks/audit.mock.ts | 10 - .../src/mocks/auth-oauth-utils.mock.ts | 2 - packages/testing/src/mocks/env-flags.mock.ts | 1 - packages/testing/src/mocks/schema.mock.ts | 35 - scripts/check-api-validation-contracts.ts | 1 - 103 files changed, 16881 insertions(+), 5388 deletions(-) delete mode 100644 apps/sim/app/api/credential-sets/[id]/invite/[invitationId]/route.ts delete mode 100644 apps/sim/app/api/credential-sets/[id]/invite/route.ts delete mode 100644 apps/sim/app/api/credential-sets/[id]/members/route.ts delete mode 100644 apps/sim/app/api/credential-sets/[id]/route.ts delete mode 100644 apps/sim/app/api/credential-sets/invitations/route.ts delete mode 100644 apps/sim/app/api/credential-sets/invite/[token]/route.ts delete mode 100644 apps/sim/app/api/credential-sets/memberships/route.ts delete mode 100644 apps/sim/app/api/credential-sets/route.ts delete mode 100644 apps/sim/app/credential-account/[token]/loading.tsx delete mode 100644 apps/sim/app/credential-account/[token]/page.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/credential-sets/credential-sets.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/credential-sets/index.ts delete mode 100644 apps/sim/components/emails/invitations/polling-group-invitation-email.tsx delete mode 100644 apps/sim/hooks/queries/credential-sets.ts delete mode 100644 apps/sim/hooks/queries/utils/fetch-credential-set.ts delete mode 100644 apps/sim/lib/api/contracts/credential-sets.ts delete mode 100644 apps/sim/lib/credential-sets/providers.ts create mode 100644 packages/db/migrations/0255_remove_credential_sets.sql create mode 100644 packages/db/migrations/meta/0255_snapshot.json diff --git a/.claude/commands/add-trigger.md b/.claude/commands/add-trigger.md index f5990517573..7e8df25695a 100644 --- a/.claude/commands/add-trigger.md +++ b/.claude/commands/add-trigger.md @@ -374,7 +374,7 @@ export const {service}PollingHandler: PollingProviderHandler = { try { // For OAuth services: - const accessToken = await resolveOAuthCredential(webhookData, '{service}', requestId, logger) + const accessToken = await resolveOAuthCredential(webhookData, '{service}', requestId) const config = webhookData.providerConfig as unknown as {Service}WebhookConfig // First poll: seed state, emit nothing @@ -421,7 +421,7 @@ export const {service}PollingTrigger: TriggerConfig = { polling: true, // REQUIRED — routes to polling infrastructure subBlocks: [ - { id: 'triggerCredentials', type: 'oauth-input', title: 'Credentials', serviceId: '{service}', requiredScopes: [], required: true, mode: 'trigger', supportsCredentialSets: true }, + { id: 'triggerCredentials', type: 'oauth-input', title: 'Credentials', serviceId: '{service}', requiredScopes: [], required: true, mode: 'trigger' }, // ... service-specific config fields (dropdowns, inputs, switches) ... { id: 'triggerInstructions', type: 'text', title: 'Setup Instructions', hideFromPreview: true, mode: 'trigger', defaultValue: '...' }, ], diff --git a/.claude/rules/sim-queries.md b/.claude/rules/sim-queries.md index 4eccbe2d7ef..14acca8f2cb 100644 --- a/.claude/rules/sim-queries.md +++ b/.claude/rules/sim-queries.md @@ -34,7 +34,7 @@ Next.js rewrites **every** export of a `'use client'` module into a *client refe So any **query-key factory, standalone `requestJson` fetcher, mapper, or constant** that a server module imports must live in a **non-`'use client'`** module: - key factories → `hooks/queries/utils/-keys.ts` (see `folder-keys.ts`, `table-keys.ts`, `credential-keys.ts`) -- standalone fetchers/mappers → `hooks/queries/utils/fetch-*.ts` / `*-list-query.ts` (see `fetch-workflow-envelope.ts`, `fetch-credential-set.ts`) +- standalone fetchers/mappers → `hooks/queries/utils/fetch-*.ts` / `*-list-query.ts` (see `fetch-workflow-envelope.ts`, `fetch-workspace-credentials.ts`) The `'use client'` hook module then imports these back for its hooks. **Never** define a server-imported factory/fetcher directly in a `'use client'` hooks file — it crashes SSR (this caused the tables-page crash). Enforced for prefetch/route/trigger/block files by `scripts/check-client-boundary-imports.ts` (`bun run check:client-boundary`, run in CI). Escape hatch for a genuinely browser-only path: `// client-boundary-allow: ` on the line above the import. diff --git a/.claude/rules/sim-settings-pages.md b/.claude/rules/sim-settings-pages.md index 38e9a364554..154b45a2599 100644 --- a/.claude/rules/sim-settings-pages.md +++ b/.claude/rules/sim-settings-pages.md @@ -117,7 +117,7 @@ pairing is: This is not a stylistic guess — it is the tokenized form of the literal-pixel pairing (`text-[14px] text-[var(--text-body)]` / `text-[12px] text-[var(--text-muted)]`) already used for this exact row shape across -`member-list.tsx`, `api-keys.tsx`, `mcp.tsx`, `billing.tsx`, `credential-sets.tsx`, +`member-list.tsx`, `api-keys.tsx`, `mcp.tsx`, `billing.tsx`, `workflow-mcp-servers.tsx`, and others — keep new rows consistent with it rather than inventing a new size pairing. @@ -203,7 +203,7 @@ changes" modal: ## Detail sub-views A drill-down view reached from a list row (selected MCP server, workflow MCP -server, credential set, permission group, retention policy) renders through +server, permission group, retention policy) renders through `SettingsPanel` like a list page: pass `back={{ text, icon: ArrowLeft, onSelect }}` for the left back chip, `title` (the entity name), and the header `actions`, then render the body. Do NOT hand-roll a shell or header bar; a tab bar renders as the diff --git a/.cursor/commands/add-trigger.md b/.cursor/commands/add-trigger.md index 6e1e6ed975f..bcf54ec6587 100644 --- a/.cursor/commands/add-trigger.md +++ b/.cursor/commands/add-trigger.md @@ -369,7 +369,7 @@ export const {service}PollingHandler: PollingProviderHandler = { try { // For OAuth services: - const accessToken = await resolveOAuthCredential(webhookData, '{service}', requestId, logger) + const accessToken = await resolveOAuthCredential(webhookData, '{service}', requestId) const config = webhookData.providerConfig as unknown as {Service}WebhookConfig // First poll: seed state, emit nothing @@ -416,7 +416,7 @@ export const {service}PollingTrigger: TriggerConfig = { polling: true, // REQUIRED — routes to polling infrastructure subBlocks: [ - { id: 'triggerCredentials', type: 'oauth-input', title: 'Credentials', serviceId: '{service}', requiredScopes: [], required: true, mode: 'trigger', supportsCredentialSets: true }, + { id: 'triggerCredentials', type: 'oauth-input', title: 'Credentials', serviceId: '{service}', requiredScopes: [], required: true, mode: 'trigger' }, // ... service-specific config fields (dropdowns, inputs, switches) ... { id: 'triggerInstructions', type: 'text', title: 'Setup Instructions', hideFromPreview: true, mode: 'trigger', defaultValue: '...' }, ], diff --git a/apps/docs/content/docs/de/enterprise/index.mdx b/apps/docs/content/docs/de/enterprise/index.mdx index 02ee74cdb34..c217e859e0e 100644 --- a/apps/docs/content/docs/de/enterprise/index.mdx +++ b/apps/docs/content/docs/de/enterprise/index.mdx @@ -79,7 +79,6 @@ Für selbst gehostete Bereitstellungen können Enterprise-Funktionen über Umgeb | Variable | Beschreibung | |----------|-------------| | `SSO_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED` | Single Sign-On mit SAML/OIDC | -| `CREDENTIAL_SETS_ENABLED`, `NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED` | Polling-Gruppen für E-Mail-Trigger | | `DISABLE_INVITATIONS`, `NEXT_PUBLIC_DISABLE_INVITATIONS` | Workspace-/Organisations-Einladungen global deaktivieren | diff --git a/apps/docs/content/docs/de/triggers/index.mdx b/apps/docs/content/docs/de/triggers/index.mdx index 23b51adf279..48e5a9c7412 100644 --- a/apps/docs/content/docs/de/triggers/index.mdx +++ b/apps/docs/content/docs/de/triggers/index.mdx @@ -33,9 +33,6 @@ Verwende den Start-Block für alles, was aus dem Editor, deploy-to-API oder depl RSS- und Atom-Feeds auf neue Inhalte überwachen - - Team-Gmail- und Outlook-Postfächer überwachen - ## Schneller Vergleich @@ -46,7 +43,6 @@ Verwende den Start-Block für alles, was aus dem Editor, deploy-to-API oder depl | **Schedule** | Timer, der im Schedule-Block verwaltet wird | | **Webhook** | Bei eingehender HTTP-Anfrage | | **RSS Feed** | Neues Element im Feed veröffentlicht | -| **Email Polling Groups** | Neue E-Mail in Team-Gmail- oder Outlook-Postfächern empfangen | > Der Start-Block stellt immer `input`, `conversationId` und `files` Felder bereit. Füge benutzerdefinierte Felder zum Eingabeformat für zusätzliche strukturierte Daten hinzu. diff --git a/apps/docs/content/docs/en/integrations/index.mdx b/apps/docs/content/docs/en/integrations/index.mdx index 79c68b05011..bc65ecb4a0f 100644 --- a/apps/docs/content/docs/en/integrations/index.mdx +++ b/apps/docs/content/docs/en/integrations/index.mdx @@ -110,12 +110,6 @@ Open a connection from the **Connected** list to manage it: If you disconnect an integration that is used in a workflow, that workflow will fail at any block referencing it. Update blocks before disconnecting. -## Email polling groups - -The Gmail and Outlook email triggers can watch several team members' inboxes through a single trigger, called an **email polling group** (Team and Enterprise plans). An admin creates a group under **Settings → Email Polling**, picks Gmail or Outlook, and invites members by email; each invitee connects their own inbox through a link. On an email trigger, select the group from the credentials dropdown instead of a single account, and the trigger routes everyone's mail through the workflow. - -Inviting someone to a group grants inbox access for that trigger only, which is separate from workspace membership. - diff --git a/apps/docs/content/docs/es/triggers/index.mdx b/apps/docs/content/docs/es/triggers/index.mdx index 94fcb23e76c..730cdbcc803 100644 --- a/apps/docs/content/docs/es/triggers/index.mdx +++ b/apps/docs/content/docs/es/triggers/index.mdx @@ -33,9 +33,6 @@ Utiliza el bloque Start para todo lo que se origina desde el editor, despliegue Monitorea feeds RSS y Atom para detectar contenido nuevo - - Monitorea bandejas de entrada de Gmail y Outlook del equipo - ## Comparación rápida @@ -46,7 +43,6 @@ Utiliza el bloque Start para todo lo que se origina desde el editor, despliegue | **Schedule** | Temporizador gestionado en el bloque de programación | | **Webhook** | Al recibir una solicitud HTTP entrante | | **RSS Feed** | Nuevo elemento publicado en el feed | -| **Email Polling Groups** | Nuevo correo electrónico recibido en bandejas de entrada de Gmail o Outlook del equipo | > El bloque Start siempre expone los campos `input`, `conversationId` y `files`. Añade campos personalizados al formato de entrada para datos estructurados adicionales. diff --git a/apps/docs/content/docs/fr/enterprise/index.mdx b/apps/docs/content/docs/fr/enterprise/index.mdx index 4af1489fda0..f7546d66af4 100644 --- a/apps/docs/content/docs/fr/enterprise/index.mdx +++ b/apps/docs/content/docs/fr/enterprise/index.mdx @@ -79,7 +79,6 @@ Pour les déploiements auto-hébergés, les fonctionnalités entreprise peuvent | Variable | Description | |----------|-------------| | `SSO_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED` | Authentification unique avec SAML/OIDC | -| `CREDENTIAL_SETS_ENABLED`, `NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED` | Groupes de sondage pour les déclencheurs d'e-mail | | `DISABLE_INVITATIONS`, `NEXT_PUBLIC_DISABLE_INVITATIONS` | Désactiver globalement les invitations aux espaces de travail/organisations | diff --git a/apps/docs/content/docs/fr/triggers/index.mdx b/apps/docs/content/docs/fr/triggers/index.mdx index 2102b482f7e..ef9b408df06 100644 --- a/apps/docs/content/docs/fr/triggers/index.mdx +++ b/apps/docs/content/docs/fr/triggers/index.mdx @@ -33,9 +33,6 @@ Utilisez le bloc Démarrer pour tout ce qui provient de l'éditeur, du déploiem Surveiller les flux RSS et Atom pour détecter du nouveau contenu - - Surveiller les boîtes de réception Gmail et Outlook de l'équipe - ## Comparaison rapide @@ -46,7 +43,6 @@ Utilisez le bloc Démarrer pour tout ce qui provient de l'éditeur, du déploiem | **Schedule** | Minuteur géré dans le bloc de planification | | **Webhook** | Lors d'une requête HTTP entrante | | **RSS Feed** | Nouvel élément publié dans le flux | -| **Email Polling Groups** | Nouvel e-mail reçu dans les boîtes de réception Gmail ou Outlook de l'équipe | > Le bloc Démarrer expose toujours les champs `input`, `conversationId` et `files`. Ajoutez des champs personnalisés au format d'entrée pour des données structurées supplémentaires. diff --git a/apps/docs/content/docs/ja/enterprise/index.mdx b/apps/docs/content/docs/ja/enterprise/index.mdx index b769395011d..473697ad064 100644 --- a/apps/docs/content/docs/ja/enterprise/index.mdx +++ b/apps/docs/content/docs/ja/enterprise/index.mdx @@ -78,7 +78,6 @@ Simのホストキーの代わりに、AIモデルプロバイダー用の独自 | 変数 | 説明 | |----------|-------------| | `SSO_ENABLED`、`NEXT_PUBLIC_SSO_ENABLED` | SAML/OIDCによるシングルサインオン | -| `CREDENTIAL_SETS_ENABLED`、`NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED` | メールトリガー用のポーリンググループ | | `DISABLE_INVITATIONS`、`NEXT_PUBLIC_DISABLE_INVITATIONS` | ワークスペース/組織への招待をグローバルに無効化 | diff --git a/apps/docs/content/docs/ja/triggers/index.mdx b/apps/docs/content/docs/ja/triggers/index.mdx index 9095371cf32..7bf66bd07c9 100644 --- a/apps/docs/content/docs/ja/triggers/index.mdx +++ b/apps/docs/content/docs/ja/triggers/index.mdx @@ -33,9 +33,6 @@ import { Image } from '@/components/ui/image' RSSおよびAtomフィードの新しいコンテンツを監視 - - チームのGmailおよびOutlook受信トレイを監視 - ## クイック比較 @@ -46,7 +43,6 @@ import { Image } from '@/components/ui/image' | **Schedule** | スケジュールブロックで管理されるタイマー | | **Webhook** | インバウンドHTTPリクエスト時 | | **RSS Feed** | フィードに新しいアイテムが公開された時 | -| **Email Polling Groups** | チームのGmailまたはOutlook受信トレイに新しいメールが受信された時 | > スタートブロックは常に `input`、`conversationId`、および `files` フィールドを公開します。追加の構造化データには入力フォーマットにカスタムフィールドを追加してください。 @@ -69,25 +65,3 @@ import { Image } from '@/components/ui/image' ワークフローに複数のトリガーがある場合、最も優先度の高いトリガーが実行されます。例えば、スタートブロックとウェブフックトリガーの両方がある場合、実行をクリックするとスタートブロックが実行されます。 **モックペイロードを持つ外部トリガー**: 外部トリガー(ウェブフックと連携)が手動で実行される場合、Simはトリガーの予想されるデータ構造に基づいてモックペイロードを自動生成します。これにより、テスト中に下流のブロックが変数を正しく解決できるようになります。 - -## Email Polling Groups - -Polling Groupsを使用すると、単一のトリガーで複数のチームメンバーのGmailまたはOutlook受信トレイを監視できます。TeamまたはEnterpriseプランが必要です。 - -**Polling Groupの作成**(管理者/オーナー) - -1. **設定 → Email Polling**に移動 -2. **作成**をクリックし、GmailまたはOutlookを選択 -3. グループの名前を入力 - -**メンバーの招待** - -1. Polling Groupの**メンバーを追加**をクリック -2. メールアドレスを入力(カンマまたは改行で区切る、またはCSVをドラッグ&ドロップ) -3. **招待を送信**をクリック - -招待された人は、アカウントを接続するためのリンクが記載されたメールを受信します。接続されると、その受信トレイは自動的にPolling Groupに含まれます。招待された人は、Sim組織のメンバーである必要はありません。 - -**ワークフローでの使用** - -メールトリガーを設定する際、個別のアカウントではなく、認証情報ドロップダウンからPolling Groupを選択します。システムは各メンバーのWebhookを作成し、すべてのメールをワークフローを通じてルーティングします。 diff --git a/apps/docs/content/docs/zh/enterprise/index.mdx b/apps/docs/content/docs/zh/enterprise/index.mdx index 25c7d8da7ed..2eb7f2bc382 100644 --- a/apps/docs/content/docs/zh/enterprise/index.mdx +++ b/apps/docs/content/docs/zh/enterprise/index.mdx @@ -78,7 +78,6 @@ Sim 企业版为需要更高安全性、合规性和管理能力的组织提供 | 变量 | 描述 | |----------|-------------| | `SSO_ENABLED`,`NEXT_PUBLIC_SSO_ENABLED` | 使用 SAML/OIDC 的单点登录 | -| `CREDENTIAL_SETS_ENABLED`,`NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED` | 用于邮件触发器的轮询组 | | `DISABLE_INVITATIONS`,`NEXT_PUBLIC_DISABLE_INVITATIONS` | 全局禁用工作区/组织邀请 | diff --git a/apps/docs/content/docs/zh/triggers/index.mdx b/apps/docs/content/docs/zh/triggers/index.mdx index a17b11dd325..bdbf48cc1b4 100644 --- a/apps/docs/content/docs/zh/triggers/index.mdx +++ b/apps/docs/content/docs/zh/triggers/index.mdx @@ -33,9 +33,6 @@ import { Image } from '@/components/ui/image' 监控 RSS 和 Atom 订阅源的新内容 - - 监控团队 Gmail 和 Outlook 收件箱 - ## 快速对比 @@ -46,7 +43,6 @@ import { Image } from '@/components/ui/image' | **Schedule** | 在 schedule 块中管理的定时器 | | **Webhook** | 收到入站 HTTP 请求时 | | **RSS Feed** | 订阅源中有新内容发布时 | -| **Email Polling Groups** | 团队 Gmail 或 Outlook 收件箱收到新邮件时 | > Start 块始终公开 `input`、`conversationId` 和 `files` 字段。通过向输入格式添加自定义字段来增加结构化数据。 diff --git a/apps/sim/app/(auth)/signup/signup-form.tsx b/apps/sim/app/(auth)/signup/signup-form.tsx index 5553cfa6fe9..e7aa86ea1f9 100644 --- a/apps/sim/app/(auth)/signup/signup-form.tsx +++ b/apps/sim/app/(auth)/signup/signup-form.tsx @@ -121,10 +121,7 @@ function SignupFormContent({ } const redirectUrl = isValidRedirectUrl ? rawRedirectUrl : '' const isInviteFlow = useMemo( - () => - searchParams.get('invite_flow') === 'true' || - redirectUrl.startsWith('/invite/') || - redirectUrl.startsWith('/credential-account/'), + () => searchParams.get('invite_flow') === 'true' || redirectUrl.startsWith('/invite/'), [searchParams, redirectUrl] ) diff --git a/apps/sim/app/api/auth/oauth/disconnect/route.test.ts b/apps/sim/app/api/auth/oauth/disconnect/route.test.ts index 3681ac5cc36..97215a0d923 100644 --- a/apps/sim/app/api/auth/oauth/disconnect/route.test.ts +++ b/apps/sim/app/api/auth/oauth/disconnect/route.test.ts @@ -13,16 +13,8 @@ import { } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockSyncAllWebhooksForCredentialSet } = vi.hoisted(() => ({ - mockSyncAllWebhooksForCredentialSet: vi.fn().mockResolvedValue({}), -})) - vi.mock('@sim/db', () => dbChainMock) -vi.mock('@/lib/webhooks/utils.server', () => ({ - syncAllWebhooksForCredentialSet: mockSyncAllWebhooksForCredentialSet, -})) - vi.mock('@sim/audit', () => auditMock) import { POST } from '@/app/api/auth/oauth/disconnect/route' diff --git a/apps/sim/app/api/auth/oauth/disconnect/route.ts b/apps/sim/app/api/auth/oauth/disconnect/route.ts index 101cbed9e44..44840b2d568 100644 --- a/apps/sim/app/api/auth/oauth/disconnect/route.ts +++ b/apps/sim/app/api/auth/oauth/disconnect/route.ts @@ -1,6 +1,6 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { db } from '@sim/db' -import { account, credential, credentialSet, credentialSetMember } from '@sim/db/schema' +import { account, credential } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, eq, inArray, like, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' @@ -11,7 +11,6 @@ import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { deleteCredential } from '@/lib/credentials/deletion' import { captureServerEvent } from '@/lib/posthog/server' -import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server' export const dynamic = 'force-dynamic' @@ -104,49 +103,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { await db.delete(account).where(inArray(account.id, targetAccountIds)) } - // Sync webhooks for all credential sets the user is a member of - // This removes webhooks that were using the disconnected credential - const userMemberships = await db - .select({ - id: credentialSetMember.id, - credentialSetId: credentialSetMember.credentialSetId, - providerId: credentialSet.providerId, - }) - .from(credentialSetMember) - .innerJoin(credentialSet, eq(credentialSetMember.credentialSetId, credentialSet.id)) - .where( - and( - eq(credentialSetMember.userId, session.user.id), - eq(credentialSetMember.status, 'active') - ) - ) - - for (const membership of userMemberships) { - // Only sync if the credential set matches this provider - // Credential sets store OAuth provider IDs like 'google-email' or 'outlook' - const matchesProvider = - membership.providerId === provider || - membership.providerId === providerId || - membership.providerId?.startsWith(`${provider}-`) - - if (matchesProvider) { - try { - await syncAllWebhooksForCredentialSet(membership.credentialSetId, requestId) - logger.info(`[${requestId}] Synced webhooks after credential disconnect`, { - credentialSetId: membership.credentialSetId, - provider, - }) - } catch (error) { - // Log but don't fail the disconnect - credential is already removed - logger.error(`[${requestId}] Failed to sync webhooks after credential disconnect`, { - credentialSetId: membership.credentialSetId, - provider, - error, - }) - } - } - } - recordAudit({ workspaceId: null, actorId: session.user.id, diff --git a/apps/sim/app/api/auth/oauth/utils.ts b/apps/sim/app/api/auth/oauth/utils.ts index 732f157d581..041a6a2ce53 100644 --- a/apps/sim/app/api/auth/oauth/utils.ts +++ b/apps/sim/app/api/auth/oauth/utils.ts @@ -1,9 +1,9 @@ import { createSign } from 'crypto' import { db } from '@sim/db' -import { account, credential, credentialSetMember } from '@sim/db/schema' +import { account, credential } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' -import { and, desc, eq, inArray } from 'drizzle-orm' +import { and, desc, eq } from 'drizzle-orm' import { withLeaderLock } from '@/lib/concurrency/leader-lock' import { coalesceLocally } from '@/lib/concurrency/singleflight' import { decryptSecret } from '@/lib/core/security/encryption' @@ -639,89 +639,3 @@ export async function refreshTokenIfNeeded( } throw new Error('Failed to refresh token') } - -export interface CredentialSetCredential { - userId: string - credentialId: string - accessToken: string - providerId: string -} - -export async function getCredentialsForCredentialSet( - credentialSetId: string, - providerId: string -): Promise { - logger.info(`Getting credentials for credential set ${credentialSetId}, provider ${providerId}`) - - const members = await db - .select({ userId: credentialSetMember.userId }) - .from(credentialSetMember) - .where( - and( - eq(credentialSetMember.credentialSetId, credentialSetId), - eq(credentialSetMember.status, 'active') - ) - ) - - logger.info(`Found ${members.length} active members in credential set ${credentialSetId}`) - - if (members.length === 0) { - logger.warn(`No active members found for credential set ${credentialSetId}`) - return [] - } - - const userIds = members.map((m) => m.userId) - logger.debug(`Member user IDs: ${userIds.join(', ')}`) - - const credentials = await db - .select({ - id: account.id, - userId: account.userId, - providerId: account.providerId, - accessToken: account.accessToken, - refreshToken: account.refreshToken, - accessTokenExpiresAt: account.accessTokenExpiresAt, - }) - .from(account) - .where(and(inArray(account.userId, userIds), eq(account.providerId, providerId))) - - logger.info( - `Found ${credentials.length} credentials with provider ${providerId} for ${members.length} members` - ) - - const results: CredentialSetCredential[] = [] - - for (const cred of credentials) { - const now = new Date() - const tokenExpiry = cred.accessTokenExpiresAt - const shouldRefresh = - !!cred.refreshToken && (!cred.accessToken || (tokenExpiry && tokenExpiry < now)) - - let accessToken = cred.accessToken - - if (shouldRefresh && cred.refreshToken) { - const fresh = await performCoalescedRefresh({ - accountId: cred.id, - providerId, - refreshToken: cred.refreshToken, - userId: cred.userId, - }) - if (fresh) accessToken = fresh - } - - if (accessToken) { - results.push({ - userId: cred.userId, - credentialId: cred.id, - accessToken, - providerId, - }) - } - } - - logger.info( - `Found ${results.length} valid credentials for credential set ${credentialSetId}, provider ${providerId}` - ) - - return results -} diff --git a/apps/sim/app/api/credential-sets/[id]/invite/[invitationId]/route.ts b/apps/sim/app/api/credential-sets/[id]/invite/[invitationId]/route.ts deleted file mode 100644 index cdd1fc4b9f5..00000000000 --- a/apps/sim/app/api/credential-sets/[id]/invite/[invitationId]/route.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' -import { db } from '@sim/db' -import { credentialSet, credentialSetInvitation, member, organization, user } from '@sim/db/schema' -import { createLogger } from '@sim/logger' -import { and, eq } from 'drizzle-orm' -import { type NextRequest, NextResponse } from 'next/server' -import { getEmailSubject, renderPollingGroupInvitationEmail } from '@/components/emails' -import { credentialSetInvitationParamsSchema } from '@/lib/api/contracts/credential-sets' -import { getSession } from '@/lib/auth' -import { hasCredentialSetsAccess } from '@/lib/billing' -import { getBaseUrl } from '@/lib/core/utils/urls' -import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { sendEmail } from '@/lib/messaging/email/mailer' - -const logger = createLogger('CredentialSetInviteResend') - -async function getCredentialSetWithAccess(credentialSetId: string, userId: string) { - const [set] = await db - .select({ - id: credentialSet.id, - organizationId: credentialSet.organizationId, - name: credentialSet.name, - providerId: credentialSet.providerId, - }) - .from(credentialSet) - .where(eq(credentialSet.id, credentialSetId)) - .limit(1) - - if (!set) return null - - const [membership] = await db - .select({ role: member.role }) - .from(member) - .where(and(eq(member.userId, userId), eq(member.organizationId, set.organizationId))) - .limit(1) - - if (!membership) return null - - return { set, role: membership.role } -} - -export const POST = withRouteHandler( - async ( - req: NextRequest, - { params }: { params: Promise<{ id: string; invitationId: string }> } - ) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - const { id, invitationId } = credentialSetInvitationParamsSchema.parse(await params) - - try { - const result = await getCredentialSetWithAccess(id, session.user.id) - - if (!result) { - return NextResponse.json({ error: 'Credential set not found' }, { status: 404 }) - } - - if (result.role !== 'admin' && result.role !== 'owner') { - return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 }) - } - - const [invitation] = await db - .select() - .from(credentialSetInvitation) - .where( - and( - eq(credentialSetInvitation.id, invitationId), - eq(credentialSetInvitation.credentialSetId, id) - ) - ) - .limit(1) - - if (!invitation) { - return NextResponse.json({ error: 'Invitation not found' }, { status: 404 }) - } - - if (invitation.status !== 'pending') { - return NextResponse.json( - { error: 'Only pending invitations can be resent' }, - { status: 400 } - ) - } - - // Update expiration - const newExpiresAt = new Date() - newExpiresAt.setDate(newExpiresAt.getDate() + 7) - - await db - .update(credentialSetInvitation) - .set({ expiresAt: newExpiresAt }) - .where(eq(credentialSetInvitation.id, invitationId)) - - const inviteUrl = `${getBaseUrl()}/credential-account/${invitation.token}` - - // Send email if email address exists - if (invitation.email) { - try { - const [inviter] = await db - .select({ name: user.name }) - .from(user) - .where(eq(user.id, session.user.id)) - .limit(1) - - const [org] = await db - .select({ name: organization.name }) - .from(organization) - .where(eq(organization.id, result.set.organizationId)) - .limit(1) - - const provider = (result.set.providerId as 'google-email' | 'outlook') || 'google-email' - const emailHtml = await renderPollingGroupInvitationEmail({ - inviterName: inviter?.name || 'A team member', - organizationName: org?.name || 'your organization', - pollingGroupName: result.set.name, - provider, - inviteLink: inviteUrl, - }) - - const emailResult = await sendEmail({ - to: invitation.email, - subject: getEmailSubject('polling-group-invitation'), - html: emailHtml, - emailType: 'transactional', - }) - - if (!emailResult.success) { - logger.warn('Failed to resend invitation email', { - email: invitation.email, - error: emailResult.message, - }) - return NextResponse.json({ error: 'Failed to send email' }, { status: 500 }) - } - } catch (emailError) { - logger.error('Error sending invitation email', emailError) - return NextResponse.json({ error: 'Failed to send email' }, { status: 500 }) - } - } - - logger.info('Resent credential set invitation', { - credentialSetId: id, - invitationId, - userId: session.user.id, - }) - - recordAudit({ - workspaceId: null, - actorId: session.user.id, - actorName: session.user.name, - actorEmail: session.user.email, - action: AuditAction.CREDENTIAL_SET_INVITATION_RESENT, - resourceType: AuditResourceType.CREDENTIAL_SET, - resourceId: id, - resourceName: result.set.name, - description: `Resent credential set invitation to ${invitation.email}`, - metadata: { - invitationId, - targetEmail: invitation.email, - providerId: result.set.providerId, - credentialSetName: result.set.name, - }, - request: req, - }) - - return NextResponse.json({ success: true }) - } catch (error) { - logger.error('Error resending invitation', error) - return NextResponse.json({ error: 'Failed to resend invitation' }, { status: 500 }) - } - } -) diff --git a/apps/sim/app/api/credential-sets/[id]/invite/route.ts b/apps/sim/app/api/credential-sets/[id]/invite/route.ts deleted file mode 100644 index 0594c9f5c5e..00000000000 --- a/apps/sim/app/api/credential-sets/[id]/invite/route.ts +++ /dev/null @@ -1,306 +0,0 @@ -import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' -import { db } from '@sim/db' -import { credentialSet, credentialSetInvitation, member, organization, user } from '@sim/db/schema' -import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' -import { and, eq } from 'drizzle-orm' -import { type NextRequest, NextResponse } from 'next/server' -import { getEmailSubject, renderPollingGroupInvitationEmail } from '@/components/emails' -import { - cancelCredentialSetInvitationQuerySchema, - createCredentialSetInvitationContract, -} from '@/lib/api/contracts/credential-sets' -import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' -import { getSession } from '@/lib/auth' -import { hasCredentialSetsAccess } from '@/lib/billing' -import { getBaseUrl } from '@/lib/core/utils/urls' -import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { sendEmail } from '@/lib/messaging/email/mailer' - -const logger = createLogger('CredentialSetInvite') - -async function getCredentialSetWithAccess(credentialSetId: string, userId: string) { - const [set] = await db - .select({ - id: credentialSet.id, - organizationId: credentialSet.organizationId, - name: credentialSet.name, - providerId: credentialSet.providerId, - }) - .from(credentialSet) - .where(eq(credentialSet.id, credentialSetId)) - .limit(1) - - if (!set) return null - - const [membership] = await db - .select({ role: member.role }) - .from(member) - .where(and(eq(member.userId, userId), eq(member.organizationId, set.organizationId))) - .limit(1) - - if (!membership) return null - - return { set, role: membership.role } -} - -export const GET = withRouteHandler( - async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - const { id } = await params - const result = await getCredentialSetWithAccess(id, session.user.id) - - if (!result) { - return NextResponse.json({ error: 'Credential set not found' }, { status: 404 }) - } - - if (result.role !== 'admin' && result.role !== 'owner') { - return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 }) - } - - const invitations = await db - .select({ - id: credentialSetInvitation.id, - credentialSetId: credentialSetInvitation.credentialSetId, - email: credentialSetInvitation.email, - status: credentialSetInvitation.status, - expiresAt: credentialSetInvitation.expiresAt, - createdAt: credentialSetInvitation.createdAt, - invitedBy: credentialSetInvitation.invitedBy, - }) - .from(credentialSetInvitation) - .where(eq(credentialSetInvitation.credentialSetId, id)) - - return NextResponse.json({ invitations }) - } -) - -export const POST = withRouteHandler( - async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - try { - const parsed = await parseRequest(createCredentialSetInvitationContract, req, context, { - validationErrorResponse: (error) => - NextResponse.json({ error: getValidationErrorMessage(error) }, { status: 400 }), - }) - if (!parsed.success) return parsed.response - - const { id } = parsed.data.params - const { email } = parsed.data.body - - const result = await getCredentialSetWithAccess(id, session.user.id) - - if (!result) { - return NextResponse.json({ error: 'Credential set not found' }, { status: 404 }) - } - - if (result.role !== 'admin' && result.role !== 'owner') { - return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 }) - } - - const token = generateId() - const expiresAt = new Date() - expiresAt.setDate(expiresAt.getDate() + 7) - - const invitation = { - id: generateId(), - credentialSetId: id, - email: email || null, - token, - invitedBy: session.user.id, - status: 'pending' as const, - expiresAt, - createdAt: new Date(), - } - - await db.insert(credentialSetInvitation).values(invitation) - - const inviteUrl = `${getBaseUrl()}/credential-account/${token}` - - // Send email if email address was provided - if (email) { - try { - // Get inviter name - const [inviter] = await db - .select({ name: user.name }) - .from(user) - .where(eq(user.id, session.user.id)) - .limit(1) - - // Get organization name - const [org] = await db - .select({ name: organization.name }) - .from(organization) - .where(eq(organization.id, result.set.organizationId)) - .limit(1) - - const provider = (result.set.providerId as 'google-email' | 'outlook') || 'google-email' - const emailHtml = await renderPollingGroupInvitationEmail({ - inviterName: inviter?.name || 'A team member', - organizationName: org?.name || 'your organization', - pollingGroupName: result.set.name, - provider, - inviteLink: inviteUrl, - }) - - const emailResult = await sendEmail({ - to: email, - subject: getEmailSubject('polling-group-invitation'), - html: emailHtml, - emailType: 'transactional', - }) - - if (!emailResult.success) { - logger.warn('Failed to send invitation email', { - email, - error: emailResult.message, - }) - } - } catch (emailError) { - logger.error('Error sending invitation email', emailError) - // Don't fail the invitation creation if email fails - } - } - - logger.info('Created credential set invitation', { - credentialSetId: id, - invitationId: invitation.id, - userId: session.user.id, - emailSent: !!email, - }) - - recordAudit({ - workspaceId: null, - actorId: session.user.id, - action: AuditAction.CREDENTIAL_SET_INVITATION_CREATED, - resourceType: AuditResourceType.CREDENTIAL_SET, - resourceId: id, - actorName: session.user.name ?? undefined, - actorEmail: session.user.email ?? undefined, - resourceName: result.set.name, - description: `Created invitation for credential set "${result.set.name}"${email ? ` to ${email}` : ''}`, - metadata: { - invitationId: invitation.id, - targetEmail: email || undefined, - providerId: result.set.providerId, - credentialSetName: result.set.name, - }, - request: req, - }) - - return NextResponse.json({ - invitation: { - ...invitation, - inviteUrl, - }, - }) - } catch (error) { - logger.error('Error creating invitation', error) - return NextResponse.json({ error: 'Failed to create invitation' }, { status: 500 }) - } - } -) - -export const DELETE = withRouteHandler( - async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - const { id } = await params - const { searchParams } = new URL(req.url) - const validation = cancelCredentialSetInvitationQuerySchema.safeParse({ - invitationId: searchParams.get('invitationId') ?? '', - }) - - if (!validation.success) { - return NextResponse.json( - { error: getValidationErrorMessage(validation.error) }, - { status: 400 } - ) - } - - const { invitationId } = validation.data - - try { - const result = await getCredentialSetWithAccess(id, session.user.id) - - if (!result) { - return NextResponse.json({ error: 'Credential set not found' }, { status: 404 }) - } - - if (result.role !== 'admin' && result.role !== 'owner') { - return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 }) - } - - const [revokedInvitation] = await db - .update(credentialSetInvitation) - .set({ status: 'cancelled' }) - .where( - and( - eq(credentialSetInvitation.id, invitationId), - eq(credentialSetInvitation.credentialSetId, id) - ) - ) - .returning({ email: credentialSetInvitation.email }) - - recordAudit({ - workspaceId: null, - actorId: session.user.id, - action: AuditAction.CREDENTIAL_SET_INVITATION_REVOKED, - resourceType: AuditResourceType.CREDENTIAL_SET, - resourceId: id, - actorName: session.user.name ?? undefined, - actorEmail: session.user.email ?? undefined, - resourceName: result.set.name, - description: `Revoked invitation "${invitationId}" for credential set "${result.set.name}"`, - metadata: { targetEmail: revokedInvitation?.email ?? undefined }, - request: req, - }) - - return NextResponse.json({ success: true }) - } catch (error) { - logger.error('Error cancelling invitation', error) - return NextResponse.json({ error: 'Failed to cancel invitation' }, { status: 500 }) - } - } -) diff --git a/apps/sim/app/api/credential-sets/[id]/members/route.ts b/apps/sim/app/api/credential-sets/[id]/members/route.ts deleted file mode 100644 index b49aadcfae0..00000000000 --- a/apps/sim/app/api/credential-sets/[id]/members/route.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' -import { db } from '@sim/db' -import { account, credentialSet, credentialSetMember, member, user } from '@sim/db/schema' -import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' -import { and, eq, inArray } from 'drizzle-orm' -import { type NextRequest, NextResponse } from 'next/server' -import { removeCredentialSetMemberQuerySchema } from '@/lib/api/contracts/credential-sets' -import { getValidationErrorMessage } from '@/lib/api/server' -import { getSession } from '@/lib/auth' -import { hasCredentialSetsAccess } from '@/lib/billing' -import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server' - -const logger = createLogger('CredentialSetMembers') - -async function getCredentialSetWithAccess(credentialSetId: string, userId: string) { - const [set] = await db - .select({ - id: credentialSet.id, - name: credentialSet.name, - organizationId: credentialSet.organizationId, - providerId: credentialSet.providerId, - }) - .from(credentialSet) - .where(eq(credentialSet.id, credentialSetId)) - .limit(1) - - if (!set) return null - - const [membership] = await db - .select({ role: member.role }) - .from(member) - .where(and(eq(member.userId, userId), eq(member.organizationId, set.organizationId))) - .limit(1) - - if (!membership) return null - - return { set, role: membership.role } -} - -export const GET = withRouteHandler( - async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - const { id } = await params - const result = await getCredentialSetWithAccess(id, session.user.id) - - if (!result) { - return NextResponse.json({ error: 'Credential set not found' }, { status: 404 }) - } - - const members = await db - .select({ - id: credentialSetMember.id, - userId: credentialSetMember.userId, - status: credentialSetMember.status, - joinedAt: credentialSetMember.joinedAt, - createdAt: credentialSetMember.createdAt, - userName: user.name, - userEmail: user.email, - userImage: user.image, - }) - .from(credentialSetMember) - .leftJoin(user, eq(credentialSetMember.userId, user.id)) - .where(eq(credentialSetMember.credentialSetId, id)) - - // Get credentials for all active members filtered by the polling group's provider - const activeMembers = members.filter((m) => m.status === 'active') - const memberUserIds = activeMembers.map((m) => m.userId) - - let credentials: { userId: string; providerId: string; accountId: string }[] = [] - if (memberUserIds.length > 0 && result.set.providerId) { - credentials = await db - .select({ - userId: account.userId, - providerId: account.providerId, - accountId: account.accountId, - }) - .from(account) - .where( - and(inArray(account.userId, memberUserIds), eq(account.providerId, result.set.providerId)) - ) - } - - // Group credentials by userId - const credentialsByUser = credentials.reduce( - (acc, cred) => { - if (!acc[cred.userId]) { - acc[cred.userId] = [] - } - acc[cred.userId].push({ - providerId: cred.providerId, - accountId: cred.accountId, - }) - return acc - }, - {} as Record - ) - - // Attach credentials to members - const membersWithCredentials = members.map((m) => ({ - ...m, - credentials: credentialsByUser[m.userId] || [], - })) - - return NextResponse.json({ members: membersWithCredentials }) - } -) - -export const DELETE = withRouteHandler( - async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - const { id } = await params - const { searchParams } = new URL(req.url) - const validation = removeCredentialSetMemberQuerySchema.safeParse({ - memberId: searchParams.get('memberId') ?? '', - }) - - if (!validation.success) { - return NextResponse.json( - { error: getValidationErrorMessage(validation.error) }, - { status: 400 } - ) - } - - const { memberId } = validation.data - - try { - const result = await getCredentialSetWithAccess(id, session.user.id) - - if (!result) { - return NextResponse.json({ error: 'Credential set not found' }, { status: 404 }) - } - - if (result.role !== 'admin' && result.role !== 'owner') { - return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 }) - } - - const [memberToRemove] = await db - .select({ - id: credentialSetMember.id, - credentialSetId: credentialSetMember.credentialSetId, - userId: credentialSetMember.userId, - status: credentialSetMember.status, - email: user.email, - }) - .from(credentialSetMember) - .innerJoin(user, eq(credentialSetMember.userId, user.id)) - .where( - and(eq(credentialSetMember.id, memberId), eq(credentialSetMember.credentialSetId, id)) - ) - .limit(1) - - if (!memberToRemove) { - return NextResponse.json({ error: 'Member not found' }, { status: 404 }) - } - - const requestId = generateId().slice(0, 8) - - await db.delete(credentialSetMember).where(eq(credentialSetMember.id, memberId)) - - // Runs after the deletion commits: the sync performs external HTTP - // (OAuth refresh, provider unsubscribe) and must not hold a pooled - // connection. A sync failure must not fail the committed mutation — - // it self-heals on the next membership change/deploy. - try { - const syncResult = await syncAllWebhooksForCredentialSet(id, requestId) - logger.info('Synced webhooks after member removed', { - credentialSetId: id, - ...syncResult, - }) - } catch (syncError) { - logger.error('Webhook sync failed after member removal', { - credentialSetId: id, - error: syncError, - }) - } - - logger.info('Removed member from credential set', { - credentialSetId: id, - memberId, - userId: session.user.id, - }) - - recordAudit({ - workspaceId: null, - actorId: session.user.id, - action: AuditAction.CREDENTIAL_SET_MEMBER_REMOVED, - resourceType: AuditResourceType.CREDENTIAL_SET, - resourceId: id, - actorName: session.user.name ?? undefined, - actorEmail: session.user.email ?? undefined, - resourceName: result.set.name, - description: `Removed member from credential set "${result.set.name}"`, - metadata: { - memberId, - memberUserId: memberToRemove.userId, - targetEmail: memberToRemove.email ?? undefined, - providerId: result.set.providerId, - }, - request: req, - }) - - return NextResponse.json({ success: true }) - } catch (error) { - logger.error('Error removing member from credential set', error) - return NextResponse.json({ error: 'Failed to remove member' }, { status: 500 }) - } - } -) diff --git a/apps/sim/app/api/credential-sets/[id]/route.ts b/apps/sim/app/api/credential-sets/[id]/route.ts deleted file mode 100644 index 8e7241382ee..00000000000 --- a/apps/sim/app/api/credential-sets/[id]/route.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' -import { db } from '@sim/db' -import { credentialSet, credentialSetMember, member, user } from '@sim/db/schema' -import { createLogger } from '@sim/logger' -import { and, count, eq } from 'drizzle-orm' -import { type NextRequest, NextResponse } from 'next/server' -import { updateCredentialSetContract } from '@/lib/api/contracts/credential-sets' -import { parseRequest } from '@/lib/api/server' -import { getSession } from '@/lib/auth' -import { hasCredentialSetsAccess } from '@/lib/billing' -import { withRouteHandler } from '@/lib/core/utils/with-route-handler' - -const logger = createLogger('CredentialSet') - -async function getCredentialSetWithAccess(credentialSetId: string, userId: string) { - const [set] = await db - .select({ - id: credentialSet.id, - organizationId: credentialSet.organizationId, - name: credentialSet.name, - description: credentialSet.description, - providerId: credentialSet.providerId, - createdBy: credentialSet.createdBy, - createdAt: credentialSet.createdAt, - updatedAt: credentialSet.updatedAt, - creatorName: user.name, - creatorEmail: user.email, - }) - .from(credentialSet) - .leftJoin(user, eq(credentialSet.createdBy, user.id)) - .where(eq(credentialSet.id, credentialSetId)) - .limit(1) - - if (!set) return null - - const [membership] = await db - .select({ role: member.role }) - .from(member) - .where(and(eq(member.userId, userId), eq(member.organizationId, set.organizationId))) - .limit(1) - - if (!membership) return null - - const [memberCount] = await db - .select({ count: count() }) - .from(credentialSetMember) - .where( - and( - eq(credentialSetMember.credentialSetId, credentialSetId), - eq(credentialSetMember.status, 'active') - ) - ) - - return { - set: { - ...set, - memberCount: memberCount?.count ?? 0, - }, - role: membership.role, - } -} - -export const GET = withRouteHandler( - async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - const { id } = await params - const result = await getCredentialSetWithAccess(id, session.user.id) - - if (!result) { - return NextResponse.json({ error: 'Credential set not found' }, { status: 404 }) - } - - return NextResponse.json({ credentialSet: result.set }) - } -) - -export const PUT = withRouteHandler( - async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - const { id } = await context.params - - try { - const result = await getCredentialSetWithAccess(id, session.user.id) - - if (!result) { - return NextResponse.json({ error: 'Credential set not found' }, { status: 404 }) - } - - if (result.role !== 'admin' && result.role !== 'owner') { - return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 }) - } - - const parsed = await parseRequest(updateCredentialSetContract, req, context) - if (!parsed.success) return parsed.response - const updates = parsed.data.body - - if (updates.name) { - const existingSet = await db - .select({ id: credentialSet.id }) - .from(credentialSet) - .where( - and( - eq(credentialSet.organizationId, result.set.organizationId), - eq(credentialSet.name, updates.name) - ) - ) - .limit(1) - - if (existingSet.length > 0 && existingSet[0].id !== id) { - return NextResponse.json( - { error: 'A credential set with this name already exists' }, - { status: 409 } - ) - } - } - - await db - .update(credentialSet) - .set({ - ...updates, - updatedAt: new Date(), - }) - .where(eq(credentialSet.id, id)) - - const [updated] = await db - .select() - .from(credentialSet) - .where(eq(credentialSet.id, id)) - .limit(1) - - recordAudit({ - workspaceId: null, - actorId: session.user.id, - action: AuditAction.CREDENTIAL_SET_UPDATED, - resourceType: AuditResourceType.CREDENTIAL_SET, - resourceId: id, - actorName: session.user.name ?? undefined, - actorEmail: session.user.email ?? undefined, - resourceName: updated?.name ?? result.set.name, - description: `Updated credential set "${updated?.name ?? result.set.name}"`, - metadata: { - organizationId: result.set.organizationId, - providerId: result.set.providerId, - updatedFields: Object.keys(updates).filter( - (k) => updates[k as keyof typeof updates] !== undefined - ), - }, - request: req, - }) - - return NextResponse.json({ credentialSet: updated }) - } catch (error) { - logger.error('Error updating credential set', error) - return NextResponse.json({ error: 'Failed to update credential set' }, { status: 500 }) - } - } -) - -export const DELETE = withRouteHandler( - async (req: NextRequest, { params }: { params: Promise<{ id: string }> }) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - const { id } = await params - - try { - const result = await getCredentialSetWithAccess(id, session.user.id) - - if (!result) { - return NextResponse.json({ error: 'Credential set not found' }, { status: 404 }) - } - - if (result.role !== 'admin' && result.role !== 'owner') { - return NextResponse.json({ error: 'Admin or owner permissions required' }, { status: 403 }) - } - - await db.delete(credentialSetMember).where(eq(credentialSetMember.credentialSetId, id)) - await db.delete(credentialSet).where(eq(credentialSet.id, id)) - - logger.info('Deleted credential set', { credentialSetId: id, userId: session.user.id }) - - recordAudit({ - workspaceId: null, - actorId: session.user.id, - action: AuditAction.CREDENTIAL_SET_DELETED, - resourceType: AuditResourceType.CREDENTIAL_SET, - resourceId: id, - actorName: session.user.name ?? undefined, - actorEmail: session.user.email ?? undefined, - resourceName: result.set.name, - description: `Deleted credential set "${result.set.name}"`, - metadata: { organizationId: result.set.organizationId, providerId: result.set.providerId }, - request: req, - }) - - return NextResponse.json({ success: true }) - } catch (error) { - logger.error('Error deleting credential set', error) - return NextResponse.json({ error: 'Failed to delete credential set' }, { status: 500 }) - } - } -) diff --git a/apps/sim/app/api/credential-sets/invitations/route.ts b/apps/sim/app/api/credential-sets/invitations/route.ts deleted file mode 100644 index a6da9ca82b2..00000000000 --- a/apps/sim/app/api/credential-sets/invitations/route.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { db } from '@sim/db' -import { credentialSet, credentialSetInvitation, organization, user } from '@sim/db/schema' -import { createLogger } from '@sim/logger' -import { and, eq, gt } from 'drizzle-orm' -import { NextResponse } from 'next/server' -import { getSession } from '@/lib/auth' -import { withRouteHandler } from '@/lib/core/utils/with-route-handler' - -const logger = createLogger('CredentialSetInvitations') - -export const GET = withRouteHandler(async () => { - const session = await getSession() - - if (!session?.user?.id || !session?.user?.email) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - try { - // Scope to invitations addressed to the caller's own email only. Open-link - // (null-email) invites carry a bearer token redeemed via the out-of-band URL - // and must never be listed, or any user could accept another org's invite. - const invitations = await db - .select({ - invitationId: credentialSetInvitation.id, - token: credentialSetInvitation.token, - status: credentialSetInvitation.status, - expiresAt: credentialSetInvitation.expiresAt, - createdAt: credentialSetInvitation.createdAt, - credentialSetId: credentialSet.id, - credentialSetName: credentialSet.name, - providerId: credentialSet.providerId, - organizationId: organization.id, - organizationName: organization.name, - invitedByName: user.name, - invitedByEmail: user.email, - }) - .from(credentialSetInvitation) - .innerJoin(credentialSet, eq(credentialSetInvitation.credentialSetId, credentialSet.id)) - .innerJoin(organization, eq(credentialSet.organizationId, organization.id)) - .leftJoin(user, eq(credentialSetInvitation.invitedBy, user.id)) - .where( - and( - eq(credentialSetInvitation.email, session.user.email), - eq(credentialSetInvitation.status, 'pending'), - gt(credentialSetInvitation.expiresAt, new Date()) - ) - ) - - return NextResponse.json({ invitations }) - } catch (error) { - logger.error('Error fetching credential set invitations', error) - return NextResponse.json({ error: 'Failed to fetch invitations' }, { status: 500 }) - } -}) diff --git a/apps/sim/app/api/credential-sets/invite/[token]/route.ts b/apps/sim/app/api/credential-sets/invite/[token]/route.ts deleted file mode 100644 index 160a5054739..00000000000 --- a/apps/sim/app/api/credential-sets/invite/[token]/route.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' -import { db } from '@sim/db' -import { - credentialSet, - credentialSetInvitation, - credentialSetMember, - organization, -} from '@sim/db/schema' -import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' -import { normalizeEmail } from '@sim/utils/string' -import { and, eq } from 'drizzle-orm' -import { type NextRequest, NextResponse } from 'next/server' -import { - acceptCredentialSetInvitationContract, - getCredentialSetInvitationContract, -} from '@/lib/api/contracts/credential-sets' -import { parseRequest } from '@/lib/api/server' -import { getSession } from '@/lib/auth' -import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server' - -const logger = createLogger('CredentialSetInviteToken') - -export const GET = withRouteHandler( - async (req: NextRequest, context: { params: Promise<{ token: string }> }) => { - const parsed = await parseRequest(getCredentialSetInvitationContract, req, context) - if (!parsed.success) return parsed.response - const { token } = parsed.data.params - - const [invitation] = await db - .select({ - id: credentialSetInvitation.id, - credentialSetId: credentialSetInvitation.credentialSetId, - email: credentialSetInvitation.email, - status: credentialSetInvitation.status, - expiresAt: credentialSetInvitation.expiresAt, - credentialSetName: credentialSet.name, - providerId: credentialSet.providerId, - organizationId: credentialSet.organizationId, - organizationName: organization.name, - }) - .from(credentialSetInvitation) - .innerJoin(credentialSet, eq(credentialSetInvitation.credentialSetId, credentialSet.id)) - .innerJoin(organization, eq(credentialSet.organizationId, organization.id)) - .where(eq(credentialSetInvitation.token, token)) - .limit(1) - - if (!invitation) { - return NextResponse.json({ error: 'Invitation not found' }, { status: 404 }) - } - - if (invitation.status !== 'pending') { - return NextResponse.json({ error: 'Invitation is no longer valid' }, { status: 410 }) - } - - if (new Date() > invitation.expiresAt) { - await db - .update(credentialSetInvitation) - .set({ status: 'expired' }) - .where(eq(credentialSetInvitation.id, invitation.id)) - - return NextResponse.json({ error: 'Invitation has expired' }, { status: 410 }) - } - - return NextResponse.json({ - invitation: { - credentialSetName: invitation.credentialSetName, - organizationName: invitation.organizationName, - providerId: invitation.providerId, - email: invitation.email, - }, - }) - } -) - -export const POST = withRouteHandler( - async (req: NextRequest, context: { params: Promise<{ token: string }> }) => { - const parsed = await parseRequest(acceptCredentialSetInvitationContract, req, context) - if (!parsed.success) return parsed.response - const { token } = parsed.data.params - - const session = await getSession() - if (!session?.user?.id) { - return NextResponse.json({ error: 'Authentication required' }, { status: 401 }) - } - - try { - const [invitationData] = await db - .select({ - id: credentialSetInvitation.id, - credentialSetId: credentialSetInvitation.credentialSetId, - email: credentialSetInvitation.email, - status: credentialSetInvitation.status, - expiresAt: credentialSetInvitation.expiresAt, - invitedBy: credentialSetInvitation.invitedBy, - credentialSetName: credentialSet.name, - providerId: credentialSet.providerId, - }) - .from(credentialSetInvitation) - .innerJoin(credentialSet, eq(credentialSetInvitation.credentialSetId, credentialSet.id)) - .where(eq(credentialSetInvitation.token, token)) - .limit(1) - - if (!invitationData) { - return NextResponse.json({ error: 'Invitation not found' }, { status: 404 }) - } - - const invitation = invitationData - - if (invitation.status !== 'pending') { - return NextResponse.json({ error: 'Invitation is no longer valid' }, { status: 410 }) - } - - if (new Date() > invitation.expiresAt) { - await db - .update(credentialSetInvitation) - .set({ status: 'expired' }) - .where(eq(credentialSetInvitation.id, invitation.id)) - - return NextResponse.json({ error: 'Invitation has expired' }, { status: 410 }) - } - - if (invitation.email) { - const sessionEmail = session.user.email - if (!sessionEmail || normalizeEmail(sessionEmail) !== normalizeEmail(invitation.email)) { - logger.warn('Rejected credential set invitation accept due to email mismatch', { - invitationId: invitation.id, - credentialSetId: invitation.credentialSetId, - userId: session.user.id, - }) - return NextResponse.json( - { error: 'This invitation was sent to a different email address' }, - { status: 403 } - ) - } - } - - const existingMember = await db - .select() - .from(credentialSetMember) - .where( - and( - eq(credentialSetMember.credentialSetId, invitation.credentialSetId), - eq(credentialSetMember.userId, session.user.id) - ) - ) - .limit(1) - - if (existingMember.length > 0) { - return NextResponse.json( - { error: 'Already a member of this credential set' }, - { status: 409 } - ) - } - - const now = new Date() - const requestId = generateId().slice(0, 8) - - await db.transaction(async (tx) => { - await tx.insert(credentialSetMember).values({ - id: generateId(), - credentialSetId: invitation.credentialSetId, - userId: session.user.id, - status: 'active', - joinedAt: now, - invitedBy: invitation.invitedBy, - createdAt: now, - updatedAt: now, - }) - - await tx - .update(credentialSetInvitation) - .set({ - status: 'accepted', - acceptedAt: now, - acceptedByUserId: session.user.id, - }) - .where(eq(credentialSetInvitation.id, invitation.id)) - - if (invitation.email) { - await tx - .update(credentialSetInvitation) - .set({ - status: 'accepted', - acceptedAt: now, - acceptedByUserId: session.user.id, - }) - .where( - and( - eq(credentialSetInvitation.credentialSetId, invitation.credentialSetId), - eq(credentialSetInvitation.email, invitation.email), - eq(credentialSetInvitation.status, 'pending') - ) - ) - } - }) - - // Runs after the membership commits: the sync performs external HTTP - // (OAuth refresh, provider unsubscribe) and must not hold a pooled - // connection. A sync failure must not fail the committed mutation — - // it self-heals on the next membership change/deploy. - try { - const syncResult = await syncAllWebhooksForCredentialSet( - invitation.credentialSetId, - requestId - ) - logger.info('Synced webhooks after member joined', { - credentialSetId: invitation.credentialSetId, - ...syncResult, - }) - } catch (syncError) { - logger.error('Webhook sync failed after invitation accept', { - credentialSetId: invitation.credentialSetId, - error: syncError, - }) - } - - logger.info('Accepted credential set invitation', { - invitationId: invitation.id, - credentialSetId: invitation.credentialSetId, - userId: session.user.id, - }) - - recordAudit({ - actorId: session.user.id, - actorName: session.user.name, - actorEmail: session.user.email, - action: AuditAction.CREDENTIAL_SET_INVITATION_ACCEPTED, - resourceType: AuditResourceType.CREDENTIAL_SET, - resourceId: invitation.credentialSetId, - resourceName: invitation.credentialSetName, - description: `Accepted credential set invitation`, - metadata: { - invitationId: invitation.id, - credentialSetId: invitation.credentialSetId, - providerId: invitation.providerId, - credentialSetName: invitation.credentialSetName, - }, - request: req, - }) - - return NextResponse.json({ - success: true, - credentialSetId: invitation.credentialSetId, - providerId: invitation.providerId, - }) - } catch (error) { - logger.error('Error accepting invitation', error) - return NextResponse.json({ error: 'Failed to accept invitation' }, { status: 500 }) - } - } -) diff --git a/apps/sim/app/api/credential-sets/memberships/route.ts b/apps/sim/app/api/credential-sets/memberships/route.ts deleted file mode 100644 index 8ce556f5b5d..00000000000 --- a/apps/sim/app/api/credential-sets/memberships/route.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' -import { db } from '@sim/db' -import { credentialSet, credentialSetMember, organization } from '@sim/db/schema' -import { createLogger } from '@sim/logger' -import { getErrorMessage } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' -import { and, eq } from 'drizzle-orm' -import { type NextRequest, NextResponse } from 'next/server' -import { leaveCredentialSetQuerySchema } from '@/lib/api/contracts/credential-sets' -import { getValidationErrorMessage } from '@/lib/api/server' -import { getSession } from '@/lib/auth' -import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server' - -const logger = createLogger('CredentialSetMemberships') - -export const GET = withRouteHandler(async () => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - try { - const memberships = await db - .select({ - membershipId: credentialSetMember.id, - status: credentialSetMember.status, - joinedAt: credentialSetMember.joinedAt, - credentialSetId: credentialSet.id, - credentialSetName: credentialSet.name, - credentialSetDescription: credentialSet.description, - providerId: credentialSet.providerId, - organizationId: organization.id, - organizationName: organization.name, - }) - .from(credentialSetMember) - .innerJoin(credentialSet, eq(credentialSetMember.credentialSetId, credentialSet.id)) - .innerJoin(organization, eq(credentialSet.organizationId, organization.id)) - .where(eq(credentialSetMember.userId, session.user.id)) - - return NextResponse.json({ memberships }) - } catch (error) { - logger.error('Error fetching credential set memberships', error) - return NextResponse.json({ error: 'Failed to fetch memberships' }, { status: 500 }) - } -}) - -/** - * Leave a credential set (self-revocation). - * Sets status to 'revoked' immediately (blocks execution), then syncs webhooks to clean up. - */ -export const DELETE = withRouteHandler(async (req: NextRequest) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - const { searchParams } = new URL(req.url) - const validation = leaveCredentialSetQuerySchema.safeParse({ - credentialSetId: searchParams.get('credentialSetId') ?? '', - }) - - if (!validation.success) { - return NextResponse.json( - { error: getValidationErrorMessage(validation.error) }, - { status: 400 } - ) - } - - const { credentialSetId } = validation.data - - try { - const requestId = generateId().slice(0, 8) - - await db.transaction(async (tx) => { - // Find and verify membership - const [membership] = await tx - .select() - .from(credentialSetMember) - .where( - and( - eq(credentialSetMember.credentialSetId, credentialSetId), - eq(credentialSetMember.userId, session.user.id) - ) - ) - .limit(1) - - if (!membership) { - throw new Error('Not a member of this credential set') - } - - if (membership.status === 'revoked') { - throw new Error('Already left this credential set') - } - - // Set status to 'revoked' - this immediately blocks credential from being used - await tx - .update(credentialSetMember) - .set({ - status: 'revoked', - updatedAt: new Date(), - }) - .where(eq(credentialSetMember.id, membership.id)) - }) - - // Runs after the revocation commits: the sync performs external HTTP - // (OAuth refresh, provider unsubscribe) and must not hold a pooled - // connection. A sync failure must not fail the committed mutation — - // it self-heals on the next membership change/deploy. - try { - const syncResult = await syncAllWebhooksForCredentialSet(credentialSetId, requestId) - logger.info('Synced webhooks after member left', { - credentialSetId, - userId: session.user.id, - ...syncResult, - }) - } catch (syncError) { - logger.error('Webhook sync failed after member left', { - credentialSetId, - userId: session.user.id, - error: syncError, - }) - } - - logger.info('User left credential set', { - credentialSetId, - userId: session.user.id, - }) - - recordAudit({ - actorId: session.user.id, - actorName: session.user.name, - actorEmail: session.user.email, - action: AuditAction.CREDENTIAL_SET_MEMBER_LEFT, - resourceType: AuditResourceType.CREDENTIAL_SET, - resourceId: credentialSetId, - description: `Left credential set`, - metadata: { credentialSetId }, - request: req, - }) - - return NextResponse.json({ success: true }) - } catch (error) { - const message = getErrorMessage(error, 'Failed to leave credential set') - logger.error('Error leaving credential set', error) - return NextResponse.json({ error: message }, { status: 500 }) - } -}) diff --git a/apps/sim/app/api/credential-sets/route.ts b/apps/sim/app/api/credential-sets/route.ts deleted file mode 100644 index 0221ca44b93..00000000000 --- a/apps/sim/app/api/credential-sets/route.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' -import { db } from '@sim/db' -import { credentialSet, credentialSetMember, member, organization, user } from '@sim/db/schema' -import { createLogger } from '@sim/logger' -import { generateId } from '@sim/utils/id' -import { and, count, desc, eq } from 'drizzle-orm' -import { type NextRequest, NextResponse } from 'next/server' -import { - createCredentialSetContract, - listCredentialSetsQuerySchema, -} from '@/lib/api/contracts/credential-sets' -import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' -import { getSession } from '@/lib/auth' -import { hasCredentialSetsAccess } from '@/lib/billing' -import { withRouteHandler } from '@/lib/core/utils/with-route-handler' - -const logger = createLogger('CredentialSets') - -export const GET = withRouteHandler(async (req: Request) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - const { searchParams } = new URL(req.url) - const validation = listCredentialSetsQuerySchema.safeParse({ - organizationId: searchParams.get('organizationId') ?? '', - }) - - if (!validation.success) { - return NextResponse.json( - { error: getValidationErrorMessage(validation.error) }, - { status: 400 } - ) - } - - const { organizationId } = validation.data - - const membership = await db - .select({ id: member.id, role: member.role }) - .from(member) - .where(and(eq(member.userId, session.user.id), eq(member.organizationId, organizationId))) - .limit(1) - - if (membership.length === 0) { - return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) - } - - const sets = await db - .select({ - id: credentialSet.id, - name: credentialSet.name, - description: credentialSet.description, - providerId: credentialSet.providerId, - createdBy: credentialSet.createdBy, - createdAt: credentialSet.createdAt, - updatedAt: credentialSet.updatedAt, - creatorName: user.name, - creatorEmail: user.email, - }) - .from(credentialSet) - .leftJoin(user, eq(credentialSet.createdBy, user.id)) - .where(eq(credentialSet.organizationId, organizationId)) - .orderBy(desc(credentialSet.createdAt)) - - const setsWithCounts = await Promise.all( - sets.map(async (set) => { - const [memberCount] = await db - .select({ count: count() }) - .from(credentialSetMember) - .where( - and( - eq(credentialSetMember.credentialSetId, set.id), - eq(credentialSetMember.status, 'active') - ) - ) - - return { - ...set, - memberCount: memberCount?.count ?? 0, - } - }) - ) - - return NextResponse.json({ credentialSets: setsWithCounts }) -}) - -export const POST = withRouteHandler(async (req: NextRequest) => { - const session = await getSession() - - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } - - // Check plan access (team/enterprise) or env var override - const hasAccess = await hasCredentialSetsAccess(session.user.id) - if (!hasAccess) { - return NextResponse.json( - { error: 'Credential sets require a Team or Enterprise plan' }, - { status: 403 } - ) - } - - try { - const parsed = await parseRequest( - createCredentialSetContract, - req, - {}, - { - validationErrorResponse: (error) => - NextResponse.json({ error: getValidationErrorMessage(error) }, { status: 400 }), - } - ) - if (!parsed.success) return parsed.response - - const { organizationId, name, description, providerId } = parsed.data.body - - const membership = await db - .select({ id: member.id, role: member.role }) - .from(member) - .where(and(eq(member.userId, session.user.id), eq(member.organizationId, organizationId))) - .limit(1) - - const role = membership[0]?.role - if (membership.length === 0 || (role !== 'admin' && role !== 'owner')) { - return NextResponse.json( - { error: 'Admin or owner permissions required to create credential sets' }, - { status: 403 } - ) - } - - // Check org existence and name uniqueness in parallel - const [orgExists, existingSet] = await Promise.all([ - db - .select({ id: organization.id }) - .from(organization) - .where(eq(organization.id, organizationId)) - .limit(1), - db - .select({ id: credentialSet.id }) - .from(credentialSet) - .where(and(eq(credentialSet.organizationId, organizationId), eq(credentialSet.name, name))) - .limit(1), - ]) - - if (orgExists.length === 0) { - return NextResponse.json({ error: 'Organization not found' }, { status: 404 }) - } - - if (existingSet.length > 0) { - return NextResponse.json( - { error: 'A credential set with this name already exists' }, - { status: 409 } - ) - } - - const now = new Date() - const newCredentialSet = { - id: generateId(), - organizationId, - name, - description: description || null, - providerId, - createdBy: session.user.id, - createdAt: now, - updatedAt: now, - } - - await db.insert(credentialSet).values(newCredentialSet) - - logger.info('Created credential set', { - credentialSetId: newCredentialSet.id, - organizationId, - userId: session.user.id, - }) - - recordAudit({ - workspaceId: null, - actorId: session.user.id, - action: AuditAction.CREDENTIAL_SET_CREATED, - resourceType: AuditResourceType.CREDENTIAL_SET, - resourceId: newCredentialSet.id, - actorName: session.user.name ?? undefined, - actorEmail: session.user.email ?? undefined, - resourceName: name, - description: `Created credential set "${name}"`, - metadata: { organizationId, providerId, credentialSetName: name }, - request: req, - }) - - return NextResponse.json( - { - credentialSet: { - ...newCredentialSet, - creatorName: session.user.name ?? null, - creatorEmail: session.user.email ?? null, - memberCount: 0, - }, - }, - { status: 201 } - ) - } catch (error) { - logger.error('Error creating credential set', error) - return NextResponse.json({ error: 'Failed to create credential set' }, { status: 500 }) - } -}) diff --git a/apps/sim/app/api/webhooks/[id]/route.ts b/apps/sim/app/api/webhooks/[id]/route.ts index 7ad630c5ff6..92506cc8627 100644 --- a/apps/sim/app/api/webhooks/[id]/route.ts +++ b/apps/sim/app/api/webhooks/[id]/route.ts @@ -223,69 +223,21 @@ export const DELETE = withRouteHandler( await assertWorkflowMutable(webhookData.workflow.id) const foundWebhook = webhookData.webhook - const credentialSetId = foundWebhook.credentialSetId as string | undefined - const blockId = foundWebhook.blockId as string | undefined - - if (credentialSetId && blockId) { - const allCredentialSetWebhooks = await db - .select() - .from(webhook) - .where( - and( - eq(webhook.workflowId, webhookData.workflow.id), - eq(webhook.blockId, blockId), - isNull(webhook.archivedAt) - ) - ) - - const webhooksToDelete = allCredentialSetWebhooks.filter( - (w) => w.credentialSetId === credentialSetId - ) - - for (const w of webhooksToDelete) { - await cleanupExternalWebhook(w, webhookData.workflow, requestId) - } - - const idsToDelete = webhooksToDelete.map((w) => w.id) - for (const wId of idsToDelete) { - await db.delete(webhook).where(eq(webhook.id, wId)) - } - - try { - for (const wId of idsToDelete) { - PlatformEvents.webhookDeleted({ - webhookId: wId, - workflowId: webhookData.workflow.id, - }) - } - } catch { - // Telemetry should not fail the operation - } - - logger.info( - `[${requestId}] Successfully deleted ${idsToDelete.length} webhooks for credential set`, - { - credentialSetId, - blockId, - deletedIds: idsToDelete, - } - ) - } else { - await cleanupExternalWebhook(foundWebhook, webhookData.workflow, requestId) - await db.delete(webhook).where(eq(webhook.id, id)) - - try { - PlatformEvents.webhookDeleted({ - webhookId: id, - workflowId: webhookData.workflow.id, - }) - } catch { - // Telemetry should not fail the operation - } - - logger.info(`[${requestId}] Successfully deleted webhook: ${id}`) + + await cleanupExternalWebhook(foundWebhook, webhookData.workflow, requestId) + await db.delete(webhook).where(eq(webhook.id, id)) + + try { + PlatformEvents.webhookDeleted({ + webhookId: id, + workflowId: webhookData.workflow.id, + }) + } catch { + // Telemetry should not fail the operation } + logger.info(`[${requestId}] Successfully deleted webhook: ${id}`) + recordAudit({ workspaceId: webhookData.workflow.workspaceId || null, actorId: userId, @@ -301,7 +253,6 @@ export const DELETE = withRouteHandler( workflowId: webhookData.workflow.id, webhookPath: foundWebhook.path || undefined, blockId: foundWebhook.blockId || undefined, - credentialSetId: credentialSetId || undefined, }, request, }) diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index 8c7fdec2ee8..a27b0d599b0 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -17,7 +17,6 @@ import { getSession } from '@/lib/auth' import { PlatformEvents } from '@/lib/core/telemetry' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { getProviderIdFromServiceId } from '@/lib/oauth' import { captureServerEvent } from '@/lib/posthog/server' import { resolveEnvVarsInObject } from '@/lib/webhooks/env-resolver' import { @@ -27,12 +26,8 @@ import { } from '@/lib/webhooks/provider-subscriptions' import { getProviderHandler } from '@/lib/webhooks/providers' import { mergeNonUserFields } from '@/lib/webhooks/utils' -import { - findConflictingWebhookPathOwner, - syncWebhooksForCredentialSet, -} from '@/lib/webhooks/utils.server' +import { findConflictingWebhookPathOwner } from '@/lib/webhooks/utils.server' import { listAccessibleWorkspaceRowsForUser } from '@/lib/workspaces/utils' -import { extractCredentialSetId, isCredentialSetValue } from '@/executor/constants' const logger = createLogger('WebhooksAPI') @@ -52,7 +47,6 @@ async function revertSavedWebhook( path: existingWebhook.path, provider: existingWebhook.provider, providerConfig: existingWebhook.providerConfig, - credentialSetId: existingWebhook.credentialSetId, isActive: existingWebhook.isActive, archivedAt: existingWebhook.archivedAt, updatedAt: existingWebhook.updatedAt, @@ -367,144 +361,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { workflowRecord.workspaceId || undefined ) - // For credential sets, we fan out to create one webhook per credential at save time. - // This applies to all OAuth-based triggers, not just polling ones. - // Check for credentialSetId directly (frontend may already extract it) or credential set value in credential fields - const rawCredentialId = (resolvedProviderConfig?.credentialId || - resolvedProviderConfig?.triggerCredentials) as string | undefined - const directCredentialSetId = resolvedProviderConfig?.credentialSetId as string | undefined - - if (directCredentialSetId || rawCredentialId) { - const credentialSetId = - directCredentialSetId || - (rawCredentialId && isCredentialSetValue(rawCredentialId) - ? extractCredentialSetId(rawCredentialId) - : null) - - if (credentialSetId) { - logger.info( - `[${requestId}] Credential set detected for ${provider} trigger. Syncing webhooks for set ${credentialSetId}` - ) - - const oauthProviderId = getProviderIdFromServiceId(provider) - - const { - credentialId: _cId, - triggerCredentials: _tCred, - credentialSetId: _csId, - ...baseProviderConfig - } = resolvedProviderConfig - - try { - const syncResult = await syncWebhooksForCredentialSet({ - workflowId, - blockId: blockId || '', - provider, - basePath: finalPath, - credentialSetId, - oauthProviderId, - providerConfig: baseProviderConfig, - requestId, - }) - - if (syncResult.webhooks.length === 0) { - logger.error( - `[${requestId}] No webhooks created for credential set - no valid credentials found` - ) - return NextResponse.json( - { - error: `No valid credentials found in credential set for ${provider}`, - details: 'Please ensure team members have connected their accounts', - }, - { status: 400 } - ) - } - - const providerHandler = getProviderHandler(provider) - - if (providerHandler.configurePolling) { - const configureErrors: string[] = [] - - for (const wh of syncResult.webhooks) { - if (wh.isNew) { - const webhookRows = await db - .select() - .from(webhook) - .where(and(eq(webhook.id, wh.id), isNull(webhook.archivedAt))) - .limit(1) - - if (webhookRows.length > 0) { - const success = await providerHandler.configurePolling({ - webhook: webhookRows[0], - requestId, - }) - if (!success) { - configureErrors.push( - `Failed to configure webhook for credential ${wh.credentialId}` - ) - logger.warn( - `[${requestId}] Failed to configure ${provider} polling for webhook ${wh.id}` - ) - } - } - } - } - - if ( - configureErrors.length > 0 && - configureErrors.length === syncResult.webhooks.length - ) { - logger.error(`[${requestId}] All webhook configurations failed, rolling back`) - for (const wh of syncResult.webhooks) { - await db.delete(webhook).where(eq(webhook.id, wh.id)) - } - return NextResponse.json( - { - error: `Failed to configure ${provider} polling`, - details: 'Please check account permissions and try again', - }, - { status: 500 } - ) - } - } - - logger.info( - `[${requestId}] Successfully synced ${syncResult.webhooks.length} webhooks for credential set ${credentialSetId}` - ) - - // Return the first webhook as the "primary" for the UI - // The UI will query by credentialSetId to get all of them - const primaryWebhookRows = await db - .select() - .from(webhook) - .where(and(eq(webhook.id, syncResult.webhooks[0].id), isNull(webhook.archivedAt))) - .limit(1) - - return NextResponse.json( - { - webhook: primaryWebhookRows[0], - credentialSetInfo: { - credentialSetId, - totalWebhooks: syncResult.webhooks.length, - created: syncResult.created, - updated: syncResult.updated, - deleted: syncResult.deleted, - }, - }, - { status: syncResult.created > 0 ? 201 : 200 } - ) - } catch (err) { - logger.error(`[${requestId}] Error syncing webhooks for credential set`, err) - return NextResponse.json( - { - error: `Failed to configure ${provider} webhook`, - details: getErrorMessage(err, 'Unknown error'), - }, - { status: 500 } - ) - } - } - } let externalSubscriptionCreated = false const createTempWebhookData = (providerConfigOverride = resolvedProviderConfig) => ({ id: targetWebhookId || generateShortId(), @@ -580,8 +436,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { blockId, provider, providerConfig: configToSave, - credentialSetId: - ((configToSave as Record)?.credentialSetId as string | null) || null, isActive: true, updatedAt: new Date(), }) @@ -605,8 +459,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => { path: finalPath, provider, providerConfig: configToSave, - credentialSetId: - ((configToSave as Record)?.credentialSetId as string | null) || null, isActive: true, createdAt: new Date(), updatedAt: new Date(), diff --git a/apps/sim/app/api/webhooks/trigger/[path]/route.ts b/apps/sim/app/api/webhooks/trigger/[path]/route.ts index f43807815bc..1c103ddad72 100644 --- a/apps/sim/app/api/webhooks/trigger/[path]/route.ts +++ b/apps/sim/app/api/webhooks/trigger/[path]/route.ts @@ -104,7 +104,7 @@ async function handleWebhookPost( return challengeResponse } - // Find all webhooks for this path (supports credential set fan-out where multiple webhooks share a path) + // 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 @@ -134,8 +134,7 @@ async function handleWebhookPost( return new NextResponse('Not Found', { status: 404 }) } - // Process each webhook - // For credential sets with shared paths, each webhook represents a different credential + // Process each webhook matched on this path const responses: NextResponse[] = [] let billingBlocked = false @@ -229,9 +228,7 @@ async function handleWebhookPost( } // For multiple webhooks, return success if at least one succeeded - logger.info( - `[${requestId}] Processed ${responses.length} webhooks for path: ${path} (credential set fan-out)` - ) + logger.info(`[${requestId}] Processed ${responses.length} webhooks for path: ${path}`) return NextResponse.json({ success: true, webhooksProcessed: responses.length, diff --git a/apps/sim/app/credential-account/[token]/loading.tsx b/apps/sim/app/credential-account/[token]/loading.tsx deleted file mode 100644 index 275aa3b854d..00000000000 --- a/apps/sim/app/credential-account/[token]/loading.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Skeleton } from '@sim/emcn' - -export default function CredentialAccountLoading() { - return ( -
-
-
-
- - - - - -
-
-
-
- ) -} diff --git a/apps/sim/app/credential-account/[token]/page.tsx b/apps/sim/app/credential-account/[token]/page.tsx deleted file mode 100644 index a9ba15dca2a..00000000000 --- a/apps/sim/app/credential-account/[token]/page.tsx +++ /dev/null @@ -1,275 +0,0 @@ -'use client' - -import { useCallback, useEffect, useState } from 'react' -import { createLogger } from '@sim/logger' -import { Mail } from 'lucide-react' -import { useParams, useRouter } from 'next/navigation' -import { GmailIcon, OutlookIcon } from '@/components/icons' -import { ApiClientError } from '@/lib/api/client/errors' -import { requestJson } from '@/lib/api/client/request' -import { - acceptCredentialSetInvitationContract, - type CredentialSetInvitePreview, - getCredentialSetInvitationContract, -} from '@/lib/api/contracts' -import { listOAuthConnectionsContract } from '@/lib/api/contracts/oauth-connections' -import { client, useSession } from '@/lib/auth/auth-client' -import { getProviderDisplayName, isPollingProvider } from '@/lib/credential-sets/providers' -import { InviteLayout, InviteStatusCard } from '@/app/invite/components' - -const logger = createLogger('CredentialAccount') - -type AcceptedState = 'connecting' | 'already-connected' - -function getErrorMessageFromBody(body: unknown): string | undefined { - if (!body || typeof body !== 'object') return undefined - const error = (body as { error?: unknown }).error - return typeof error === 'string' ? error : undefined -} - -export default function CredentialAccountInvitePage() { - const params = useParams() - const router = useRouter() - const token = params.token as string - - const { data: session, isPending: sessionLoading } = useSession() - - const [invitation, setInvitation] = useState(null) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - const [accepting, setAccepting] = useState(false) - const [acceptedState, setAcceptedState] = useState(null) - - useEffect(() => { - async function fetchInvitation() { - try { - const data = await requestJson(getCredentialSetInvitationContract, { - params: { token }, - }) - setInvitation(data.invitation) - } catch (fetchError) { - if (fetchError instanceof ApiClientError) { - setError(getErrorMessageFromBody(fetchError.body) || fetchError.message) - } else { - setError('Failed to load invitation') - } - } finally { - setLoading(false) - } - } - - fetchInvitation() - }, [token]) - - const handleAccept = useCallback(async () => { - if (!session?.user?.id) { - // Include invite_flow=true so the login page preserves callbackUrl when linking to signup - const callbackUrl = encodeURIComponent(`/credential-account/${token}`) - router.push(`/login?invite_flow=true&callbackUrl=${callbackUrl}`) - return - } - - setAccepting(true) - try { - const acceptResponse = await requestJson(acceptCredentialSetInvitationContract, { - params: { token }, - }).catch((acceptError: unknown) => { - const fallback = 'Failed to accept invitation' - if (acceptError instanceof ApiClientError) { - setError(getErrorMessageFromBody(acceptError.body) || acceptError.message || fallback) - } else { - setError(fallback) - } - return null - }) - - if (!acceptResponse) { - return - } - - const credentialSetProviderId = acceptResponse.providerId || invitation?.providerId - - // Check if user already has this provider connected - let isAlreadyConnected = false - if (credentialSetProviderId && isPollingProvider(credentialSetProviderId)) { - try { - const connectionsData = await requestJson(listOAuthConnectionsContract, {}) - isAlreadyConnected = (connectionsData.connections ?? []).some( - (conn) => - conn.provider === credentialSetProviderId && conn.accounts && conn.accounts.length > 0 - ) - } catch { - // If we can't check connections, proceed with OAuth flow - } - } - - if (isAlreadyConnected) { - // Already connected - redirect to workspace - setAcceptedState('already-connected') - setTimeout(() => { - router.push('/workspace') - }, 2000) - } else if (credentialSetProviderId && isPollingProvider(credentialSetProviderId)) { - // Not connected - start OAuth flow - setAcceptedState('connecting') - - // Small delay to show success message before redirect - setTimeout(async () => { - try { - await client.oauth2.link({ - providerId: credentialSetProviderId, - callbackURL: `${window.location.origin}/workspace`, - }) - } catch (oauthError) { - // OAuth redirect will happen, this catch is for any pre-redirect errors - logger.error('OAuth initiation error:', oauthError) - // If OAuth fails, redirect to workspace where they can connect manually - router.push('/workspace') - } - }, 1500) - } else { - // No provider specified - just redirect to workspace - router.push('/workspace') - } - } catch { - setError('Failed to accept invitation') - } finally { - setAccepting(false) - } - }, [session?.user?.id, token, router, invitation?.providerId]) - - const providerName = invitation?.providerId - ? getProviderDisplayName(invitation.providerId) - : 'email' - - const ProviderIcon = - invitation?.providerId === 'outlook' - ? OutlookIcon - : invitation?.providerId === 'google-email' - ? GmailIcon - : Mail - - const providerWithIcon = ( - - - {providerName} - - ) - - const getCallbackUrl = () => `/credential-account/${token}` - - if (loading || sessionLoading) { - return ( - - - - ) - } - - if (error) { - return ( - - router.push('/'), - }, - ]} - /> - - ) - } - - if (acceptedState === 'already-connected') { - return ( - - - - ) - } - - if (acceptedState === 'connecting') { - return ( - - - - ) - } - - // Not logged in - if (!session?.user) { - const callbackUrl = encodeURIComponent(getCallbackUrl()) - - return ( - - router.push(`/login?callbackUrl=${callbackUrl}&invite_flow=true`), - }, - { - label: 'Create an account', - onClick: () => - router.push(`/signup?callbackUrl=${callbackUrl}&invite_flow=true&new=true`), - }, - { - label: 'Return to Home', - onClick: () => router.push('/'), - }, - ]} - /> - - ) - } - - // Logged in - show invitation - return ( - - - You've been invited to join {invitation?.credentialSetName} by{' '} - {invitation?.organizationName}. - {invitation?.providerId && ( - <> You'll be asked to connect your {providerWithIcon} account after accepting. - )} - - } - icon='mail' - actions={[ - { - label: `Accept & Connect ${providerName}`, - onClick: handleAccept, - disabled: accepting, - loading: accepting, - }, - { - label: 'Return to Home', - onClick: () => router.push('/'), - }, - ]} - /> - - ) -} diff --git a/apps/sim/app/robots.ts b/apps/sim/app/robots.ts index e2aef2bb753..ca4b523265f 100644 --- a/apps/sim/app/robots.ts +++ b/apps/sim/app/robots.ts @@ -9,7 +9,6 @@ const DISALLOWED_PATHS = [ '/invite/', '/unsubscribe/', '/w/', - '/credential-account/', '/_next/', '/private/', ] diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx index 88a3ec5f519..f0ac5684c16 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx @@ -35,7 +35,6 @@ const SECTION_TITLES: Record = { mcp: 'MCP Tools', 'custom-tools': 'Custom Tools', 'workflow-mcp-servers': 'MCP Servers', - 'credential-sets': 'Email Polling', 'data-retention': 'Data Retention', 'recently-deleted': 'Recently Deleted', debug: 'Debug', diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx index b2fc4a7daa5..dc181b229b6 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx @@ -9,10 +9,7 @@ import { General } from '@/app/workspace/[workspaceId]/settings/components/gener import { SettingsSectionProvider } from '@/app/workspace/[workspaceId]/settings/components/settings-panel' import { useSettingsBeforeUnload } from '@/app/workspace/[workspaceId]/settings/hooks/use-settings-before-unload' import type { SettingsSection } from '@/app/workspace/[workspaceId]/settings/navigation' -import { - isBillingEnabled, - isCredentialSetsEnabled, -} from '@/app/workspace/[workspaceId]/settings/navigation' +import { isBillingEnabled } from '@/app/workspace/[workspaceId]/settings/navigation' const Admin = dynamic(() => import('@/app/workspace/[workspaceId]/settings/components/admin/admin').then((m) => m.Admin) @@ -28,11 +25,6 @@ const BYOK = dynamic(() => const Copilot = dynamic(() => import('@/app/workspace/[workspaceId]/settings/components/copilot/copilot').then((m) => m.Copilot) ) -const CredentialSets = dynamic(() => - import('@/app/workspace/[workspaceId]/settings/components/credential-sets/credential-sets').then( - (m) => m.CredentialSets - ) -) const Secrets = dynamic(() => import('@/app/workspace/[workspaceId]/settings/components/secrets/secrets').then((m) => m.Secrets) ) @@ -114,13 +106,11 @@ export function SettingsPage({ section }: SettingsPageProps) { const effectiveSection = !isBillingEnabled && (normalizedSection === 'billing' || normalizedSection === 'organization') ? 'general' - : normalizedSection === 'credential-sets' && !isCredentialSetsEnabled + : normalizedSection === 'admin' && !sessionLoading && !isAdminRole ? 'general' - : normalizedSection === 'admin' && !sessionLoading && !isAdminRole + : normalizedSection === 'mothership' && !sessionLoading && !isAdminRole ? 'general' - : normalizedSection === 'mothership' && !sessionLoading && !isAdminRole - ? 'general' - : normalizedSection + : normalizedSection useEffect(() => { if (sessionLoading) return @@ -131,7 +121,6 @@ export function SettingsPage({ section }: SettingsPageProps) { {effectiveSection === 'general' && } {effectiveSection === 'secrets' && } - {effectiveSection === 'credential-sets' && } {effectiveSection === 'access-control' && } {effectiveSection === 'audit-logs' && } {effectiveSection === 'apikeys' && } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/credential-sets/credential-sets.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/credential-sets/credential-sets.tsx deleted file mode 100644 index 22728319e20..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/credential-sets/credential-sets.tsx +++ /dev/null @@ -1,818 +0,0 @@ -'use client' - -import { useCallback, useMemo, useState } from 'react' -import { - Avatar, - AvatarFallback, - AvatarImage, - Badge, - ButtonGroup, - ButtonGroupItem, - Chip, - ChipConfirmModal, - ChipModal, - ChipModalBody, - ChipModalError, - ChipModalField, - ChipModalFooter, - ChipModalHeader, - type FileInputOptions, - TagInput, - type TagItem, -} from '@sim/emcn' -import { ArrowLeft } from '@sim/emcn/icons' -import { createLogger } from '@sim/logger' -import { isOrgAdminRole } from '@sim/platform-authz/predicates' -import { Plus } from 'lucide-react' -import { GmailIcon, OutlookIcon } from '@/components/icons' -import { useSession } from '@/lib/auth/auth-client' -import { getSubscriptionAccessState } from '@/lib/billing/client' -import { getProviderDisplayName, type PollingProvider } from '@/lib/credential-sets/providers' -import { quickValidateEmail } from '@/lib/messaging/email/validation' -import { getUserColor } from '@/lib/workspaces/colors' -import { getUserRole } from '@/lib/workspaces/organization' -import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu' -import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' -import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel' -import { SettingsResourceRow } from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row' -import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' -import { - type CredentialSet, - useAcceptCredentialSetInvitation, - useCancelCredentialSetInvitation, - useCreateCredentialSet, - useCreateCredentialSetInvitation, - useCredentialSetInvitations, - useCredentialSetInvitationsDetail, - useCredentialSetMembers, - useCredentialSetMemberships, - useCredentialSets, - useDeleteCredentialSet, - useLeaveCredentialSet, - useRemoveCredentialSetMember, - useResendCredentialSetInvitation, -} from '@/hooks/queries/credential-sets' -import { useOrganizations } from '@/hooks/queries/organization' -import { useSubscriptionData } from '@/hooks/queries/subscription' - -const logger = createLogger('EmailPolling') - -function getProviderIcon(providerId: string | null) { - if (providerId === 'outlook') return - return -} - -export function CredentialSets() { - const { data: session } = useSession() - const { data: organizationsData } = useOrganizations() - const { data: subscriptionData } = useSubscriptionData() - - const activeOrganization = organizationsData?.activeOrganization - const subscriptionAccess = getSubscriptionAccessState(subscriptionData?.data) - const hasTeamPlan = subscriptionAccess.hasUsableTeamAccess - const userRole = getUserRole(activeOrganization, session?.user?.email) - const isAdmin = isOrgAdminRole(userRole) - const canManageCredentialSets = hasTeamPlan && isAdmin && !!activeOrganization?.id - - const { data: memberships = [], isPending: membershipsLoading } = useCredentialSetMemberships() - const { data: invitations = [], isPending: invitationsLoading } = useCredentialSetInvitations() - const { data: ownedSets = [], isPending: ownedSetsLoading } = useCredentialSets( - activeOrganization?.id, - canManageCredentialSets - ) - - const acceptInvitation = useAcceptCredentialSetInvitation() - const createCredentialSet = useCreateCredentialSet() - const createInvitation = useCreateCredentialSetInvitation() - - const [searchTerm, setSearchTerm] = useState('') - const [showCreateModal, setShowCreateModal] = useState(false) - const [viewingSet, setViewingSet] = useState(null) - const [newSetName, setNewSetName] = useState('') - const [newSetDescription, setNewSetDescription] = useState('') - const [newSetProvider, setNewSetProvider] = useState('google-email') - const [createError, setCreateError] = useState(null) - const [emailItems, setEmailItems] = useState([]) - const [emailError, setEmailError] = useState(null) - const [leavingMembership, setLeavingMembership] = useState<{ - credentialSetId: string - name: string - } | null>(null) - - const { data: members = [], isPending: membersLoading } = useCredentialSetMembers(viewingSet?.id) - const { data: pendingInvitations = [], isPending: pendingInvitationsLoading } = - useCredentialSetInvitationsDetail(viewingSet?.id) - const removeMember = useRemoveCredentialSetMember() - const leaveCredentialSet = useLeaveCredentialSet() - const deleteCredentialSet = useDeleteCredentialSet() - const cancelInvitation = useCancelCredentialSetInvitation() - const resendInvitation = useResendCredentialSetInvitation() - - const [deletingSet, setDeletingSet] = useState<{ id: string; name: string } | null>(null) - const [deletingSetIds, setDeletingSetIds] = useState>(() => new Set()) - const [cancellingInvitations, setCancellingInvitations] = useState>(() => new Set()) - const [resendingInvitations, setResendingInvitations] = useState>(() => new Set()) - const [resendCooldowns, setResendCooldowns] = useState>({}) - - const addEmail = useCallback( - (email: string) => { - if (!email.trim()) return false - - const normalized = email.trim().toLowerCase() - const validation = quickValidateEmail(normalized) - const isValid = validation.isValid - - if (emailItems.some((item) => item.value === normalized)) { - return false - } - - const isPendingInvitation = pendingInvitations.some( - (inv) => inv.email?.toLowerCase() === normalized - ) - if (isPendingInvitation) { - setEmailError(`${normalized} already has a pending invitation`) - return false - } - - const isActiveMember = members.some( - (m) => m.userEmail?.toLowerCase() === normalized && m.status === 'active' - ) - if (isActiveMember) { - setEmailError(`${normalized} is already a member of this group`) - return false - } - - setEmailItems((prev) => [ - ...prev, - { - value: normalized, - isValid, - error: isValid ? undefined : (validation.reason ?? 'Invalid email format'), - }, - ]) - - if (isValid) { - setEmailError(null) - } - - return isValid - }, - [emailItems, pendingInvitations, members] - ) - - const removeEmailItem = useCallback((_value: string, index: number, _isValid: boolean) => { - setEmailItems((prev) => prev.filter((_, i) => i !== index)) - }, []) - - const fileInputOptions: FileInputOptions = useMemo( - () => ({ - enabled: true, - accept: '.csv,.txt,text/csv,text/plain', - extractValues: (text: string) => { - const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g - const matches = text.match(emailRegex) || [] - return [...new Set(matches.map((e) => e.toLowerCase()))] - }, - }), - [] - ) - - const handleRemoveMember = useCallback( - async (memberId: string) => { - if (!viewingSet) return - try { - await removeMember.mutateAsync({ - credentialSetId: viewingSet.id, - memberId, - }) - } catch (error) { - logger.error('Failed to remove member', error) - } - }, - [viewingSet, removeMember] - ) - - const handleLeave = useCallback((credentialSetId: string, name: string) => { - setLeavingMembership({ credentialSetId, name }) - }, []) - - const confirmLeave = useCallback(async () => { - if (!leavingMembership) return - try { - await leaveCredentialSet.mutateAsync(leavingMembership.credentialSetId) - setLeavingMembership(null) - } catch (error) { - logger.error('Failed to leave polling group', error) - } - }, [leavingMembership, leaveCredentialSet]) - - const handleAcceptInvitation = useCallback( - async (token: string) => { - try { - await acceptInvitation.mutateAsync(token) - } catch (error) { - logger.error('Failed to accept invitation', error) - } - }, - [acceptInvitation] - ) - - const handleCreateCredentialSet = useCallback(async () => { - if (!newSetName.trim() || !activeOrganization?.id) return - setCreateError(null) - try { - const result = await createCredentialSet.mutateAsync({ - organizationId: activeOrganization.id, - name: newSetName.trim(), - description: newSetDescription.trim() || undefined, - providerId: newSetProvider, - }) - setShowCreateModal(false) - setNewSetName('') - setNewSetDescription('') - setNewSetProvider('google-email') - - if (result?.credentialSet) { - setViewingSet(result.credentialSet) - } - } catch (error) { - logger.error('Failed to create polling group', error) - if (error instanceof Error) { - setCreateError(error.message) - } else { - setCreateError('Failed to create polling group') - } - } - }, [newSetName, newSetDescription, newSetProvider, activeOrganization?.id, createCredentialSet]) - - const validEmails = useMemo(() => { - const result: string[] = [] - for (const item of emailItems) { - if (item.isValid) result.push(item.value) - } - return result - }, [emailItems]) - - const handleInviteMembers = useCallback(async () => { - if (!viewingSet?.id) return - - if (validEmails.length === 0) return - - try { - for (const email of validEmails) { - await createInvitation.mutateAsync({ - credentialSetId: viewingSet.id, - email, - }) - } - setEmailItems([]) - setEmailError(null) - } catch (error) { - logger.error('Failed to create invitations', error) - } - }, [viewingSet?.id, validEmails, createInvitation]) - - const handleCloseCreateModal = useCallback(() => { - setShowCreateModal(false) - setNewSetName('') - setNewSetDescription('') - setNewSetProvider('google-email') - setCreateError(null) - }, []) - - const handleBackToList = useCallback(() => { - setViewingSet(null) - setEmailItems([]) - setEmailError(null) - }, []) - - const handleCancelInvitation = useCallback( - async (invitationId: string) => { - if (!viewingSet?.id) return - - setCancellingInvitations((prev) => new Set([...prev, invitationId])) - try { - await cancelInvitation.mutateAsync({ - credentialSetId: viewingSet.id, - invitationId, - }) - } catch (error) { - logger.error('Failed to cancel invitation', error) - } finally { - setCancellingInvitations((prev) => { - const next = new Set(prev) - next.delete(invitationId) - return next - }) - } - }, - [viewingSet?.id, cancelInvitation] - ) - - const handleResendInvitation = useCallback( - async (invitationId: string, email: string) => { - if (!viewingSet?.id) return - - const secondsLeft = resendCooldowns[invitationId] - if (secondsLeft && secondsLeft > 0) return - - setResendingInvitations((prev) => new Set([...prev, invitationId])) - try { - await resendInvitation.mutateAsync({ - credentialSetId: viewingSet.id, - invitationId, - email, - }) - - setResendCooldowns((prev) => ({ ...prev, [invitationId]: 60 })) - const interval = setInterval(() => { - setResendCooldowns((prev) => { - const current = prev[invitationId] - if (current === undefined) return prev - if (current <= 1) { - const next = { ...prev } - delete next[invitationId] - clearInterval(interval) - return next - } - return { ...prev, [invitationId]: current - 1 } - }) - }, 1000) - } catch (error) { - logger.error('Failed to resend invitation', error) - } finally { - setResendingInvitations((prev) => { - const next = new Set(prev) - next.delete(invitationId) - return next - }) - } - }, - [viewingSet?.id, resendInvitation, resendCooldowns] - ) - - const handleDeleteClick = useCallback((set: CredentialSet) => { - setDeletingSet({ id: set.id, name: set.name }) - }, []) - - const confirmDelete = useCallback(async () => { - if (!deletingSet || !activeOrganization?.id) return - setDeletingSetIds((prev) => new Set(prev).add(deletingSet.id)) - try { - await deleteCredentialSet.mutateAsync({ - credentialSetId: deletingSet.id, - organizationId: activeOrganization.id, - }) - setDeletingSet(null) - } catch (error) { - logger.error('Failed to delete polling group', error) - } finally { - setDeletingSetIds((prev) => { - const next = new Set(prev) - next.delete(deletingSet.id) - return next - }) - } - }, [deletingSet, activeOrganization?.id, deleteCredentialSet]) - - const activeMemberships = useMemo( - () => memberships.filter((m) => m.status === 'active'), - [memberships] - ) - - const filteredInvitations = useMemo(() => { - if (!searchTerm.trim()) return invitations - const searchLower = searchTerm.toLowerCase() - return invitations.filter( - (inv) => - inv.credentialSetName.toLowerCase().includes(searchLower) || - inv.organizationName.toLowerCase().includes(searchLower) - ) - }, [invitations, searchTerm]) - - const filteredMemberships = useMemo(() => { - if (!searchTerm.trim()) return activeMemberships - const searchLower = searchTerm.toLowerCase() - return activeMemberships.filter( - (m) => - m.credentialSetName.toLowerCase().includes(searchLower) || - m.organizationName.toLowerCase().includes(searchLower) - ) - }, [activeMemberships, searchTerm]) - - const filteredOwnedSets = useMemo(() => { - if (!searchTerm.trim()) return ownedSets - const searchLower = searchTerm.toLowerCase() - return ownedSets.filter((set) => set.name.toLowerCase().includes(searchLower)) - }, [ownedSets, searchTerm]) - - const hasNoContent = - invitations.length === 0 && activeMemberships.length === 0 && ownedSets.length === 0 - const hasNoResults = - searchTerm.trim() && - filteredInvitations.length === 0 && - filteredMemberships.length === 0 && - filteredOwnedSets.length === 0 && - !hasNoContent - - if (membershipsLoading || invitationsLoading) { - return null - } - - if (viewingSet) { - const activeMembers = members.filter((m) => m.status === 'active') - const totalCount = activeMembers.length + pendingInvitations.length - - return ( - -
- -
-
- Group Name - {viewingSet.name} -
-
-
- Provider -
- {getProviderIcon(viewingSet.providerId)} - - {getProviderDisplayName(viewingSet.providerId as PollingProvider)} - -
-
-
- - - -
-
- addEmail(value)} - onRemove={removeEmailItem} - placeholder='Enter email addresses' - placeholderWithTags='Add another email' - disabled={createInvitation.isPending} - fileInputOptions={fileInputOptions} - className='flex-1' - /> - - {createInvitation.isPending ? 'Sending...' : 'Invite'} - -
- {emailError &&

{emailError}

} -
-
- - - {membersLoading || pendingInvitationsLoading ? null : totalCount === 0 ? ( -

- No members yet. Send invitations above. -

- ) : ( -
- {activeMembers.map((member) => { - const name = member.userName || 'Unknown' - const avatarInitial = name.charAt(0).toUpperCase() - - return ( -
-
- - {member.userImage && } - - {avatarInitial} - - - -
-
- - {name} - - {member.credentials.length === 0 && ( - - Disconnected - - )} -
-
- {member.userEmail} -
-
-
- -
- handleRemoveMember(member.id), - }, - ]} - /> -
-
- ) - })} - - {pendingInvitations.map((invitation) => { - const email = invitation.email || 'Unknown' - const emailPrefix = email.split('@')[0] - const avatarInitial = emailPrefix.charAt(0).toUpperCase() - - return ( -
-
- - - {avatarInitial} - - - -
-
- - {emailPrefix} - - - Pending - -
-
- {email} -
-
-
- -
- 0, - onSelect: () => handleResendInvitation(invitation.id, email), - }, - { - label: cancellingInvitations.has(invitation.id) - ? 'Cancelling...' - : 'Cancel', - destructive: true, - disabled: cancellingInvitations.has(invitation.id), - onSelect: () => handleCancelInvitation(invitation.id), - }, - ]} - /> -
-
- ) - })} -
- )} -
-
- - ) - } - - return ( - <> - setShowCreateModal(true), - }, - ] - : undefined - } - > -
- {hasNoContent && !canManageCredentialSets ? ( - - You're not a member of any polling groups yet. When someone invites you, it will - appear here. - - ) : hasNoResults ? ( - - No results found matching "{searchTerm}" - - ) : ( -
- {filteredInvitations.length > 0 && ( - -
- {filteredInvitations.map((invitation) => ( - handleAcceptInvitation(invitation.token)} - disabled={acceptInvitation.isPending} - > - {acceptInvitation.isPending ? 'Accepting...' : 'Accept'} - - } - /> - ))} -
-
- )} - - {filteredMemberships.length > 0 && ( - -
- {filteredMemberships.map((membership) => ( - - handleLeave(membership.credentialSetId, membership.credentialSetName) - } - disabled={leaveCredentialSet.isPending} - > - Leave - - } - /> - ))} -
-
- )} - - {canManageCredentialSets && - (filteredOwnedSets.length > 0 || - ownedSetsLoading || - (!searchTerm.trim() && ownedSets.length === 0)) && ( - - {ownedSetsLoading ? null : !searchTerm.trim() && ownedSets.length === 0 ? ( -
- No polling groups created yet -
- ) : ( -
- {filteredOwnedSets.map((set) => ( - setViewingSet(set) }, - { - label: 'Delete', - destructive: true, - disabled: deletingSetIds.has(set.id), - onSelect: () => handleDeleteClick(set), - }, - ]} - /> - } - /> - ))} -
- )} -
- )} -
- )} -
-
- - - Create Polling Group - - { - setNewSetName(value) - if (createError) setCreateError(null) - }} - required - placeholder='e.g., Marketing Team' - /> - - - setNewSetProvider(v as PollingProvider)} - > - Gmail - Outlook - -

- Members will connect their {getProviderDisplayName(newSetProvider)} account -

-
- {createError} -
- -
- - { - if (!open) setLeavingMembership(null) - }} - srTitle='Leave Polling Group' - title='Leave Polling Group' - text={[ - 'Are you sure you want to leave ', - { text: leavingMembership?.name ?? 'this group', bold: true }, - '? Your email account will no longer be polled in workflows using this group.', - ]} - confirm={{ - label: 'Leave', - onClick: confirmLeave, - pending: leaveCredentialSet.isPending, - pendingLabel: 'Leaving...', - }} - /> - - { - if (!open) setDeletingSet(null) - }} - srTitle='Delete Polling Group' - title='Delete Polling Group' - text={[ - 'Are you sure you want to delete ', - { text: deletingSet?.name ?? 'this group', bold: true }, - '? This action cannot be undone.', - ]} - confirm={{ - label: 'Delete', - onClick: confirmDelete, - pending: deleteCredentialSet.isPending, - pendingLabel: 'Deleting...', - }} - /> - - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/credential-sets/index.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/credential-sets/index.ts deleted file mode 100644 index eb992fc6b13..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/credential-sets/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { CredentialSets } from './credential-sets' diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.tsx index a3bf1b764d6..51a7dbd9619 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.tsx @@ -6,7 +6,7 @@ import type { ReactNode } from 'react' * (action chips, a {@link RowActionsMenu}, a status label, etc.). * * Single source of truth for the credential-style row shared by the BYOK key - * manager, credential sets, and recently-deleted lists — never re-derive the + * manager and recently-deleted lists — never re-derive the * tile/text chrome per consumer. The tile force-sizes any ``/`` it * contains to 20px, so callers pass their raw icon node without pre-sizing it. */ diff --git a/apps/sim/app/workspace/[workspaceId]/settings/navigation.ts b/apps/sim/app/workspace/[workspaceId]/settings/navigation.ts index 538e7182899..5cbe90e4780 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/navigation.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/navigation.ts @@ -6,7 +6,6 @@ import { KeySquare, Lock, LogIn, - Mail, Palette, Send, Server, @@ -25,7 +24,6 @@ import { getEnv, isTruthy } from '@/lib/core/config/env' export type SettingsSection = | 'general' | 'secrets' - | 'credential-sets' | 'access-control' | 'audit-logs' | 'apikeys' @@ -79,7 +77,6 @@ export interface NavigationItem { } const isSSOEnabled = isTruthy(getEnv('NEXT_PUBLIC_SSO_ENABLED')) -const isCredentialSetsEnabled = isTruthy(getEnv('NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED')) const isAccessControlEnabled = isTruthy(getEnv('NEXT_PUBLIC_ACCESS_CONTROL_ENABLED')) const isInboxEnabled = isTruthy(getEnv('NEXT_PUBLIC_INBOX_ENABLED')) const isWhitelabelingEnabled = isTruthy(getEnv('NEXT_PUBLIC_WHITELABELING_ENABLED')) @@ -88,7 +85,6 @@ const isDataRetentionEnabled = isTruthy(getEnv('NEXT_PUBLIC_DATA_RETENTION_ENABL const isDataDrainsEnabled = isTruthy(getEnv('NEXT_PUBLIC_DATA_DRAINS_ENABLED')) export const isBillingEnabled = isTruthy(getEnv('NEXT_PUBLIC_BILLING_ENABLED')) -export { isCredentialSetsEnabled } export const sectionConfig: { key: NavigationSection; title: string }[] = [ { key: 'account', title: 'Account' }, @@ -216,17 +212,6 @@ export const allNavigationItems: NavigationItem[] = [ selfHostedOverride: isInboxEnabled, showWhenLocked: true, }, - ...(isCredentialSetsEnabled - ? [ - { - id: 'credential-sets' as const, - label: 'Email polling', - description: 'Share email-polling credentials across your team.', - icon: Mail, - section: 'system' as const, - }, - ] - : []), { id: 'recently-deleted', label: 'Recently deleted', diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx index b060f525835..72fa3638a55 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx @@ -2,10 +2,8 @@ import { useCallback, useMemo, useState } from 'react' import { Button, Combobox } from '@sim/emcn' -import { ExternalLink, KeyRound, Users } from 'lucide-react' +import { ExternalLink, KeyRound } from 'lucide-react' import { useParams } from 'next/navigation' -import { getSubscriptionAccessState } from '@/lib/billing/client' -import { getPollingProviderFromOAuth } from '@/lib/credential-sets/providers' import { consumeOAuthReturnContext, writeOAuthReturnContext } from '@/lib/credentials/client-state' import { getCanonicalScopesForProvider, @@ -16,7 +14,6 @@ import { } from '@/lib/oauth' import { getMissingRequiredScopes } from '@/lib/oauth/utils' import { ConnectOAuthModal } from '@/app/workspace/[workspaceId]/components/connect-oauth-modal' -import { isBillingEnabled } from '@/app/workspace/[workspaceId]/settings/navigation' import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text' import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight' import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' @@ -24,12 +21,8 @@ import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/c import { useActiveSearchTarget } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/providers/active-search-target-provider' import { getBareIconStyle, type StyleableIcon } from '@/blocks/icon-color' import type { SubBlockConfig } from '@/blocks/types' -import { CREDENTIAL_SET } from '@/executor/constants' -import { useCredentialSets } from '@/hooks/queries/credential-sets' import { useWorkspaceCredential, useWorkspaceCredentials } from '@/hooks/queries/credentials' import { useOAuthCredentials } from '@/hooks/queries/oauth/oauth-credentials' -import { useOrganizations } from '@/hooks/queries/organization' -import { useSubscriptionData } from '@/hooks/queries/subscription' import { useCredentialRefreshTriggers } from '@/hooks/use-credential-refresh-triggers' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' @@ -64,19 +57,6 @@ export function CredentialSelector({ const label = subBlock.placeholder || 'Select credential' const serviceId = subBlock.serviceId || '' const isAllCredentials = !serviceId - const supportsCredentialSets = subBlock.supportsCredentialSets || false - - const { data: organizationsData } = useOrganizations() - const { data: subscriptionData } = useSubscriptionData({ enabled: isBillingEnabled }) - const activeOrganization = organizationsData?.activeOrganization - const subscriptionAccess = getSubscriptionAccessState(subscriptionData?.data) - const hasTeamPlan = subscriptionAccess.hasUsableTeamAccess - const canUseCredentialSets = supportsCredentialSets && hasTeamPlan && !!activeOrganization?.id - - const { data: credentialSets = [] } = useCredentialSets( - activeOrganization?.id, - canUseCredentialSets - ) const { depsSatisfied, dependsOn } = useDependsOnGate(blockId, subBlock, { disabled, @@ -88,12 +68,7 @@ export function CredentialSelector({ const effectiveDisabled = disabled || (hasDependencies && !depsSatisfied) const effectiveValue = isPreview && previewValue !== undefined ? previewValue : storeValue - const rawSelectedId = typeof effectiveValue === 'string' ? effectiveValue : '' - const isCredentialSetSelected = rawSelectedId.startsWith(CREDENTIAL_SET.PREFIX) - const selectedId = isCredentialSetSelected ? '' : rawSelectedId - const selectedCredentialSetId = isCredentialSetSelected - ? rawSelectedId.slice(CREDENTIAL_SET.PREFIX.length) - : '' + const selectedId = typeof effectiveValue === 'string' ? effectiveValue : '' const effectiveProviderId = useMemo( () => getProviderIdFromServiceId(serviceId) as OAuthProvider, @@ -147,11 +122,6 @@ export function CredentialSelector({ [selectedCredential, selectedAllCredential] ) - const selectedCredentialSet = useMemo( - () => credentialSets.find((cs) => cs.id === selectedCredentialSetId), - [credentialSets, selectedCredentialSetId] - ) - const { data: inaccessibleCredential } = useWorkspaceCredential( selectedId || undefined, Boolean(selectedId) && @@ -163,12 +133,11 @@ export function CredentialSelector({ const inaccessibleCredentialName = inaccessibleCredential?.displayName ?? null const resolvedLabel = useMemo(() => { - if (selectedCredentialSet) return selectedCredentialSet.name if (selectedAllCredential) return selectedAllCredential.displayName if (selectedCredential) return selectedCredential.name if (inaccessibleCredentialName) return inaccessibleCredentialName return '' - }, [selectedCredentialSet, selectedAllCredential, selectedCredential, inaccessibleCredentialName]) + }, [selectedAllCredential, selectedCredential, inaccessibleCredentialName]) const displayValue = isEditing ? editingValue : resolvedLabel @@ -208,15 +177,6 @@ export function CredentialSelector({ [isPreview, setStoreValue] ) - const handleCredentialSetSelect = useCallback( - (credentialSetId: string) => { - if (isPreview) return - setStoreValue(`${CREDENTIAL_SET.PREFIX}${credentialSetId}`) - setIsEditing(false) - }, - [isPreview, setStoreValue] - ) - const handleAddCredential = useCallback(() => { setShowConnectModal(true) }, []) @@ -246,57 +206,10 @@ export function CredentialSelector({ .join(' ') }, []) - const { comboboxOptions, comboboxGroups } = useMemo(() => { + const comboboxOptions = useMemo(() => { if (isAllCredentials) { const oauthCredentials = allWorkspaceCredentials.filter((c) => c.type === 'oauth') - const options = oauthCredentials.map((cred) => ({ label: cred.displayName, value: cred.id })) - return { comboboxOptions: options, comboboxGroups: undefined } - } - - const pollingProviderId = getPollingProviderFromOAuth(effectiveProviderId) - // Handle both old ('gmail') and new ('google-email') provider IDs for backwards compatibility - const matchesProvider = (csProviderId: string | null) => { - if (!csProviderId || !pollingProviderId) return false - if (csProviderId === pollingProviderId) return true - // Handle legacy 'gmail' mapping to 'google-email' - if (pollingProviderId === 'google-email' && csProviderId === 'gmail') return true - return false - } - const filteredCredentialSets = pollingProviderId - ? credentialSets.filter((cs) => matchesProvider(cs.providerId)) - : [] - - if (canUseCredentialSets && filteredCredentialSets.length > 0) { - const groups = [] - - groups.push({ - section: 'Polling Groups', - items: filteredCredentialSets.map((cs) => ({ - label: cs.name, - value: `${CREDENTIAL_SET.PREFIX}${cs.id}`, - })), - }) - - const credentialItems = credentials.map((cred) => ({ - label: cred.name, - value: cred.id, - iconElement: getProviderIcon((cred.provider ?? provider) as OAuthProvider), - })) - credentialItems.push({ - label: - credentials.length > 0 - ? `Connect another ${getProviderName(provider)} account` - : `Connect ${getProviderName(provider)} account`, - value: '__connect_account__', - iconElement: , - }) - - groups.push({ - section: 'Personal Credential', - items: credentialItems, - }) - - return { comboboxOptions: [], comboboxGroups: groups } + return oauthCredentials.map((cred) => ({ label: cred.displayName, value: cred.id })) } const options = credentials.map((cred) => ({ @@ -314,17 +227,14 @@ export function CredentialSelector({ iconElement: , }) - return { comboboxOptions: options, comboboxGroups: undefined } + return options }, [ isAllCredentials, allWorkspaceCredentials, credentials, provider, - effectiveProviderId, getProviderIcon, getProviderName, - canUseCredentialSets, - credentialSets, ]) const selectedCredentialProvider = selectedCredential?.provider ?? provider @@ -338,19 +248,6 @@ export function CredentialSelector({ const overlayContent = useMemo(() => { if (!displayValue) return null - if (isCredentialSetSelected && selectedCredentialSet) { - return ( -
-
- -
- - {formatDisplayText(displayValue, { workflowSearchHighlight })} - -
- ) - } - if (isAllCredentials && selectedAllCredential) { return (
@@ -378,8 +275,6 @@ export function CredentialSelector({ getProviderIcon, displayValue, selectedCredentialProvider, - isCredentialSetSelected, - selectedCredentialSet, isAllCredentials, selectedAllCredential, workflowSearchHighlight, @@ -392,15 +287,6 @@ export function CredentialSelector({ return } - if (value.startsWith(CREDENTIAL_SET.PREFIX)) { - const credentialSetId = value.slice(CREDENTIAL_SET.PREFIX.length) - const matchedSet = credentialSets.find((cs) => cs.id === credentialSetId) - if (matchedSet) { - handleCredentialSetSelect(credentialSetId) - return - } - } - const matchedCred = ( isAllCredentials ? allWorkspaceCredentials.filter((c) => c.type === 'oauth') : credentials ).find((c) => c.id === value) @@ -412,24 +298,15 @@ export function CredentialSelector({ setIsEditing(true) setEditingValue(value) }, - [ - isAllCredentials, - allWorkspaceCredentials, - credentials, - credentialSets, - handleAddCredential, - handleSelect, - handleCredentialSetSelect, - ] + [isAllCredentials, allWorkspaceCredentials, credentials, handleAddCredential, handleSelect] ) return (
- Hello, - - {inviterName} from {organizationName} has invited you to - join the polling group {pollingGroupName} on {brand.name}. - - - - By accepting this invitation, your {providerName} account will be connected to enable email - polling for automated workflows. - - - - Accept Invitation - - - {/* Divider */} -
- - - This invitation expires in 7 days. If you weren't expecting this email, you can safely - ignore it. - - - ) -} - -export default PollingGroupInvitationEmail diff --git a/apps/sim/components/emails/render.ts b/apps/sim/components/emails/render.ts index 3747122ce49..652d6e7df2a 100644 --- a/apps/sim/components/emails/render.ts +++ b/apps/sim/components/emails/render.ts @@ -20,7 +20,6 @@ import { import { BatchInvitationEmail, InvitationEmail, - PollingGroupInvitationEmail, WorkspaceAddedEmail, WorkspaceInvitationEmail, } from '@/components/emails/invitations' @@ -244,24 +243,6 @@ export async function renderWorkspaceAddedEmail( ) } -export async function renderPollingGroupInvitationEmail(params: { - inviterName: string - organizationName: string - pollingGroupName: string - provider: 'google-email' | 'outlook' - inviteLink: string -}): Promise { - return await render( - PollingGroupInvitationEmail({ - inviterName: params.inviterName, - organizationName: params.organizationName, - pollingGroupName: params.pollingGroupName, - provider: params.provider, - inviteLink: params.inviteLink, - }) - ) -} - export async function renderPaymentFailedEmail(params: { userName?: string amountDue: number diff --git a/apps/sim/components/emails/subjects.ts b/apps/sim/components/emails/subjects.ts index 5714f2e2529..8eeb9b06fdf 100644 --- a/apps/sim/components/emails/subjects.ts +++ b/apps/sim/components/emails/subjects.ts @@ -12,7 +12,6 @@ export type EmailSubjectType = | 'invitation' | 'batch-invitation' | 'workspace-added' - | 'polling-group-invitation' | 'help-confirmation' | 'enterprise-subscription' | 'usage-threshold' @@ -52,8 +51,6 @@ export function getEmailSubject(type: EmailSubjectType): string { return `You've been invited to join a team and workspaces on ${brandName}` case 'workspace-added': return `You've been added to a workspace on ${brandName}` - case 'polling-group-invitation': - return `You've been invited to join an email polling group on ${brandName}` case 'help-confirmation': return 'Your request has been received' case 'enterprise-subscription': diff --git a/apps/sim/executor/constants.ts b/apps/sim/executor/constants.ts index 08fdf93a171..b5883c61905 100644 --- a/apps/sim/executor/constants.ts +++ b/apps/sim/executor/constants.ts @@ -221,18 +221,6 @@ export const MCP = { TOOL_PREFIX: 'mcp-', } as const -export const CREDENTIAL_SET = { - PREFIX: 'credentialSet:', -} as const - -export function isCredentialSetValue(value: string | null | undefined): boolean { - return typeof value === 'string' && value.startsWith(CREDENTIAL_SET.PREFIX) -} - -export function extractCredentialSetId(value: string): string { - return value.slice(CREDENTIAL_SET.PREFIX.length) -} - export const MEMORY = { DEFAULT_SLIDING_WINDOW_SIZE: 10, DEFAULT_SLIDING_WINDOW_TOKENS: 4000, diff --git a/apps/sim/hooks/queries/credential-sets.ts b/apps/sim/hooks/queries/credential-sets.ts deleted file mode 100644 index a984e339eea..00000000000 --- a/apps/sim/hooks/queries/credential-sets.ts +++ /dev/null @@ -1,318 +0,0 @@ -'use client' - -import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { requestJson } from '@/lib/api/client/request' -import type { - ContractBodyInput, - ContractParamsInput, - ContractQueryInput, -} from '@/lib/api/contracts' -import { - acceptCredentialSetInvitationContract, - type CreateCredentialSetData, - type CredentialSet, - type CredentialSetInvitation, - type CredentialSetInvitationListItem, - type CredentialSetMember, - type CredentialSetMembership, - cancelCredentialSetInvitationContract, - createCredentialSetContract, - createCredentialSetInvitationContract, - deleteCredentialSetContract, - leaveCredentialSetContract, - listCredentialSetInvitationDetailsContract, - listCredentialSetInvitationsContract, - listCredentialSetMembersContract, - listCredentialSetMembershipsContract, - listCredentialSetsContract, - removeCredentialSetMemberContract, - resendCredentialSetInvitationContract, -} from '@/lib/api/contracts' -import { fetchCredentialSetById } from '@/hooks/queries/utils/fetch-credential-set' - -export type { - CreateCredentialSetData, - CredentialSet, - CredentialSetInvitation, - CredentialSetInvitationListItem, - CredentialSetMember, - CredentialSetMembership, -} - -export const CREDENTIAL_SET_LIST_STALE_TIME = 60 * 1000 -export const CREDENTIAL_SET_DETAIL_STALE_TIME = 60 * 1000 -export const CREDENTIAL_SET_MEMBERSHIP_STALE_TIME = 60 * 1000 -export const CREDENTIAL_SET_INVITATION_LIST_STALE_TIME = 30 * 1000 -export const CREDENTIAL_SET_MEMBER_LIST_STALE_TIME = 30 * 1000 -export const CREDENTIAL_SET_INVITATION_DETAIL_STALE_TIME = 30 * 1000 - -export const credentialSetKeys = { - all: ['credentialSets'] as const, - lists: () => [...credentialSetKeys.all, 'list'] as const, - list: (organizationId?: string) => - [...credentialSetKeys.lists(), organizationId ?? 'none'] as const, - details: () => [...credentialSetKeys.all, 'detail'] as const, - detail: (id?: string) => [...credentialSetKeys.details(), id ?? 'none'] as const, - detailMembers: (credentialSetId?: string) => - [...credentialSetKeys.detail(credentialSetId), 'members'] as const, - detailInvitations: (credentialSetId?: string) => - [...credentialSetKeys.detail(credentialSetId), 'invitations'] as const, - memberships: () => [...credentialSetKeys.all, 'memberships'] as const, - invitations: () => [...credentialSetKeys.all, 'invitations'] as const, -} - -async function fetchCredentialSets( - organizationId: string, - signal?: AbortSignal -): Promise { - if (!organizationId) return [] - const data = await requestJson(listCredentialSetsContract, { - query: { organizationId }, - signal, - }) - return data.credentialSets ?? [] -} - -export function useCredentialSets(organizationId?: string, enabled = true) { - return useQuery({ - queryKey: credentialSetKeys.list(organizationId), - queryFn: ({ signal }) => fetchCredentialSets(organizationId ?? '', signal), - enabled: Boolean(organizationId) && enabled, - staleTime: CREDENTIAL_SET_LIST_STALE_TIME, - placeholderData: keepPreviousData, - }) -} - -export function useCredentialSetDetail(id?: string, enabled = true) { - return useQuery({ - queryKey: credentialSetKeys.detail(id), - queryFn: ({ signal }) => fetchCredentialSetById(id ?? '', signal), - enabled: Boolean(id) && enabled, - staleTime: CREDENTIAL_SET_DETAIL_STALE_TIME, - placeholderData: keepPreviousData, - }) -} - -export function useCredentialSetMemberships() { - return useQuery({ - queryKey: credentialSetKeys.memberships(), - queryFn: async ({ signal }) => { - const data = await requestJson(listCredentialSetMembershipsContract, { signal }) - return data.memberships ?? [] - }, - staleTime: CREDENTIAL_SET_MEMBERSHIP_STALE_TIME, - }) -} - -export function useCredentialSetInvitations() { - return useQuery({ - queryKey: credentialSetKeys.invitations(), - queryFn: async ({ signal }) => { - const data = await requestJson(listCredentialSetInvitationsContract, { signal }) - return data.invitations ?? [] - }, - staleTime: CREDENTIAL_SET_INVITATION_LIST_STALE_TIME, - }) -} - -export function useAcceptCredentialSetInvitation() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async (token: string) => { - return requestJson(acceptCredentialSetInvitationContract, { - params: { token }, - }) - }, - onSettled: () => { - return Promise.all([ - queryClient.invalidateQueries({ queryKey: credentialSetKeys.memberships() }), - queryClient.invalidateQueries({ queryKey: credentialSetKeys.invitations() }), - ]) - }, - }) -} - -export function useCreateCredentialSet() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async (data: CreateCredentialSetData) => { - return requestJson(createCredentialSetContract, { body: data }) - }, - onSettled: (_data, _error, variables) => { - return queryClient.invalidateQueries({ - queryKey: credentialSetKeys.list(variables.organizationId), - }) - }, - }) -} - -export function useCreateCredentialSetInvitation() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ( - data: { credentialSetId: string } & ContractBodyInput< - typeof createCredentialSetInvitationContract - > - ) => { - return requestJson(createCredentialSetInvitationContract, { - params: { id: data.credentialSetId }, - body: { email: data.email }, - }) - }, - onSettled: (_data, _error, variables) => { - return Promise.all([ - queryClient.invalidateQueries({ - queryKey: credentialSetKeys.detailInvitations(variables.credentialSetId), - }), - queryClient.invalidateQueries({ queryKey: credentialSetKeys.invitations() }), - ]) - }, - }) -} - -export function useCredentialSetMembers(credentialSetId?: string) { - return useQuery({ - queryKey: credentialSetKeys.detailMembers(credentialSetId), - queryFn: async ({ signal }) => { - if (!credentialSetId) return [] - const data = await requestJson(listCredentialSetMembersContract, { - params: { id: credentialSetId }, - signal, - }) - return data.members ?? [] - }, - enabled: Boolean(credentialSetId), - staleTime: CREDENTIAL_SET_MEMBER_LIST_STALE_TIME, - }) -} - -export function useRemoveCredentialSetMember() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ( - data: { credentialSetId: string } & ContractQueryInput< - typeof removeCredentialSetMemberContract - > - ) => { - return requestJson(removeCredentialSetMemberContract, { - params: { id: data.credentialSetId }, - query: { memberId: data.memberId }, - }) - }, - onSettled: (_data, _error, variables) => { - return Promise.all([ - queryClient.invalidateQueries({ - queryKey: credentialSetKeys.detailMembers(variables.credentialSetId), - }), - queryClient.invalidateQueries({ queryKey: credentialSetKeys.memberships() }), - ]) - }, - }) -} - -export function useLeaveCredentialSet() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async (credentialSetId: string) => { - return requestJson(leaveCredentialSetContract, { - query: { credentialSetId }, - }) - }, - onSettled: () => { - return queryClient.invalidateQueries({ queryKey: credentialSetKeys.memberships() }) - }, - }) -} - -export interface DeleteCredentialSetParams { - credentialSetId: string - organizationId: string -} - -export function useDeleteCredentialSet() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ({ credentialSetId }: DeleteCredentialSetParams) => { - return requestJson(deleteCredentialSetContract, { - params: { id: credentialSetId }, - }) - }, - onSettled: (_data, _error, variables) => { - return Promise.all([ - queryClient.invalidateQueries({ - queryKey: credentialSetKeys.list(variables.organizationId), - }), - queryClient.invalidateQueries({ queryKey: credentialSetKeys.memberships() }), - queryClient.invalidateQueries({ - queryKey: credentialSetKeys.detail(variables.credentialSetId), - }), - ]) - }, - }) -} - -export function useCredentialSetInvitationsDetail(credentialSetId?: string) { - return useQuery({ - queryKey: credentialSetKeys.detailInvitations(credentialSetId), - queryFn: async ({ signal }) => { - if (!credentialSetId) return [] - const data = await requestJson(listCredentialSetInvitationDetailsContract, { - params: { id: credentialSetId }, - signal, - }) - return (data.invitations ?? []).filter((inv) => inv.status === 'pending') - }, - enabled: Boolean(credentialSetId), - staleTime: CREDENTIAL_SET_INVITATION_DETAIL_STALE_TIME, - }) -} - -export function useCancelCredentialSetInvitation() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ( - data: { credentialSetId: string } & ContractQueryInput< - typeof cancelCredentialSetInvitationContract - > - ) => { - return requestJson(cancelCredentialSetInvitationContract, { - params: { id: data.credentialSetId }, - query: { invitationId: data.invitationId }, - }) - }, - onSettled: (_data, _error, variables) => { - return queryClient.invalidateQueries({ - queryKey: credentialSetKeys.detailInvitations(variables.credentialSetId), - }) - }, - }) -} - -export function useResendCredentialSetInvitation() { - const queryClient = useQueryClient() - - return useMutation({ - mutationFn: async ( - data: { credentialSetId: string; email: string } & Pick< - ContractParamsInput, - 'invitationId' - > - ) => { - return requestJson(resendCredentialSetInvitationContract, { - params: { id: data.credentialSetId, invitationId: data.invitationId }, - }) - }, - onSettled: (_data, _error, variables) => { - return queryClient.invalidateQueries({ - queryKey: credentialSetKeys.detailInvitations(variables.credentialSetId), - }) - }, - }) -} diff --git a/apps/sim/hooks/queries/oauth/oauth-credentials.ts b/apps/sim/hooks/queries/oauth/oauth-credentials.ts index bfe0d75c403..2cf9f22cc16 100644 --- a/apps/sim/hooks/queries/oauth/oauth-credentials.ts +++ b/apps/sim/hooks/queries/oauth/oauth-credentials.ts @@ -2,8 +2,6 @@ import { useQuery } from '@tanstack/react-query' import { requestJson } from '@/lib/api/client/request' import { listOAuthCredentialsContract } from '@/lib/api/contracts' import type { Credential } from '@/lib/oauth' -import { CREDENTIAL_SET } from '@/executor/constants' -import { useCredentialSetDetail } from '@/hooks/queries/credential-sets' import { useWorkspaceCredential } from '@/hooks/queries/credentials' export const OAUTH_CREDENTIAL_LIST_STALE_TIME = 60 * 1000 @@ -128,22 +126,10 @@ export function useCredentialName( workflowId?: string, workspaceId?: string ) { - // Check if this is a credential set value - const isCredentialSet = credentialId?.startsWith(CREDENTIAL_SET.PREFIX) ?? false - const credentialSetId = isCredentialSet - ? credentialId?.slice(CREDENTIAL_SET.PREFIX.length) - : undefined - - // Fetch credential set by ID directly - const { data: credentialSetData, isFetching: credentialSetLoading } = useCredentialSetDetail( - credentialSetId, - isCredentialSet - ) - const { data: credentials = [], isFetching: credentialsLoading } = useOAuthCredentials( providerId, { - enabled: Boolean(providerId) && !isCredentialSet, + enabled: Boolean(providerId), workspaceId, workflowId, } @@ -151,9 +137,7 @@ export function useCredentialName( const selectedCredential = credentials.find((cred) => cred.id === credentialId) - const shouldFetchDetail = Boolean( - credentialId && !selectedCredential && providerId && workflowId && !isCredentialSet - ) + const shouldFetchDetail = Boolean(credentialId && !selectedCredential && providerId && workflowId) const { data: foreignCredentials = [], isFetching: foreignLoading } = useOAuthCredentialDetail( shouldFetchDetail ? credentialId : undefined, @@ -163,25 +147,17 @@ export function useCredentialName( // Fallback for credential blocks that have no serviceId/providerId — look up by ID directly const { data: workspaceCredential, isFetching: workspaceCredentialLoading } = - useWorkspaceCredential(!providerId && !isCredentialSet ? credentialId : undefined) + useWorkspaceCredential(!providerId ? credentialId : undefined) const detailCredential = foreignCredentials[0] const hasForeignMeta = foreignCredentials.length > 0 const displayName = - credentialSetData?.name ?? - selectedCredential?.name ?? - detailCredential?.name ?? - workspaceCredential?.displayName ?? - null + selectedCredential?.name ?? detailCredential?.name ?? workspaceCredential?.displayName ?? null return { displayName, - isLoading: - credentialsLoading || - foreignLoading || - workspaceCredentialLoading || - (isCredentialSet && credentialSetLoading && !credentialSetData), + isLoading: credentialsLoading || foreignLoading || workspaceCredentialLoading, hasForeignMeta, } } diff --git a/apps/sim/hooks/queries/utils/fetch-credential-set.ts b/apps/sim/hooks/queries/utils/fetch-credential-set.ts deleted file mode 100644 index c9523d53633..00000000000 --- a/apps/sim/hooks/queries/utils/fetch-credential-set.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { requestJson } from '@/lib/api/client/request' -import { type CredentialSet, getCredentialSetContract } from '@/lib/api/contracts' - -/** - * Fetches a credential set by id (returns `null` for an empty id). - * - * Lives in this standalone (non-`'use client'`) module so server-reachable - * workflow-comparison helpers can import it without pulling client-reference - * stubs from the `'use client'` `@/hooks/queries/credential-sets` module. - */ -export async function fetchCredentialSetById( - id: string, - signal?: AbortSignal -): Promise { - if (!id) return null - const data = await requestJson(getCredentialSetContract, { - params: { id }, - signal, - }) - return data.credentialSet ?? null -} diff --git a/apps/sim/hooks/use-webhook-management.ts b/apps/sim/hooks/use-webhook-management.ts index 1d534a48463..9b03b378b3b 100644 --- a/apps/sim/hooks/use-webhook-management.ts +++ b/apps/sim/hooks/use-webhook-management.ts @@ -157,7 +157,6 @@ export function useWebhookManagement({ const { credentialId: _credId, - credentialSetId: _credSetId, userId: _userId, historyId: _historyId, lastCheckedTimestamp: _lastChecked, diff --git a/apps/sim/lib/api/contracts/credential-sets.ts b/apps/sim/lib/api/contracts/credential-sets.ts deleted file mode 100644 index b191979a714..00000000000 --- a/apps/sim/lib/api/contracts/credential-sets.ts +++ /dev/null @@ -1,344 +0,0 @@ -import { z } from 'zod' -import { defineRouteContract } from '@/lib/api/contracts/types' - -export const credentialSetSchema = z.object({ - id: z.string(), - name: z.string(), - description: z.string().nullable(), - providerId: z.string().nullable(), - createdBy: z.string(), - createdAt: z.string(), - updatedAt: z.string(), - creatorName: z.string().nullable(), - creatorEmail: z.string().nullable(), - memberCount: z.number(), -}) - -export const credentialSetWriteSchema = z.object({ - id: z.string(), - organizationId: z.string(), - name: z.string(), - description: z.string().nullable(), - providerId: z.string().nullable(), - createdBy: z.string(), - createdAt: z.string(), - updatedAt: z.string(), -}) - -export const credentialSetMembershipSchema = z.object({ - membershipId: z.string(), - status: z.string(), - joinedAt: z.string().nullable(), - credentialSetId: z.string(), - credentialSetName: z.string(), - credentialSetDescription: z.string().nullable(), - providerId: z.string().nullable(), - organizationId: z.string(), - organizationName: z.string(), -}) - -export const credentialSetInvitationSchema = z.object({ - invitationId: z.string(), - token: z.string(), - status: z.string(), - expiresAt: z.string(), - createdAt: z.string(), - credentialSetId: z.string(), - credentialSetName: z.string(), - providerId: z.string().nullable(), - organizationId: z.string(), - organizationName: z.string(), - invitedByName: z.string().nullable(), - invitedByEmail: z.string().nullable(), -}) - -export const credentialSetInvitationDetailSchema = z.object({ - id: z.string(), - credentialSetId: z.string(), - email: z.string().nullable(), - token: z.string(), - status: z.string(), - expiresAt: z.string(), - createdAt: z.string(), - invitedBy: z.string(), -}) - -/** - * Management-list view of an invitation. Omits the bearer `token` — the secret - * redeemed via the invite link — so listing a credential set's invitations never - * broadcasts it. Only the creating admin receives the token (and its `inviteUrl`) - * in the create response. - */ -export const credentialSetInvitationListItemSchema = credentialSetInvitationDetailSchema.omit({ - token: true, -}) - -export const credentialSetInvitePreviewSchema = z.object({ - credentialSetName: z.string(), - organizationName: z.string(), - providerId: z.string().nullable(), - email: z.string().nullable(), -}) - -export const credentialSetMemberSchema = z.object({ - id: z.string(), - userId: z.string(), - status: z.string(), - joinedAt: z.string().nullable(), - createdAt: z.string(), - userName: z.string().nullable(), - userEmail: z.string().nullable(), - userImage: z.string().nullable(), - credentials: z.array( - z.object({ - providerId: z.string(), - accountId: z.string(), - }) - ), -}) - -export type CredentialSet = z.output -export type CredentialSetMembership = z.output -export type CredentialSetInvitation = z.output -export type CredentialSetMember = z.output -export type CredentialSetInvitationDetail = z.output -export type CredentialSetInvitationListItem = z.output -export type CredentialSetInvitePreview = z.output - -export const listCredentialSetsQuerySchema = z.object({ - organizationId: z.string().min(1), -}) - -export const createCredentialSetBodySchema = z.object({ - organizationId: z.string().min(1), - name: z.string().trim().min(1).max(100), - description: z.string().max(500).optional(), - providerId: z.enum(['google-email', 'outlook']), -}) - -export type CreateCredentialSetData = z.input - -export const credentialSetIdParamsSchema = z.object({ - id: z.string().min(1), -}) - -export const credentialSetInviteTokenParamsSchema = z.object({ - token: z.string().min(1), -}) - -export const credentialSetInvitationParamsSchema = z.object({ - id: z.string().min(1), - invitationId: z.string().min(1), -}) - -export const createCredentialSetInvitationBodySchema = z.object({ - email: z.string().email().optional(), -}) - -export const removeCredentialSetMemberQuerySchema = z.object({ - memberId: z.string().min(1), -}) - -export const leaveCredentialSetQuerySchema = z.object({ - credentialSetId: z.string().min(1), -}) - -export const cancelCredentialSetInvitationQuerySchema = z.object({ - invitationId: z.string().min(1), -}) - -export const updateCredentialSetBodySchema = z.object({ - name: z.string().trim().min(1).max(100).optional(), - description: z.string().max(500).nullable().optional(), -}) - -const successResponseSchema = z.object({ - success: z.literal(true), -}) - -export const listCredentialSetsContract = defineRouteContract({ - method: 'GET', - path: '/api/credential-sets', - query: listCredentialSetsQuerySchema, - response: { - mode: 'json', - schema: z.object({ - credentialSets: z.array(credentialSetSchema).optional(), - }), - }, -}) - -export const createCredentialSetContract = defineRouteContract({ - method: 'POST', - path: '/api/credential-sets', - body: createCredentialSetBodySchema, - response: { - mode: 'json', - schema: z.object({ - credentialSet: credentialSetSchema, - }), - }, -}) - -export const getCredentialSetContract = defineRouteContract({ - method: 'GET', - path: '/api/credential-sets/[id]', - params: credentialSetIdParamsSchema, - response: { - mode: 'json', - schema: z.object({ - credentialSet: credentialSetSchema.optional(), - }), - }, -}) - -export const deleteCredentialSetContract = defineRouteContract({ - method: 'DELETE', - path: '/api/credential-sets/[id]', - params: credentialSetIdParamsSchema, - response: { - mode: 'json', - schema: successResponseSchema, - }, -}) - -export const updateCredentialSetContract = defineRouteContract({ - method: 'PUT', - path: '/api/credential-sets/[id]', - params: credentialSetIdParamsSchema, - body: updateCredentialSetBodySchema, - response: { - mode: 'json', - schema: z.object({ - credentialSet: credentialSetWriteSchema.passthrough().optional(), - }), - }, -}) - -export const listCredentialSetMembershipsContract = defineRouteContract({ - method: 'GET', - path: '/api/credential-sets/memberships', - response: { - mode: 'json', - schema: z.object({ - memberships: z.array(credentialSetMembershipSchema).optional(), - }), - }, -}) - -export const listCredentialSetInvitationsContract = defineRouteContract({ - method: 'GET', - path: '/api/credential-sets/invitations', - response: { - mode: 'json', - schema: z.object({ - invitations: z.array(credentialSetInvitationSchema).optional(), - }), - }, -}) - -export const listCredentialSetMembersContract = defineRouteContract({ - method: 'GET', - path: '/api/credential-sets/[id]/members', - params: credentialSetIdParamsSchema, - response: { - mode: 'json', - schema: z.object({ - members: z.array(credentialSetMemberSchema).optional(), - }), - }, -}) - -export const listCredentialSetInvitationDetailsContract = defineRouteContract({ - method: 'GET', - path: '/api/credential-sets/[id]/invite', - params: credentialSetIdParamsSchema, - response: { - mode: 'json', - schema: z.object({ - invitations: z.array(credentialSetInvitationListItemSchema).optional(), - }), - }, -}) - -export const getCredentialSetInvitationContract = defineRouteContract({ - method: 'GET', - path: '/api/credential-sets/invite/[token]', - params: credentialSetInviteTokenParamsSchema, - response: { - mode: 'json', - schema: z.object({ - invitation: credentialSetInvitePreviewSchema, - }), - }, -}) - -export const acceptCredentialSetInvitationContract = defineRouteContract({ - method: 'POST', - path: '/api/credential-sets/invite/[token]', - params: credentialSetInviteTokenParamsSchema, - response: { - mode: 'json', - schema: successResponseSchema.extend({ - credentialSetId: z.string(), - providerId: z.string().nullable(), - }), - }, -}) - -export const createCredentialSetInvitationContract = defineRouteContract({ - method: 'POST', - path: '/api/credential-sets/[id]/invite', - params: credentialSetIdParamsSchema, - body: createCredentialSetInvitationBodySchema, - response: { - mode: 'json', - schema: z.object({ - invitation: credentialSetInvitationDetailSchema.extend({ - inviteUrl: z.string(), - }), - }), - }, -}) - -export const removeCredentialSetMemberContract = defineRouteContract({ - method: 'DELETE', - path: '/api/credential-sets/[id]/members', - params: credentialSetIdParamsSchema, - query: removeCredentialSetMemberQuerySchema, - response: { - mode: 'json', - schema: successResponseSchema, - }, -}) - -export const leaveCredentialSetContract = defineRouteContract({ - method: 'DELETE', - path: '/api/credential-sets/memberships', - query: leaveCredentialSetQuerySchema, - response: { - mode: 'json', - schema: successResponseSchema, - }, -}) - -export const cancelCredentialSetInvitationContract = defineRouteContract({ - method: 'DELETE', - path: '/api/credential-sets/[id]/invite', - params: credentialSetIdParamsSchema, - query: cancelCredentialSetInvitationQuerySchema, - response: { - mode: 'json', - schema: successResponseSchema, - }, -}) - -export const resendCredentialSetInvitationContract = defineRouteContract({ - method: 'POST', - path: '/api/credential-sets/[id]/invite/[invitationId]', - params: credentialSetInvitationParamsSchema, - response: { - mode: 'json', - schema: successResponseSchema, - }, -}) diff --git a/apps/sim/lib/api/contracts/index.ts b/apps/sim/lib/api/contracts/index.ts index 690c60f25c5..ef339965c5b 100644 --- a/apps/sim/lib/api/contracts/index.ts +++ b/apps/sim/lib/api/contracts/index.ts @@ -5,7 +5,6 @@ export * from './byok-keys' export * from './chats' export * from './common' export * from './copilot' -export * from './credential-sets' export * from './credentials' export * from './demo-requests' export * from './environment' diff --git a/apps/sim/lib/api/contracts/webhooks.ts b/apps/sim/lib/api/contracts/webhooks.ts index ca309c6f44e..4903d558614 100644 --- a/apps/sim/lib/api/contracts/webhooks.ts +++ b/apps/sim/lib/api/contracts/webhooks.ts @@ -133,15 +133,6 @@ const listWebhooksResponseSchema = z.object({ const upsertWebhookResponseSchema = z.object({ webhook: webhookDataSchema, - credentialSetInfo: z - .object({ - credentialSetId: z.string(), - totalWebhooks: z.number(), - created: z.number(), - updated: z.number(), - deleted: z.number(), - }) - .optional(), }) const getWebhookResponseSchema = z.object({ diff --git a/apps/sim/lib/auth/auth.ts b/apps/sim/lib/auth/auth.ts index 3640723a32f..8accab00a40 100644 --- a/apps/sim/lib/auth/auth.ts +++ b/apps/sim/lib/auth/auth.ts @@ -84,7 +84,6 @@ import { quickValidateEmail } from '@/lib/messaging/email/validation' import { validateSignupEmailMx } from '@/lib/messaging/email/validation.server' import { scheduleLifecycleEmail } from '@/lib/messaging/lifecycle' import { captureServerEvent, getPostHogClient } from '@/lib/posthog/server' -import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server' import { disableUserResources } from '@/lib/workflows/lifecycle' import { SSO_TRUSTED_PROVIDERS } from '@/ee/sso/constants' import { createAnonymousSession, ensureAnonymousUserExists } from './anonymous' @@ -550,46 +549,6 @@ export const auth = betterAuth({ .where(eq(schema.account.id, account.id)) } - // Sync webhooks for credential sets after connecting a new credential - const requestId = generateId().slice(0, 8) - const userMemberships = await db - .select({ - credentialSetId: schema.credentialSetMember.credentialSetId, - providerId: schema.credentialSet.providerId, - }) - .from(schema.credentialSetMember) - .innerJoin( - schema.credentialSet, - eq(schema.credentialSetMember.credentialSetId, schema.credentialSet.id) - ) - .where( - and( - eq(schema.credentialSetMember.userId, account.userId), - eq(schema.credentialSetMember.status, 'active') - ) - ) - - for (const membership of userMemberships) { - if (membership.providerId === account.providerId) { - try { - await syncAllWebhooksForCredentialSet(membership.credentialSetId, requestId) - logger.info('[account.create.after] Synced webhooks after credential connect', { - credentialSetId: membership.credentialSetId, - providerId: account.providerId, - }) - } catch (error) { - logger.error( - '[account.create.after] Failed to sync webhooks after credential connect', - { - credentialSetId: membership.credentialSetId, - providerId: account.providerId, - error, - } - ) - } - } - } - try { PlatformEvents.oauthConnected({ userId: account.userId, diff --git a/apps/sim/lib/billing/core/subscription.test.ts b/apps/sim/lib/billing/core/subscription.test.ts index 4d25f9ec426..6f82cdba7ed 100644 --- a/apps/sim/lib/billing/core/subscription.test.ts +++ b/apps/sim/lib/billing/core/subscription.test.ts @@ -33,7 +33,6 @@ vi.mock('@/lib/billing/subscriptions/utils', () => ({ vi.mock('@/lib/core/config/env-flags', () => ({ isAccessControlEnabled: false, isBillingEnabled: true, - isCredentialSetsEnabled: false, isHosted: true, isInboxEnabled: false, isSsoEnabled: false, diff --git a/apps/sim/lib/billing/core/subscription.ts b/apps/sim/lib/billing/core/subscription.ts index 5084ce91081..cd0eb71f9ec 100644 --- a/apps/sim/lib/billing/core/subscription.ts +++ b/apps/sim/lib/billing/core/subscription.ts @@ -25,7 +25,6 @@ import { import { isAccessControlEnabled, isBillingEnabled, - isCredentialSetsEnabled, isHosted, isInboxEnabled, isSsoEnabled, @@ -336,91 +335,6 @@ export async function isEnterpriseOrgAdminOrOwner(userId: string): Promise { - try { - if (!isBillingEnabled) { - return true - } - - const [memberRecord] = await db - .select({ - organizationId: member.organizationId, - role: member.role, - }) - .from(member) - .where(eq(member.userId, userId)) - .limit(1) - - if (!memberRecord) { - return false - } - - if (memberRecord.role !== 'owner' && memberRecord.role !== 'admin') { - return false - } - - const billingStatus = await getEffectiveBillingStatus(userId) - if (billingStatus.billingBlocked) { - return false - } - - const orgSub = await getOrganizationSubscriptionUsable(memberRecord.organizationId) - - const hasTeamPlan = orgSub && (checkTeamPlan(orgSub) || checkEnterprisePlan(orgSub)) - - if (hasTeamPlan) { - logger.info('User is team org admin/owner', { - userId, - organizationId: memberRecord.organizationId, - role: memberRecord.role, - plan: orgSub.plan, - }) - } - - return !!hasTeamPlan - } catch (error) { - logger.error('Error checking team org admin/owner status', { error, userId }) - return false - } -} - -/** - * Check if an organization has team or enterprise plan - * Used at execution time (e.g., polling services) to check org billing directly - */ -export async function isOrganizationOnTeamOrEnterprisePlan( - organizationId: string -): Promise { - try { - if (!isBillingEnabled) { - return true - } - - if (isCredentialSetsEnabled && !isHosted) { - return true - } - - if (await isOrganizationBillingBlocked(organizationId)) { - return false - } - - const orgSub = await getOrganizationSubscriptionUsable(organizationId) - - return !!orgSub && (checkTeamPlan(orgSub) || checkEnterprisePlan(orgSub)) - } catch (error) { - logger.error('Error checking organization plan status', { error, organizationId }) - return false - } -} - /** * Check if an organization has an enterprise plan * Used for Access Control (Permission Groups) feature gating @@ -448,27 +362,6 @@ export async function isOrganizationOnEnterprisePlan(organizationId: string): Pr } } -/** - * Check if user has access to credential sets (email polling) feature - * Returns true if: - * - CREDENTIAL_SETS_ENABLED env var is set (self-hosted override), OR - * - User is admin/owner of a team/enterprise organization - * - * In non-production environments, returns true for convenience. - */ -export async function hasCredentialSetsAccess(userId: string): Promise { - try { - if (isCredentialSetsEnabled && !isHosted) { - return true - } - - return isTeamOrgAdminOrOwner(userId) - } catch (error) { - logger.error('Error checking credential sets access', { error, userId }) - return false - } -} - /** * Check if user has access to SSO feature * Returns true if: diff --git a/apps/sim/lib/billing/index.ts b/apps/sim/lib/billing/index.ts index eff8e32c75f..8c5c8b36d45 100644 --- a/apps/sim/lib/billing/index.ts +++ b/apps/sim/lib/billing/index.ts @@ -10,15 +10,12 @@ export * from '@/lib/billing/core/organization' export * from '@/lib/billing/core/subscription' export { getHighestPrioritySubscription as getActiveSubscription, - hasCredentialSetsAccess, hasPaidSubscription, hasSSOAccess, isEnterpriseOrgAdminOrOwner, isEnterprisePlan as hasEnterprisePlan, isOrganizationOnEnterprisePlan, - isOrganizationOnTeamOrEnterprisePlan, isProPlan as hasProPlan, - isTeamOrgAdminOrOwner, isTeamPlan as hasTeamPlan, isWorkspaceOnEnterprisePlan, sendPlanWelcomeEmail, diff --git a/apps/sim/lib/compare/data/competitors/tines.ts b/apps/sim/lib/compare/data/competitors/tines.ts index eab44d28cbf..a02becb6e3a 100644 --- a/apps/sim/lib/compare/data/competitors/tines.ts +++ b/apps/sim/lib/compare/data/competitors/tines.ts @@ -829,7 +829,7 @@ export const tinesProfile: CompetitorProfile = { value: "Yes: credentials are scoped to Teams by default, and Team Admin/Editor roles control which teams a credential can be shared with. Sensitive settings like Access (where a credential can be used) and Domains (allowed outbound hosts/paths) are restricted to Team Admins or the credential's creator. Custom roles can extend the default viewer/builder/manager roles for finer-grained control.", detail: - "Governance operates at the team/role level with per-credential Access and Domain restrictions, not a credential-set-to-role assignment matrix like Sim's, but reaches a similar outcome.", + "Governance operates at the team/role level with per-credential Access and Domain restrictions, not a credential-to-role assignment matrix like Sim's, but reaches a similar outcome.", shortValue: 'Yes: team-scoped credential access rules', confidence: 'verified', sources: [ diff --git a/apps/sim/lib/compare/data/feature-catalog.ts b/apps/sim/lib/compare/data/feature-catalog.ts index f5c4f55f027..4f9c4b614db 100644 --- a/apps/sim/lib/compare/data/feature-catalog.ts +++ b/apps/sim/lib/compare/data/feature-catalog.ts @@ -439,7 +439,7 @@ export const SIM_FEATURES: SimFeature[] = [ category: 'environments-enterprise', tags: ['enterprise', 'security', 'flagship'], description: - "Forking never copies credentials. All credential references are cleared in the child workspace at creation time. Instead, an admin explicitly maps each source OAuth/service-account credential to the target workspace's own credential via a dedicated mapping UI/API before promoting; environment variables remap by name (including rewriting {{ENV_KEY}} references inside copied custom-tool code or MCP headers if renamed, e.g. SLACK_API_KEY → SLACK_API_KEY_TEST). Credential and env-var mappings are required. An unmapped one blocks the promote rather than silently syncing a secret across environments, while optional resources (knowledge bases, tables, files, MCP servers) clear gracefully if unmapped. Org-scoped shared credential sets are preserved verbatim across environments.", + "Forking never copies credentials. All credential references are cleared in the child workspace at creation time. Instead, an admin explicitly maps each source OAuth/service-account credential to the target workspace's own credential via a dedicated mapping UI/API before promoting; environment variables remap by name (including rewriting {{ENV_KEY}} references inside copied custom-tool code or MCP headers if renamed, e.g. SLACK_API_KEY → SLACK_API_KEY_TEST). Credential and env-var mappings are required. An unmapped one blocks the promote rather than silently syncing a secret across environments, while optional resources (knowledge bases, tables, files, MCP servers) clear gracefully if unmapped.", competitiveNote: 'Treating credentials/env-vars as required-and-blocking on promote (rather than silently copying secrets) is a specific, auditable safety design for enterprise dev→qa→prod pipelines.', sources: [ diff --git a/apps/sim/lib/core/config/env-flags.ts b/apps/sim/lib/core/config/env-flags.ts index ce8aef23e50..b9f3478159f 100644 --- a/apps/sim/lib/core/config/env-flags.ts +++ b/apps/sim/lib/core/config/env-flags.ts @@ -139,12 +139,6 @@ export const isTriggerDevEnabled = isTruthy(env.TRIGGER_DEV_ENABLED) */ export const isSsoEnabled = isTruthy(env.SSO_ENABLED) -/** - * Is credential sets (email polling) enabled via env var override - * This bypasses plan requirements for self-hosted deployments - */ -export const isCredentialSetsEnabled = isTruthy(env.CREDENTIAL_SETS_ENABLED) - /** * Is access control (permission groups) enabled via env var override * This bypasses plan requirements for self-hosted deployments diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 7f5f117b961..5985231962e 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -412,9 +412,6 @@ export const env = createEnv({ MOTHERSHIP_E2B_DOC_TEMPLATE_ID: z.string().optional(), // Dedicated E2B template with python-pptx/docx/openpyxl/reportlab for document generation; when set (and E2B enabled), docs compile via Python instead of the JS isolated-vm path E2B_PI_TEMPLATE_ID: z.string().optional(), // E2B template ID/alias with the Pi CLI + git baked in (Pi Coding Agent cloud mode) - // Credential Sets (Email Polling) - for self-hosted deployments - CREDENTIAL_SETS_ENABLED: z.boolean().optional(), // Enable credential sets on self-hosted (bypasses plan requirements) - // Access Control (Permission Groups) - for self-hosted deployments ACCESS_CONTROL_ENABLED: z.boolean().optional(), // Enable access control on self-hosted (bypasses plan requirements) @@ -518,7 +515,6 @@ export const env = createEnv({ // Feature Flags NEXT_PUBLIC_SSO_ENABLED: z.boolean().optional(), // Enable SSO login UI components - NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED: z.boolean().optional(), // Enable credential sets (email polling) on self-hosted NEXT_PUBLIC_ACCESS_CONTROL_ENABLED: z.boolean().optional(), // Enable access control (permission groups) on self-hosted NEXT_PUBLIC_WHITELABELING_ENABLED: z.boolean().optional(), // Enable whitelabeling on self-hosted (bypasses hosted requirements) NEXT_PUBLIC_AUDIT_LOGS_ENABLED: z.boolean().optional(), // Enable audit logs on self-hosted (bypasses hosted requirements) @@ -558,7 +554,6 @@ export const env = createEnv({ NEXT_PUBLIC_BRAND_ACCENT_HOVER_COLOR: process.env.NEXT_PUBLIC_BRAND_ACCENT_HOVER_COLOR, NEXT_PUBLIC_BRAND_BACKGROUND_COLOR: process.env.NEXT_PUBLIC_BRAND_BACKGROUND_COLOR, NEXT_PUBLIC_SSO_ENABLED: process.env.NEXT_PUBLIC_SSO_ENABLED, - NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED: process.env.NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED, NEXT_PUBLIC_ACCESS_CONTROL_ENABLED: process.env.NEXT_PUBLIC_ACCESS_CONTROL_ENABLED, NEXT_PUBLIC_WHITELABELING_ENABLED: process.env.NEXT_PUBLIC_WHITELABELING_ENABLED, NEXT_PUBLIC_AUDIT_LOGS_ENABLED: process.env.NEXT_PUBLIC_AUDIT_LOGS_ENABLED, diff --git a/apps/sim/lib/core/security/input-validation.test.ts b/apps/sim/lib/core/security/input-validation.test.ts index e1fbf1f7171..b4c87e09374 100644 --- a/apps/sim/lib/core/security/input-validation.test.ts +++ b/apps/sim/lib/core/security/input-validation.test.ts @@ -2109,7 +2109,7 @@ describe('validateCallbackUrl', () => { ['/invite/abc-123'], ['/invite/abc?foo=bar&baz=qux'], ['/workspace#section'], - ['/credential-account/456'], + ['/workspace/456'], ['?reset=true'], ['/'], ['https://sim.app/workspace'], diff --git a/apps/sim/lib/core/telemetry.ts b/apps/sim/lib/core/telemetry.ts index 1b67c865edb..125592d0ad7 100644 --- a/apps/sim/lib/core/telemetry.ts +++ b/apps/sim/lib/core/telemetry.ts @@ -789,17 +789,6 @@ export const PlatformEvents = { }) }, - /** - * Track credential set created - */ - credentialSetCreated: (attrs: { credentialSetId: string; userId: string; name: string }) => { - trackPlatformEvent('platform.credential_set.created', { - 'credential_set.id': attrs.credentialSetId, - 'credential_set.name': attrs.name, - 'user.id': attrs.userId, - }) - }, - /** * Track webhook created */ diff --git a/apps/sim/lib/credential-sets/providers.ts b/apps/sim/lib/credential-sets/providers.ts deleted file mode 100644 index 4de42ffb783..00000000000 --- a/apps/sim/lib/credential-sets/providers.ts +++ /dev/null @@ -1,27 +0,0 @@ -export type PollingProvider = 'google-email' | 'outlook' - -export const POLLING_PROVIDERS: Record = { - 'google-email': { displayName: 'Gmail' }, - outlook: { displayName: 'Outlook' }, -} - -export function getProviderDisplayName(providerId: string): string { - if (providerId === 'google-email') return 'Gmail' - if (providerId === 'outlook') return 'Outlook' - return providerId -} - -export function isPollingProvider(provider: string): provider is PollingProvider { - return provider === 'google-email' || provider === 'outlook' -} - -/** - * Maps an OAuth provider ID to its corresponding polling provider ID. - * Since credential sets now store the OAuth provider ID directly, this is primarily - * used in the credential selector to match OAuth providers to credential sets. - */ -export function getPollingProviderFromOAuth(oauthProviderId: string): PollingProvider | null { - if (oauthProviderId === 'google-email') return 'google-email' - if (oauthProviderId === 'outlook') return 'outlook' - return null -} diff --git a/apps/sim/lib/webhooks/deploy.test.ts b/apps/sim/lib/webhooks/deploy.test.ts index ca7e1949af7..f5c111ed68a 100644 --- a/apps/sim/lib/webhooks/deploy.test.ts +++ b/apps/sim/lib/webhooks/deploy.test.ts @@ -9,7 +9,6 @@ import type { BlockState } from '@/stores/workflows/workflow/types' // buildProviderConfig (a pure function), so stub them to keep this unit test fast and isolated. vi.mock('@/blocks', () => ({ getBlock: vi.fn() })) vi.mock('@/triggers', () => ({ getTrigger: vi.fn(), isTriggerValid: vi.fn(() => true) })) -vi.mock('@/lib/oauth', () => ({ getProviderIdFromServiceId: vi.fn() })) vi.mock('@/lib/webhooks/providers', () => ({ getProviderHandler: vi.fn() })) vi.mock('@/lib/webhooks/provider-subscriptions', () => ({ cleanupExternalWebhook: vi.fn(), @@ -18,7 +17,6 @@ vi.mock('@/lib/webhooks/provider-subscriptions', () => ({ })) vi.mock('@/lib/webhooks/utils.server', () => ({ findConflictingWebhookPathOwner: vi.fn(), - syncWebhooksForCredentialSet: vi.fn(), })) vi.mock('@/lib/webhooks/pending-verification', () => ({ PendingWebhookVerificationTracker: vi.fn(), diff --git a/apps/sim/lib/webhooks/deploy.ts b/apps/sim/lib/webhooks/deploy.ts index 0fcb668f8e1..93e21eb6b87 100644 --- a/apps/sim/lib/webhooks/deploy.ts +++ b/apps/sim/lib/webhooks/deploy.ts @@ -1,11 +1,9 @@ import { db } from '@sim/db' -import { account, credentialSetMember, webhook, workflowDeploymentVersion } from '@sim/db/schema' +import { webhook, workflowDeploymentVersion } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateShortId } from '@sim/utils/id' -import { and, eq, inArray, isNotNull, isNull, or } from 'drizzle-orm' +import { and, eq, inArray, isNull } from 'drizzle-orm' import type { NextRequest } from 'next/server' -import type { DbOrTx } from '@/lib/db/types' -import { getProviderIdFromServiceId } from '@/lib/oauth' import { PendingWebhookVerificationTracker } from '@/lib/webhooks/pending-verification' import { cleanupExternalWebhook, @@ -13,10 +11,7 @@ import { shouldRecreateExternalWebhookSubscription, } from '@/lib/webhooks/provider-subscriptions' import { getProviderHandler } from '@/lib/webhooks/providers' -import { - findConflictingWebhookPathOwner, - syncWebhooksForCredentialSet, -} from '@/lib/webhooks/utils.server' +import { findConflictingWebhookPathOwner } from '@/lib/webhooks/utils.server' import { buildCanonicalIndex, buildSubBlockValues, @@ -30,7 +25,6 @@ import { getTrigger, isTriggerValid } from '@/triggers' import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants' const logger = createLogger('DeployWebhookSync') -const CREDENTIAL_SET_PREFIX = 'credentialSet:' interface TriggerSaveError { message: string @@ -39,12 +33,10 @@ interface TriggerSaveError { interface TriggerSaveResult { success: boolean error?: TriggerSaveError - warnings?: string[] } export async function validateTriggerWebhookConfigForDeploy( - blocks: Record, - executor: DbOrTx = db + blocks: Record ): Promise { const triggerBlocks = Object.values(blocks || {}).filter((b) => b && b.enabled !== false) @@ -53,7 +45,6 @@ export async function validateTriggerWebhookConfigForDeploy( if (!triggerId || !isTriggerValid(triggerId)) continue const triggerDef = getTrigger(triggerId) - const provider = triggerDef.provider const { providerConfig, missingFields } = buildProviderConfig(block, triggerId, triggerDef) if (missingFields.length > 0) { @@ -76,69 +67,11 @@ export async function validateTriggerWebhookConfigForDeploy( }, } } - - if (providerConfig.credentialSetId) { - const oauthProviderId = getProviderIdFromServiceId(provider) - const hasCredential = await credentialSetHasProviderCredential( - providerConfig.credentialSetId as string, - oauthProviderId, - executor - ) - if (!hasCredential) { - return { - success: false, - error: { - message: `No valid credentials found in credential set for ${provider}. Please connect accounts and try again.`, - status: 400, - }, - } - } - } } return { success: true } } -async function credentialSetHasProviderCredential( - credentialSetId: string, - providerId: string, - executor: DbOrTx -): Promise { - const members = await executor - .select({ userId: credentialSetMember.userId }) - .from(credentialSetMember) - .where( - and( - eq(credentialSetMember.credentialSetId, credentialSetId), - eq(credentialSetMember.status, 'active') - ) - ) - - if (members.length === 0) return false - - const [credential] = await executor - .select({ id: account.id }) - .from(account) - .where( - and( - inArray( - account.userId, - members.map((member) => member.userId) - ), - eq(account.providerId, providerId), - or(isNotNull(account.accessToken), isNotNull(account.refreshToken)) - ) - ) - .limit(1) - - return Boolean(credential) -} - -interface CredentialSetSyncResult { - error: TriggerSaveError | null - warnings: string[] -} - interface SaveTriggerWebhooksInput { request: NextRequest workflowId: string @@ -269,7 +202,6 @@ export function buildProviderConfig( providerConfig: Record missingFields: string[] credentialId?: string - credentialSetId?: string triggerPath: string } { const triggerConfigValue = getSubBlockValue(block, 'triggerConfig') @@ -351,15 +283,9 @@ export function buildProviderConfig( } let credentialId: string | undefined - let credentialSetId: string | undefined if (typeof triggerCredentials === 'string' && triggerCredentials.length > 0) { - if (triggerCredentials.startsWith(CREDENTIAL_SET_PREFIX)) { - credentialSetId = triggerCredentials.slice(CREDENTIAL_SET_PREFIX.length) - providerConfig.credentialSetId = credentialSetId - } else { - credentialId = triggerCredentials - providerConfig.credentialId = credentialId - } + credentialId = triggerCredentials + providerConfig.credentialId = credentialId } providerConfig.triggerId = triggerId @@ -370,7 +296,7 @@ export function buildProviderConfig( ? triggerPathValue : block.id - return { providerConfig, missingFields, credentialId, credentialSetId, triggerPath } + return { providerConfig, missingFields, credentialId, triggerPath } } async function configurePollingIfNeeded( @@ -395,91 +321,6 @@ async function configurePollingIfNeeded( return null } -async function syncCredentialSetWebhooks(params: { - workflowId: string - blockId: string - provider: string - triggerPath: string - providerConfig: Record - requestId: string - deploymentVersionId?: string -}): Promise { - const { - workflowId, - blockId, - provider, - triggerPath, - providerConfig, - requestId, - deploymentVersionId, - } = params - - const credentialSetId = providerConfig.credentialSetId as string | undefined - if (!credentialSetId) { - return { error: null, warnings: [] } - } - - const oauthProviderId = getProviderIdFromServiceId(provider) - - const { credentialId: _cId, credentialSetId: _csId, userId: _uId, ...baseConfig } = providerConfig - - const syncResult = await syncWebhooksForCredentialSet({ - workflowId, - blockId, - provider, - basePath: triggerPath, - credentialSetId, - oauthProviderId, - providerConfig: baseConfig as Record, - requestId, - deploymentVersionId, - }) - - const warnings: string[] = [] - - if (syncResult.failed.length > 0) { - const failedCount = syncResult.failed.length - const totalCount = syncResult.webhooks.length + failedCount - warnings.push( - `${failedCount} of ${totalCount} credentials in the set failed to sync for ${provider}. Some team members may not receive triggers.` - ) - } - - if (syncResult.webhooks.length === 0) { - return { - error: { - message: `No valid credentials found in credential set for ${provider}. Please connect accounts and try again.`, - status: 400, - }, - warnings, - } - } - - const handler = getProviderHandler(provider) - if (handler.configurePolling) { - for (const wh of syncResult.webhooks) { - if (wh.isNew) { - const rows = await db.select().from(webhook).where(eq(webhook.id, wh.id)).limit(1) - if (rows.length > 0) { - const success = await handler.configurePolling({ webhook: rows[0], requestId }) - if (!success) { - await db.delete(webhook).where(eq(webhook.id, wh.id)) - return { - error: { - message: `Failed to configure ${provider} polling. Please check account permissions.`, - status: 500, - }, - warnings, - } - } - } - } - } - } - - return { error: null, warnings } -} - /** * Saves trigger webhook configurations as part of workflow deployment. * Uses delete + create approach for changed/deleted webhooks. @@ -534,13 +375,11 @@ export async function saveTriggerWebhooksForDeploy({ provider: string providerConfig: Record triggerPath: string - triggerDef: ReturnType } const webhookConfigs = new Map() const webhooksToDelete: typeof existingWebhooks = [] const blocksNeedingWebhook: BlockState[] = [] - const blocksNeedingCredentialSetSync: BlockState[] = [] for (const block of triggerBlocks) { const triggerId = resolveTriggerId(block) @@ -592,12 +431,7 @@ export async function saveTriggerWebhooksForDeploy({ } } - webhookConfigs.set(block.id, { provider, providerConfig, triggerPath, triggerDef }) - - if (providerConfig.credentialSetId) { - blocksNeedingCredentialSetSync.push(block) - continue - } + webhookConfigs.set(block.id, { provider, providerConfig, triggerPath }) const existingForBlock = webhooksByBlockId.get(block.id) ?? [] if (existingForBlock.length === 0) { @@ -669,45 +503,6 @@ export async function saveTriggerWebhooksForDeploy({ } } - const collectedWarnings: string[] = [] - - for (const block of blocksNeedingCredentialSetSync) { - const config = webhookConfigs.get(block.id) - if (!config) continue - - const { provider, providerConfig, triggerPath } = config - - try { - const syncResult = await syncCredentialSetWebhooks({ - workflowId, - blockId: block.id, - provider, - triggerPath, - providerConfig, - requestId, - deploymentVersionId, - }) - - if (syncResult.warnings.length > 0) { - collectedWarnings.push(...syncResult.warnings) - } - - if (syncResult.error) { - return { success: false, error: syncResult.error, warnings: collectedWarnings } - } - } catch (error: unknown) { - logger.error(`[${requestId}] Failed to create webhook for ${block.id}`, error) - return { - success: false, - error: { - message: (error as Error)?.message || 'Failed to save trigger configuration', - status: 500, - }, - warnings: collectedWarnings, - } - } - } - // 5. Create webhooks for blocks that need them (two-phase approach for atomicity) const createdSubscriptions: Array<{ webhookId: string @@ -815,8 +610,6 @@ export async function saveTriggerWebhooksForDeploy({ path: sub.triggerPath, provider: sub.provider, providerConfig: sub.updatedProviderConfig, - credentialSetId: - (sub.updatedProviderConfig.credentialSetId as string | undefined) || null, isActive: true, createdAt: new Date(), updatedAt: new Date(), @@ -915,7 +708,7 @@ export async function saveTriggerWebhooksForDeploy({ } } - return { success: true, warnings: collectedWarnings.length > 0 ? collectedWarnings : undefined } + return { success: true } } async function persistCreatedWebhookRecordAfterCleanupFailure({ @@ -944,7 +737,6 @@ async function persistCreatedWebhookRecordAfterCleanupFailure({ path: sub.triggerPath, provider: sub.provider, providerConfig: sub.updatedProviderConfig, - credentialSetId: (sub.updatedProviderConfig.credentialSetId as string | undefined) || null, isActive: true, createdAt: new Date(), updatedAt: new Date(), diff --git a/apps/sim/lib/webhooks/polling/gmail.ts b/apps/sim/lib/webhooks/polling/gmail.ts index cc85dc8c930..b2deb341b52 100644 --- a/apps/sim/lib/webhooks/polling/gmail.ts +++ b/apps/sim/lib/webhooks/polling/gmail.ts @@ -68,12 +68,7 @@ export const gmailPollingHandler: PollingProviderHandler = { const webhookId = webhookData.id try { - const accessToken = await resolveOAuthCredential( - webhookData, - 'google-email', - requestId, - logger - ) + const accessToken = await resolveOAuthCredential(webhookData, 'google-email', requestId) const config = getProviderConfig(webhookData.providerConfig) const now = new Date() diff --git a/apps/sim/lib/webhooks/polling/google-calendar.ts b/apps/sim/lib/webhooks/polling/google-calendar.ts index 22ebfb0de0b..94a3c120e8c 100644 --- a/apps/sim/lib/webhooks/polling/google-calendar.ts +++ b/apps/sim/lib/webhooks/polling/google-calendar.ts @@ -99,12 +99,7 @@ export const googleCalendarPollingHandler: PollingProviderHandler = { const webhookId = webhookData.id try { - const accessToken = await resolveOAuthCredential( - webhookData, - 'google-calendar', - requestId, - logger - ) + const accessToken = await resolveOAuthCredential(webhookData, 'google-calendar', requestId) const config = getProviderConfig(webhookData.providerConfig) // Canonical key `calendarId` first; `manualCalendarId` is a transitional basic-first diff --git a/apps/sim/lib/webhooks/polling/google-drive.ts b/apps/sim/lib/webhooks/polling/google-drive.ts index 57a7ceba0f8..aaf321c14e8 100644 --- a/apps/sim/lib/webhooks/polling/google-drive.ts +++ b/apps/sim/lib/webhooks/polling/google-drive.ts @@ -87,12 +87,7 @@ export const googleDrivePollingHandler: PollingProviderHandler = { const webhookId = webhookData.id try { - const accessToken = await resolveOAuthCredential( - webhookData, - 'google-drive', - requestId, - logger - ) + const accessToken = await resolveOAuthCredential(webhookData, 'google-drive', requestId) const config = getProviderConfig(webhookData.providerConfig) diff --git a/apps/sim/lib/webhooks/polling/google-sheets.ts b/apps/sim/lib/webhooks/polling/google-sheets.ts index 1239993ffb6..72147225469 100644 --- a/apps/sim/lib/webhooks/polling/google-sheets.ts +++ b/apps/sim/lib/webhooks/polling/google-sheets.ts @@ -56,12 +56,7 @@ export const googleSheetsPollingHandler: PollingProviderHandler = { const webhookId = webhookData.id try { - const accessToken = await resolveOAuthCredential( - webhookData, - 'google-sheets', - requestId, - logger - ) + const accessToken = await resolveOAuthCredential(webhookData, 'google-sheets', requestId) const config = getProviderConfig(webhookData.providerConfig) // Canonical keys (`spreadsheetId`/`sheetName`) first; the `manual*` keys are a transitional diff --git a/apps/sim/lib/webhooks/polling/hubspot.ts b/apps/sim/lib/webhooks/polling/hubspot.ts index a714d269264..65c64ecffa7 100644 --- a/apps/sim/lib/webhooks/polling/hubspot.ts +++ b/apps/sim/lib/webhooks/polling/hubspot.ts @@ -190,7 +190,7 @@ export const hubspotPollingHandler: PollingProviderHandler = { const webhookId = webhookData.id try { - const accessToken = await resolveOAuthCredential(webhookData, 'hubspot', requestId, logger) + const accessToken = await resolveOAuthCredential(webhookData, 'hubspot', requestId) const config = getProviderConfig(webhookData.providerConfig) if (config.objectType === 'list_membership') { diff --git a/apps/sim/lib/webhooks/polling/outlook.ts b/apps/sim/lib/webhooks/polling/outlook.ts index 660c81f4972..4cedf69a4dd 100644 --- a/apps/sim/lib/webhooks/polling/outlook.ts +++ b/apps/sim/lib/webhooks/polling/outlook.ts @@ -117,7 +117,7 @@ export const outlookPollingHandler: PollingProviderHandler = { try { logger.info(`[${requestId}] Processing Outlook webhook: ${webhookId}`) - const accessToken = await resolveOAuthCredential(webhookData, 'outlook', requestId, logger) + const accessToken = await resolveOAuthCredential(webhookData, 'outlook', requestId) const config = getProviderConfig(webhookData.providerConfig) const now = new Date() diff --git a/apps/sim/lib/webhooks/polling/utils.test.ts b/apps/sim/lib/webhooks/polling/utils.test.ts index d8f4472c32a..137c70b3667 100644 --- a/apps/sim/lib/webhooks/polling/utils.test.ts +++ b/apps/sim/lib/webhooks/polling/utils.test.ts @@ -3,14 +3,25 @@ */ import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockUpdate, mockSet, mockWhere, sqlCalls } = vi.hoisted(() => ({ - mockUpdate: vi.fn(), - mockSet: vi.fn(), - mockWhere: vi.fn(), - sqlCalls: [] as Array<{ strings: readonly string[]; values: unknown[] }>, -})) +const { mockUpdate, mockSet, mockWhere, mockSelect, mockSelectRows, sqlCalls } = vi.hoisted(() => { + const mockSelectRows = vi.fn() + return { + mockUpdate: vi.fn(), + mockSet: vi.fn(), + mockWhere: vi.fn(), + mockSelectRows, + mockSelect: vi.fn(() => ({ + from: vi.fn(() => ({ + where: vi.fn(() => ({ + limit: mockSelectRows, + })), + })), + })), + sqlCalls: [] as Array<{ strings: readonly string[]; values: unknown[] }>, + } +}) -vi.mock('@sim/db', () => ({ db: { update: mockUpdate } })) +vi.mock('@sim/db', () => ({ db: { update: mockUpdate, select: mockSelect } })) vi.mock('@sim/db/schema', () => ({ webhook: { id: 'webhook.id', @@ -18,7 +29,6 @@ vi.mock('@sim/db/schema', () => ({ updatedAt: 'webhook.updatedAt', }, account: {}, - credentialSet: {}, workflow: {}, workflowDeploymentVersion: {}, })) @@ -34,7 +44,6 @@ vi.mock('drizzle-orm', () => ({ ne: vi.fn(), or: vi.fn(), })) -vi.mock('@/lib/billing', () => ({ isOrganizationOnTeamOrEnterprisePlan: vi.fn() })) vi.mock('@/app/api/auth/oauth/utils', () => ({ getOAuthToken: vi.fn(), refreshAccessTokenIfNeeded: vi.fn(), @@ -42,7 +51,13 @@ vi.mock('@/app/api/auth/oauth/utils', () => ({ })) vi.mock('@/triggers/constants', () => ({ MAX_CONSECUTIVE_FAILURES: 5 })) -import { updateWebhookProviderConfig } from '@/lib/webhooks/polling/utils' +import type { WebhookRecord } from '@/lib/webhooks/polling/types' +import { resolveOAuthCredential, updateWebhookProviderConfig } from '@/lib/webhooks/polling/utils' +import { + getOAuthToken, + refreshAccessTokenIfNeeded, + resolveOAuthAccountId, +} from '@/app/api/auth/oauth/utils' const logger = { error: vi.fn() } as never @@ -92,3 +107,79 @@ describe('updateWebhookProviderConfig (atomic jsonb merge)', () => { expect(sqlText).toContain(')::json') }) }) + +describe('resolveOAuthCredential (single-credential polling)', () => { + const makeWebhook = (providerConfig: Record): WebhookRecord => + ({ id: 'wh-1', providerConfig }) as unknown as WebhookRecord + + beforeEach(() => { + vi.clearAllMocks() + mockSelectRows.mockResolvedValue([]) + }) + + it('resolves via credentialId: account lookup then token refresh', async () => { + vi.mocked(resolveOAuthAccountId).mockResolvedValue({ + accountId: 'acc-1', + } as Awaited>) + mockSelectRows.mockResolvedValue([{ userId: 'owner-1' }]) + vi.mocked(refreshAccessTokenIfNeeded).mockResolvedValue('tok-abc') + + const token = await resolveOAuthCredential( + makeWebhook({ credentialId: 'cred-1' }), + 'google-email', + 'req-1' + ) + + expect(token).toBe('tok-abc') + expect(resolveOAuthAccountId).toHaveBeenCalledWith('cred-1') + expect(refreshAccessTokenIfNeeded).toHaveBeenCalledWith('acc-1', 'owner-1', 'req-1') + expect(getOAuthToken).not.toHaveBeenCalled() + }) + + it('throws when the credential cannot be resolved to an OAuth account', async () => { + vi.mocked(resolveOAuthAccountId).mockResolvedValue(null) + + await expect( + resolveOAuthCredential(makeWebhook({ credentialId: 'cred-gone' }), 'google-email', 'req-1') + ).rejects.toThrow('Failed to resolve OAuth account for credential cred-gone') + }) + + it('throws when the resolved account row does not exist', async () => { + vi.mocked(resolveOAuthAccountId).mockResolvedValue({ + accountId: 'acc-missing', + } as Awaited>) + mockSelectRows.mockResolvedValue([]) + + await expect( + resolveOAuthCredential(makeWebhook({ credentialId: 'cred-1' }), 'google-email', 'req-1') + ).rejects.toThrow('Credential cred-1 not found for webhook wh-1') + }) + + it('falls back to the legacy userId path via getOAuthToken', async () => { + vi.mocked(getOAuthToken).mockResolvedValue('tok-legacy') + + const token = await resolveOAuthCredential( + makeWebhook({ userId: 'user-1' }), + 'outlook', + 'req-1' + ) + + expect(token).toBe('tok-legacy') + expect(getOAuthToken).toHaveBeenCalledWith('user-1', 'outlook') + expect(resolveOAuthAccountId).not.toHaveBeenCalled() + }) + + it('throws when neither credentialId nor userId is present', async () => { + await expect(resolveOAuthCredential(makeWebhook({}), 'gmail', 'req-1')).rejects.toThrow( + 'Missing credential info for webhook wh-1' + ) + }) + + it('throws when the legacy userId path yields no token', async () => { + vi.mocked(getOAuthToken).mockResolvedValue(null) + + await expect( + resolveOAuthCredential(makeWebhook({ userId: 'user-1' }), 'outlook', 'req-1') + ).rejects.toThrow('Failed to get outlook access token for webhook wh-1') + }) +}) diff --git a/apps/sim/lib/webhooks/polling/utils.ts b/apps/sim/lib/webhooks/polling/utils.ts index 400b36f686d..91f65015b69 100644 --- a/apps/sim/lib/webhooks/polling/utils.ts +++ b/apps/sim/lib/webhooks/polling/utils.ts @@ -1,14 +1,7 @@ import { db } from '@sim/db' -import { - account, - credentialSet, - webhook, - workflow, - workflowDeploymentVersion, -} from '@sim/db/schema' +import { account, webhook, workflow, workflowDeploymentVersion } from '@sim/db/schema' import type { Logger } from '@sim/logger' import { and, eq, isNull, ne, or, sql } from 'drizzle-orm' -import { isOrganizationOnTeamOrEnterprisePlan } from '@/lib/billing' import type { WebhookRecord, WorkflowRecord } from '@/lib/webhooks/polling/types' import { getOAuthToken, @@ -191,41 +184,16 @@ export async function updateWebhookProviderConfig( export async function resolveOAuthCredential( webhookData: WebhookRecord, oauthProvider: string, - requestId: string, - logger: Logger + requestId: string ): Promise { const metadata = webhookData.providerConfig as Record | null const credentialId = metadata?.credentialId as string | undefined const userId = metadata?.userId as string | undefined - const credentialSetId = (webhookData.credentialSetId as string | undefined) ?? undefined if (!credentialId && !userId) { throw new Error(`Missing credential info for webhook ${webhookData.id}`) } - if (credentialSetId) { - const [cs] = await db - .select({ organizationId: credentialSet.organizationId }) - .from(credentialSet) - .where(eq(credentialSet.id, credentialSetId)) - .limit(1) - - if (cs?.organizationId) { - const hasAccess = await isOrganizationOnTeamOrEnterprisePlan(cs.organizationId) - if (!hasAccess) { - logger.error( - `[${requestId}] Polling Group plan restriction: Your current plan does not support Polling Groups. Upgrade to Team or Enterprise to use this feature.`, - { - webhookId: webhookData.id, - credentialSetId, - organizationId: cs.organizationId, - } - ) - throw new Error('Polling Group plan restriction') - } - } - } - let accessToken: string | null = null if (credentialId) { diff --git a/apps/sim/lib/webhooks/processor.test.ts b/apps/sim/lib/webhooks/processor.test.ts index 60f26d86c33..1e823ed8af5 100644 --- a/apps/sim/lib/webhooks/processor.test.ts +++ b/apps/sim/lib/webhooks/processor.test.ts @@ -139,7 +139,7 @@ describe('findAllWebhooksForPath cross-tenant collision', () => { workflow: { id: workflowId }, }) - it('returns all rows when they belong to a single workflow (credential set fan-out)', async () => { + it('returns all rows when they belong to a single workflow', async () => { mockWebhookLookupResult.rows = [ makeRow('workflow-1', 'wh-a', new Date('2026-01-01')), makeRow('workflow-1', 'wh-b', new Date('2026-01-02')), @@ -163,7 +163,7 @@ describe('findAllWebhooksForPath cross-tenant collision', () => { expect(results[0].webhook.workflowId).toBe('victim-workflow') }) - it("preserves the owner's full credential-set fan-out while dropping a foreign row", async () => { + it("preserves the owner's full multi-webhook match while dropping a foreign row", async () => { const victimA = makeRow('victim-workflow', 'victim-wh-a', new Date('2026-01-01')) const victimB = makeRow('victim-workflow', 'victim-wh-b', new Date('2026-01-03')) const attacker = makeRow('attacker-workflow', 'attacker-wh', new Date('2026-05-01')) diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index dd6629215b3..67ca4ac030a 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -1,15 +1,12 @@ import { db, webhook, workflow, workflowDeploymentVersion } from '@sim/db' -import { credentialSet } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' import { and, eq, isNull, or } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' -import { isOrganizationOnTeamOrEnterprisePlan } from '@/lib/billing/core/subscription' import { tryAdmit } from '@/lib/core/admission/gate' import { getInlineJobQueue, getJobQueue, shouldExecuteInline } from '@/lib/core/async-jobs' import type { AsyncExecutionCorrelation } from '@/lib/core/async-jobs/types' -import { isProd } from '@/lib/core/config/env-flags' import { assertContentLengthWithinLimit, isPayloadSizeLimitError, @@ -52,35 +49,6 @@ export interface WebhookPreprocessingResult { correlation?: AsyncExecutionCorrelation } -async function verifyCredentialSetBilling(credentialSetId: string): Promise<{ - valid: boolean - error?: string -}> { - if (!isProd) { - return { valid: true } - } - - const [set] = await db - .select({ organizationId: credentialSet.organizationId }) - .from(credentialSet) - .where(eq(credentialSet.id, credentialSetId)) - .limit(1) - - if (!set) { - return { valid: false, error: 'Credential set not found' } - } - - const hasTeamPlan = await isOrganizationOnTeamOrEnterprisePlan(set.organizationId) - if (!hasTeamPlan) { - return { - valid: false, - error: 'Credential sets require a Team or Enterprise plan. Please upgrade to continue.', - } - } - - return { valid: true } -} - const WEBHOOK_BODY_LABEL = 'Webhook request body' export async function parseWebhookBody( @@ -332,7 +300,7 @@ async function findWebhookAndWorkflow( /** * Finds all webhooks matching a path, scoped to a single workflow. * - * Legitimate fan-out (credential sets) is always within one workflow, but paths + * Legitimate multi-webhook matches are always within one workflow, but paths * are user-controlled and only unique per deployment version, so two tenants can * register the same path. On collision we keep only the workflow that registered * the path first, so one tenant can never receive another's webhook deliveries. @@ -398,9 +366,7 @@ export async function findAllWebhooksForPath( } if (results.length > 1) { - logger.info( - `[${options.requestId}] Found ${results.length} webhooks for path: ${options.path} (credential set fan-out)` - ) + logger.info(`[${options.requestId}] Found ${results.length} webhooks for path: ${options.path}`) } return results @@ -587,17 +553,6 @@ export async function queueWebhookExecution( } const credentialId = providerConfig.credentialId as string | undefined - const credentialSetId = foundWebhook.credentialSetId as string | undefined - - if (credentialSetId) { - const billingCheck = await verifyCredentialSetBilling(credentialSetId) - if (!billingCheck.valid) { - logger.warn( - `[${options.requestId}] Credential set billing check failed: ${billingCheck.error}` - ) - return NextResponse.json({ error: billingCheck.error }, { status: 403 }) - } - } const actorUserId = options.actorUserId if (!actorUserId) { @@ -780,15 +735,6 @@ export async function processPolledWebhookEvent( const providerConfig = (foundWebhook.providerConfig as Record) || {} const credentialId = providerConfig.credentialId as string | undefined - const credentialSetId = foundWebhook.credentialSetId as string | undefined - - if (credentialSetId) { - const billingCheck = await verifyCredentialSetBilling(credentialSetId) - if (!billingCheck.valid) { - logger.warn(`[${requestId}] Credential set billing check failed: ${billingCheck.error}`) - return { success: false, error: billingCheck.error, statusCode: 403 } - } - } const actorUserId = preprocessResult.actorUserId if (!actorUserId) { diff --git a/apps/sim/lib/webhooks/utils.server.ts b/apps/sim/lib/webhooks/utils.server.ts index 8a34efc72a7..0bff56de94e 100644 --- a/apps/sim/lib/webhooks/utils.server.ts +++ b/apps/sim/lib/webhooks/utils.server.ts @@ -1,14 +1,7 @@ -import { db, workflowDeploymentVersion } from '@sim/db' +import { db } from '@sim/db' import { webhook, workflow } from '@sim/db/schema' -import { createLogger } from '@sim/logger' -import { getErrorMessage } from '@sim/utils/errors' -import { generateShortId } from '@sim/utils/id' -import { and, eq, isNull, or } from 'drizzle-orm' +import { and, eq, isNull } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' -import { getProviderIdFromServiceId } from '@/lib/oauth' -import { cleanupExternalWebhook } from '@/lib/webhooks/provider-subscriptions' -import { getCredentialsForCredentialSet } from '@/app/api/auth/oauth/utils' -import { isPollingWebhookProvider } from '@/triggers/constants' /** * Returns the id of a different workflow that already owns an active webhook on @@ -46,363 +39,3 @@ export async function findConflictingWebhookPathOwner(params: { const conflict = existing.find((row) => row.workflowId !== workflowId) return conflict ? conflict.workflowId : null } - -/** - * Result of syncing webhooks for a credential set - */ -export interface CredentialSetWebhookSyncResult { - webhooks: Array<{ - id: string - credentialId: string - isNew: boolean - }> - created: number - updated: number - deleted: number - failed: Array<{ - credentialId: string - error: string - }> -} - -/** - * Sync webhooks for a credential set. - * - * For credential sets, we create one webhook per credential in the set. - * Each webhook has its own state and credentialId. - * - * Path strategy: - * - Polling triggers (gmail, outlook): unique paths per credential (for independent polling) - * - External triggers (slack, etc.): shared path (external service sends to one URL) - * - * This function: - * 1. Gets all credentials in the credential set - * 2. Gets existing webhooks for this workflow+block with this credentialSetId - * 3. Creates webhooks for new credentials - * 4. Updates config for existing webhooks (preserving state) - * 5. Deletes webhooks for credentials no longer in the set - * - * Must run OUTSIDE any open transaction: credential resolution can trigger an - * OAuth token refresh (external HTTP + its own committed writes) and webhook - * cleanup can call external provider APIs, neither of which may execute while - * a pooled connection is held. - */ -export async function syncWebhooksForCredentialSet(params: { - workflowId: string - blockId: string - provider: string - basePath: string - credentialSetId: string - oauthProviderId: string - providerConfig: Record - requestId: string - deploymentVersionId?: string -}): Promise { - const { - workflowId, - blockId, - provider, - basePath, - credentialSetId, - oauthProviderId, - providerConfig, - requestId, - deploymentVersionId, - } = params - - const syncLogger = createLogger('CredentialSetWebhookSync') - syncLogger.info( - `[${requestId}] Syncing webhooks for credential set ${credentialSetId}, provider ${provider}` - ) - - const useUniquePaths = isPollingWebhookProvider(provider) - - const credentials = await getCredentialsForCredentialSet(credentialSetId, oauthProviderId) - - if (credentials.length === 0) { - syncLogger.warn( - `[${requestId}] No credentials found in credential set ${credentialSetId} for provider ${oauthProviderId}` - ) - return { webhooks: [], created: 0, updated: 0, deleted: 0, failed: [] } - } - - syncLogger.info( - `[${requestId}] Found ${credentials.length} credentials in set ${credentialSetId}` - ) - - const existingWebhooks = await db - .select() - .from(webhook) - .where( - deploymentVersionId - ? and( - eq(webhook.workflowId, workflowId), - eq(webhook.blockId, blockId), - eq(webhook.deploymentVersionId, deploymentVersionId), - isNull(webhook.archivedAt) - ) - : and( - eq(webhook.workflowId, workflowId), - eq(webhook.blockId, blockId), - isNull(webhook.archivedAt) - ) - ) - - const credentialSetWebhooks = existingWebhooks.filter( - (wh) => wh.credentialSetId === credentialSetId - ) - - syncLogger.info( - `[${requestId}] Found ${credentialSetWebhooks.length} existing webhooks for credential set` - ) - - const existingByCredentialId = new Map() - for (const wh of credentialSetWebhooks) { - const config = wh.providerConfig as Record - if (config?.credentialId) { - existingByCredentialId.set(config.credentialId as string, wh) - } - } - - const credentialIdsInSet = new Set(credentials.map((c) => c.credentialId)) - const [workflowRecord] = await db - .select({ - id: workflow.id, - userId: workflow.userId, - workspaceId: workflow.workspaceId, - }) - .from(workflow) - .where(eq(workflow.id, workflowId)) - .limit(1) - - const result: CredentialSetWebhookSyncResult = { - webhooks: [], - created: 0, - updated: 0, - deleted: 0, - failed: [], - } - - for (const cred of credentials) { - try { - const existingWebhook = existingByCredentialId.get(cred.credentialId) - - if (existingWebhook) { - const existingConfig = existingWebhook.providerConfig as Record - - const updatedConfig = { - ...providerConfig, - basePath, - credentialId: cred.credentialId, - credentialSetId: credentialSetId, - historyId: existingConfig?.historyId, - lastCheckedTimestamp: existingConfig?.lastCheckedTimestamp, - setupCompleted: existingConfig?.setupCompleted, - externalId: existingConfig?.externalId, - userId: cred.userId, - } - - await db - .update(webhook) - .set({ - ...(deploymentVersionId ? { deploymentVersionId } : {}), - providerConfig: updatedConfig, - isActive: true, - updatedAt: new Date(), - }) - .where(eq(webhook.id, existingWebhook.id)) - - result.webhooks.push({ - id: existingWebhook.id, - credentialId: cred.credentialId, - isNew: false, - }) - result.updated++ - - syncLogger.debug( - `[${requestId}] Updated webhook ${existingWebhook.id} for credential ${cred.credentialId}` - ) - } else { - const webhookId = generateShortId() - const webhookPath = useUniquePaths - ? `${basePath}-${cred.credentialId.slice(0, 8)}` - : basePath - - const newConfig = { - ...providerConfig, - basePath, - credentialId: cred.credentialId, - credentialSetId: credentialSetId, - userId: cred.userId, - } - - await db.insert(webhook).values({ - id: webhookId, - workflowId, - blockId, - path: webhookPath, - provider, - providerConfig: newConfig, - credentialSetId, - isActive: true, - ...(deploymentVersionId ? { deploymentVersionId } : {}), - createdAt: new Date(), - updatedAt: new Date(), - }) - - result.webhooks.push({ - id: webhookId, - credentialId: cred.credentialId, - isNew: true, - }) - result.created++ - - syncLogger.debug( - `[${requestId}] Created webhook ${webhookId} for credential ${cred.credentialId}` - ) - } - } catch (error) { - const errorMessage = getErrorMessage(error, 'Unknown error') - syncLogger.error( - `[${requestId}] Failed to sync webhook for credential ${cred.credentialId}: ${errorMessage}` - ) - result.failed.push({ - credentialId: cred.credentialId, - error: errorMessage, - }) - } - } - - for (const [credentialId, existingWebhook] of existingByCredentialId) { - if (!credentialIdsInSet.has(credentialId)) { - try { - if (workflowRecord) { - await cleanupExternalWebhook(existingWebhook, workflowRecord, requestId) - } - await db.delete(webhook).where(eq(webhook.id, existingWebhook.id)) - result.deleted++ - - syncLogger.debug( - `[${requestId}] Deleted webhook ${existingWebhook.id} for removed credential ${credentialId}` - ) - } catch (error) { - const errorMessage = getErrorMessage(error, 'Unknown error') - syncLogger.error( - `[${requestId}] Failed to delete webhook ${existingWebhook.id} for credential ${credentialId}: ${errorMessage}` - ) - result.failed.push({ - credentialId, - error: `Failed to delete: ${errorMessage}`, - }) - } - } - } - - syncLogger.info( - `[${requestId}] Credential set webhook sync complete: ${result.created} created, ${result.updated} updated, ${result.deleted} deleted, ${result.failed.length} failed` - ) - - return result -} - -/** - * Sync all webhooks that use a specific credential set. - * Called when credential set membership changes (member added/removed). - * - * This finds all workflows with webhooks using this credential set and resyncs them. - * Must run OUTSIDE any open transaction — see {@link syncWebhooksForCredentialSet}. - */ -export async function syncAllWebhooksForCredentialSet( - credentialSetId: string, - requestId: string -): Promise<{ workflowsUpdated: number; totalCreated: number; totalDeleted: number }> { - const syncLogger = createLogger('CredentialSetMembershipSync') - syncLogger.info(`[${requestId}] Syncing all webhooks for credential set ${credentialSetId}`) - - const webhooksForSet = await db - .select({ webhook }) - .from(webhook) - .leftJoin( - workflowDeploymentVersion, - and( - eq(workflowDeploymentVersion.workflowId, webhook.workflowId), - eq(workflowDeploymentVersion.isActive, true) - ) - ) - .where( - and( - eq(webhook.credentialSetId, credentialSetId), - isNull(webhook.archivedAt), - or( - eq(webhook.deploymentVersionId, workflowDeploymentVersion.id), - and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId)) - ) - ) - ) - - if (webhooksForSet.length === 0) { - syncLogger.info(`[${requestId}] No webhooks found using credential set ${credentialSetId}`) - return { workflowsUpdated: 0, totalCreated: 0, totalDeleted: 0 } - } - - const triggerGroups = new Map() - for (const row of webhooksForSet) { - const wh = row.webhook - const key = `${wh.workflowId}:${wh.blockId}` - if (!triggerGroups.has(key)) { - triggerGroups.set(key, wh) - } - } - - syncLogger.info( - `[${requestId}] Found ${triggerGroups.size} triggers using credential set ${credentialSetId}` - ) - - let workflowsUpdated = 0 - let totalCreated = 0 - let totalDeleted = 0 - - for (const [key, representativeWebhook] of triggerGroups) { - if (!representativeWebhook.provider) { - syncLogger.warn(`[${requestId}] Skipping webhook without provider: ${key}`) - continue - } - - const config = representativeWebhook.providerConfig as Record - const oauthProviderId = getProviderIdFromServiceId(representativeWebhook.provider) - - const { credentialId: _cId, userId: _uId, basePath: _bp, ...baseConfig } = config - const basePath = - (config.basePath as string) || representativeWebhook.blockId || representativeWebhook.path - - try { - const syncResult = await syncWebhooksForCredentialSet({ - workflowId: representativeWebhook.workflowId, - blockId: representativeWebhook.blockId || '', - provider: representativeWebhook.provider, - basePath, - credentialSetId, - oauthProviderId, - providerConfig: baseConfig, - requestId, - deploymentVersionId: representativeWebhook.deploymentVersionId || undefined, - }) - - workflowsUpdated++ - totalCreated += syncResult.created - totalDeleted += syncResult.deleted - - syncLogger.debug( - `[${requestId}] Synced webhooks for ${key}: ${syncResult.created} created, ${syncResult.deleted} deleted` - ) - } catch (error) { - syncLogger.error(`[${requestId}] Error syncing webhooks for ${key}`, error) - } - } - - syncLogger.info( - `[${requestId}] Credential set membership sync complete: ${workflowsUpdated} workflows updated, ${totalCreated} webhooks created, ${totalDeleted} webhooks deleted` - ) - - return { workflowsUpdated, totalCreated, totalDeleted } -} diff --git a/apps/sim/lib/workflows/comparison/format-description.test.ts b/apps/sim/lib/workflows/comparison/format-description.test.ts index 22fb5b2fa05..c1051bafa10 100644 --- a/apps/sim/lib/workflows/comparison/format-description.test.ts +++ b/apps/sim/lib/workflows/comparison/format-description.test.ts @@ -21,7 +21,6 @@ vi.mock('@/blocks/types', () => ({ })) vi.mock('@/executor/constants', () => ({ - CREDENTIAL_SET: { PREFIX: 'cred_set_' }, isUuid: (v: string) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v), })) @@ -36,10 +35,6 @@ vi.mock('@/lib/workflows/subblocks/context', () => ({ buildSelectorContextFromBlock: vi.fn(() => ({})), })) -vi.mock('@/hooks/queries/utils/fetch-credential-set', () => ({ - fetchCredentialSetById: vi.fn(), -})) - vi.mock('@/hooks/queries/oauth/oauth-credentials', () => ({ fetchOAuthCredentialDetail: vi.fn(() => []), })) diff --git a/apps/sim/lib/workflows/comparison/resolve-values.ts b/apps/sim/lib/workflows/comparison/resolve-values.ts index ac866070a14..64cfc114cef 100644 --- a/apps/sim/lib/workflows/comparison/resolve-values.ts +++ b/apps/sim/lib/workflows/comparison/resolve-values.ts @@ -3,9 +3,8 @@ import { truncate } from '@sim/utils/string' import { buildSelectorContextFromBlock } from '@/lib/workflows/subblocks/context' import { getBlock } from '@/blocks/registry' import { SELECTOR_TYPES_HYDRATION_REQUIRED, type SubBlockConfig } from '@/blocks/types' -import { CREDENTIAL_SET, isUuid } from '@/executor/constants' +import { isUuid } from '@/executor/constants' import { fetchOAuthCredentialDetail } from '@/hooks/queries/oauth/oauth-credentials' -import { fetchCredentialSetById } from '@/hooks/queries/utils/fetch-credential-set' import { getSelectorDefinition, loadAllSelectorOptions } from '@/hooks/selectors/registry' import { resolveSelectorForSubBlock } from '@/hooks/selectors/resolution' import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types' @@ -50,12 +49,6 @@ function getSemanticFallback(subBlockConfig: SubBlockConfig): string { async function resolveCredential(credentialId: string, workflowId: string): Promise { try { - if (credentialId.startsWith(CREDENTIAL_SET.PREFIX)) { - const setId = credentialId.slice(CREDENTIAL_SET.PREFIX.length) - const credentialSet = await fetchCredentialSetById(setId) - return credentialSet?.name ?? null - } - const credentials = await fetchOAuthCredentialDetail(credentialId, workflowId) if (credentials.length > 0) { return credentials[0].name ?? null @@ -228,7 +221,7 @@ export async function resolveValueForDisplay( const isCredentialField = subBlockConfig.type === 'oauth-input' || context.subBlockId === 'credential' - if (isCredentialField && (value.startsWith(CREDENTIAL_SET.PREFIX) || isUuid(value))) { + if (isCredentialField && isUuid(value)) { const label = await resolveCredential(value, context.workflowId) if (label) { return { original: value, displayLabel: label, resolved: true } @@ -284,14 +277,6 @@ export async function resolveValueForDisplay( return { original: value, displayLabel: semanticFallback, resolved: true } } - if (value.startsWith(CREDENTIAL_SET.PREFIX)) { - const label = await resolveCredential(value, context.workflowId) - if (label) { - return { original: value, displayLabel: label, resolved: true } - } - return { original: value, displayLabel: semanticFallback, resolved: true } - } - return { original: value, displayLabel: formatValueForDisplay(value), diff --git a/apps/sim/lib/workflows/orchestration/deploy.ts b/apps/sim/lib/workflows/orchestration/deploy.ts index 26947600a0f..7f7617f3402 100644 --- a/apps/sim/lib/workflows/orchestration/deploy.ts +++ b/apps/sim/lib/workflows/orchestration/deploy.ts @@ -125,7 +125,7 @@ export async function performFullDeploy( workflowName: workflowName || workflowRecord.name || undefined, description: params.versionDescription, name: params.versionName, - validateWorkflowState: async (workflowState, executor) => { + validateWorkflowState: async (workflowState) => { const scheduleValidation = validateWorkflowSchedules(workflowState.blocks) if (!scheduleValidation.isValid) { return { @@ -134,10 +134,7 @@ export async function performFullDeploy( errorCode: 'validation', } } - const triggerValidation = await validateTriggerWebhookConfigForDeploy( - workflowState.blocks, - executor - ) + const triggerValidation = await validateTriggerWebhookConfigForDeploy(workflowState.blocks) if (!triggerValidation.success) { return { success: false, diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts index 683d9a34511..9b9927cb489 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts @@ -255,7 +255,7 @@ describe('collectForkDependentReconfigs', () => { expect(collectForkDependentReconfigs([replaceItem], states, resolve)).toEqual([]) }) - it('skips create-mode targets and credentialSet refs', () => { + it('skips create-mode targets', () => { vi.mocked(getBlock).mockReturnValue( blockWith([ { id: 'credential', title: 'Credential', type: 'oauth-input' }, @@ -281,17 +281,6 @@ describe('collectForkDependentReconfigs', () => { resolve ) ).toEqual([]) - - const orgSet = new Map([ - [ - 'wf-src', - sourceState('gmail', { - credential: { value: 'credentialSet:cs-1' }, - folder: { value: 'INBOX' }, - }), - ], - ]) - expect(collectForkDependentReconfigs([replaceItem], orgSet, resolve)).toEqual([]) }) it('walks the transitive chain and tags the context key a re-pick provides', () => { diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts index 9ffc55d9eb1..9b20a3dfaac 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts @@ -122,8 +122,7 @@ function emitAnchoredDependents(params: EmitAnchoredParams): void { ? resolveActiveCanonicalValue(group, values, canonicalModes) : values[anchorCfg.id] const parentSourceId = typeof rawValue === 'string' ? rawValue : '' - // Skip empty and org-scoped credential sets (those carry over unchanged). - if (!parentSourceId || parentSourceId.startsWith('credentialSet:')) continue + if (!parentSourceId) continue // Multi-value parents (comma-joined) can't match a single mapping entry; skip // (the field falls back to needs-config) rather than mis-bind to one of several. if (parentSourceId.includes(',')) continue diff --git a/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts b/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts index a2f05cea3f4..14410f2cf43 100644 --- a/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts +++ b/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts @@ -242,8 +242,8 @@ export function findDuplicateTargetEntry( const sourcesByTarget = new Map>() for (const entry of entries) { if (entry.targetId == null) continue - // Null-byte separator so a targetId containing ':' (e.g. credentialSet:...) can't - // be confused with a different (resourceType, targetId) pair. + // Null-byte separator so a targetId containing ':' can't be confused with a + // different (resourceType, targetId) pair. const key = `${entry.resourceType}\u0000${entry.targetId}` const sources = sourcesByTarget.get(key) if (!sources) { diff --git a/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts b/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts index d549381d74a..94ccc3845e0 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts +++ b/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts @@ -128,24 +128,6 @@ describe('remapToolBlockResources', () => { expect(result.params).toEqual({ manualCredential: 'mc-src', knowledgeBaseId: 'kb-dst' }) }) - it('preserves an org-scoped credentialSet ref without remapping or recording it', () => { - const tool = { - type: 'testblock', - toolId: 'testblock_run', - params: { credential: 'credentialSet:cs-1' }, - } - const recorded: Array<{ kind: string; id: string; mapped: boolean }> = [] - const result = remapToolBlockResources(tool, { - resolve: () => null, - resolveFileKey: () => null, - record: (kind, id, mapped) => recorded.push({ kind, id, mapped }), - clearUnresolved: true, - blockConfigs, - }) - expect((result.params as Record).credential).toBe('credentialSet:cs-1') - expect(recorded).toHaveLength(0) - }) - it('drops only the uncopied entry in a mixed multi-value field', () => { const tool = { type: 'testblock', @@ -276,33 +258,6 @@ describe('remapForkSubBlocks', () => { expect(result.unmapped.every((r) => r.kind !== 'knowledge-base')).toBe(true) }) - it('promote mode: preserves a credentialSet ref without flagging it', () => { - const sb: SubBlockRecord = { - triggerCredentials: { - id: 'triggerCredentials', - type: 'oauth-input', - value: 'credentialSet:cs-1', - }, - } - const result = remapForkSubBlocks(sb, () => null, 'promote') - expect(result.subBlocks.triggerCredentials.value).toBe('credentialSet:cs-1') - expect(result.references).toHaveLength(0) - expect(result.unmapped).toHaveLength(0) - }) - - it('create mode: keeps a credentialSet ref (org-scoped, not cleared)', () => { - const sb: SubBlockRecord = { - triggerCredentials: { - id: 'triggerCredentials', - type: 'oauth-input', - value: 'credentialSet:cs-1', - }, - } - const result = remapForkSubBlocks(sb, () => null, 'create') - expect(result.subBlocks.triggerCredentials.value).toBe('credentialSet:cs-1') - expect(result.references).toHaveLength(0) - }) - it('promote mode: rewrites {{ENV}} nested in an array-form tool param', () => { const sb: SubBlockRecord = { tools: { diff --git a/apps/sim/lib/workspaces/fork/remap/remap-references.ts b/apps/sim/lib/workspaces/fork/remap/remap-references.ts index fe645f8b9ad..679ccdedd0f 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-references.ts +++ b/apps/sim/lib/workspaces/fork/remap/remap-references.ts @@ -112,15 +112,6 @@ export function rewriteEnvRefsInText( }) } -/** - * A `credentialSet:` reference points at an ORG-scoped credential set. A fork - * inherits its parent's org, so the set is already valid in the target — these refs - * are preserved verbatim and never treated as a workspace credential to remap/flag. - */ -function isCredentialSetRef(value: string): boolean { - return value.startsWith('credentialSet:') -} - /** * Resolves a source-workspace resource id (or env key, for `env-var`) to its * mapped target id. Returns the target id (which may equal the source for env @@ -257,7 +248,6 @@ export function remapToolBlockResources( if (!overrideKind) continue const currentValue = params[paramId] if (typeof currentValue !== 'string' || !currentValue) continue - if (overrideKind === 'credential' && isCredentialSetRef(currentValue)) continue const target = opts.resolve(overrideKind, currentValue) opts.record?.(overrideKind, currentValue, target != null) if (target != null) { @@ -324,7 +314,6 @@ export function remapToolBlockResources( for (const ref of refs) { if (seen.has(ref.rawValue)) continue seen.add(ref.rawValue) - if (forkKind === 'credential' && isCredentialSetRef(ref.rawValue)) continue const target = opts.resolve(forkKind, ref.rawValue) const mapped = target != null opts.record?.(forkKind, ref.rawValue, mapped) @@ -565,7 +554,6 @@ export function remapForkSubBlocks( for (const ref of parsed) { if (seen.has(ref.rawValue)) continue seen.add(ref.rawValue) - if (forkKind === 'credential' && isCredentialSetRef(ref.rawValue)) continue const required = REQUIRED_KINDS.has(forkKind) const reference: ForkReference = { kind: forkKind, diff --git a/apps/sim/triggers/gmail/poller.ts b/apps/sim/triggers/gmail/poller.ts index 2caabc2e87e..6689f3fc9cb 100644 --- a/apps/sim/triggers/gmail/poller.ts +++ b/apps/sim/triggers/gmail/poller.ts @@ -2,29 +2,11 @@ import { createLogger } from '@sim/logger' import { GmailIcon } from '@/components/icons' import { requestJson } from '@/lib/api/client/request' import { gmailLabelsSelectorContract } from '@/lib/api/contracts/selectors/google' -import { isCredentialSetValue } from '@/executor/constants' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import type { TriggerConfig } from '@/triggers/types' const logger = createLogger('GmailPollingTrigger') -// Gmail system labels that exist for all accounts (used as defaults for credential sets) -const GMAIL_SYSTEM_LABELS = [ - { id: 'INBOX', label: 'Inbox' }, - { id: 'SENT', label: 'Sent' }, - { id: 'DRAFT', label: 'Drafts' }, - { id: 'SPAM', label: 'Spam' }, - { id: 'TRASH', label: 'Trash' }, - { id: 'STARRED', label: 'Starred' }, - { id: 'IMPORTANT', label: 'Important' }, - { id: 'UNREAD', label: 'Unread' }, - { id: 'CATEGORY_PERSONAL', label: 'Category: Personal' }, - { id: 'CATEGORY_SOCIAL', label: 'Category: Social' }, - { id: 'CATEGORY_PROMOTIONS', label: 'Category: Promotions' }, - { id: 'CATEGORY_UPDATES', label: 'Category: Updates' }, - { id: 'CATEGORY_FORUMS', label: 'Category: Forums' }, -] - export const gmailPollingTrigger: TriggerConfig = { id: 'gmail_poller', name: 'Gmail Email Trigger', @@ -44,7 +26,6 @@ export const gmailPollingTrigger: TriggerConfig = { requiredScopes: [], required: true, mode: 'trigger', - supportsCredentialSets: true, }, { id: 'labelIds', @@ -63,10 +44,6 @@ export const gmailPollingTrigger: TriggerConfig = { // Return a sentinel to prevent infinite retry loops when credential is missing throw new Error('No Gmail credential selected') } - // Return default system labels for credential sets (can't fetch user-specific labels for a pool) - if (isCredentialSetValue(credentialId)) { - return GMAIL_SYSTEM_LABELS - } try { const data = await requestJson(gmailLabelsSelectorContract, { query: { credentialId }, diff --git a/apps/sim/triggers/google-calendar/poller.ts b/apps/sim/triggers/google-calendar/poller.ts index 977cd2df4cb..4365cf37a2e 100644 --- a/apps/sim/triggers/google-calendar/poller.ts +++ b/apps/sim/triggers/google-calendar/poller.ts @@ -20,7 +20,6 @@ export const googleCalendarPollingTrigger: TriggerConfig = { requiredScopes: [], required: true, mode: 'trigger', - supportsCredentialSets: true, canonicalParamId: 'oauthCredential', }, { diff --git a/apps/sim/triggers/google-drive/poller.ts b/apps/sim/triggers/google-drive/poller.ts index 3f697a47b25..381c7c3bebc 100644 --- a/apps/sim/triggers/google-drive/poller.ts +++ b/apps/sim/triggers/google-drive/poller.ts @@ -30,7 +30,6 @@ export const googleDrivePollingTrigger: TriggerConfig = { requiredScopes: [], required: true, mode: 'trigger', - supportsCredentialSets: true, canonicalParamId: 'oauthCredential', }, { diff --git a/apps/sim/triggers/google-sheets/poller.ts b/apps/sim/triggers/google-sheets/poller.ts index ea5a51d1f51..4dabd7617e9 100644 --- a/apps/sim/triggers/google-sheets/poller.ts +++ b/apps/sim/triggers/google-sheets/poller.ts @@ -20,7 +20,6 @@ export const googleSheetsPollingTrigger: TriggerConfig = { requiredScopes: [], required: true, mode: 'trigger', - supportsCredentialSets: true, canonicalParamId: 'oauthCredential', }, { diff --git a/apps/sim/triggers/hubspot/poller.ts b/apps/sim/triggers/hubspot/poller.ts index 9f7015eb98b..75a58dbe569 100644 --- a/apps/sim/triggers/hubspot/poller.ts +++ b/apps/sim/triggers/hubspot/poller.ts @@ -8,7 +8,6 @@ import { hubspotPropertiesSelectorContract, } from '@/lib/api/contracts/selectors/hubspot' import { getScopesForService } from '@/lib/oauth/utils' -import { isCredentialSetValue } from '@/executor/constants' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import type { TriggerConfig } from '@/triggers/types' @@ -38,7 +37,6 @@ async function fetchHubSpotProperties(blockId: string, objectType: string) { | string | null if (!credentialId) throw new Error('No HubSpot credential selected') - if (isCredentialSetValue(credentialId)) return [] const data = await requestJson(hubspotPropertiesSelectorContract, { query: { credentialId, objectType }, }) @@ -65,7 +63,6 @@ export const hubspotPollingTrigger: TriggerConfig = { requiredScopes: getScopesForService('hubspot'), required: true, mode: 'trigger', - supportsCredentialSets: true, }, { id: 'objectType', @@ -107,7 +104,6 @@ export const hubspotPollingTrigger: TriggerConfig = { | string | null if (!credentialId) throw new Error('No HubSpot credential selected') - if (isCredentialSetValue(credentialId)) return [] try { const data = await requestJson(hubspotListsSelectorContract, { query: { credentialId }, @@ -202,7 +198,6 @@ export const hubspotPollingTrigger: TriggerConfig = { | null const objectType = resolveSelectedObjectType(blockId) ?? 'contact' if (!credentialId) throw new Error('No HubSpot credential selected') - if (isCredentialSetValue(credentialId)) return [] try { const data = await requestJson(hubspotPipelinesSelectorContract, { query: { credentialId, objectType }, @@ -234,7 +229,6 @@ export const hubspotPollingTrigger: TriggerConfig = { | string | null if (!credentialId) throw new Error('No HubSpot credential selected') - if (isCredentialSetValue(credentialId)) return [] if (!pipelineId) return [] try { const data = await requestJson(hubspotPipelinesSelectorContract, { @@ -264,7 +258,6 @@ export const hubspotPollingTrigger: TriggerConfig = { | string | null if (!credentialId) throw new Error('No HubSpot credential selected') - if (isCredentialSetValue(credentialId)) return [] try { const data = await requestJson(hubspotOwnersSelectorContract, { query: { credentialId }, diff --git a/apps/sim/triggers/outlook/poller.ts b/apps/sim/triggers/outlook/poller.ts index 739809658dc..5496ac2f46f 100644 --- a/apps/sim/triggers/outlook/poller.ts +++ b/apps/sim/triggers/outlook/poller.ts @@ -2,23 +2,11 @@ import { createLogger } from '@sim/logger' import { OutlookIcon } from '@/components/icons' import { requestJson } from '@/lib/api/client/request' import { outlookFoldersSelectorContract } from '@/lib/api/contracts/selectors/microsoft' -import { isCredentialSetValue } from '@/executor/constants' import { useSubBlockStore } from '@/stores/workflows/subblock/store' import type { TriggerConfig } from '@/triggers/types' const logger = createLogger('OutlookPollingTrigger') -// Outlook well-known folders that exist for all accounts (used as defaults for credential sets) -const OUTLOOK_SYSTEM_FOLDERS = [ - { id: 'inbox', label: 'Inbox' }, - { id: 'drafts', label: 'Drafts' }, - { id: 'sentitems', label: 'Sent Items' }, - { id: 'deleteditems', label: 'Deleted Items' }, - { id: 'junkemail', label: 'Junk Email' }, - { id: 'archive', label: 'Archive' }, - { id: 'outbox', label: 'Outbox' }, -] - export const outlookPollingTrigger: TriggerConfig = { id: 'outlook_poller', name: 'Outlook Email Trigger', @@ -38,7 +26,6 @@ export const outlookPollingTrigger: TriggerConfig = { requiredScopes: [], required: true, mode: 'trigger', - supportsCredentialSets: true, }, { id: 'folderIds', @@ -56,10 +43,6 @@ export const outlookPollingTrigger: TriggerConfig = { if (!credentialId) { throw new Error('No Outlook credential selected') } - // Return default system folders for credential sets (can't fetch user-specific folders for a pool) - if (isCredentialSetValue(credentialId)) { - return OUTLOOK_SYSTEM_FOLDERS - } try { const data = await requestJson(outlookFoldersSelectorContract, { query: { credentialId }, diff --git a/helm/sim/values.yaml b/helm/sim/values.yaml index 91ae16747fe..f089cbb638a 100644 --- a/helm/sim/values.yaml +++ b/helm/sim/values.yaml @@ -222,8 +222,6 @@ app: SSO_TRUSTED_PROVIDER_IDS: "" # Enterprise Feature Overrides (self-hosted) - CREDENTIAL_SETS_ENABLED: "" # Enable credential sets (email polling) on self-hosted ("true" to enable) - NEXT_PUBLIC_CREDENTIAL_SETS_ENABLED: "" # Show credential sets settings page ("true" to enable) INBOX_ENABLED: "" # Enable Sim Mailer on self-hosted ("true" to enable) NEXT_PUBLIC_INBOX_ENABLED: "" # Show Sim Mailer settings page ("true" to enable) WHITELABELING_ENABLED: "" # Enable whitelabeling on self-hosted ("true" to enable) diff --git a/packages/audit/src/types.ts b/packages/audit/src/types.ts index 0d5bbe02388..3ad3ad9dc9a 100644 --- a/packages/audit/src/types.ts +++ b/packages/audit/src/types.ts @@ -34,17 +34,6 @@ export const AuditAction = { // Billing CREDIT_PURCHASED: 'credit.purchased', - // Credential Sets - CREDENTIAL_SET_CREATED: 'credential_set.created', - CREDENTIAL_SET_UPDATED: 'credential_set.updated', - CREDENTIAL_SET_DELETED: 'credential_set.deleted', - CREDENTIAL_SET_MEMBER_REMOVED: 'credential_set_member.removed', - CREDENTIAL_SET_MEMBER_LEFT: 'credential_set_member.left', - CREDENTIAL_SET_INVITATION_CREATED: 'credential_set_invitation.created', - CREDENTIAL_SET_INVITATION_ACCEPTED: 'credential_set_invitation.accepted', - CREDENTIAL_SET_INVITATION_RESENT: 'credential_set_invitation.resent', - CREDENTIAL_SET_INVITATION_REVOKED: 'credential_set_invitation.revoked', - // Connector Documents CONNECTOR_DOCUMENT_RESTORED: 'connector_document.restored', CONNECTOR_DOCUMENT_EXCLUDED: 'connector_document.excluded', @@ -199,7 +188,6 @@ export const AuditResourceType = { CHAT: 'chat', CONNECTOR: 'connector', CREDENTIAL: 'credential', - CREDENTIAL_SET: 'credential_set', CUSTOM_TOOL: 'custom_tool', DATA_DRAIN: 'data_drain', DOCUMENT: 'document', diff --git a/packages/db/migrations/0255_remove_credential_sets.sql b/packages/db/migrations/0255_remove_credential_sets.sql new file mode 100644 index 00000000000..23dce3250df --- /dev/null +++ b/packages/db/migrations/0255_remove_credential_sets.sql @@ -0,0 +1,12 @@ +-- migration-safe: credential-sets (email polling groups) feature fully removed in this change; prod verified 2026-07-06 at 0 invitations, 0 members, 0 webhooks with credential_set_id, 0 workflow-config references +DROP TABLE "credential_set_invitation" CASCADE;--> statement-breakpoint +-- migration-safe: credential_set_member readers removed with the credential-sets API/fan-out in this change; prod has 0 rows +DROP TABLE "credential_set_member" CASCADE;--> statement-breakpoint +-- migration-safe: credential_set readers removed in this change; CASCADE also drops the webhook.credential_set_id FK constraint; prod has 1 orphaned row with no members, invitations, or webhooks +DROP TABLE "credential_set" CASCADE;--> statement-breakpoint +-- migration-safe: webhook.credential_set_id readers/writers (deploy fan-out, polling plan gate, processor billing gate, webhook routes) all removed in this change; prod verified at 0 non-null values; drops webhook_credential_set_id_idx with it +ALTER TABLE "webhook" DROP COLUMN "credential_set_id";--> statement-breakpoint +-- migration-safe: credential_set_invitation_status enum is only referenced by the credential_set_invitation table dropped above +DROP TYPE "public"."credential_set_invitation_status";--> statement-breakpoint +-- migration-safe: credential_set_member_status enum is only referenced by the credential_set_member table dropped above +DROP TYPE "public"."credential_set_member_status"; diff --git a/packages/db/migrations/meta/0255_snapshot.json b/packages/db/migrations/meta/0255_snapshot.json new file mode 100644 index 00000000000..11e2a922f43 --- /dev/null +++ b/packages/db/migrations/meta/0255_snapshot.json @@ -0,0 +1,16681 @@ +{ + "id": "89ca1946-db4f-4726-b15f-b7869d9bcf41", + "prevId": "1439d1e8-9a5e-4002-a68a-a9bbdd22290f", + "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": {} + } + }, + "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 + }, + "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": {} + }, + "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", + "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 6138ec0120b..f9320ed41b7 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1779,6 +1779,13 @@ "when": 1782957781005, "tag": "0254_custom_block", "breakpoints": true + }, + { + "idx": 255, + "version": "7", + "when": 1783382202081, + "tag": "0255_remove_credential_sets", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index bd6f978daa0..122b987e336 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -744,9 +744,6 @@ export const webhook = pgTable( isActive: boolean('is_active').notNull().default(true), failedCount: integer('failed_count').default(0), // Track consecutive failures lastFailedAt: timestamp('last_failed_at'), // When the webhook last failed - credentialSetId: text('credential_set_id').references(() => credentialSet.id, { - onDelete: 'set null', - }), // For credential set webhooks - enables efficient queries archivedAt: timestamp('archived_at'), createdAt: timestamp('created_at').notNull().defaultNow(), updatedAt: timestamp('updated_at').notNull().defaultNow(), @@ -761,8 +758,6 @@ export const webhook = pgTable( table.workflowId, table.deploymentVersionId ), - // Optimize queries for credential set webhooks - credentialSetIdIdx: index('webhook_credential_set_id_idx').on(table.credentialSetId), archivedAtPartialIdx: index('webhook_archived_at_partial_idx') .on(table.archivedAt) .where(sql`${table.archivedAt} IS NOT NULL`), @@ -3107,99 +3102,6 @@ export const pendingCredentialDraft = pgTable( }) ) -export const credentialSet = pgTable( - 'credential_set', - { - id: text('id').primaryKey(), - organizationId: text('organization_id') - .notNull() - .references(() => organization.id, { onDelete: 'cascade' }), - name: text('name').notNull(), - description: text('description'), - providerId: text('provider_id').notNull(), - createdBy: text('created_by') - .notNull() - .references(() => user.id, { onDelete: 'cascade' }), - createdAt: timestamp('created_at').notNull().defaultNow(), - updatedAt: timestamp('updated_at').notNull().defaultNow(), - }, - (table) => ({ - createdByIdx: index('credential_set_created_by_idx').on(table.createdBy), - orgNameUnique: uniqueIndex('credential_set_org_name_unique').on( - table.organizationId, - table.name - ), - providerIdIdx: index('credential_set_provider_id_idx').on(table.providerId), - }) -) - -export const credentialSetMemberStatusEnum = pgEnum('credential_set_member_status', [ - 'active', - 'pending', - 'revoked', -]) - -export const credentialSetMember = pgTable( - 'credential_set_member', - { - id: text('id').primaryKey(), - credentialSetId: text('credential_set_id') - .notNull() - .references(() => credentialSet.id, { onDelete: 'cascade' }), - userId: text('user_id') - .notNull() - .references(() => user.id, { onDelete: 'cascade' }), - status: credentialSetMemberStatusEnum('status').notNull().default('pending'), - joinedAt: timestamp('joined_at'), - invitedBy: text('invited_by').references(() => user.id, { onDelete: 'set null' }), - createdAt: timestamp('created_at').notNull().defaultNow(), - updatedAt: timestamp('updated_at').notNull().defaultNow(), - }, - (table) => ({ - userIdIdx: index('credential_set_member_user_id_idx').on(table.userId), - uniqueMembership: uniqueIndex('credential_set_member_unique').on( - table.credentialSetId, - table.userId - ), - statusIdx: index('credential_set_member_status_idx').on(table.status), - }) -) - -export const credentialSetInvitationStatusEnum = pgEnum('credential_set_invitation_status', [ - 'pending', - 'accepted', - 'expired', - 'cancelled', -]) - -export const credentialSetInvitation = pgTable( - 'credential_set_invitation', - { - id: text('id').primaryKey(), - credentialSetId: text('credential_set_id') - .notNull() - .references(() => credentialSet.id, { onDelete: 'cascade' }), - email: text('email'), - token: text('token').notNull().unique(), - invitedBy: text('invited_by') - .notNull() - .references(() => user.id, { onDelete: 'cascade' }), - status: credentialSetInvitationStatusEnum('status').notNull().default('pending'), - expiresAt: timestamp('expires_at').notNull(), - acceptedAt: timestamp('accepted_at'), - acceptedByUserId: text('accepted_by_user_id').references(() => user.id, { - onDelete: 'set null', - }), - createdAt: timestamp('created_at').notNull().defaultNow(), - }, - (table) => ({ - credentialSetIdIdx: index('credential_set_invitation_set_id_idx').on(table.credentialSetId), - tokenIdx: index('credential_set_invitation_token_idx').on(table.token), - statusIdx: index('credential_set_invitation_status_idx').on(table.status), - expiresAtIdx: index('credential_set_invitation_expires_at_idx').on(table.expiresAt), - }) -) - /** * A named set of access-control restrictions (`config`) governing users within * an organization. diff --git a/packages/testing/src/mocks/audit.mock.ts b/packages/testing/src/mocks/audit.mock.ts index d7a16b82364..82a59734b8f 100644 --- a/packages/testing/src/mocks/audit.mock.ts +++ b/packages/testing/src/mocks/audit.mock.ts @@ -47,15 +47,6 @@ export const auditMock = { CREDENTIAL_MEMBER_REMOVED: 'credential_member.removed', CREDENTIAL_MEMBER_ROLE_CHANGED: 'credential_member.role_changed', CREDIT_PURCHASED: 'credit.purchased', - CREDENTIAL_SET_CREATED: 'credential_set.created', - CREDENTIAL_SET_UPDATED: 'credential_set.updated', - CREDENTIAL_SET_DELETED: 'credential_set.deleted', - CREDENTIAL_SET_MEMBER_REMOVED: 'credential_set_member.removed', - CREDENTIAL_SET_MEMBER_LEFT: 'credential_set_member.left', - CREDENTIAL_SET_INVITATION_CREATED: 'credential_set_invitation.created', - CREDENTIAL_SET_INVITATION_ACCEPTED: 'credential_set_invitation.accepted', - CREDENTIAL_SET_INVITATION_RESENT: 'credential_set_invitation.resent', - CREDENTIAL_SET_INVITATION_REVOKED: 'credential_set_invitation.revoked', CUSTOM_TOOL_CREATED: 'custom_tool.created', CUSTOM_TOOL_UPDATED: 'custom_tool.updated', CUSTOM_TOOL_DELETED: 'custom_tool.deleted', @@ -165,7 +156,6 @@ export const auditMock = { CHAT: 'chat', CONNECTOR: 'connector', CREDENTIAL: 'credential', - CREDENTIAL_SET: 'credential_set', CUSTOM_TOOL: 'custom_tool', DATA_DRAIN: 'data_drain', DOCUMENT: 'document', diff --git a/packages/testing/src/mocks/auth-oauth-utils.mock.ts b/packages/testing/src/mocks/auth-oauth-utils.mock.ts index ad0d6395ac1..a11162377c6 100644 --- a/packages/testing/src/mocks/auth-oauth-utils.mock.ts +++ b/packages/testing/src/mocks/auth-oauth-utils.mock.ts @@ -35,7 +35,6 @@ export const authOAuthUtilsMockFns = { mockGetOAuthToken: vi.fn(), mockRefreshAccessTokenIfNeeded: vi.fn(), mockRefreshTokenIfNeeded: vi.fn(), - mockGetCredentialsForCredentialSet: vi.fn(), } /** @@ -55,5 +54,4 @@ export const authOAuthUtilsMock = { getOAuthToken: authOAuthUtilsMockFns.mockGetOAuthToken, refreshAccessTokenIfNeeded: authOAuthUtilsMockFns.mockRefreshAccessTokenIfNeeded, refreshTokenIfNeeded: authOAuthUtilsMockFns.mockRefreshTokenIfNeeded, - getCredentialsForCredentialSet: authOAuthUtilsMockFns.mockGetCredentialsForCredentialSet, } diff --git a/packages/testing/src/mocks/env-flags.mock.ts b/packages/testing/src/mocks/env-flags.mock.ts index 597d572090a..480ae0d62f0 100644 --- a/packages/testing/src/mocks/env-flags.mock.ts +++ b/packages/testing/src/mocks/env-flags.mock.ts @@ -23,7 +23,6 @@ export const envFlagsMock = { isTriggerDevEnabled: false, isTablesFractionalOrderingEnabled: false, isSsoEnabled: false, - isCredentialSetsEnabled: false, isAccessControlEnabled: false, isOrganizationsEnabled: false, isInboxEnabled: false, diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index aa28b59fe36..4d52742a3a5 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -302,7 +302,6 @@ export const schemaMock = { isActive: 'isActive', failedCount: 'failedCount', lastFailedAt: 'lastFailedAt', - credentialSetId: 'credentialSetId', archivedAt: 'archivedAt', createdAt: 'createdAt', updatedAt: 'updatedAt', @@ -939,40 +938,6 @@ export const schemaMock = { expiresAt: 'expiresAt', createdAt: 'createdAt', }, - credentialSet: { - id: 'id', - organizationId: 'organizationId', - name: 'name', - description: 'description', - providerId: 'providerId', - createdBy: 'createdBy', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - }, - credentialSetMemberStatusEnum: 'credentialSetMemberStatusEnum', - credentialSetMember: { - id: 'id', - credentialSetId: 'credentialSetId', - userId: 'userId', - status: 'status', - joinedAt: 'joinedAt', - invitedBy: 'invitedBy', - createdAt: 'createdAt', - updatedAt: 'updatedAt', - }, - credentialSetInvitationStatusEnum: 'credentialSetInvitationStatusEnum', - credentialSetInvitation: { - id: 'id', - credentialSetId: 'credentialSetId', - email: 'email', - token: 'token', - invitedBy: 'invitedBy', - status: 'status', - expiresAt: 'expiresAt', - acceptedAt: 'acceptedAt', - acceptedByUserId: 'acceptedByUserId', - createdAt: 'createdAt', - }, permissionGroup: { id: 'id', organizationId: 'organizationId', diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index 0135f8596d5..55c735dbca6 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -45,7 +45,6 @@ const INDIRECT_ZOD_ROUTES = new Set([ 'apps/sim/app/api/auth/oauth/connections/route.ts', 'apps/sim/app/api/auth/providers/route.ts', 'apps/sim/app/api/auth/socket-token/route.ts', - 'apps/sim/app/api/credential-sets/invitations/route.ts', 'apps/sim/app/api/workspaces/invitations/route.ts', // Internal cron entry point that authenticates via `Authorization: Bearer // CRON_SECRET` and ignores query/body. The boundary contract is "no