## Bug Fixes ### Note Card Actions - Fix broken size change functionality (missing state declaration) - Implement React 19 useOptimistic for instant UI feedback - Add startTransition for non-blocking updates - Ensure smooth animations without page refresh - All note actions now work: pin, archive, color, size, checklist ### Markdown LaTeX Rendering - Add remark-math and rehype-katex plugins - Support inline equations with dollar sign syntax - Support block equations with double dollar sign syntax - Import KaTeX CSS for proper styling - Equations now render correctly instead of showing raw LaTeX ## Technical Details - Replace undefined currentNote references with optimistic state - Add optimistic updates before server actions for instant feedback - Use router.refresh() in transitions for smart cache invalidation - Install remark-math, rehype-katex, and katex packages ## Testing - Build passes successfully with no TypeScript errors - Dev server hot-reloads changes correctly
460 lines
11 KiB
Markdown
460 lines
11 KiB
Markdown
# 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
|