/** * Stats and audit-log routes module. * * Punkt 7: Single aggregated query for stats instead of 9 nested callbacks. */ const express = require('express'); const db = require('../db'); const { authMiddleware, adminMiddleware } = require('../middleware/auth'); const { validateQuery, paginationSchema } = require('../middleware/validation'); const router = express.Router(); router.use(authMiddleware); router.use(adminMiddleware); // Punkt 7: Single aggregated stats query router.get('/stats', async (req, res) => { try { const rawStats = await db.prepare(` SELECT (SELECT COUNT(*) FROM users WHERE status = 'aktiv') as activeUsers, (SELECT COUNT(*) FROM users) as totalUsers, (SELECT COUNT(*) FROM tasks WHERE status = 'offen') as openTasks, (SELECT COUNT(*) FROM tasks WHERE status = 'erledigt') as completedTasks, (SELECT COUNT(*) FROM tasks) as totalTasks, (SELECT COUNT(*) FROM templates) as totalTemplates, (SELECT COUNT(*) FROM templates WHERE is_assignable = 1) as assignableTemplates, (SELECT COUNT(*) FROM users WHERE source = 'ad') as adUsers, (SELECT COUNT(*) FROM users WHERE source = 'local') as localUsers `).get(); // PostgreSQL lowercases aliases; normalize keys and coerce counts to numbers. const normalizeKey = (key) => key.toLowerCase(); const keyMap = { activeusers: 'activeUsers', totalusers: 'totalUsers', opentasks: 'openTasks', completedtasks: 'completedTasks', totaltasks: 'totalTasks', totaltemplates: 'totalTemplates', assignabletemplates: 'assignableTemplates', adusers: 'adUsers', localusers: 'localUsers' }; const stats = {}; for (const [key, value] of Object.entries(rawStats)) { const normalized = normalizeKey(key); const newKey = keyMap[normalized] || normalized; stats[newKey] = typeof value === 'string' ? Number(value) : value; } // Top 5 Vorlagen nach Task-Anzahl (Graph zeigt max. 5 Säulen). // HAVING filtert Vorlagen ohne Tasks heraus, damit der Graph nur // tatsächlich genutzte Vorlagen zeigt. const topTemplates = await db.prepare( 'SELECT t.id, t.name, COUNT(tk.id) as task_count FROM templates t LEFT JOIN tasks tk ON t.id = tk.template_id GROUP BY t.id HAVING COUNT(tk.id) > 0 ORDER BY task_count DESC LIMIT 5' ).all(); const recentActivity = await db.prepare( 'SELECT al.*, u.name as user_name, u.email as user_email FROM audit_log al LEFT JOIN users u ON al.user_id = u.id ORDER BY al.created_at DESC LIMIT 10' ).all(); res.json({ ...stats, topTemplates, recentActivity }); } catch (err) { console.error('[ERROR] GET /stats -', err.message); res.status(500).json({ error: 'Interner Serverfehler.' }); } }); // Audit log with pagination (Punkt 16: bounded limits) router.get('/audit-log', validateQuery(paginationSchema), async (req, res) => { const { page, limit } = req.validatedQuery; const offset = (page - 1) * limit; try { const rows = await db.prepare('SELECT al.*, u.name as user_name, u.email as user_email FROM audit_log al LEFT JOIN users u ON al.user_id = u.id ORDER BY al.created_at DESC LIMIT ? OFFSET ?').all(limit, offset); const countRow = await db.prepare('SELECT COUNT(*) as total FROM audit_log').get(); const total = countRow?.total || 0; res.json({ entries: rows, total, page, limit, totalPages: Math.ceil(total / limit) }); } catch (err) { console.error('[ERROR] GET /audit-log -', err.message); res.status(500).json({ error: 'Interner Serverfehler.' }); } }); module.exports = router;