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) coverType String @default("custom") @db.VarChar(32) coverIcon String? @db.VarChar(50) coverColor String? @db.VarChar(20) visibility String @default("private") @db.VarChar(16) isPinned Boolean @default(false) ownerType String @default("user") @db.VarChar(16) isVerified Boolean @default(false) 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[] subscriptions KnowledgeBaseSubscription[] @@index([userId]) @@index([status]) @@index([visibility]) @@index([ownerType]) } model KnowledgeBaseSubscription { id String @id @default(cuid()) userId String knowledgeBaseId String createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id]) knowledgeBase KnowledgeBase @relation(fields: [knowledgeBaseId], references: [id]) @@unique([userId, knowledgeBaseId]) @@index([userId]) @@index([knowledgeBaseId]) } 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]) @@index([createdAt]) } 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]) @@index([createdAt]) } 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]) @@index([createdAt]) } 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]) @@index([createdAt]) } 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]) @@index([createdAt]) } 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]) @@index([createdAt]) } 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]) @@index([createdAt]) } 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]) @@index([createdAt]) } 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]) }