337 Zeilen
10 KiB
JavaScript
337 Zeilen
10 KiB
JavaScript
/**
|
|
* 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;
|