Initial commit
Dieser Commit ist enthalten in:
302
backend/routes/columns.js
Normale Datei
302
backend/routes/columns.js
Normale Datei
@ -0,0 +1,302 @@
|
||||
/**
|
||||
* 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;
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren