## 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
9.5 KiB
API Contracts - keep-notes (Memento Web App)
Overview
The keep-notes web application exposes REST API endpoints via Next.js App Router. All endpoints return JSON responses with a consistent format.
Base URL: /api
Authentication: NextAuth session-based (required for most endpoints)
Response Format:
{
"success": true|false,
"data": any,
"error": string // only present when success: false
}
Notes Endpoints
GET /api/notes
Get all notes with optional filtering.
Authentication: Not required (currently)
Query Parameters:
archived(boolean, optional): Include archived notes. Default:falsesearch(string, optional): Search in title and content (case-insensitive)
Response (200 OK):
{
"success": true,
"data": [
{
"id": "clxxxxxxx",
"title": "string or null",
"content": "string",
"color": "default|red|orange|yellow|green|teal|blue|purple|pink|gray",
"isPinned": boolean,
"isArchived": boolean,
"type": "text|checklist",
"checkItems": [
{
"id": "string",
"text": "string",
"checked": boolean
}
] | null,
"labels": ["string"] | null,
"images": ["string"] | null,
"reminder": "ISO8601 datetime" | null,
"isReminderDone": boolean,
"reminderRecurrence": "none|daily|weekly|monthly|custom" | null,
"reminderLocation": "string" | null,
"isMarkdown": boolean,
"size": "small|medium|large",
"embedding": "JSON string" | null,
"order": number,
"createdAt": "ISO8601 datetime",
"updatedAt": "ISO8601 datetime"
}
]
}
Error Response (500):
{
"success": false,
"error": "Failed to fetch notes"
}
POST /api/notes
Create a new note.
Authentication: Not required (currently)
Request Body:
{
"title": "string (optional)",
"content": "string (required unless type=checklist)",
"color": "string (optional, default: 'default')",
"type": "text|checklist (optional, default: 'text')",
"checkItems": [
{
"id": "string",
"text": "string",
"checked": boolean
}
] (optional),
"labels": ["string"] (optional),
"images": ["string"] (optional, base64 encoded)
}
Response (201 Created):
{
"success": true,
"data": { /* note object */ }
}
Error Responses:
400 Bad Request: Content is required500 Internal Server Error: Failed to create note
PUT /api/notes
Update an existing note.
Authentication: Not required (currently)
Request Body:
{
"id": "string (required)",
"title": "string (optional)",
"content": "string (optional)",
"color": "string (optional)",
"type": "text|checklist (optional)",
"checkItems": [...] (optional),
"labels": ["string"] (optional),
"isPinned": boolean (optional),
"isArchived": boolean (optional),
"images": ["string"] (optional)
}
Response (200 OK):
{
"success": true,
"data": { /* updated note object */ }
}
Error Responses:
400 Bad Request: Note ID is required500 Internal Server Error: Failed to update note
DELETE /api/notes?id=xxx
Delete a note by ID.
Authentication: Not required (currently)
Query Parameters:
id(string, required): Note ID
Response (200 OK):
{
"success": true,
"message": "Note deleted successfully"
}
Error Responses:
400 Bad Request: Note ID is required500 Internal Server Error: Failed to delete note
Labels Endpoints
GET /api/labels
Get all labels for the authenticated user.
Authentication: Required (NextAuth session)
Response (200 OK):
{
"success": true,
"data": [
{
"id": "clxxxxxxx",
"name": "string",
"color": "red|orange|yellow|green|teal|blue|purple|pink|gray",
"userId": "string",
"createdAt": "ISO8601 datetime",
"updatedAt": "ISO8601 datetime"
}
]
}
Error Response (401 Unauthorized):
{
"error": "Unauthorized"
}
POST /api/labels
Create a new label.
Authentication: Required (NextAuth session)
Request Body:
{
"name": "string (required)",
"color": "string (optional, random color if not provided)"
}
Response (200 OK):
{
"success": true,
"data": { /* label object */ }
}
Error Responses:
400 Bad Request: Label name is required401 Unauthorized: Not authenticated409 Conflict: Label already exists for this user500 Internal Server Error: Failed to create label
DELETE /api/labels/{id}
Delete a label by ID.
Authentication: Required (NextAuth session)
URL Parameters:
id(string): Label ID
Response (200 OK):
{
"success": true,
"message": "Label deleted successfully"
}
Error Responses:
401 Unauthorized: Not authenticated500 Internal Server Error: Failed to delete label
Authentication Endpoints
GET/POST /api/auth/[...nextauth]
NextAuth.js authentication handler.
Authentication: Not required (this is the auth endpoint)
All NextAuth operations are handled by this route:
- Sign in
- Sign out
- Session management
- OAuth callbacks
- Email verification
Implementation: Delegates to @/auth configuration
AI Endpoints
POST /api/ai/tags
Generate intelligent tags for a note using AI.
Authentication: Not specified (likely required)
Request Body: (to be determined from implementation)
Response: (to be determined from implementation)
POST /api/ai/test
Test AI integration.
Authentication: Not specified (likely required)
Request Body: (to be determined from implementation)
Response: (to be determined from implementation)
Admin Endpoints
POST /api/admin/randomize-labels
Randomize label colors (admin functionality).
Authentication: Required (admin role)
Request Body: (to be determined from implementation)
Response: (to be determined from implementation)
POST /api/admin/sync-labels
Synchronize labels across the system (admin functionality).
Authentication: Required (admin role)
Request Body: (to be determined from implementation)
Response: (to be determined from implementation)
Upload Endpoint
POST /api/upload
Upload files (e.g., images for notes).
Authentication: Not specified
Request Body: multipart/form-data
Response: (to be determined from implementation)
Cron/Reminder Endpoint
POST /api/cron/reminders
Scheduled job for handling reminders.
Authentication: Not required (cron job)
Request Body: (to be determined from implementation)
Response: (to be determined from implementation)
Debug Endpoints
POST /api/debug/search
Debug search functionality.
Authentication: Not specified (admin/debug only)
Request Body: (to be determined from implementation)
Response: (to be determined from implementation)
Data Models
Note Model (Prisma)
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("text")
checkItems String? // JSON array
labels String? // JSON array
images String? // JSON array
links String? // JSON array
reminder DateTime?
isReminderDone Boolean @default(false)
reminderRecurrence String?
reminderLocation String?
isMarkdown Boolean @default(false)
size String @default("small")
embedding String? // JSON vector
userId String?
user User? @relation(fields: [userId], references: [id])
order Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([isPinned])
@@index([isArchived])
@@index([order])
@@index([reminder])
@@index([userId])
}
Label Model (Prisma)
model Label {
id String @id @default(cuid())
name String
color String @default("gray")
userId String?
user User? @relation(fields: [userId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([name, userId])
@@index([userId])
}
User Model (Prisma)
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
password String?
role String @default("USER")
image String?
theme String @default("light")
resetToken String? @unique
resetTokenExpiry DateTime?
accounts Account[]
sessions Session[]
notes Note[]
labels Label[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Notes
- State Management: Server actions and API routes (no dedicated state management library detected)
- Database: SQLite via Prisma ORM with better-sqlite3 adapter
- Authentication: NextAuth.js v5 with Prisma adapter
- Error Handling: All endpoints return consistent error format
- JSON Parsing: Arrays (checkItems, labels, images) stored as JSON strings in DB, parsed in API layer
- Ordering: Notes sorted by
isPinned DESC, thenorder ASC, thenupdatedAt DESC - Search: Case-insensitive search across title and content fields