generator client { provider = "prisma-client-js" binaryTargets = ["native", "debian-openssl-3.0.x", "linux-musl-openssl-3.0.x"] previewFeatures = ["postgresqlExtensions"] } datasource db { provider = "postgresql" url = env("DATABASE_URL") extensions = [vector] } model User { id String @id @default(cuid()) name String? email String @unique emailVerified DateTime? password String? role String @default("USER") sessionVersion Int @default(0) image String? theme String @default("light") resetToken String? @unique resetTokenExpiry DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt cardSizeMode String @default("variable") accentColor String @default("#A47148") onboardingCompleted Boolean @default(false) onboardingStep Int @default(0) accounts Account[] agents Agent[] aiFeedback AiFeedback[] brainstormActivities BrainstormActivity[] brainstormParticipant BrainstormParticipant[] brainstormSessions BrainstormSession[] canvases Canvas[] conversations Conversation[] labels Label[] memoryEchoInsights MemoryEchoInsight[] notes Note[] noteHistories NoteHistory[] sentShares NoteShare[] @relation("SentShares") receivedShares NoteShare[] @relation("ReceivedShares") sentBrainstormShares BrainstormShare[] @relation("SentBrainstormShares") receivedBrainstormShares BrainstormShare[] @relation("ReceivedBrainstormShares") notebooks Notebook[] notifications Notification[] sessions Session[] aiSettings UserAISettings? workflows Workflow[] subscription Subscription? usageLogs UsageLog[] apiKeys UserAPIKey[] aiConsentLogs AiConsentLog[] noteClusters NoteCluster[] bridgeNotes BridgeNote[] bridgeSuggestions BridgeSuggestion[] flashcardDecks FlashcardDeck[] errorLogs ErrorLog[] } model Account { userId String type String provider String providerAccountId String refresh_token String? access_token String? expires_at Int? token_type String? scope String? id_token String? session_state String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@id([provider, providerAccountId]) } model Session { sessionToken String @unique userId String expires DateTime createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model VerificationToken { identifier String token String expires DateTime @@id([identifier, token]) } model Notebook { id String @id @default(cuid()) name String icon String? color String? order Int userId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt parentId String? trashedAt DateTime? agents Agent[] conversations Conversation[] labels Label[] notes Note[] parent Notebook? @relation("NotebookTree", fields: [parentId], references: [id], onDelete: Cascade) children Notebook[] @relation("NotebookTree") user User @relation(fields: [userId], references: [id], onDelete: Cascade) workflows Workflow[] flashcardDecks FlashcardDeck[] schema NotebookSchema? site NotebookSite? @@index([userId, order]) @@index([userId]) @@index([parentId]) @@index([trashedAt]) } model NotebookSite { id String @id @default(cuid()) notebookId String @unique slug String @unique isPublic Boolean @default(true) publishedAt DateTime @default(now()) updatedAt DateTime @updatedAt template String @default("magazine") selectedNoteIds String // JSON array de note IDs ordonnés description String? // Résumé IA du carnet notebook Notebook @relation(fields: [notebookId], references: [id], onDelete: Cascade) } model Label { id String @id @default(cuid()) name String color String @default("gray") notebookId String? userId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt type String @default("user") notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: Cascade) notes Note[] @relation("LabelToNote") @@unique([notebookId, name]) @@index([notebookId]) @@index([userId]) } model Note { id String @id @default(cuid()) title String? content String color String @default("default") isPinned Boolean @default(false) isArchived Boolean @default(false) type String @default("richtext") dismissedFromRecent Boolean @default(false) checkItems String? labels String? images String? links String? reminder DateTime? isReminderDone Boolean @default(false) reminderRecurrence String? reminderLocation String? isMarkdown Boolean @default(false) size String @default("small") sharedWith String? userId String? order Int @default(0) notebookId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt contentUpdatedAt DateTime @default(now()) autoGenerated Boolean? aiProvider String? aiConfidence Int? language String? languageConfidence Float? lastAiAnalysis DateTime? trashedAt DateTime? historyEnabled Boolean @default(false) isPublic Boolean @default(false) publicSlug String? @unique publishedAt DateTime? /// HTML rendu pour la page publique (publication IA) publishedContent String? /// magazine | brief | essay publishedTemplate String? /// Hash du contenu source au moment de la publication publishedSourceHash String? /// URL d'origine pour les clips web (Web Clipper) sourceUrl String? /// Note de démonstration insérée lors de l'onboarding isDemo Boolean @default(false) /// Illustration SVG (sanitized) for editorial feed thumbnail — optional, peut être généré par IA illustrationSvg String? tsv Unsupported("tsvector")? aiFeedback AiFeedback[] convertedBrainstormIdeas BrainstormIdea[] @relation("ConvertedBrainstormIdea") exportedBrainstormSessions BrainstormSession[] @relation("ExportedBrainstorm") sourceBrainstormSessions BrainstormSession[] @relation("SourceBrainstorm") memoryEchoAsNote1 MemoryEchoInsight[] @relation("EchoNote1") memoryEchoAsNote2 MemoryEchoInsight[] @relation("EchoNote2") notebook Notebook? @relation(fields: [notebookId], references: [id]) user User? @relation(fields: [userId], references: [id], onDelete: Cascade) noteEmbedding NoteEmbedding? historyEntries NoteHistory[] shares NoteShare[] labelRelations Label[] @relation("LabelToNote") attachments NoteAttachment[] brainstormNoteRefs BrainstormNoteRef[] outgoingLinks NoteLink[] @relation("SourceLinks") incomingLinks NoteLink[] @relation("TargetLinks") clusterMemberships ClusterMember[] bridgeNote BridgeNote? sourceLiveBlocks LiveBlockRef[] @relation("SourceLiveBlocks") targetLiveBlocks LiveBlockRef[] @relation("TargetLiveBlocks") flashcards Flashcard[] properties NoteProperty[] embeddingChunks NoteEmbeddingChunk[] @@index([isPinned]) @@index([isArchived]) @@index([trashedAt]) @@index([order]) @@index([reminder]) @@index([userId]) @@index([userId, notebookId]) } model LiveBlockRef { id String @id @default(cuid()) sourceNoteId String blockId String targetNoteId String createdAt DateTime @default(now()) sourceNote Note @relation("SourceLiveBlocks", fields: [sourceNoteId], references: [id], onDelete: Cascade) targetNote Note @relation("TargetLiveBlocks", fields: [targetNoteId], references: [id], onDelete: Cascade) @@index([sourceNoteId, blockId]) @@index([targetNoteId]) } model NoteHistory { id String @id @default(cuid()) noteId String userId String version Int reason String? title String? content String color String isPinned Boolean isArchived Boolean type String checkItems String? labels String? images String? links String? isMarkdown Boolean size String notebookId String? createdAt DateTime @default(now()) note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([noteId, version]) @@index([noteId, createdAt(sort: Desc)]) @@index([userId, noteId, createdAt(sort: Desc)]) } model NoteShare { id String @id @default(cuid()) noteId String userId String sharedBy String status String @default("pending") permission String @default("view") notifiedAt DateTime? respondedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) sharer User @relation("SentShares", fields: [sharedBy], references: [id], onDelete: Cascade) user User @relation("ReceivedShares", fields: [userId], references: [id], onDelete: Cascade) @@unique([noteId, userId]) @@index([userId]) @@index([status]) @@index([sharedBy]) } model SystemConfig { key String @id value String } model AiFeedback { id String @id @default(cuid()) noteId String userId String? feedbackType String feature String originalContent String correctedContent String? metadata String? createdAt DateTime @default(now()) note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([noteId]) @@index([userId]) @@index([feature]) @@index([createdAt]) } model MemoryEchoInsight { id String @id @default(cuid()) userId String? note1Id String note2Id String similarityScore Float insight String insightDate DateTime @default(now()) viewed Boolean @default(false) feedback String? dismissed Boolean @default(false) note1 Note @relation("EchoNote1", fields: [note1Id], references: [id], onDelete: Cascade) note2 Note @relation("EchoNote2", fields: [note2Id], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, insightDate]) @@index([userId, insightDate]) @@index([userId, dismissed]) } model UserAISettings { userId String @id titleSuggestions Boolean @default(true) semanticSearch Boolean @default(true) paragraphRefactor Boolean @default(true) memoryEcho Boolean @default(true) memoryEchoFrequency String @default("daily") aiProvider String @default("auto") preferredLanguage String @default("auto") fontSize String @default("medium") demoMode Boolean @default(false) showRecentNotes Boolean @default(true) emailNotifications Boolean @default(false) desktopNotifications Boolean @default(false) anonymousAnalytics Boolean @default(false) autoLabeling Boolean @default(true) fontFamily String @default("inter") languageDetection Boolean @default(true) noteHistory Boolean @default(false) noteHistoryMode String @default("manual") autoSave Boolean @default(true) aiProcessingConsent Boolean @default(false) svgComplexity String @default("simple") integrationTokens Json? // Stores third-party integration tokens (Readwise, Calendar, etc.) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([memoryEcho]) @@index([aiProvider]) @@index([memoryEchoFrequency]) @@index([preferredLanguage]) } model AiConsentLog { id String @id @default(cuid()) userId String consent Boolean @default(true) ipAddress String? userAgent String? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model NoteEmbedding { id String @id @default(cuid()) noteId String @unique embedding Unsupported("vector(1536)")? createdAt DateTime @default(now()) note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) @@index([noteId]) } model NoteEmbeddingChunk { id String @id @default(cuid()) noteId String fragmentId String chunkIndex Int content String charCount Int embedding Unsupported("vector(1536)")? embeddingModel String? @default("text-embedding-3-small") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) @@unique([noteId, fragmentId]) @@index([noteId]) @@index([fragmentId]) } model NoteLink { id String @id @default(cuid()) sourceNoteId String targetNoteId String contextSnippet String? createdAt DateTime @default(now()) sourceNote Note @relation("SourceLinks", fields: [sourceNoteId], references: [id], onDelete: Cascade) targetNote Note @relation("TargetLinks", fields: [targetNoteId], references: [id], onDelete: Cascade) @@unique([sourceNoteId, targetNoteId]) @@index([sourceNoteId]) @@index([targetNoteId]) } model Agent { id String @id @default(cuid()) name String description String? type String? @default("scraper") role String sourceUrls String? frequency String @default("manual") lastRun DateTime? nextRun DateTime? isEnabled Boolean @default(true) targetNotebookId String? sourceNotebookId String? tools String? @default("[]") maxSteps Int @default(10) notifyEmail Boolean @default(false) includeImages Boolean @default(false) userId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt scheduledTime String? @default("08:00") scheduledDay Int? timezone String? sourceNoteIds String? slideStyle String? slideTheme String? notebook Notebook? @relation(fields: [targetNotebookId], references: [id]) user User @relation(fields: [userId], references: [id], onDelete: Cascade) actions AgentAction[] @@index([userId]) @@index([isEnabled]) } model AgentAction { id String @id @default(cuid()) agentId String status String @default("pending") result String? log String? input String? toolLog String? tokensUsed Int? createdAt DateTime @default(now()) agent Agent @relation(fields: [agentId], references: [id], onDelete: Cascade) @@index([agentId]) } model Conversation { id String @id @default(cuid()) title String? userId String notebookId String? summary String? summaryUpTo String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt messages ChatMessage[] notebook Notebook? @relation(fields: [notebookId], references: [id]) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([notebookId]) } model ChatMessage { id String @id @default(cuid()) conversationId String role String content String createdAt DateTime @default(now()) conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade) @@index([conversationId]) } model Canvas { id String @id @default(cuid()) name String data String userId String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) } model Workflow { id String @id @default(cuid()) name String description String? graph String @default("{\"nodes\":[],\"edges\":[]}") isEnabled Boolean @default(true) userId String notebookId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt notebook Notebook? @relation(fields: [notebookId], references: [id]) user User @relation(fields: [userId], references: [id], onDelete: Cascade) runs WorkflowRun[] @@index([userId]) @@index([isEnabled]) } model WorkflowRun { id String @id @default(cuid()) workflowId String status String @default("running") log String? createdAt DateTime @default(now()) workflow Workflow @relation(fields: [workflowId], references: [id], onDelete: Cascade) @@index([workflowId]) @@index([status]) } model Notification { id String @id @default(cuid()) userId String type String title String message String? read Boolean @default(false) actionUrl String? relatedId String? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId, read]) @@index([userId, createdAt]) } model BrainstormSession { id String @id @default(cuid()) seedIdea String sourceNoteId String? contextNoteIds String? exportedNoteId String? userId String inviteToken String? @unique inviteExpiry DateTime? liveblocksRoomId String? isPublic Boolean @default(false) guestCanEdit Boolean @default(false) status String @default("active") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt ideas BrainstormIdea[] participants BrainstormParticipant[] activities BrainstormActivity[] shares BrainstormShare[] snapshots BrainstormSnapshot[] sourceNote Note? @relation("SourceBrainstorm", fields: [sourceNoteId], references: [id]) exportedNote Note? @relation("ExportedBrainstorm", fields: [exportedNoteId], references: [id]) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([userId, createdAt]) @@index([inviteToken]) @@index([isPublic]) } model BrainstormIdea { id String @id @default(cuid()) sessionId String waveNumber Int title String description String connectionToSeed String? noveltyScore Int? parentIdeaId String? convertedToNoteId String? relatedNoteIds String? status String @default("active") positionX Float? positionY Float? isStarred Boolean @default(false) createdAt DateTime @default(now()) createdBy String? createdByType String? @default("ai") convertedNote Note? @relation("ConvertedBrainstormIdea", fields: [convertedToNoteId], references: [id]) parentIdea BrainstormIdea? @relation("IdeaTree", fields: [parentIdeaId], references: [id]) children BrainstormIdea[] @relation("IdeaTree") session BrainstormSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) noteRefs BrainstormNoteRef[] @@index([sessionId]) @@index([waveNumber]) @@index([status]) @@index([parentIdeaId]) @@index([sessionId, status]) @@index([sessionId, waveNumber, createdAt]) } model BrainstormNoteRef { id String @id @default(cuid()) ideaId String noteId String? relation String explanation String verdict String @default("unresolved") createdAt DateTime @default(now()) visibility String @default("participants") idea BrainstormIdea @relation(fields: [ideaId], references: [id], onDelete: Cascade) note Note? @relation(fields: [noteId], references: [id]) @@index([ideaId]) @@index([noteId]) @@index([noteId, relation]) @@index([visibility]) } model BrainstormParticipant { id String @id @default(cuid()) sessionId String userId String role String @default("viewer") joinedAt DateTime @default(now()) lastSeenAt DateTime @default(now()) session BrainstormSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([sessionId, userId]) @@index([sessionId]) @@index([userId]) @@index([sessionId, userId, role]) } model BrainstormActivity { id String @id @default(cuid()) sessionId String userId String? action String details String? createdAt DateTime @default(now()) session BrainstormSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) user User? @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([sessionId, createdAt], map: "BrainstormActivity_sessionId_createdAt_asc_idx") @@index([sessionId, createdAt(sort: Desc)], map: "BrainstormActivity_sessionId_createdAt_desc_idx") @@index([sessionId, createdAt]) } model BrainstormShare { id String @id @default(cuid()) sessionId String userId String sharedBy String status String @default("pending") permission String @default("editor") notifiedAt DateTime? respondedAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt session BrainstormSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) sharer User @relation("SentBrainstormShares", fields: [sharedBy], references: [id], onDelete: Cascade) user User @relation("ReceivedBrainstormShares", fields: [userId], references: [id], onDelete: Cascade) @@unique([sessionId, userId]) @@index([userId]) @@index([status]) } model BrainstormSnapshot { id String @id @default(cuid()) sessionId String activityId String? step Int label String? ideaGraph String createdAt DateTime @default(now()) session BrainstormSession @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@index([sessionId, step]) @@index([sessionId, createdAt]) @@index([activityId]) } model NoteAttachment { id String @id @default(cuid()) noteId String fileName String fileType String fileSize Int filePath String mimeType String status String @default("pending") pageCount Int? error String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) chunks DocumentChunk[] @@index([noteId]) @@index([status]) } model DocumentChunk { id String @id @default(cuid()) attachmentId String content String chunkIndex Int pageNumber Int? startChar Int? endChar Int? metadata String? embedding Unsupported("vector(1536)")? createdAt DateTime @default(now()) attachment NoteAttachment @relation(fields: [attachmentId], references: [id], onDelete: Cascade) @@index([attachmentId]) @@index([attachmentId, chunkIndex]) } // ===== BYOK (Story 3.5) ===== model UserAPIKey { id String @id @default(cuid()) userId String provider String alias String @default("") encryptedKey String keyHash String model String? baseUrl String? @db.Text isActive Boolean @default(true) lastUsedAt DateTime? lastUsedFor String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, provider]) @@index([userId]) @@index([keyHash]) } // ===== SUBSCRIPTION MODELS ===== enum SubscriptionTier { BASIC PRO BUSINESS ENTERPRISE } enum SubscriptionStatus { ACTIVE PAST_DUE CANCELED TRIALING INACTIVE } model Subscription { id String @id @default(cuid()) userId String @unique tier SubscriptionTier @default(BASIC) status SubscriptionStatus @default(ACTIVE) stripeCustomerId String? @unique stripeSubscriptionId String? @unique stripePriceId String? trialEndsAt DateTime? currentPeriodStart DateTime currentPeriodEnd DateTime canceledAt DateTime? cancelAtPeriodEnd Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) } model UsageLog { id String @id @default(cuid()) userId String feature String periodStart DateTime periodEnd DateTime requestsCount Int @default(0) tokensUsed Int @default(0) syncedAt DateTime? metadata String? createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@unique([userId, feature, periodStart]) @@index([userId, periodStart]) @@index([periodStart]) } model FeatureFlag { id String @id @default(cuid()) key String @unique enabled Boolean @default(false) tiers String[] metadata String? updatedAt DateTime @updatedAt } model PlanEntitlement { id String @id @default(cuid()) tier SubscriptionTier feature String limitValue Int? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([tier, feature]) @@index([tier]) } // ===== CLUSTERING & BRIDGE NOTES ===== model NoteCluster { id String @id @default(cuid()) userId String clusterId Int // Cluster number for this user (0, 1, 2, ...) name String? // Auto-generated cluster name centroid Unsupported("vector(1536)")? // Optional: centroid embedding for cluster noteCount Int @default(0) lastCalculated DateTime @default(now()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([userId, clusterId]) @@unique([userId, clusterId]) } model ClusterMember { id String @id @default(cuid()) userId String noteId String clusterId Int // Cluster number membershipScore Float @default(0.0) // How strongly this note belongs to the cluster isCentral Boolean @default(false) // Is this a central note in the cluster? createdAt DateTime @default(now()) note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) @@index([userId, clusterId]) @@index([noteId]) @@unique([noteId, clusterId]) } model BridgeNote { id String @id @default(cuid()) userId String noteId String @unique bridgeScore Float // len(clusters_touched) / max_clusters clustersConnected String // JSON array: ["0", "2", "5"] lastCalculated DateTime @default(now()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([bridgeScore]) } model BridgeSuggestion { id String @id @default(cuid()) userId String clusterAId Int clusterBId Int clusterAName String clusterBName String suggestedTitle String suggestedContent String // @default(dbgenerated) justification String // Why this connection makes sense isDismissed Boolean @default(false) createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) @@index([userId]) @@index([userId, isDismissed]) @@index([clusterAId, clusterBId]) } model NotebookSchema { id String @id @default(cuid()) notebookId String @unique viewSettings String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt notebook Notebook @relation(fields: [notebookId], references: [id], onDelete: Cascade) properties NotebookProperty[] } model NotebookProperty { id String @id @default(cuid()) schemaId String name String type String options String? position Int schema NotebookSchema @relation(fields: [schemaId], references: [id], onDelete: Cascade) noteValues NoteProperty[] @@index([schemaId, position]) } model NoteProperty { id String @id @default(cuid()) noteId String propertyId String value String? note Note @relation(fields: [noteId], references: [id], onDelete: Cascade) property NotebookProperty @relation(fields: [propertyId], references: [id], onDelete: Cascade) @@unique([noteId, propertyId]) @@index([noteId]) @@index([propertyId]) } model FlashcardDeck { id String @id @default(cuid()) userId String notebookId String? name String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt flashcards Flashcard[] user User @relation(fields: [userId], references: [id], onDelete: Cascade) notebook Notebook? @relation(fields: [notebookId], references: [id], onDelete: SetNull) @@index([userId]) } model Flashcard { id String @id @default(cuid()) deckId String noteId String? front String back String type String @default("qa") interval Int @default(1) easinessFactor Float @default(2.5) nextReviewAt DateTime @default(now()) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deck FlashcardDeck @relation(fields: [deckId], references: [id], onDelete: Cascade) note Note? @relation(fields: [noteId], references: [id], onDelete: SetNull) reviews FlashcardReview[] @@index([deckId]) @@index([noteId]) @@index([nextReviewAt]) } model FlashcardReview { id String @id @default(cuid()) cardId String grade Int reviewedAt DateTime @default(now()) card Flashcard @relation(fields: [cardId], references: [id], onDelete: Cascade) @@index([cardId]) @@index([reviewedAt]) } model AuditLog { id String @id @default(cuid()) userId String? action String resource String? metadata Json? ip String? userAgent String? createdAt DateTime @default(now()) @@index([userId]) @@index([action]) @@index([createdAt]) } model ErrorLog { id String @id @default(cuid()) userId String? message String stack String? @db.Text url String? userAgent String? @db.Text component String? // Component name where error occurred severity String @default("error") // error, warning, info resolved Boolean @default(false) createdAt DateTime @default(now()) user User? @relation(fields: [userId], references: [id], onDelete: SetNull) @@index([userId]) @@index([severity]) @@index([createdAt]) @@index([resolved]) }