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