300 Zeilen
9.3 KiB
JavaScript
300 Zeilen
9.3 KiB
JavaScript
/**
|
|
* 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;
|