import { pgTable, pgEnum, uuid, text, integer, boolean, timestamp, jsonb, real, uniqueIndex, index, } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; // ── Enums ────────────────────────────────────────────────────────────────────── export const userTierEnum = pgEnum('user_tier', [ 'free', 'pro', 'team', 'enterprise', ]); export const projectStatusEnum = pgEnum('project_status', [ 'draft', 'analyzed', 'generated', 'tested', 'deployed', ]); export const deploymentStatusEnum = pgEnum('deployment_status', [ 'pending', 'building', 'live', 'failed', 'stopped', ]); export const listingStatusEnum = pgEnum('listing_status', [ 'review', 'published', 'rejected', 'archived', ]); // ── Teams ────────────────────────────────────────────────────────────────────── export const teams = pgTable( 'teams', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull(), slug: text('slug').notNull(), ownerId: uuid('owner_id'), // FK to users.id (circular ref handled in relations) tier: userTierEnum('tier').default('team').notNull(), stripeSubscriptionId: text('stripe_subscription_id'), maxSeats: integer('max_seats').default(5).notNull(), createdAt: timestamp('created_at', { withTimezone: true }) .defaultNow() .notNull(), }, (t) => [ uniqueIndex('teams_slug_idx').on(t.slug), ], ); // ── Users ────────────────────────────────────────────────────────────────────── export const users = pgTable( 'users', { id: uuid('id').primaryKey().defaultRandom(), clerkId: text('clerk_id').notNull(), email: text('email').notNull(), name: text('name'), avatarUrl: text('avatar_url'), tier: userTierEnum('tier').default('free').notNull(), stripeCustomerId: text('stripe_customer_id'), teamId: uuid('team_id').references(() => teams.id), createdAt: timestamp('created_at', { withTimezone: true }) .defaultNow() .notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }) .defaultNow() .notNull(), }, (t) => [ uniqueIndex('users_clerk_id_idx').on(t.clerkId), uniqueIndex('users_email_idx').on(t.email), index('users_team_id_idx').on(t.teamId), ], ); // ── Projects ─────────────────────────────────────────────────────────────────── export const projects = pgTable( 'projects', { id: uuid('id').primaryKey().defaultRandom(), userId: uuid('user_id') .references(() => users.id) .notNull(), teamId: uuid('team_id').references(() => teams.id), name: text('name').notNull(), slug: text('slug').notNull(), description: text('description'), status: projectStatusEnum('status').default('draft').notNull(), specUrl: text('spec_url'), specRaw: jsonb('spec_raw'), analysis: jsonb('analysis'), toolConfig: jsonb('tool_config'), appConfig: jsonb('app_config'), authConfig: jsonb('auth_config'), serverBundle: jsonb('server_bundle'), templateId: uuid('template_id'), // FK to marketplace_listings (circular ref handled in relations) createdAt: timestamp('created_at', { withTimezone: true }) .defaultNow() .notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }) .defaultNow() .notNull(), }, (t) => [ uniqueIndex('projects_user_slug_idx').on(t.userId, t.slug), index('projects_user_id_idx').on(t.userId), index('projects_team_id_idx').on(t.teamId), index('projects_status_idx').on(t.status), index('projects_template_id_idx').on(t.templateId), ], ); // ── Tools ────────────────────────────────────────────────────────────────────── export const tools = pgTable( 'tools', { id: uuid('id').primaryKey().defaultRandom(), projectId: uuid('project_id') .references(() => projects.id, { onDelete: 'cascade' }) .notNull(), name: text('name').notNull(), description: text('description'), groupName: text('group_name'), inputSchema: jsonb('input_schema'), outputSchema: jsonb('output_schema'), annotations: jsonb('annotations'), enabled: boolean('enabled').default(true).notNull(), position: integer('position').default(0).notNull(), canvasX: real('canvas_x'), canvasY: real('canvas_y'), createdAt: timestamp('created_at', { withTimezone: true }) .defaultNow() .notNull(), }, (t) => [ index('tools_project_id_idx').on(t.projectId), index('tools_group_name_idx').on(t.groupName), ], ); // ── Apps ──────────────────────────────────────────────────────────────────────── export const apps = pgTable( 'apps', { id: uuid('id').primaryKey().defaultRandom(), projectId: uuid('project_id') .references(() => projects.id, { onDelete: 'cascade' }) .notNull(), name: text('name').notNull(), pattern: text('pattern'), layoutConfig: jsonb('layout_config'), htmlBundle: text('html_bundle'), toolBindings: jsonb('tool_bindings'), createdAt: timestamp('created_at', { withTimezone: true }) .defaultNow() .notNull(), }, (t) => [ index('apps_project_id_idx').on(t.projectId), ], ); // ── Deployments ──────────────────────────────────────────────────────────────── export const deployments = pgTable( 'deployments', { id: uuid('id').primaryKey().defaultRandom(), projectId: uuid('project_id') .references(() => projects.id) .notNull(), userId: uuid('user_id') .references(() => users.id) .notNull(), target: text('target').notNull(), status: deploymentStatusEnum('status').default('pending').notNull(), url: text('url'), endpoint: text('endpoint'), workerId: text('worker_id'), version: text('version'), logs: jsonb('logs'), createdAt: timestamp('created_at', { withTimezone: true }) .defaultNow() .notNull(), stoppedAt: timestamp('stopped_at', { withTimezone: true }), }, (t) => [ index('deployments_project_id_idx').on(t.projectId), index('deployments_user_id_idx').on(t.userId), index('deployments_status_idx').on(t.status), ], ); // ── Marketplace Listings ─────────────────────────────────────────────────────── export const marketplaceListings = pgTable( 'marketplace_listings', { id: uuid('id').primaryKey().defaultRandom(), projectId: uuid('project_id').references(() => projects.id), authorId: uuid('author_id').references(() => users.id), name: text('name').notNull(), slug: text('slug').notNull(), description: text('description'), category: text('category'), tags: text('tags').array(), iconUrl: text('icon_url'), previewUrl: text('preview_url'), toolCount: integer('tool_count').default(0).notNull(), appCount: integer('app_count').default(0).notNull(), forkCount: integer('fork_count').default(0).notNull(), isOfficial: boolean('is_official').default(false).notNull(), isFeatured: boolean('is_featured').default(false).notNull(), priceCents: integer('price_cents').default(0).notNull(), status: listingStatusEnum('status').default('review').notNull(), createdAt: timestamp('created_at', { withTimezone: true }) .defaultNow() .notNull(), publishedAt: timestamp('published_at', { withTimezone: true }), }, (t) => [ uniqueIndex('marketplace_slug_idx').on(t.slug), index('marketplace_category_idx').on(t.category), index('marketplace_status_idx').on(t.status), index('marketplace_is_official_idx').on(t.isOfficial), index('marketplace_is_featured_idx').on(t.isFeatured), index('marketplace_author_id_idx').on(t.authorId), ], ); // ── Usage Logs ───────────────────────────────────────────────────────────────── export const usageLogs = pgTable( 'usage_logs', { id: uuid('id').primaryKey().defaultRandom(), userId: uuid('user_id') .references(() => users.id) .notNull(), action: text('action').notNull(), projectId: uuid('project_id').references(() => projects.id), tokensUsed: integer('tokens_used').default(0), durationMs: integer('duration_ms').default(0), createdAt: timestamp('created_at', { withTimezone: true }) .defaultNow() .notNull(), }, (t) => [ index('usage_logs_user_id_idx').on(t.userId), index('usage_logs_project_id_idx').on(t.projectId), index('usage_logs_action_idx').on(t.action), index('usage_logs_created_at_idx').on(t.createdAt), ], ); // ── API Keys ─────────────────────────────────────────────────────────────────── export const apiKeys = pgTable( 'api_keys', { id: uuid('id').primaryKey().defaultRandom(), projectId: uuid('project_id') .references(() => projects.id, { onDelete: 'cascade' }) .notNull(), userId: uuid('user_id') .references(() => users.id) .notNull(), keyName: text('key_name').notNull(), encryptedValue: text('encrypted_value').notNull(), createdAt: timestamp('created_at', { withTimezone: true }) .defaultNow() .notNull(), }, (t) => [ index('api_keys_project_id_idx').on(t.projectId), index('api_keys_user_id_idx').on(t.userId), ], ); // ── Relations ────────────────────────────────────────────────────────────────── export const usersRelations = relations(users, ({ one, many }) => ({ team: one(teams, { fields: [users.teamId], references: [teams.id] }), projects: many(projects), deployments: many(deployments), usageLogs: many(usageLogs), apiKeys: many(apiKeys), })); export const teamsRelations = relations(teams, ({ one, many }) => ({ owner: one(users, { fields: [teams.ownerId], references: [users.id] }), members: many(users), projects: many(projects), })); export const projectsRelations = relations(projects, ({ one, many }) => ({ user: one(users, { fields: [projects.userId], references: [users.id] }), team: one(teams, { fields: [projects.teamId], references: [teams.id] }), template: one(marketplaceListings, { fields: [projects.templateId], references: [marketplaceListings.id], }), tools: many(tools), apps: many(apps), deployments: many(deployments), apiKeys: many(apiKeys), })); export const toolsRelations = relations(tools, ({ one }) => ({ project: one(projects, { fields: [tools.projectId], references: [projects.id] }), })); export const appsRelations = relations(apps, ({ one }) => ({ project: one(projects, { fields: [apps.projectId], references: [projects.id] }), })); export const deploymentsRelations = relations(deployments, ({ one }) => ({ project: one(projects, { fields: [deployments.projectId], references: [projects.id], }), user: one(users, { fields: [deployments.userId], references: [users.id] }), })); export const marketplaceListingsRelations = relations( marketplaceListings, ({ one, many }) => ({ project: one(projects, { fields: [marketplaceListings.projectId], references: [projects.id], }), author: one(users, { fields: [marketplaceListings.authorId], references: [users.id], }), }), ); export const usageLogsRelations = relations(usageLogs, ({ one }) => ({ user: one(users, { fields: [usageLogs.userId], references: [users.id] }), project: one(projects, { fields: [usageLogs.projectId], references: [projects.id], }), })); export const apiKeysRelations = relations(apiKeys, ({ one }) => ({ project: one(projects, { fields: [apiKeys.projectId], references: [projects.id], }), user: one(users, { fields: [apiKeys.userId], references: [users.id] }), }));