api-server/prisma/schema.prisma

796 lines
25 KiB
Plaintext
Raw Normal View History

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[]
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 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[]
@@index([userId])
@@index([status])
}
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
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 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)
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)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
user User @relation(fields: [userId], references: [id])
logs ReviewLog[]
@@index([userId])
@@index([nextReviewAt])
@@index([focusItemId])
}
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?
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 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[]
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])
@@index([userId])
@@index([sourceId])
@@index([knowledgeBaseId])
@@index([externalVectorId])
}
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 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[]
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)
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)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}