import { index, pgTable, text, timestamp, uniqueIndex, uuid } from 'drizzle-orm/pg-core'; import { users } from './identity'; /** * A Notion grant belongs to the person who completed OAuth. Credentials are a * single authenticated-encryption envelope so no token-shaped value is ever * queryable, searchable, or accidentally selected as ordinary metadata. */ export const notionConnections = pgTable( 'notion_connections', { id: uuid('id').primaryKey().defaultRandom(), userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), workspaceId: text('workspace_id').notNull(), workspaceName: text('workspace_name'), workspaceIcon: text('workspace_icon'), botId: text('bot_id'), credentialsEncrypted: text('credentials_encrypted').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ uniqueIndex('notion_connections_user_workspace_key').on(table.userId, table.workspaceId), index('notion_connections_user_idx').on(table.userId), ], ); /** * OAuth attempts survive multiple API processes but not replay. Only hashes * are durable; the browser verifier is an HttpOnly, short-lived cookie. */ export const notionOauthStates = pgTable( 'notion_oauth_states', { stateHash: text('state_hash').primaryKey(), userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), verifierHash: text('verifier_hash').notNull(), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [index('notion_oauth_states_expiry_idx').on(table.expiresAt)], ); export type NotionConnection = typeof notionConnections.$inferSelect; export type NotionOauthState = typeof notionOauthStates.$inferSelect;