// 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") // 所属笔记本 ID,nil 表示未分类 parentId String? @map("parent_id") // 父笔记 ID,nil 表示顶级 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-based;null=单次 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 相关模型已删除