/** * TASKMATE - Proposals Routes * =========================== * API-Endpunkte fuer Vorschlaege und Genehmigungen */ const express = require('express'); const router = express.Router(); const { getDb } = require('../database'); const { authenticateToken, requireRegularUser, checkPermission } = require('../middleware/auth'); const logger = require('../utils/logger'); const notificationService = require('../services/notificationService'); // Alle Proposals-Routes erfordern Authentifizierung und regulaeren User (kein Admin) router.use(authenticateToken); router.use(requireRegularUser); /** * GET /api/proposals - Alle Genehmigungen abrufen (projektbezogen) * Query-Parameter: sort = 'date' | 'alpha', archived = '0' | '1', projectId = number */ router.get('/', (req, res) => { try { const db = getDb(); const sort = req.query.sort || 'date'; const archived = req.query.archived === '1' ? 1 : 0; const projectId = req.query.projectId ? parseInt(req.query.projectId) : null; let orderBy; switch (sort) { case 'alpha': orderBy = 'p.title ASC'; break; case 'date': default: orderBy = 'p.created_at DESC'; break; } // Nur Genehmigungen des aktuellen Projekts laden let whereClause = 'p.archived = ?'; const params = [archived]; if (projectId) { whereClause += ' AND p.project_id = ?'; params.push(projectId); } const proposals = db.prepare(` SELECT p.*, u.display_name as created_by_name, u.color as created_by_color, ua.display_name as approved_by_name, t.title as task_title, t.id as linked_task_id FROM proposals p LEFT JOIN users u ON p.created_by = u.id LEFT JOIN users ua ON p.approved_by = ua.id LEFT JOIN tasks t ON p.task_id = t.id WHERE ${whereClause} ORDER BY ${orderBy} `).all(...params); res.json(proposals); } catch (error) { logger.error('Fehler beim Abrufen der Genehmigungen:', error); res.status(500).json({ error: 'Fehler beim Abrufen der Genehmigungen' }); } }); /** * POST /api/proposals - Neue Genehmigung erstellen (projektbezogen) */ router.post('/', (req, res) => { try { const { title, description, taskId, projectId } = req.body; if (!title || title.trim().length === 0) { return res.status(400).json({ error: 'Titel erforderlich' }); } if (!projectId) { return res.status(400).json({ error: 'Projekt erforderlich' }); } const db = getDb(); const result = db.prepare(` INSERT INTO proposals (title, description, created_by, task_id, project_id) VALUES (?, ?, ?, ?, ?) `).run(title.trim(), description?.trim() || null, req.user.id, taskId || null, projectId); const proposal = db.prepare(` SELECT p.*, u.display_name as created_by_name, u.color as created_by_color, t.title as task_title, t.id as linked_task_id FROM proposals p LEFT JOIN users u ON p.created_by = u.id LEFT JOIN tasks t ON p.task_id = t.id WHERE p.id = ? `).get(result.lastInsertRowid); logger.info(`Benutzer ${req.user.username} hat Genehmigung "${title}" erstellt`); // Benachrichtigungen an User mit 'genehmigung'-Berechtigung senden (persistent) const io = req.app.get('io'); const usersWithPermission = db.prepare(` SELECT id FROM users WHERE role = 'user' AND permissions LIKE '%genehmigung%' AND id != ? `).all(req.user.id); usersWithPermission.forEach(user => { notificationService.create(user.id, 'approval:pending', { proposalId: proposal.id, proposalTitle: title.trim(), projectId: projectId, actorId: req.user.id, actorName: req.user.display_name || req.user.username }, io, true); // persistent = true }); res.status(201).json(proposal); } catch (error) { logger.error('Fehler beim Erstellen der Genehmigung:', error); res.status(500).json({ error: 'Fehler beim Erstellen der Genehmigung' }); } }); /** * PUT /api/proposals/:id/approve - Genehmigung erteilen (nur mit Berechtigung) */ router.put('/:id/approve', checkPermission('genehmigung'), (req, res) => { try { const proposalId = parseInt(req.params.id); const { approved } = req.body; const db = getDb(); // Genehmigung pruefen const proposal = db.prepare('SELECT * FROM proposals WHERE id = ?').get(proposalId); if (!proposal) { return res.status(404).json({ error: 'Genehmigung nicht gefunden' }); } if (approved) { // Genehmigen db.prepare(` UPDATE proposals SET approved = 1, approved_by = ?, approved_at = CURRENT_TIMESTAMP WHERE id = ? `).run(req.user.id, proposalId); logger.info(`Benutzer ${req.user.username} hat Genehmigung ${proposalId} erteilt`); } else { // Genehmigung zurueckziehen db.prepare(` UPDATE proposals SET approved = 0, approved_by = NULL, approved_at = NULL WHERE id = ? `).run(proposalId); logger.info(`Benutzer ${req.user.username} hat Genehmigung ${proposalId} zurueckgezogen`); } // Aktualisierte Genehmigung zurueckgeben const updatedProposal = db.prepare(` SELECT p.*, u.display_name as created_by_name, u.color as created_by_color, ua.display_name as approved_by_name, t.title as task_title, t.id as linked_task_id FROM proposals p LEFT JOIN users u ON p.created_by = u.id LEFT JOIN users ua ON p.approved_by = ua.id LEFT JOIN tasks t ON p.task_id = t.id WHERE p.id = ? `).get(proposalId); // Benachrichtigungen senden const io = req.app.get('io'); if (approved) { // Ersteller benachrichtigen dass genehmigt wurde if (proposal.created_by !== req.user.id) { notificationService.create(proposal.created_by, 'approval:granted', { proposalId: proposalId, proposalTitle: proposal.title, projectId: proposal.project_id, actorId: req.user.id, actorName: req.user.display_name || req.user.username }, io); } // Persistente Benachrichtigungen auflösen notificationService.resolvePersistent(proposalId); // Aktualisierte Zählung an alle User mit Berechtigung senden const usersWithPermission = db.prepare(` SELECT id FROM users WHERE role = 'user' AND permissions LIKE '%genehmigung%' `).all(); usersWithPermission.forEach(user => { const count = notificationService.getUnreadCount(user.id); io.to(`user:${user.id}`).emit('notification:count', { count }); }); } res.json(updatedProposal); } catch (error) { logger.error('Fehler beim Genehmigen:', error); res.status(500).json({ error: 'Fehler beim Genehmigen' }); } }); /** * PUT /api/proposals/:id/archive - Genehmigung archivieren/wiederherstellen (nur mit Berechtigung) */ router.put('/:id/archive', checkPermission('genehmigung'), (req, res) => { try { const proposalId = parseInt(req.params.id); const { archived } = req.body; const db = getDb(); // Genehmigung pruefen const proposal = db.prepare('SELECT * FROM proposals WHERE id = ?').get(proposalId); if (!proposal) { return res.status(404).json({ error: 'Genehmigung nicht gefunden' }); } db.prepare(` UPDATE proposals SET archived = ? WHERE id = ? `).run(archived ? 1 : 0, proposalId); logger.info(`Benutzer ${req.user.username} hat Genehmigung ${proposalId} ${archived ? 'archiviert' : 'wiederhergestellt'}`); // Aktualisierte Genehmigung zurueckgeben const updatedProposal = db.prepare(` SELECT p.*, u.display_name as created_by_name, u.color as created_by_color, ua.display_name as approved_by_name, t.title as task_title, t.id as linked_task_id FROM proposals p LEFT JOIN users u ON p.created_by = u.id LEFT JOIN users ua ON p.approved_by = ua.id LEFT JOIN tasks t ON p.task_id = t.id WHERE p.id = ? `).get(proposalId); res.json(updatedProposal); } catch (error) { logger.error('Fehler beim Archivieren:', error); res.status(500).json({ error: 'Fehler beim Archivieren' }); } }); /** * DELETE /api/proposals/:id - Eigene Genehmigung loeschen */ router.delete('/:id', (req, res) => { try { const proposalId = parseInt(req.params.id); const db = getDb(); // Genehmigung pruefen const proposal = db.prepare('SELECT * FROM proposals WHERE id = ?').get(proposalId); if (!proposal) { return res.status(404).json({ error: 'Genehmigung nicht gefunden' }); } // Nur eigene Genehmigungen loeschen (oder mit genehmigung-Berechtigung) const permissions = req.user.permissions || []; if (proposal.created_by !== req.user.id && !permissions.includes('genehmigung')) { return res.status(403).json({ error: 'Nur eigene Genehmigungen koennen geloescht werden' }); } db.prepare('DELETE FROM proposals WHERE id = ?').run(proposalId); logger.info(`Benutzer ${req.user.username} hat Genehmigung ${proposalId} geloescht`); res.json({ success: true }); } catch (error) { logger.error('Fehler beim Loeschen der Genehmigung:', error); res.status(500).json({ error: 'Fehler beim Loeschen der Genehmigung' }); } }); module.exports = router;