feat: add GitHub integrations table and migration

- Add githubIntegrationStatusEnum with states: pending, active, suspended, revoked
- Add githubIntegrations table with GitHub App architecture fields:
  - installationId, appId for GitHub App integration
  - githubOrgId, githubOrgName for GitHub organization info
  - tokenVaultKey, webhookSecretVaultKey for secure token storage
  - repositoryPermissions JSONB field for access control
- Add foreign key constraints to organizations and users tables
- Add indexes for organizationId and installationId for query performance
- Add unique constraints for tokenVaultKey and org-installation pairs
- Create migration 0084_github_integrations.sql following Drizzle pattern

Implements BUS-1462: GitHub integrations table for tracking GitHub App integrations

Co-Authored-By: Dallin Bentley <dallinbentley98@gmail.com>
This commit is contained in:
Devin AI 2025-07-21 05:45:51 +00:00
parent 34e128874a
commit 6bc6602028
2 changed files with 88 additions and 0 deletions

View File

@ -0,0 +1,26 @@
CREATE TYPE "public"."github_integration_status_enum" AS ENUM('pending', 'active', 'suspended', 'revoked');--> statement-breakpoint
CREATE TABLE "github_integrations" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"organization_id" uuid NOT NULL,
"user_id" uuid NOT NULL,
"installation_id" varchar(255),
"app_id" varchar(255),
"github_org_id" varchar(255),
"github_org_name" varchar(255),
"token_vault_key" varchar(255),
"webhook_secret_vault_key" varchar(255),
"repository_permissions" jsonb DEFAULT '{}'::jsonb,
"status" "github_integration_status_enum" DEFAULT 'pending' NOT NULL,
"installed_at" timestamp with time zone,
"last_used_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
"deleted_at" timestamp with time zone,
CONSTRAINT "github_integrations_token_vault_key_unique" UNIQUE("token_vault_key"),
CONSTRAINT "github_integrations_org_installation_key" UNIQUE("organization_id","installation_id")
);
--> statement-breakpoint
ALTER TABLE "github_integrations" ADD CONSTRAINT "github_integrations_organization_id_fkey" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "github_integrations" ADD CONSTRAINT "github_integrations_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_github_integrations_org_id" ON "github_integrations" USING btree ("organization_id" uuid_ops);--> statement-breakpoint
CREATE INDEX "idx_github_integrations_installation_id" ON "github_integrations" USING btree ("installation_id" text_ops);

View File

@ -104,6 +104,13 @@ export const slackSharingPermissionEnum = pgEnum('slack_sharing_permission_enum'
'noSharing', 'noSharing',
]); ]);
export const githubIntegrationStatusEnum = pgEnum('github_integration_status_enum', [
'pending',
'active',
'suspended',
'revoked',
]);
export const workspaceSharingEnum = pgEnum('workspace_sharing_enum', [ export const workspaceSharingEnum = pgEnum('workspace_sharing_enum', [
'none', 'none',
'can_view', 'can_view',
@ -1942,6 +1949,61 @@ export const slackIntegrations = pgTable(
] ]
); );
// GitHub integrations table
export const githubIntegrations = pgTable(
'github_integrations',
{
id: uuid().defaultRandom().primaryKey().notNull(),
organizationId: uuid('organization_id').notNull(),
userId: uuid('user_id').notNull(),
installationId: varchar('installation_id', { length: 255 }),
appId: varchar('app_id', { length: 255 }),
githubOrgId: varchar('github_org_id', { length: 255 }),
githubOrgName: varchar('github_org_name', { length: 255 }),
tokenVaultKey: varchar('token_vault_key', { length: 255 }).unique(),
webhookSecretVaultKey: varchar('webhook_secret_vault_key', { length: 255 }),
repositoryPermissions: jsonb('repository_permissions').default({}),
status: githubIntegrationStatusEnum().default('pending').notNull(),
installedAt: timestamp('installed_at', { withTimezone: true, mode: 'string' }),
lastUsedAt: timestamp('last_used_at', { withTimezone: true, mode: 'string' }),
// 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: 'github_integrations_organization_id_fkey',
}).onDelete('cascade'),
foreignKey({
columns: [table.userId],
foreignColumns: [users.id],
name: 'github_integrations_user_id_fkey',
}),
unique('github_integrations_org_installation_key').on(table.organizationId, table.installationId),
index('idx_github_integrations_org_id').using(
'btree',
table.organizationId.asc().nullsLast().op('uuid_ops')
),
index('idx_github_integrations_installation_id').using(
'btree',
table.installationId.asc().nullsLast().op('text_ops')
),
]
);
// Slack message tracking table (optional) // Slack message tracking table (optional)
export const slackMessageTracking = pgTable( export const slackMessageTracking = pgTable(
'slack_message_tracking', 'slack_message_tracking',