Initial commit
Dieser Commit ist enthalten in:
336
backend/routes/templates.js
Normale Datei
336
backend/routes/templates.js
Normale Datei
@ -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;
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren