/** * TASKMATE - Column Routes * ======================== * CRUD für Board-Spalten */ const express = require('express'); const router = express.Router(); const { getDb } = require('../database'); const logger = require('../utils/logger'); const { validators } = require('../middleware/validation'); /** * GET /api/columns/:projectId * Alle Spalten eines Projekts */ router.get('/:projectId', (req, res) => { try { const db = getDb(); const columns = db.prepare(` SELECT * FROM columns WHERE project_id = ? ORDER BY position `).all(req.params.projectId); res.json(columns.map(c => ({ id: c.id, projectId: c.project_id, name: c.name, position: c.position, color: c.color, filterCategory: c.filter_category || 'in_progress' }))); } catch (error) { logger.error('Fehler beim Abrufen der Spalten:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * POST /api/columns * Neue Spalte erstellen */ router.post('/', (req, res) => { try { const { projectId, name, color, filterCategory } = 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 (color) errors.push(validators.hexColor(color, 'Farbe')); const firstError = errors.find(e => e !== null); if (firstError) { return res.status(400).json({ error: firstError }); } const db = getDb(); // Höchste Position ermitteln const maxPos = db.prepare( 'SELECT COALESCE(MAX(position), -1) as max FROM columns WHERE project_id = ?' ).get(projectId).max; // Spalte erstellen mit filter_category const result = db.prepare(` INSERT INTO columns (project_id, name, position, color, filter_category) VALUES (?, ?, ?, ?, ?) `).run(projectId, name, maxPos + 1, color || null, filterCategory || 'in_progress'); const column = db.prepare('SELECT * FROM columns WHERE id = ?').get(result.lastInsertRowid); logger.info(`Spalte erstellt: ${name} in Projekt ${projectId} (Filter: ${filterCategory || 'in_progress'})`); // WebSocket const io = req.app.get('io'); io.to(`project:${projectId}`).emit('column:created', { id: column.id, projectId: column.project_id, name: column.name, position: column.position, color: column.color, filterCategory: column.filter_category }); res.status(201).json({ id: column.id, projectId: column.project_id, name: column.name, position: column.position, color: column.color, filterCategory: column.filter_category }); } catch (error) { logger.error('Fehler beim Erstellen der Spalte:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * PUT /api/columns/:id * Spalte aktualisieren */ router.put('/:id', (req, res) => { try { const columnId = req.params.id; const { name, color, filterCategory } = req.body; // Validierung if (name) { const nameError = validators.maxLength(name, 50, 'Name'); if (nameError) { return res.status(400).json({ error: nameError }); } } if (color) { const colorError = validators.hexColor(color, 'Farbe'); if (colorError) { return res.status(400).json({ error: colorError }); } } const db = getDb(); const existing = db.prepare('SELECT * FROM columns WHERE id = ?').get(columnId); if (!existing) { return res.status(404).json({ error: 'Spalte nicht gefunden' }); } db.prepare(` UPDATE columns SET name = COALESCE(?, name), color = ?, filter_category = COALESCE(?, filter_category) WHERE id = ? `).run(name || null, color !== undefined ? color : existing.color, filterCategory || null, columnId); const column = db.prepare('SELECT * FROM columns WHERE id = ?').get(columnId); logger.info(`Spalte aktualisiert: ${column.name} (ID: ${columnId})`); // WebSocket const io = req.app.get('io'); io.to(`project:${column.project_id}`).emit('column:updated', { id: column.id, name: column.name, color: column.color, filterCategory: column.filter_category }); res.json({ id: column.id, projectId: column.project_id, name: column.name, position: column.position, color: column.color, filterCategory: column.filter_category }); } catch (error) { logger.error('Fehler beim Aktualisieren der Spalte:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * PUT /api/columns/:id/position * Spalten-Position ändern (Reihenfolge) */ router.put('/:id/position', (req, res) => { try { const columnId = req.params.id; const { newPosition } = req.body; if (typeof newPosition !== 'number' || newPosition < 0) { return res.status(400).json({ error: 'Ungültige Position' }); } const db = getDb(); const column = db.prepare('SELECT * FROM columns WHERE id = ?').get(columnId); if (!column) { return res.status(404).json({ error: 'Spalte nicht gefunden' }); } const oldPosition = column.position; const projectId = column.project_id; // Positionen der anderen Spalten anpassen if (newPosition > oldPosition) { // Nach rechts verschoben: Spalten dazwischen nach links db.prepare(` UPDATE columns SET position = position - 1 WHERE project_id = ? AND position > ? AND position <= ? `).run(projectId, oldPosition, newPosition); } else if (newPosition < oldPosition) { // Nach links verschoben: Spalten dazwischen nach rechts db.prepare(` UPDATE columns SET position = position + 1 WHERE project_id = ? AND position >= ? AND position < ? `).run(projectId, newPosition, oldPosition); } // Neue Position setzen db.prepare('UPDATE columns SET position = ? WHERE id = ?').run(newPosition, columnId); // Alle Spalten des Projekts zurückgeben const columns = db.prepare( 'SELECT * FROM columns WHERE project_id = ? ORDER BY position' ).all(projectId); logger.info(`Spalte ${column.name} von Position ${oldPosition} zu ${newPosition} verschoben`); // WebSocket const io = req.app.get('io'); io.to(`project:${projectId}`).emit('columns:reordered', { columns: columns.map(c => ({ id: c.id, name: c.name, position: c.position, color: c.color, filterCategory: c.filter_category })) }); res.json({ columns: columns.map(c => ({ id: c.id, projectId: c.project_id, name: c.name, position: c.position, color: c.color, filterCategory: c.filter_category })) }); } catch (error) { logger.error('Fehler beim Verschieben der Spalte:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); /** * DELETE /api/columns/:id * Spalte löschen */ router.delete('/:id', (req, res) => { try { const columnId = req.params.id; const db = getDb(); const column = db.prepare('SELECT * FROM columns WHERE id = ?').get(columnId); if (!column) { return res.status(404).json({ error: 'Spalte nicht gefunden' }); } // Prüfen ob Aufgaben in der Spalte sind const taskCount = db.prepare( 'SELECT COUNT(*) as count FROM tasks WHERE column_id = ?' ).get(columnId).count; if (taskCount > 0) { return res.status(400).json({ error: 'Spalte enthält noch Aufgaben. Verschiebe oder lösche diese zuerst.' }); } // Mindestens eine Spalte muss bleiben const columnCount = db.prepare( 'SELECT COUNT(*) as count FROM columns WHERE project_id = ?' ).get(column.project_id).count; if (columnCount <= 1) { return res.status(400).json({ error: 'Mindestens eine Spalte muss vorhanden sein.' }); } // Spalte löschen db.prepare('DELETE FROM columns WHERE id = ?').run(columnId); // Positionen neu nummerieren const remainingColumns = db.prepare( 'SELECT id FROM columns WHERE project_id = ? ORDER BY position' ).all(column.project_id); remainingColumns.forEach((col, index) => { db.prepare('UPDATE columns SET position = ? WHERE id = ?').run(index, col.id); }); logger.info(`Spalte gelöscht: ${column.name} (ID: ${columnId})`); // WebSocket const io = req.app.get('io'); io.to(`project:${column.project_id}`).emit('column:deleted', { id: columnId }); res.json({ message: 'Spalte gelöscht' }); } catch (error) { logger.error('Fehler beim Löschen der Spalte:', { error: error.message }); res.status(500).json({ error: 'Interner Serverfehler' }); } }); module.exports = router;