Dieser Commit ist enthalten in:
Claude Project Manager
2025-12-28 21:36:45 +00:00
Commit ab1e5be9a9
146 geänderte Dateien mit 65525 neuen und 0 gelöschten Zeilen

336
backend/routes/templates.js Normale Datei
Datei anzeigen

@ -0,0 +1,336 @@
/**
* 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;