import { bigint, boolean, index, integer, jsonb, pgEnum, pgTable, text, timestamp, uuid, varchar, } from 'drizzle-orm/pg-core'; export const planEnum = pgEnum('plan', ['hobby', 'pro', 'team', 'enterprise']); export const serverStatusEnum = pgEnum('server_status', [ 'draft', 'queued', 'generating', 'building', 'deploying', 'live', 'failed', 'paused', ]); export const buildStatusEnum = pgEnum('build_status', [ 'queued', 'generating', 'building', 'deploying', 'success', 'failed', 'cancelled', ]); export const organizations = pgTable('organizations', { id: uuid('id').defaultRandom().primaryKey(), slug: varchar('slug', { length: 64 }).notNull().unique(), name: varchar('name', { length: 128 }).notNull(), plan: planEnum('plan').default('hobby').notNull(), stripeCustomerId: varchar('stripe_customer_id', { length: 128 }), stripeSubscriptionId: varchar('stripe_subscription_id', { length: 128 }), monthlyCallQuota: bigint('monthly_call_quota', { mode: 'number' }).default(100_000).notNull(), callsThisPeriod: bigint('calls_this_period', { mode: 'number' }).default(0).notNull(), periodStartsAt: timestamp('period_starts_at').defaultNow().notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const users = pgTable('users', { id: uuid('id').defaultRandom().primaryKey(), email: varchar('email', { length: 255 }).notNull().unique(), name: varchar('name', { length: 128 }), avatarUrl: text('avatar_url'), emailVerified: boolean('email_verified').default(false).notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const sessions = pgTable( 'sessions', { id: uuid('id').defaultRandom().primaryKey(), userId: uuid('user_id') .references(() => users.id, { onDelete: 'cascade' }) .notNull(), tokenHash: text('token_hash').notNull().unique(), expiresAt: timestamp('expires_at').notNull(), ipAddress: varchar('ip_address', { length: 64 }), userAgent: text('user_agent'), createdAt: timestamp('created_at').defaultNow().notNull(), }, (t) => ({ userIdx: index('idx_sessions_user').on(t.userId), }), ); export const magicLinks = pgTable('magic_links', { id: uuid('id').defaultRandom().primaryKey(), email: varchar('email', { length: 255 }).notNull(), tokenHash: text('token_hash').notNull().unique(), expiresAt: timestamp('expires_at').notNull(), consumedAt: timestamp('consumed_at'), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const memberships = pgTable('memberships', { id: uuid('id').defaultRandom().primaryKey(), orgId: uuid('org_id') .references(() => organizations.id, { onDelete: 'cascade' }) .notNull(), userId: uuid('user_id') .references(() => users.id, { onDelete: 'cascade' }) .notNull(), role: varchar('role', { length: 32 }).default('owner').notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const mcpServers = pgTable( 'mcp_servers', { id: uuid('id').defaultRandom().primaryKey(), orgId: uuid('org_id') .references(() => organizations.id, { onDelete: 'cascade' }) .notNull(), slug: varchar('slug', { length: 64 }).notNull(), name: varchar('name', { length: 128 }).notNull(), description: text('description'), status: serverStatusEnum('status').default('draft').notNull(), currentVersion: integer('current_version').default(0).notNull(), containerId: varchar('container_id', { length: 128 }), hostPort: integer('host_port'), publicUrl: text('public_url'), toolsSchema: jsonb('tools_schema'), oauthEnabled: boolean('oauth_enabled').default(true).notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at').defaultNow().notNull(), }, (t) => ({ orgSlugIdx: index('idx_servers_org_slug').on(t.orgId, t.slug), }), ); export const builds = pgTable( 'builds', { id: uuid('id').defaultRandom().primaryKey(), serverId: uuid('server_id') .references(() => mcpServers.id, { onDelete: 'cascade' }) .notNull(), version: integer('version').notNull(), prompt: text('prompt').notNull(), generatedSpec: jsonb('generated_spec'), generatedCode: text('generated_code'), status: buildStatusEnum('status').default('queued').notNull(), errorMessage: text('error_message'), startedAt: timestamp('started_at'), finishedAt: timestamp('finished_at'), createdAt: timestamp('created_at').defaultNow().notNull(), }, (t) => ({ serverIdx: index('idx_builds_server').on(t.serverId), }), ); export const buildLogs = pgTable( 'build_logs', { id: uuid('id').defaultRandom().primaryKey(), buildId: uuid('build_id') .references(() => builds.id, { onDelete: 'cascade' }) .notNull(), level: varchar('level', { length: 16 }).default('info').notNull(), message: text('message').notNull(), timestamp: timestamp('timestamp').defaultNow().notNull(), }, (t) => ({ buildIdx: index('idx_logs_build').on(t.buildId, t.timestamp), }), ); export const secrets = pgTable('secrets', { id: uuid('id').defaultRandom().primaryKey(), serverId: uuid('server_id') .references(() => mcpServers.id, { onDelete: 'cascade' }) .notNull(), key: varchar('key', { length: 128 }).notNull(), encryptedValue: text('encrypted_value').notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const oauthClients = pgTable('oauth_clients', { id: uuid('id').defaultRandom().primaryKey(), serverId: uuid('server_id') .references(() => mcpServers.id, { onDelete: 'cascade' }) .notNull(), clientId: varchar('client_id', { length: 128 }).notNull().unique(), clientSecretHash: text('client_secret_hash'), redirectUris: jsonb('redirect_uris').notNull(), metadata: jsonb('metadata'), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const oauthCodes = pgTable('oauth_codes', { id: uuid('id').defaultRandom().primaryKey(), clientDbId: uuid('client_db_id') .references(() => oauthClients.id, { onDelete: 'cascade' }) .notNull(), code: varchar('code', { length: 128 }).notNull().unique(), codeChallenge: varchar('code_challenge', { length: 256 }).notNull(), codeChallengeMethod: varchar('code_challenge_method', { length: 16 }).notNull(), redirectUri: text('redirect_uri').notNull(), scope: text('scope'), resource: text('resource'), userId: uuid('user_id').references(() => users.id, { onDelete: 'set null' }), expiresAt: timestamp('expires_at').notNull(), consumedAt: timestamp('consumed_at'), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const oauthTokens = pgTable('oauth_tokens', { id: uuid('id').defaultRandom().primaryKey(), clientDbId: uuid('client_db_id') .references(() => oauthClients.id, { onDelete: 'cascade' }) .notNull(), accessTokenHash: text('access_token_hash').notNull(), refreshTokenHash: text('refresh_token_hash'), scope: text('scope'), resource: text('resource'), expiresAt: timestamp('expires_at').notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const toolCallMetrics = pgTable( 'tool_call_metrics', { id: uuid('id').defaultRandom().primaryKey(), serverId: uuid('server_id') .references(() => mcpServers.id, { onDelete: 'cascade' }) .notNull(), toolName: varchar('tool_name', { length: 128 }).notNull(), durationMs: integer('duration_ms'), success: boolean('success').notNull(), errorCode: varchar('error_code', { length: 64 }), timestamp: timestamp('timestamp').defaultNow().notNull(), }, (t) => ({ serverTimeIdx: index('idx_metrics_server_time').on(t.serverId, t.timestamp), }), ); export const auditLog = pgTable('audit_log', { id: uuid('id').defaultRandom().primaryKey(), orgId: uuid('org_id').references(() => organizations.id, { onDelete: 'set null' }), userId: uuid('user_id').references(() => users.id, { onDelete: 'set null' }), action: varchar('action', { length: 128 }).notNull(), resourceType: varchar('resource_type', { length: 64 }), resourceId: varchar('resource_id', { length: 128 }), metadata: jsonb('metadata'), ipAddress: varchar('ip_address', { length: 64 }), createdAt: timestamp('created_at').defaultNow().notNull(), }); export type Organization = typeof organizations.$inferSelect; export type User = typeof users.$inferSelect; export type Session = typeof sessions.$inferSelect; export type McpServer = typeof mcpServers.$inferSelect; export type Build = typeof builds.$inferSelect; export type BuildLog = typeof buildLogs.$inferSelect; export type Secret = typeof secrets.$inferSelect; export type OAuthClient = typeof oauthClients.$inferSelect;