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
This commit is contained in:
447
docs/api-contracts-keep-notes.md
Normal file
447
docs/api-contracts-keep-notes.md
Normal file
@@ -0,0 +1,447 @@
|
||||
# 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:**
|
||||
```json
|
||||
{
|
||||
"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: `false`
|
||||
- `search` (string, optional): Search in title and content (case-insensitive)
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"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):**
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": "Failed to fetch notes"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /api/notes
|
||||
Create a new note.
|
||||
|
||||
**Authentication:** Not required (currently)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"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):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { /* note object */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
- `400 Bad Request`: Content is required
|
||||
- `500 Internal Server Error`: Failed to create note
|
||||
|
||||
---
|
||||
|
||||
### PUT /api/notes
|
||||
Update an existing note.
|
||||
|
||||
**Authentication:** Not required (currently)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"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):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { /* updated note object */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
- `400 Bad Request`: Note ID is required
|
||||
- `500 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):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Note deleted successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
- `400 Bad Request`: Note ID is required
|
||||
- `500 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):**
|
||||
```json
|
||||
{
|
||||
"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):**
|
||||
```json
|
||||
{
|
||||
"error": "Unauthorized"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /api/labels
|
||||
Create a new label.
|
||||
|
||||
**Authentication:** Required (NextAuth session)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "string (required)",
|
||||
"color": "string (optional, random color if not provided)"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (200 OK):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { /* label object */ }
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
- `400 Bad Request`: Label name is required
|
||||
- `401 Unauthorized`: Not authenticated
|
||||
- `409 Conflict`: Label already exists for this user
|
||||
- `500 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):**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Label deleted successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Error Responses:**
|
||||
- `401 Unauthorized`: Not authenticated
|
||||
- `500 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)
|
||||
```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)
|
||||
```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)
|
||||
```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`, then `order ASC`, then `updatedAt DESC`
|
||||
- **Search**: Case-insensitive search across title and content fields
|
||||
Reference in New Issue
Block a user