1. replaceAll (Find & Replace) — une seule transaction ProseMirror au lieu d'un forEach cassé. Tous les matchs sont maintenant remplacés. 2. Link Preview unwrap — deleteNode() au lieu de clearer les attrs qui laissaient un nœud fantôme invisible dans le document. 3. Conversion Markdown → richtext — breaks: true dans marked.parse() Les simple newlines sont maintenant convertis en <br>. + préserve les blocs custom (toggle, callout, math, columns, outline, link-preview) en commentaires HTML lors de l'export MD. 4. emitNoteChange exercices — shape corrigée (type:'created' attend un objet Note, pas noteId/notebookId séparés). 5. Raccourcis clavier sans conflit : Cmd+Shift+C → Cmd+Alt+C (callout, avant: copier) Cmd+Shift+O → Cmd+Alt+O (outline, avant: historique/signets) Cmd+Shift+L → Cmd+Alt+L (colonnes, avant: lock screen macOS)
1024 lines
33 KiB
Plaintext
1024 lines
33 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[]
|
|
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?
|
|
|
|
@@index([userId, order])
|
|
@@index([userId])
|
|
@@index([parentId])
|
|
@@index([trashedAt])
|
|
}
|
|
|
|
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?
|
|
/// 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])
|
|
}
|