diff --git a/src/databases/officer_db/src/schema/server.ts b/src/databases/officer_db/src/schema/server.ts index 752f888e..494363ab 100644 --- a/src/databases/officer_db/src/schema/server.ts +++ b/src/databases/officer_db/src/schema/server.ts @@ -28,8 +28,12 @@ export const serverIntegrations = pgTable( (table) => [ check( 'ck_server_integrations_provider', + // sql.raw, not `sql`${p}``: interpolating a JS string binds it as a parameter, so the constraint + // is emitted as `IN ($1, $2)` and Postgres rejects it ("there is no parameter $1") — which breaks + // `bun db:push` for the entire schema, not just this table. Safe because the values are compile-time + // literals from the const tuple above. sql`${table.provider} IN (${sql.join( - SERVER_PROVIDERS.map((p) => sql`${p}`), + SERVER_PROVIDERS.map((p) => sql.raw(`'${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 fcd59a93..d9298451 100644 --- a/src/databases/officer_db/src/schema/user-data.ts +++ b/src/databases/officer_db/src/schema/user-data.ts @@ -4,13 +4,17 @@ import { sql } from 'drizzle-orm'; import { serverIntegrations } from './server'; export const userSettings = pgTable('user_settings', { - userId: integer('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }), + userId: integer('user_id') + .primaryKey() + .references(() => users.id, { onDelete: 'cascade' }), settings: jsonb('settings').notNull().default({}), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); export const userState = pgTable('user_state', { - userId: integer('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }), + userId: integer('user_id') + .primaryKey() + .references(() => users.id, { onDelete: 'cascade' }), state: jsonb('state').notNull().default({}), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }); @@ -18,24 +22,39 @@ export const userState = pgTable('user_state', { // 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' }), - provider: text('provider').notNull(), - serverIntegrationId: integer('server_integration_id').references(() => serverIntegrations.id, { onDelete: 'set null' }), - config: jsonb('config').notNull().default({}), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), - 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 userIntegrations = pgTable( + 'user_integrations', + { + id: serial('id').primaryKey(), + userId: integer('user_id') + .notNull() + .references(() => users.id, { onDelete: 'cascade' }), + provider: text('provider').notNull(), + serverIntegrationId: integer('server_integration_id').references(() => serverIntegrations.id, { + onDelete: 'set null', + }), + config: jsonb('config').notNull().default({}), + createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), + 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.raw — see the note on ck_server_integrations_provider; an interpolated JS string becomes a + // bound `$1` that Postgres will not accept in a CHECK. + sql`${table.provider} IN (${sql.join( + USER_PROVIDERS.map((p) => sql.raw(`'${p}'`)), + sql`, `, + )})`, + ), + ], +); export const dockConfigs = pgTable('dock_configs', { - userId: integer('user_id').primaryKey().references(() => users.id, { onDelete: 'cascade' }), + userId: integer('user_id') + .primaryKey() + .references(() => users.id, { onDelete: 'cascade' }), paths: jsonb('paths').notNull().default([]), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), });