diff --git a/src/databases/officer_db/src/schema/server.ts b/src/databases/officer_db/src/schema/server.ts index 57f721db..752f888e 100644 --- a/src/databases/officer_db/src/schema/server.ts +++ b/src/databases/officer_db/src/schema/server.ts @@ -1,4 +1,13 @@ -import { pgTable, serial, boolean, text, timestamp, jsonb } from 'drizzle-orm/pg-core'; +import { pgTable, serial, boolean, text, timestamp, jsonb, check } from 'drizzle-orm/pg-core'; +import { sql } from 'drizzle-orm'; + +// The integrations the server knows how to configure. `provider` was free text, which is how rows for +// telegram, whatsapp and discord went on holding bot tokens long after the code that read them was +// deleted — nothing structural said they were no longer legal. +// +// Adding an integration means adding it here. That is the point: the schema is the source of truth for +// what may exist, so a provider the code no longer supports cannot sit in the table unnoticed. +const SERVER_PROVIDERS = ['google', 'apify'] as const; export const serverConfig = pgTable('server_config', { key: text('key').primaryKey(), @@ -6,11 +15,23 @@ export const serverConfig = pgTable('server_config', { updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); -export const serverIntegrations = pgTable('server_integrations', { - id: serial('id').primaryKey(), - provider: text('provider').notNull().unique(), - enabled: boolean('enabled').notNull().default(true), - config: jsonb('config').notNull().default({}), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), -}); +export const serverIntegrations = pgTable( + 'server_integrations', + { + id: serial('id').primaryKey(), + provider: text('provider').notNull().unique(), + enabled: boolean('enabled').notNull().default(true), + config: jsonb('config').notNull().default({}), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), + }, + (table) => [ + check( + 'ck_server_integrations_provider', + sql`${table.provider} IN (${sql.join( + SERVER_PROVIDERS.map((p) => sql`${p}`), + sql`, `, + )})`, + ), + ], +); diff --git a/src/databases/officer_db/src/schema/user-data.ts b/src/databases/officer_db/src/schema/user-data.ts index 9617e3fd..fcd59a93 100644 --- a/src/databases/officer_db/src/schema/user-data.ts +++ b/src/databases/officer_db/src/schema/user-data.ts @@ -1,5 +1,6 @@ -import { pgTable, serial, integer, text, timestamp, jsonb, unique } from 'drizzle-orm/pg-core'; +import { pgTable, serial, integer, text, timestamp, jsonb, unique, check } from 'drizzle-orm/pg-core'; import { users } from './auth'; +import { sql } from 'drizzle-orm'; import { serverIntegrations } from './server'; export const userSettings = pgTable('user_settings', { @@ -14,6 +15,9 @@ export const userState = pgTable('user_state', { updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); +// Per-user integrations the code knows how to read. Same reasoning as SERVER_PROVIDERS in ./server. +const USER_PROVIDERS = ['google', 'browser-relay'] as const; + export const userIntegrations = pgTable('user_integrations', { id: serial('id').primaryKey(), userId: integer('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), @@ -24,6 +28,10 @@ export const userIntegrations = pgTable('user_integrations', { updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ unique('uq_user_integrations_user_provider').on(table.userId, table.provider), + check( + 'ck_user_integrations_provider', + sql`${table.provider} IN (${sql.join(USER_PROVIDERS.map((p) => sql`${p}`), sql`, `)})`, + ), ]); export const dockConfigs = pgTable('dock_configs', {