# Data Models - Memento Project ## Overview Memento uses SQLite as its database with Prisma ORM. The database schema is shared between the web application (keep-notes) and the MCP server. **Database:** SQLite (`prisma/dev.db`) **ORM:** Prisma 5.22.0 **Adapters:** - `@prisma/adapter-better-sqlite3` (primary) - `@prisma/adapter-libsql` (alternative for Turso) --- ## Database Schema ### User Represents a user account with authentication and profile information. ```prisma model User { id String @id @default(cuid()) name String? email String @unique emailVerified DateTime? password String? // Hashed password (bcrypt) role String @default("USER") // "USER" or "ADMIN" image String? // Profile picture URL theme String @default("light") // UI theme preference resetToken String? @unique // Password reset token resetTokenExpiry DateTime? // Reset token expiration accounts Account[] sessions Session[] notes Note[] labels Label[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } ``` **Relationships:** - One-to-many with Account (OAuth providers) - One-to-many with Session (active sessions) - One-to-many with Note (user's notes) - One-to-many with Label (user's labels) **Fields:** - `id`: CUID (Collision-resistant Unique Identifier) - `email`: Unique email address - `password`: Optional (can be OAuth-only users) - `role`: RBAC - "USER" or "ADMIN" - `theme`: UI theme preference ("light" or "dark") - `resetToken`: Password recovery token (unique) - `resetTokenExpiry`: Token validity period --- ### Account Stores OAuth provider account information (NextAuth.js). ```prisma model Account { userId String type String provider String // google, github, etc. 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]) } ``` **Purpose:** Links OAuth provider accounts to local User accounts **Relationships:** - Many-to-one with User (via `userId`) **Constraints:** - Composite unique key on `provider` + `providerAccountId` --- ### Session Stores active user sessions (NextAuth.js). ```prisma model Session { sessionToken String @unique userId String expires DateTime user User @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } ``` **Purpose:** Manages active user sessions for authentication **Relationships:** - Many-to-one with User (via `userId`) **Fields:** - `sessionToken`: Unique session identifier - `expires`: Session expiration timestamp --- ### VerificationToken Stores email verification tokens (NextAuth.js). ```prisma model VerificationToken { identifier String token String expires DateTime @@id([identifier, token]) } ``` **Purpose:** Email verification flow **Constraints:** - Composite unique key on `identifier` + `token` --- ### Label User-defined labels/tags for organizing notes. ```prisma model Label { id String @id @default(cuid()) name String color String @default("gray") userId String? // Made optional for migration user User? @relation(fields: [userId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@unique([name, userId]) // Labels unique per user @@index([userId]) } ``` **Purpose:** Categorization and organization of notes **Relationships:** - Many-to-one with User (via `userId`) **Fields:** - `name`: Label display name - `color`: Visual color (red, orange, yellow, green, teal, blue, purple, pink, gray) - `userId`: Optional (migration artifact, logic enforces ownership) **Constraints:** - Unique label name per user - Indexed on `userId` for fast lookup **Usage:** - Stored as JSON array in `Note.labels` - User can have multiple labels with the same name? No, unique constraint --- ### Note Core data model - represents a note in the system. ```prisma model Note { id String @id @default(cuid()) title String? // Optional title content String color String @default("default") isPinned Boolean @default(false) isArchived Boolean @default(false) type String @default("text") // "text" or "checklist" checkItems String? // JSON array labels String? // JSON array images String? // JSON array links String? // JSON array reminder DateTime? // Reminder timestamp isReminderDone Boolean @default(false) reminderRecurrence String? // "none", "daily", "weekly", "monthly", "custom" reminderLocation String? // Location-based reminders isMarkdown Boolean @default(false) // Markdown rendering size String @default("small") // "small", "medium", "large" embedding String? // Vector embeddings (JSON) userId String? // Owner user User? @relation(fields: [userId], references: [id], onDelete: Cascade) order Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt @@index([isPinned]) @@index([isArchived]) @@index([order]) @@index([reminder]) @@index([userId]) } ``` **Purpose:** Core note-taking entity **Relationships:** - Many-to-one with User (via `userId`) **Fields:** **Basic:** - `id`: CUID - `title`: Optional heading/title - `content`: Note content (text or markdown) - `color`: Background color (default, red, orange, yellow, green, teal, blue, purple, pink, gray) - `type`: Note type - "text" or "checklist" **State Flags:** - `isPinned`: Shows at top of list - `isArchived**: Removed from main view - `isReminderDone`: Reminder completed **Rich Features:** - `checkItems`: JSON array of checklist items (if type=checklist) ```json [ {"id": "string", "text": "string", "checked": boolean} ] ``` - `labels`: JSON array of label names ```json ["work", "ideas", "urgent"] ``` - `images`: JSON array of image URLs or base64 ```json ["data:image/png;base64,..."] ``` - `links`: JSON array of link metadata - `reminder`: ISO8601 datetime for reminder - `reminderRecurrence`: Recurrence pattern - `reminderLocation`: Location-based reminders (future) - `isMarkdown`: Enable markdown rendering - `size`: Visual size (small, medium, large) - `embedding`: Vector embeddings for semantic search (JSON) **Ordering:** - `order`: Manual sort order (drag-and-drop) **Indexes:** - `isPinned`: Fast lookup for pinned notes - `isArchived`: Filter archived notes - `order`: Sort by manual ordering - `reminder`: Reminder queries (cron jobs) - `userId`: Filter by user --- ### SystemConfig Key-value storage for system-wide configuration. ```prisma model SystemConfig { key String @id value String } ``` **Purpose:** System configuration and feature flags **Examples:** - Feature toggles - System-wide settings - Admin configuration --- ## Relationships Diagram ``` User (1) ----< (N) Account | | (1) | +----< (N) Session | | (1) | +----< (N) Note | | (1) | +----< (N) Label VerificationToken (standalone) SystemConfig (standalone) ``` --- ## JSON Field Structures ### checkItems (Note) Array of checklist items: ```json [ { "id": "unique-id", "text": "Task description", "checked": false } ] ``` ### labels (Note) Array of label names (strings): ```json [ "work", "ideas", "urgent" ] ``` ### images (Note) Array of image URLs or base64 strings: ```json [ "data:image/png;base64,iVBORw0KGgo...", "/uploads/images/note-image.png" ] ``` ### links (Note) Array of link metadata (structure TBD): ```json [ { "url": "https://example.com", "title": "Example Site", "description": "..." } ] ``` ### embedding (Note) Vector embeddings for semantic search (JSON string): ```json [0.123, -0.456, 0.789, ...] ``` --- ## Database Operations ### Common Queries **Get active user's notes:** ```typescript const notes = await prisma.note.findMany({ where: { userId: session.user.id, isArchived: false }, orderBy: [ { isPinned: 'desc' }, { order: 'asc' }, { updatedAt: 'desc' } ] }) ``` **Search notes:** ```typescript const notes = await prisma.note.findMany({ where: { userId: session.user.id, OR: [ { title: { contains: query, mode: 'insensitive' } }, { content: { contains: query, mode: 'insensitive' } } ] } }) ``` **Get user's labels:** ```typescript const labels = await prisma.label.findMany({ where: { userId: session.user.id }, orderBy: { name: 'asc' } }) ``` --- ## Data Integrity **Cascade Deletes:** - When User is deleted: delete their Accounts, Sessions, Notes, and Labels - Maintains referential integrity **Unique Constraints:** - User.email - Account.provider + Account.providerAccountId - VerificationToken.identifier + VerificationToken.token - Label.name + Label.userId (per user) **Indexes:** - All foreign keys indexed - Frequently queried fields indexed (isPinned, isArchived, order, reminder, userId) --- ## Migration Notes **Current State:** - SQLite database for local development - Better-sqlite3 adapter for embedded usage - Optional LibSQL adapter for cloud deployment (Turso) **Schema Evolution:** - `Label.userId` made optional for migration (logic enforces ownership) - JSON fields stored as strings (parsed in application layer) - Future: Consider PostgreSQL for production with proper JSONB support --- ## Database Size Considerations **SQLite Limits:** - Max database size: 281 TB (theoretical) - Max row size: 1 GB - Max string/blob size: 1 GB **Practical Considerations:** - Base64 images in Note.images could bloat database - Consider storing large files in filesystem and storing paths - Vector embeddings (Note.embedding) will grow with data **Recommendations:** - Use CDN or object storage for images in production - Implement image compression before storage - Monitor database size with large note collections