From ba01e881e6878d8735b3761865277b1772691599 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 3 Jul 2025 20:47:18 +0000 Subject: [PATCH 1/5] Add default channel support for Slack integration Co-authored-by: dallin --- apps/server/src/api/v2/slack/handler.ts | 105 +- apps/server/src/api/v2/slack/index.ts | 1 + .../api/v2/slack/services/slack-helpers.ts | 93 +- ..._default_channel_to_slack_integrations.sql | 3 + packages/database/src/schema.ts | 3537 +++++++++-------- 5 files changed, 1936 insertions(+), 1803 deletions(-) create mode 100644 packages/database/drizzle/0073_add_default_channel_to_slack_integrations.sql diff --git a/apps/server/src/api/v2/slack/handler.ts b/apps/server/src/api/v2/slack/handler.ts index 2dc5133e6..40cb6d998 100644 --- a/apps/server/src/api/v2/slack/handler.ts +++ b/apps/server/src/api/v2/slack/handler.ts @@ -20,12 +20,17 @@ const OAuthCallbackSchema = z.object({ state: z.string(), }); +const UpdateDefaultChannelSchema = z.object({ + name: z.string().min(1), + id: z.string().min(1), +}); + // Custom error class export class SlackError extends Error { constructor( message: string, public statusCode: 500 | 400 | 401 | 403 | 404 | 409 | 503 = 500, - public code?: string + public code?: string, ) { super(message); this.name = 'SlackError'; @@ -72,7 +77,7 @@ export class SlackHandler { error: 'Slack integration is not configured', code: 'INTEGRATION_NOT_CONFIGURED', }, - 503 + 503, ); } @@ -83,7 +88,7 @@ export class SlackHandler { error: 'Slack integration is not enabled', code: 'INTEGRATION_DISABLED', }, - 503 + 503, ); } @@ -131,14 +136,14 @@ export class SlackHandler { throw new SlackError( 'Organization already has an active Slack integration', 409, - 'INTEGRATION_EXISTS' + 'INTEGRATION_EXISTS', ); } throw new SlackError( error instanceof Error ? error.message : 'Failed to initiate OAuth', 500, - 'OAUTH_INIT_ERROR' + 'OAUTH_INIT_ERROR', ); } } @@ -193,7 +198,7 @@ export class SlackHandler { console.error('OAuth callback error:', error); const errorMessage = error instanceof Error ? error.message : 'callback_failed'; return c.redirect( - `/settings/integrations?status=error&error=${encodeURIComponent(errorMessage)}` + `/settings/integrations?status=error&error=${encodeURIComponent(errorMessage)}`, ); } } @@ -214,7 +219,7 @@ export class SlackHandler { error: 'Slack integration is not configured', code: 'INTEGRATION_NOT_CONFIGURED', }, - 503 + 503, ); } @@ -243,7 +248,7 @@ export class SlackHandler { throw new SlackError( error instanceof Error ? error.message : 'Failed to get integration status', 500, - 'GET_INTEGRATION_ERROR' + 'GET_INTEGRATION_ERROR', ); } } @@ -264,7 +269,7 @@ export class SlackHandler { error: 'Slack integration is not configured', code: 'INTEGRATION_NOT_CONFIGURED', }, - 503 + 503, ); } @@ -282,14 +287,14 @@ export class SlackHandler { const result = await slackOAuthService.removeIntegration( organizationGrant.organizationId, - user.id + user.id, ); if (!result.success) { throw new SlackError( result.error || 'Failed to remove integration', 404, - 'INTEGRATION_NOT_FOUND' + 'INTEGRATION_NOT_FOUND', ); } @@ -306,7 +311,83 @@ export class SlackHandler { throw new SlackError( error instanceof Error ? error.message : 'Failed to remove integration', 500, - 'REMOVE_INTEGRATION_ERROR' + 'REMOVE_INTEGRATION_ERROR', + ); + } + } + + /** + * PUT /api/v2/slack/integration/default-channel + * Update default channel for Slack integration + */ + async updateDefaultChannel(c: Context) { + try { + // Get service instance (lazy initialization) + const slackOAuthService = this.getSlackOAuthService(); + + // Check if service is available + if (!slackOAuthService) { + return c.json( + { + error: 'Slack integration is not configured', + code: 'INTEGRATION_NOT_CONFIGURED', + }, + 503, + ); + } + + const user = c.get('busterUser'); + + if (!user) { + throw new HTTPException(401, { message: 'Authentication required' }); + } + + const organizationGrant = await getUserOrganizationId(user.id); + + if (!organizationGrant) { + throw new HTTPException(400, { message: 'Organization not found' }); + } + + // Parse request body + const body = await c.req.json(); + const parsed = UpdateDefaultChannelSchema.safeParse(body); + + if (!parsed.success) { + throw new SlackError( + `Invalid request body: ${parsed.error.errors.map((e) => e.message).join(', ')}`, + 400, + 'INVALID_REQUEST_BODY', + ); + } + + // Get active integration + const { getActiveIntegration, updateDefaultChannel } = await import( + './services/slack-helpers' + ); + const integration = await getActiveIntegration(organizationGrant.organizationId); + + if (!integration) { + throw new SlackError('No active Slack integration found', 404, 'INTEGRATION_NOT_FOUND'); + } + + // Update default channel + await updateDefaultChannel(integration.id, parsed.data); + + return c.json({ + message: 'Default channel updated successfully', + defaultChannel: parsed.data, + }); + } catch (error) { + console.error('Failed to update default channel:', error); + + if (error instanceof HTTPException || error instanceof SlackError) { + throw error; + } + + throw new SlackError( + error instanceof Error ? error.message : 'Failed to update default channel', + 500, + 'UPDATE_DEFAULT_CHANNEL_ERROR', ); } } diff --git a/apps/server/src/api/v2/slack/index.ts b/apps/server/src/api/v2/slack/index.ts index 2ecbe46b4..e54b84488 100644 --- a/apps/server/src/api/v2/slack/index.ts +++ b/apps/server/src/api/v2/slack/index.ts @@ -10,6 +10,7 @@ const app = new Hono() // Protected endpoints .get('/integration', requireAuth, (c) => slackHandler.getIntegration(c)) .delete('/integration', requireAuth, (c) => slackHandler.removeIntegration(c)) + .put('/integration/default-channel', requireAuth, (c) => slackHandler.updateDefaultChannel(c)) // Error handling .onError((e, c) => { if (e instanceof SlackError) { diff --git a/apps/server/src/api/v2/slack/services/slack-helpers.ts b/apps/server/src/api/v2/slack/services/slack-helpers.ts index 39c5eefbd..61050df75 100644 --- a/apps/server/src/api/v2/slack/services/slack-helpers.ts +++ b/apps/server/src/api/v2/slack/services/slack-helpers.ts @@ -9,7 +9,7 @@ export type SlackIntegration = InferSelectModel; * Get active Slack integration for an organization */ export async function getActiveIntegration( - organizationId: string + organizationId: string, ): Promise { try { const [integration] = await db @@ -19,8 +19,8 @@ export async function getActiveIntegration( and( eq(slackIntegrations.organizationId, organizationId), eq(slackIntegrations.status, 'active'), - isNull(slackIntegrations.deletedAt) - ) + isNull(slackIntegrations.deletedAt), + ), ) .limit(1); @@ -28,7 +28,9 @@ export async function getActiveIntegration( } catch (error) { console.error('Failed to get active Slack integration:', error); throw new Error( - `Failed to get active Slack integration: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to get active Slack integration: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, ); } } @@ -37,7 +39,7 @@ export async function getActiveIntegration( * Get pending integration by OAuth state */ export async function getPendingIntegrationByState( - state: string + state: string, ): Promise { try { const [integration] = await db @@ -47,8 +49,8 @@ export async function getPendingIntegrationByState( and( eq(slackIntegrations.oauthState, state), eq(slackIntegrations.status, 'pending'), - gt(slackIntegrations.oauthExpiresAt, new Date().toISOString()) - ) + gt(slackIntegrations.oauthExpiresAt, new Date().toISOString()), + ), ) .limit(1); @@ -56,7 +58,9 @@ export async function getPendingIntegrationByState( } catch (error) { console.error('Failed to get pending integration by state:', error); throw new Error( - `Failed to get pending integration: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to get pending integration: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, ); } } @@ -118,7 +122,9 @@ export async function createPendingIntegration(params: { } catch (error) { console.error('Failed to create pending Slack integration:', error); throw new Error( - `Failed to create pending integration: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to create pending integration: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, ); } } @@ -137,7 +143,7 @@ export async function updateIntegrationAfterOAuth( scope: string; tokenVaultKey: string; installedBySlackUserId?: string; - } + }, ): Promise { try { await db @@ -155,7 +161,7 @@ export async function updateIntegrationAfterOAuth( } catch (error) { console.error('Failed to update integration after OAuth:', error); throw new Error( - `Failed to activate integration: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to activate integration: ${error instanceof Error ? error.message : 'Unknown error'}`, ); } } @@ -165,7 +171,7 @@ export async function updateIntegrationAfterOAuth( */ export async function markIntegrationAsFailed( integrationId: string, - _error?: string + _error?: string, ): Promise { try { // Due to database constraint, we cannot mark a pending integration as failed @@ -206,7 +212,9 @@ export async function markIntegrationAsFailed( } catch (error) { console.error('Failed to mark integration as failed:', error); throw new Error( - `Failed to mark integration as failed: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to mark integration as failed: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, ); } } @@ -227,7 +235,9 @@ export async function softDeleteIntegration(integrationId: string): Promise { } catch (error) { console.error('Failed to update last used timestamp:', error); throw new Error( - `Failed to update last used timestamp: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to update last used timestamp: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, ); } } @@ -267,7 +279,9 @@ export async function getIntegrationById(integrationId: string): Promise { try { // Get the most recent non-deleted integration for this organization @@ -306,7 +322,34 @@ export async function getExistingIntegration( } catch (error) { console.error('Failed to get existing Slack integration:', error); throw new Error( - `Failed to get existing Slack integration: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to get existing Slack integration: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, + ); + } +} + +/** + * Update default channel for Slack integration + */ +export async function updateDefaultChannel( + integrationId: string, + defaultChannel: { name: string; id: string }, +): Promise { + try { + await db + .update(slackIntegrations) + .set({ + defaultChannel, + updatedAt: new Date().toISOString(), + }) + .where(eq(slackIntegrations.id, integrationId)); + } catch (error) { + console.error('Failed to update default channel:', error); + throw new Error( + `Failed to update default channel: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, ); } } @@ -323,8 +366,8 @@ export async function cleanupExpiredPendingIntegrations(): Promise { .where( and( eq(slackIntegrations.status, 'pending'), - lt(slackIntegrations.oauthExpiresAt, new Date().toISOString()) - ) + lt(slackIntegrations.oauthExpiresAt, new Date().toISOString()), + ), ); // Clean up vault tokens for each expired integration @@ -345,8 +388,8 @@ export async function cleanupExpiredPendingIntegrations(): Promise { .where( and( eq(slackIntegrations.status, 'pending'), - lt(slackIntegrations.oauthExpiresAt, new Date().toISOString()) - ) + lt(slackIntegrations.oauthExpiresAt, new Date().toISOString()), + ), ) .returning({ id: slackIntegrations.id }); @@ -354,7 +397,9 @@ export async function cleanupExpiredPendingIntegrations(): Promise { } catch (error) { console.error('Failed to cleanup expired pending integrations:', error); throw new Error( - `Failed to cleanup expired integrations: ${error instanceof Error ? error.message : 'Unknown error'}` + `Failed to cleanup expired integrations: ${ + error instanceof Error ? error.message : 'Unknown error' + }`, ); } } diff --git a/packages/database/drizzle/0073_add_default_channel_to_slack_integrations.sql b/packages/database/drizzle/0073_add_default_channel_to_slack_integrations.sql new file mode 100644 index 000000000..77bf88324 --- /dev/null +++ b/packages/database/drizzle/0073_add_default_channel_to_slack_integrations.sql @@ -0,0 +1,3 @@ +-- Add default_channel column to slack_integrations table +ALTER TABLE "slack_integrations" +ADD COLUMN "default_channel" jsonb DEFAULT '{}'::jsonb; \ No newline at end of file diff --git a/packages/database/src/schema.ts b/packages/database/src/schema.ts index 4053547b2..bc69a394c 100644 --- a/packages/database/src/schema.ts +++ b/packages/database/src/schema.ts @@ -1,1946 +1,1949 @@ import { sql } from 'drizzle-orm'; import { - bigint, - boolean, - check, - doublePrecision, - foreignKey, - index, - integer, - jsonb, - pgEnum, - pgPolicy, - pgTable, - primaryKey, - text, - timestamp, - unique, - uniqueIndex, - uuid, - varchar, + bigint, + boolean, + check, + doublePrecision, + foreignKey, + index, + integer, + jsonb, + pgEnum, + pgPolicy, + pgTable, + primaryKey, + text, + timestamp, + unique, + uniqueIndex, + uuid, + varchar, } from 'drizzle-orm/pg-core'; export const assetPermissionRoleEnum = pgEnum('asset_permission_role_enum', [ - 'owner', - 'editor', - 'viewer', - 'full_access', - 'can_edit', - 'can_filter', - 'can_view', + 'owner', + 'editor', + 'viewer', + 'full_access', + 'can_edit', + 'can_filter', + 'can_view', ]); export const assetTypeEnum = pgEnum('asset_type_enum', [ - 'dashboard', - 'thread', - 'collection', - 'chat', - 'metric_file', - 'dashboard_file', + 'dashboard', + 'thread', + 'collection', + 'chat', + 'metric_file', + 'dashboard_file', ]); export const dataSourceOnboardingStatusEnum = pgEnum('data_source_onboarding_status_enum', [ - 'notStarted', - 'inProgress', - 'completed', - 'failed', + 'notStarted', + 'inProgress', + 'completed', + 'failed', ]); export const datasetTypeEnum = pgEnum('dataset_type_enum', ['table', 'view', 'materializedView']); export const identityTypeEnum = pgEnum('identity_type_enum', ['user', 'team', 'organization']); export const messageFeedbackEnum = pgEnum('message_feedback_enum', ['positive', 'negative']); export const sharingSettingEnum = pgEnum('sharing_setting_enum', [ - 'none', - 'team', - 'organization', - 'public', + 'none', + 'team', + 'organization', + 'public', ]); export const storedValuesStatusEnum = pgEnum('stored_values_status_enum', [ - 'syncing', - 'success', - 'failed', + 'syncing', + 'success', + 'failed', ]); export const teamRoleEnum = pgEnum('team_role_enum', ['manager', 'member']); export const userOrganizationRoleEnum = pgEnum('user_organization_role_enum', [ - 'workspace_admin', - 'data_admin', - 'querier', - 'restricted_querier', - 'viewer', + 'workspace_admin', + 'data_admin', + 'querier', + 'restricted_querier', + 'viewer', ]); export const userOrganizationStatusEnum = pgEnum('user_organization_status_enum', [ - 'active', - 'inactive', - 'pending', - 'guest', + 'active', + 'inactive', + 'pending', + 'guest', ]); export const verificationEnum = pgEnum('verification_enum', [ - 'verified', - 'backlogged', - 'inReview', - 'requested', - 'notRequested', + 'verified', + 'backlogged', + 'inReview', + 'requested', + 'notRequested', ]); export const tableTypeEnum = pgEnum('table_type_enum', [ - 'TABLE', - 'VIEW', - 'MATERIALIZED_VIEW', - 'EXTERNAL_TABLE', - 'TEMPORARY_TABLE', + 'TABLE', + 'VIEW', + 'MATERIALIZED_VIEW', + 'EXTERNAL_TABLE', + 'TEMPORARY_TABLE', ]); export const slackIntegrationStatusEnum = pgEnum('slack_integration_status_enum', [ - 'pending', - 'active', - 'failed', - 'revoked', + 'pending', + 'active', + 'failed', + 'revoked', ]); export const apiKeys = pgTable( - 'api_keys', - { - id: uuid().defaultRandom().primaryKey().notNull(), - ownerId: uuid('owner_id').notNull(), - key: text().notNull(), - organizationId: uuid('organization_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'api_keys_organization_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.ownerId], - foreignColumns: [users.id], - name: 'api_keys_owner_id_fkey', - }).onUpdate('cascade'), - unique('api_keys_key_key').on(table.key), - ] + 'api_keys', + { + id: uuid().defaultRandom().primaryKey().notNull(), + ownerId: uuid('owner_id').notNull(), + key: text().notNull(), + organizationId: uuid('organization_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'api_keys_organization_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.ownerId], + foreignColumns: [users.id], + name: 'api_keys_owner_id_fkey', + }).onUpdate('cascade'), + unique('api_keys_key_key').on(table.key), + ], ); export const teams = pgTable( - 'teams', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: text().notNull(), - organizationId: uuid('organization_id').notNull(), - sharingSetting: sharingSettingEnum('sharing_setting').default('none').notNull(), - editSql: boolean('edit_sql').default(false).notNull(), - uploadCsv: boolean('upload_csv').default(false).notNull(), - exportAssets: boolean('export_assets').default(false).notNull(), - emailSlackEnabled: boolean('email_slack_enabled').default(false).notNull(), - createdBy: uuid('created_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'teams_organization_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'teams_created_by_fkey', - }).onUpdate('cascade'), - unique('teams_name_key').on(table.name), - ] + 'teams', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: text().notNull(), + organizationId: uuid('organization_id').notNull(), + sharingSetting: sharingSettingEnum('sharing_setting').default('none').notNull(), + editSql: boolean('edit_sql').default(false).notNull(), + uploadCsv: boolean('upload_csv').default(false).notNull(), + exportAssets: boolean('export_assets').default(false).notNull(), + emailSlackEnabled: boolean('email_slack_enabled').default(false).notNull(), + createdBy: uuid('created_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'teams_organization_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'teams_created_by_fkey', + }).onUpdate('cascade'), + unique('teams_name_key').on(table.name), + ], ); export const permissionGroups = pgTable( - 'permission_groups', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: text().notNull(), - organizationId: uuid('organization_id').notNull(), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'permission_groups_organization_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'permission_groups_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'permission_groups_updated_by_fkey', - }).onUpdate('cascade'), - ] + 'permission_groups', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: text().notNull(), + organizationId: uuid('organization_id').notNull(), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'permission_groups_organization_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'permission_groups_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'permission_groups_updated_by_fkey', + }).onUpdate('cascade'), + ], ); export const terms = pgTable( - 'terms', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: text().notNull(), - definition: text(), - sqlSnippet: text('sql_snippet'), - organizationId: uuid('organization_id').notNull(), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'terms_organization_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'terms_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'terms_updated_by_fkey', - }).onUpdate('cascade'), - ] + 'terms', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: text().notNull(), + definition: text(), + sqlSnippet: text('sql_snippet'), + organizationId: uuid('organization_id').notNull(), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'terms_organization_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'terms_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'terms_updated_by_fkey', + }).onUpdate('cascade'), + ], ); export const collections = pgTable( - 'collections', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: text().notNull(), - description: text(), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - organizationId: uuid('organization_id').notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'collections_organization_id_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'collections_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'collections_updated_by_fkey', - }).onUpdate('cascade'), - ] + 'collections', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: text().notNull(), + description: text(), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + organizationId: uuid('organization_id').notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'collections_organization_id_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'collections_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'collections_updated_by_fkey', + }).onUpdate('cascade'), + ], ); export const dashboards = pgTable( - 'dashboards', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: text().notNull(), - description: text(), - config: jsonb().notNull(), - publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), - publiclyEnabledBy: uuid('publicly_enabled_by'), - publicExpiryDate: timestamp('public_expiry_date', { - withTimezone: true, - mode: 'string', - }), - passwordSecretId: uuid('password_secret_id'), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - organizationId: uuid('organization_id').notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.publiclyEnabledBy], - foreignColumns: [users.id], - name: 'dashboards_publicly_enabled_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'dashboards_organization_id_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'dashboards_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'dashboards_updated_by_fkey', - }).onUpdate('cascade'), - ] + 'dashboards', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: text().notNull(), + description: text(), + config: jsonb().notNull(), + publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), + publiclyEnabledBy: uuid('publicly_enabled_by'), + publicExpiryDate: timestamp('public_expiry_date', { + withTimezone: true, + mode: 'string', + }), + passwordSecretId: uuid('password_secret_id'), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + organizationId: uuid('organization_id').notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.publiclyEnabledBy], + foreignColumns: [users.id], + name: 'dashboards_publicly_enabled_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'dashboards_organization_id_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'dashboards_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'dashboards_updated_by_fkey', + }).onUpdate('cascade'), + ], ); export const dashboardVersions = pgTable( - 'dashboard_versions', - { - id: uuid().defaultRandom().primaryKey().notNull(), - dashboardId: uuid('dashboard_id').notNull(), - config: jsonb().notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.dashboardId], - foreignColumns: [dashboards.id], - name: 'dashboard_versions_dashboard_id_fkey', - }).onDelete('cascade'), - ] + 'dashboard_versions', + { + id: uuid().defaultRandom().primaryKey().notNull(), + dashboardId: uuid('dashboard_id').notNull(), + config: jsonb().notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.dashboardId], + foreignColumns: [dashboards.id], + name: 'dashboard_versions_dashboard_id_fkey', + }).onDelete('cascade'), + ], ); export const dataSources = pgTable( - 'data_sources', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: text().notNull(), - type: text().notNull(), - secretId: uuid('secret_id').notNull(), - onboardingStatus: dataSourceOnboardingStatusEnum('onboarding_status') - .default('notStarted') - .notNull(), - onboardingError: text('onboarding_error'), - organizationId: uuid('organization_id').notNull(), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - env: varchar().default('dev').notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'data_sources_organization_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'data_sources_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'data_sources_updated_by_fkey', - }).onUpdate('cascade'), - unique('data_sources_name_organization_id_env_key').on( - table.name, - table.organizationId, - table.env - ), - ] + 'data_sources', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: text().notNull(), + type: text().notNull(), + secretId: uuid('secret_id').notNull(), + onboardingStatus: dataSourceOnboardingStatusEnum('onboarding_status') + .default('notStarted') + .notNull(), + onboardingError: text('onboarding_error'), + organizationId: uuid('organization_id').notNull(), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + env: varchar().default('dev').notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'data_sources_organization_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'data_sources_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'data_sources_updated_by_fkey', + }).onUpdate('cascade'), + unique('data_sources_name_organization_id_env_key').on( + table.name, + table.organizationId, + table.env, + ), + ], ); export const datasetColumns = pgTable( - 'dataset_columns', - { - id: uuid().primaryKey().notNull(), - datasetId: uuid('dataset_id').notNull(), - name: text().notNull(), - type: text().notNull(), - description: text(), - nullable: boolean().notNull(), - createdAt: timestamp('created_at', { - withTimezone: true, - mode: 'string', - }).notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - storedValues: boolean('stored_values').default(false), - storedValuesStatus: storedValuesStatusEnum('stored_values_status'), - storedValuesError: text('stored_values_error'), - // You can use { mode: "bigint" } if numbers are exceeding js number limitations - storedValuesCount: bigint('stored_values_count', { mode: 'number' }), - storedValuesLastSynced: timestamp('stored_values_last_synced', { - withTimezone: true, - mode: 'string', - }), - semanticType: text('semantic_type'), - dimType: text('dim_type'), - expr: text(), - }, - (table) => [unique('unique_dataset_column_name').on(table.datasetId, table.name)] + 'dataset_columns', + { + id: uuid().primaryKey().notNull(), + datasetId: uuid('dataset_id').notNull(), + name: text().notNull(), + type: text().notNull(), + description: text(), + nullable: boolean().notNull(), + createdAt: timestamp('created_at', { + withTimezone: true, + mode: 'string', + }).notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + storedValues: boolean('stored_values').default(false), + storedValuesStatus: storedValuesStatusEnum('stored_values_status'), + storedValuesError: text('stored_values_error'), + // You can use { mode: "bigint" } if numbers are exceeding js number limitations + storedValuesCount: bigint('stored_values_count', { mode: 'number' }), + storedValuesLastSynced: timestamp('stored_values_last_synced', { + withTimezone: true, + mode: 'string', + }), + semanticType: text('semantic_type'), + dimType: text('dim_type'), + expr: text(), + }, + (table) => [unique('unique_dataset_column_name').on(table.datasetId, table.name)], ); export const sqlEvaluations = pgTable('sql_evaluations', { - id: uuid().default(sql`uuid_generate_v4()`).primaryKey().notNull(), - evaluationObj: jsonb('evaluation_obj').notNull(), - evaluationSummary: text('evaluation_summary').notNull(), - score: text().notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }).defaultNow().notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }).defaultNow().notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + id: uuid().default(sql`uuid_generate_v4()`).primaryKey().notNull(), + evaluationObj: jsonb('evaluation_obj').notNull(), + evaluationSummary: text('evaluation_summary').notNull(), + score: text().notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }).defaultNow().notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }).defaultNow().notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), }); export const assetSearch = pgTable( - 'asset_search', - { - id: uuid().defaultRandom().primaryKey().notNull(), - assetId: uuid('asset_id').notNull(), - assetType: text('asset_type').notNull(), - content: text().notNull(), - organizationId: uuid('organization_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - uniqueIndex('asset_search_asset_id_asset_type_idx').using( - 'btree', - table.assetId.asc().nullsLast().op('text_ops'), - table.assetType.asc().nullsLast().op('text_ops') - ), - index('pgroonga_content_index').using( - 'pgroonga', - table.content.asc().nullsLast().op('pgroonga_text_full_text_search_ops_v2') - ), - ] + 'asset_search', + { + id: uuid().defaultRandom().primaryKey().notNull(), + assetId: uuid('asset_id').notNull(), + assetType: text('asset_type').notNull(), + content: text().notNull(), + organizationId: uuid('organization_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + uniqueIndex('asset_search_asset_id_asset_type_idx').using( + 'btree', + table.assetId.asc().nullsLast().op('text_ops'), + table.assetType.asc().nullsLast().op('text_ops'), + ), + index('pgroonga_content_index').using( + 'pgroonga', + table.content.asc().nullsLast().op('pgroonga_text_full_text_search_ops_v2'), + ), + ], ); export const datasetGroups = pgTable( - 'dataset_groups', - { - id: uuid().defaultRandom().primaryKey().notNull(), - organizationId: uuid('organization_id').notNull(), - name: varchar().notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - index('dataset_groups_deleted_at_idx').using( - 'btree', - table.deletedAt.asc().nullsLast().op('timestamptz_ops') - ), - index('dataset_groups_organization_id_idx').using( - 'btree', - table.organizationId.asc().nullsLast().op('uuid_ops') - ), - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'dataset_groups_organization_id_fkey', - }).onDelete('cascade'), - pgPolicy('dataset_groups_policy', { - as: 'permissive', - for: 'all', - to: ['authenticated'], - using: sql`true`, - }), - ] + 'dataset_groups', + { + id: uuid().defaultRandom().primaryKey().notNull(), + organizationId: uuid('organization_id').notNull(), + name: varchar().notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + index('dataset_groups_deleted_at_idx').using( + 'btree', + table.deletedAt.asc().nullsLast().op('timestamptz_ops'), + ), + index('dataset_groups_organization_id_idx').using( + 'btree', + table.organizationId.asc().nullsLast().op('uuid_ops'), + ), + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'dataset_groups_organization_id_fkey', + }).onDelete('cascade'), + pgPolicy('dataset_groups_policy', { + as: 'permissive', + for: 'all', + to: ['authenticated'], + using: sql`true`, + }), + ], ); export const datasetPermissions = pgTable( - 'dataset_permissions', - { - id: uuid().defaultRandom().primaryKey().notNull(), - organizationId: uuid('organization_id').notNull(), - datasetId: uuid('dataset_id').notNull(), - permissionId: uuid('permission_id').notNull(), - permissionType: varchar('permission_type').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - index('dataset_permissions_dataset_id_idx').using( - 'btree', - table.datasetId.asc().nullsLast().op('uuid_ops') - ), - index('dataset_permissions_deleted_at_idx').using( - 'btree', - table.deletedAt.asc().nullsLast().op('timestamptz_ops') - ), - index('dataset_permissions_organization_id_idx').using( - 'btree', - table.organizationId.asc().nullsLast().op('uuid_ops') - ), - index('dataset_permissions_permission_lookup_idx').using( - 'btree', - table.permissionId.asc().nullsLast().op('uuid_ops'), - table.permissionType.asc().nullsLast().op('text_ops') - ), - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'dataset_permissions_organization_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.datasetId], - foreignColumns: [datasets.id], - name: 'dataset_permissions_dataset_id_fkey', - }).onDelete('cascade'), - unique('dataset_permissions_dataset_id_permission_id_permission_typ_key').on( - table.datasetId, - table.permissionId, - table.permissionType - ), - pgPolicy('dataset_permissions_policy', { - as: 'permissive', - for: 'all', - to: ['authenticated'], - using: sql`true`, - }), - check( - 'dataset_permissions_permission_type_check', - sql`(permission_type)::text = ANY ((ARRAY['user'::character varying, 'dataset_group'::character varying, 'permission_group'::character varying])::text[])` - ), - ] + 'dataset_permissions', + { + id: uuid().defaultRandom().primaryKey().notNull(), + organizationId: uuid('organization_id').notNull(), + datasetId: uuid('dataset_id').notNull(), + permissionId: uuid('permission_id').notNull(), + permissionType: varchar('permission_type').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + index('dataset_permissions_dataset_id_idx').using( + 'btree', + table.datasetId.asc().nullsLast().op('uuid_ops'), + ), + index('dataset_permissions_deleted_at_idx').using( + 'btree', + table.deletedAt.asc().nullsLast().op('timestamptz_ops'), + ), + index('dataset_permissions_organization_id_idx').using( + 'btree', + table.organizationId.asc().nullsLast().op('uuid_ops'), + ), + index('dataset_permissions_permission_lookup_idx').using( + 'btree', + table.permissionId.asc().nullsLast().op('uuid_ops'), + table.permissionType.asc().nullsLast().op('text_ops'), + ), + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'dataset_permissions_organization_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.datasetId], + foreignColumns: [datasets.id], + name: 'dataset_permissions_dataset_id_fkey', + }).onDelete('cascade'), + unique('dataset_permissions_dataset_id_permission_id_permission_typ_key').on( + table.datasetId, + table.permissionId, + table.permissionType, + ), + pgPolicy('dataset_permissions_policy', { + as: 'permissive', + for: 'all', + to: ['authenticated'], + using: sql`true`, + }), + check( + 'dataset_permissions_permission_type_check', + sql`(permission_type)::text = ANY ((ARRAY['user'::character varying, 'dataset_group'::character varying, 'permission_group'::character varying])::text[])`, + ), + ], ); export const dieselSchemaMigrations = pgTable( - '__diesel_schema_migrations', - { - version: varchar({ length: 50 }).primaryKey().notNull(), - runOn: timestamp('run_on', { mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), - }, - (_table) => [ - pgPolicy('diesel_schema_migrations_policy', { - as: 'permissive', - for: 'all', - to: ['authenticated'], - using: sql`true`, - }), - ] + '__diesel_schema_migrations', + { + version: varchar({ length: 50 }).primaryKey().notNull(), + runOn: timestamp('run_on', { mode: 'string' }).default(sql`CURRENT_TIMESTAMP`).notNull(), + }, + (_table) => [ + pgPolicy('diesel_schema_migrations_policy', { + as: 'permissive', + for: 'all', + to: ['authenticated'], + using: sql`true`, + }), + ], ); export const datasetGroupsPermissions = pgTable( - 'dataset_groups_permissions', - { - id: uuid().defaultRandom().primaryKey().notNull(), - datasetGroupId: uuid('dataset_group_id').notNull(), - permissionId: uuid('permission_id').notNull(), - permissionType: varchar('permission_type').notNull(), - organizationId: uuid('organization_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - index('dataset_groups_permissions_dataset_group_id_idx').using( - 'btree', - table.datasetGroupId.asc().nullsLast().op('uuid_ops') - ), - index('dataset_groups_permissions_organization_id_idx').using( - 'btree', - table.organizationId.asc().nullsLast().op('uuid_ops') - ), - index('dataset_groups_permissions_permission_id_idx').using( - 'btree', - table.permissionId.asc().nullsLast().op('uuid_ops') - ), - foreignKey({ - columns: [table.datasetGroupId], - foreignColumns: [datasetGroups.id], - name: 'dataset_groups_permissions_dataset_group_id_fkey', - }), - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'dataset_groups_permissions_organization_id_fkey', - }), - unique('unique_dataset_group_permission').on( - table.datasetGroupId, - table.permissionId, - table.permissionType - ), - ] + 'dataset_groups_permissions', + { + id: uuid().defaultRandom().primaryKey().notNull(), + datasetGroupId: uuid('dataset_group_id').notNull(), + permissionId: uuid('permission_id').notNull(), + permissionType: varchar('permission_type').notNull(), + organizationId: uuid('organization_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + index('dataset_groups_permissions_dataset_group_id_idx').using( + 'btree', + table.datasetGroupId.asc().nullsLast().op('uuid_ops'), + ), + index('dataset_groups_permissions_organization_id_idx').using( + 'btree', + table.organizationId.asc().nullsLast().op('uuid_ops'), + ), + index('dataset_groups_permissions_permission_id_idx').using( + 'btree', + table.permissionId.asc().nullsLast().op('uuid_ops'), + ), + foreignKey({ + columns: [table.datasetGroupId], + foreignColumns: [datasetGroups.id], + name: 'dataset_groups_permissions_dataset_group_id_fkey', + }), + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'dataset_groups_permissions_organization_id_fkey', + }), + unique('unique_dataset_group_permission').on( + table.datasetGroupId, + table.permissionId, + table.permissionType, + ), + ], ); export const threadsDeprecated = pgTable( - 'threads_deprecated', - { - id: uuid().defaultRandom().primaryKey().notNull(), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), - publiclyEnabledBy: uuid('publicly_enabled_by'), - publicExpiryDate: timestamp('public_expiry_date', { - withTimezone: true, - mode: 'string', - }), - passwordSecretId: uuid('password_secret_id'), - stateMessageId: uuid('state_message_id'), - parentThreadId: uuid('parent_thread_id'), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - organizationId: uuid('organization_id').notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'threads_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'threads_updated_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.publiclyEnabledBy], - foreignColumns: [users.id], - name: 'threads_publicly_enabled_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.parentThreadId], - foreignColumns: [table.id], - name: 'threads_parent_thread_id_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'threads_organization_id_fkey', - }), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'threads_deprecated_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'threads_deprecated_updated_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.publiclyEnabledBy], - foreignColumns: [users.id], - name: 'threads_deprecated_publicly_enabled_by_fkey', - }).onUpdate('cascade'), - ] + 'threads_deprecated', + { + id: uuid().defaultRandom().primaryKey().notNull(), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), + publiclyEnabledBy: uuid('publicly_enabled_by'), + publicExpiryDate: timestamp('public_expiry_date', { + withTimezone: true, + mode: 'string', + }), + passwordSecretId: uuid('password_secret_id'), + stateMessageId: uuid('state_message_id'), + parentThreadId: uuid('parent_thread_id'), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + organizationId: uuid('organization_id').notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'threads_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'threads_updated_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.publiclyEnabledBy], + foreignColumns: [users.id], + name: 'threads_publicly_enabled_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.parentThreadId], + foreignColumns: [table.id], + name: 'threads_parent_thread_id_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'threads_organization_id_fkey', + }), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'threads_deprecated_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'threads_deprecated_updated_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.publiclyEnabledBy], + foreignColumns: [users.id], + name: 'threads_deprecated_publicly_enabled_by_fkey', + }).onUpdate('cascade'), + ], ); export const messagesDeprecated = pgTable( - 'messages_deprecated', - { - id: uuid().defaultRandom().primaryKey().notNull(), - threadId: uuid('thread_id').notNull(), - sentBy: uuid('sent_by').notNull(), - message: text().notNull(), - responses: jsonb(), - code: text(), - context: jsonb(), - title: text(), - feedback: messageFeedbackEnum(), - verification: verificationEnum().default('notRequested').notNull(), - datasetId: uuid('dataset_id'), - chartConfig: jsonb('chart_config').default({}), - chartRecommendations: jsonb('chart_recommendations').default({}), - timeFrame: text('time_frame'), - dataMetadata: jsonb('data_metadata'), - draftSessionId: uuid('draft_session_id'), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - draftState: jsonb('draft_state'), - summaryQuestion: text('summary_question'), - sqlEvaluationId: uuid('sql_evaluation_id'), - }, - (table) => [ - foreignKey({ - columns: [table.sentBy], - foreignColumns: [users.id], - name: 'messages_sent_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.datasetId], - foreignColumns: [datasets.id], - name: 'messages_dataset_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.sentBy], - foreignColumns: [users.id], - name: 'messages_deprecated_sent_by_fkey', - }).onUpdate('cascade'), - ] + 'messages_deprecated', + { + id: uuid().defaultRandom().primaryKey().notNull(), + threadId: uuid('thread_id').notNull(), + sentBy: uuid('sent_by').notNull(), + message: text().notNull(), + responses: jsonb(), + code: text(), + context: jsonb(), + title: text(), + feedback: messageFeedbackEnum(), + verification: verificationEnum().default('notRequested').notNull(), + datasetId: uuid('dataset_id'), + chartConfig: jsonb('chart_config').default({}), + chartRecommendations: jsonb('chart_recommendations').default({}), + timeFrame: text('time_frame'), + dataMetadata: jsonb('data_metadata'), + draftSessionId: uuid('draft_session_id'), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + draftState: jsonb('draft_state'), + summaryQuestion: text('summary_question'), + sqlEvaluationId: uuid('sql_evaluation_id'), + }, + (table) => [ + foreignKey({ + columns: [table.sentBy], + foreignColumns: [users.id], + name: 'messages_sent_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.datasetId], + foreignColumns: [datasets.id], + name: 'messages_dataset_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.sentBy], + foreignColumns: [users.id], + name: 'messages_deprecated_sent_by_fkey', + }).onUpdate('cascade'), + ], ); export const datasets = pgTable( - 'datasets', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: text().notNull(), - databaseName: text('database_name').notNull(), - whenToUse: text('when_to_use'), - whenNotToUse: text('when_not_to_use'), - type: datasetTypeEnum().notNull(), - definition: text().notNull(), - schema: text().notNull(), - enabled: boolean().default(false).notNull(), - imported: boolean().default(false).notNull(), - dataSourceId: uuid('data_source_id').notNull(), - organizationId: uuid('organization_id').notNull(), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - model: text(), - ymlFile: text('yml_file'), - databaseIdentifier: text('database_identifier'), - }, - (table) => [ - foreignKey({ - columns: [table.dataSourceId], - foreignColumns: [dataSources.id], - name: 'datasets_data_source_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'datasets_organization_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'datasets_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'datasets_updated_by_fkey', - }).onUpdate('cascade'), - unique('datasets_database_name_data_source_id_key').on(table.databaseName, table.dataSourceId), - ] + 'datasets', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: text().notNull(), + databaseName: text('database_name').notNull(), + whenToUse: text('when_to_use'), + whenNotToUse: text('when_not_to_use'), + type: datasetTypeEnum().notNull(), + definition: text().notNull(), + schema: text().notNull(), + enabled: boolean().default(false).notNull(), + imported: boolean().default(false).notNull(), + dataSourceId: uuid('data_source_id').notNull(), + organizationId: uuid('organization_id').notNull(), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + model: text(), + ymlFile: text('yml_file'), + databaseIdentifier: text('database_identifier'), + }, + (table) => [ + foreignKey({ + columns: [table.dataSourceId], + foreignColumns: [dataSources.id], + name: 'datasets_data_source_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'datasets_organization_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'datasets_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'datasets_updated_by_fkey', + }).onUpdate('cascade'), + unique('datasets_database_name_data_source_id_key').on(table.databaseName, table.dataSourceId), + ], ); export const users = pgTable( - 'users', - { - id: uuid().defaultRandom().primaryKey().notNull(), - email: text().notNull(), - name: text(), - config: jsonb().default({}).notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - attributes: jsonb().default({}).notNull(), - avatarUrl: text('avatar_url'), - }, - (table) => [unique('users_email_key').on(table.email)] + 'users', + { + id: uuid().defaultRandom().primaryKey().notNull(), + email: text().notNull(), + name: text(), + config: jsonb().default({}).notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + attributes: jsonb().default({}).notNull(), + avatarUrl: text('avatar_url'), + }, + (table) => [unique('users_email_key').on(table.email)], ); export const messages = pgTable( - 'messages', - { - id: uuid().defaultRandom().primaryKey().notNull(), - requestMessage: text('request_message'), - responseMessages: jsonb('response_messages').notNull(), - reasoning: jsonb().notNull(), - title: text().notNull(), - rawLlmMessages: jsonb('raw_llm_messages').notNull(), - finalReasoningMessage: text('final_reasoning_message'), - chatId: uuid('chat_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - createdBy: uuid('created_by').notNull(), - feedback: text(), - isCompleted: boolean('is_completed').default(false).notNull(), - }, - (table) => [ - index('messages_chat_id_idx').using('btree', table.chatId.asc().nullsLast().op('uuid_ops')), - index('messages_created_at_idx').using( - 'btree', - table.createdAt.asc().nullsLast().op('timestamptz_ops') - ), - index('messages_created_by_idx').using( - 'btree', - table.createdBy.asc().nullsLast().op('uuid_ops') - ), - foreignKey({ - columns: [table.chatId], - foreignColumns: [chats.id], - name: 'messages_chat_id_fkey', - }), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'messages_created_by_fkey', - }).onUpdate('cascade'), - ] + 'messages', + { + id: uuid().defaultRandom().primaryKey().notNull(), + requestMessage: text('request_message'), + responseMessages: jsonb('response_messages').notNull(), + reasoning: jsonb().notNull(), + title: text().notNull(), + rawLlmMessages: jsonb('raw_llm_messages').notNull(), + finalReasoningMessage: text('final_reasoning_message'), + chatId: uuid('chat_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + createdBy: uuid('created_by').notNull(), + feedback: text(), + isCompleted: boolean('is_completed').default(false).notNull(), + }, + (table) => [ + index('messages_chat_id_idx').using('btree', table.chatId.asc().nullsLast().op('uuid_ops')), + index('messages_created_at_idx').using( + 'btree', + table.createdAt.asc().nullsLast().op('timestamptz_ops'), + ), + index('messages_created_by_idx').using( + 'btree', + table.createdBy.asc().nullsLast().op('uuid_ops'), + ), + foreignKey({ + columns: [table.chatId], + foreignColumns: [chats.id], + name: 'messages_chat_id_fkey', + }), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'messages_created_by_fkey', + }).onUpdate('cascade'), + ], ); export const messagesToFiles = pgTable( - 'messages_to_files', - { - id: uuid().primaryKey().notNull(), - messageId: uuid('message_id').notNull(), - fileId: uuid('file_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - isDuplicate: boolean('is_duplicate').default(false).notNull(), - versionNumber: integer('version_number').default(1).notNull(), - }, - (table) => [ - index('messages_files_file_id_idx').using( - 'btree', - table.fileId.asc().nullsLast().op('uuid_ops') - ), - index('messages_files_message_id_idx').using( - 'btree', - table.messageId.asc().nullsLast().op('uuid_ops') - ), - foreignKey({ - columns: [table.messageId], - foreignColumns: [messages.id], - name: 'messages_to_files_message_id_fkey', - }), - unique('messages_to_files_message_id_file_id_key').on(table.messageId, table.fileId), - ] + 'messages_to_files', + { + id: uuid().primaryKey().notNull(), + messageId: uuid('message_id').notNull(), + fileId: uuid('file_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + isDuplicate: boolean('is_duplicate').default(false).notNull(), + versionNumber: integer('version_number').default(1).notNull(), + }, + (table) => [ + index('messages_files_file_id_idx').using( + 'btree', + table.fileId.asc().nullsLast().op('uuid_ops'), + ), + index('messages_files_message_id_idx').using( + 'btree', + table.messageId.asc().nullsLast().op('uuid_ops'), + ), + foreignKey({ + columns: [table.messageId], + foreignColumns: [messages.id], + name: 'messages_to_files_message_id_fkey', + }), + unique('messages_to_files_message_id_file_id_key').on(table.messageId, table.fileId), + ], ); export const dashboardFiles = pgTable( - 'dashboard_files', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: varchar().notNull(), - fileName: varchar('file_name').notNull(), - content: jsonb().notNull(), - filter: varchar(), - organizationId: uuid('organization_id').notNull(), - createdBy: uuid('created_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), - publiclyEnabledBy: uuid('publicly_enabled_by'), - publicExpiryDate: timestamp('public_expiry_date', { - withTimezone: true, - mode: 'string', - }), - versionHistory: jsonb('version_history').default({}).notNull(), - publicPassword: text('public_password'), - }, - (table) => [ - index('dashboard_files_created_by_idx').using( - 'btree', - table.createdBy.asc().nullsLast().op('uuid_ops') - ), - index('dashboard_files_deleted_at_idx').using( - 'btree', - table.deletedAt.asc().nullsLast().op('timestamptz_ops') - ), - index('dashboard_files_organization_id_idx').using( - 'btree', - table.organizationId.asc().nullsLast().op('uuid_ops') - ), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'dashboard_files_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.publiclyEnabledBy], - foreignColumns: [users.id], - name: 'dashboard_files_publicly_enabled_by_fkey', - }).onUpdate('cascade'), - ] + 'dashboard_files', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: varchar().notNull(), + fileName: varchar('file_name').notNull(), + content: jsonb().notNull(), + filter: varchar(), + organizationId: uuid('organization_id').notNull(), + createdBy: uuid('created_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), + publiclyEnabledBy: uuid('publicly_enabled_by'), + publicExpiryDate: timestamp('public_expiry_date', { + withTimezone: true, + mode: 'string', + }), + versionHistory: jsonb('version_history').default({}).notNull(), + publicPassword: text('public_password'), + }, + (table) => [ + index('dashboard_files_created_by_idx').using( + 'btree', + table.createdBy.asc().nullsLast().op('uuid_ops'), + ), + index('dashboard_files_deleted_at_idx').using( + 'btree', + table.deletedAt.asc().nullsLast().op('timestamptz_ops'), + ), + index('dashboard_files_organization_id_idx').using( + 'btree', + table.organizationId.asc().nullsLast().op('uuid_ops'), + ), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'dashboard_files_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.publiclyEnabledBy], + foreignColumns: [users.id], + name: 'dashboard_files_publicly_enabled_by_fkey', + }).onUpdate('cascade'), + ], ); export const chats = pgTable( - 'chats', - { - id: uuid().defaultRandom().primaryKey().notNull(), - title: text().notNull(), - organizationId: uuid('organization_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), - publiclyEnabledBy: uuid('publicly_enabled_by'), - publicExpiryDate: timestamp('public_expiry_date', { - withTimezone: true, - mode: 'string', - }), - mostRecentFileId: uuid('most_recent_file_id'), - mostRecentFileType: varchar('most_recent_file_type', { length: 255 }), - mostRecentVersionNumber: integer('most_recent_version_number'), - }, - (table) => [ - index('chats_created_at_idx').using( - 'btree', - table.createdAt.asc().nullsLast().op('timestamptz_ops') - ), - index('chats_created_by_idx').using('btree', table.createdBy.asc().nullsLast().op('uuid_ops')), - index('chats_organization_id_idx').using( - 'btree', - table.organizationId.asc().nullsLast().op('uuid_ops') - ), - index('idx_chats_most_recent_file_id').using( - 'btree', - table.mostRecentFileId.asc().nullsLast().op('uuid_ops') - ), - index('idx_chats_most_recent_file_type').using( - 'btree', - table.mostRecentFileType.asc().nullsLast().op('text_ops') - ), - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'chats_organization_id_fkey', - }), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'chats_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'chats_updated_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.publiclyEnabledBy], - foreignColumns: [users.id], - name: 'chats_publicly_enabled_by_fkey', - }).onUpdate('cascade'), - ] + 'chats', + { + id: uuid().defaultRandom().primaryKey().notNull(), + title: text().notNull(), + organizationId: uuid('organization_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), + publiclyEnabledBy: uuid('publicly_enabled_by'), + publicExpiryDate: timestamp('public_expiry_date', { + withTimezone: true, + mode: 'string', + }), + mostRecentFileId: uuid('most_recent_file_id'), + mostRecentFileType: varchar('most_recent_file_type', { length: 255 }), + mostRecentVersionNumber: integer('most_recent_version_number'), + }, + (table) => [ + index('chats_created_at_idx').using( + 'btree', + table.createdAt.asc().nullsLast().op('timestamptz_ops'), + ), + index('chats_created_by_idx').using('btree', table.createdBy.asc().nullsLast().op('uuid_ops')), + index('chats_organization_id_idx').using( + 'btree', + table.organizationId.asc().nullsLast().op('uuid_ops'), + ), + index('idx_chats_most_recent_file_id').using( + 'btree', + table.mostRecentFileId.asc().nullsLast().op('uuid_ops'), + ), + index('idx_chats_most_recent_file_type').using( + 'btree', + table.mostRecentFileType.asc().nullsLast().op('text_ops'), + ), + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'chats_organization_id_fkey', + }), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'chats_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'chats_updated_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.publiclyEnabledBy], + foreignColumns: [users.id], + name: 'chats_publicly_enabled_by_fkey', + }).onUpdate('cascade'), + ], ); export const organizations = pgTable( - 'organizations', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: text().notNull(), - domain: text(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - paymentRequired: boolean('payment_required').default(false).notNull(), - }, - (table) => [unique('organizations_name_key').on(table.name)] + 'organizations', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: text().notNull(), + domain: text(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + paymentRequired: boolean('payment_required').default(false).notNull(), + }, + (table) => [unique('organizations_name_key').on(table.name)], ); export const storedValuesSyncJobs = pgTable( - 'stored_values_sync_jobs', - { - id: uuid().defaultRandom().primaryKey().notNull(), - dataSourceId: uuid('data_source_id').notNull(), - databaseName: text('database_name').notNull(), - schemaName: text('schema_name').notNull(), - tableName: text('table_name').notNull(), - columnName: text('column_name').notNull(), - lastSyncedAt: timestamp('last_synced_at', { - withTimezone: true, - mode: 'string', - }), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - status: text().notNull(), - errorMessage: text('error_message'), - }, - (table) => [ - index('idx_stored_values_sync_jobs_data_source_id').using( - 'btree', - table.dataSourceId.asc().nullsLast().op('uuid_ops') - ), - index('idx_stored_values_sync_jobs_db_schema_table_column').using( - 'btree', - table.databaseName.asc().nullsLast().op('text_ops'), - table.schemaName.asc().nullsLast().op('text_ops'), - table.tableName.asc().nullsLast().op('text_ops'), - table.columnName.asc().nullsLast().op('text_ops') - ), - index('idx_stored_values_sync_jobs_status').using( - 'btree', - table.status.asc().nullsLast().op('text_ops') - ), - foreignKey({ - columns: [table.dataSourceId], - foreignColumns: [dataSources.id], - name: 'stored_values_sync_jobs_data_source_id_fkey', - }).onDelete('cascade'), - ] + 'stored_values_sync_jobs', + { + id: uuid().defaultRandom().primaryKey().notNull(), + dataSourceId: uuid('data_source_id').notNull(), + databaseName: text('database_name').notNull(), + schemaName: text('schema_name').notNull(), + tableName: text('table_name').notNull(), + columnName: text('column_name').notNull(), + lastSyncedAt: timestamp('last_synced_at', { + withTimezone: true, + mode: 'string', + }), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + status: text().notNull(), + errorMessage: text('error_message'), + }, + (table) => [ + index('idx_stored_values_sync_jobs_data_source_id').using( + 'btree', + table.dataSourceId.asc().nullsLast().op('uuid_ops'), + ), + index('idx_stored_values_sync_jobs_db_schema_table_column').using( + 'btree', + table.databaseName.asc().nullsLast().op('text_ops'), + table.schemaName.asc().nullsLast().op('text_ops'), + table.tableName.asc().nullsLast().op('text_ops'), + table.columnName.asc().nullsLast().op('text_ops'), + ), + index('idx_stored_values_sync_jobs_status').using( + 'btree', + table.status.asc().nullsLast().op('text_ops'), + ), + foreignKey({ + columns: [table.dataSourceId], + foreignColumns: [dataSources.id], + name: 'stored_values_sync_jobs_data_source_id_fkey', + }).onDelete('cascade'), + ], ); export const metricFiles = pgTable( - 'metric_files', - { - id: uuid().defaultRandom().primaryKey().notNull(), - name: varchar().notNull(), - fileName: varchar('file_name').notNull(), - content: jsonb().notNull(), - verification: verificationEnum().default('notRequested').notNull(), - evaluationObj: jsonb('evaluation_obj'), - evaluationSummary: text('evaluation_summary'), - evaluationScore: doublePrecision('evaluation_score'), - organizationId: uuid('organization_id').notNull(), - createdBy: uuid('created_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), - publiclyEnabledBy: uuid('publicly_enabled_by'), - publicExpiryDate: timestamp('public_expiry_date', { - withTimezone: true, - mode: 'string', - }), - versionHistory: jsonb('version_history').default({}).notNull(), - dataMetadata: jsonb('data_metadata'), - publicPassword: text('public_password'), - dataSourceId: uuid('data_source_id').notNull(), - }, - (table) => [ - index('metric_files_created_by_idx').using( - 'btree', - table.createdBy.asc().nullsLast().op('uuid_ops') - ), - index('metric_files_data_metadata_idx').using( - 'gin', - table.dataMetadata.asc().nullsLast().op('jsonb_ops') - ), - index('metric_files_deleted_at_idx').using( - 'btree', - table.deletedAt.asc().nullsLast().op('timestamptz_ops') - ), - index('metric_files_organization_id_idx').using( - 'btree', - table.organizationId.asc().nullsLast().op('uuid_ops') - ), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'metric_files_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.publiclyEnabledBy], - foreignColumns: [users.id], - name: 'metric_files_publicly_enabled_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.dataSourceId], - foreignColumns: [dataSources.id], - name: 'fk_data_source', - }), - ] + 'metric_files', + { + id: uuid().defaultRandom().primaryKey().notNull(), + name: varchar().notNull(), + fileName: varchar('file_name').notNull(), + content: jsonb().notNull(), + verification: verificationEnum().default('notRequested').notNull(), + evaluationObj: jsonb('evaluation_obj'), + evaluationSummary: text('evaluation_summary'), + evaluationScore: doublePrecision('evaluation_score'), + organizationId: uuid('organization_id').notNull(), + createdBy: uuid('created_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + publiclyAccessible: boolean('publicly_accessible').default(false).notNull(), + publiclyEnabledBy: uuid('publicly_enabled_by'), + publicExpiryDate: timestamp('public_expiry_date', { + withTimezone: true, + mode: 'string', + }), + versionHistory: jsonb('version_history').default({}).notNull(), + dataMetadata: jsonb('data_metadata'), + publicPassword: text('public_password'), + dataSourceId: uuid('data_source_id').notNull(), + }, + (table) => [ + index('metric_files_created_by_idx').using( + 'btree', + table.createdBy.asc().nullsLast().op('uuid_ops'), + ), + index('metric_files_data_metadata_idx').using( + 'gin', + table.dataMetadata.asc().nullsLast().op('jsonb_ops'), + ), + index('metric_files_deleted_at_idx').using( + 'btree', + table.deletedAt.asc().nullsLast().op('timestamptz_ops'), + ), + index('metric_files_organization_id_idx').using( + 'btree', + table.organizationId.asc().nullsLast().op('uuid_ops'), + ), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'metric_files_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.publiclyEnabledBy], + foreignColumns: [users.id], + name: 'metric_files_publicly_enabled_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.dataSourceId], + foreignColumns: [dataSources.id], + name: 'fk_data_source', + }), + ], ); export const permissionGroupsToUsers = pgTable( - 'permission_groups_to_users', - { - permissionGroupId: uuid('permission_group_id').notNull(), - userId: uuid('user_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - }, - (table) => [ - index('permission_groups_to_users_user_id_idx').using( - 'btree', - table.userId.asc().nullsLast().op('uuid_ops') - ), - foreignKey({ - columns: [table.permissionGroupId], - foreignColumns: [permissionGroups.id], - name: 'permission_groups_to_users_permission_group_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.userId], - foreignColumns: [users.id], - name: 'permission_groups_to_users_user_id_fkey', - }).onUpdate('cascade'), - primaryKey({ - columns: [table.permissionGroupId, table.userId], - name: 'permission_groups_to_users_pkey', - }), - pgPolicy('permission_groups_to_users_policy', { - as: 'permissive', - for: 'all', - to: ['authenticated'], - using: sql`true`, - }), - ] + 'permission_groups_to_users', + { + permissionGroupId: uuid('permission_group_id').notNull(), + userId: uuid('user_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + }, + (table) => [ + index('permission_groups_to_users_user_id_idx').using( + 'btree', + table.userId.asc().nullsLast().op('uuid_ops'), + ), + foreignKey({ + columns: [table.permissionGroupId], + foreignColumns: [permissionGroups.id], + name: 'permission_groups_to_users_permission_group_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.userId], + foreignColumns: [users.id], + name: 'permission_groups_to_users_user_id_fkey', + }).onUpdate('cascade'), + primaryKey({ + columns: [table.permissionGroupId, table.userId], + name: 'permission_groups_to_users_pkey', + }), + pgPolicy('permission_groups_to_users_policy', { + as: 'permissive', + for: 'all', + to: ['authenticated'], + using: sql`true`, + }), + ], ); export const entityRelationship = pgTable( - 'entity_relationship', - { - primaryDatasetId: uuid('primary_dataset_id').notNull(), - foreignDatasetId: uuid('foreign_dataset_id').notNull(), - relationshipType: text('relationship_type').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - }, - (table) => [ - primaryKey({ - columns: [table.primaryDatasetId, table.foreignDatasetId], - name: 'entity_relationship_pkey', - }), - ] + 'entity_relationship', + { + primaryDatasetId: uuid('primary_dataset_id').notNull(), + foreignDatasetId: uuid('foreign_dataset_id').notNull(), + relationshipType: text('relationship_type').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + }, + (table) => [ + primaryKey({ + columns: [table.primaryDatasetId, table.foreignDatasetId], + name: 'entity_relationship_pkey', + }), + ], ); export const metricFilesToDatasets = pgTable( - 'metric_files_to_datasets', - { - metricFileId: uuid('metric_file_id').notNull(), - datasetId: uuid('dataset_id').notNull(), - metricVersionNumber: integer('metric_version_number').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.metricFileId], - foreignColumns: [metricFiles.id], - name: 'fk_metric_file', - }).onDelete('cascade'), - foreignKey({ - columns: [table.datasetId], - foreignColumns: [datasets.id], - name: 'fk_dataset', - }).onDelete('cascade'), - primaryKey({ - columns: [table.metricFileId, table.datasetId, table.metricVersionNumber], - name: 'metric_files_to_datasets_pkey', - }), - ] + 'metric_files_to_datasets', + { + metricFileId: uuid('metric_file_id').notNull(), + datasetId: uuid('dataset_id').notNull(), + metricVersionNumber: integer('metric_version_number').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.metricFileId], + foreignColumns: [metricFiles.id], + name: 'fk_metric_file', + }).onDelete('cascade'), + foreignKey({ + columns: [table.datasetId], + foreignColumns: [datasets.id], + name: 'fk_dataset', + }).onDelete('cascade'), + primaryKey({ + columns: [table.metricFileId, table.datasetId, table.metricVersionNumber], + name: 'metric_files_to_datasets_pkey', + }), + ], ); export const termsToDatasets = pgTable( - 'terms_to_datasets', - { - termId: uuid('term_id').notNull(), - datasetId: uuid('dataset_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.termId], - foreignColumns: [terms.id], - name: 'terms_to_datasets_term_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.datasetId], - foreignColumns: [datasets.id], - name: 'terms_to_datasets_dataset_id_fkey', - }).onDelete('cascade'), - primaryKey({ - columns: [table.termId, table.datasetId], - name: 'terms_to_datasets_pkey', - }), - ] + 'terms_to_datasets', + { + termId: uuid('term_id').notNull(), + datasetId: uuid('dataset_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.termId], + foreignColumns: [terms.id], + name: 'terms_to_datasets_term_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.datasetId], + foreignColumns: [datasets.id], + name: 'terms_to_datasets_dataset_id_fkey', + }).onDelete('cascade'), + primaryKey({ + columns: [table.termId, table.datasetId], + name: 'terms_to_datasets_pkey', + }), + ], ); export const datasetsToPermissionGroups = pgTable( - 'datasets_to_permission_groups', - { - datasetId: uuid('dataset_id').notNull(), - permissionGroupId: uuid('permission_group_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.datasetId], - foreignColumns: [datasets.id], - name: 'datasets_to_permission_groups_dataset_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.permissionGroupId], - foreignColumns: [permissionGroups.id], - name: 'datasets_to_permission_groups_permission_group_id_fkey', - }).onDelete('cascade'), - primaryKey({ - columns: [table.datasetId, table.permissionGroupId], - name: 'datasets_to_permission_groups_pkey', - }), - pgPolicy('datasets_to_permission_groups_policy', { - as: 'permissive', - for: 'all', - to: ['authenticated'], - using: sql`true`, - }), - ] + 'datasets_to_permission_groups', + { + datasetId: uuid('dataset_id').notNull(), + permissionGroupId: uuid('permission_group_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.datasetId], + foreignColumns: [datasets.id], + name: 'datasets_to_permission_groups_dataset_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.permissionGroupId], + foreignColumns: [permissionGroups.id], + name: 'datasets_to_permission_groups_permission_group_id_fkey', + }).onDelete('cascade'), + primaryKey({ + columns: [table.datasetId, table.permissionGroupId], + name: 'datasets_to_permission_groups_pkey', + }), + pgPolicy('datasets_to_permission_groups_policy', { + as: 'permissive', + for: 'all', + to: ['authenticated'], + using: sql`true`, + }), + ], ); export const datasetsToDatasetGroups = pgTable( - 'datasets_to_dataset_groups', - { - datasetId: uuid('dataset_id').notNull(), - datasetGroupId: uuid('dataset_group_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - index('datasets_to_dataset_groups_dataset_group_id_idx').using( - 'btree', - table.datasetGroupId.asc().nullsLast().op('uuid_ops') - ), - foreignKey({ - columns: [table.datasetId], - foreignColumns: [datasets.id], - name: 'datasets_to_dataset_groups_dataset_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.datasetGroupId], - foreignColumns: [datasetGroups.id], - name: 'datasets_to_dataset_groups_dataset_group_id_fkey', - }).onDelete('cascade'), - primaryKey({ - columns: [table.datasetId, table.datasetGroupId], - name: 'datasets_to_dataset_groups_pkey', - }), - pgPolicy('datasets_to_dataset_groups_policy', { - as: 'permissive', - for: 'all', - to: ['authenticated'], - using: sql`true`, - }), - ] + 'datasets_to_dataset_groups', + { + datasetId: uuid('dataset_id').notNull(), + datasetGroupId: uuid('dataset_group_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + index('datasets_to_dataset_groups_dataset_group_id_idx').using( + 'btree', + table.datasetGroupId.asc().nullsLast().op('uuid_ops'), + ), + foreignKey({ + columns: [table.datasetId], + foreignColumns: [datasets.id], + name: 'datasets_to_dataset_groups_dataset_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.datasetGroupId], + foreignColumns: [datasetGroups.id], + name: 'datasets_to_dataset_groups_dataset_group_id_fkey', + }).onDelete('cascade'), + primaryKey({ + columns: [table.datasetId, table.datasetGroupId], + name: 'datasets_to_dataset_groups_pkey', + }), + pgPolicy('datasets_to_dataset_groups_policy', { + as: 'permissive', + for: 'all', + to: ['authenticated'], + using: sql`true`, + }), + ], ); export const threadsToDashboards = pgTable( - 'threads_to_dashboards', - { - threadId: uuid('thread_id').notNull(), - dashboardId: uuid('dashboard_id').notNull(), - addedBy: uuid('added_by').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.threadId], - foreignColumns: [threadsDeprecated.id], - name: 'threads_to_dashboards_thread_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.dashboardId], - foreignColumns: [dashboards.id], - name: 'threads_to_dashboards_dashboard_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.addedBy], - foreignColumns: [users.id], - name: 'threads_to_dashboards_added_by_fkey', - }).onUpdate('cascade'), - primaryKey({ - columns: [table.threadId, table.dashboardId], - name: 'threads_to_dashboards_pkey', - }), - ] + 'threads_to_dashboards', + { + threadId: uuid('thread_id').notNull(), + dashboardId: uuid('dashboard_id').notNull(), + addedBy: uuid('added_by').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.threadId], + foreignColumns: [threadsDeprecated.id], + name: 'threads_to_dashboards_thread_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.dashboardId], + foreignColumns: [dashboards.id], + name: 'threads_to_dashboards_dashboard_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.addedBy], + foreignColumns: [users.id], + name: 'threads_to_dashboards_added_by_fkey', + }).onUpdate('cascade'), + primaryKey({ + columns: [table.threadId, table.dashboardId], + name: 'threads_to_dashboards_pkey', + }), + ], ); export const userFavorites = pgTable( - 'user_favorites', - { - userId: uuid('user_id').notNull(), - assetId: uuid('asset_id').notNull(), - assetType: assetTypeEnum('asset_type').notNull(), - orderIndex: integer('order_index').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.userId], - foreignColumns: [users.id], - name: 'user_favorites_user_id_fkey', - }).onUpdate('cascade'), - primaryKey({ - columns: [table.userId, table.assetId, table.assetType], - name: 'user_favorites_pkey', - }), - ] + 'user_favorites', + { + userId: uuid('user_id').notNull(), + assetId: uuid('asset_id').notNull(), + assetType: assetTypeEnum('asset_type').notNull(), + orderIndex: integer('order_index').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.userId], + foreignColumns: [users.id], + name: 'user_favorites_user_id_fkey', + }).onUpdate('cascade'), + primaryKey({ + columns: [table.userId, table.assetId, table.assetType], + name: 'user_favorites_pkey', + }), + ], ); export const teamsToUsers = pgTable( - 'teams_to_users', - { - teamId: uuid('team_id').notNull(), - userId: uuid('user_id').notNull(), - role: teamRoleEnum().default('member').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.teamId], - foreignColumns: [teams.id], - name: 'teams_to_users_team_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.userId], - foreignColumns: [users.id], - name: 'teams_to_users_user_id_fkey', - }).onUpdate('cascade'), - primaryKey({ - columns: [table.teamId, table.userId], - name: 'teams_to_users_pkey', - }), - ] + 'teams_to_users', + { + teamId: uuid('team_id').notNull(), + userId: uuid('user_id').notNull(), + role: teamRoleEnum().default('member').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.teamId], + foreignColumns: [teams.id], + name: 'teams_to_users_team_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.userId], + foreignColumns: [users.id], + name: 'teams_to_users_user_id_fkey', + }).onUpdate('cascade'), + primaryKey({ + columns: [table.teamId, table.userId], + name: 'teams_to_users_pkey', + }), + ], ); export const metricFilesToDashboardFiles = pgTable( - 'metric_files_to_dashboard_files', - { - metricFileId: uuid('metric_file_id').notNull(), - dashboardFileId: uuid('dashboard_file_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .default(sql`CURRENT_TIMESTAMP`) - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - createdBy: uuid('created_by').notNull(), - }, - (table) => [ - index('metric_files_to_dashboard_files_dashboard_id_idx').using( - 'btree', - table.dashboardFileId.asc().nullsLast().op('uuid_ops') - ), - index('metric_files_to_dashboard_files_deleted_at_idx').using( - 'btree', - table.deletedAt.asc().nullsLast().op('timestamptz_ops') - ), - index('metric_files_to_dashboard_files_metric_id_idx').using( - 'btree', - table.metricFileId.asc().nullsLast().op('uuid_ops') - ), - foreignKey({ - columns: [table.metricFileId], - foreignColumns: [metricFiles.id], - name: 'metric_files_to_dashboard_files_metric_file_id_fkey', - }), - foreignKey({ - columns: [table.dashboardFileId], - foreignColumns: [dashboardFiles.id], - name: 'metric_files_to_dashboard_files_dashboard_file_id_fkey', - }), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'metric_files_to_dashboard_files_created_by_fkey', - }).onUpdate('cascade'), - primaryKey({ - columns: [table.metricFileId, table.dashboardFileId], - name: 'metric_files_to_dashboard_files_pkey', - }), - ] + 'metric_files_to_dashboard_files', + { + metricFileId: uuid('metric_file_id').notNull(), + dashboardFileId: uuid('dashboard_file_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + createdBy: uuid('created_by').notNull(), + }, + (table) => [ + index('metric_files_to_dashboard_files_dashboard_id_idx').using( + 'btree', + table.dashboardFileId.asc().nullsLast().op('uuid_ops'), + ), + index('metric_files_to_dashboard_files_deleted_at_idx').using( + 'btree', + table.deletedAt.asc().nullsLast().op('timestamptz_ops'), + ), + index('metric_files_to_dashboard_files_metric_id_idx').using( + 'btree', + table.metricFileId.asc().nullsLast().op('uuid_ops'), + ), + foreignKey({ + columns: [table.metricFileId], + foreignColumns: [metricFiles.id], + name: 'metric_files_to_dashboard_files_metric_file_id_fkey', + }), + foreignKey({ + columns: [table.dashboardFileId], + foreignColumns: [dashboardFiles.id], + name: 'metric_files_to_dashboard_files_dashboard_file_id_fkey', + }), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'metric_files_to_dashboard_files_created_by_fkey', + }).onUpdate('cascade'), + primaryKey({ + columns: [table.metricFileId, table.dashboardFileId], + name: 'metric_files_to_dashboard_files_pkey', + }), + ], ); export const collectionsToAssets = pgTable( - 'collections_to_assets', - { - collectionId: uuid('collection_id').notNull(), - assetId: uuid('asset_id').notNull(), - assetType: assetTypeEnum('asset_type').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'collections_to_assets_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'collections_to_assets_updated_by_fkey', - }).onUpdate('cascade'), - primaryKey({ - columns: [table.collectionId, table.assetId, table.assetType], - name: 'collections_to_assets_pkey', - }), - ] + 'collections_to_assets', + { + collectionId: uuid('collection_id').notNull(), + assetId: uuid('asset_id').notNull(), + assetType: assetTypeEnum('asset_type').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'collections_to_assets_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'collections_to_assets_updated_by_fkey', + }).onUpdate('cascade'), + primaryKey({ + columns: [table.collectionId, table.assetId, table.assetType], + name: 'collections_to_assets_pkey', + }), + ], ); export const permissionGroupsToIdentities = pgTable( - 'permission_groups_to_identities', - { - permissionGroupId: uuid('permission_group_id').notNull(), - identityId: uuid('identity_id').notNull(), - identityType: identityTypeEnum('identity_type').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'permission_groups_to_identities_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'permission_groups_to_identities_updated_by_fkey', - }).onUpdate('cascade'), - primaryKey({ - columns: [table.permissionGroupId, table.identityId, table.identityType], - name: 'permission_groups_to_identities_pkey', - }), - ] + 'permission_groups_to_identities', + { + permissionGroupId: uuid('permission_group_id').notNull(), + identityId: uuid('identity_id').notNull(), + identityType: identityTypeEnum('identity_type').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'permission_groups_to_identities_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'permission_groups_to_identities_updated_by_fkey', + }).onUpdate('cascade'), + primaryKey({ + columns: [table.permissionGroupId, table.identityId, table.identityType], + name: 'permission_groups_to_identities_pkey', + }), + ], ); export const assetPermissions = pgTable( - 'asset_permissions', - { - identityId: uuid('identity_id').notNull(), - identityType: identityTypeEnum('identity_type').notNull(), - assetId: uuid('asset_id').notNull(), - assetType: assetTypeEnum('asset_type').notNull(), - role: assetPermissionRoleEnum().notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'asset_permissions_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'asset_permissions_updated_by_fkey', - }).onUpdate('cascade'), - primaryKey({ - columns: [table.identityId, table.identityType, table.assetId, table.assetType], - name: 'asset_permissions_pkey', - }), - ] + 'asset_permissions', + { + identityId: uuid('identity_id').notNull(), + identityType: identityTypeEnum('identity_type').notNull(), + assetId: uuid('asset_id').notNull(), + assetType: assetTypeEnum('asset_type').notNull(), + role: assetPermissionRoleEnum().notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'asset_permissions_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'asset_permissions_updated_by_fkey', + }).onUpdate('cascade'), + primaryKey({ + columns: [table.identityId, table.identityType, table.assetId, table.assetType], + name: 'asset_permissions_pkey', + }), + ], ); export const usersToOrganizations = pgTable( - 'users_to_organizations', - { - userId: uuid('user_id').notNull(), - organizationId: uuid('organization_id').notNull(), - role: userOrganizationRoleEnum().default('querier').notNull(), - sharingSetting: sharingSettingEnum('sharing_setting').default('none').notNull(), - editSql: boolean('edit_sql').default(false).notNull(), - uploadCsv: boolean('upload_csv').default(false).notNull(), - exportAssets: boolean('export_assets').default(false).notNull(), - emailSlackEnabled: boolean('email_slack_enabled').default(false).notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - createdBy: uuid('created_by').notNull(), - updatedBy: uuid('updated_by').notNull(), - deletedBy: uuid('deleted_by'), - status: userOrganizationStatusEnum().default('active').notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'users_to_organizations_organization_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.userId], - foreignColumns: [users.id], - name: 'users_to_organizations_user_id_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.createdBy], - foreignColumns: [users.id], - name: 'users_to_organizations_created_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.updatedBy], - foreignColumns: [users.id], - name: 'users_to_organizations_updated_by_fkey', - }).onUpdate('cascade'), - foreignKey({ - columns: [table.deletedBy], - foreignColumns: [users.id], - name: 'users_to_organizations_deleted_by_fkey', - }).onUpdate('cascade'), - primaryKey({ - columns: [table.userId, table.organizationId], - name: 'users_to_organizations_pkey', - }), - ] + 'users_to_organizations', + { + userId: uuid('user_id').notNull(), + organizationId: uuid('organization_id').notNull(), + role: userOrganizationRoleEnum().default('querier').notNull(), + sharingSetting: sharingSettingEnum('sharing_setting').default('none').notNull(), + editSql: boolean('edit_sql').default(false).notNull(), + uploadCsv: boolean('upload_csv').default(false).notNull(), + exportAssets: boolean('export_assets').default(false).notNull(), + emailSlackEnabled: boolean('email_slack_enabled').default(false).notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + createdBy: uuid('created_by').notNull(), + updatedBy: uuid('updated_by').notNull(), + deletedBy: uuid('deleted_by'), + status: userOrganizationStatusEnum().default('active').notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'users_to_organizations_organization_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.userId], + foreignColumns: [users.id], + name: 'users_to_organizations_user_id_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.createdBy], + foreignColumns: [users.id], + name: 'users_to_organizations_created_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.updatedBy], + foreignColumns: [users.id], + name: 'users_to_organizations_updated_by_fkey', + }).onUpdate('cascade'), + foreignKey({ + columns: [table.deletedBy], + foreignColumns: [users.id], + name: 'users_to_organizations_deleted_by_fkey', + }).onUpdate('cascade'), + primaryKey({ + columns: [table.userId, table.organizationId], + name: 'users_to_organizations_pkey', + }), + ], ); export const databaseMetadata = pgTable( - 'database_metadata', - { - id: uuid().defaultRandom().primaryKey().notNull(), - dataSourceId: uuid('data_source_id').notNull(), - name: text().notNull(), - owner: text(), - comment: text(), - created: timestamp({ withTimezone: true, mode: 'string' }), - lastModified: timestamp('last_modified', { - withTimezone: true, - mode: 'string', - }), - metadata: jsonb().default({}).notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.dataSourceId], - foreignColumns: [dataSources.id], - name: 'database_metadata_data_source_id_fkey', - }).onDelete('cascade'), - unique('database_metadata_data_source_id_name_key').on(table.dataSourceId, table.name), - index('database_metadata_data_source_id_idx').using( - 'btree', - table.dataSourceId.asc().nullsLast().op('uuid_ops') - ), - ] + 'database_metadata', + { + id: uuid().defaultRandom().primaryKey().notNull(), + dataSourceId: uuid('data_source_id').notNull(), + name: text().notNull(), + owner: text(), + comment: text(), + created: timestamp({ withTimezone: true, mode: 'string' }), + lastModified: timestamp('last_modified', { + withTimezone: true, + mode: 'string', + }), + metadata: jsonb().default({}).notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.dataSourceId], + foreignColumns: [dataSources.id], + name: 'database_metadata_data_source_id_fkey', + }).onDelete('cascade'), + unique('database_metadata_data_source_id_name_key').on(table.dataSourceId, table.name), + index('database_metadata_data_source_id_idx').using( + 'btree', + table.dataSourceId.asc().nullsLast().op('uuid_ops'), + ), + ], ); export const schemaMetadata = pgTable( - 'schema_metadata', - { - id: uuid().defaultRandom().primaryKey().notNull(), - dataSourceId: uuid('data_source_id').notNull(), - databaseId: uuid('database_id'), // Optional for MySQL - name: text().notNull(), - databaseName: text('database_name').notNull(), - owner: text(), - comment: text(), - created: timestamp({ withTimezone: true, mode: 'string' }), - lastModified: timestamp('last_modified', { - withTimezone: true, - mode: 'string', - }), - metadata: jsonb().default({}).notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.dataSourceId], - foreignColumns: [dataSources.id], - name: 'schema_metadata_data_source_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.databaseId], - foreignColumns: [databaseMetadata.id], - name: 'schema_metadata_database_id_fkey', - }).onDelete('cascade'), - unique('schema_metadata_data_source_id_database_id_name_key').on( - table.dataSourceId, - table.databaseId, - table.name - ), - index('schema_metadata_data_source_id_idx').using( - 'btree', - table.dataSourceId.asc().nullsLast().op('uuid_ops') - ), - index('schema_metadata_database_id_idx').using( - 'btree', - table.databaseId.asc().nullsLast().op('uuid_ops') - ), - ] + 'schema_metadata', + { + id: uuid().defaultRandom().primaryKey().notNull(), + dataSourceId: uuid('data_source_id').notNull(), + databaseId: uuid('database_id'), // Optional for MySQL + name: text().notNull(), + databaseName: text('database_name').notNull(), + owner: text(), + comment: text(), + created: timestamp({ withTimezone: true, mode: 'string' }), + lastModified: timestamp('last_modified', { + withTimezone: true, + mode: 'string', + }), + metadata: jsonb().default({}).notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.dataSourceId], + foreignColumns: [dataSources.id], + name: 'schema_metadata_data_source_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.databaseId], + foreignColumns: [databaseMetadata.id], + name: 'schema_metadata_database_id_fkey', + }).onDelete('cascade'), + unique('schema_metadata_data_source_id_database_id_name_key').on( + table.dataSourceId, + table.databaseId, + table.name, + ), + index('schema_metadata_data_source_id_idx').using( + 'btree', + table.dataSourceId.asc().nullsLast().op('uuid_ops'), + ), + index('schema_metadata_database_id_idx').using( + 'btree', + table.databaseId.asc().nullsLast().op('uuid_ops'), + ), + ], ); export const tableMetadata = pgTable( - 'table_metadata', - { - id: uuid().defaultRandom().primaryKey().notNull(), - dataSourceId: uuid('data_source_id').notNull(), - databaseId: uuid('database_id'), // Optional for some databases - schemaId: uuid('schema_id').notNull(), - name: text().notNull(), - schemaName: text('schema_name').notNull(), - databaseName: text('database_name').notNull(), - type: tableTypeEnum().notNull(), - rowCount: bigint('row_count', { mode: 'number' }), - sizeBytes: bigint('size_bytes', { mode: 'number' }), - comment: text(), - created: timestamp({ withTimezone: true, mode: 'string' }), - lastModified: timestamp('last_modified', { - withTimezone: true, - mode: 'string', - }), - clusteringKeys: jsonb('clustering_keys').default([]).notNull(), - columns: jsonb().default([]).notNull(), // Array of Column objects - metadata: jsonb().default({}).notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.dataSourceId], - foreignColumns: [dataSources.id], - name: 'table_metadata_data_source_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.databaseId], - foreignColumns: [databaseMetadata.id], - name: 'table_metadata_database_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.schemaId], - foreignColumns: [schemaMetadata.id], - name: 'table_metadata_schema_id_fkey', - }).onDelete('cascade'), - unique('table_metadata_data_source_id_schema_id_name_key').on( - table.dataSourceId, - table.schemaId, - table.name - ), - index('table_metadata_data_source_id_idx').using( - 'btree', - table.dataSourceId.asc().nullsLast().op('uuid_ops') - ), - index('table_metadata_database_id_idx').using( - 'btree', - table.databaseId.asc().nullsLast().op('uuid_ops') - ), - index('table_metadata_schema_id_idx').using( - 'btree', - table.schemaId.asc().nullsLast().op('uuid_ops') - ), - ] + 'table_metadata', + { + id: uuid().defaultRandom().primaryKey().notNull(), + dataSourceId: uuid('data_source_id').notNull(), + databaseId: uuid('database_id'), // Optional for some databases + schemaId: uuid('schema_id').notNull(), + name: text().notNull(), + schemaName: text('schema_name').notNull(), + databaseName: text('database_name').notNull(), + type: tableTypeEnum().notNull(), + rowCount: bigint('row_count', { mode: 'number' }), + sizeBytes: bigint('size_bytes', { mode: 'number' }), + comment: text(), + created: timestamp({ withTimezone: true, mode: 'string' }), + lastModified: timestamp('last_modified', { + withTimezone: true, + mode: 'string', + }), + clusteringKeys: jsonb('clustering_keys').default([]).notNull(), + columns: jsonb().default([]).notNull(), // Array of Column objects + metadata: jsonb().default({}).notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.dataSourceId], + foreignColumns: [dataSources.id], + name: 'table_metadata_data_source_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.databaseId], + foreignColumns: [databaseMetadata.id], + name: 'table_metadata_database_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.schemaId], + foreignColumns: [schemaMetadata.id], + name: 'table_metadata_schema_id_fkey', + }).onDelete('cascade'), + unique('table_metadata_data_source_id_schema_id_name_key').on( + table.dataSourceId, + table.schemaId, + table.name, + ), + index('table_metadata_data_source_id_idx').using( + 'btree', + table.dataSourceId.asc().nullsLast().op('uuid_ops'), + ), + index('table_metadata_database_id_idx').using( + 'btree', + table.databaseId.asc().nullsLast().op('uuid_ops'), + ), + index('table_metadata_schema_id_idx').using( + 'btree', + table.schemaId.asc().nullsLast().op('uuid_ops'), + ), + ], ); // Slack integrations table export const slackIntegrations = pgTable( - 'slack_integrations', - { - id: uuid().defaultRandom().primaryKey().notNull(), - organizationId: uuid('organization_id').notNull(), - userId: uuid('user_id').notNull(), + 'slack_integrations', + { + id: uuid().defaultRandom().primaryKey().notNull(), + organizationId: uuid('organization_id').notNull(), + userId: uuid('user_id').notNull(), - // OAuth state fields (for pending integrations) - oauthState: varchar('oauth_state', { length: 255 }).unique(), - oauthExpiresAt: timestamp('oauth_expires_at', { withTimezone: true, mode: 'string' }), - oauthMetadata: jsonb('oauth_metadata').default({}), + // OAuth state fields (for pending integrations) + oauthState: varchar('oauth_state', { length: 255 }).unique(), + oauthExpiresAt: timestamp('oauth_expires_at', { withTimezone: true, mode: 'string' }), + oauthMetadata: jsonb('oauth_metadata').default({}), - // Slack workspace info (populated after successful OAuth) - teamId: varchar('team_id', { length: 255 }), - teamName: varchar('team_name', { length: 255 }), - teamDomain: varchar('team_domain', { length: 255 }), - enterpriseId: varchar('enterprise_id', { length: 255 }), + // Slack workspace info (populated after successful OAuth) + teamId: varchar('team_id', { length: 255 }), + teamName: varchar('team_name', { length: 255 }), + teamDomain: varchar('team_domain', { length: 255 }), + enterpriseId: varchar('enterprise_id', { length: 255 }), - // Bot info - botUserId: varchar('bot_user_id', { length: 255 }), - scope: text(), + // Bot info + botUserId: varchar('bot_user_id', { length: 255 }), + scope: text(), - // Token reference (actual token in Supabase Vault) - tokenVaultKey: varchar('token_vault_key', { length: 255 }).unique(), + // Token reference (actual token in Supabase Vault) + tokenVaultKey: varchar('token_vault_key', { length: 255 }).unique(), - // Metadata - installedBySlackUserId: varchar('installed_by_slack_user_id', { length: 255 }), - installedAt: timestamp('installed_at', { withTimezone: true, mode: 'string' }), - lastUsedAt: timestamp('last_used_at', { withTimezone: true, mode: 'string' }), - status: slackIntegrationStatusEnum().default('pending').notNull(), + // Metadata + installedBySlackUserId: varchar('installed_by_slack_user_id', { length: 255 }), + installedAt: timestamp('installed_at', { withTimezone: true, mode: 'string' }), + lastUsedAt: timestamp('last_used_at', { withTimezone: true, mode: 'string' }), + status: slackIntegrationStatusEnum().default('pending').notNull(), - // Timestamps - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - foreignKey({ - columns: [table.organizationId], - foreignColumns: [organizations.id], - name: 'slack_integrations_organization_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.userId], - foreignColumns: [users.id], - name: 'slack_integrations_user_id_fkey', - }), - unique('slack_integrations_org_team_key').on(table.organizationId, table.teamId), - index('idx_slack_integrations_org_id').using( - 'btree', - table.organizationId.asc().nullsLast().op('uuid_ops') - ), - index('idx_slack_integrations_team_id').using( - 'btree', - table.teamId.asc().nullsLast().op('text_ops') - ), - index('idx_slack_integrations_oauth_state').using( - 'btree', - table.oauthState.asc().nullsLast().op('text_ops') - ), - index('idx_slack_integrations_oauth_expires').using( - 'btree', - table.oauthExpiresAt.asc().nullsLast().op('timestamptz_ops') - ), - check( - 'slack_integrations_status_check', - sql`(status = 'pending' AND oauth_state IS NOT NULL) OR (status != 'pending' AND team_id IS NOT NULL)` - ), - ] + // Default channel configuration + defaultChannel: jsonb('default_channel').default({}), + + // Timestamps + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + foreignKey({ + columns: [table.organizationId], + foreignColumns: [organizations.id], + name: 'slack_integrations_organization_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.userId], + foreignColumns: [users.id], + name: 'slack_integrations_user_id_fkey', + }), + unique('slack_integrations_org_team_key').on(table.organizationId, table.teamId), + index('idx_slack_integrations_org_id').using( + 'btree', + table.organizationId.asc().nullsLast().op('uuid_ops'), + ), + index('idx_slack_integrations_team_id').using( + 'btree', + table.teamId.asc().nullsLast().op('text_ops'), + ), + index('idx_slack_integrations_oauth_state').using( + 'btree', + table.oauthState.asc().nullsLast().op('text_ops'), + ), + index('idx_slack_integrations_oauth_expires').using( + 'btree', + table.oauthExpiresAt.asc().nullsLast().op('timestamptz_ops'), + ), + check( + 'slack_integrations_status_check', + sql`(status = 'pending' AND oauth_state IS NOT NULL) OR (status != 'pending' AND team_id IS NOT NULL)`, + ), + ], ); // Slack message tracking table (optional) export const slackMessageTracking = pgTable( - 'slack_message_tracking', - { - id: uuid().defaultRandom().primaryKey().notNull(), - integrationId: uuid('integration_id').notNull(), + 'slack_message_tracking', + { + id: uuid().defaultRandom().primaryKey().notNull(), + integrationId: uuid('integration_id').notNull(), - // Internal reference - internalMessageId: uuid('internal_message_id').notNull().unique(), + // Internal reference + internalMessageId: uuid('internal_message_id').notNull().unique(), - // Slack references - slackChannelId: varchar('slack_channel_id', { length: 255 }).notNull(), - slackMessageTs: varchar('slack_message_ts', { length: 255 }).notNull(), - slackThreadTs: varchar('slack_thread_ts', { length: 255 }), + // Slack references + slackChannelId: varchar('slack_channel_id', { length: 255 }).notNull(), + slackMessageTs: varchar('slack_message_ts', { length: 255 }).notNull(), + slackThreadTs: varchar('slack_thread_ts', { length: 255 }), - // Metadata - messageType: varchar('message_type', { length: 50 }).notNull(), // 'message', 'reply', 'update' - content: text(), - senderInfo: jsonb('sender_info'), + // Metadata + messageType: varchar('message_type', { length: 50 }).notNull(), // 'message', 'reply', 'update' + content: text(), + senderInfo: jsonb('sender_info'), - // Timestamps - sentAt: timestamp('sent_at', { withTimezone: true, mode: 'string' }).notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - }, - (table) => [ - foreignKey({ - columns: [table.integrationId], - foreignColumns: [slackIntegrations.id], - name: 'slack_message_tracking_integration_id_fkey', - }).onDelete('cascade'), - index('idx_message_tracking_integration').using( - 'btree', - table.integrationId.asc().nullsLast().op('uuid_ops') - ), - index('idx_message_tracking_channel').using( - 'btree', - table.slackChannelId.asc().nullsLast().op('text_ops') - ), - index('idx_message_tracking_thread').using( - 'btree', - table.slackThreadTs.asc().nullsLast().op('text_ops') - ), - ] + // Timestamps + sentAt: timestamp('sent_at', { withTimezone: true, mode: 'string' }).notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + }, + (table) => [ + foreignKey({ + columns: [table.integrationId], + foreignColumns: [slackIntegrations.id], + name: 'slack_message_tracking_integration_id_fkey', + }).onDelete('cascade'), + index('idx_message_tracking_integration').using( + 'btree', + table.integrationId.asc().nullsLast().op('uuid_ops'), + ), + index('idx_message_tracking_channel').using( + 'btree', + table.slackChannelId.asc().nullsLast().op('text_ops'), + ), + index('idx_message_tracking_thread').using( + 'btree', + table.slackThreadTs.asc().nullsLast().op('text_ops'), + ), + ], ); // Join table between messages and slack messages export const messagesToSlackMessages = pgTable( - 'messages_to_slack_messages', - { - messageId: uuid('message_id').notNull(), - slackMessageId: uuid('slack_message_id').notNull(), - createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) - .defaultNow() - .notNull(), - deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), - }, - (table) => [ - // Foreign keys - foreignKey({ - columns: [table.messageId], - foreignColumns: [messages.id], - name: 'messages_to_slack_messages_message_id_fkey', - }).onDelete('cascade'), - foreignKey({ - columns: [table.slackMessageId], - foreignColumns: [slackMessageTracking.id], - name: 'messages_to_slack_messages_slack_message_id_fkey', - }).onDelete('cascade'), - // Composite primary key - primaryKey({ - columns: [table.messageId, table.slackMessageId], - name: 'messages_to_slack_messages_pkey', - }), - // Indexes for query performance - index('messages_to_slack_messages_message_id_idx').using( - 'btree', - table.messageId.asc().nullsLast().op('uuid_ops') - ), - index('messages_to_slack_messages_slack_message_id_idx').using( - 'btree', - table.slackMessageId.asc().nullsLast().op('uuid_ops') - ), - ] + 'messages_to_slack_messages', + { + messageId: uuid('message_id').notNull(), + slackMessageId: uuid('slack_message_id').notNull(), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' }) + .defaultNow() + .notNull(), + deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }), + }, + (table) => [ + // Foreign keys + foreignKey({ + columns: [table.messageId], + foreignColumns: [messages.id], + name: 'messages_to_slack_messages_message_id_fkey', + }).onDelete('cascade'), + foreignKey({ + columns: [table.slackMessageId], + foreignColumns: [slackMessageTracking.id], + name: 'messages_to_slack_messages_slack_message_id_fkey', + }).onDelete('cascade'), + // Composite primary key + primaryKey({ + columns: [table.messageId, table.slackMessageId], + name: 'messages_to_slack_messages_pkey', + }), + // Indexes for query performance + index('messages_to_slack_messages_message_id_idx').using( + 'btree', + table.messageId.asc().nullsLast().op('uuid_ops'), + ), + index('messages_to_slack_messages_slack_message_id_idx').using( + 'btree', + table.slackMessageId.asc().nullsLast().op('uuid_ops'), + ), + ], ); From 151662f300629872eb71bb21429b5bc1bdc231cb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 3 Jul 2025 20:50:53 +0000 Subject: [PATCH 2/5] Refactor Slack integration update endpoint to support more flexible settings Co-authored-by: dallin --- apps/server/src/api/v2/slack/handler.ts | 30 +++++++++++++------------ apps/server/src/api/v2/slack/index.ts | 2 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/apps/server/src/api/v2/slack/handler.ts b/apps/server/src/api/v2/slack/handler.ts index 40cb6d998..4ec10aa5e 100644 --- a/apps/server/src/api/v2/slack/handler.ts +++ b/apps/server/src/api/v2/slack/handler.ts @@ -20,9 +20,13 @@ const OAuthCallbackSchema = z.object({ state: z.string(), }); -const UpdateDefaultChannelSchema = z.object({ - name: z.string().min(1), - id: z.string().min(1), +const UpdateIntegrationSchema = z.object({ + defaultChannel: z + .object({ + name: z.string().min(1), + id: z.string().min(1), + }) + .optional(), }); // Custom error class @@ -317,10 +321,10 @@ export class SlackHandler { } /** - * PUT /api/v2/slack/integration/default-channel - * Update default channel for Slack integration + * PUT /api/v2/slack/integration + * Update Slack integration settings */ - async updateDefaultChannel(c: Context) { + async updateIntegration(c: Context) { try { // Get service instance (lazy initialization) const slackOAuthService = this.getSlackOAuthService(); @@ -350,7 +354,7 @@ export class SlackHandler { // Parse request body const body = await c.req.json(); - const parsed = UpdateDefaultChannelSchema.safeParse(body); + const parsed = UpdateIntegrationSchema.safeParse(body); if (!parsed.success) { throw new SlackError( @@ -361,21 +365,19 @@ export class SlackHandler { } // Get active integration - const { getActiveIntegration, updateDefaultChannel } = await import( - './services/slack-helpers' - ); + const { getActiveIntegration, updateIntegration } = await import('./services/slack-helpers'); const integration = await getActiveIntegration(organizationGrant.organizationId); if (!integration) { throw new SlackError('No active Slack integration found', 404, 'INTEGRATION_NOT_FOUND'); } - // Update default channel - await updateDefaultChannel(integration.id, parsed.data); + // Update integration settings + await updateIntegration(integration.id, parsed.data); return c.json({ - message: 'Default channel updated successfully', - defaultChannel: parsed.data, + message: 'Integration updated successfully', + ...parsed.data, }); } catch (error) { console.error('Failed to update default channel:', error); diff --git a/apps/server/src/api/v2/slack/index.ts b/apps/server/src/api/v2/slack/index.ts index e54b84488..a769d29ab 100644 --- a/apps/server/src/api/v2/slack/index.ts +++ b/apps/server/src/api/v2/slack/index.ts @@ -9,8 +9,8 @@ const app = new Hono() .get('/auth/callback', (c) => slackHandler.handleOAuthCallback(c)) // Protected endpoints .get('/integration', requireAuth, (c) => slackHandler.getIntegration(c)) + .put('/integration', requireAuth, (c) => slackHandler.updateIntegration(c)) .delete('/integration', requireAuth, (c) => slackHandler.removeIntegration(c)) - .put('/integration/default-channel', requireAuth, (c) => slackHandler.updateDefaultChannel(c)) // Error handling .onError((e, c) => { if (e instanceof SlackError) { From 2ef68e2a1b46d752c1b61aef7d8298e1d05ad2a9 Mon Sep 17 00:00:00 2001 From: dal Date: Thu, 3 Jul 2025 15:18:28 -0600 Subject: [PATCH 3/5] Refactor Slack integration handling and update tests for new default channel functionality - Introduced `updateDefaultChannel` method in Slack helpers to manage default channel settings. - Updated SlackHandler to utilize the new method for updating integration settings. - Enhanced integration tests to cover new functionality and ensure proper handling of active integrations. - Removed obsolete SQL migration for default channel as it is now managed in code. --- .cursor/rules/global.mdc | 616 ++ apps/server/src/api/v2/slack/handler.ts | 36 +- .../slack/services/slack-helpers.int.test.ts | 850 +-- ..._default_channel_to_slack_integrations.sql | 3 - .../drizzle/0073_lovely_white_tiger.sql | 1 + .../database/drizzle/meta/0073_snapshot.json | 6164 +++++++++++++++++ packages/database/drizzle/meta/_journal.json | 7 + 7 files changed, 7076 insertions(+), 601 deletions(-) create mode 100644 .cursor/rules/global.mdc delete mode 100644 packages/database/drizzle/0073_add_default_channel_to_slack_integrations.sql create mode 100644 packages/database/drizzle/0073_lovely_white_tiger.sql create mode 100644 packages/database/drizzle/meta/0073_snapshot.json diff --git a/.cursor/rules/global.mdc b/.cursor/rules/global.mdc new file mode 100644 index 000000000..fd985ee75 --- /dev/null +++ b/.cursor/rules/global.mdc @@ -0,0 +1,616 @@ +--- +description: +globs: +alwaysApply: true +--- +# CLAUDE.md + +This file provides guidance to Claude Code when working with code in this monorepo. + +## Monorepo Structure + +This is a pnpm-based monorepo using Turborepo with the following structure: + +### Apps (`@buster-app/*`) +- `apps/web` - Next.js frontend application +- `apps/server` - Node.js/Hono backend server +- `apps/trigger` - Background job processing with Trigger.dev v3 +- `apps/electric-server` - Electric SQL sync server +- `apps/api` - Rust backend API (legacy) +- `apps/cli` - Command-line tools (Rust) + +### Packages (`@buster/*`) +- `packages/ai` - AI agents, tools, and workflows using Mastra framework +- `packages/database` - Database schema, migrations, and utilities (Drizzle ORM) +- `packages/data-source` - Data source adapters (PostgreSQL, MySQL, BigQuery, Snowflake, etc.) +- `packages/access-controls` - Permission and access control logic +- `packages/stored-values` - Stored values management +- `packages/rerank` - Document reranking functionality +- `packages/server-shared` - Shared server types and utilities +- `packages/test-utils` - Shared testing utilities +- `packages/vitest-config` - Shared Vitest configuration +- `packages/typescript-config` - Shared TypeScript configuration +- `packages/web-tools` - Web scraping and research tools +- `packages/slack` - Standalone Slack integration (OAuth, messaging, channels) +- `packages/supabase` - Supabase setup and configuration + +## Development Workflow + +When writing code, follow this workflow to ensure code quality: + +### 1. Write Modular, Testable Functions +- Create small, focused functions with single responsibilities +- Design functions to be easily testable with clear inputs/outputs +- Use dependency injection for external dependencies +- Follow existing patterns in the codebase + +### 2. Build Features by Composing Functions +- Combine modular functions to create complete features +- Keep business logic separate from infrastructure concerns +- Use proper error handling at each level + +### 3. Ensure Type Safety +```bash +# Build entire monorepo to check types +turbo run build + +# Build specific package/app +turbo run build --filter=@buster/ai +turbo run build --filter=@buster-app/web + +# Type check without building +turbo run typecheck +turbo run typecheck --filter=@buster/database +``` + +### 4. Run Biome for Linting & Formatting +```bash +# Check files with Biome +pnpm run check path/to/file.ts +pnpm run check packages/ai + +# Auto-fix linting, formatting, and import organization +pnpm run check:fix path/to/file.ts +pnpm run check:fix packages/ai +``` + +### 5. Run Tests with Vitest +```bash +# Run all tests +pnpm run test + +# Run tests for specific package +turbo run test --filter=@buster/ai + +# Run specific test file +pnpm run test path/to/file.test.ts + +# Watch mode for development +pnpm run test:watch +``` + +## Code Quality Standards + +### TypeScript Configuration +- **Strict mode enabled** - All strict checks are on +- **No implicit any** - Always use specific types +- **Strict null checks** - Handle null/undefined explicitly +- **No implicit returns** - All code paths must return +- **Consistent file casing** - Enforced by TypeScript + +### Biome Rules (Key Enforcements) +- **`useImportType: "warn"`** - Use type-only imports when possible +- **`noExplicitAny: "error"`** - Never use `any` type +- **`noUnusedVariables: "error"`** - Remove unused code +- **`noNonNullAssertion: "error"`** - No `!` assertions +- **`noConsoleLog: "warn"`** - Avoid console.log in production +- **`useNodejsImportProtocol: "error"`** - Use `node:` prefix for Node.js imports + +### Testing Practices + +#### Test File Naming & Location +- **Unit tests**: `filename.test.ts` (alongside the source file) +- **Integration tests**: `filename.int.test.ts` (alongside the source file) +- Never separate tests into their own folders - keep them with the code they test + +#### Testing Strategy +1. **Prioritize mocking** for unit tests after understanding API/DB structure +2. **Integration tests** should focus on single connection confirmations +3. **Mock external dependencies** appropriately +4. **Use descriptive test names** that explain the behavior +5. **Write tests alongside implementation** for better coverage + +#### Example Test Structure +```typescript +// user-service.ts +export function getUserById(id: string) { /* ... */ } + +// user-service.test.ts (same directory) +import { describe, it, expect, vi } from 'vitest'; +import { getUserById } from './user-service'; + +describe('getUserById', () => { + it('should return user when found', async () => { + // Test implementation + }); +}); + +// user-service.int.test.ts (integration test) +import { describe, it, expect } from 'vitest'; +import { getUserById } from './user-service'; + +describe('getUserById integration', () => { + it('should connect to database successfully', async () => { + // Single connection test + }); +}); +``` + +## Code Style Preferences + +### Type Safety +- **Zod-First Approach** - Use Zod schemas as the single source of truth for both validation and types +- **Use `z.infer` for types** - Prefer inferred types over separate interfaces +- **Never use `any`** - Biome enforces this with `noExplicitAny: "error"` +- **Avoid `unknown` unless necessary** - Prefer specific types or properly typed unions +- **Handle null/undefined explicitly** - TypeScript strict mode enforces this +- **Safe array access** - Use validation helpers when needed +- **Type-only imports** - Use `import type` for better performance +#### Zod-First Type Safety Pattern +```typescript +// ✅ Good: Zod schema as single source of truth +const userSchema = z.object({ + id: z.string().min(1), + email: z.string().email(), + role: z.enum(['admin', 'user']), +}); + +type User = z.infer; // Inferred type + +// ✅ Good: Safe runtime validation +const validatedUser = userSchema.parse(rawData); + +// ✅ Good: Safe array access when needed +import { validateArrayAccess } from '@buster/ai/utils/validation-helpers'; +const firstItem = validateArrayAccess(array, 0, 'user processing'); + +// ❌ Avoid: Separate interface + unsafe access +interface User { + id: string; + email: string; +} +const user = rawData as User; // Unsafe type assertion +const firstItem = array[0]!; // Non-null assertion not allowed +``` + +### Import Organization +- Use **type-only imports** when importing only types: `import type { SomeType } from './types'` +- Biome automatically organizes imports with `pnpm run check:fix` +- Use Node.js protocol: `import { readFile } from 'node:fs'` +- Follow path aliases defined in each package's tsconfig.json + +### String Handling +- **Prefer template literals** over string concatenation for better readability +- Use template literals for multi-line strings and string interpolation + +#### String Handling Patterns +```typescript +// ✅ Good: Template literals +const message = `User ${userId} not found`; +const multiLine = `This is a +multi-line string`; +const path = `${baseUrl}/api/users/${userId}`; + +// ❌ Avoid: String concatenation +const message = 'User ' + userId + ' not found'; +const path = baseUrl + '/api/users/' + userId; +``` + +### Error Handling +- **Always use try-catch blocks** for async operations and external calls +- **Never use `any` in catch blocks** - Biome enforces this +- **Validate external data** with Zod schemas before processing +- **Provide meaningful error messages** with context for debugging +- **Handle errors at appropriate levels** - don't let errors bubble up uncaught +- **Use structured logging** for error tracking + +#### Error Handling Patterns +```typescript +// ✅ Good: Comprehensive error handling +async function processUserData(userId: string) { + try { + const user = await getUserById(userId); + if (!user) { + throw new Error(`User not found: ${userId}`); + } + + const validatedData = UserSchema.parse(user); + return await processData(validatedData); + } catch (error) { + logger.error('Failed to process user data', { + userId, + error: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : undefined + }); + throw new Error(`User data processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`); + } +} + +// ✅ Good: Database operations with error handling +async function createResource(data: CreateResourceInput) { + try { + const validatedData = CreateResourceSchema.parse(data); + return await db.transaction(async (tx) => { + const resource = await tx.insert(resources).values(validatedData).returning(); + await tx.insert(resourceAudit).values({ + resourceId: resource[0].id, + action: 'created', + createdAt: new Date() + }); + return resource[0]; + }); + } catch (error) { + if (error instanceof ZodError) { + throw new Error(`Invalid resource data: ${error.errors.map(e => e.message).join(', ')}`); + } + logger.error('Database error creating resource', { data, error }); + throw new Error('Failed to create resource'); + } +} + +// ❌ Avoid: Unhandled async operations +async function badExample(userId: string) { + const user = await getUserById(userId); // No error handling + return user.data; // Could fail if user is null +} +``` + +## Test Utilities + +The `@buster/test-utils` package provides shared testing utilities: + +### Environment Helpers +```typescript +import { setupTestEnvironment, withTestEnv } from '@buster/test-utils/env-helpers'; + +// Manual setup/teardown +beforeAll(() => setupTestEnvironment()); +afterAll(() => cleanupTestEnvironment()); + +// Or use the wrapper +await withTestEnv(async () => { + // Your test code here +}); +``` + +### Mock Helpers +```typescript +import { createMockFunction, mockConsole, createMockDate } from '@buster/test-utils/mock-helpers'; + +// Create vitest mock functions +const mockFn = createMockFunction<(arg: string) => void>(); + +// Mock console methods (allowed in tests) +const consoleMock = mockConsole(); +// Test code that logs... +consoleMock.restore(); + +// Mock dates for time-sensitive tests +const dateMock = createMockDate(new Date('2024-01-01')); +// Test code... +dateMock.restore(); +``` + +### Database Test Helpers +```typescript +import { createTestChat, cleanupTestChats } from '@buster/test-utils/database/chats'; +import { createTestMessage, cleanupTestMessages } from '@buster/test-utils/database/messages'; + +// Create test data +const chat = await createTestChat({ + userId: 'test-user', + title: 'Test Chat' +}); + +const message = await createTestMessage({ + chatId: chat.id, + role: 'user', + content: 'Test message' +}); + +// Cleanup after tests +await cleanupTestMessages(chat.id); +await cleanupTestChats('test-user'); +``` + +## Quick Command Reference + +### Building & Type Checking +```bash +# Build all packages +turbo run build + +# Build specific package/app +turbo run build --filter=@buster/ai +turbo run build --filter=@buster-app/web + +# Type check only +turbo run typecheck +turbo run typecheck --filter=@buster/database +``` + +### Linting & Formatting +```bash +# Check and auto-fix with Biome +pnpm run check:fix path/to/file.ts +pnpm run check:fix packages/ai + +# Check only (no fixes) +pnpm run check path/to/file.ts +``` + +### Testing +```bash +# Run all tests +pnpm run test + +# Run tests for specific package +turbo run test --filter=@buster/ai + +# Run specific test file +pnpm run test path/to/file.test.ts + +# Watch mode +pnpm run test:watch +``` + +### Database Commands +```bash +pnpm run db:generate # Generate types from schema +pnpm run db:migrate # Run migrations +pnpm run db:push # Push schema changes +pnpm run db:studio # Open Drizzle Studio +``` + +## Helper Organization Pattern + +When building helper functions, follow this organizational pattern: + +### Database Helpers (in `packages/database/`) +``` +packages/database/src/helpers/ +├── index.ts # Export all helpers +├── messages.ts # Message-related helpers +├── users.ts # User-related helpers +├── chats.ts # Chat-related helpers +└── {entity}.ts # Entity-specific helpers +``` + +### Package-Specific Utilities +``` +packages/{package}/src/utils/ +├── index.ts # Export all utilities +├── {domain}/ # Domain-specific utilities +│ ├── index.ts +│ └── helpers.ts +└── helpers.ts # General helpers +``` + +### Key Principles +- **Co-locate helpers** with the schema/types they operate on +- **Group by entity** (one file per database table/domain object) +- **Export from package root** for easy importing +- **Use TypeScript** with proper types (no `any`) +- **Follow naming conventions** that clearly indicate purpose + +### Example Usage +```typescript +// ✅ Good: Clear, typed helpers exported from package root +import { getRawLlmMessages, getMessagesForChat } from '@buster/database'; + +// ❌ Avoid: Direct database queries scattered throughout codebase +import { db, messages, eq } from '@buster/database'; +const result = await db.select().from(messages).where(eq(messages.chatId, chatId)); +``` + +## Background Job Processing (Trigger.dev) + +The `apps/trigger` package provides background job processing using **Trigger.dev v3**. + +### 🚨 CRITICAL: Always Use v3 Patterns + +```typescript +// ✅ CORRECT - Always use this pattern +import { task } from '@trigger.dev/sdk/v3'; + +export const myTask = task({ + id: 'my-task', + run: async (payload: InputType): Promise => { + // Task implementation + }, +}); +``` + +### Essential Requirements +1. **MUST export every task** from the file +2. **MUST use unique task IDs** within the project +3. **MUST import from** `@trigger.dev/sdk/v3` +4. **Use Zod schemas** for payload validation + +### Common Task Patterns + +#### Schema-Validated Task (Recommended) +```typescript +import { schemaTask } from '@trigger.dev/sdk/v3'; +import { z } from 'zod'; + +// Define schema for type safety +export const TaskInputSchema = z.object({ + userId: z.string(), + data: z.record(z.unknown()), +}); + +export type TaskInput = z.infer; + +export const processUserTask = schemaTask({ + id: 'process-user', + schema: TaskInputSchema, + maxDuration: 300, // 5 minutes + run: async (payload) => { + // Payload is validated and typed + return { success: true }; + }, +}); +``` + +#### Triggering Tasks +```typescript +import { tasks } from '@trigger.dev/sdk/v3'; +import type { processUserTask } from '@buster-app/trigger/tasks'; + +// Trigger from API routes +const handle = await tasks.trigger('process-user', { + userId: 'user123', + data: {} +}); +``` + +### Development Commands +```bash +# Development server +pnpm run trigger:dev + +# Run tests +pnpm run trigger:test + +# Deploy +pnpm run trigger:deploy +``` + +**See `apps/trigger/CLAUDE.md` for complete Trigger.dev guidelines.** + +## Key Dependencies + +- **Turborepo** - Monorepo orchestration and caching +- **pnpm** - Fast, disk space efficient package manager +- **Biome** - Fast linting and formatting (replaces ESLint/Prettier) +- **TypeScript** - Strict type checking across all packages +- **Vitest** - Fast unit testing framework +- **Zod** - Runtime validation and type inference +- **Mastra** - AI agent framework for LLM workflows +- **Trigger.dev v3** - Background job processing +- **Drizzle ORM** - Type-safe database toolkit +- **Braintrust** - LLM observability and evaluation + +## Complete Development Workflow Example + +When implementing a new feature: + +```bash +# 1. Write your modular, testable functions +# 2. Compose them into the feature +# 3. Write tests alongside the code + +# 4. Ensure type safety +turbo run build --filter=@buster/ai +# or for all packages: +turbo run build + +# 5. Fix linting and formatting +pnpm run check:fix packages/ai + +# 6. Run tests +turbo run test --filter=@buster/ai +# or specific test: +pnpm run test packages/ai/src/feature.test.ts + +# 7. If all passes, commit your changes +git add . +git commit -m "feat: add new feature" +``` + +## Slack Package (@buster/slack) + +The `@buster/slack` package is a **standalone Slack integration** with no database dependencies. It provides: + +### Features +- **OAuth 2.0 Authentication** - Complete OAuth flow with state management +- **Channel Management** - List, validate, join/leave channels +- **Messaging** - Send messages, replies, updates with retry logic +- **Message Tracking** - Interface for threading support +- **Type Safety** - Zod validation throughout + +### Architecture +The package uses **interface-based design** where consuming applications must implement: +- `ISlackTokenStorage` - For token persistence +- `ISlackOAuthStateStorage` - For OAuth state management +- `ISlackMessageTracking` - For message threading (optional) + +### Usage Pattern +```typescript +// All functions accept tokens as parameters +const channels = await channelService.getAvailableChannels(accessToken); +const result = await messagingService.sendMessage(accessToken, channelId, message); +``` + +### Testing +```bash +# Run tests +turbo run test --filter=@buster/slack + +# Build +turbo run build --filter=@buster/slack + +# Type check +turbo run typecheck --filter=@buster/slack +``` + +### Key Principles +- **No database dependencies** - Uses interfaces for storage +- **Token-based** - All functions accept tokens as parameters +- **Framework-agnostic** - Works with any Node.js application +- **Comprehensive error handling** - Typed errors with retry logic + +## Important Notes + +- **Never use `any`** - Biome will error on this +- **Always handle errors** properly with try-catch +- **Write tests alongside code** - not in separate folders +- **Use Zod for validation** - single source of truth +- **Run type checks** before committing +- **Follow existing patterns** in the codebase + +This ensures high code quality and maintainability across the monorepo. + +## Common Biome Overrides + +Test files have relaxed rules to allow: +- `console.log` for debugging tests +- Non-null assertions (`!`) in test scenarios +- `any` type when mocking (though prefer proper types) + +Database package allows `any` for Drizzle ORM compatibility. + +## Environment Variables + +The monorepo uses a strict environment mode. Key variables include: +- Database connections (Supabase, PostgreSQL, etc.) +- API keys (OpenAI, Anthropic, etc.) +- Service URLs and configurations + +See `.env.example` files in each package for required variables. + +# important-instruction-reminders +Do what has been asked; nothing more, nothing less. +NEVER create files unless they're absolutely necessary for achieving your goal. +ALWAYS prefer editing an existing file to creating a new one. +NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User. + +## Biome Linting Instructions + +### Linting Rules +- Always use `pnpm run check` or `pnpm run check:fix` +- **Rule: `i dont' want caldue to ever run a biome lint fix only biome lint`** + - This means ONLY use `pnpm run check` (linting without auto-fixing) + - Do NOT use `pnpm run check:fix` + - Claude should understand to ONLY run lint checks, never auto-fix diff --git a/apps/server/src/api/v2/slack/handler.ts b/apps/server/src/api/v2/slack/handler.ts index 4ec10aa5e..c7264b3c8 100644 --- a/apps/server/src/api/v2/slack/handler.ts +++ b/apps/server/src/api/v2/slack/handler.ts @@ -2,6 +2,7 @@ import { getUserOrganizationId } from '@buster/database'; import type { Context } from 'hono'; import { HTTPException } from 'hono/http-exception'; import { z } from 'zod'; +import { getActiveIntegration, updateDefaultChannel } from './services/slack-helpers'; import { type SlackOAuthService, createSlackOAuthService } from './services/slack-oauth-service'; // Request schemas @@ -34,7 +35,7 @@ export class SlackError extends Error { constructor( message: string, public statusCode: 500 | 400 | 401 | 403 | 404 | 409 | 503 = 500, - public code?: string, + public code?: string ) { super(message); this.name = 'SlackError'; @@ -81,7 +82,7 @@ export class SlackHandler { error: 'Slack integration is not configured', code: 'INTEGRATION_NOT_CONFIGURED', }, - 503, + 503 ); } @@ -92,7 +93,7 @@ export class SlackHandler { error: 'Slack integration is not enabled', code: 'INTEGRATION_DISABLED', }, - 503, + 503 ); } @@ -140,14 +141,14 @@ export class SlackHandler { throw new SlackError( 'Organization already has an active Slack integration', 409, - 'INTEGRATION_EXISTS', + 'INTEGRATION_EXISTS' ); } throw new SlackError( error instanceof Error ? error.message : 'Failed to initiate OAuth', 500, - 'OAUTH_INIT_ERROR', + 'OAUTH_INIT_ERROR' ); } } @@ -202,7 +203,7 @@ export class SlackHandler { console.error('OAuth callback error:', error); const errorMessage = error instanceof Error ? error.message : 'callback_failed'; return c.redirect( - `/settings/integrations?status=error&error=${encodeURIComponent(errorMessage)}`, + `/settings/integrations?status=error&error=${encodeURIComponent(errorMessage)}` ); } } @@ -223,7 +224,7 @@ export class SlackHandler { error: 'Slack integration is not configured', code: 'INTEGRATION_NOT_CONFIGURED', }, - 503, + 503 ); } @@ -252,7 +253,7 @@ export class SlackHandler { throw new SlackError( error instanceof Error ? error.message : 'Failed to get integration status', 500, - 'GET_INTEGRATION_ERROR', + 'GET_INTEGRATION_ERROR' ); } } @@ -273,7 +274,7 @@ export class SlackHandler { error: 'Slack integration is not configured', code: 'INTEGRATION_NOT_CONFIGURED', }, - 503, + 503 ); } @@ -291,14 +292,14 @@ export class SlackHandler { const result = await slackOAuthService.removeIntegration( organizationGrant.organizationId, - user.id, + user.id ); if (!result.success) { throw new SlackError( result.error || 'Failed to remove integration', 404, - 'INTEGRATION_NOT_FOUND', + 'INTEGRATION_NOT_FOUND' ); } @@ -315,7 +316,7 @@ export class SlackHandler { throw new SlackError( error instanceof Error ? error.message : 'Failed to remove integration', 500, - 'REMOVE_INTEGRATION_ERROR', + 'REMOVE_INTEGRATION_ERROR' ); } } @@ -336,7 +337,7 @@ export class SlackHandler { error: 'Slack integration is not configured', code: 'INTEGRATION_NOT_CONFIGURED', }, - 503, + 503 ); } @@ -360,12 +361,11 @@ export class SlackHandler { throw new SlackError( `Invalid request body: ${parsed.error.errors.map((e) => e.message).join(', ')}`, 400, - 'INVALID_REQUEST_BODY', + 'INVALID_REQUEST_BODY' ); } // Get active integration - const { getActiveIntegration, updateIntegration } = await import('./services/slack-helpers'); const integration = await getActiveIntegration(organizationGrant.organizationId); if (!integration) { @@ -373,7 +373,9 @@ export class SlackHandler { } // Update integration settings - await updateIntegration(integration.id, parsed.data); + if (parsed.data.defaultChannel) { + await updateDefaultChannel(integration.id, parsed.data.defaultChannel); + } return c.json({ message: 'Integration updated successfully', @@ -389,7 +391,7 @@ export class SlackHandler { throw new SlackError( error instanceof Error ? error.message : 'Failed to update default channel', 500, - 'UPDATE_DEFAULT_CHANNEL_ERROR', + 'UPDATE_DEFAULT_CHANNEL_ERROR' ); } } diff --git a/apps/server/src/api/v2/slack/services/slack-helpers.int.test.ts b/apps/server/src/api/v2/slack/services/slack-helpers.int.test.ts index 13ddd8c47..f14dfa072 100644 --- a/apps/server/src/api/v2/slack/services/slack-helpers.int.test.ts +++ b/apps/server/src/api/v2/slack/services/slack-helpers.int.test.ts @@ -1,4 +1,10 @@ -import { db, slackIntegrations } from '@buster/database'; +import { + db, + organizations, + slackIntegrations, + users, + usersToOrganizations, +} from '@buster/database'; import { and, eq, isNull } from 'drizzle-orm'; import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'; import * as slackHelpers from './slack-helpers'; @@ -16,41 +22,60 @@ vi.mock('./token-storage', () => ({ const mockedTokenStorage = vi.mocked(tokenStorage); -describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { - // Use existing seed data IDs - const testOrganizationId = 'bf58d19a-8bb9-4f1d-a257-2d2105e7f1ce'; - const testUserId = 'c2dd64cd-f7f3-4884-bc91-d46ae431901e'; - let createdIntegrationIds: string[] = []; +describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', () => { + // Test data IDs + let testOrganizationId: string; + let testUserId: string; + const createdIntegrationIds: string[] = []; - // Helper function to generate unique test IDs + // Helper to generate unique test data identifiers const generateTestIds = () => ({ teamId: `T${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, botUserId: `U${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, enterpriseId: `E${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + oauthState: `state-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, }); beforeAll(async () => { if (skipIfNoDatabase) { - console.log('Skipping Slack helpers integration tests - DATABASE_URL not set'); + console.log('Skipping Slack helpers database integration tests - DATABASE_URL not set'); return; } - // Clean up any existing test data for this organization - try { - await db - .delete(slackIntegrations) - .where(eq(slackIntegrations.organizationId, testOrganizationId)); + // Create test organization + testOrganizationId = crypto.randomUUID(); + const [org] = await db + .insert(organizations) + .values({ + id: testOrganizationId, + name: `Test Org ${Date.now()}`, + }) + .returning(); - // Also clean up any test integrations with our test team ID patterns - await db.delete(slackIntegrations).where(eq(slackIntegrations.teamId, 'T12345')); - } catch (error) { - console.error('Error cleaning up existing test data:', error); - } + // Create test user + testUserId = crypto.randomUUID(); + const [user] = await db + .insert(users) + .values({ + id: testUserId, + email: `test-${Date.now()}@example.com`, + name: 'Test User', + }) + .returning(); + + // Create user-to-organization relationship + await db.insert(usersToOrganizations).values({ + userId: testUserId, + organizationId: testOrganizationId, + role: 'workspace_admin', + createdBy: testUserId, + updatedBy: testUserId, + }); }); afterAll(async () => { - // Clean up only the specific test data created by this test suite - if (!skipIfNoDatabase && createdIntegrationIds.length > 0) { + if (!skipIfNoDatabase) { + // Clean up all created integrations for (const id of createdIntegrationIds) { try { await db.delete(slackIntegrations).where(eq(slackIntegrations.id, id)); @@ -58,20 +83,43 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { console.error(`Failed to clean up integration ${id}:`, error); } } - createdIntegrationIds = []; + + // Clean up user-to-organization relationship + await db + .delete(usersToOrganizations) + .where( + and( + eq(usersToOrganizations.userId, testUserId), + eq(usersToOrganizations.organizationId, testOrganizationId) + ) + ); + + // Clean up test user + await db.delete(users).where(eq(users.id, testUserId)); + + // Clean up test organization + await db.delete(organizations).where(eq(organizations.id, testOrganizationId)); + } + }); + + beforeEach(async () => { + if (!skipIfNoDatabase) { + // Clean up any existing integrations for this organization before each test + await db + .delete(slackIntegrations) + .where(eq(slackIntegrations.organizationId, testOrganizationId)); } }); describe('getActiveIntegration', () => { it('should return null when no integration exists', async () => { - const result = await slackHelpers.getActiveIntegration( - 'f47ac10b-58cc-4372-a567-0e02b2c3d479' - ); + const result = await slackHelpers.getActiveIntegration(testOrganizationId); expect(result).toBeNull(); }); it('should return active integration when it exists', async () => { const testIds = generateTestIds(); + // Create an active integration const [integration] = await db .insert(slackIntegrations) @@ -84,38 +132,25 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { teamDomain: 'test-workspace', botUserId: testIds.botUserId, scope: 'channels:read chat:write', - tokenVaultKey: 'test-token-key', + tokenVaultKey: `test-token-key-${Date.now()}`, }) .returning(); createdIntegrationIds.push(integration.id); const result = await slackHelpers.getActiveIntegration(testOrganizationId); + expect(result).toBeTruthy(); expect(result?.id).toBe(integration.id); expect(result?.status).toBe('active'); expect(result?.teamName).toBe('Test Workspace'); + expect(result?.organizationId).toBe(testOrganizationId); }); - it('should not return soft-deleted integrations', async () => { - // First, ensure we have no active integrations by cleaning up any from previous tests - const existingActive = await db - .select({ id: slackIntegrations.id }) - .from(slackIntegrations) - .where( - and( - eq(slackIntegrations.organizationId, testOrganizationId), - eq(slackIntegrations.status, 'active'), - isNull(slackIntegrations.deletedAt) - ) - ); - - for (const integration of existingActive) { - await db.delete(slackIntegrations).where(eq(slackIntegrations.id, integration.id)); - } - + it('should not return deleted integrations', async () => { const testIds = generateTestIds(); - // Create a soft-deleted integration + + // Create a deleted integration const [integration] = await db .insert(slackIntegrations) .values({ @@ -124,9 +159,6 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { status: 'active', teamId: testIds.teamId, teamName: 'Deleted Workspace', - botUserId: testIds.botUserId, - scope: 'channels:read', - tokenVaultKey: `deleted-token-${Date.now()}`, deletedAt: new Date().toISOString(), }) .returning(); @@ -136,152 +168,17 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { const result = await slackHelpers.getActiveIntegration(testOrganizationId); expect(result).toBeNull(); }); - - it('should not return non-active integrations', async () => { - // First, ensure we have no active integrations by cleaning up any from previous tests - const existingActive = await db - .select({ id: slackIntegrations.id }) - .from(slackIntegrations) - .where( - and( - eq(slackIntegrations.organizationId, testOrganizationId), - eq(slackIntegrations.status, 'active'), - isNull(slackIntegrations.deletedAt) - ) - ); - - for (const integration of existingActive) { - await db.delete(slackIntegrations).where(eq(slackIntegrations.id, integration.id)); - } - - // Create a pending integration - const [integration] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'pending', - oauthState: 'test-state', - oauthExpiresAt: new Date(Date.now() + 900000).toISOString(), // 15 mins from now - }) - .returning(); - - createdIntegrationIds.push(integration.id); - - const result = await slackHelpers.getActiveIntegration(testOrganizationId); - expect(result).toBeNull(); - }); - }); - - describe('getIntegrationById', () => { - it('should return integration by id', async () => { - const testIds = generateTestIds(); - const [integration] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'active', - teamId: testIds.teamId, - teamName: 'Test Workspace', - }) - .returning(); - - createdIntegrationIds.push(integration.id); - - const result = await slackHelpers.getIntegrationById(integration.id); - expect(result).toBeTruthy(); - expect(result?.id).toBe(integration.id); - }); - - it('should return null for non-existent id', async () => { - const result = await slackHelpers.getIntegrationById('f47ac10b-58cc-4372-a567-0e02b2c3d480'); - expect(result).toBeNull(); - }); - }); - - describe('getPendingIntegrationByState', () => { - it('should return pending integration with valid state', async () => { - const testState = `test-state-${Date.now()}`; - const [integration] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'pending', - oauthState: testState, - oauthExpiresAt: new Date(Date.now() + 900000).toISOString(), // 15 mins from now - }) - .returning(); - - createdIntegrationIds.push(integration.id); - - const result = await slackHelpers.getPendingIntegrationByState(testState); - expect(result).toBeTruthy(); - expect(result?.id).toBe(integration.id); - expect(result?.oauthState).toBe(testState); - }); - - it('should not return expired integrations', async () => { - const testState = `expired-state-${Date.now()}`; - const [integration] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'pending', - oauthState: testState, - oauthExpiresAt: new Date(Date.now() - 60000).toISOString(), // 1 min ago - }) - .returning(); - - createdIntegrationIds.push(integration.id); - - const result = await slackHelpers.getPendingIntegrationByState(testState); - expect(result).toBeNull(); - }); - - it('should not return non-pending integrations', async () => { - const testState = `active-state-${Date.now()}`; - const testIds = generateTestIds(); - const [integration] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'active', - oauthState: testState, - teamId: testIds.teamId, - teamName: 'Test Workspace', - }) - .returning(); - - createdIntegrationIds.push(integration.id); - - const result = await slackHelpers.getPendingIntegrationByState(testState); - expect(result).toBeNull(); - }); }); describe('createPendingIntegration', () => { - beforeEach(async () => { - // Clean up any existing integrations for the test organization - await db - .delete(slackIntegrations) - .where(eq(slackIntegrations.organizationId, testOrganizationId)); - - // Clear mock calls - mockedTokenStorage.deleteToken.mockClear(); - }); - it('should create a new pending integration', async () => { - const testState = `new-state-${Date.now()}`; + const testIds = generateTestIds(); const metadata = { returnUrl: '/dashboard', source: 'settings' }; const integrationId = await slackHelpers.createPendingIntegration({ organizationId: testOrganizationId, userId: testUserId, - oauthState: testState, + oauthState: testIds.oauthState, oauthMetadata: metadata, }); @@ -298,161 +195,19 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { expect(created.organizationId).toBe(testOrganizationId); expect(created.userId).toBe(testUserId); expect(created.status).toBe('pending'); - expect(created.oauthState).toBe(testState); + expect(created.oauthState).toBe(testIds.oauthState); expect(created.oauthMetadata).toEqual(metadata); // Check expiry is set to ~15 minutes const expiryTime = new Date(created.oauthExpiresAt!).getTime(); const expectedExpiry = Date.now() + 15 * 60 * 1000; - expect(Math.abs(expiryTime - expectedExpiry)).toBeLessThan(5000); // Within 5 seconds + expect(Math.abs(expiryTime - expectedExpiry)).toBeLessThan(30000); // Within 30 seconds }); - it('should delete revoked integration and create new pending', async () => { - // First, create a revoked integration with a token vault key + it('should throw error if active integration already exists', async () => { const testIds = generateTestIds(); - const tokenVaultKey = `revoked-token-${Date.now()}`; - const [revokedIntegration] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'revoked', - teamId: testIds.teamId, - teamName: 'Old Workspace', - teamDomain: 'old-workspace', - botUserId: testIds.botUserId, - scope: 'channels:read', - tokenVaultKey, - }) - .returning(); - createdIntegrationIds.push(revokedIntegration.id); - - // Now try to create a new pending integration for the same org - const newState = `reuse-state-${Date.now()}`; - const newMetadata = { returnUrl: '/settings', source: 'reconnect' }; - - const integrationId = await slackHelpers.createPendingIntegration({ - organizationId: testOrganizationId, - userId: testUserId, - oauthState: newState, - oauthMetadata: newMetadata, - }); - - // Should be a new integration ID (not the same as revoked) - expect(integrationId).not.toBe(revokedIntegration.id); - createdIntegrationIds.push(integrationId); - - // Verify vault token cleanup was called - expect(mockedTokenStorage.deleteToken).toHaveBeenCalledWith(tokenVaultKey); - - // Verify old integration was deleted - const [deletedIntegration] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, revokedIntegration.id)); - - expect(deletedIntegration).toBeUndefined(); - - // Verify new integration was created correctly - const [newIntegration] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, integrationId)); - - expect(newIntegration).toBeTruthy(); - expect(newIntegration.status).toBe('pending'); - expect(newIntegration.oauthState).toBe(newState); - expect(newIntegration.oauthMetadata).toEqual(newMetadata); - expect(newIntegration.organizationId).toBe(testOrganizationId); - }); - - it('should delete failed integration and create new pending', async () => { - // First, create a failed integration with a token vault key - const testIds = generateTestIds(); - const tokenVaultKey = `failed-token-${Date.now()}`; - const [failedIntegration] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'failed', - teamId: testIds.teamId, - teamName: 'Failed Workspace', - tokenVaultKey, - }) - .returning(); - - createdIntegrationIds.push(failedIntegration.id); - - // Now try to create a new pending integration for the same org - const newState = `retry-state-${Date.now()}`; - - const integrationId = await slackHelpers.createPendingIntegration({ - organizationId: testOrganizationId, - userId: testUserId, - oauthState: newState, - }); - - // Should be a new integration ID (not the same as failed) - expect(integrationId).not.toBe(failedIntegration.id); - createdIntegrationIds.push(integrationId); - - // Verify vault token cleanup was called - expect(mockedTokenStorage.deleteToken).toHaveBeenCalledWith(tokenVaultKey); - - // Verify old integration was deleted - const [deletedIntegration] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, failedIntegration.id)); - - expect(deletedIntegration).toBeUndefined(); - - // Verify new integration was created with pending status - const [newIntegration] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, integrationId)); - - expect(newIntegration.status).toBe('pending'); - expect(newIntegration.oauthState).toBe(newState); - }); - - it('should not call deleteToken when integration has no vault key', async () => { - // Create a revoked integration without a token vault key - const testIds = generateTestIds(); - const [revokedIntegration] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'revoked', - teamId: testIds.teamId, - teamName: 'Old Workspace Without Token', - // No tokenVaultKey - }) - .returning(); - - createdIntegrationIds.push(revokedIntegration.id); - - // Create a new pending integration - const newState = `no-token-state-${Date.now()}`; - const integrationId = await slackHelpers.createPendingIntegration({ - organizationId: testOrganizationId, - userId: testUserId, - oauthState: newState, - }); - - createdIntegrationIds.push(integrationId); - - // Verify deleteToken was not called since there was no token vault key - expect(mockedTokenStorage.deleteToken).not.toHaveBeenCalled(); - }); - - it('should throw error if active integration exists', async () => { // Create an active integration - const testIds = generateTestIds(); const [activeIntegration] = await db .insert(slackIntegrations) .values({ @@ -466,7 +221,7 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { createdIntegrationIds.push(activeIntegration.id); - // Try to create a new pending integration for the same org + // Try to create a new pending integration await expect( slackHelpers.createPendingIntegration({ organizationId: testOrganizationId, @@ -478,23 +233,23 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { }); describe('updateIntegrationAfterOAuth', () => { - it('should update pending integration to active with all fields', async () => { + it('should update pending integration to active', async () => { + const testIds = generateTestIds(); + // Create a pending integration - const uniqueState = `test-state-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const [pending] = await db .insert(slackIntegrations) .values({ organizationId: testOrganizationId, userId: testUserId, status: 'pending', - oauthState: uniqueState, + oauthState: testIds.oauthState, oauthExpiresAt: new Date(Date.now() + 900000).toISOString(), }) .returning(); createdIntegrationIds.push(pending.id); - const testIds = generateTestIds(); // Update it with OAuth response data await slackHelpers.updateIntegrationAfterOAuth(pending.id, { teamId: testIds.teamId, @@ -526,149 +281,13 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { expect(updated.oauthState).toBeNull(); expect(updated.oauthExpiresAt).toBeNull(); }); - - it('should handle updates with minimal fields', async () => { - // Create a pending integration - const uniqueState = `test-state-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - const [pending] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'pending', - oauthState: uniqueState, - oauthExpiresAt: new Date(Date.now() + 900000).toISOString(), - }) - .returning(); - - createdIntegrationIds.push(pending.id); - - const testIds = generateTestIds(); - // Update with minimal required fields - await slackHelpers.updateIntegrationAfterOAuth(pending.id, { - teamId: testIds.teamId, - teamName: 'Minimal Workspace', - botUserId: testIds.botUserId, - scope: 'basic', - tokenVaultKey: 'minimal-token', - }); - - // Verify the update - const [updated] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, pending.id)); - - expect(updated.status).toBe('active'); - expect(updated.teamId).toBe(testIds.teamId); - expect(updated.teamName).toBe('Minimal Workspace'); - expect(updated.teamDomain).toBeNull(); - expect(updated.enterpriseId).toBeNull(); - }); }); - describe('markIntegrationAsFailed', () => { - beforeEach(() => { - // Clear mock calls - mockedTokenStorage.deleteToken.mockClear(); - }); - - it('should mark active integration as failed', async () => { + describe('updateDefaultChannel', () => { + it('should update the default channel for an integration', async () => { const testIds = generateTestIds(); - // Create an active integration that we'll mark as failed - const [active] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'active', - teamId: testIds.teamId, - teamName: 'Failed Team', - botUserId: testIds.botUserId, - scope: 'channels:read', - tokenVaultKey: 'test-failed-token', - }) - .returning(); - createdIntegrationIds.push(active.id); - - await slackHelpers.markIntegrationAsFailed(active.id, 'Token revoked'); - - const [updated] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, active.id)); - - expect(updated).toBeDefined(); - expect(updated.status).toBe('failed'); - - // Active integrations are not deleted, so vault token should not be cleaned up - expect(mockedTokenStorage.deleteToken).not.toHaveBeenCalled(); - }); - - it('should delete pending integration when marking as failed', async () => { - // Create a pending integration - const [pending] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'pending', - oauthState: 'test-state-to-fail', - oauthExpiresAt: new Date(Date.now() + 900000).toISOString(), - }) - .returning(); - - // Don't add to cleanup since it will be deleted - // createdIntegrationIds.push(pending.id); - - await slackHelpers.markIntegrationAsFailed(pending.id, 'OAuth failed'); - - const [deleted] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, pending.id)); - - expect(deleted).toBeUndefined(); - - // Pending integration without token vault key should not call deleteToken - expect(mockedTokenStorage.deleteToken).not.toHaveBeenCalled(); - }); - - it('should delete pending integration with token vault key and clean up vault', async () => { - const tokenVaultKey = `pending-token-${Date.now()}`; - // Create a pending integration with a token vault key - const [pending] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'pending', - oauthState: 'test-state-with-token', - oauthExpiresAt: new Date(Date.now() + 900000).toISOString(), - tokenVaultKey, - }) - .returning(); - - // Don't add to cleanup since it will be deleted - - await slackHelpers.markIntegrationAsFailed(pending.id, 'OAuth failed'); - - const [deleted] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, pending.id)); - - expect(deleted).toBeUndefined(); - - // Verify vault token cleanup was called - expect(mockedTokenStorage.deleteToken).toHaveBeenCalledWith(tokenVaultKey); - }); - }); - - describe('softDeleteIntegration', () => { - it('should soft delete integration', async () => { - const testIds = generateTestIds(); + // Create an active integration const [integration] = await db .insert(slackIntegrations) .values({ @@ -676,68 +295,229 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { userId: testUserId, status: 'active', teamId: testIds.teamId, - teamName: 'To Delete', + teamName: 'Test Workspace', botUserId: testIds.botUserId, scope: 'channels:read', - tokenVaultKey: 'test-soft-delete-token', + tokenVaultKey: `test-token-${Date.now()}`, }) .returning(); createdIntegrationIds.push(integration.id); + // Update default channel + const defaultChannel = { + name: 'general', + id: 'C12345', + }; + + await slackHelpers.updateDefaultChannel(integration.id, defaultChannel); + + // Verify the update + const [updated] = await db + .select() + .from(slackIntegrations) + .where(eq(slackIntegrations.id, integration.id)); + + expect(updated.defaultChannel).toEqual(defaultChannel); + expect(new Date(updated.updatedAt).getTime()).toBeGreaterThan( + new Date(updated.createdAt).getTime() + ); + }); + }); + + describe('softDeleteIntegration', () => { + it('should soft delete an integration', async () => { + const testIds = generateTestIds(); + + // Create an active integration + const [integration] = await db + .insert(slackIntegrations) + .values({ + organizationId: testOrganizationId, + userId: testUserId, + status: 'active', + teamId: testIds.teamId, + teamName: 'To Delete Workspace', + botUserId: testIds.botUserId, + scope: 'channels:read', + tokenVaultKey: `test-token-${Date.now()}`, + }) + .returning(); + + createdIntegrationIds.push(integration.id); + + // Soft delete it await slackHelpers.softDeleteIntegration(integration.id); + // Verify it was soft deleted const [deleted] = await db .select() .from(slackIntegrations) .where(eq(slackIntegrations.id, integration.id)); - expect(deleted.deletedAt).toBeTruthy(); expect(deleted.status).toBe('revoked'); + expect(deleted.deletedAt).toBeTruthy(); + expect(new Date(deleted.updatedAt).getTime()).toBeGreaterThan( + new Date(deleted.createdAt).getTime() + ); + }); + }); + + describe('updateLastUsedAt', () => { + it('should update the last used timestamp', async () => { + const testIds = generateTestIds(); + + // Create an active integration + const [integration] = await db + .insert(slackIntegrations) + .values({ + organizationId: testOrganizationId, + userId: testUserId, + status: 'active', + teamId: testIds.teamId, + teamName: 'Test Workspace', + botUserId: testIds.botUserId, + scope: 'channels:read', + tokenVaultKey: `test-token-${Date.now()}`, + }) + .returning(); + + createdIntegrationIds.push(integration.id); + + // Wait a bit to ensure timestamp difference + await new Promise((resolve) => setTimeout(resolve, 100)); + + // Update last used + await slackHelpers.updateLastUsedAt(integration.id); + + // Verify the update + const [updated] = await db + .select() + .from(slackIntegrations) + .where(eq(slackIntegrations.id, integration.id)); + + expect(updated.lastUsedAt).toBeTruthy(); + expect(new Date(updated.lastUsedAt!).getTime()).toBeGreaterThan( + new Date(updated.createdAt).getTime() + ); + }); + }); + + describe('hasActiveIntegration', () => { + it('should return false when no active integration exists', async () => { + const result = await slackHelpers.hasActiveIntegration(testOrganizationId); + expect(result).toBe(false); + }); + + it('should return true when active integration exists', async () => { + const testIds = generateTestIds(); + + // Create an active integration + const [integration] = await db + .insert(slackIntegrations) + .values({ + organizationId: testOrganizationId, + userId: testUserId, + status: 'active', + teamId: testIds.teamId, + teamName: 'Test Workspace', + }) + .returning(); + + createdIntegrationIds.push(integration.id); + + const result = await slackHelpers.hasActiveIntegration(testOrganizationId); + expect(result).toBe(true); + }); + }); + + describe('getPendingIntegrationByState', () => { + it('should return pending integration with valid state', async () => { + const testIds = generateTestIds(); + + const [integration] = await db + .insert(slackIntegrations) + .values({ + organizationId: testOrganizationId, + userId: testUserId, + status: 'pending', + oauthState: testIds.oauthState, + oauthExpiresAt: new Date(Date.now() + 900000).toISOString(), // 15 mins from now + }) + .returning(); + + createdIntegrationIds.push(integration.id); + + const result = await slackHelpers.getPendingIntegrationByState(testIds.oauthState); + + expect(result).toBeTruthy(); + expect(result?.id).toBe(integration.id); + expect(result?.oauthState).toBe(testIds.oauthState); + }); + + it('should not return expired integrations', async () => { + const testIds = generateTestIds(); + + const [integration] = await db + .insert(slackIntegrations) + .values({ + organizationId: testOrganizationId, + userId: testUserId, + status: 'pending', + oauthState: testIds.oauthState, + oauthExpiresAt: new Date(Date.now() - 60000).toISOString(), // 1 min ago + }) + .returning(); + + createdIntegrationIds.push(integration.id); + + const result = await slackHelpers.getPendingIntegrationByState(testIds.oauthState); + expect(result).toBeNull(); }); }); describe('cleanupExpiredPendingIntegrations', () => { - beforeEach(() => { - // Clear mock calls - mockedTokenStorage.deleteToken.mockClear(); - }); - it('should delete expired pending integrations', async () => { - // Create expired pending integration + const testIds1 = generateTestIds(); + const testIds2 = generateTestIds(); + + // Create an expired pending integration const [expired] = await db .insert(slackIntegrations) .values({ organizationId: testOrganizationId, userId: testUserId, status: 'pending', - oauthState: 'expired-state', - oauthExpiresAt: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago + oauthState: testIds1.oauthState, + oauthExpiresAt: new Date(Date.now() - 60000).toISOString(), // 1 min ago }) .returning(); - // Create valid pending integration + // Create a valid pending integration const [valid] = await db .insert(slackIntegrations) .values({ organizationId: testOrganizationId, userId: testUserId, status: 'pending', - oauthState: 'valid-state', - oauthExpiresAt: new Date(Date.now() + 3600000).toISOString(), // 1 hour from now + oauthState: testIds2.oauthState, + oauthExpiresAt: new Date(Date.now() + 900000).toISOString(), // 15 mins from now }) .returning(); - createdIntegrationIds.push(expired.id, valid.id); + createdIntegrationIds.push(valid.id); // Only track valid one since expired will be deleted + // Run cleanup const deletedCount = await slackHelpers.cleanupExpiredPendingIntegrations(); - expect(deletedCount).toBeGreaterThanOrEqual(1); + + expect(deletedCount).toBe(1); // Verify expired was deleted const [expiredCheck] = await db .select() .from(slackIntegrations) .where(eq(slackIntegrations.id, expired.id)); + expect(expiredCheck).toBeUndefined(); // Verify valid still exists @@ -745,100 +525,8 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Integration Tests', () => { .select() .from(slackIntegrations) .where(eq(slackIntegrations.id, valid.id)); + expect(validCheck).toBeTruthy(); - - // No token vault keys in this test, so deleteToken should not be called - expect(mockedTokenStorage.deleteToken).not.toHaveBeenCalled(); - }); - - it('should clean up vault tokens when deleting expired pending integrations', async () => { - const expiredTokenKey = `expired-token-${Date.now()}`; - const validTokenKey = `valid-token-${Date.now()}`; - - // Create expired pending integration with token vault key - const [expired] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'pending', - oauthState: 'expired-state-with-token', - oauthExpiresAt: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago - tokenVaultKey: expiredTokenKey, - }) - .returning(); - - // Create valid pending integration with token vault key - const [valid] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'pending', - oauthState: 'valid-state-with-token', - oauthExpiresAt: new Date(Date.now() + 3600000).toISOString(), // 1 hour from now - tokenVaultKey: validTokenKey, - }) - .returning(); - - createdIntegrationIds.push(expired.id, valid.id); - - const deletedCount = await slackHelpers.cleanupExpiredPendingIntegrations(); - expect(deletedCount).toBeGreaterThanOrEqual(1); - - // Verify expired was deleted - const [expiredCheck] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, expired.id)); - expect(expiredCheck).toBeUndefined(); - - // Verify valid still exists - const [validCheck] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, valid.id)); - expect(validCheck).toBeTruthy(); - - // Verify vault token cleanup was called only for the expired integration - expect(mockedTokenStorage.deleteToken).toHaveBeenCalledWith(expiredTokenKey); - expect(mockedTokenStorage.deleteToken).not.toHaveBeenCalledWith(validTokenKey); - }); - - it('should handle vault token cleanup errors gracefully', async () => { - const expiredTokenKey = `error-token-${Date.now()}`; - - // Mock deleteToken to throw an error - mockedTokenStorage.deleteToken.mockRejectedValueOnce(new Error('Vault error')); - - // Create expired pending integration with token vault key - const [expired] = await db - .insert(slackIntegrations) - .values({ - organizationId: testOrganizationId, - userId: testUserId, - status: 'pending', - oauthState: 'expired-state-error', - oauthExpiresAt: new Date(Date.now() - 3600000).toISOString(), // 1 hour ago - tokenVaultKey: expiredTokenKey, - }) - .returning(); - - createdIntegrationIds.push(expired.id); - - // Should not throw error even if vault cleanup fails - const deletedCount = await slackHelpers.cleanupExpiredPendingIntegrations(); - expect(deletedCount).toBeGreaterThanOrEqual(1); - - // Verify expired was still deleted despite vault error - const [expiredCheck] = await db - .select() - .from(slackIntegrations) - .where(eq(slackIntegrations.id, expired.id)); - expect(expiredCheck).toBeUndefined(); - - // Verify vault token cleanup was attempted - expect(mockedTokenStorage.deleteToken).toHaveBeenCalledWith(expiredTokenKey); }); }); }); diff --git a/packages/database/drizzle/0073_add_default_channel_to_slack_integrations.sql b/packages/database/drizzle/0073_add_default_channel_to_slack_integrations.sql deleted file mode 100644 index 77bf88324..000000000 --- a/packages/database/drizzle/0073_add_default_channel_to_slack_integrations.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Add default_channel column to slack_integrations table -ALTER TABLE "slack_integrations" -ADD COLUMN "default_channel" jsonb DEFAULT '{}'::jsonb; \ No newline at end of file diff --git a/packages/database/drizzle/0073_lovely_white_tiger.sql b/packages/database/drizzle/0073_lovely_white_tiger.sql new file mode 100644 index 000000000..94cd09bc1 --- /dev/null +++ b/packages/database/drizzle/0073_lovely_white_tiger.sql @@ -0,0 +1 @@ +ALTER TABLE "slack_integrations" ADD COLUMN "default_channel" jsonb DEFAULT '{}'::jsonb; \ No newline at end of file diff --git a/packages/database/drizzle/meta/0073_snapshot.json b/packages/database/drizzle/meta/0073_snapshot.json new file mode 100644 index 000000000..6a01077b2 --- /dev/null +++ b/packages/database/drizzle/meta/0073_snapshot.json @@ -0,0 +1,6164 @@ +{ + "id": "c588dcb2-6347-40e9-89a8-39036d3bbcc9", + "prevId": "6aa829b8-9bfc-47f7-8ad3-5990622cf244", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_organization_id_fkey": { + "name": "api_keys_organization_id_fkey", + "tableFrom": "api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_keys_owner_id_fkey": { + "name": "api_keys_owner_id_fkey", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_keys_key_key": { + "name": "api_keys_key_key", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.asset_permissions": { + "name": "asset_permissions", + "schema": "", + "columns": { + "identity_id": { + "name": "identity_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_type": { + "name": "identity_type", + "type": "identity_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_type": { + "name": "asset_type", + "type": "asset_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "asset_permission_role_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "asset_permissions_created_by_fkey": { + "name": "asset_permissions_created_by_fkey", + "tableFrom": "asset_permissions", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "asset_permissions_updated_by_fkey": { + "name": "asset_permissions_updated_by_fkey", + "tableFrom": "asset_permissions", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "asset_permissions_pkey": { + "name": "asset_permissions_pkey", + "columns": [ + "identity_id", + "identity_type", + "asset_id", + "asset_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.asset_search": { + "name": "asset_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_type": { + "name": "asset_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "asset_search_asset_id_asset_type_idx": { + "name": "asset_search_asset_id_asset_type_idx", + "columns": [ + { + "expression": "asset_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "asset_type", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pgroonga_content_index": { + "name": "pgroonga_content_index", + "columns": [ + { + "expression": "content", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "pgroonga_text_full_text_search_ops_v2" + } + ], + "isUnique": false, + "concurrently": false, + "method": "pgroonga", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chats": { + "name": "chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "most_recent_file_id": { + "name": "most_recent_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "most_recent_file_type": { + "name": "most_recent_file_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "most_recent_version_number": { + "name": "most_recent_version_number", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "chats_created_at_idx": { + "name": "chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chats_created_by_idx": { + "name": "chats_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "chats_organization_id_idx": { + "name": "chats_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chats_most_recent_file_id": { + "name": "idx_chats_most_recent_file_id", + "columns": [ + { + "expression": "most_recent_file_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chats_most_recent_file_type": { + "name": "idx_chats_most_recent_file_type", + "columns": [ + { + "expression": "most_recent_file_type", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chats_organization_id_fkey": { + "name": "chats_organization_id_fkey", + "tableFrom": "chats", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "chats_created_by_fkey": { + "name": "chats_created_by_fkey", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "chats_updated_by_fkey": { + "name": "chats_updated_by_fkey", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "chats_publicly_enabled_by_fkey": { + "name": "chats_publicly_enabled_by_fkey", + "tableFrom": "chats", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collections": { + "name": "collections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "collections_organization_id_fkey": { + "name": "collections_organization_id_fkey", + "tableFrom": "collections", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "collections_created_by_fkey": { + "name": "collections_created_by_fkey", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "collections_updated_by_fkey": { + "name": "collections_updated_by_fkey", + "tableFrom": "collections", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.collections_to_assets": { + "name": "collections_to_assets", + "schema": "", + "columns": { + "collection_id": { + "name": "collection_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_type": { + "name": "asset_type", + "type": "asset_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "collections_to_assets_created_by_fkey": { + "name": "collections_to_assets_created_by_fkey", + "tableFrom": "collections_to_assets", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "collections_to_assets_updated_by_fkey": { + "name": "collections_to_assets_updated_by_fkey", + "tableFrom": "collections_to_assets", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "collections_to_assets_pkey": { + "name": "collections_to_assets_pkey", + "columns": [ + "collection_id", + "asset_id", + "asset_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_files": { + "name": "dashboard_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "filter": { + "name": "filter", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "version_history": { + "name": "version_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "public_password": { + "name": "public_password", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dashboard_files_created_by_idx": { + "name": "dashboard_files_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dashboard_files_deleted_at_idx": { + "name": "dashboard_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dashboard_files_organization_id_idx": { + "name": "dashboard_files_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dashboard_files_created_by_fkey": { + "name": "dashboard_files_created_by_fkey", + "tableFrom": "dashboard_files", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "dashboard_files_publicly_enabled_by_fkey": { + "name": "dashboard_files_publicly_enabled_by_fkey", + "tableFrom": "dashboard_files", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard_versions": { + "name": "dashboard_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "dashboard_versions_dashboard_id_fkey": { + "name": "dashboard_versions_dashboard_id_fkey", + "tableFrom": "dashboard_versions", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboards": { + "name": "dashboards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "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 + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "password_secret_id": { + "name": "password_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "dashboards_publicly_enabled_by_fkey": { + "name": "dashboards_publicly_enabled_by_fkey", + "tableFrom": "dashboards", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "dashboards_organization_id_fkey": { + "name": "dashboards_organization_id_fkey", + "tableFrom": "dashboards", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "dashboards_created_by_fkey": { + "name": "dashboards_created_by_fkey", + "tableFrom": "dashboards", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "dashboards_updated_by_fkey": { + "name": "dashboards_updated_by_fkey", + "tableFrom": "dashboards", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_sources": { + "name": "data_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_id": { + "name": "secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "onboarding_status": { + "name": "onboarding_status", + "type": "data_source_onboarding_status_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'notStarted'" + }, + "onboarding_error": { + "name": "onboarding_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "env": { + "name": "env", + "type": "varchar", + "primaryKey": false, + "notNull": true, + "default": "'dev'" + } + }, + "indexes": {}, + "foreignKeys": { + "data_sources_organization_id_fkey": { + "name": "data_sources_organization_id_fkey", + "tableFrom": "data_sources", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_sources_created_by_fkey": { + "name": "data_sources_created_by_fkey", + "tableFrom": "data_sources", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "data_sources_updated_by_fkey": { + "name": "data_sources_updated_by_fkey", + "tableFrom": "data_sources", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "data_sources_name_organization_id_env_key": { + "name": "data_sources_name_organization_id_env_key", + "nullsNotDistinct": false, + "columns": [ + "name", + "organization_id", + "env" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.database_metadata": { + "name": "database_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "database_metadata_data_source_id_idx": { + "name": "database_metadata_data_source_id_idx", + "columns": [ + { + "expression": "data_source_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "database_metadata_data_source_id_fkey": { + "name": "database_metadata_data_source_id_fkey", + "tableFrom": "database_metadata", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "database_metadata_data_source_id_name_key": { + "name": "database_metadata_data_source_id_name_key", + "nullsNotDistinct": false, + "columns": [ + "data_source_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_columns": { + "name": "dataset_columns", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "nullable": { + "name": "nullable", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stored_values": { + "name": "stored_values", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "stored_values_status": { + "name": "stored_values_status", + "type": "stored_values_status_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "stored_values_error": { + "name": "stored_values_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stored_values_count": { + "name": "stored_values_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "stored_values_last_synced": { + "name": "stored_values_last_synced", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "semantic_type": { + "name": "semantic_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dim_type": { + "name": "dim_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expr": { + "name": "expr", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_dataset_column_name": { + "name": "unique_dataset_column_name", + "nullsNotDistinct": false, + "columns": [ + "dataset_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_groups": { + "name": "dataset_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dataset_groups_deleted_at_idx": { + "name": "dataset_groups_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_groups_organization_id_idx": { + "name": "dataset_groups_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dataset_groups_organization_id_fkey": { + "name": "dataset_groups_organization_id_fkey", + "tableFrom": "dataset_groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "dataset_groups_policy": { + "name": "dataset_groups_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_groups_permissions": { + "name": "dataset_groups_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "dataset_group_id": { + "name": "dataset_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_id": { + "name": "permission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dataset_groups_permissions_dataset_group_id_idx": { + "name": "dataset_groups_permissions_dataset_group_id_idx", + "columns": [ + { + "expression": "dataset_group_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_groups_permissions_organization_id_idx": { + "name": "dataset_groups_permissions_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_groups_permissions_permission_id_idx": { + "name": "dataset_groups_permissions_permission_id_idx", + "columns": [ + { + "expression": "permission_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dataset_groups_permissions_dataset_group_id_fkey": { + "name": "dataset_groups_permissions_dataset_group_id_fkey", + "tableFrom": "dataset_groups_permissions", + "tableTo": "dataset_groups", + "columnsFrom": [ + "dataset_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "dataset_groups_permissions_organization_id_fkey": { + "name": "dataset_groups_permissions_organization_id_fkey", + "tableFrom": "dataset_groups_permissions", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_dataset_group_permission": { + "name": "unique_dataset_group_permission", + "nullsNotDistinct": false, + "columns": [ + "dataset_group_id", + "permission_id", + "permission_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dataset_permissions": { + "name": "dataset_permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_id": { + "name": "permission_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "dataset_permissions_dataset_id_idx": { + "name": "dataset_permissions_dataset_id_idx", + "columns": [ + { + "expression": "dataset_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_permissions_deleted_at_idx": { + "name": "dataset_permissions_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_permissions_organization_id_idx": { + "name": "dataset_permissions_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "dataset_permissions_permission_lookup_idx": { + "name": "dataset_permissions_permission_lookup_idx", + "columns": [ + { + "expression": "permission_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dataset_permissions_organization_id_fkey": { + "name": "dataset_permissions_organization_id_fkey", + "tableFrom": "dataset_permissions", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dataset_permissions_dataset_id_fkey": { + "name": "dataset_permissions_dataset_id_fkey", + "tableFrom": "dataset_permissions", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "dataset_permissions_dataset_id_permission_id_permission_typ_key": { + "name": "dataset_permissions_dataset_id_permission_id_permission_typ_key", + "nullsNotDistinct": false, + "columns": [ + "dataset_id", + "permission_id", + "permission_type" + ] + } + }, + "policies": { + "dataset_permissions_policy": { + "name": "dataset_permissions_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": { + "dataset_permissions_permission_type_check": { + "name": "dataset_permissions_permission_type_check", + "value": "(permission_type)::text = ANY ((ARRAY['user'::character varying, 'dataset_group'::character varying, 'permission_group'::character varying])::text[])" + } + }, + "isRLSEnabled": false + }, + "public.datasets": { + "name": "datasets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "database_name": { + "name": "database_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "when_to_use": { + "name": "when_to_use", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "when_not_to_use": { + "name": "when_not_to_use", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "dataset_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "imported": { + "name": "imported", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "yml_file": { + "name": "yml_file", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "database_identifier": { + "name": "database_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "datasets_data_source_id_fkey": { + "name": "datasets_data_source_id_fkey", + "tableFrom": "datasets", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datasets_organization_id_fkey": { + "name": "datasets_organization_id_fkey", + "tableFrom": "datasets", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datasets_created_by_fkey": { + "name": "datasets_created_by_fkey", + "tableFrom": "datasets", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "datasets_updated_by_fkey": { + "name": "datasets_updated_by_fkey", + "tableFrom": "datasets", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "datasets_database_name_data_source_id_key": { + "name": "datasets_database_name_data_source_id_key", + "nullsNotDistinct": false, + "columns": [ + "database_name", + "data_source_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.datasets_to_dataset_groups": { + "name": "datasets_to_dataset_groups", + "schema": "", + "columns": { + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dataset_group_id": { + "name": "dataset_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "datasets_to_dataset_groups_dataset_group_id_idx": { + "name": "datasets_to_dataset_groups_dataset_group_id_idx", + "columns": [ + { + "expression": "dataset_group_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "datasets_to_dataset_groups_dataset_id_fkey": { + "name": "datasets_to_dataset_groups_dataset_id_fkey", + "tableFrom": "datasets_to_dataset_groups", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datasets_to_dataset_groups_dataset_group_id_fkey": { + "name": "datasets_to_dataset_groups_dataset_group_id_fkey", + "tableFrom": "datasets_to_dataset_groups", + "tableTo": "dataset_groups", + "columnsFrom": [ + "dataset_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "datasets_to_dataset_groups_pkey": { + "name": "datasets_to_dataset_groups_pkey", + "columns": [ + "dataset_id", + "dataset_group_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": { + "datasets_to_dataset_groups_policy": { + "name": "datasets_to_dataset_groups_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.datasets_to_permission_groups": { + "name": "datasets_to_permission_groups", + "schema": "", + "columns": { + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "datasets_to_permission_groups_dataset_id_fkey": { + "name": "datasets_to_permission_groups_dataset_id_fkey", + "tableFrom": "datasets_to_permission_groups", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "datasets_to_permission_groups_permission_group_id_fkey": { + "name": "datasets_to_permission_groups_permission_group_id_fkey", + "tableFrom": "datasets_to_permission_groups", + "tableTo": "permission_groups", + "columnsFrom": [ + "permission_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "datasets_to_permission_groups_pkey": { + "name": "datasets_to_permission_groups_pkey", + "columns": [ + "dataset_id", + "permission_group_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": { + "datasets_to_permission_groups_policy": { + "name": "datasets_to_permission_groups_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.__diesel_schema_migrations": { + "name": "__diesel_schema_migrations", + "schema": "", + "columns": { + "version": { + "name": "version", + "type": "varchar(50)", + "primaryKey": true, + "notNull": true + }, + "run_on": { + "name": "run_on", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": { + "diesel_schema_migrations_policy": { + "name": "diesel_schema_migrations_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.entity_relationship": { + "name": "entity_relationship", + "schema": "", + "columns": { + "primary_dataset_id": { + "name": "primary_dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "foreign_dataset_id": { + "name": "foreign_dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "relationship_type": { + "name": "relationship_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "entity_relationship_pkey": { + "name": "entity_relationship_pkey", + "columns": [ + "primary_dataset_id", + "foreign_dataset_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "request_message": { + "name": "request_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_messages": { + "name": "response_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "reasoning": { + "name": "reasoning", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "raw_llm_messages": { + "name": "raw_llm_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "final_reasoning_message": { + "name": "final_reasoning_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_completed": { + "name": "is_completed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "messages_chat_id_idx": { + "name": "messages_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_created_at_idx": { + "name": "messages_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_created_by_idx": { + "name": "messages_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_chat_id_fkey": { + "name": "messages_chat_id_fkey", + "tableFrom": "messages", + "tableTo": "chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_created_by_fkey": { + "name": "messages_created_by_fkey", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages_deprecated": { + "name": "messages_deprecated", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sent_by": { + "name": "sent_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "responses": { + "name": "responses", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback": { + "name": "feedback", + "type": "message_feedback_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "verification": { + "name": "verification", + "type": "verification_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'notRequested'" + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chart_config": { + "name": "chart_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "chart_recommendations": { + "name": "chart_recommendations", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "time_frame": { + "name": "time_frame", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_metadata": { + "name": "data_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "draft_session_id": { + "name": "draft_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "draft_state": { + "name": "draft_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "summary_question": { + "name": "summary_question", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sql_evaluation_id": { + "name": "sql_evaluation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "messages_sent_by_fkey": { + "name": "messages_sent_by_fkey", + "tableFrom": "messages_deprecated", + "tableTo": "users", + "columnsFrom": [ + "sent_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "messages_dataset_id_fkey": { + "name": "messages_dataset_id_fkey", + "tableFrom": "messages_deprecated", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_deprecated_sent_by_fkey": { + "name": "messages_deprecated_sent_by_fkey", + "tableFrom": "messages_deprecated", + "tableTo": "users", + "columnsFrom": [ + "sent_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages_to_files": { + "name": "messages_to_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "is_duplicate": { + "name": "is_duplicate", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "version_number": { + "name": "version_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": { + "messages_files_file_id_idx": { + "name": "messages_files_file_id_idx", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_files_message_id_idx": { + "name": "messages_files_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_to_files_message_id_fkey": { + "name": "messages_to_files_message_id_fkey", + "tableFrom": "messages_to_files", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "messages_to_files_message_id_file_id_key": { + "name": "messages_to_files_message_id_file_id_key", + "nullsNotDistinct": false, + "columns": [ + "message_id", + "file_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages_to_slack_messages": { + "name": "messages_to_slack_messages", + "schema": "", + "columns": { + "message_id": { + "name": "message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slack_message_id": { + "name": "slack_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "messages_to_slack_messages_message_id_idx": { + "name": "messages_to_slack_messages_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "messages_to_slack_messages_slack_message_id_idx": { + "name": "messages_to_slack_messages_slack_message_id_idx", + "columns": [ + { + "expression": "slack_message_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "messages_to_slack_messages_message_id_fkey": { + "name": "messages_to_slack_messages_message_id_fkey", + "tableFrom": "messages_to_slack_messages", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_to_slack_messages_slack_message_id_fkey": { + "name": "messages_to_slack_messages_slack_message_id_fkey", + "tableFrom": "messages_to_slack_messages", + "tableTo": "slack_message_tracking", + "columnsFrom": [ + "slack_message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "messages_to_slack_messages_pkey": { + "name": "messages_to_slack_messages_pkey", + "columns": [ + "message_id", + "slack_message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.metric_files": { + "name": "metric_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "file_name": { + "name": "file_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "verification": { + "name": "verification", + "type": "verification_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'notRequested'" + }, + "evaluation_obj": { + "name": "evaluation_obj", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "evaluation_summary": { + "name": "evaluation_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "evaluation_score": { + "name": "evaluation_score", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "version_history": { + "name": "version_history", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "data_metadata": { + "name": "data_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "public_password": { + "name": "public_password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "metric_files_created_by_idx": { + "name": "metric_files_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "metric_files_data_metadata_idx": { + "name": "metric_files_data_metadata_idx", + "columns": [ + { + "expression": "data_metadata", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "jsonb_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "metric_files_deleted_at_idx": { + "name": "metric_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "metric_files_organization_id_idx": { + "name": "metric_files_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "metric_files_created_by_fkey": { + "name": "metric_files_created_by_fkey", + "tableFrom": "metric_files", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "metric_files_publicly_enabled_by_fkey": { + "name": "metric_files_publicly_enabled_by_fkey", + "tableFrom": "metric_files", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "fk_data_source": { + "name": "fk_data_source", + "tableFrom": "metric_files", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.metric_files_to_dashboard_files": { + "name": "metric_files_to_dashboard_files", + "schema": "", + "columns": { + "metric_file_id": { + "name": "metric_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dashboard_file_id": { + "name": "dashboard_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "CURRENT_TIMESTAMP" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "metric_files_to_dashboard_files_dashboard_id_idx": { + "name": "metric_files_to_dashboard_files_dashboard_id_idx", + "columns": [ + { + "expression": "dashboard_file_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "metric_files_to_dashboard_files_deleted_at_idx": { + "name": "metric_files_to_dashboard_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "metric_files_to_dashboard_files_metric_id_idx": { + "name": "metric_files_to_dashboard_files_metric_id_idx", + "columns": [ + { + "expression": "metric_file_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "metric_files_to_dashboard_files_metric_file_id_fkey": { + "name": "metric_files_to_dashboard_files_metric_file_id_fkey", + "tableFrom": "metric_files_to_dashboard_files", + "tableTo": "metric_files", + "columnsFrom": [ + "metric_file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "metric_files_to_dashboard_files_dashboard_file_id_fkey": { + "name": "metric_files_to_dashboard_files_dashboard_file_id_fkey", + "tableFrom": "metric_files_to_dashboard_files", + "tableTo": "dashboard_files", + "columnsFrom": [ + "dashboard_file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "metric_files_to_dashboard_files_created_by_fkey": { + "name": "metric_files_to_dashboard_files_created_by_fkey", + "tableFrom": "metric_files_to_dashboard_files", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "metric_files_to_dashboard_files_pkey": { + "name": "metric_files_to_dashboard_files_pkey", + "columns": [ + "metric_file_id", + "dashboard_file_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.metric_files_to_datasets": { + "name": "metric_files_to_datasets", + "schema": "", + "columns": { + "metric_file_id": { + "name": "metric_file_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "metric_version_number": { + "name": "metric_version_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "fk_metric_file": { + "name": "fk_metric_file", + "tableFrom": "metric_files_to_datasets", + "tableTo": "metric_files", + "columnsFrom": [ + "metric_file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "fk_dataset": { + "name": "fk_dataset", + "tableFrom": "metric_files_to_datasets", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "metric_files_to_datasets_pkey": { + "name": "metric_files_to_datasets_pkey", + "columns": [ + "metric_file_id", + "dataset_id", + "metric_version_number" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payment_required": { + "name": "payment_required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "organizations_name_key": { + "name": "organizations_name_key", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_groups": { + "name": "permission_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "permission_groups_organization_id_fkey": { + "name": "permission_groups_organization_id_fkey", + "tableFrom": "permission_groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_groups_created_by_fkey": { + "name": "permission_groups_created_by_fkey", + "tableFrom": "permission_groups", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "permission_groups_updated_by_fkey": { + "name": "permission_groups_updated_by_fkey", + "tableFrom": "permission_groups", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_groups_to_identities": { + "name": "permission_groups_to_identities", + "schema": "", + "columns": { + "permission_group_id": { + "name": "permission_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_id": { + "name": "identity_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "identity_type": { + "name": "identity_type", + "type": "identity_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "permission_groups_to_identities_created_by_fkey": { + "name": "permission_groups_to_identities_created_by_fkey", + "tableFrom": "permission_groups_to_identities", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "permission_groups_to_identities_updated_by_fkey": { + "name": "permission_groups_to_identities_updated_by_fkey", + "tableFrom": "permission_groups_to_identities", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "permission_groups_to_identities_pkey": { + "name": "permission_groups_to_identities_pkey", + "columns": [ + "permission_group_id", + "identity_id", + "identity_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_groups_to_users": { + "name": "permission_groups_to_users", + "schema": "", + "columns": { + "permission_group_id": { + "name": "permission_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_groups_to_users_user_id_idx": { + "name": "permission_groups_to_users_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_groups_to_users_permission_group_id_fkey": { + "name": "permission_groups_to_users_permission_group_id_fkey", + "tableFrom": "permission_groups_to_users", + "tableTo": "permission_groups", + "columnsFrom": [ + "permission_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_groups_to_users_user_id_fkey": { + "name": "permission_groups_to_users_user_id_fkey", + "tableFrom": "permission_groups_to_users", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "permission_groups_to_users_pkey": { + "name": "permission_groups_to_users_pkey", + "columns": [ + "permission_group_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": { + "permission_groups_to_users_policy": { + "name": "permission_groups_to_users_policy", + "as": "PERMISSIVE", + "for": "ALL", + "to": [ + "authenticated" + ], + "using": "true" + } + }, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.schema_metadata": { + "name": "schema_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "database_id": { + "name": "database_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "database_name": { + "name": "database_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "schema_metadata_data_source_id_idx": { + "name": "schema_metadata_data_source_id_idx", + "columns": [ + { + "expression": "data_source_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "schema_metadata_database_id_idx": { + "name": "schema_metadata_database_id_idx", + "columns": [ + { + "expression": "database_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "schema_metadata_data_source_id_fkey": { + "name": "schema_metadata_data_source_id_fkey", + "tableFrom": "schema_metadata", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "schema_metadata_database_id_fkey": { + "name": "schema_metadata_database_id_fkey", + "tableFrom": "schema_metadata", + "tableTo": "database_metadata", + "columnsFrom": [ + "database_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "schema_metadata_data_source_id_database_id_name_key": { + "name": "schema_metadata_data_source_id_database_id_name_key", + "nullsNotDistinct": false, + "columns": [ + "data_source_id", + "database_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_integrations": { + "name": "slack_integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "oauth_state": { + "name": "oauth_state", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "oauth_expires_at": { + "name": "oauth_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "oauth_metadata": { + "name": "oauth_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "team_id": { + "name": "team_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "team_name": { + "name": "team_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "team_domain": { + "name": "team_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "enterprise_id": { + "name": "enterprise_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_vault_key": { + "name": "token_vault_key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "installed_by_slack_user_id": { + "name": "installed_by_slack_user_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "slack_integration_status_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "default_channel": { + "name": "default_channel", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_slack_integrations_org_id": { + "name": "idx_slack_integrations_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_integrations_team_id": { + "name": "idx_slack_integrations_team_id", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_integrations_oauth_state": { + "name": "idx_slack_integrations_oauth_state", + "columns": [ + { + "expression": "oauth_state", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_integrations_oauth_expires": { + "name": "idx_slack_integrations_oauth_expires", + "columns": [ + { + "expression": "oauth_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "timestamptz_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_integrations_organization_id_fkey": { + "name": "slack_integrations_organization_id_fkey", + "tableFrom": "slack_integrations", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_integrations_user_id_fkey": { + "name": "slack_integrations_user_id_fkey", + "tableFrom": "slack_integrations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "slack_integrations_oauth_state_unique": { + "name": "slack_integrations_oauth_state_unique", + "nullsNotDistinct": false, + "columns": [ + "oauth_state" + ] + }, + "slack_integrations_token_vault_key_unique": { + "name": "slack_integrations_token_vault_key_unique", + "nullsNotDistinct": false, + "columns": [ + "token_vault_key" + ] + }, + "slack_integrations_org_team_key": { + "name": "slack_integrations_org_team_key", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "team_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "slack_integrations_status_check": { + "name": "slack_integrations_status_check", + "value": "(status = 'pending' AND oauth_state IS NOT NULL) OR (status != 'pending' AND team_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.slack_message_tracking": { + "name": "slack_message_tracking", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "integration_id": { + "name": "integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "internal_message_id": { + "name": "internal_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slack_message_ts": { + "name": "slack_message_ts", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "slack_thread_ts": { + "name": "slack_thread_ts", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "message_type": { + "name": "message_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_info": { + "name": "sender_info", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_message_tracking_integration": { + "name": "idx_message_tracking_integration", + "columns": [ + { + "expression": "integration_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_tracking_channel": { + "name": "idx_message_tracking_channel", + "columns": [ + { + "expression": "slack_channel_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_tracking_thread": { + "name": "idx_message_tracking_thread", + "columns": [ + { + "expression": "slack_thread_ts", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_message_tracking_integration_id_fkey": { + "name": "slack_message_tracking_integration_id_fkey", + "tableFrom": "slack_message_tracking", + "tableTo": "slack_integrations", + "columnsFrom": [ + "integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "slack_message_tracking_internal_message_id_unique": { + "name": "slack_message_tracking_internal_message_id_unique", + "nullsNotDistinct": false, + "columns": [ + "internal_message_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sql_evaluations": { + "name": "sql_evaluations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "uuid_generate_v4()" + }, + "evaluation_obj": { + "name": "evaluation_obj", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "evaluation_summary": { + "name": "evaluation_summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "score": { + "name": "score", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.stored_values_sync_jobs": { + "name": "stored_values_sync_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "database_name": { + "name": "database_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_name": { + "name": "schema_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "column_name": { + "name": "column_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_stored_values_sync_jobs_data_source_id": { + "name": "idx_stored_values_sync_jobs_data_source_id", + "columns": [ + { + "expression": "data_source_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_stored_values_sync_jobs_db_schema_table_column": { + "name": "idx_stored_values_sync_jobs_db_schema_table_column", + "columns": [ + { + "expression": "database_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "schema_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "table_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "column_name", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_stored_values_sync_jobs_status": { + "name": "idx_stored_values_sync_jobs_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stored_values_sync_jobs_data_source_id_fkey": { + "name": "stored_values_sync_jobs_data_source_id_fkey", + "tableFrom": "stored_values_sync_jobs", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_metadata": { + "name": "table_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "data_source_id": { + "name": "data_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "database_id": { + "name": "database_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "schema_id": { + "name": "schema_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_name": { + "name": "schema_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "database_name": { + "name": "database_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "table_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "row_count": { + "name": "row_count", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created": { + "name": "created", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_modified": { + "name": "last_modified", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "clustering_keys": { + "name": "clustering_keys", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "columns": { + "name": "columns", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_metadata_data_source_id_idx": { + "name": "table_metadata_data_source_id_idx", + "columns": [ + { + "expression": "data_source_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_metadata_database_id_idx": { + "name": "table_metadata_database_id_idx", + "columns": [ + { + "expression": "database_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_metadata_schema_id_idx": { + "name": "table_metadata_schema_id_idx", + "columns": [ + { + "expression": "schema_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "uuid_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_metadata_data_source_id_fkey": { + "name": "table_metadata_data_source_id_fkey", + "tableFrom": "table_metadata", + "tableTo": "data_sources", + "columnsFrom": [ + "data_source_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_metadata_database_id_fkey": { + "name": "table_metadata_database_id_fkey", + "tableFrom": "table_metadata", + "tableTo": "database_metadata", + "columnsFrom": [ + "database_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_metadata_schema_id_fkey": { + "name": "table_metadata_schema_id_fkey", + "tableFrom": "table_metadata", + "tableTo": "schema_metadata", + "columnsFrom": [ + "schema_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "table_metadata_data_source_id_schema_id_name_key": { + "name": "table_metadata_data_source_id_schema_id_name_key", + "nullsNotDistinct": false, + "columns": [ + "data_source_id", + "schema_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams": { + "name": "teams", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sharing_setting": { + "name": "sharing_setting", + "type": "sharing_setting_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "edit_sql": { + "name": "edit_sql", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upload_csv": { + "name": "upload_csv", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "export_assets": { + "name": "export_assets", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_slack_enabled": { + "name": "email_slack_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "teams_organization_id_fkey": { + "name": "teams_organization_id_fkey", + "tableFrom": "teams", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_created_by_fkey": { + "name": "teams_created_by_fkey", + "tableFrom": "teams", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "teams_name_key": { + "name": "teams_name_key", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.teams_to_users": { + "name": "teams_to_users", + "schema": "", + "columns": { + "team_id": { + "name": "team_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "team_role_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "teams_to_users_team_id_fkey": { + "name": "teams_to_users_team_id_fkey", + "tableFrom": "teams_to_users", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "teams_to_users_user_id_fkey": { + "name": "teams_to_users_user_id_fkey", + "tableFrom": "teams_to_users", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "teams_to_users_pkey": { + "name": "teams_to_users_pkey", + "columns": [ + "team_id", + "user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.terms": { + "name": "terms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sql_snippet": { + "name": "sql_snippet", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "terms_organization_id_fkey": { + "name": "terms_organization_id_fkey", + "tableFrom": "terms", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "terms_created_by_fkey": { + "name": "terms_created_by_fkey", + "tableFrom": "terms", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "terms_updated_by_fkey": { + "name": "terms_updated_by_fkey", + "tableFrom": "terms", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.terms_to_datasets": { + "name": "terms_to_datasets", + "schema": "", + "columns": { + "term_id": { + "name": "term_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dataset_id": { + "name": "dataset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "terms_to_datasets_term_id_fkey": { + "name": "terms_to_datasets_term_id_fkey", + "tableFrom": "terms_to_datasets", + "tableTo": "terms", + "columnsFrom": [ + "term_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "terms_to_datasets_dataset_id_fkey": { + "name": "terms_to_datasets_dataset_id_fkey", + "tableFrom": "terms_to_datasets", + "tableTo": "datasets", + "columnsFrom": [ + "dataset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "terms_to_datasets_pkey": { + "name": "terms_to_datasets_pkey", + "columns": [ + "term_id", + "dataset_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.threads_deprecated": { + "name": "threads_deprecated", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "publicly_accessible": { + "name": "publicly_accessible", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "publicly_enabled_by": { + "name": "publicly_enabled_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "public_expiry_date": { + "name": "public_expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "password_secret_id": { + "name": "password_secret_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "state_message_id": { + "name": "state_message_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_thread_id": { + "name": "parent_thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "threads_created_by_fkey": { + "name": "threads_created_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_updated_by_fkey": { + "name": "threads_updated_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_publicly_enabled_by_fkey": { + "name": "threads_publicly_enabled_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_parent_thread_id_fkey": { + "name": "threads_parent_thread_id_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "threads_deprecated", + "columnsFrom": [ + "parent_thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_organization_id_fkey": { + "name": "threads_organization_id_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "threads_deprecated_created_by_fkey": { + "name": "threads_deprecated_created_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_deprecated_updated_by_fkey": { + "name": "threads_deprecated_updated_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "threads_deprecated_publicly_enabled_by_fkey": { + "name": "threads_deprecated_publicly_enabled_by_fkey", + "tableFrom": "threads_deprecated", + "tableTo": "users", + "columnsFrom": [ + "publicly_enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.threads_to_dashboards": { + "name": "threads_to_dashboards", + "schema": "", + "columns": { + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dashboard_id": { + "name": "dashboard_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "added_by": { + "name": "added_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "threads_to_dashboards_thread_id_fkey": { + "name": "threads_to_dashboards_thread_id_fkey", + "tableFrom": "threads_to_dashboards", + "tableTo": "threads_deprecated", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "threads_to_dashboards_dashboard_id_fkey": { + "name": "threads_to_dashboards_dashboard_id_fkey", + "tableFrom": "threads_to_dashboards", + "tableTo": "dashboards", + "columnsFrom": [ + "dashboard_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "threads_to_dashboards_added_by_fkey": { + "name": "threads_to_dashboards_added_by_fkey", + "tableFrom": "threads_to_dashboards", + "tableTo": "users", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "threads_to_dashboards_pkey": { + "name": "threads_to_dashboards_pkey", + "columns": [ + "thread_id", + "dashboard_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_favorites": { + "name": "user_favorites", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_id": { + "name": "asset_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "asset_type": { + "name": "asset_type", + "type": "asset_type_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "order_index": { + "name": "order_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_favorites_user_id_fkey": { + "name": "user_favorites_user_id_fkey", + "tableFrom": "user_favorites", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "user_favorites_pkey": { + "name": "user_favorites_pkey", + "columns": [ + "user_id", + "asset_id", + "asset_type" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attributes": { + "name": "attributes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_key": { + "name": "users_email_key", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users_to_organizations": { + "name": "users_to_organizations", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "user_organization_role_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'querier'" + }, + "sharing_setting": { + "name": "sharing_setting", + "type": "sharing_setting_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "edit_sql": { + "name": "edit_sql", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upload_csv": { + "name": "upload_csv", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "export_assets": { + "name": "export_assets", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_slack_enabled": { + "name": "email_slack_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "updated_by": { + "name": "updated_by", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "deleted_by": { + "name": "deleted_by", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "user_organization_status_enum", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + } + }, + "indexes": {}, + "foreignKeys": { + "users_to_organizations_organization_id_fkey": { + "name": "users_to_organizations_organization_id_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "users_to_organizations_user_id_fkey": { + "name": "users_to_organizations_user_id_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "users_to_organizations_created_by_fkey": { + "name": "users_to_organizations_created_by_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "users_to_organizations_updated_by_fkey": { + "name": "users_to_organizations_updated_by_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + }, + "users_to_organizations_deleted_by_fkey": { + "name": "users_to_organizations_deleted_by_fkey", + "tableFrom": "users_to_organizations", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": { + "users_to_organizations_pkey": { + "name": "users_to_organizations_pkey", + "columns": [ + "user_id", + "organization_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.asset_permission_role_enum": { + "name": "asset_permission_role_enum", + "schema": "public", + "values": [ + "owner", + "editor", + "viewer", + "full_access", + "can_edit", + "can_filter", + "can_view" + ] + }, + "public.asset_type_enum": { + "name": "asset_type_enum", + "schema": "public", + "values": [ + "dashboard", + "thread", + "collection", + "chat", + "metric_file", + "dashboard_file" + ] + }, + "public.data_source_onboarding_status_enum": { + "name": "data_source_onboarding_status_enum", + "schema": "public", + "values": [ + "notStarted", + "inProgress", + "completed", + "failed" + ] + }, + "public.dataset_type_enum": { + "name": "dataset_type_enum", + "schema": "public", + "values": [ + "table", + "view", + "materializedView" + ] + }, + "public.identity_type_enum": { + "name": "identity_type_enum", + "schema": "public", + "values": [ + "user", + "team", + "organization" + ] + }, + "public.message_feedback_enum": { + "name": "message_feedback_enum", + "schema": "public", + "values": [ + "positive", + "negative" + ] + }, + "public.sharing_setting_enum": { + "name": "sharing_setting_enum", + "schema": "public", + "values": [ + "none", + "team", + "organization", + "public" + ] + }, + "public.slack_integration_status_enum": { + "name": "slack_integration_status_enum", + "schema": "public", + "values": [ + "pending", + "active", + "failed", + "revoked" + ] + }, + "public.stored_values_status_enum": { + "name": "stored_values_status_enum", + "schema": "public", + "values": [ + "syncing", + "success", + "failed" + ] + }, + "public.table_type_enum": { + "name": "table_type_enum", + "schema": "public", + "values": [ + "TABLE", + "VIEW", + "MATERIALIZED_VIEW", + "EXTERNAL_TABLE", + "TEMPORARY_TABLE" + ] + }, + "public.team_role_enum": { + "name": "team_role_enum", + "schema": "public", + "values": [ + "manager", + "member" + ] + }, + "public.user_organization_role_enum": { + "name": "user_organization_role_enum", + "schema": "public", + "values": [ + "workspace_admin", + "data_admin", + "querier", + "restricted_querier", + "viewer" + ] + }, + "public.user_organization_status_enum": { + "name": "user_organization_status_enum", + "schema": "public", + "values": [ + "active", + "inactive", + "pending", + "guest" + ] + }, + "public.verification_enum": { + "name": "verification_enum", + "schema": "public", + "values": [ + "verified", + "backlogged", + "inReview", + "requested", + "notRequested" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/database/drizzle/meta/_journal.json b/packages/database/drizzle/meta/_journal.json index 6c2ac713c..db691a3eb 100644 --- a/packages/database/drizzle/meta/_journal.json +++ b/packages/database/drizzle/meta/_journal.json @@ -512,6 +512,13 @@ "when": 1750958715096, "tag": "0072_slow_earthquake", "breakpoints": true + }, + { + "idx": 73, + "version": "7", + "when": 1751577399834, + "tag": "0073_lovely_white_tiger", + "breakpoints": true } ] } \ No newline at end of file From 64727348720419210daf1a3167b79cb24f951ef8 Mon Sep 17 00:00:00 2001 From: dal Date: Thu, 3 Jul 2025 14:28:41 -0700 Subject: [PATCH 4/5] Update apps/server/src/api/v2/slack/handler.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/server/src/api/v2/slack/handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/api/v2/slack/handler.ts b/apps/server/src/api/v2/slack/handler.ts index c7264b3c8..aed724805 100644 --- a/apps/server/src/api/v2/slack/handler.ts +++ b/apps/server/src/api/v2/slack/handler.ts @@ -354,7 +354,7 @@ export class SlackHandler { } // Parse request body - const body = await c.req.json(); + const body = await c.req.json().catch(() => ({})); const parsed = UpdateIntegrationSchema.safeParse(body); if (!parsed.success) { From f82177156d681b912a0148dd68d53e6bce7ab12a Mon Sep 17 00:00:00 2001 From: dal Date: Thu, 3 Jul 2025 14:29:31 -0700 Subject: [PATCH 5/5] Update .cursor/rules/global.mdc Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- .cursor/rules/global.mdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.cursor/rules/global.mdc b/.cursor/rules/global.mdc index fd985ee75..9d13f13d7 100644 --- a/.cursor/rules/global.mdc +++ b/.cursor/rules/global.mdc @@ -610,7 +610,7 @@ NEVER proactively create documentation files (*.md) or README files. Only create ### Linting Rules - Always use `pnpm run check` or `pnpm run check:fix` -- **Rule: `i dont' want caldue to ever run a biome lint fix only biome lint`** +- **Rule: `I don't want Claude to ever run a biome lint fix only biome lint`** - This means ONLY use `pnpm run check` (linting without auto-fixing) - Do NOT use `pnpm run check:fix` - Claude should understand to ONLY run lint checks, never auto-fix