Keep/docs/api-contracts-keep-notes.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

448 lines
9.5 KiB
Markdown

# 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