Files
ai-learning-platform/backend/prisma/schema.prisma
T
yuzhiran-dev 728edc59ef feat: admin back-office system + AI assistant with action commands
- Admin analytics (recharts charts, overview stats, trend analysis, time range selector)
- Admin permissions (AdminRole/AdminUser models, role CRUD, permission catalog)
- Admin system config (site/AI/member category tabs, per-key save)
- Admin operations (Banner CRUD, push notification send/delete)
- Admin user management (table, search, create/edit, ban/delete)
- Admin layout (custom top bar with branding + sidebar, admin AI assistant)
- Admin login (adminToken localStorage, adminInfo display)
- Admin AI assistant (purple '运营助手', context-aware prompts, action commands)
- Public AI assistant Phase B (action commands: navigate, setModel, startChat, openSkill, setParameter)
- Assistant context mapping + action executor
- Skills API fix: tags parsing fallback (JSON.parse → split)
- Sandbox crash fix: curScene fallback system prompt
- JWT token expiration: access 2h→7d, refresh 7d→30d, admin 8h→7d
- i18n: assistant-related translations (zh/en)
- PM2 ecosystem config for process management
- AI assistant login prompt fix: admin uses getAdminToken(), public uses apiFetch with token refresh
2026-05-20 10:38:58 +08:00

591 lines
15 KiB
Plaintext

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())
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(10)
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[]
@@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?
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?
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?
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?
sortOrder Int @default(0)
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@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
user User @relation(fields: [userId], references: [id])
@@index([userId, status])
@@index([status, createdAt])
@@map("orders")
}
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
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 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")
}