/** * TASKMATE - Template Routes * ========================== * CRUD für Aufgaben-Vorlagen */ const express = require('express'); const router = express.Router(); const { getDb } = require('../database'); const logger = require('../utils/logger'); const { validators } = require('../middleware/validation'); /** * GET /api/templates/:projectId * Alle Vorlagen eines Projekts */ router.get('/:projectId', (req, res) => { try { const db = getDb(); const templates = db.prepare(` SELECT * FROM task_templates WHERE project_id = ? ORDER BY name `).all(req.params.projectId); res.json(templates.map(t => ({ id: t.id, projectId: t.project_id, name: t.name, titleTemplate: t.title_template, description: t.description, priority: t.priority, labels: t.labels ? JSON.parse(t.labels) : [], subtasks: t.subtasks ? JSON.parse(t.subtasks) : [], timeEstimateMin: t.time_estimate_min }))); } catch (error) { logger.error('Fehler beim Abrufen der Vorlagen:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * POST /api/templates * Neue Vorlage erstellen */ router.post('/', (req, res) => { try { const { projectId, name, titleTemplate, description, priority, labels, subtasks, timeEstimateMin } = req.body; // Validierung const errors = []; errors.push(validators.required(projectId, 'Projekt-ID')); errors.push(validators.required(name, 'Name')); errors.push(validators.maxLength(name, 50, 'Name')); if (titleTemplate) errors.push(validators.maxLength(titleTemplate, 200, 'Titel-Vorlage')); if (priority) errors.push(validators.enum(priority, ['low', 'medium', 'high'], 'Priorität')); const firstError = errors.find(e => e !== null); if (firstError) { return res.status(400).json({ error: firstError }); } const db = getDb(); const result = db.prepare(` INSERT INTO task_templates ( project_id, name, title_template, description, priority, labels, subtasks, time_estimate_min ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `).run( projectId, name, titleTemplate || null, description || null, priority || 'medium', labels ? JSON.stringify(labels) : null, subtasks ? JSON.stringify(subtasks) : null, timeEstimateMin || null ); const template = db.prepare('SELECT * FROM task_templates WHERE id = ?').get(result.lastInsertRowid); logger.info(`Vorlage erstellt: ${name} in Projekt ${projectId}`); res.status(201).json({ id: template.id, projectId: template.project_id, name: template.name, titleTemplate: template.title_template, description: template.description, priority: template.priority, labels: template.labels ? JSON.parse(template.labels) : [], subtasks: template.subtasks ? JSON.parse(template.subtasks) : [], timeEstimateMin: template.time_estimate_min }); } catch (error) { logger.error('Fehler beim Erstellen der Vorlage:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * PUT /api/templates/:id * Vorlage aktualisieren */ router.put('/:id', (req, res) => { try { const templateId = req.params.id; const { name, titleTemplate, description, priority, labels, subtasks, timeEstimateMin } = req.body; const db = getDb(); const existing = db.prepare('SELECT * FROM task_templates WHERE id = ?').get(templateId); if (!existing) { return res.status(404).json({ error: 'Vorlage nicht gefunden' }); } // Validierung if (name) { const nameError = validators.maxLength(name, 50, 'Name'); if (nameError) return res.status(400).json({ error: nameError }); } if (priority) { const prioError = validators.enum(priority, ['low', 'medium', 'high'], 'Priorität'); if (prioError) return res.status(400).json({ error: prioError }); } db.prepare(` UPDATE task_templates SET name = COALESCE(?, name), title_template = ?, description = ?, priority = COALESCE(?, priority), labels = ?, subtasks = ?, time_estimate_min = ? WHERE id = ? `).run( name || null, titleTemplate !== undefined ? titleTemplate : existing.title_template, description !== undefined ? description : existing.description, priority || null, labels !== undefined ? JSON.stringify(labels) : existing.labels, subtasks !== undefined ? JSON.stringify(subtasks) : existing.subtasks, timeEstimateMin !== undefined ? timeEstimateMin : existing.time_estimate_min, templateId ); const template = db.prepare('SELECT * FROM task_templates WHERE id = ?').get(templateId); logger.info(`Vorlage aktualisiert: ${template.name} (ID: ${templateId})`); res.json({ id: template.id, projectId: template.project_id, name: template.name, titleTemplate: template.title_template, description: template.description, priority: template.priority, labels: template.labels ? JSON.parse(template.labels) : [], subtasks: template.subtasks ? JSON.parse(template.subtasks) : [], timeEstimateMin: template.time_estimate_min }); } catch (error) { logger.error('Fehler beim Aktualisieren der Vorlage:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * POST /api/templates/:id/create-task * Aufgabe aus Vorlage erstellen */ router.post('/:id/create-task', (req, res) => { try { const templateId = req.params.id; const { columnId, title, assignedTo, dueDate } = req.body; const db = getDb(); const template = db.prepare('SELECT * FROM task_templates WHERE id = ?').get(templateId); if (!template) { return res.status(404).json({ error: 'Vorlage nicht gefunden' }); } // columnId ist erforderlich if (!columnId) { return res.status(400).json({ error: 'Spalten-ID erforderlich' }); } // Höchste Position ermitteln const maxPos = db.prepare( 'SELECT COALESCE(MAX(position), -1) as max FROM tasks WHERE column_id = ?' ).get(columnId).max; // Aufgabe erstellen const taskTitle = title || template.title_template || 'Neue Aufgabe'; const result = db.prepare(` INSERT INTO tasks ( project_id, column_id, title, description, priority, due_date, assigned_to, time_estimate_min, position, created_by ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).run( template.project_id, columnId, taskTitle, template.description, template.priority || 'medium', dueDate || null, assignedTo || null, template.time_estimate_min, maxPos + 1, req.user.id ); const taskId = result.lastInsertRowid; // Labels zuweisen if (template.labels) { const labelIds = JSON.parse(template.labels); const insertLabel = db.prepare('INSERT INTO task_labels (task_id, label_id) VALUES (?, ?)'); labelIds.forEach(labelId => { try { insertLabel.run(taskId, labelId); } catch (e) { /* Label existiert nicht mehr */ } }); } // Subtasks erstellen if (template.subtasks) { const subtaskTitles = JSON.parse(template.subtasks); const insertSubtask = db.prepare( 'INSERT INTO subtasks (task_id, title, position) VALUES (?, ?, ?)' ); subtaskTitles.forEach((st, idx) => { insertSubtask.run(taskId, st, idx); }); } // Historie db.prepare(` INSERT INTO history (task_id, user_id, action, new_value) VALUES (?, ?, 'created', ?) `).run(taskId, req.user.id, `Aus Vorlage: ${template.name}`); logger.info(`Aufgabe aus Vorlage erstellt: ${taskTitle} (Vorlage: ${template.name})`); // Vollständige Task-Daten laden (vereinfacht) const task = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId); const labels = db.prepare(` SELECT l.* FROM labels l JOIN task_labels tl ON l.id = tl.label_id WHERE tl.task_id = ? `).all(taskId); const subtasks = db.prepare('SELECT * FROM subtasks WHERE task_id = ? ORDER BY position').all(taskId); // WebSocket const io = req.app.get('io'); io.to(`project:${template.project_id}`).emit('task:created', { id: task.id, projectId: task.project_id, columnId: task.column_id, title: task.title, description: task.description, priority: task.priority, dueDate: task.due_date, assignedTo: task.assigned_to, timeEstimateMin: task.time_estimate_min, position: task.position, labels: labels.map(l => ({ id: l.id, name: l.name, color: l.color })), subtasks: subtasks.map(s => ({ id: s.id, title: s.title, completed: !!s.completed, position: s.position })) }); res.status(201).json({ id: task.id, projectId: task.project_id, columnId: task.column_id, title: task.title, description: task.description, priority: task.priority, dueDate: task.due_date, assignedTo: task.assigned_to, timeEstimateMin: task.time_estimate_min, position: task.position, labels: labels.map(l => ({ id: l.id, name: l.name, color: l.color })), subtasks: subtasks.map(s => ({ id: s.id, title: s.title, completed: !!s.completed, position: s.position })) }); } catch (error) { logger.error('Fehler beim Erstellen aus Vorlage:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * DELETE /api/templates/:id * Vorlage löschen */ router.delete('/:id', (req, res) => { try { const templateId = req.params.id; const db = getDb(); const template = db.prepare('SELECT * FROM task_templates WHERE id = ?').get(templateId); if (!template) { return res.status(404).json({ error: 'Vorlage nicht gefunden' }); } db.prepare('DELETE FROM task_templates WHERE id = ?').run(templateId); logger.info(`Vorlage gelöscht: ${template.name} (ID: ${templateId})`); res.json({ message: 'Vorlage gelöscht' }); } catch (error) { logger.error('Fehler beim Löschen der Vorlage:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); module.exports = router;