Files
Momento/memento-note/prisma/schema.prisma
Antigravity e2672cd2c2
Some checks failed
CI / Lint, Test & Build (push) Failing after 1m19s
CI / Deploy production (on server) (push) Has been skipped
feat(notes): liens internes, onglet Réseau, living blocks et consentement IA
Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 14:27:29 +00:00

855 lines
28 KiB
Plaintext

generator client {
provider = "prisma-client-js"
binaryTargets = ["debian-openssl-3.0.x", "native"]
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")
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[]
}
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[]
@@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)
/// 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")
@@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)
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 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?
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?
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?
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
}
// ===== 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])
}