diff --git a/drizzle.config.ts b/drizzle.config.ts index 98bcc84..9fe35a8 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ port: parseInt(process.env.DB_PORT || '35433'), user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'postgres', - database: process.env.DB_NAME || 'cchcode_ui', + database: process.env.DB_NAME || 'lioncode_ui', ssl: false, }, }); diff --git a/src/drizzle/db.ts b/src/drizzle/db.ts index bb3d7f8..b71e17c 100644 --- a/src/drizzle/db.ts +++ b/src/drizzle/db.ts @@ -8,7 +8,7 @@ const pool = new Pool({ port: parseInt(process.env.DB_PORT || '35433'), user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'postgres', - database: process.env.DB_NAME || 'cchcode_ui', + database: process.env.DB_NAME || 'lioncode_ui', max: 10, // 最大连接数 idleTimeoutMillis: 30000, // 空闲超时 connectionTimeoutMillis: 5000, // 连接超时 diff --git a/src/drizzle/migrations/0004_lowly_red_shift.sql b/src/drizzle/migrations/0004_lowly_red_shift.sql new file mode 100644 index 0000000..4f4f08d --- /dev/null +++ b/src/drizzle/migrations/0004_lowly_red_shift.sql @@ -0,0 +1,30 @@ +CREATE TABLE "users" ( + "id" serial PRIMARY KEY NOT NULL, + "user_id" varchar(64) NOT NULL, + "email" varchar(255) NOT NULL, + "password" varchar(255) NOT NULL, + "nickname" varchar(64) NOT NULL, + "avatar" varchar(512), + "plan" varchar(20) DEFAULT 'free', + "status" varchar(20) DEFAULT 'active', + "email_verified" boolean DEFAULT false, + "last_login_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT now(), + "updated_at" timestamp with time zone DEFAULT now(), + CONSTRAINT "users_user_id_unique" UNIQUE("user_id"), + CONSTRAINT "users_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "verification_codes" ( + "id" serial PRIMARY KEY NOT NULL, + "email" varchar(255) NOT NULL, + "code" varchar(6) NOT NULL, + "type" varchar(20) NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + "used" boolean DEFAULT false, + "created_at" timestamp with time zone DEFAULT now() +); +--> statement-breakpoint +ALTER TABLE "conversations" ADD COLUMN "user_id" varchar(64);--> statement-breakpoint +ALTER TABLE "user_settings" ADD COLUMN "user_id" varchar(64);--> statement-breakpoint +ALTER TABLE "user_settings" ADD CONSTRAINT "user_settings_user_id_unique" UNIQUE("user_id"); \ No newline at end of file diff --git a/src/drizzle/migrations/meta/0004_snapshot.json b/src/drizzle/migrations/meta/0004_snapshot.json new file mode 100644 index 0000000..e14a949 --- /dev/null +++ b/src/drizzle/migrations/meta/0004_snapshot.json @@ -0,0 +1,791 @@ +{ + "id": "593838ef-b7eb-4197-aa54-b1d2912da729", + "prevId": "43d40968-3585-47d7-ac89-873c6b72655b", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.conversations": { + "name": "conversations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "default": "'新对话'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "tools": { + "name": "tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "enable_thinking": { + "name": "enable_thinking", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "temperature": { + "name": "temperature", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "is_archived": { + "name": "is_archived", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_pinned": { + "name": "is_pinned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "conversations_conversation_id_unique": { + "name": "conversations_conversation_id_unique", + "nullsNotDistinct": false, + "columns": [ + "conversation_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.messages": { + "name": "messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "conversation_id": { + "name": "conversation_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "thinking_content": { + "name": "thinking_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "thinking_collapsed": { + "name": "thinking_collapsed", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "tool_calls": { + "name": "tool_calls", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "tool_results": { + "name": "tool_results", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "images": { + "name": "images", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "output_tokens": { + "name": "output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'completed'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback": { + "name": "feedback", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "messages_message_id_unique": { + "name": "messages_message_id_unique", + "nullsNotDistinct": false, + "columns": [ + "message_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.models": { + "name": "models", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "supports_tools": { + "name": "supports_tools", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "supports_thinking": { + "name": "supports_thinking", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "supports_vision": { + "name": "supports_vision", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 8192 + }, + "context_window": { + "name": "context_window", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 200000 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "models_model_id_unique": { + "name": "models_model_id_unique", + "nullsNotDistinct": false, + "columns": [ + "model_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tools": { + "name": "tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "tool_id": { + "name": "tool_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "input_schema": { + "name": "input_schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tools_tool_id_unique": { + "name": "tools_tool_id_unique", + "nullsNotDistinct": false, + "columns": [ + "tool_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_settings": { + "name": "user_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cch_url": { + "name": "cch_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": true, + "default": "'http://localhost:13500'" + }, + "cch_api_key": { + "name": "cch_api_key", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cch_api_key_configured": { + "name": "cch_api_key_configured", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "default_model": { + "name": "default_model", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false, + "default": "'claude-sonnet-4-20250514'" + }, + "default_tools": { + "name": "default_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[\"web_search\",\"code_execution\",\"web_fetch\"]'::jsonb" + }, + "system_prompt": { + "name": "system_prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "temperature": { + "name": "temperature", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'0.7'" + }, + "theme": { + "name": "theme", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'light'" + }, + "language": { + "name": "language", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'zh-CN'" + }, + "font_size": { + "name": "font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 15 + }, + "enable_thinking": { + "name": "enable_thinking", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "save_chat_history": { + "name": "save_chat_history", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_settings_user_id_unique": { + "name": "user_settings_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "nickname": { + "name": "nickname", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "avatar": { + "name": "avatar", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'free'" + }, + "status": { + "name": "status", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'active'" + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "last_login_at": { + "name": "last_login_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_user_id_unique": { + "name": "users_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification_codes": { + "name": "verification_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "varchar(6)", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "used": { + "name": "used", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/drizzle/migrations/meta/_journal.json b/src/drizzle/migrations/meta/_journal.json index 2c65d97..f56f993 100644 --- a/src/drizzle/migrations/meta/_journal.json +++ b/src/drizzle/migrations/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1766143752533, "tag": "0003_melted_shockwave", "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1766149111995, + "tag": "0004_lowly_red_shift", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/drizzle/schema.ts b/src/drizzle/schema.ts index a8e7006..8e5375a 100644 --- a/src/drizzle/schema.ts +++ b/src/drizzle/schema.ts @@ -10,11 +10,55 @@ import { } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; +// ============================================ +// 用户表 +// ============================================ +export const users = pgTable('users', { + id: serial('id').primaryKey(), + // 用户唯一标识 + userId: varchar('user_id', { length: 64 }).notNull().unique(), + // 基本信息 + email: varchar('email', { length: 255 }).notNull().unique(), + password: varchar('password', { length: 255 }).notNull(), + nickname: varchar('nickname', { length: 64 }).notNull(), + avatar: varchar('avatar', { length: 512 }), + // 套餐和状态 + plan: varchar('plan', { length: 20 }).default('free'), // free, pro, enterprise + status: varchar('status', { length: 20 }).default('active'), // active, inactive, banned + emailVerified: boolean('email_verified').default(false), + // 登录信息 + lastLoginAt: timestamp('last_login_at', { withTimezone: true }), + // 时间戳 + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(), + updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(), +}); + +// ============================================ +// 验证码表 +// ============================================ +export const verificationCodes = pgTable('verification_codes', { + id: serial('id').primaryKey(), + // 邮箱 + email: varchar('email', { length: 255 }).notNull(), + // 验证码 + code: varchar('code', { length: 6 }).notNull(), + // 类型: register(注册), login(登录), reset(重置密码) + type: varchar('type', { length: 20 }).notNull(), + // 过期时间 + expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), + // 是否已使用 + used: boolean('used').default(false), + // 创建时间 + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(), +}); + // ============================================ // 用户设置表 // ============================================ export const userSettings = pgTable('user_settings', { id: serial('id').primaryKey(), + // 关联用户 + userId: varchar('user_id', { length: 64 }).unique(), // CCH 配置 cchUrl: varchar('cch_url', { length: 512 }).notNull().default('http://localhost:13500'), cchApiKey: varchar('cch_api_key', { length: 512 }), @@ -43,6 +87,8 @@ export const conversations = pgTable('conversations', { id: serial('id').primaryKey(), // 对话唯一标识 conversationId: varchar('conversation_id', { length: 64 }).notNull().unique(), + // 关联用户 + userId: varchar('user_id', { length: 64 }), // 对话信息 title: varchar('title', { length: 255 }).notNull().default('新对话'), summary: text('summary'), @@ -150,8 +196,27 @@ export const models = pgTable('models', { // ============================================ // 关系定义 // ============================================ -export const conversationsRelations = relations(conversations, ({ many }) => ({ +export const usersRelations = relations(users, ({ many, one }) => ({ + conversations: many(conversations), + settings: one(userSettings, { + fields: [users.userId], + references: [userSettings.userId], + }), +})); + +export const userSettingsRelations = relations(userSettings, ({ one }) => ({ + user: one(users, { + fields: [userSettings.userId], + references: [users.userId], + }), +})); + +export const conversationsRelations = relations(conversations, ({ many, one }) => ({ messages: many(messages), + user: one(users, { + fields: [conversations.userId], + references: [users.userId], + }), })); export const messagesRelations = relations(messages, ({ one }) => ({ @@ -177,6 +242,12 @@ export interface ToolResult { } // 导出类型 +export type User = typeof users.$inferSelect; +export type NewUser = typeof users.$inferInsert; + +export type VerificationCode = typeof verificationCodes.$inferSelect; +export type NewVerificationCode = typeof verificationCodes.$inferInsert; + export type UserSettings = typeof userSettings.$inferSelect; export type NewUserSettings = typeof userSettings.$inferInsert;