feat: global block library tree + grouping tool — 19 files, 588 tests green
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* Global Blocks Routes – CRUD for global block folders and blocks
|
||||
*/
|
||||
import type { FastifyInstance } from 'fastify';
|
||||
import type { DatabaseInterface } from '../database/DatabaseInterface.js';
|
||||
import { requireAuth } from '../auth/authMiddleware.js';
|
||||
import type { AuthService } from '../auth/AuthService.js';
|
||||
import { validateName, validateIdParam } from '../utils/validation.js';
|
||||
|
||||
export function registerGlobalBlockRoutes(fastify: FastifyInstance, db: DatabaseInterface, authService: AuthService) {
|
||||
// ─── Global Block Folders ──────────────────────────────
|
||||
|
||||
// List all folders, optionally filtered by parent
|
||||
fastify.get('/api/global-folders', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const parentId = (request.query as { parentId?: string | null }).parentId;
|
||||
if (parentId === undefined || parentId === '' || parentId === 'null') {
|
||||
return db.listGlobalFolders(parentId === 'null' ? null : undefined);
|
||||
}
|
||||
const idErr = validateIdParam(parentId, 'parentId');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
return db.listGlobalFolders(parentId);
|
||||
});
|
||||
|
||||
// Get a single folder
|
||||
fastify.get('/api/global-folders/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
const folder = db.getGlobalFolder(id);
|
||||
if (!folder) return reply.code(404).send({ error: 'Folder not found' });
|
||||
return folder;
|
||||
});
|
||||
|
||||
// Create a new folder
|
||||
fastify.post('/api/global-folders', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const body = request.body as { name?: string; parent_id?: string | null };
|
||||
const nameErr = validateName(body.name, 'name');
|
||||
if (nameErr) return reply.code(400).send({ error: nameErr });
|
||||
if (body.parent_id !== undefined && body.parent_id !== null) {
|
||||
const pidErr = validateIdParam(body.parent_id, 'parent_id');
|
||||
if (pidErr) return reply.code(400).send({ error: pidErr });
|
||||
}
|
||||
return reply.code(201).send(db.createGlobalFolder({ name: body.name, parent_id: body.parent_id ?? null }));
|
||||
});
|
||||
|
||||
// Rename / move a folder
|
||||
fastify.patch('/api/global-folders/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
const body = request.body as { name?: string; parent_id?: string | null };
|
||||
if (body.name !== undefined) {
|
||||
const nameErr = validateName(body.name, 'name');
|
||||
if (nameErr) return reply.code(400).send({ error: nameErr });
|
||||
}
|
||||
if (body.parent_id !== undefined && body.parent_id !== null) {
|
||||
const pidErr = validateIdParam(body.parent_id, 'parent_id');
|
||||
if (pidErr) return reply.code(400).send({ error: pidErr });
|
||||
// Prevent setting parent to self
|
||||
if (body.parent_id === id) return reply.code(400).send({ error: 'Cannot set parent to self' });
|
||||
}
|
||||
const updated = db.updateGlobalFolder(id, { name: body.name, parent_id: body.parent_id });
|
||||
if (!updated) return reply.code(404).send({ error: 'Folder not found' });
|
||||
return updated;
|
||||
});
|
||||
|
||||
// Delete a folder (cascades to children, blocks get folder_id = NULL)
|
||||
fastify.delete('/api/global-folders/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
const ok = db.deleteGlobalFolder(id);
|
||||
if (!ok) return reply.code(404).send({ error: 'Folder not found' });
|
||||
return reply.code(204).send();
|
||||
});
|
||||
|
||||
// ─── Global Blocks ────────────────────────────────────
|
||||
|
||||
// List all global blocks, optionally filtered by folder
|
||||
fastify.get('/api/global-blocks', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const folderId = (request.query as { folderId?: string | null }).folderId;
|
||||
if (folderId === undefined || folderId === '' || folderId === 'null') {
|
||||
return db.listGlobalBlocks(folderId === 'null' ? null : undefined);
|
||||
}
|
||||
const idErr = validateIdParam(folderId, 'folderId');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
return db.listGlobalBlocks(folderId);
|
||||
});
|
||||
|
||||
// Get a single global block
|
||||
fastify.get('/api/global-blocks/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
const block = db.getGlobalBlock(id);
|
||||
if (!block) return reply.code(404).send({ error: 'Global block not found' });
|
||||
return block;
|
||||
});
|
||||
|
||||
// Create a new global block
|
||||
fastify.post('/api/global-blocks', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const body = request.body as { name?: string; folder_id?: string | null; block_data?: string; svg_data?: string };
|
||||
const nameErr = validateName(body.name, 'name');
|
||||
if (nameErr) return reply.code(400).send({ error: nameErr });
|
||||
if (body.folder_id !== undefined && body.folder_id !== null) {
|
||||
const fidErr = validateIdParam(body.folder_id, 'folder_id');
|
||||
if (fidErr) return reply.code(400).send({ error: fidErr });
|
||||
}
|
||||
if (body.block_data !== undefined && typeof body.block_data !== 'string') {
|
||||
return reply.code(400).send({ error: 'block_data must be a JSON string' });
|
||||
}
|
||||
if (body.svg_data !== undefined && typeof body.svg_data !== 'string') {
|
||||
return reply.code(400).send({ error: 'svg_data must be a string' });
|
||||
}
|
||||
return reply.code(201).send(db.createGlobalBlock({
|
||||
name: body.name,
|
||||
folder_id: body.folder_id ?? null,
|
||||
block_data: body.block_data ?? '{}',
|
||||
svg_data: body.svg_data ?? null,
|
||||
}));
|
||||
});
|
||||
|
||||
// Update a global block (rename, move, update data)
|
||||
fastify.patch('/api/global-blocks/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
const body = request.body as { name?: string; folder_id?: string | null; block_data?: string; svg_data?: string };
|
||||
if (body.name !== undefined) {
|
||||
const nameErr = validateName(body.name, 'name');
|
||||
if (nameErr) return reply.code(400).send({ error: nameErr });
|
||||
}
|
||||
if (body.folder_id !== undefined && body.folder_id !== null) {
|
||||
const fidErr = validateIdParam(body.folder_id, 'folder_id');
|
||||
if (fidErr) return reply.code(400).send({ error: fidErr });
|
||||
}
|
||||
if (body.block_data !== undefined && typeof body.block_data !== 'string') {
|
||||
return reply.code(400).send({ error: 'block_data must be a JSON string' });
|
||||
}
|
||||
if (body.svg_data !== undefined && typeof body.svg_data !== 'string') {
|
||||
return reply.code(400).send({ error: 'svg_data must be a string' });
|
||||
}
|
||||
const updated = db.updateGlobalBlock(id, body);
|
||||
if (!updated) return reply.code(404).send({ error: 'Global block not found' });
|
||||
return updated;
|
||||
});
|
||||
|
||||
// Delete a global block
|
||||
fastify.delete('/api/global-blocks/:id', async (request, reply) => {
|
||||
if (!requireAuth(request, reply, authService)) return;
|
||||
const { id } = request.params as { id: string };
|
||||
const idErr = validateIdParam(id, 'id');
|
||||
if (idErr) return reply.code(400).send({ error: idErr });
|
||||
const ok = db.deleteGlobalBlock(id);
|
||||
if (!ok) return reply.code(404).send({ error: 'Global block not found' });
|
||||
return reply.code(204).send();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user