fix(api): align frontend API client with backend endpoints
- Fix addDocumentTags to use POST /documents/:id/tags (was using PUT) - Add getDocumentsByTag(tag) -> GET /tags/:tag - Add getLibraryTree(id) -> GET /libraries/:id/tree - Add getLibraryDocuments(id) -> GET /libraries/:id/documents - Add getProjectDocuments(id) -> GET /projects/:id/documents - Add getFolder(id) -> GET /folders/:id - Add getFolderDocuments(id) -> GET /folders/:id/documents - Add getFolderTree(id) -> GET /folders/:id/tree - Add api-endpoints.md with full API documentation - Remove duplicate/unused dead code blocks
This commit is contained in:
422
api-endpoints.md
Normal file
422
api-endpoints.md
Normal file
@@ -0,0 +1,422 @@
|
|||||||
|
# SimpleNote Web - API Endpoints
|
||||||
|
|
||||||
|
**Base URL:** `/api/v1`
|
||||||
|
**Auth:** All endpoints require `Authorization: Bearer <token>` header unless noted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auth
|
||||||
|
|
||||||
|
### `POST /auth/token`
|
||||||
|
Generate a new API token (admin only).
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{ "label": "my-token-label" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `201`:**
|
||||||
|
```json
|
||||||
|
{ "token": "snk_...", "label": "my-token-label", "createdAt": "2026-03-28T..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /auth/verify`
|
||||||
|
Verify a token is valid.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "valid": true, "token": "snk_..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Projects
|
||||||
|
|
||||||
|
### `GET /projects`
|
||||||
|
List all projects.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "projects": [...] }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /projects`
|
||||||
|
Create a project.
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{ "name": "Project Name", "description": "optional" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `201`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "name": "...", "description": "...", "createdAt": "...", "updatedAt": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /projects/:id`
|
||||||
|
Get a single project.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "name": "...", "description": "...", "createdAt": "...", "updatedAt": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `404`:**
|
||||||
|
```json
|
||||||
|
{ "error": "Project not found", "code": "NOT_FOUND" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PUT /projects/:id`
|
||||||
|
Update a project.
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{ "name": "New Name", "description": "New desc" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "name": "...", "description": "...", "createdAt": "...", "updatedAt": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /projects/:id`
|
||||||
|
Delete a project.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "deleted": true, "id": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /projects/:id/tree`
|
||||||
|
Get full project tree (folder hierarchy + documents).
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"project": { "id": "...", "name": "..." },
|
||||||
|
"documents": [...],
|
||||||
|
"folders": [...],
|
||||||
|
"totalDocuments": 5
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /projects/:id/documents`
|
||||||
|
List documents directly in a project (not in sub-folders).
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "documents": [...], "total": 3 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Folders
|
||||||
|
|
||||||
|
### `GET /folders?projectId=X&parentId=Y`
|
||||||
|
List folders in a project. `projectId` is required.
|
||||||
|
|
||||||
|
**Query params:**
|
||||||
|
- `projectId` (required): Project ID
|
||||||
|
- `parentId` (optional): Parent folder ID. Omit for root folders.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "folders": [...] }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `400`:**
|
||||||
|
```json
|
||||||
|
{ "error": "projectId query parameter is required", "code": "VALIDATION_ERROR" }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /folders`
|
||||||
|
Create a folder.
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{ "name": "Folder Name", "projectId": "...", "parentId": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `201`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "name": "...", "projectId": "...", "parentId": null, "createdAt": "...", "updatedAt": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /folders/:id`
|
||||||
|
Get a single folder.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "name": "...", "projectId": "...", "parentId": null, "createdAt": "...", "updatedAt": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PUT /folders/:id`
|
||||||
|
Update a folder (rename).
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{ "name": "New Folder Name" }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "name": "New Folder Name", ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /folders/:id`
|
||||||
|
Delete a folder.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "deleted": true, "id": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /folders/:id/documents`
|
||||||
|
List documents directly in a folder.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "documents": [...], "total": 2 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /folders/:id/tree`
|
||||||
|
Get full folder tree (sub-folders + documents recursively).
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"folder": { "id": "...", "name": "..." },
|
||||||
|
"documents": [...],
|
||||||
|
"subFolders": [...],
|
||||||
|
"totalDocuments": 4
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documents
|
||||||
|
|
||||||
|
### `GET /documents`
|
||||||
|
List documents with optional filters.
|
||||||
|
|
||||||
|
**Query params:**
|
||||||
|
- `tag` - Filter by tag
|
||||||
|
- `library` - Filter by library ID
|
||||||
|
- `project` - Filter by project ID
|
||||||
|
- `folder` - Filter by folder ID
|
||||||
|
- `type` - Filter by type (e.g., `requirement`, `general`)
|
||||||
|
- `status` - Filter by status (e.g., `draft`, `approved`)
|
||||||
|
- `limit` - Max results (default 50)
|
||||||
|
- `offset` - Pagination offset (default 0)
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "documents": [...], "total": 10, "limit": 50, "offset": 0 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /documents`
|
||||||
|
Create a document.
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Doc Title",
|
||||||
|
"libraryId": "...",
|
||||||
|
"projectId": "...",
|
||||||
|
"folderId": "...",
|
||||||
|
"content": "# Markdown content",
|
||||||
|
"tags": ["tag1", "tag2"],
|
||||||
|
"type": "requirement",
|
||||||
|
"priority": "high",
|
||||||
|
"status": "draft"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `201`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "title": "...", "content": "...", "tags": [...], "type": "...", "priority": "...", "status": "...", "createdAt": "...", "updatedAt": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /documents/:id`
|
||||||
|
Get a single document.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "title": "...", "content": "...", "tags": [...], "type": "...", "priority": "...", "status": "...", "createdAt": "...", "updatedAt": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PUT /documents/:id`
|
||||||
|
Update a document.
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{ "title": "New Title", "content": "...", "tags": [...], "type": "...", "priority": "...", "status": "...", "folderId": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `200`:** Document object.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /documents/:id`
|
||||||
|
Delete a document.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "deleted": true, "id": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /documents/:id/export`
|
||||||
|
Export document as markdown.
|
||||||
|
|
||||||
|
**Response `200`:** Raw text/markdown (Content-Type: `text/markdown`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /documents/:id/tags`
|
||||||
|
Add tags to a document.
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{ "tags": ["new-tag", "another"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `200`:** Updated document object.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tags
|
||||||
|
|
||||||
|
### `GET /tags`
|
||||||
|
List all tags with counts.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "tags": [{ "name": "backend", "count": 5 }, ...], "total": 12 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /tags/:tag`
|
||||||
|
Get all documents with a specific tag.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "tag": "backend", "documents": [...], "count": 5 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Libraries
|
||||||
|
|
||||||
|
### `GET /libraries`
|
||||||
|
List root libraries.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "libraries": [...] }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /libraries`
|
||||||
|
Create a library.
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
```json
|
||||||
|
{ "name": "Library Name", "parentId": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response `201`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "name": "...", "parentId": null, "createdAt": "...", "updatedAt": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /libraries/:id`
|
||||||
|
Get library contents.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "id": "...", "name": "...", "parentId": null, ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /libraries/:id/tree`
|
||||||
|
Get full library tree.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"library": { ... },
|
||||||
|
"documents": [...],
|
||||||
|
"subLibraries": [...],
|
||||||
|
"totalDocuments": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /libraries/:id/documents`
|
||||||
|
List documents in a library.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "documents": [...], "total": 3 }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /libraries/:id`
|
||||||
|
Delete a library.
|
||||||
|
|
||||||
|
**Response `200`:**
|
||||||
|
```json
|
||||||
|
{ "deleted": true, "id": "..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Format
|
||||||
|
|
||||||
|
All errors follow this structure:
|
||||||
|
```json
|
||||||
|
{ "error": "Human-readable message", "code": "ERROR_CODE" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Common codes: `VALIDATION_ERROR`, `NOT_FOUND`, `UNAUTHORIZED`, `INTERNAL_ERROR`
|
||||||
@@ -36,7 +36,7 @@ class ApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${API_BASE}${path}`, options);
|
const response = await fetch(`${API_BASE}${path}`, options);
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const error = await response.json().catch(() => ({ message: 'Request failed' }));
|
const error = await response.json().catch(() => ({ message: 'Request failed' }));
|
||||||
throw new Error(error.message || `HTTP ${response.status}`);
|
throw new Error(error.message || `HTTP ${response.status}`);
|
||||||
@@ -50,7 +50,7 @@ class ApiClient {
|
|||||||
put(path, body) { return this.request('PUT', path, body); }
|
put(path, body) { return this.request('PUT', path, body); }
|
||||||
delete(path) { return this.request('DELETE', path); }
|
delete(path) { return this.request('DELETE', path); }
|
||||||
|
|
||||||
// Auth
|
// ===== Auth =====
|
||||||
async login(token) {
|
async login(token) {
|
||||||
try {
|
try {
|
||||||
this.setToken(token); // Set token BEFORE making request
|
this.setToken(token); // Set token BEFORE making request
|
||||||
@@ -62,7 +62,7 @@ class ApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Documents
|
// ===== Documents =====
|
||||||
getDocuments(params = {}) {
|
getDocuments(params = {}) {
|
||||||
const query = new URLSearchParams(params).toString();
|
const query = new URLSearchParams(params).toString();
|
||||||
return this.get(`/documents${query ? '?' + query : ''}`);
|
return this.get(`/documents${query ? '?' + query : ''}`);
|
||||||
@@ -90,7 +90,25 @@ class ApiClient {
|
|||||||
}).then(r => r.text());
|
}).then(r => r.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Libraries
|
addDocumentTags(documentId, tags) {
|
||||||
|
return this.post(`/documents/${documentId}/tags`, { tags });
|
||||||
|
}
|
||||||
|
|
||||||
|
moveDocumentToFolder(documentId, folderId) {
|
||||||
|
return this.put(`/documents/${documentId}`, { folderId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Tags =====
|
||||||
|
getTags() {
|
||||||
|
return this.get('/tags');
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /tags/:tag - get documents by tag
|
||||||
|
getDocumentsByTag(tag) {
|
||||||
|
return this.get(`/tags/${encodeURIComponent(tag)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Libraries =====
|
||||||
getLibraries() {
|
getLibraries() {
|
||||||
return this.get('/libraries');
|
return this.get('/libraries');
|
||||||
}
|
}
|
||||||
@@ -111,12 +129,17 @@ class ApiClient {
|
|||||||
return this.delete(`/libraries/${id}`);
|
return this.delete(`/libraries/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tags
|
// GET /libraries/:id/tree
|
||||||
getTags() {
|
getLibraryTree(id) {
|
||||||
return this.get('/tags');
|
return this.get(`/libraries/${id}/tree`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== PROJECTS =====
|
// GET /libraries/:id/documents
|
||||||
|
getLibraryDocuments(id) {
|
||||||
|
return this.get(`/libraries/${id}/documents`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Projects =====
|
||||||
getProjects() {
|
getProjects() {
|
||||||
return this.get('/projects');
|
return this.get('/projects');
|
||||||
}
|
}
|
||||||
@@ -137,17 +160,27 @@ class ApiClient {
|
|||||||
return this.delete(`/projects/${id}`);
|
return this.delete(`/projects/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /projects/:id/tree
|
||||||
getProjectTree(id) {
|
getProjectTree(id) {
|
||||||
return this.get(`/projects/${id}/tree`);
|
return this.get(`/projects/${id}/tree`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== FOLDERS =====
|
// GET /projects/:id/documents
|
||||||
|
getProjectDocuments(id) {
|
||||||
|
return this.get(`/projects/${id}/documents`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Folders =====
|
||||||
getFolders(projectId, parentId = null) {
|
getFolders(projectId, parentId = null) {
|
||||||
const params = new URLSearchParams({ projectId });
|
const params = new URLSearchParams({ projectId });
|
||||||
if (parentId) params.append('parentId', parentId);
|
if (parentId) params.append('parentId', parentId);
|
||||||
return this.get(`/folders?${params.toString()}`);
|
return this.get(`/folders?${params.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFolder(id) {
|
||||||
|
return this.get(`/folders/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
createFolder(data) {
|
createFolder(data) {
|
||||||
return this.post('/folders', data);
|
return this.post('/folders', data);
|
||||||
}
|
}
|
||||||
@@ -160,9 +193,14 @@ class ApiClient {
|
|||||||
return this.delete(`/folders/${id}`);
|
return this.delete(`/folders/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== Move document to folder =====
|
// GET /folders/:id/documents
|
||||||
moveDocumentToFolder(documentId, folderId) {
|
getFolderDocuments(id) {
|
||||||
return this.put(`/documents/${documentId}`, { folderId });
|
return this.get(`/folders/${id}/documents`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /folders/:id/tree
|
||||||
|
getFolderTree(id) {
|
||||||
|
return this.get(`/folders/${id}/tree`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user