001project_wildgrowth/backend/prisma/schema.prisma

378 lines
16 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 相关模型已删除