303 Zeilen
8.8 KiB
JavaScript
303 Zeilen
8.8 KiB
JavaScript
/**
|
|
* 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;
|