feat: add HTML preview pane for templates via Carbone+LibreOffice
This commit is contained in:
@@ -709,6 +709,67 @@ router.post('/templates/:id/render', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/carbone/templates/:id/preview - Render template with data -> HTML preview
|
||||
router.post('/templates/:id/preview', async (req, res) => {
|
||||
const templateId = req.params.id;
|
||||
const registry = loadRegistry();
|
||||
const template = registry.find(t => t.id === templateId);
|
||||
|
||||
if (!template) return res.status(404).json({ error: 'Template not found' });
|
||||
if (!fs.existsSync(template.path)) return res.status(404).json({ error: 'Template file missing' });
|
||||
|
||||
const candidateData = req.body.candidateData || req.body;
|
||||
const referenceDate = req.body.referenceDate || req.body.generation_date;
|
||||
const carboneData = transformCandidateData(candidateData, referenceDate);
|
||||
|
||||
try {
|
||||
// Step 1: Merge data into DOCX with Carbone
|
||||
const docxBuffer = await new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error('Carbone merge timed out')), 30000);
|
||||
carbone.render(template.path, carboneData, {}, (err, result) => {
|
||||
clearTimeout(timer);
|
||||
if (err) reject(err); else resolve(result);
|
||||
});
|
||||
});
|
||||
|
||||
// Step 2: Convert to HTML with LibreOffice
|
||||
const tmpDir = path.join(__dirname, 'output');
|
||||
fs.mkdirSync(tmpDir, { recursive: true });
|
||||
const tmpId = uuidv4();
|
||||
const tmpDocx = path.join(tmpDir, `${tmpId}.docx`);
|
||||
const tmpHtml = path.join(tmpDir, `${tmpId}.html`);
|
||||
|
||||
fs.writeFileSync(tmpDocx, docxBuffer);
|
||||
|
||||
const { execFile } = require('child_process');
|
||||
const htmlPath = await new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error('LibreOffice conversion timed out')), 60000);
|
||||
execFile('soffice', [
|
||||
'--headless', '--invisible', '--nocrashreport', '--nodefault',
|
||||
'--nologo', '--nofirststartwizard', '--norestore',
|
||||
'--convert-to', 'html', '--outdir', tmpDir, tmpDocx
|
||||
], { timeout: 60000 }, (err) => {
|
||||
clearTimeout(timer);
|
||||
if (err) reject(err);
|
||||
else if (!fs.existsSync(tmpHtml)) reject(new Error('HTML was not generated'));
|
||||
else resolve(tmpHtml);
|
||||
});
|
||||
});
|
||||
|
||||
const htmlBuffer = fs.readFileSync(htmlPath);
|
||||
|
||||
// Clean up temp files
|
||||
try { fs.unlinkSync(tmpDocx); } catch {}
|
||||
try { fs.unlinkSync(tmpHtml); } catch {}
|
||||
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.send(htmlBuffer);
|
||||
} catch (err) {
|
||||
console.error('Preview error:', err.message);
|
||||
res.status(500).json({ error: 'Preview failed: ' + err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE /api/carbone/templates/:id - Delete a template
|
||||
router.delete('/templates/:id', (req, res) => {
|
||||
const templateId = req.params.id;
|
||||
|
||||
Reference in New Issue
Block a user