Files
TaskMate/backend/routes/projects.js
Claude Project Manager ab1e5be9a9 Initial commit
2025-12-28 21:36:45 +00:00

360 Zeilen
11 KiB
JavaScript

/**
* TASKMATE - Project Routes
* =========================
* CRUD für Projekte
*/
const express = require('express');
const router = express.Router();
const { getDb } = require('../database');
const logger = require('../utils/logger');
const { validators } = require('../middleware/validation');
/**
* GET /api/projects
* Alle Projekte abrufen
*/
router.get('/', (req, res) => {
try {
const db = getDb();
const includeArchived = req.query.archived === 'true';
let query = `
SELECT p.*, u.display_name as creator_name,
(SELECT COUNT(*) FROM tasks t WHERE t.project_id = p.id AND t.archived = 0) as task_count,
(SELECT COUNT(*) FROM tasks t WHERE t.project_id = p.id AND t.archived = 0 AND t.column_id IN
(SELECT c.id FROM columns c WHERE c.project_id = p.id ORDER BY c.position DESC LIMIT 1)) as completed_count
FROM projects p
LEFT JOIN users u ON p.created_by = u.id
`;
if (!includeArchived) {
query += ' WHERE p.archived = 0';
}
query += ' ORDER BY p.created_at DESC';
const projects = db.prepare(query).all();
res.json(projects.map(p => ({
id: p.id,
name: p.name,
description: p.description,
archived: !!p.archived,
createdAt: p.created_at,
createdBy: p.created_by,
creatorName: p.creator_name,
taskCount: p.task_count,
completedCount: p.completed_count
})));
} catch (error) {
logger.error('Fehler beim Abrufen der Projekte:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* GET /api/projects/:id
* Einzelnes Projekt mit Spalten und Aufgaben
*/
router.get('/:id', (req, res) => {
try {
const db = getDb();
const projectId = req.params.id;
// Projekt abrufen
const project = db.prepare(`
SELECT p.*, u.display_name as creator_name
FROM projects p
LEFT JOIN users u ON p.created_by = u.id
WHERE p.id = ?
`).get(projectId);
if (!project) {
return res.status(404).json({ error: 'Projekt nicht gefunden' });
}
// Spalten abrufen
const columns = db.prepare(`
SELECT * FROM columns WHERE project_id = ? ORDER BY position
`).all(projectId);
// Labels abrufen
const labels = db.prepare(`
SELECT * FROM labels WHERE project_id = ?
`).all(projectId);
res.json({
id: project.id,
name: project.name,
description: project.description,
archived: !!project.archived,
createdAt: project.created_at,
createdBy: project.created_by,
creatorName: project.creator_name,
columns: columns.map(c => ({
id: c.id,
name: c.name,
position: c.position,
color: c.color
})),
labels: labels.map(l => ({
id: l.id,
name: l.name,
color: l.color
}))
});
} catch (error) {
logger.error('Fehler beim Abrufen des Projekts:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* POST /api/projects
* Neues Projekt erstellen
*/
router.post('/', (req, res) => {
try {
const { name, description } = req.body;
// Validierung
const nameError = validators.required(name, 'Name') ||
validators.maxLength(name, 100, 'Name');
if (nameError) {
return res.status(400).json({ error: nameError });
}
const db = getDb();
// Projekt erstellen
const result = db.prepare(`
INSERT INTO projects (name, description, created_by)
VALUES (?, ?, ?)
`).run(name, description || null, req.user.id);
const projectId = result.lastInsertRowid;
// Standard-Spalten erstellen
const insertColumn = db.prepare(`
INSERT INTO columns (project_id, name, position) VALUES (?, ?, ?)
`);
insertColumn.run(projectId, 'Offen', 0);
insertColumn.run(projectId, 'In Arbeit', 1);
insertColumn.run(projectId, 'Erledigt', 2);
// Standard-Labels erstellen
const insertLabel = db.prepare(`
INSERT INTO labels (project_id, name, color) VALUES (?, ?, ?)
`);
insertLabel.run(projectId, 'Bug', '#DC2626');
insertLabel.run(projectId, 'Feature', '#059669');
insertLabel.run(projectId, 'Dokumentation', '#3182CE');
// Projekt mit Spalten und Labels zurückgeben
const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
const columns = db.prepare('SELECT * FROM columns WHERE project_id = ? ORDER BY position').all(projectId);
const labels = db.prepare('SELECT * FROM labels WHERE project_id = ?').all(projectId);
logger.info(`Projekt erstellt: ${name} (ID: ${projectId}) von ${req.user.username}`);
// WebSocket: Andere Clients benachrichtigen
const io = req.app.get('io');
io.emit('project:created', {
id: projectId,
name: project.name,
description: project.description,
createdBy: req.user.id
});
res.status(201).json({
id: project.id,
name: project.name,
description: project.description,
archived: false,
createdAt: project.created_at,
createdBy: project.created_by,
columns: columns.map(c => ({ id: c.id, name: c.name, position: c.position, color: c.color })),
labels: labels.map(l => ({ id: l.id, name: l.name, color: l.color }))
});
} catch (error) {
logger.error('Fehler beim Erstellen des Projekts:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* PUT /api/projects/:id
* Projekt aktualisieren
*/
router.put('/:id', (req, res) => {
try {
const projectId = req.params.id;
const { name, description } = req.body;
// Validierung
if (name) {
const nameError = validators.maxLength(name, 100, 'Name');
if (nameError) {
return res.status(400).json({ error: nameError });
}
}
const db = getDb();
// Prüfen ob Projekt existiert
const existing = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
if (!existing) {
return res.status(404).json({ error: 'Projekt nicht gefunden' });
}
// Aktualisieren
db.prepare(`
UPDATE projects
SET name = COALESCE(?, name), description = COALESCE(?, description)
WHERE id = ?
`).run(name || null, description !== undefined ? description : null, projectId);
const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
logger.info(`Projekt aktualisiert: ${project.name} (ID: ${projectId})`);
// WebSocket
const io = req.app.get('io');
io.emit('project:updated', {
id: project.id,
name: project.name,
description: project.description
});
res.json({
id: project.id,
name: project.name,
description: project.description,
archived: !!project.archived,
createdAt: project.created_at
});
} catch (error) {
logger.error('Fehler beim Aktualisieren des Projekts:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* PUT /api/projects/:id/archive
* Projekt archivieren/wiederherstellen
*/
router.put('/:id/archive', (req, res) => {
try {
const projectId = req.params.id;
const { archived } = req.body;
const db = getDb();
const existing = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
if (!existing) {
return res.status(404).json({ error: 'Projekt nicht gefunden' });
}
db.prepare('UPDATE projects SET archived = ? WHERE id = ?')
.run(archived ? 1 : 0, projectId);
logger.info(`Projekt ${archived ? 'archiviert' : 'wiederhergestellt'}: ${existing.name}`);
// WebSocket
const io = req.app.get('io');
io.emit('project:archived', { id: projectId, archived: !!archived });
res.json({ message: archived ? 'Projekt archiviert' : 'Projekt wiederhergestellt' });
} catch (error) {
logger.error('Fehler beim Archivieren:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* DELETE /api/projects/:id
* Projekt löschen
* Query param: force=true um alle zugehörigen Aufgaben mitzulöschen
*/
router.delete('/:id', (req, res) => {
try {
const projectId = req.params.id;
const forceDelete = req.query.force === 'true';
const db = getDb();
const project = db.prepare('SELECT * FROM projects WHERE id = ?').get(projectId);
if (!project) {
return res.status(404).json({ error: 'Projekt nicht gefunden' });
}
// Anzahl der Aufgaben ermitteln
const taskCount = db.prepare(
'SELECT COUNT(*) as count FROM tasks WHERE project_id = ?'
).get(projectId).count;
// Ohne force: Prüfen ob noch aktive Aufgaben existieren
if (!forceDelete && taskCount > 0) {
return res.status(400).json({
error: 'Projekt enthält noch Aufgaben. Verwende force=true um alles zu löschen.',
taskCount: taskCount
});
}
// Bei force=true: Explizit alle zugehörigen Daten löschen
if (forceDelete && taskCount > 0) {
// Alle Task-IDs für das Projekt holen
const taskIds = db.prepare('SELECT id FROM tasks WHERE project_id = ?')
.all(projectId)
.map(t => t.id);
if (taskIds.length > 0) {
const placeholders = taskIds.map(() => '?').join(',');
// Anhänge löschen
db.prepare(`DELETE FROM attachments WHERE task_id IN (${placeholders})`).run(...taskIds);
// Kommentare löschen
db.prepare(`DELETE FROM comments WHERE task_id IN (${placeholders})`).run(...taskIds);
// Task-Labels löschen
db.prepare(`DELETE FROM task_labels WHERE task_id IN (${placeholders})`).run(...taskIds);
// Task-Assignees löschen (Mehrfachzuweisung)
db.prepare(`DELETE FROM task_assignees WHERE task_id IN (${placeholders})`).run(...taskIds);
// Unteraufgaben löschen
db.prepare(`DELETE FROM subtasks WHERE task_id IN (${placeholders})`).run(...taskIds);
// Links löschen
db.prepare(`DELETE FROM links WHERE task_id IN (${placeholders})`).run(...taskIds);
// Historie löschen
db.prepare(`DELETE FROM history WHERE task_id IN (${placeholders})`).run(...taskIds);
// Tasks löschen
db.prepare(`DELETE FROM tasks WHERE project_id = ?`).run(projectId);
}
logger.info(`${taskCount} Aufgaben gelöscht für Projekt: ${project.name}`);
}
// Labels des Projekts löschen
db.prepare('DELETE FROM labels WHERE project_id = ?').run(projectId);
// Spalten löschen
db.prepare('DELETE FROM columns WHERE project_id = ?').run(projectId);
// Projekt löschen
db.prepare('DELETE FROM projects WHERE id = ?').run(projectId);
logger.info(`Projekt gelöscht: ${project.name} (ID: ${projectId}), ${taskCount} Aufgaben entfernt`);
// WebSocket
const io = req.app.get('io');
io.emit('project:deleted', { id: projectId });
res.json({ message: 'Projekt gelöscht', deletedTasks: taskCount });
} catch (error) {
logger.error('Fehler beim Löschen des Projekts:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
module.exports = router;