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

280 Zeilen
8.2 KiB
JavaScript

/**
* TASKMATE - Comment Routes
* =========================
* CRUD für Kommentare
*/
const express = require('express');
const router = express.Router();
const { getDb } = require('../database');
const logger = require('../utils/logger');
const { validators, sanitizeMarkdown } = require('../middleware/validation');
const notificationService = require('../services/notificationService');
/**
* GET /api/comments/:taskId
* Alle Kommentare einer Aufgabe
*/
router.get('/:taskId', (req, res) => {
try {
const db = getDb();
const comments = db.prepare(`
SELECT c.*, u.display_name, u.color
FROM comments c
JOIN users u ON c.user_id = u.id
WHERE c.task_id = ?
ORDER BY c.created_at ASC
`).all(req.params.taskId);
res.json(comments.map(c => ({
id: c.id,
taskId: c.task_id,
userId: c.user_id,
userName: c.display_name,
userColor: c.color,
content: c.content,
createdAt: c.created_at
})));
} catch (error) {
logger.error('Fehler beim Abrufen der Kommentare:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* POST /api/comments
* Neuen Kommentar erstellen
*/
router.post('/', (req, res) => {
try {
const { taskId, content } = req.body;
// Validierung
const contentError = validators.required(content, 'Inhalt') ||
validators.maxLength(content, 5000, 'Inhalt');
if (contentError) {
return res.status(400).json({ error: contentError });
}
const db = getDb();
// Task prüfen
const task = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId);
if (!task) {
return res.status(404).json({ error: 'Aufgabe nicht gefunden' });
}
// Inhalt bereinigen (Markdown erlaubt)
const sanitizedContent = sanitizeMarkdown(content);
// @Erwähnungen verarbeiten
const mentions = content.match(/@(\w+)/g);
const mentionedUsers = [];
if (mentions) {
mentions.forEach(mention => {
const username = mention.substring(1);
const user = db.prepare('SELECT id, display_name FROM users WHERE username = ?').get(username);
if (user) {
mentionedUsers.push(user);
}
});
}
// Kommentar erstellen
const result = db.prepare(`
INSERT INTO comments (task_id, user_id, content)
VALUES (?, ?, ?)
`).run(taskId, req.user.id, sanitizedContent);
// Task updated_at aktualisieren
db.prepare('UPDATE tasks SET updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(taskId);
// Historie
db.prepare(`
INSERT INTO history (task_id, user_id, action, new_value)
VALUES (?, ?, 'commented', ?)
`).run(taskId, req.user.id, sanitizedContent.substring(0, 100));
const comment = db.prepare(`
SELECT c.*, u.display_name, u.color
FROM comments c
JOIN users u ON c.user_id = u.id
WHERE c.id = ?
`).get(result.lastInsertRowid);
logger.info(`Kommentar erstellt in Task ${taskId} von ${req.user.username}`);
// WebSocket
const io = req.app.get('io');
io.to(`project:${task.project_id}`).emit('comment:created', {
taskId,
comment: {
id: comment.id,
taskId: comment.task_id,
userId: comment.user_id,
userName: comment.display_name,
userColor: comment.color,
content: comment.content,
createdAt: comment.created_at
},
mentionedUsers
});
// Benachrichtigungen senden
// 1. Benachrichtigung an zugewiesene Mitarbeiter der Aufgabe
const assignees = db.prepare('SELECT user_id FROM task_assignees WHERE task_id = ?').all(taskId);
const mentionedUserIds = mentionedUsers.map(u => u.id);
assignees.forEach(a => {
// Nicht an Kommentator und nicht an erwähnte Benutzer (die bekommen separate Benachrichtigung)
if (a.user_id !== req.user.id && !mentionedUserIds.includes(a.user_id)) {
notificationService.create(a.user_id, 'comment:created', {
taskId: parseInt(taskId),
taskTitle: task.title,
projectId: task.project_id,
actorId: req.user.id,
actorName: req.user.display_name || req.user.username
}, io);
}
});
// 2. Benachrichtigung an erwähnte Benutzer
mentionedUsers.forEach(user => {
if (user.id !== req.user.id) {
notificationService.create(user.id, 'comment:mention', {
taskId: parseInt(taskId),
taskTitle: task.title,
projectId: task.project_id,
actorId: req.user.id,
actorName: req.user.display_name || req.user.username
}, io);
}
});
res.status(201).json({
id: comment.id,
taskId: comment.task_id,
userId: comment.user_id,
userName: comment.display_name,
userColor: comment.color,
content: comment.content,
createdAt: comment.created_at
});
} catch (error) {
logger.error('Fehler beim Erstellen des Kommentars:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* PUT /api/comments/:id
* Kommentar bearbeiten (nur eigene)
*/
router.put('/:id', (req, res) => {
try {
const commentId = req.params.id;
const { content } = req.body;
// Validierung
const contentError = validators.required(content, 'Inhalt') ||
validators.maxLength(content, 5000, 'Inhalt');
if (contentError) {
return res.status(400).json({ error: contentError });
}
const db = getDb();
const comment = db.prepare('SELECT * FROM comments WHERE id = ?').get(commentId);
if (!comment) {
return res.status(404).json({ error: 'Kommentar nicht gefunden' });
}
// Nur eigene Kommentare bearbeiten
if (comment.user_id !== req.user.id) {
return res.status(403).json({ error: 'Nur eigene Kommentare können bearbeitet werden' });
}
const sanitizedContent = sanitizeMarkdown(content);
db.prepare('UPDATE comments SET content = ? WHERE id = ?')
.run(sanitizedContent, commentId);
const updated = db.prepare(`
SELECT c.*, u.display_name, u.color
FROM comments c
JOIN users u ON c.user_id = u.id
WHERE c.id = ?
`).get(commentId);
const task = db.prepare('SELECT project_id FROM tasks WHERE id = ?').get(comment.task_id);
// WebSocket
const io = req.app.get('io');
io.to(`project:${task.project_id}`).emit('comment:updated', {
taskId: comment.task_id,
comment: {
id: updated.id,
taskId: updated.task_id,
userId: updated.user_id,
userName: updated.display_name,
userColor: updated.color,
content: updated.content,
createdAt: updated.created_at
}
});
res.json({
id: updated.id,
taskId: updated.task_id,
userId: updated.user_id,
userName: updated.display_name,
userColor: updated.color,
content: updated.content,
createdAt: updated.created_at
});
} catch (error) {
logger.error('Fehler beim Aktualisieren des Kommentars:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
/**
* DELETE /api/comments/:id
* Kommentar löschen (nur eigene)
*/
router.delete('/:id', (req, res) => {
try {
const commentId = req.params.id;
const db = getDb();
const comment = db.prepare('SELECT * FROM comments WHERE id = ?').get(commentId);
if (!comment) {
return res.status(404).json({ error: 'Kommentar nicht gefunden' });
}
// Nur eigene Kommentare löschen
if (comment.user_id !== req.user.id) {
return res.status(403).json({ error: 'Nur eigene Kommentare können gelöscht werden' });
}
db.prepare('DELETE FROM comments WHERE id = ?').run(commentId);
const task = db.prepare('SELECT project_id FROM tasks WHERE id = ?').get(comment.task_id);
// WebSocket
const io = req.app.get('io');
io.to(`project:${task.project_id}`).emit('comment:deleted', {
taskId: comment.task_id,
commentId
});
res.json({ message: 'Kommentar gelöscht' });
} catch (error) {
logger.error('Fehler beim Löschen des Kommentars:', { error: error.message });
res.status(500).json({ error: 'Interner Serverfehler' });
}
});
module.exports = router;