001project_wildgrowth/backend/prisma/schema.prisma

378 lines
16 KiB
Plaintext
Raw Permalink Normal View History

2026-02-11 15:26:03 +08:00
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
phone String? @unique
appleId String? @unique @map("apple_id")
nickname String?
avatar String?
digitalId String? @unique @map("digital_id") // ✅ 赛博学习证ID (Wild ID)
agreementAccepted Boolean @default(false) @map("agreement_accepted")
isPro Boolean @default(false) @map("is_pro") // 是否为付费会员
proExpireDate DateTime? @map("pro_expire_date") // 会员过期时间(可选,预留给订阅制)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
settings UserSettings?
learningProgress UserLearningProgress[]
achievements UserAchievement[]
courses UserCourse[]
notes Note[]
notebooks Notebook[]
createdCourses Course[] @relation("CreatedCourses") // ✅ 新增:用户创建的课程
generationTasks CourseGenerationTask[] // ✅ AI 课程生成任务
@@map("users")
}
model UserSettings {
userId String @id @map("user_id")
pushNotification Boolean @default(true) @map("push_notification")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("user_settings")
}
model Course {
id String @id @default(uuid())
title String
subtitle String? // 课程副标题
description String?
coverImage String? @map("cover_image")
themeColor String? @map("theme_color") // ✅ 新增:主题色 Hex如 "#2266FF"
watermarkIcon String? @map("watermark_icon") // ✅ 新增水印图标名称SF Symbol如 "book.closed.fill"
type String @default("system") // ✅ 简化:所有课程统一为 system竖屏课程
status String @default("published") // ✅ 新增published | draft | test_published
minAppVersion String? @map("min_app_version") // ✅ 新增最低App版本号如 "1.0.0"null表示所有版本可见
isPortrait Boolean @default(true) @map("is_portrait") // ✅ 简化:所有课程都是竖屏,默认值改为 true
deletedAt DateTime? @map("deleted_at") // ✅ 新增:软删除时间戳
totalNodes Int @default(0) @map("total_nodes")
// ✅ 创作者和可见范围
createdBy String? @map("created_by") // null = 系统创建,有值 = 用户ID
visibility String @default("private") @map("visibility") // "public" | "private"
createdAsDraft Boolean @default(false) @map("created_as_draft") // 后台 AI 创建为草稿时 true完成逻辑据此跳过自动发布避免多查 Task 表
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
// ✅ 续旧课链路
parentCourseId String? @map("parent_course_id") // 续旧课时指向父课程
accumulatedSummary String? @map("accumulated_summary") // 累积知识点摘要≤1000字
creator User? @relation("CreatedCourses", fields: [createdBy], references: [id], onDelete: SetNull)
parentCourse Course? @relation("CourseContinuation", fields: [parentCourseId], references: [id], onDelete: SetNull)
childCourses Course[] @relation("CourseContinuation")
chapters CourseChapter[]
nodes CourseNode[]
userCourses UserCourse[]
notes Note[]
generationTask CourseGenerationTask? // ✅ AI 生成任务关联
operationalBannerCourses OperationalBannerCourse[]
@@index([createdBy])
@@index([visibility])
@@index([parentCourseId])
@@map("courses")
}
// 发现页运营位软删除orderIndex 从 1
model OperationalBanner {
id String @id @default(uuid())
title String
orderIndex Int @default(1) @map("order_index")
isEnabled Boolean @default(true) @map("is_enabled")
deletedAt DateTime? @map("deleted_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
courses OperationalBannerCourse[]
@@index([deletedAt, isEnabled])
@@map("operational_banners")
}
// 运营位-课程关联(每运营位最多 10 门课,业务层校验)
model OperationalBannerCourse {
id String @id @default(uuid())
bannerId String @map("banner_id")
courseId String @map("course_id")
orderIndex Int @default(1) @map("order_index")
createdAt DateTime @default(now()) @map("created_at")
banner OperationalBanner @relation(fields: [bannerId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
@@unique([bannerId, courseId])
@@index([bannerId])
@@map("operational_banner_courses")
}
// ✅ AI 课程生成任务表
model CourseGenerationTask {
id String @id @default(uuid())
courseId String @unique @map("course_id")
userId String @map("user_id")
sourceText String @map("source_text")
sourceType String? @map("source_type") // ✅ 新增direct | document | continue
persona String? @map("persona") // ✅ 新增architect | muse | hacker
mode String? // "detailed" | "essence"
modelProvider String @default("doubao") @map("model_provider")
status String @default("pending") // pending | mode_selected | outline_generating | outline_completed | content_generating | completed | failed
progress Int @default(0) // 0-100
errorMessage String? @map("error_message")
outline Json? // 生成的大纲JSON格式
currentStep String? @map("current_step") // 当前步骤outline | content | node_xxx
saveAsDraft Boolean @default(false) @map("save_as_draft") // 后台创建:生成完成后不自动发布、不自动加入 UserCourse
promptSent String? @map("prompt_sent") // 当时发给模型的真实提示词fire-and-forget 写入,供调用记录查看)
modelId String? @map("model_id") // 本次任务实际使用的模型 ID如 doubao-seed-1-6-flash-250828 / doubao-seed-1-6-lite-251015供调用记录详情展示
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([status])
@@map("course_generation_tasks")
}
model CourseChapter {
id String @id @default(uuid())
courseId String @map("course_id")
title String
orderIndex Int @map("order_index")
createdAt DateTime @default(now()) @map("created_at")
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
nodes CourseNode[]
@@unique([courseId, orderIndex])
@@map("course_chapters")
}
model CourseNode {
id String @id @default(uuid())
courseId String @map("course_id")
chapterId String? @map("chapter_id") // 可选,支持无章节的节点
title String
subtitle String?
orderIndex Int @map("order_index")
duration Int? // 预估时长(分钟)
unlockCondition String? @map("unlock_condition") // 解锁条件
createdAt DateTime @default(now()) @map("created_at")
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
chapter CourseChapter? @relation(fields: [chapterId], references: [id], onDelete: SetNull)
slides NodeSlide[]
learningProgress UserLearningProgress[]
notes Note[]
@@unique([courseId, orderIndex])
@@map("course_nodes")
}
model NodeSlide {
id String @id @default(uuid())
nodeId String @map("node_id")
slideType String @map("slide_type") // text | image | quiz | interactive
orderIndex Int @map("order_index")
content Json // 存储卡片内容(灵活结构)
effect String? // fade_in | typewriter | slide_up
interaction String? // tap_to_reveal | zoom | parallax
createdAt DateTime @default(now()) @map("created_at")
node CourseNode @relation(fields: [nodeId], references: [id], onDelete: Cascade)
@@map("node_slides")
}
model UserLearningProgress {
id String @id @default(uuid())
userId String @map("user_id")
nodeId String @map("node_id")
status String @default("not_started") // not_started | in_progress | completed
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
totalStudyTime Int @default(0) @map("total_study_time") // 总学习时长(秒)
currentSlide Int @default(0) @map("current_slide") // 当前学习到的幻灯片位置
completionRate Int @default(0) @map("completion_rate") // 完成度(%
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
node CourseNode @relation(fields: [nodeId], references: [id], onDelete: Cascade)
@@unique([userId, nodeId])
@@map("user_learning_progress")
}
model UserAchievement {
id String @id @default(uuid())
userId String @map("user_id")
achievementType String @map("achievement_type") // lesson_completed | course_completed
achievementData Json @map("achievement_data") // 存储成就详情
createdAt DateTime @default(now()) @map("created_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("user_achievements")
}
model UserCourse {
id String @id @default(uuid())
userId String @map("user_id")
courseId String @map("course_id")
lastOpenedAt DateTime? @map("last_opened_at") // ✅ 新增:最近打开时间,用于排序
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
@@unique([userId, courseId])
@@map("user_courses")
}
// ✅ Phase 1: 新增 Notebook 模型
model Notebook {
id String @id @default(uuid())
userId String @map("user_id")
title String // 笔记本名称1-50字符
description String? // 描述可选0-200字符
coverImage String? @map("cover_image") // 封面图片,可选
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
notes Note[]
@@index([userId])
@@map("notebooks")
}
// ✅ Phase 1: 扩展 Note 模型,支持笔记本和层级结构
model Note {
id String @id @default(uuid())
userId String @map("user_id")
// ✅ 新增:笔记本和层级字段
notebookId String? @map("notebook_id") // 所属笔记本 IDnil 表示未分类
parentId String? @map("parent_id") // 父笔记 IDnil 表示顶级
order Int @default(0) // 同级排序0, 1, 2...
level Int @default(0) // 层级深度0=顶级, 1=二级, 2=三级
// ✅ 修改:这些字段改为可选(支持独立笔记)
courseId String? @map("course_id")
nodeId String? @map("node_id")
startIndex Int? @map("start_index") // 全局 NSRange.location相对于合并后的全文
length Int? // 全局 NSRange.length
type String // highlight | thought | comment未来扩展
content String // 笔记内容(想法笔记的内容,或划线笔记的备注)
quotedText String? @map("quoted_text") // ✅ 修改:改为可选,独立笔记为 null
style String? // 样式(如 "yellow", "purple", "underline" 等,未来扩展)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: Cascade) // ✅ 重要Cascade 删除
course Course? @relation(fields: [courseId], references: [id], onDelete: Cascade)
node CourseNode? @relation(fields: [nodeId], references: [id], onDelete: Cascade)
@@index([userId, courseId])
@@index([userId, nodeId])
@@index([courseId, nodeId])
@@index([userId, notebookId]) // ✅ 新增:按笔记本查询
@@index([notebookId, parentId]) // ✅ 新增:按父笔记查询
@@index([notebookId, order]) // ✅ 新增:按排序查询
@@map("notes")
}
// 应用配置(如书籍解析 Prompt 等可运维修改的文案)
model AppConfig {
id String @id @default(uuid())
key String @unique
value String @db.Text
updatedAt DateTime @updatedAt @map("updated_at")
@@map("app_config")
}
// ✅ AI 提示词配置表提示词管理2.0
model AiPromptConfig {
id String @id @default(uuid())
promptType String @unique @map("prompt_type") // summary | outline | outline-essence | outline-detailed | content
promptTitle String? @map("prompt_title") // 提示词标题
description String? @db.Text // 描述
systemPrompt String @db.Text @map("system_prompt") // 系统提示词(可编辑)
userPromptTemplate String @db.Text @map("user_prompt_template") // 用户提示词模板(只读展示)
variables Json? // 变量说明 [{name, description, required, example}]
temperature Float @default(0.7) // 温度参数
maxTokens Int? @map("max_tokens") // 最大token数
topP Float? @map("top_p") // Top P 参数
enabled Boolean @default(true) // 是否启用
version Int @default(1) // 版本号
isDefault Boolean @default(false) @map("is_default") // 是否为默认配置
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([promptType])
@@map("ai_prompt_configs")
}
// 书籍解析 AI 调用日志(仅后台查看,不影响主流程)
model BookAiCallLog {
id String @id @default(uuid())
taskId String @map("task_id")
courseId String? @map("course_id")
chunkIndex Int? @map("chunk_index") // 切块时第几段0-basednull=单次
status String // success | failed
promptPreview String @map("prompt_preview") @db.Text // 前 2000 字
promptFull String @map("prompt_full") @db.Text // 完整,存时截断 500KB
responsePreview String? @map("response_preview") @db.Text // 前 5000 字
responseFull String? @map("response_full") @db.Text // 完整,存时截断 500KB
errorMessage String? @map("error_message") @db.Text
durationMs Int? @map("duration_ms")
createdAt DateTime @default(now()) @map("created_at")
@@index([taskId])
@@index([createdAt])
@@map("book_ai_call_logs")
}
// ✅ V1.0 埋点体系:轻量级自建事件追踪
model AnalyticsEvent {
id BigInt @id @default(autoincrement())
userId String? @map("user_id")
deviceId String @map("device_id")
sessionId String @map("session_id")
eventName String @map("event_name")
properties Json?
appVersion String? @map("app_version")
osVersion String? @map("os_version")
deviceModel String? @map("device_model")
networkType String? @map("network_type")
clientTs DateTime @map("client_ts")
serverTs DateTime @default(now()) @map("server_ts")
@@index([userId])
@@index([eventName])
@@index([clientTs])
@@index([sessionId])
@@map("analytics_events")
}
// ✅ AI 相关模型已删除