feat: implement Projects and Folders API (v2 architecture)

- Add projectService.js with full CRUD + tree operations
- Add folderService.js with hierarchical folder support
- Add projects.js routes: GET/POST/PUT/DELETE /projects, /projects/:id/tree, /projects/:id/documents
- Add folders.js routes: GET/POST/PUT/DELETE /folders, /folders/:id/tree, /folders/:id/documents
- Update documentService.js to support projectId/folderId with backwards compat (libraryId)
- Update routes/index.js to mount new routers
- Maintain backwards compatibility with legacy libraryId
- All endpoints tested and working
This commit is contained in:
Hiro
2026-03-28 13:07:09 +00:00
parent 9496fc8e36
commit b81e670ce4
7 changed files with 1252 additions and 44 deletions

138
src/routes/folders.js Normal file
View File

@@ -0,0 +1,138 @@
/**
* SimpleNote Web - Folders Routes
* CRUD + tree for folders
*/
import { Router } from 'express';
import { authMiddleware } from '../middleware/auth.js';
import { getFolderService } from '../services/folderService.js';
import { NotFoundError, ValidationError } from '../utils/errors.js';
const router = Router();
router.use(authMiddleware);
// GET /folders?projectId=X&parentId=Y - List folders
router.get('/', async (req, res) => {
try {
const { projectId, parentId } = req.query;
if (!projectId) {
throw new ValidationError('projectId query parameter is required');
}
const folderService = getFolderService();
const folders = await folderService.getFolders(projectId, parentId || null);
res.json({ folders });
} catch (err) {
if (err instanceof ValidationError) {
return res.status(400).json({ error: err.message, code: err.code });
}
if (err instanceof NotFoundError) {
return res.status(404).json({ error: err.message, code: err.code });
}
console.error('Error listing folders:', err);
res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' });
}
});
// POST /folders - Create folder
router.post('/', async (req, res) => {
try {
const { name, projectId, parentId } = req.body;
if (!name) {
throw new ValidationError('name is required');
}
if (!projectId) {
throw new ValidationError('projectId is required');
}
const folderService = getFolderService();
const folder = await folderService.createFolder({ name, projectId, parentId: parentId || null });
res.status(201).json(folder);
} catch (err) {
if (err instanceof ValidationError || err instanceof NotFoundError) {
const status = err instanceof ValidationError ? 400 : 404;
return res.status(status).json({ error: err.message, code: err.code });
}
console.error('Error creating folder:', err);
res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' });
}
});
// GET /folders/:id - Get folder contents
router.get('/:id', async (req, res) => {
try {
const folderService = getFolderService();
const folder = await folderService.getFolder(req.params.id);
res.json(folder);
} catch (err) {
if (err instanceof NotFoundError) {
return res.status(404).json({ error: err.message, code: err.code });
}
console.error('Error getting folder:', err);
res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' });
}
});
// PUT /folders/:id - Update folder
router.put('/:id', async (req, res) => {
try {
const { name } = req.body;
const folderService = getFolderService();
const folder = await folderService.updateFolder(req.params.id, { name });
res.json(folder);
} catch (err) {
if (err instanceof NotFoundError) {
return res.status(404).json({ error: err.message, code: err.code });
}
if (err instanceof ValidationError) {
return res.status(400).json({ error: err.message, code: err.code });
}
console.error('Error updating folder:', err);
res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' });
}
});
// DELETE /folders/:id - Delete folder
router.delete('/:id', async (req, res) => {
try {
const folderService = getFolderService();
const result = await folderService.deleteFolder(req.params.id);
res.json(result);
} catch (err) {
if (err instanceof NotFoundError) {
return res.status(404).json({ error: err.message, code: err.code });
}
console.error('Error deleting folder:', err);
res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' });
}
});
// GET /folders/:id/documents - List documents in folder
router.get('/:id/documents', async (req, res) => {
try {
const folderService = getFolderService();
const result = await folderService.getFolderDocuments(req.params.id);
res.json(result);
} catch (err) {
if (err instanceof NotFoundError) {
return res.status(404).json({ error: err.message, code: err.code });
}
console.error('Error listing folder documents:', err);
res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' });
}
});
// GET /folders/:id/tree - Get full folder tree
router.get('/:id/tree', async (req, res) => {
try {
const folderService = getFolderService();
const tree = await folderService.getFolderTree(req.params.id);
res.json(tree);
} catch (err) {
if (err instanceof NotFoundError) {
return res.status(404).json({ error: err.message, code: err.code });
}
console.error('Error getting folder tree:', err);
res.status(500).json({ error: 'Internal server error', code: 'INTERNAL_ERROR' });
}
});
export default router;