schema: constrain integration providers to the ones the code supports
`provider` was free text on both integration tables, 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 had stopped being legal, so nothing noticed. A CHECK constraint on each table now lists what may exist — google/apify for the server, google/browser-relay for the user. Adding an integration means adding it to the list, which is the point: the schema is the source of truth for what the database may contain, so a provider the code no longer supports cannot sit there unnoticed. This does NOT delete the existing rows, and no schema change can — drizzle-kit push diffs structure, never data. What it does instead is refuse: push will fail to add the constraint while violating rows exist. That is the enforcement working rather than a problem to route around; the three rows have to go first, and then the structure guarantees they cannot come back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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', {
|
||||
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`, `,
|
||||
)})`,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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', {
|
||||
|
||||
Reference in New Issue
Block a user