2026-07-04 16:43:55 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Global Blocks Routes – CRUD for global block folders and blocks
|
|
|
|
|
|
*/
|
|
|
|
|
|
import type { FastifyInstance } from 'fastify';
|
|
|
|
|
|
import type { DatabaseInterface } from '../database/DatabaseInterface.js';
|
2026-08-29 03:01:55 +02:00
|
|
|
|
import { requireAuth, requireLibraryAdmin } from '../auth/authMiddleware.js';
|
2026-07-04 16:43:55 +02:00
|
|
|
|
import type { AuthService } from '../auth/AuthService.js';
|
|
|
|
|
|
import { validateName, validateIdParam } from '../utils/validation.js';
|
2026-08-29 02:25:10 +02:00
|
|
|
|
import JSZip from 'jszip';
|
2026-07-04 16:43:55 +02:00
|
|
|
|
|
|
|
|
|
|
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) => {
|
2026-08-29 03:01:55 +02:00
|
|
|
|
if (!requireLibraryAdmin(request, reply, authService)) return;
|
2026-07-04 16:43:55 +02:00
|
|
|
|
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) => {
|
2026-08-29 03:01:55 +02:00
|
|
|
|
if (!requireLibraryAdmin(request, reply, authService)) return;
|
2026-07-04 16:43:55 +02:00
|
|
|
|
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) => {
|
2026-08-29 03:01:55 +02:00
|
|
|
|
if (!requireLibraryAdmin(request, reply, authService)) return;
|
2026-07-04 16:43:55 +02:00
|
|
|
|
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) => {
|
2026-08-29 03:01:55 +02:00
|
|
|
|
if (!requireLibraryAdmin(request, reply, authService)) return;
|
2026-07-04 16:43:55 +02:00
|
|
|
|
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) => {
|
2026-08-29 03:01:55 +02:00
|
|
|
|
if (!requireLibraryAdmin(request, reply, authService)) return;
|
2026-07-04 16:43:55 +02:00
|
|
|
|
const { id } = request.params as { id: string };
|
|
|
|
|
|
const idErr = validateIdParam(id, 'id');
|
|
|
|
|
|
if (idErr) return reply.code(400).send({ error: idErr });
|
2026-08-28 13:27:50 +02:00
|
|
|
|
const body = request.body as { name?: string; folder_id?: string | null; block_data?: string; svg_data?: string; is_favorite?: boolean };
|
2026-07-04 16:43:55 +02:00
|
|
|
|
if (body.name !== undefined) {
|
|
|
|
|
|
const nameErr = validateName(body.name, 'name');
|
|
|
|
|
|
if (nameErr) return reply.code(400).send({ error: nameErr });
|
|
|
|
|
|
}
|
2026-08-28 13:27:50 +02:00
|
|
|
|
if (body.is_favorite !== undefined && typeof body.is_favorite !== 'boolean') {
|
|
|
|
|
|
return reply.code(400).send({ error: 'is_favorite must be a boolean' });
|
|
|
|
|
|
}
|
2026-07-04 16:43:55 +02:00
|
|
|
|
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) => {
|
2026-08-29 03:01:55 +02:00
|
|
|
|
if (!requireLibraryAdmin(request, reply, authService)) return;
|
2026-07-04 16:43:55 +02:00
|
|
|
|
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();
|
|
|
|
|
|
});
|
2026-08-28 12:19:44 +02:00
|
|
|
|
|
|
|
|
|
|
// ─── Task F1: Library-Paket Export/Import (.wcadlib) ──────
|
|
|
|
|
|
|
|
|
|
|
|
// Export: komplette Library als JSON-Paket (Ordner + Blöcke)
|
|
|
|
|
|
fastify.get('/api/global-blocks/export', async (request, reply) => {
|
|
|
|
|
|
if (!requireAuth(request, reply, authService)) return;
|
|
|
|
|
|
const folders = db.listGlobalFolders();
|
|
|
|
|
|
const blocks = db.listGlobalBlocks();
|
|
|
|
|
|
return {
|
|
|
|
|
|
format: 'wcadlib',
|
|
|
|
|
|
version: 1,
|
|
|
|
|
|
exported_at: new Date().toISOString(),
|
|
|
|
|
|
folders: folders.map((f) => ({ id: f.id, name: f.name, parent_id: f.parent_id })),
|
|
|
|
|
|
blocks: blocks.map((b) => ({
|
|
|
|
|
|
name: b.name,
|
|
|
|
|
|
folder_id: b.folder_id,
|
|
|
|
|
|
block_data: b.block_data,
|
|
|
|
|
|
svg_data: b.svg_data,
|
|
|
|
|
|
})),
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Import: .wcadlib-Paket einspielen (IDs neu, Ordner-Hierarchie über Mapping erhalten)
|
|
|
|
|
|
fastify.post('/api/global-blocks/import', async (request, reply) => {
|
2026-08-29 03:01:55 +02:00
|
|
|
|
if (!requireLibraryAdmin(request, reply, authService)) return;
|
2026-08-28 12:19:44 +02:00
|
|
|
|
const pkg = request.body as {
|
|
|
|
|
|
format?: string;
|
|
|
|
|
|
folders?: Array<{ id: string; name: string; parent_id: string | null }>;
|
|
|
|
|
|
blocks?: Array<{ name: string; folder_id: string | null; block_data: string; svg_data: string | null }>;
|
|
|
|
|
|
};
|
|
|
|
|
|
if (!pkg || pkg.format !== 'wcadlib' || !Array.isArray(pkg.blocks)) {
|
|
|
|
|
|
return reply.code(400).send({ error: 'Invalid wcadlib package (format != wcadlib or blocks missing)' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ordner mit ID-Mapping importieren:
|
|
|
|
|
|
const folderIdMap = new Map<string, string>();
|
|
|
|
|
|
const sortedFolders = [...(pkg.folders ?? [])].sort((a, b) => {
|
|
|
|
|
|
if (a.parent_id === b.parent_id) return 0;
|
|
|
|
|
|
if (a.parent_id === null) return -1;
|
|
|
|
|
|
if (b.parent_id === null) return 1;
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
});
|
|
|
|
|
|
for (const f of sortedFolders) {
|
|
|
|
|
|
const newParent = f.parent_id ? folderIdMap.get(f.parent_id) ?? null : null;
|
|
|
|
|
|
const created = db.createGlobalFolder({ name: f.name, parent_id: newParent });
|
|
|
|
|
|
folderIdMap.set(f.id, created.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Blöcke importieren:
|
|
|
|
|
|
let imported = 0;
|
|
|
|
|
|
for (const b of pkg.blocks) {
|
|
|
|
|
|
const newFolderId = b.folder_id ? folderIdMap.get(b.folder_id) ?? null : null;
|
|
|
|
|
|
db.createGlobalBlock({
|
|
|
|
|
|
name: b.name,
|
|
|
|
|
|
folder_id: newFolderId,
|
|
|
|
|
|
block_data: b.block_data,
|
|
|
|
|
|
svg_data: b.svg_data,
|
|
|
|
|
|
});
|
|
|
|
|
|
imported++;
|
|
|
|
|
|
}
|
|
|
|
|
|
return reply.code(201).send({ imported_blocks: imported, imported_folders: folderIdMap.size });
|
|
|
|
|
|
});
|
2026-08-29 02:25:10 +02:00
|
|
|
|
|
|
|
|
|
|
// ─── Task F7: .wcadlib als echtes ZIP-Paket ──────────────
|
|
|
|
|
|
|
|
|
|
|
|
// ZIP-Export: manifest.json + blocks.json + thumbs/ (SVG je Block)
|
|
|
|
|
|
fastify.get('/api/global-blocks/export-zip', async (request, reply) => {
|
|
|
|
|
|
if (!requireAuth(request, reply, authService)) return;
|
|
|
|
|
|
const folders = db.listGlobalFolders();
|
|
|
|
|
|
const blocks = db.listGlobalBlocks();
|
|
|
|
|
|
|
|
|
|
|
|
const zip = new JSZip();
|
|
|
|
|
|
zip.file('manifest.json', JSON.stringify({
|
|
|
|
|
|
format: 'wcadlib-zip',
|
|
|
|
|
|
version: 1,
|
|
|
|
|
|
exported_at: new Date().toISOString(),
|
|
|
|
|
|
block_count: blocks.length,
|
|
|
|
|
|
folder_count: folders.length,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
const blockEntries = [] as Array<{
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
folder_id: string | null;
|
|
|
|
|
|
block_data: string;
|
|
|
|
|
|
thumbnail: string | null;
|
|
|
|
|
|
}>;
|
|
|
|
|
|
for (const b of blocks) {
|
|
|
|
|
|
let thumbnail: string | null = null;
|
|
|
|
|
|
if (b.svg_data) {
|
|
|
|
|
|
thumbnail = `thumbs/${b.id}.svg`;
|
|
|
|
|
|
zip.file(thumbnail, b.svg_data);
|
|
|
|
|
|
}
|
|
|
|
|
|
blockEntries.push({
|
|
|
|
|
|
name: b.name,
|
|
|
|
|
|
folder_id: b.folder_id,
|
|
|
|
|
|
block_data: b.block_data,
|
|
|
|
|
|
thumbnail,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
zip.file('blocks.json', JSON.stringify({
|
|
|
|
|
|
folders: folders.map((f) => ({ id: f.id, name: f.name, parent_id: f.parent_id })),
|
|
|
|
|
|
blocks: blockEntries,
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
const buffer = await zip.generateAsync({ type: 'nodebuffer' });
|
|
|
|
|
|
reply.header('content-type', 'application/zip');
|
|
|
|
|
|
reply.header('content-disposition', 'attachment; filename="library.wcadlib.zip"');
|
|
|
|
|
|
return reply.send(buffer);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// ZIP-Import: .wcadlib-ZIP einspielen (Ordner-Mapping, Blöcke, Thumbnails)
|
|
|
|
|
|
fastify.post('/api/global-blocks/import-zip', async (request, reply) => {
|
2026-08-29 03:01:55 +02:00
|
|
|
|
if (!requireLibraryAdmin(request, reply, authService)) return;
|
2026-08-29 02:25:10 +02:00
|
|
|
|
const body = request.body;
|
|
|
|
|
|
if (!Buffer.isBuffer(body) || body.length === 0) {
|
|
|
|
|
|
return reply.code(400).send({ error: 'ZIP body fehlt oder leer' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let zip: JSZip;
|
|
|
|
|
|
try {
|
|
|
|
|
|
zip = await JSZip.loadAsync(body);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return reply.code(400).send({ error: 'Kein valides ZIP-Format' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const blocksFile = zip.file('blocks.json');
|
|
|
|
|
|
if (!blocksFile) {
|
|
|
|
|
|
return reply.code(400).send({ error: 'blocks.json fehlt im ZIP' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let pkg: { folders?: Array<{ id: string; name: string; parent_id: string | null }>; blocks?: Array<{ name: string; folder_id: string | null; block_data: string; thumbnail?: string | null }> };
|
|
|
|
|
|
try {
|
|
|
|
|
|
pkg = JSON.parse(await blocksFile.async('string'));
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return reply.code(400).send({ error: 'blocks.json ist kein valides JSON' });
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!Array.isArray(pkg.blocks)) {
|
|
|
|
|
|
return reply.code(400).send({ error: 'blocks.json enthaelt kein blocks-Array' });
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Optional: manifest pruefen (falls vorhanden)
|
|
|
|
|
|
const manifestFile = zip.file('manifest.json');
|
|
|
|
|
|
if (manifestFile) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const manifest = JSON.parse(await manifestFile.async('string'));
|
|
|
|
|
|
if (manifest.format && manifest.format !== 'wcadlib-zip') {
|
|
|
|
|
|
return reply.code(400).send({ error: `Unbekanntes Paket-Format: ${manifest.format}` });
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
return reply.code(400).send({ error: 'manifest.json ist kein valides JSON' });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Ordner mit ID-Mapping importieren (Eltern zuerst):
|
|
|
|
|
|
const folderIdMap = new Map<string, string>();
|
|
|
|
|
|
const sortedFolders = [...(pkg.folders ?? [])].sort((a, b) => {
|
|
|
|
|
|
if (a.parent_id === null) return -1;
|
|
|
|
|
|
if (b.parent_id === null) return 1;
|
|
|
|
|
|
if (a.parent_id === b.parent_id) return 0;
|
|
|
|
|
|
const aRes = folderIdMap.has(a.parent_id!) ? -1 : 0;
|
|
|
|
|
|
return aRes;
|
|
|
|
|
|
});
|
|
|
|
|
|
for (const f of sortedFolders) {
|
|
|
|
|
|
const newParent = f.parent_id ? folderIdMap.get(f.parent_id) ?? null : null;
|
|
|
|
|
|
const created = db.createGlobalFolder({ name: f.name, parent_id: newParent });
|
|
|
|
|
|
folderIdMap.set(f.id, created.id);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Blöcke importieren; Thumbnail aus thumbs/ lesen wenn referenziert:
|
|
|
|
|
|
let imported = 0;
|
|
|
|
|
|
for (const b of pkg.blocks) {
|
|
|
|
|
|
let svgData: string | null = null;
|
|
|
|
|
|
if (b.thumbnail) {
|
|
|
|
|
|
const thumbFile = zip.file(b.thumbnail);
|
|
|
|
|
|
if (thumbFile) {
|
|
|
|
|
|
try { svgData = await thumbFile.async('string'); } catch { svgData = null; }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
const newFolderId = b.folder_id ? folderIdMap.get(b.folder_id) ?? null : null;
|
|
|
|
|
|
db.createGlobalBlock({
|
|
|
|
|
|
name: b.name,
|
|
|
|
|
|
folder_id: newFolderId,
|
|
|
|
|
|
block_data: b.block_data,
|
|
|
|
|
|
svg_data: svgData,
|
|
|
|
|
|
});
|
|
|
|
|
|
imported++;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return reply.code(201).send({ imported_blocks: imported, imported_folders: folderIdMap.size });
|
|
|
|
|
|
});
|
2026-07-04 16:43:55 +02:00
|
|
|
|
}
|