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