280 Zeilen
8.2 KiB
JavaScript
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;
|