api-server/prisma/schema.prisma
WangDL 8a6b103571
Some checks failed
Deploy API Server / build-and-deploy (push) Has been cancelled
fix: add createdAt index to ReviewCard model to fix slow admin page load
The admin review list query uses ORDER BY createdAt DESC but there was no
index on createdAt, causing full table scan + filesort on large tables.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 19:55:01 +08:00

1539 lines
45 KiB
Plaintext

generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String? @db.VarChar(255)
nickname String? @db.VarChar(100)
avatarUrl String? @db.VarChar(500)
role String @default("USER") @db.VarChar(32)
status String @default("active") @db.VarChar(32)
onboardingCompleted Boolean @default(false)
lastLoginAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
authAccounts AuthAccount[]
refreshTokens RefreshToken[]
memberships UserMembership[]
profile UserProfile?
preferences UserPreference?
consents UserConsent[]
knowledgeBases KnowledgeBase[]
knowledgeItems KnowledgeItem[]
knowledgeItemRelations KnowledgeItemRelation[]
tags Tag[]
uploadedFiles UploadedFile[]
documentImports DocumentImport[]
learningSessions LearningSession[]
learningRecords LearningRecord[]
activeRecallQuestions ActiveRecallQuestion[]
activeRecallAnswers ActiveRecallAnswer[]
aiAnalysisJobs AiAnalysisJob[]
aiAnalysisResults AiAnalysisResult[]
focusItems FocusItem[]
reviewCards ReviewCard[]
reviewLogs ReviewLog[]
reviewPlans ReviewPlan[]
dailyLearningActivities DailyLearningActivity[]
notifications Notification[]
feedbacks Feedback[]
aiUsageLogs AiUsageLog[]
knowledgeSources KnowledgeSource[]
knowledgeChunks KnowledgeChunk[]
importCandidates ImportCandidate[]
@@index([email])
@@index([status])
}
model AuthAccount {
id String @id @default(cuid())
userId String
provider String @db.VarChar(32)
providerUserId String @db.VarChar(255)
email String? @db.VarChar(255)
rawProfileJson Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@unique([provider, providerUserId])
@@index([userId])
}
model RefreshToken {
id String @id @default(cuid())
userId String
tokenHash String @db.VarChar(255)
deviceId String? @db.VarChar(255)
deviceName String? @db.VarChar(255)
expiresAt DateTime
revokedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@index([userId])
@@index([tokenHash])
}
model UserProfile {
id String @id @default(cuid())
userId String @unique
learningIdentity String? @db.VarChar(100)
learningDirection String? @db.VarChar(255)
bio String? @db.Text
currentGoal String? @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
}
model UserPreference {
id String @id @default(cuid())
userId String @unique
preferredMethods Json?
defaultFocusMinutes Int @default(25)
aiSuggestionLevel String @default("normal") @db.VarChar(32)
language String @default("zh-CN") @db.VarChar(32)
appearance String @default("system") @db.VarChar(32)
notificationEnabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
}
model UserConsent {
id String @id @default(cuid())
userId String
consentType String @db.VarChar(32)
version String @db.VarChar(50)
acceptedAt DateTime
ipAddress String? @db.VarChar(100)
userAgent String? @db.VarChar(500)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
@@index([userId])
@@index([consentType])
}
model Workspace {
id String @id @default(cuid())
userId String
name String @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
}
model KnowledgeFolder {
id String @id @default(cuid())
knowledgeBaseId String
parentId String?
name String @db.VarChar(255)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
knowledgeBase KnowledgeBase @relation(fields: [knowledgeBaseId], references: [id])
parent KnowledgeFolder? @relation("FolderTree", fields: [parentId], references: [id])
children KnowledgeFolder[] @relation("FolderTree")
@@index([knowledgeBaseId])
@@index([parentId])
}
model KnowledgeBase {
id String @id @default(cuid())
userId String
title String @db.VarChar(255)
description String? @db.Text
coverKey String? @db.VarChar(100)
status String @default("active") @db.VarChar(32)
itemCount Int @default(0)
lastStudiedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
user User @relation(fields: [userId], references: [id])
items KnowledgeItem[]
sources KnowledgeSource[]
candidates ImportCandidate[]
chunks KnowledgeChunk[]
focusItems FocusItem[]
folders KnowledgeFolder[]
@@index([userId])
@@index([status])
}
model Artifact {
id String @id @default(cuid())
userId String
kbId String
type String @db.VarChar(32)
title String @db.VarChar(255)
configJson Json?
status String @default("draft") @db.VarChar(16)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([kbId])
}
model KnowledgeItem {
id String @id @default(cuid())
userId String
knowledgeBaseId String
parentId String?
itemType String @db.VarChar(32)
title String @db.VarChar(255)
content String? @db.LongText
summary String? @db.Text
learnable Boolean @default(true)
sourceType String? @db.VarChar(32)
sourceRef String? @db.VarChar(500)
sourceDeleted Boolean @default(false)
sourceTitleSnapshot String? @db.VarChar(255)
sourceSnippetSnapshot String? @db.Text
orderIndex Int @default(0)
status String @default("active") @db.VarChar(32)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
user User @relation(fields: [userId], references: [id])
knowledgeBase KnowledgeBase @relation(fields: [knowledgeBaseId], references: [id])
parent KnowledgeItem? @relation("KnowledgeItemRelations", fields: [parentId], references: [id])
children KnowledgeItem[] @relation("KnowledgeItemRelations")
tags KnowledgeItemTag[]
@@index([userId])
@@index([knowledgeBaseId])
@@index([parentId])
@@index([itemType])
}
model KnowledgeItemRelation {
id String @id @default(cuid())
userId String
sourceItemId String
targetItemId String
relationType String @db.VarChar(32)
confidence Decimal? @db.Decimal(5, 2)
reason String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@index([sourceItemId])
@@index([targetItemId])
}
model Tag {
id String @id @default(cuid())
userId String
name String @db.VarChar(100)
color String? @db.VarChar(32)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
items KnowledgeItemTag[]
@@unique([userId, name])
}
model KnowledgeItemTag {
id String @id @default(cuid())
knowledgeItemId String
tagId String
createdAt DateTime @default(now())
knowledgeItem KnowledgeItem @relation(fields: [knowledgeItemId], references: [id])
tag Tag @relation(fields: [tagId], references: [id])
@@unique([knowledgeItemId, tagId])
}
model UploadedFile {
id String @id @default(cuid())
userId String
filename String @db.VarChar(255)
mimeType String? @db.VarChar(100)
storagePath String @db.VarChar(500)
objectKey String? @db.VarChar(500)
bucket String? @db.VarChar(100)
sizeBytes BigInt @default(0)
checksum String? @db.VarChar(255)
sha256 String? @db.VarChar(64)
purpose String? @db.VarChar(32)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
sources KnowledgeSource[]
@@index([userId])
@@index([objectKey])
@@index([sha256])
}
model DocumentImport {
id String @id @default(cuid())
userId String
knowledgeBaseId String?
sourceId String?
fileId String?
sourceType String @db.VarChar(32)
sourceName String? @db.VarChar(255)
sourceUrl String? @db.VarChar(500)
rawText String? @db.LongText
status String @default("QUEUED") @db.VarChar(32)
step String? @db.VarChar(32)
progress Int @default(0)
workerId String? @db.VarChar(255)
retryCount Int @default(0)
maxRetries Int @default(3)
heartbeatAt DateTime?
errorCode String? @db.VarChar(32)
errorMessage String? @db.Text
resultJson Json?
startedAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
source KnowledgeSource? @relation(fields: [sourceId], references: [id])
candidates ImportCandidate[]
@@index([userId])
@@index([status])
@@index([sourceId])
@@index([workerId])
}
model ImportStepLog {
id String @id @default(cuid())
importId String
step String @db.VarChar(32)
status String @db.VarChar(16)
detail String? @db.VarChar(500)
startedAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
@@index([importId])
}
model LearningSession {
id String @id @default(cuid())
userId String
knowledgeBaseId String?
knowledgeItemId String?
mode String @db.VarChar(32)
status String @default("active") @db.VarChar(32)
startedAt DateTime
endedAt DateTime?
durationSeconds Int @default(0)
focusMinutes Int?
metadata Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@index([userId])
@@index([knowledgeItemId])
@@index([startedAt])
}
model LearningRecord {
id String @id @default(cuid())
userId String
sessionId String?
recordType String @db.VarChar(32)
title String @db.VarChar(255)
description String? @db.Text
durationSeconds Int @default(0)
occurredAt DateTime
metadata Json?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
@@index([userId])
@@index([occurredAt])
}
model ActiveRecallQuestion {
id String @id @default(cuid())
userId String
knowledgeItemId String?
questionText String @db.Text
difficulty String? @db.VarChar(32)
createdBy String @default("ai") @db.VarChar(32)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
answers ActiveRecallAnswer[]
@@index([userId])
@@index([knowledgeItemId])
}
model ActiveRecallAnswer {
id String @id @default(cuid())
userId String
questionId String?
sessionId String?
answerType String @default("text") @db.VarChar(32)
answerText String? @db.LongText
audioFileId String?
submittedAt DateTime
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
question ActiveRecallQuestion? @relation(fields: [questionId], references: [id])
@@index([userId])
@@index([questionId])
@@index([sessionId])
}
model AiAnalysisJob {
id String @id @default(cuid())
userId String
sessionId String?
answerId String?
jobType String @db.VarChar(32)
status String @default("pending") @db.VarChar(32)
progress Int @default(0)
errorMessage String? @db.Text
queuedAt DateTime?
startedAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
results AiAnalysisResult[]
@@index([userId])
@@index([status])
@@index([sessionId])
}
model AiAnalysisResult {
id String @id @default(cuid())
userId String
jobId String
sessionId String?
answerId String?
summary String? @db.Text
masteryScore Int?
strengths Json?
weaknesses Json?
suggestions Json?
nextActions Json?
rawResult Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
job AiAnalysisJob @relation(fields: [jobId], references: [id])
@@index([userId])
@@index([jobId])
@@index([sessionId])
}
model FocusItem {
id String @id @default(cuid())
userId String
knowledgeBaseId String?
knowledgeItemId String?
analysisResultId String?
title String @db.VarChar(255)
reason String? @db.Text
suggestion String? @db.Text
priority String @default("normal") @db.VarChar(32)
status String @default("open") @db.VarChar(32)
source String? @db.VarChar(32)
masteryScore Int?
dueAt DateTime?
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
user User @relation(fields: [userId], references: [id])
knowledgeBase KnowledgeBase? @relation(fields: [knowledgeBaseId], references: [id])
@@index([userId])
@@index([status])
@@index([dueAt])
}
model ReviewCard {
id String @id @default(cuid())
userId String
knowledgeItemId String?
focusItemId String?
frontText String @db.Text
backText String? @db.Text
difficulty String? @db.VarChar(32)
status String @default("active") @db.VarChar(32)
nextReviewAt DateTime?
intervalDays Int @default(1)
easeFactor Decimal @default(2.50) @db.Decimal(4, 2)
repetitionCount Int @default(0)
lapseCount Int @default(0)
scheduleState String? @db.VarChar(16)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
user User @relation(fields: [userId], references: [id])
logs ReviewLog[]
@@index([userId])
@@index([nextReviewAt])
@@index([focusItemId])
@@index([createdAt])
}
model ReviewLog {
id String @id @default(cuid())
userId String
reviewCardId String
sessionId String?
rating String @db.VarChar(32)
responseText String? @db.Text
reviewedAt DateTime
nextReviewAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
reviewCard ReviewCard @relation(fields: [reviewCardId], references: [id])
@@index([userId])
@@index([reviewCardId])
@@index([reviewedAt])
}
model ReviewPlan {
id String @id @default(cuid())
userId String
title String @db.VarChar(255)
status String @default("active") @db.VarChar(32)
scheduledAt DateTime?
completedAt DateTime?
cardCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@index([userId])
@@index([scheduledAt])
}
model DailyLearningActivity {
id String @id @default(cuid())
userId String
activityDate DateTime @db.Date
durationSeconds Int @default(0)
sessionsCount Int @default(0)
activeRecallCount Int @default(0)
reviewCount Int @default(0)
aiAnalysisCount Int @default(0)
completedLoopCount Int @default(0)
activityLevel Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@unique([userId, activityDate])
@@index([userId])
}
model Notification {
id String @id @default(cuid())
userId String
type String @db.VarChar(32)
title String @db.VarChar(255)
content String? @db.Text
data Json?
scope String @default("user") @db.VarChar(16)
readAt DateTime?
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
@@index([userId])
@@index([readAt])
@@index([type])
}
model Feedback {
id String @id @default(cuid())
userId String?
email String? @db.VarChar(255)
category String @db.VarChar(64)
content String @db.Text
deviceInfo Json?
status String @default("open") @db.VarChar(32)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User? @relation(fields: [userId], references: [id])
@@index([userId])
@@index([status])
}
model AiUsageLog {
id String @id @default(cuid())
userId String
feature String @db.VarChar(64)
provider String @db.VarChar(32)
model String @db.VarChar(100)
tier String @db.VarChar(32)
promptKey String @db.VarChar(128)
promptVersion String @db.VarChar(32)
inputTokens Int @default(0)
outputTokens Int @default(0)
estimatedCost Float @default(0)
latencyMs Int @default(0)
success Boolean @default(true)
errorMessage String? @db.VarChar(500)
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
@@index([userId])
@@index([feature])
@@index([createdAt])
}
model ModelRoute {
id String @id @default(cuid())
tier String @db.VarChar(32)
taskType String @default("*") @db.VarChar(32)
preferredProvider String @db.VarChar(32)
preferredModel String @db.VarChar(100)
fallbackProvider String @db.VarChar(32)
fallbackModel String @db.VarChar(100)
maxRetries Int @default(2)
isActive Boolean @default(true)
createdBy String? @db.VarChar(100)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([tier, taskType])
}
model ProviderConfig {
id String @id @default(cuid())
name String @unique @db.VarChar(32)
enabled Boolean @default(true)
baseUrl String? @db.VarChar(255)
updatedBy String? @db.VarChar(100)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model FallbackEvent {
id String @id @default(cuid())
tier String @db.VarChar(32)
taskType String @db.VarChar(32)
fromProvider String @db.VarChar(32)
fromModel String @db.VarChar(100)
toProvider String @db.VarChar(32)
toModel String @db.VarChar(100)
errorMessage String? @db.VarChar(500)
createdAt DateTime @default(now())
}
model WaitlistEntry {
id String @id @default(cuid())
nickname String @db.VarChar(100)
email String @db.VarChar(255)
devices Json?
interests Json?
painpoint String? @db.Text
willingBeta Boolean @default(false)
createdAt DateTime @default(now())
@@index([email])
}
model AppChangelog {
id String @id @default(cuid())
version String @db.VarChar(50)
title String @db.VarChar(255)
content String @db.Text
platform String @default("ios") @db.VarChar(32)
publishedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// ── 知识库新增模型 ──
model KnowledgeSource {
id String @id @default(cuid())
userId String
knowledgeBaseId String
fileId String?
type String @default("file") @db.VarChar(32)
title String? @db.VarChar(255)
originalFilename String? @db.VarChar(255)
mimeType String? @db.VarChar(100)
sizeBytes BigInt @default(0)
textLength Int @default(0)
parseStatus String @default("pending") @db.VarChar(32)
indexStatus String @default("pending") @db.VarChar(32)
learningStatus String @default("pending") @db.VarChar(32)
parsedObjectKey String? @db.VarChar(500)
metadataObjectKey String? @db.VarChar(500)
originalObjectKey String? @db.VarChar(500)
version Int @default(1)
parentSourceId String?
replacedBySourceId String?
errorCode String? @db.VarChar(32)
errorMessage String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
user User @relation(fields: [userId], references: [id])
knowledgeBase KnowledgeBase @relation(fields: [knowledgeBaseId], references: [id])
file UploadedFile? @relation(fields: [fileId], references: [id])
chunks KnowledgeChunk[]
imports DocumentImport[]
references SourceReference[]
candidates ImportCandidate[]
@@index([userId])
@@index([knowledgeBaseId])
@@index([fileId])
@@index([parseStatus])
@@index([indexStatus])
}
model KnowledgeChunk {
id String @id @default(cuid())
userId String
knowledgeBaseId String
sourceId String
content String @db.LongText
chunkIndex Int
pageNumber Int?
sectionTitle String? @db.VarChar(500)
tokenCount Int @default(0)
externalVectorId String? @db.VarChar(255)
embeddingModel String? @db.VarChar(100)
embeddingStatus String @default("pending") @db.VarChar(32)
metadataJson Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
user User @relation(fields: [userId], references: [id])
knowledgeBase KnowledgeBase @relation(fields: [knowledgeBaseId], references: [id])
source KnowledgeSource @relation(fields: [sourceId], references: [id])
references SourceReference[]
@@index([userId])
@@index([sourceId])
@@index([knowledgeBaseId])
@@index([externalVectorId])
}
model SourceReference {
id String @id @default(cuid())
sourceId String
chunkId String?
artifactType String @db.VarChar(32)
artifactId String @db.VarChar(100)
pageNumber Int?
sectionTitle String? @db.VarChar(500)
excerptText String? @db.VarChar(2000)
createdAt DateTime @default(now())
source KnowledgeSource @relation(fields: [sourceId], references: [id])
chunk KnowledgeChunk? @relation(fields: [chunkId], references: [id])
@@index([artifactType, artifactId])
@@index([sourceId])
}
model ImportCandidate {
id String @id @default(cuid())
userId String
knowledgeBaseId String
sourceId String
importId String
title String @db.VarChar(255)
summary String? @db.Text
content String? @db.LongText
tagsJson Json?
recallQuestionsJson Json?
sourceTextSnippet String? @db.Text
sourceChunkIds Json?
confidence Decimal @default(0) @db.Decimal(4, 3)
difficulty String? @db.VarChar(16)
orderIndex Int @default(0)
status String @default("PENDING") @db.VarChar(16)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
knowledgeBase KnowledgeBase @relation(fields: [knowledgeBaseId], references: [id])
source KnowledgeSource @relation(fields: [sourceId], references: [id])
import DocumentImport @relation(fields: [importId], references: [id])
@@index([userId])
@@index([sourceId])
@@index([importId])
@@index([status])
}
model BackupJob {
id String @id @default(cuid())
type String @db.VarChar(16)
status String @default("RUNNING") @db.VarChar(16)
localPath String? @db.VarChar(500)
cosObjectKey String? @db.VarChar(500)
fileSizeBytes BigInt @default(0)
startedAt DateTime @default(now())
completedAt DateTime?
errorMessage String? @db.Text
createdAt DateTime @default(now())
}
model CleanupJob {
id String @id @default(cuid())
type String @db.VarChar(32)
status String @default("RUNNING") @db.VarChar(16)
target String? @db.VarChar(255)
rowsAffected Int @default(0)
startedAt DateTime @default(now())
completedAt DateTime?
errorMessage String? @db.Text
createdAt DateTime @default(now())
}
model AdminUser {
id String @id @default(cuid())
email String @unique @db.VarChar(255)
passwordHash String @db.VarChar(255)
displayName String @db.VarChar(100)
role String @default("ADMIN") @db.VarChar(32)
status String @default("ACTIVE") @db.VarChar(32)
twoFactorEnabled Boolean @default(false)
twoFactorSecret String? @db.VarChar(100)
lastLoginAt DateTime?
lastLoginIp String? @db.VarChar(45)
failedLoginCount Int @default(0)
lockedUntil DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
sessions AdminSession[]
conversations AdminConversation[]
auditLogs AdminAuditLog[]
@@index([email])
@@index([status])
}
model AdminSession {
id String @id @default(cuid())
adminUserId String
refreshTokenHash String @db.VarChar(255)
ip String? @db.VarChar(45)
userAgent String? @db.VarChar(500)
expiresAt DateTime
revokedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
adminUser AdminUser @relation(fields: [adminUserId], references: [id])
@@index([adminUserId])
@@index([refreshTokenHash])
}
model AdminAuditLog {
id String @id @default(cuid())
adminUserId String
action String @db.VarChar(64)
resourceType String? @db.VarChar(64)
resourceId String? @db.VarChar(255)
beforeJson Json?
afterJson Json?
ip String? @db.VarChar(45)
userAgent String? @db.VarChar(500)
riskLevel String? @db.VarChar(16)
reason String? @db.VarChar(500)
createdAt DateTime @default(now())
adminUser AdminUser @relation(fields: [adminUserId], references: [id])
@@index([adminUserId])
@@index([action])
@@index([createdAt])
}
model MembershipPlan {
id String @id @default(cuid())
code String @unique @db.VarChar(32)
name String @db.VarChar(100)
priceMonthly Int @default(0)
priceYearly Int @default(0)
maxKnowledgeBases Int @default(1)
maxStorageBytes BigInt @default(0)
maxFileSizeBytes BigInt @default(0)
monthlyOcrPages Int @default(0)
monthlyVisionPages Int @default(0)
monthlyChatCount Int @default(0)
monthlyAiAnalysisCount Int @default(0)
monthlyRecallCount Int @default(0)
monthlyCardGenCount Int @default(0)
isActive Boolean @default(true)
memberships UserMembership[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model AdminConversation {
id String @id @default(cuid())
adminUserId String
title String @default("新对话") @db.VarChar(200)
hermesSessionId String @unique @db.VarChar(64)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
messages AdminMessage[]
adminUser AdminUser @relation(fields: [adminUserId], references: [id])
@@index([adminUserId])
@@index([hermesSessionId])
}
model AdminMessage {
id String @id @default(cuid())
conversationId String
role String @db.VarChar(16)
content String @db.LongText
createdAt DateTime @default(now())
conversation AdminConversation @relation(fields: [conversationId], references: [id])
@@index([conversationId])
@@index([createdAt])
}
model ChatSession {
id String @id @default(cuid())
userId String
knowledgeBaseId String
title String @default("新对话") @db.VarChar(200)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
messages ChatMessage[]
@@index([userId])
@@index([knowledgeBaseId])
}
model ChatMessage {
id String @id @default(cuid())
sessionId String
role String @db.VarChar(16)
content String @db.LongText
tokens Int @default(0)
createdAt DateTime @default(now())
session ChatSession @relation(fields: [sessionId], references: [id])
citations ChatCitation[]
@@index([sessionId])
}
model ChatCitation {
id String @id @default(cuid())
messageId String
chunkId String?
sourceId String?
sourceTitle String? @db.VarChar(255)
excerptText String? @db.VarChar(2000)
pageNumber Int?
createdAt DateTime @default(now())
message ChatMessage @relation(fields: [messageId], references: [id])
@@index([messageId])
}
model AdminCostItem {
id String @id @default(cuid())
name String @db.VarChar(100)
category String @default("other") @db.VarChar(32)
amount Float
currency String @default("CNY") @db.VarChar(8)
purchaseDate DateTime
expiryDate DateTime?
billingCycle String @default("once") @db.VarChar(16)
note String? @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([category])
@@index([expiryDate])
}
model AppConfig {
id String @id @default(cuid())
key String @unique @db.VarChar(100)
value String @db.Text
description String? @db.VarChar(500)
environment String @default("production") @db.VarChar(32)
updatedBy String? @db.VarChar(100)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([key])
}
model FeatureFlag {
id String @id @default(cuid())
name String @unique @db.VarChar(100)
enabled Boolean @default(false)
description String? @db.VarChar(500)
rolloutPct Int @default(100)
whitelist String? @db.Text
updatedBy String? @db.VarChar(100)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([name])
}
model ConfigChangeLog {
id String @id @default(cuid())
entityType String @db.VarChar(32)
entityId String @db.VarChar(100)
field String @db.VarChar(100)
oldValue String? @db.Text
newValue String? @db.Text
changedBy String? @db.VarChar(100)
createdAt DateTime @default(now())
@@index([entityType, entityId])
@@index([createdAt])
}
model SecurityEvent {
id String @id @default(cuid())
userId String?
adminUserId String?
eventType String @db.VarChar(64)
severity String @default("low") @db.VarChar(16)
ip String? @db.VarChar(45)
userAgent String? @db.VarChar(500)
detail Json?
handled Boolean @default(false)
handledBy String? @db.VarChar(100)
createdAt DateTime @default(now())
@@index([userId])
@@index([eventType])
@@index([createdAt])
}
model SensitiveWord {
id String @id @default(cuid())
word String @unique @db.VarChar(100)
category String @default("general") @db.VarChar(32)
riskLevel String @default("medium") @db.VarChar(16)
enabled Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([word])
@@index([category])
}
model ContentSafetyCheck {
id String @id @default(cuid())
userId String? @db.VarChar(100)
contentType String @db.VarChar(32)
content String @db.Text
riskLevel String @db.VarChar(16)
matchedWords String? @db.Text
result String @default("pending") @db.VarChar(16)
reviewerId String? @db.VarChar(100)
reviewNote String? @db.VarChar(500)
createdAt DateTime @default(now())
reviewedAt DateTime?
@@index([userId])
@@index([result])
@@index([createdAt])
}
model ContentReport {
id String @id @default(cuid())
reporterId String
targetType String @db.VarChar(32)
targetId String @db.VarChar(100)
reason String @db.VarChar(500)
status String @default("pending") @db.VarChar(16)
handledBy String? @db.VarChar(100)
handleNote String? @db.VarChar(500)
createdAt DateTime @default(now())
handledAt DateTime?
@@index([status])
@@index([createdAt])
}
model ViolationRecord {
id String @id @default(cuid())
userId String
contentType String @db.VarChar(32)
content String @db.VarChar(1000)
riskLevel String @db.VarChar(16)
penalty String @default("none") @db.VarChar(32)
appliedBy String? @db.VarChar(100)
appliedAt DateTime?
createdAt DateTime @default(now())
@@index([userId])
@@index([createdAt])
}
model ApiMetric {
id String @id @default(cuid())
path String @db.VarChar(255)
method String @db.VarChar(10)
statusCode Int
duration Int
userId String? @db.VarChar(100)
ip String? @db.VarChar(45)
createdAt DateTime @default(now())
@@index([path])
@@index([createdAt])
}
model TaskLog {
id String @id @default(cuid())
queueName String @db.VarChar(64)
jobId String @db.VarChar(100)
status String @default("enqueued") @db.VarChar(16)
payload Json?
error String? @db.Text
attempts Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([queueName])
@@index([status])
@@index([createdAt])
}
model UserMembership {
id String @id @default(cuid())
userId String
planId String
startedAt DateTime @default(now())
expiresAt DateTime?
active Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
plan MembershipPlan @relation(fields: [planId], references: [id])
@@index([userId])
}
model UserDevice {
id String @id @default(cuid())
userId String
deviceId String @db.VarChar(255)
deviceName String? @db.VarChar(100)
osVersion String? @db.VarChar(50)
pushToken String? @db.VarChar(500)
lastSeenAt DateTime @default(now())
createdAt DateTime @default(now())
@@unique([userId, deviceId])
@@index([userId])
}
model AccountDeletionRequest {
id String @id @default(cuid())
userId String
status String @default("pending") @db.VarChar(16)
requestedAt DateTime @default(now())
coolingEndsAt DateTime
reviewedBy String? @db.VarChar(100)
reviewedAt DateTime?
completedAt DateTime?
cancelledAt DateTime?
createdAt DateTime @default(now())
@@index([userId])
@@index([status])
}
model QuotaUsage {
id String @id @default(cuid())
userId String
quotaType String @db.VarChar(32)
amount Int
resource String? @db.VarChar(255)
createdAt DateTime @default(now())
@@index([userId, quotaType])
@@index([createdAt])
}
model CostDailySummary {
id String @id @default(cuid())
date DateTime
provider String @db.VarChar(32)
model String? @db.VarChar(100)
calls Int @default(0)
tokens Int @default(0)
cost Float @default(0)
createdAt DateTime @default(now())
@@unique([date, provider, model])
}
model SecretRecord {
id String @id @default(cuid())
name String @unique @db.VarChar(100)
provider String @db.VarChar(32)
encrypted String @db.Text
maskLast4 String @db.VarChar(4)
status String @default("active") @db.VarChar(16)
expiresAt DateTime?
rotatedFrom String? @db.VarChar(100)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([provider])
}
model SecretAccessLog {
id String @id @default(cuid())
secretId String
secretName String @db.VarChar(100)
accessedBy String @db.VarChar(100)
createdAt DateTime @default(now())
@@index([secretId])
@@index([createdAt])
}
model RecentItem {
id String @id @default(cuid())
userId String
targetType String @db.VarChar(32)
targetId String @db.VarChar(255)
title String @db.VarChar(255)
metadata Json?
accessedAt DateTime @updatedAt
createdAt DateTime @default(now())
@@index([userId, accessedAt])
@@index([userId, targetType])
}
model Favorite {
id String @id @default(cuid())
userId String
targetType String @db.VarChar(32)
targetId String @db.VarChar(255)
title String? @db.VarChar(255)
metadata Json?
createdAt DateTime @default(now())
@@unique([userId, targetType, targetId])
@@index([userId])
}
model SearchHistory {
id String @id @default(cuid())
userId String
query String @db.VarChar(500)
resultsCount Int @default(0)
createdAt DateTime @default(now())
@@index([userId, createdAt])
}
model NotificationPreference {
id String @id @default(cuid())
userId String @unique
reviewReminder Boolean @default(true)
learningReminder Boolean @default(true)
streakAlert Boolean @default(true)
pushEnabled Boolean @default(true)
quietStartHour Int?
quietEndHour Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model PushToken {
id String @id @default(cuid())
userId String
token String @db.VarChar(500)
platform String @db.VarChar(32)
deviceId String? @db.VarChar(255)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, token])
@@index([userId])
}
model NotificationTemplate {
id String @id @default(cuid())
name String @db.VarChar(100)
type String @db.VarChar(32)
title String @db.VarChar(255)
content String @db.Text
channel String @default("in_app") @db.VarChar(32)
enabled Boolean @default(true)
createdBy String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([type])
}
model LearningGoal {
id String @id @default(cuid())
userId String @unique
dailyCardTarget Int @default(10)
dailyMinuteTarget Int @default(30)
weeklyCardTarget Int @default(50)
favoriteSubject String? @db.VarChar(100)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model StreakRecord {
id String @id @default(cuid())
userId String
streakType String @db.VarChar(32)
length Int @default(0)
startDate DateTime
endDate DateTime
createdAt DateTime @default(now())
@@index([userId])
@@index([streakType])
}
model LearningStats {
id String @id @default(cuid())
userId String
date DateTime
totalCards Int @default(0)
reviewedCards Int @default(0)
newCards Int @default(0)
totalMinutes Int @default(0)
avgMasteryScore Decimal? @db.Decimal(5, 2)
createdAt DateTime @default(now())
@@unique([userId, date])
@@index([userId])
}
model ServiceHealth {
id String @id @default(cuid())
serverName String @db.VarChar(100)
serviceName String @db.VarChar(64)
status String @db.VarChar(16)
message String? @db.VarChar(500)
checkedAt DateTime @default(now())
@@index([serverName])
@@index([serviceName])
@@index([checkedAt])
}
model ExportJob {
id String @id @default(cuid())
type String @db.VarChar(32)
status String @default("pending") @db.VarChar(16)
format String @default("csv") @db.VarChar(8)
filePath String? @db.VarChar(500)
fileSize Int @default(0)
startedAt DateTime?
completedAt DateTime?
errorMessage String? @db.Text
createdAt DateTime @default(now())
}
model AgentTask {
id String @id @default(cuid())
userId String?
type String @db.VarChar(32)
status String @default("pending") @db.VarChar(16)
input String? @db.Text
output String? @db.Text
sessionId String? @db.VarChar(255)
approvedBy String?
approvedAt DateTime?
giteaUrl String? @db.VarChar(500)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status])
@@index([userId])
}
model AgentArtifact {
id String @id @default(cuid())
taskId String?
type String @db.VarChar(32)
title String @db.VarChar(255)
content String? @db.Text
status String @default("draft") @db.VarChar(16)
createdBy String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([taskId])
@@index([type])
}
model DecisionRecord {
id String @id @default(cuid())
title String @db.VarChar(255)
context String? @db.Text
decision String? @db.Text
rationale String? @db.Text
status String @default("proposed") @db.VarChar(32)
createdBy String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([status])
}
model ReleaseChecklist {
id String @id @default(cuid())
version String @db.VarChar(50)
item String @db.VarChar(255)
checked Boolean @default(false)
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([version])
}
model PrivacyPolicy {
id String @id @default(cuid())
version String @db.VarChar(50)
title String @db.VarChar(255)
content String @db.Text
effectiveAt DateTime
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([version])
}
model UserAgreement {
id String @id @default(cuid())
version String @db.VarChar(50)
title String @db.VarChar(255)
content String @db.Text
effectiveAt DateTime
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([version])
}
model FilingRecord {
id String @id @default(cuid())
type String @db.VarChar(64)
title String @db.VarChar(255)
filePath String? @db.VarChar(500)
status String @default("pending") @db.VarChar(16)
notes String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model DataExportRequest {
id String @id @default(cuid())
userId String
status String @default("pending") @db.VarChar(16)
filePath String? @db.VarChar(500)
completedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([status])
}
model VendorBill {
id String @id @default(cuid())
provider String @db.VarChar(32)
billMonth String @db.VarChar(7)
amount Decimal @db.Decimal(10, 2)
currency String @default("CNY") @db.VarChar(8)
usageSummary Json?
paidAt DateTime?
notes String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([provider, billMonth])
@@index([provider])
}