feat(数据库): 添加用户认证相关数据表

- 新增 users 用户表,支持邮箱注册和登录
- 新增 verification_codes 验证码表,支持多种验证场景
- 为 user_settings 表添加 userId 关联字段
- 为 conversations 表添加 userId 关联字段
- 定义表关系实现用户数据隔离
- 更新数据库迁移文件
This commit is contained in:
gaoziman 2025-12-19 22:35:54 +08:00
parent 7d8a6a6939
commit 629ff540fc
6 changed files with 902 additions and 3 deletions

View File

@ -9,7 +9,7 @@ export default defineConfig({
port: parseInt(process.env.DB_PORT || '35433'), port: parseInt(process.env.DB_PORT || '35433'),
user: process.env.DB_USER || 'postgres', user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres', password: process.env.DB_PASSWORD || 'postgres',
database: process.env.DB_NAME || 'cchcode_ui', database: process.env.DB_NAME || 'lioncode_ui',
ssl: false, ssl: false,
}, },
}); });

View File

@ -8,7 +8,7 @@ const pool = new Pool({
port: parseInt(process.env.DB_PORT || '35433'), port: parseInt(process.env.DB_PORT || '35433'),
user: process.env.DB_USER || 'postgres', user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres', password: process.env.DB_PASSWORD || 'postgres',
database: process.env.DB_NAME || 'cchcode_ui', database: process.env.DB_NAME || 'lioncode_ui',
max: 10, // 最大连接数 max: 10, // 最大连接数
idleTimeoutMillis: 30000, // 空闲超时 idleTimeoutMillis: 30000, // 空闲超时
connectionTimeoutMillis: 5000, // 连接超时 connectionTimeoutMillis: 5000, // 连接超时

View File

@ -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");

View File

@ -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": {}
}
}

View File

@ -29,6 +29,13 @@
"when": 1766143752533, "when": 1766143752533,
"tag": "0003_melted_shockwave", "tag": "0003_melted_shockwave",
"breakpoints": true "breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1766149111995,
"tag": "0004_lowly_red_shift",
"breakpoints": true
} }
] ]
} }

View File

@ -10,11 +10,55 @@ import {
} from 'drizzle-orm/pg-core'; } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm'; 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', { export const userSettings = pgTable('user_settings', {
id: serial('id').primaryKey(), id: serial('id').primaryKey(),
// 关联用户
userId: varchar('user_id', { length: 64 }).unique(),
// CCH 配置 // CCH 配置
cchUrl: varchar('cch_url', { length: 512 }).notNull().default('http://localhost:13500'), cchUrl: varchar('cch_url', { length: 512 }).notNull().default('http://localhost:13500'),
cchApiKey: varchar('cch_api_key', { length: 512 }), cchApiKey: varchar('cch_api_key', { length: 512 }),
@ -43,6 +87,8 @@ export const conversations = pgTable('conversations', {
id: serial('id').primaryKey(), id: serial('id').primaryKey(),
// 对话唯一标识 // 对话唯一标识
conversationId: varchar('conversation_id', { length: 64 }).notNull().unique(), conversationId: varchar('conversation_id', { length: 64 }).notNull().unique(),
// 关联用户
userId: varchar('user_id', { length: 64 }),
// 对话信息 // 对话信息
title: varchar('title', { length: 255 }).notNull().default('新对话'), title: varchar('title', { length: 255 }).notNull().default('新对话'),
summary: text('summary'), 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), messages: many(messages),
user: one(users, {
fields: [conversations.userId],
references: [users.userId],
}),
})); }));
export const messagesRelations = relations(messages, ({ one }) => ({ 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 UserSettings = typeof userSettings.$inferSelect;
export type NewUserSettings = typeof userSettings.$inferInsert; export type NewUserSettings = typeof userSettings.$inferInsert;