/** * Templates routes module. * * Punkt 8: Uses transactions for template updates (delete+insert steps). * Punkt 6: Uses better-sqlite3 synchronous API. */ const express = require('express'); const db = require('../db'); const { auditLog } = require('../auditLog'); const { authMiddleware, adminMiddleware } = require('../middleware/auth'); const { validate, createTemplateSchema, updateTemplateSchema } = require('../middleware/validation'); const router = express.Router(); router.use(authMiddleware); // List templates router.get('/', async (req, res) => { const templates = await db.prepare('SELECT * FROM templates ORDER BY id DESC').all(); if (templates.length === 0) return res.json([]); const templateIds = templates.map(t => t.id).filter(id => Number.isInteger(id)); if (templateIds.length === 0) return res.json(templates.map(t => ({ ...t, steps: [] }))); const placeholders = templateIds.map(() => '?').join(','); const steps = await db.prepare(`SELECT * FROM template_steps WHERE template_id IN (${placeholders}) ORDER BY step_order ASC`).all(...templateIds); const result = templates.map(t => ({ ...t, steps: steps.filter(s => s.template_id === t.id) })); res.json(result); }); // Create template (admin only) - Punkt 8: Transaction router.post('/', adminMiddleware, validate(createTemplateSchema), async (req, res) => { const { name, description, is_assignable, allows_file_upload, ad_create, steps } = req.validatedBody; const assignable = is_assignable ? 1 : 0; const fileUpload = allows_file_upload ? 1 : 0; const adCreate = ad_create ? 1 : 0; // H2: Alle Statements innerhalb der Transaktion über txDb erzeugen // (fn erhält txDb als this) — sonst kein Rollback möglich. const createTemplate = db.transaction(async function () { const txDb = this; const insertTemplate = txDb.prepare('INSERT INTO templates (name, description, is_assignable, allows_file_upload, ad_create) VALUES (?, ?, ?, ?, ?)'); const insertStep = txDb.prepare('INSERT INTO template_steps (template_id, page_num, label, type, step_order, email_domain, email_source_fields, dropdown_options, ad_field, hidden, ad_prefix) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); const info = await insertTemplate.run(name, description, assignable, fileUpload, adCreate); const templateId = info.lastInsertRowid; for (const [idx, step] of steps.entries()) { await insertStep.run( templateId, step.page_num || 1, step.label, step.type, idx + 1, step.email_domain || null, step.email_source_fields || null, step.dropdown_options || null, step.ad_field || null, step.hidden ? 1 : 0, step.ad_prefix || null ); } return templateId; }); try { const templateId = await createTemplate(); auditLog(req.user?.id, 'create_template', 'template', templateId, `Created template: ${name}`, req); res.status(201).json({ id: templateId, name, description, is_assignable: !!assignable, allows_file_upload: !!fileUpload, ad_create: !!adCreate, steps }); } catch (err) { res.status(500).json({ error: 'Interner Serverfehler.' }); } }); // Update template (admin only) - Punkt 8: Transaction router.put('/:id', adminMiddleware, validate(updateTemplateSchema), async (req, res) => { const templateId = parseInt(req.params.id); const { name, description, is_assignable, allows_file_upload, ad_create, steps } = req.validatedBody; const assignable = is_assignable ? 1 : 0; const fileUpload = allows_file_upload ? 1 : 0; const adCreate = ad_create ? 1 : 0; // H2: Statements innerhalb der Transaktion über txDb erzeugen const updateTemplateTransaction = db.transaction(async function () { const txDb = this; const updateTemplate = txDb.prepare('UPDATE templates SET name = ?, description = ?, is_assignable = ?, allows_file_upload = ?, ad_create = ? WHERE id = ?'); const deleteSteps = txDb.prepare('DELETE FROM template_steps WHERE template_id = ?'); const insertStep = txDb.prepare('INSERT INTO template_steps (template_id, page_num, label, type, step_order, email_domain, email_source_fields, dropdown_options, ad_field, hidden, ad_prefix) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'); const info = await updateTemplate.run(name, description, assignable, fileUpload, adCreate, templateId); if (info.changes === 0) throw new Error('NOT_FOUND'); await deleteSteps.run(templateId); for (const [idx, step] of steps.entries()) { await insertStep.run( templateId, step.page_num || 1, step.label, step.type, idx + 1, step.email_domain || null, step.email_source_fields || null, step.dropdown_options || null, step.ad_field || null, step.hidden ? 1 : 0, step.ad_prefix || null ); } }); try { await updateTemplateTransaction(); auditLog(req.user?.id, 'update_template', 'template', templateId, `Updated template: ${name}`, req); res.json({ id: templateId, name, description, is_assignable: !!assignable, allows_file_upload: !!fileUpload, ad_create: !!adCreate, steps }); } catch (err) { if (err.message === 'NOT_FOUND') return res.status(404).json({ error: 'Vorlage nicht gefunden.' }); res.status(500).json({ error: 'Interner Serverfehler.' }); } }); // Delete template (admin only) router.delete('/:id', adminMiddleware, async (req, res) => { const templateId = parseInt(req.params.id); const info = await db.prepare('DELETE FROM templates WHERE id = ?').run(templateId); if (info.changes === 0) return res.status(404).json({ error: 'Vorlage nicht gefunden.' }); auditLog(req.user?.id, 'delete_template', 'template', templateId, null, req); res.json({ message: 'Vorlage gelöscht.' }); }); module.exports = router;