Files
ai-learning-platform/backend/prisma/schema.prisma
T
yuzhiran-dev 0f215d2aad P8 平台轻量化改造 + SSG修复 + 编程导师 + 文档完善
- Prisma: Tool 模型加 affiliateLink;免费用户沙盒 10→5 次/日
- 后端: Tools API + /admin/tools CRUD 5 端点;Practices 完整模块
- 导航: 主菜单隐藏企业版/社区(URL 可访问)
- 首页: 重定位为 AI 工具指南;新增精选工具区块;Feature 重写
- 工具页: affiliateLink 绿色推荐 Badge
- SSG 修复: config.ts 构建时直连 localhost:4000,页面 108→127
- 沙盒: 新增编程导师场景(苏格拉底教学法)
- 练习系统: Practices 多场景练习(含结构化评分)
- 技能广场: 6 个付费 Skill(标题大师/回款助手等)
- 管理后台: Models/Posts/Practices CRUD 页面
- 文档: README + progress.md 全面更新;AGENTS.md 同步定位
- 清理: .env.example 移除;tsbuildinfo gitignore
2026-06-18 18:14:07 +08:00

664 lines
18 KiB
Plaintext
Executable File

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
enum UserStatus {
ACTIVE
INACTIVE
BANNED
}
enum ContentStatus {
DRAFT
PUBLISHED
ARCHIVED
}
enum OrderStatus {
PENDING
PAID
CANCELLED
REFUNDED
}
enum MemberPlan {
FREE
MONTHLY
YEARLY
}
model User {
id Int @id @default(autoincrement())
username String? @unique
phone String? @unique
email String? @unique
passwordHash String?
nickname String?
avatar String?
bio String?
status UserStatus @default(ACTIVE)
memberPlan MemberPlan @default(FREE)
memberExpire DateTime?
sandboxDaily Int @default(5)
sandboxExtra Int @default(0)
followerCount Int @default(0)
followingCount Int @default(0)
postCount Int @default(0)
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
learnRecords LearnRecord[]
promptFavorites PromptFavorite[]
prompts Prompt[]
sandboxSessions SandboxSession[]
orders Order[]
subscriptions Subscription[]
posts Post[]
comments Comment[]
postLikes PostLike[]
followers Follow[] @relation("Following")
following Follow[] @relation("Follower")
createdCircles Circle[] @relation("CircleCreator")
circleMemberships CircleMember[]
organizationMemberships OrganizationMember[]
notifications Notification[]
practiceSubmissions PracticeSubmission[]
userSkills UserSkill[]
@@map("users")
}
model Category {
id Int @id @default(autoincrement())
name String
slug String @unique
description String?
sortOrder Int @default(0)
parentId Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
parent Category? @relation("CategoryTree", fields: [parentId], references: [id])
children Category[] @relation("CategoryTree")
courses Course[]
prompts Prompt[]
contents Content[]
tools Tool[]
@@map("categories")
}
model Course {
id Int @id @default(autoincrement())
title String
description String?
cover String?
categoryId Int?
price Float @default(0)
isFree Boolean @default(true)
status ContentStatus @default(DRAFT)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
category Category? @relation(fields: [categoryId], references: [id])
chapters Chapter[]
progress LearnRecord[]
assignments CourseAssignment[]
@@map("courses")
}
model Chapter {
id Int @id @default(autoincrement())
courseId Int
title String
sortOrder Int @default(0)
createdAt DateTime @default(now())
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
lessons Lesson[]
@@map("chapters")
}
model Lesson {
id Int @id @default(autoincrement())
chapterId Int
title String
content String? @db.Text
videoUrl String?
duration Int?
sortOrder Int @default(0)
status ContentStatus @default(DRAFT)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
chapter Chapter @relation(fields: [chapterId], references: [id], onDelete: Cascade)
progress LearnRecord[]
@@map("lessons")
}
model LearnRecord {
id Int @id @default(autoincrement())
userId Int
courseId Int
lessonId Int
completed Boolean @default(false)
progress Float @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
@@unique([userId, lessonId])
@@map("learn_records")
}
model Prompt {
id Int @id @default(autoincrement())
title String
content String
description String?
categoryId Int?
tags String?
authorId Int?
model String?
isPublic Boolean @default(true)
status ContentStatus @default(PUBLISHED)
viewCount Int @default(0)
likeCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
category Category? @relation(fields: [categoryId], references: [id])
author User? @relation(fields: [authorId], references: [id])
favorites PromptFavorite[]
@@map("prompts")
}
model PromptFavorite {
id Int @id @default(autoincrement())
userId Int
promptId Int
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade)
@@unique([userId, promptId])
@@map("prompt_favorites")
}
model Tool {
id Int @id @default(autoincrement())
name String
description String?
url String
icon String?
affiliateLink String?
categoryId Int?
tags String?
isFeatured Boolean @default(false)
status ContentStatus @default(PUBLISHED)
viewCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
category Category? @relation(fields: [categoryId], references: [id])
@@map("tools")
}
model Content {
id Int @id @default(autoincrement())
title String
summary String?
content String? @db.Text
cover String?
categoryId Int?
tags String?
authorName String?
contentType String @default("article")
status ContentStatus @default(DRAFT)
viewCount Int @default(0)
isAiGenerated Boolean @default(false)
publishedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
category Category? @relation(fields: [categoryId], references: [id])
@@map("contents")
}
model AiModel {
id Int @id @default(autoincrement())
name String
provider String
description String?
capabilities String?
contextWindow Int?
maxTokens Int?
pricing String?
isFree Boolean @default(false)
isFeatured Boolean @default(false)
icon String?
sortOrder Int @default(0)
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("ai_models")
}
model Skill {
id String @id
name String
description String
icon String
category String
difficulty String
systemPrompt String @db.Text
starters String @db.Text
tasks String @db.Text
tags String
prerequisiteIds String?
price Float? @default(0)
sortOrder Int @default(0)
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
purchasedBy UserSkill[]
@@map("skills")
}
model SandboxSession {
id Int @id @default(autoincrement())
userId Int
conversationId String @default("")
model String
title String @default("AI 对话")
messages String @db.Text
feedback String?
tokens Int @default(0)
duration Int @default(0)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, conversationId])
@@index([userId, createdAt])
@@map("sandbox_sessions")
}
model Order {
id Int @id @default(autoincrement())
orderNo String @unique
userId Int
amount Float
planType String
status OrderStatus @default(PENDING)
payChannel String?
transactionId String?
gatewayOrderId String?
metadata String?
paidAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userSkills UserSkill[]
@@index([userId, status])
@@index([status, createdAt])
@@map("orders")
}
model UserSkill {
id Int @id @default(autoincrement())
userId Int
skillId String
orderId Int?
purchasedAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
skill Skill @relation(fields: [skillId], references: [id])
order Order? @relation(fields: [orderId], references: [id])
@@unique([userId, skillId])
@@map("user_skills")
}
model Subscription {
id Int @id @default(autoincrement())
userId Int
plan MemberPlan
startDate DateTime
endDate DateTime
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@index([userId, status])
@@map("subscriptions")
}
model AdminRole {
id String @id @default(cuid())
name String @unique
description String?
permissions Json @default("[]")
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
admins AdminUser[]
@@map("admin_roles")
}
model AdminUser {
id Int @id @default(autoincrement())
username String @unique
passwordHash String
nickname String?
roleId String?
role AdminRole? @relation(fields: [roleId], references: [id])
status String @default("ACTIVE")
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("admin_users")
}
model AdminLog {
id Int @id @default(autoincrement())
adminId Int
action String
target String?
detail String?
ip String?
createdAt DateTime @default(now())
@@map("admin_logs")
}
model Post {
id Int @id @default(autoincrement())
userId Int
title String
content String @db.Text
tags String?
circleId Int?
status String @default("PUBLISHED")
viewCount Int @default(0)
likeCount Int @default(0)
commentCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
comments Comment[]
likes PostLike[]
circle Circle? @relation(fields: [circleId], references: [id])
@@index([userId])
@@index([status, createdAt])
@@index([circleId])
@@map("posts")
}
model Comment {
id Int @id @default(autoincrement())
postId Int
userId Int
content String
parentId Int?
status String @default("PUBLISHED")
reviewNote String?
reviewedAt DateTime?
createdAt DateTime @default(now())
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
parent Comment? @relation("CommentReply", fields: [parentId], references: [id])
replies Comment[] @relation("CommentReply")
@@index([postId])
@@index([status])
@@map("comments")
}
model PostLike {
id Int @id @default(autoincrement())
postId Int
userId Int
createdAt DateTime @default(now())
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([postId, userId])
@@map("post_likes")
}
model Circle {
id Int @id @default(autoincrement())
name String
description String?
tags String?
creatorId Int
isPublic Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
creator User @relation("CircleCreator", fields: [creatorId], references: [id])
members CircleMember[]
posts Post[]
@@map("circles")
}
model CircleMember {
id Int @id @default(autoincrement())
circleId Int
userId Int
role String @default("member")
joinedAt DateTime @default(now())
circle Circle @relation(fields: [circleId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([circleId, userId])
@@map("circle_members")
}
model Follow {
id Int @id @default(autoincrement())
followerId Int
followingId Int
createdAt DateTime @default(now())
follower User @relation("Follower", fields: [followerId], references: [id], onDelete: Cascade)
following User @relation("Following", fields: [followingId], references: [id], onDelete: Cascade)
@@unique([followerId, followingId])
@@map("follows")
}
model Organization {
id Int @id @default(autoincrement())
name String
description String?
contactName String?
contactPhone String?
logo String?
memberCount Int @default(0)
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
members OrganizationMember[]
assignments CourseAssignment[]
@@map("organizations")
}
model OrganizationMember {
id Int @id @default(autoincrement())
organizationId Int
userId Int
role String @default("MEMBER")
status String @default("ACTIVE")
joinedAt DateTime @default(now())
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([organizationId, userId])
@@map("organization_members")
}
model CourseAssignment {
id Int @id @default(autoincrement())
organizationId Int
courseId Int
assignedBy Int
deadline DateTime?
createdAt DateTime @default(now())
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
@@map("course_assignments")
}
model Notification {
id Int @id @default(autoincrement())
userId Int
type String // 'like', 'comment', 'follow', 'system'
title String
content String?
link String?
relatedId Int?
isRead Boolean @default(false)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, isRead])
@@map("notifications")
}
model PracticeQuestion {
id Int @id @default(autoincrement())
skillId String?
scenario String @db.Text
title String
description String @db.Text
instructions String @db.Text
difficulty String @default("BEGINNER")
category String @default("general")
expectedCriteria String @db.Text
starterCode String? @db.Text
hint String? @db.Text
sortOrder Int @default(0)
status String @default("PUBLISHED")
attemptCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
submissions PracticeSubmission[]
@@map("practice_questions")
}
model PracticeSubmission {
id Int @id @default(autoincrement())
userId Int
questionId Int
answer String @db.Text
score Int?
maxScore Int @default(100)
feedback String? @db.Text
criteriaScores String? @db.Text
duration Int @default(0)
status String @default("SUBMITTED")
submittedAt DateTime @default(now())
scoredAt DateTime?
question PracticeQuestion @relation(fields: [questionId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([questionId])
@@index([userId, status])
@@map("practice_submissions")
}
model Banner {
id Int @id @default(autoincrement())
title String
image String
link String?
position String @default("home")
sortOrder Int @default(0)
status String @default("DRAFT")
startAt DateTime?
endAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("banners")
}
model SystemNotification {
id Int @id @default(autoincrement())
title String
content String
type String @default("system")
target String @default("all")
targetIds String?
status String @default("DRAFT")
scheduledAt DateTime?
sentAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("system_notifications")
}
model SystemConfig {
id String @id @default(cuid())
category String
key String @unique
value String
description String?
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("system_configs")
}