Keep/docs/data-models.md
sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## 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
2026-01-09 22:13:49 +01:00

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