Les comptes déjà créés ne sont pas inscrits. Le choix est facultatif à l’inscription et dans les paramètres. Un lien public demande confirmation avant d’arrêter les actualités, sans toucher aux messages de compte.
1181 lines
39 KiB
Plaintext
1181 lines
39 KiB
Plaintext
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[]
|
|
agentSuggestions AgentSuggestion[]
|
|
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[]
|
|
marketingPreference MarketingPreference?
|
|
emailSuppressions EmailSuppression[]
|
|
noteClusters NoteCluster[]
|
|
bridgeNotes BridgeNote[]
|
|
bridgeSuggestions BridgeSuggestion[]
|
|
flashcardDecks FlashcardDeck[]
|
|
errorLogs ErrorLog[]
|
|
gmailScanHistory GmailScanHistory[]
|
|
aiCreditAccount AiCreditAccount?
|
|
aiCreditLedger AiCreditLedger[]
|
|
}
|
|
|
|
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])
|
|
@@index([contentUpdatedAt(sort: Desc)])
|
|
@@index([isPublic])
|
|
}
|
|
|
|
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")
|
|
dashboardLayout Json?
|
|
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 AgentSuggestion {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
topic String
|
|
reason String
|
|
suggestedRole String @db.Text
|
|
suggestedType String @default("researcher")
|
|
suggestedFrequency String @default("weekly")
|
|
relatedNoteIds String // JSON string[]
|
|
clusterId Int?
|
|
status String @default("pending") // pending | accepted | dismissed
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, status])
|
|
@@unique([userId, clusterId])
|
|
}
|
|
|
|
model GmailScanHistory {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
messageId String
|
|
category String // flight | package | subscription
|
|
data Json?
|
|
noteId String?
|
|
scannedAt DateTime @default(now())
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([userId, messageId])
|
|
@@index([userId, scannedAt])
|
|
}
|
|
|
|
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])
|
|
}
|
|
|
|
// ===== CRÉDITS IA GLOBAUX (modèle A) =====
|
|
|
|
/// Solde de crédits par utilisateur pour la période mensuelle en cours.
|
|
model AiCreditAccount {
|
|
id String @id @default(cuid())
|
|
userId String @unique
|
|
/// Clé de période YYYY-MM
|
|
periodKey String
|
|
/// Allocation abonnement du mois (ex. 300 pour PRO)
|
|
subscriptionGranted Int @default(0)
|
|
/// Crédits d'abonnement déjà consommés ce mois
|
|
subscriptionSpent Int @default(0)
|
|
/// Crédits de packs achetés encore disponibles (reportables)
|
|
purchasedBalance Int @default(0)
|
|
updatedAt DateTime @updatedAt
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([periodKey])
|
|
}
|
|
|
|
/// Journal immuable des mouvements de crédits (transparence d'usage).
|
|
model AiCreditLedger {
|
|
id String @id @default(cuid())
|
|
userId String
|
|
/// GRANT_SUBSCRIPTION | PURCHASE_PACK | CONSUME | RELEASE | ADMIN_ADJUST | EXPIRE_SUBSCRIPTION | ADMIN_RESET
|
|
kind String
|
|
/// Positif = crédit, négatif = débit
|
|
amount Int
|
|
/// Feature technique pour le tableau de répartition (ex. slide_generate)
|
|
feature String?
|
|
balanceAfter Int?
|
|
metadata String?
|
|
createdAt DateTime @default(now())
|
|
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([userId, createdAt])
|
|
@@index([userId, feature, createdAt])
|
|
@@index([userId, kind, createdAt])
|
|
}
|
|
|
|
// ===== 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])
|
|
}
|
|
|
|
/// Explicit marketing announcements opt-in. Missing row = not opted in.
|
|
model MarketingPreference {
|
|
id String @id @default(cuid())
|
|
userId String @unique
|
|
emailNormalized String
|
|
optedIn Boolean @default(false)
|
|
preferredLanguage String?
|
|
consentVersion String?
|
|
consentText String? @db.Text
|
|
source String
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
events MarketingPreferenceEvent[]
|
|
|
|
@@index([emailNormalized])
|
|
@@index([optedIn])
|
|
}
|
|
|
|
model MarketingPreferenceEvent {
|
|
id String @id @default(cuid())
|
|
preferenceId String
|
|
userId String?
|
|
emailNormalized String
|
|
optedIn Boolean
|
|
source String
|
|
consentVersion String?
|
|
consentText String? @db.Text
|
|
ip String?
|
|
userAgent String?
|
|
createdAt DateTime @default(now())
|
|
preference MarketingPreference @relation(fields: [preferenceId], references: [id], onDelete: Cascade)
|
|
|
|
@@index([preferenceId])
|
|
@@index([userId, createdAt])
|
|
@@index([emailNormalized, createdAt])
|
|
}
|
|
|
|
/// Blocks campaign mail. Kept after account deletion (userId set null).
|
|
model EmailSuppression {
|
|
id String @id @default(cuid())
|
|
emailNormalized String @unique
|
|
reason String
|
|
source String
|
|
userId String?
|
|
createdAt DateTime @default(now())
|
|
user User? @relation(fields: [userId], references: [id], onDelete: SetNull)
|
|
|
|
@@index([reason])
|
|
@@index([userId])
|
|
}
|