From cc17faf835927c3f3efd022c855dfc826630518b Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Sat, 23 May 2026 01:22:32 +0100 Subject: [PATCH] feat(integrations): add kind discriminator and nullable token to connections --- ...523T120000-integration-connections-kind.ts | 53 +++++++++++++++++++ apps/server/src/database/types/db.d.ts | 3 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 apps/server/src/database/migrations/20260523T120000-integration-connections-kind.ts diff --git a/apps/server/src/database/migrations/20260523T120000-integration-connections-kind.ts b/apps/server/src/database/migrations/20260523T120000-integration-connections-kind.ts new file mode 100644 index 000000000..6b3866321 --- /dev/null +++ b/apps/server/src/database/migrations/20260523T120000-integration-connections-kind.ts @@ -0,0 +1,53 @@ +import { type Kysely, sql } from 'kysely'; + +export async function up(db: Kysely): Promise { + // 1. Drop NOT NULL on access_token (identity-only rows have no token) + await db.schema + .alterTable('integration_connections') + .alterColumn('access_token', (col) => col.dropNotNull()) + .execute(); + + // 2. Add kind discriminator with check constraint + await db.schema + .alterTable('integration_connections') + .addColumn('kind', 'text', (col) => + col.notNull().defaultTo('user'), + ) + .execute(); + + await sql` + ALTER TABLE integration_connections + ADD CONSTRAINT integration_connections_kind_check + CHECK (kind IN ('workspace', 'user')) + `.execute(db); + + // 3. Backfill: existing rows with access_token AND scopes are workspace installs + await sql` + UPDATE integration_connections + SET kind = 'workspace' + WHERE access_token IS NOT NULL AND scopes IS NOT NULL + `.execute(db); + + // 4. One workspace connection per integration + await db.schema + .createIndex('uq_integration_connections_workspace_per_integration') + .on('integration_connections') + .column('integration_id') + .where('kind', '=', 'workspace') + .unique() + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema + .dropIndex('uq_integration_connections_workspace_per_integration') + .ifExists() + .execute(); + + await sql`ALTER TABLE integration_connections DROP CONSTRAINT IF EXISTS integration_connections_kind_check`.execute(db); + + await db.schema + .alterTable('integration_connections') + .dropColumn('kind') + .execute(); +} diff --git a/apps/server/src/database/types/db.d.ts b/apps/server/src/database/types/db.d.ts index 6047580a6..cecdf34e6 100644 --- a/apps/server/src/database/types/db.d.ts +++ b/apps/server/src/database/types/db.d.ts @@ -477,10 +477,11 @@ export interface IntegrationConnections { userId: string; workspaceId: string; providerUserId: string | null; - accessToken: string; + accessToken: string | null; refreshToken: string | null; tokenExpiresAt: Timestamp | null; scopes: string | null; + kind: string; metadata: Json | null; createdAt: Generated; updatedAt: Generated;