Files
ai-learning-platform/backend/prisma/schema.prisma
T
TradeMate Dev 00a3904eba feat: 落地打赏(个人码捐赠)模式并将付费面改为免费+赞助(无ICP证合规)
- 新增 Donation 模型/迁移/后端 API(GET,POST /donations)
- 新增前端 /donate 页:收款码+感谢留言+感谢墙
- 会员页/技能广场/用量包 下架付费,改免费开放+赞助入口
- 沙箱用量耗尽引导至 /donate 赞助
- 导航与页脚新增 赞助/联盟返佣 入口,补充 i18n
2026-07-11 15:02:08 +08:00

733 lines
22 KiB
Plaintext
Executable File

generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
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)
followerCount Int @default(0)
followingCount Int @default(0)
postCount Int @default(0)
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
username String? @unique
sandboxExtra Int @default(0)
circleMemberships CircleMember[]
createdCircles Circle[] @relation("CircleCreator")
comments Comment[]
following Follow[] @relation("Follower")
followers Follow[] @relation("Following")
learnRecords LearnRecord[]
notifications Notification[]
orders Order[]
organizationMemberships OrganizationMember[]
postLikes PostLike[]
posts Post[]
practiceSubmissions PracticeSubmission[]
promptFavorites PromptFavorite[]
prompts Prompt[]
sandboxSessions SandboxSession[]
subscriptions Subscription[]
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")
contents Content[]
courses Course[]
prompts Prompt[]
tools Tool[]
@@index([parentId], map: "categories_parentId_fkey")
@@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?
chapters Chapter[]
assignments CourseAssignment[]
category Category? @relation(fields: [categoryId], references: [id])
progress LearnRecord[]
@@index([categoryId], map: "courses_categoryId_fkey")
@@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[]
@@index([courseId], map: "chapters_courseId_fkey")
@@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
progress LearnRecord[]
chapter Chapter @relation(fields: [chapterId], references: [id], onDelete: Cascade)
@@index([chapterId], map: "lessons_chapterId_fkey")
@@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
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, lessonId])
@@index([courseId], map: "learn_records_courseId_fkey")
@@index([lessonId], map: "learn_records_lessonId_fkey")
@@map("learn_records")
}
model Prompt {
id Int @id @default(autoincrement())
title String
content String @db.Text
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?
favorites PromptFavorite[]
author User? @relation(fields: [authorId], references: [id])
category Category? @relation(fields: [categoryId], references: [id])
@@index([authorId], map: "prompts_authorId_fkey")
@@index([categoryId], map: "prompts_categoryId_fkey")
@@map("prompts")
}
model PromptFavorite {
id Int @id @default(autoincrement())
userId Int
promptId Int
createdAt DateTime @default(now())
prompt Prompt @relation(fields: [promptId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, promptId])
@@index([promptId], map: "prompt_favorites_promptId_fkey")
@@map("prompt_favorites")
}
model Tool {
id Int @id @default(autoincrement())
slug String? @unique(map: "slug") @db.VarChar(200)
name String
metaTitle String? @db.VarChar(200)
metaDesc String? @db.VarChar(300)
description String?
content String? @db.LongText
url String
icon 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?
affiliateLink String?
category Category? @relation(fields: [categoryId], references: [id])
@@index([categoryId], map: "tools_categoryId_fkey")
@@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])
@@index([categoryId], map: "contents_categoryId_fkey")
@@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?
sortOrder Int @default(0)
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
price Float? @default(0)
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?
paidAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
transactionId String?
gatewayOrderId String?
metadata String?
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())
order Order? @relation(fields: [orderId], references: [id])
skill Skill @relation(fields: [skillId], references: [id])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([userId, skillId])
@@index([orderId], map: "user_skills_orderId_fkey")
@@index([skillId], map: "user_skills_skillId_fkey")
@@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
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?
status String @default("ACTIVE")
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
role AdminRole? @relation(fields: [roleId], references: [id])
@@index([roleId], map: "admin_users_roleId_fkey")
@@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
comments Comment[]
likes PostLike[]
circle Circle? @relation(fields: [circleId], references: [id])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@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())
parent Comment? @relation("CommentReply", fields: [parentId], references: [id])
replies Comment[] @relation("CommentReply")
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([postId])
@@index([status])
@@index([parentId], map: "comments_parentId_fkey")
@@index([userId], map: "comments_userId_fkey")
@@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])
@@index([userId], map: "post_likes_userId_fkey")
@@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
members CircleMember[]
creator User @relation("CircleCreator", fields: [creatorId], references: [id])
posts Post[]
@@index([creatorId], map: "circles_creatorId_fkey")
@@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])
@@index([userId], map: "circle_members_userId_fkey")
@@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])
@@index([followingId], map: "follows_followingId_fkey")
@@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
assignments CourseAssignment[]
members OrganizationMember[]
@@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])
@@index([userId], map: "organization_members_userId_fkey")
@@map("organization_members")
}
model CourseAssignment {
id Int @id @default(autoincrement())
organizationId Int
courseId Int
assignedBy Int
deadline DateTime?
createdAt DateTime @default(now())
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
@@index([courseId], map: "course_assignments_courseId_fkey")
@@index([organizationId], map: "course_assignments_organizationId_fkey")
@@map("course_assignments")
}
model Notification {
id Int @id @default(autoincrement())
userId Int
type String
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")
}
enum UserStatus {
ACTIVE
INACTIVE
BANNED
}
enum ContentStatus {
DRAFT
PUBLISHED
ARCHIVED
}
enum OrderStatus {
PENDING
PAID
CANCELLED
REFUNDED
}
enum MemberPlan {
FREE
MONTHLY
YEARLY
}
model AffiliateProgram {
id Int @id @default(autoincrement())
name String
slug String @unique
provider String?
description String?
commissionRate String?
cookieDays Int?
homepageUrl String?
logo String?
active Boolean @default(true)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
links AffiliateLink[]
@@map("affiliate_programs")
}
model AffiliateLink {
id Int @id @default(autoincrement())
programId Int
title String
description String?
url String
thumbnail String?
toolId Int?
skillId String?
tags String?
estimatedCommission Float @default(0)
sortOrder Int @default(0)
isFeatured Boolean @default(false)
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
clicks AffiliateClick[]
program AffiliateProgram @relation(fields: [programId], references: [id], onDelete: Cascade)
@@index([programId])
@@index([toolId])
@@index([skillId])
@@map("affiliate_links")
}
model AffiliateClick {
id Int @id @default(autoincrement())
linkId Int
userId Int?
ip String?
userAgent String?
ref String?
createdAt DateTime @default(now())
link AffiliateLink @relation(fields: [linkId], references: [id], onDelete: Cascade)
@@index([linkId])
@@map("affiliate_clicks")
}
model Donation {
id Int @id @default(autoincrement())
name String?
message String? @db.VarChar(280)
channel String @default("ALIPAY")
amount Float?
createdAt DateTime @default(now())
@@index([createdAt])
@@map("donations")
}