## 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
448 lines
9.5 KiB
Markdown
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
|