jagd-apps/stoeberhunde/backend/controllers/auditController.js

264 lines
7.8 KiB
JavaScript

const AuditLog = require('../models/AuditLog');
const logger = require('../utils/logger');
/**
* Get all audit logs with pagination and filtering
*/
const getAuditLogs = async (req, res) => {
try {
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 50));
const skip = (page - 1) * limit;
// Build filter
const filter = {};
if (req.query.action) {
filter.action = req.query.action;
}
if (req.query.resource) {
filter.resource = req.query.resource;
}
if (req.query.adminUsername) {
filter.adminUsername = req.query.adminUsername;
}
if (req.query.success !== undefined) {
filter.success = req.query.success === 'true';
}
// Date range filter
if (req.query.startDate || req.query.endDate) {
filter.createdAt = {};
if (req.query.startDate) {
filter.createdAt.$gte = new Date(req.query.startDate);
}
if (req.query.endDate) {
filter.createdAt.$lte = new Date(req.query.endDate);
}
}
const [logs, total] = await Promise.all([
AuditLog.find(filter)
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit)
.populate('adminId', 'username')
.select('-__v'),
AuditLog.countDocuments(filter)
]);
res.json({
success: true,
data: logs,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
});
} catch (error) {
logger.error('Fehler beim Abrufen der Audit-Logs:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Abrufen der Audit-Logs'
});
}
};
/**
* Get audit logs for a specific resource
*/
const getResourceAuditLogs = async (req, res) => {
try {
const { resource, resourceId } = req.params;
const logs = await AuditLog.find({ resource, resourceId })
.sort({ createdAt: -1 })
.limit(50)
.populate('adminId', 'username')
.select('-__v');
res.json({
success: true,
data: logs,
count: logs.length
});
} catch (error) {
logger.error('Fehler beim Abrufen der Resource-Audit-Logs:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Abrufen der Resource-Audit-Logs'
});
}
};
/**
* Get admin activity summary
*/
const getAdminActivity = async (req, res) => {
try {
const { adminId } = req.params;
const stats = await AuditLog.aggregate([
{ $match: { adminId: require('mongoose').Types.ObjectId(adminId) } },
{
$group: {
_id: '$action',
count: { $sum: 1 }
}
},
{ $sort: { count: -1 } }
]);
const recentActivity = await AuditLog.find({ adminId })
.sort({ createdAt: -1 })
.limit(20)
.select('-__v');
res.json({
success: true,
data: {
stats,
recentActivity
}
});
} catch (error) {
logger.error('Fehler beim Abrufen der Admin-Aktivität:', error);
res.status(500).json({
success: false,
message: 'Fehler beim Abrufen der Admin-Aktivität'
});
}
};
/**
* Aggregate statistics: counts by action, resource, admin, success/fail, daily activity
*/
const getAuditStats = async (req, res) => {
try {
const days = Math.min(365, Math.max(1, parseInt(req.query.days) || 30));
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
const [byAction, byAdmin, byResource, bySuccess, dailyActivity] = await Promise.all([
AuditLog.aggregate([
{ $match: { createdAt: { $gte: since } } },
{ $group: { _id: '$action', count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]),
AuditLog.aggregate([
{ $match: { createdAt: { $gte: since } } },
{ $group: { _id: '$adminUsername', count: { $sum: 1 } } },
{ $sort: { count: -1 } },
{ $limit: 10 }
]),
AuditLog.aggregate([
{ $match: { createdAt: { $gte: since } } },
{ $group: { _id: '$resource', count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]),
AuditLog.aggregate([
{ $match: { createdAt: { $gte: since } } },
{ $group: { _id: '$success', count: { $sum: 1 } } }
]),
AuditLog.aggregate([
{ $match: { createdAt: { $gte: since } } },
{ $group: {
_id: { $dateToString: { format: '%Y-%m-%d', date: '$createdAt' } },
count: { $sum: 1 },
failed: { $sum: { $cond: [{ $eq: ['$success', false] }, 1, 0] } }
}},
{ $sort: { _id: 1 } }
])
]);
const total = byAction.reduce((s, a) => s + a.count, 0);
const failed = bySuccess.find(s => s._id === false)?.count || 0;
res.json({
success: true,
data: {
total,
failed,
days,
since,
byAction: byAction.map(a => ({ action: a._id, count: a.count })),
byAdmin: byAdmin.map(a => ({ admin: a._id || 'unknown', count: a.count })),
byResource: byResource.map(a => ({ resource: a._id, count: a.count })),
dailyActivity: dailyActivity.map(a => ({ date: a._id, count: a.count, failed: a.failed }))
}
});
} catch (error) {
logger.error('Fehler beim Abrufen der Audit-Statistiken:', error);
res.status(500).json({ success: false, message: 'Fehler beim Abrufen der Audit-Statistiken' });
}
};
/**
* Export audit logs as CSV (respects same filters as getAuditLogs)
*/
const exportAuditLogs = async (req, res) => {
try {
const filter = {};
if (req.query.action) filter.action = req.query.action;
if (req.query.resource) filter.resource = req.query.resource;
if (req.query.adminUsername) filter.adminUsername = req.query.adminUsername;
if (req.query.success !== undefined) filter.success = req.query.success === 'true';
if (req.query.startDate || req.query.endDate) {
filter.createdAt = {};
if (req.query.startDate) filter.createdAt.$gte = new Date(req.query.startDate);
if (req.query.endDate) filter.createdAt.$lte = new Date(req.query.endDate);
}
const logs = await AuditLog.find(filter)
.sort({ createdAt: -1 })
.limit(10000)
.lean();
const escapeCell = (val) => {
if (val == null) return '';
const str = String(val);
return str.includes(',') || str.includes('"') || str.includes('\n')
? `"${str.replace(/"/g, '""')}"` : str;
};
const header = [
'Zeitstempel', 'Aktion', 'Ressource', 'Ressourcen-Name', 'Admin',
'IP-Adresse', 'Methode', 'Pfad', 'Status-Code', 'Dauer (ms)',
'Erfolgreich', 'Fehlermeldung', 'Geänderte Felder', 'Metadaten'
].join(',');
const rows = logs.map(log => [
escapeCell(new Date(log.createdAt).toLocaleString('de-DE')),
escapeCell(log.action),
escapeCell(log.resource),
escapeCell(log.resourceName),
escapeCell(log.adminUsername),
escapeCell(log.ipAddress),
escapeCell(log.requestMethod),
escapeCell(log.requestPath),
escapeCell(log.statusCode),
escapeCell(log.duration),
log.success ? 'Ja' : 'Nein',
escapeCell(log.errorMessage),
escapeCell(log.changes ? Object.keys(log.changes).join(', ') : ''),
escapeCell(log.metadata ? JSON.stringify(log.metadata) : '')
].join(','));
const filename = `audit-logs_${new Date().toISOString().slice(0, 10)}.csv`;
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
res.send('\uFEFF' + [header, ...rows].join('\n'));
} catch (error) {
logger.error('Fehler beim Exportieren der Audit-Logs:', error);
res.status(500).json({ success: false, message: 'Fehler beim Exportieren der Audit-Logs' });
}
};
module.exports = {
getAuditLogs,
getResourceAuditLogs,
getAdminActivity,
getAuditStats,
exportAuditLogs
};