Files
tilbudgivern/backend/routes/adminLogs.js
T
alexpolo1 8773d96e94 feat: Add AI-Powered Visual Testing Suite using Selenium and OpenAI Vision API
- Implemented VisualAITester class for automated UI/UX testing
- Configured environment variables for API keys and paths
- Developed methods for browser setup, login, screenshot capture, and image encoding
- Integrated OpenAI Vision API for visual quality analysis with detailed feedback
- Created tests for various application pages including homepage, project flow, geometry form, and more
- Generated comprehensive HTML report summarizing test results and AI analysis
- Added functionality to copy AI prompts for automated issue resolution
2025-11-14 20:27:19 +00:00

589 lines
13 KiB
JavaScript

/**
* Admin Logs Routes
* Endpoint til at se og søge i system logs
*/
const express = require('express');
const router = express.Router();
const db = require('../src/db/connection');
const { asyncHandler } = require('../src/middleware/errorHandler');
const logger = require('../src/utils/logger');
/**
* GET /api/admin/logs
* Hent system logs med filtrering og paginering
*/
router.get('/', asyncHandler(async (req, res) => {
const {
level,
context,
category,
startDate,
endDate,
limit = 100,
offset = 0,
resolved,
errorCode
} = req.query;
// Build WHERE clause dynamically
const conditions = [];
const params = [];
if (level) {
conditions.push('level = ?');
params.push(level);
}
if (context) {
conditions.push('context LIKE ?');
params.push(`%${context}%`);
}
if (category) {
conditions.push('category = ?');
params.push(category);
}
if (errorCode) {
conditions.push('error_code = ?');
params.push(errorCode);
}
if (startDate) {
conditions.push('created_at >= ?');
params.push(startDate);
}
if (endDate) {
conditions.push('created_at <= ?');
params.push(endDate);
}
if (resolved !== undefined) {
conditions.push('resolved = ?');
params.push(resolved === 'true' ? 1 : 0);
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
// Get total count
const [countResult] = await db.query(
`SELECT COUNT(*) as total FROM system_logs ${whereClause}`,
params
);
// Get logs
const [logs] = await db.query(
`SELECT
id, created_at, level, context, category, message, error_code,
request_method, request_url, user_id, user_email, resolved, resolved_at
FROM system_logs
${whereClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?`,
[...params, parseInt(limit), parseInt(offset)]
);
logger.logInfo('Admin.GetLogs', 'Logs retrieved', {
category: 'ADMIN',
req,
metadata: {
count: logs.length,
total: countResult[0].total
}
});
res.json({
success: true,
data: logs,
pagination: {
total: countResult[0].total,
limit: parseInt(limit),
offset: parseInt(offset),
hasMore: parseInt(offset) + logs.length < countResult[0].total
}
});
}));
/**
* GET /api/admin/logs/:id
* Hent fuld information om en specifik log
*/
router.get('/:id', asyncHandler(async (req, res) => {
const { id } = req.params;
const [logs] = await db.query(
'SELECT * FROM system_logs WHERE id = ?',
[id]
);
if (logs.length === 0) {
const error = new Error('Log entry ikke fundet');
error.status = 404;
throw error;
}
res.json({
success: true,
data: logs[0]
});
}));
/**
* GET /api/admin/logs/stats/summary
* Hent statistik over logs
*/
router.get('/stats/summary', asyncHandler(async (req, res) => {
const { startDate, endDate } = req.query;
const conditions = [];
const params = [];
if (startDate) {
conditions.push('created_at >= ?');
params.push(startDate);
}
if (endDate) {
conditions.push('created_at <= ?');
params.push(endDate);
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
// Count by level
const [levelStats] = await db.query(
`SELECT level, COUNT(*) as count
FROM system_logs
${whereClause}
GROUP BY level`,
params
);
// Count by category
const [categoryStats] = await db.query(
`SELECT category, COUNT(*) as count
FROM system_logs
${whereClause}
GROUP BY category
ORDER BY count DESC
LIMIT 10`,
params
);
// Most common errors
const [topErrors] = await db.query(
`SELECT error_code, message, COUNT(*) as count
FROM system_logs
${whereClause} AND level = 'ERROR'
GROUP BY error_code, message
ORDER BY count DESC
LIMIT 10`,
params
);
// Unresolved errors
const [unresolvedCount] = await db.query(
`SELECT COUNT(*) as count
FROM system_logs
${whereClause} AND level = 'ERROR' AND resolved = FALSE`,
params
);
logger.logInfo('Admin.GetLogStats', 'Log statistics retrieved', {
category: 'ADMIN',
req
});
res.json({
success: true,
stats: {
byLevel: levelStats,
byCategory: categoryStats,
topErrors: topErrors,
unresolvedErrors: unresolvedCount[0].count
}
});
}));
/**
* PATCH /api/admin/logs/:id/resolve
* Marker en log som løst
*/
router.patch('/:id/resolve', asyncHandler(async (req, res) => {
const { id } = req.params;
const { notes, resolvedBy } = req.body;
await db.query(
`UPDATE system_logs
SET resolved = TRUE, resolved_at = NOW(), resolved_by = ?, resolution_notes = ?
WHERE id = ?`,
[resolvedBy || 'system', notes || null, id]
);
logger.logInfo('Admin.ResolveLog', `Log ${id} marked as resolved`, {
category: 'ADMIN',
req,
metadata: { logId: id, resolvedBy }
});
res.json({
success: true,
message: 'Log markeret som løst'
});
}));
/**
* GET /api/admin/logs/performance/slow-requests
* Hent langsomme requests
*/
router.get('/performance/slow-requests', asyncHandler(async (req, res) => {
const { limit = 50, minResponseTime = 1000 } = req.query;
const [slowRequests] = await db.query(
`SELECT
method, url, status_code, response_time_ms,
user_id, created_at, metadata
FROM request_logs
WHERE response_time_ms >= ?
ORDER BY response_time_ms DESC
LIMIT ?`,
[parseInt(minResponseTime), parseInt(limit)]
);
logger.logInfo('Admin.GetSlowRequests', 'Slow requests retrieved', {
category: 'ADMIN',
req,
metadata: { count: slowRequests.length }
});
res.json({
success: true,
data: slowRequests
});
}));
/**
* GET /api/admin/logs/performance/endpoint-stats
* Hent endpoint statistik
*/
router.get('/performance/endpoint-stats', asyncHandler(async (req, res) => {
const { startDate, endDate } = req.query;
const conditions = [];
const params = [];
if (startDate) {
conditions.push('created_at >= ?');
params.push(startDate);
}
if (endDate) {
conditions.push('created_at <= ?');
params.push(endDate);
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
const [endpointStats] = await db.query(
`SELECT
url,
method,
COUNT(*) as request_count,
AVG(response_time_ms) as avg_response_time,
MIN(response_time_ms) as min_response_time,
MAX(response_time_ms) as max_response_time,
SUM(CASE WHEN status_code >= 400 THEN 1 ELSE 0 END) as error_count
FROM request_logs
${whereClause}
GROUP BY url, method
ORDER BY request_count DESC
LIMIT 20`,
params
);
logger.logInfo('Admin.GetEndpointStats', 'Endpoint statistics retrieved', {
category: 'ADMIN',
req
});
res.json({
success: true,
data: endpointStats
});
}));
/**
* GET /api/admin/logs/user-errors/:userId
* Hent alle fejl for en specifik bruger
*/
router.get('/user-errors/:userId', asyncHandler(async (req, res) => {
const { userId } = req.params;
const { limit = 50, offset = 0 } = req.query;
const [logs] = await db.query(
`SELECT
id, created_at, level, context, category, message, error_code,
request_method, request_url, resolved
FROM system_logs
WHERE user_id = ? AND level IN ('ERROR', 'WARNING')
ORDER BY created_at DESC
LIMIT ? OFFSET ?`,
[userId, parseInt(limit), parseInt(offset)]
);
logger.logInfo('Admin.GetUserErrors', `Fetched errors for user ${userId}`, {
category: 'ADMIN',
req,
metadata: { userId, count: logs.length }
});
res.json({
success: true,
data: logs
});
}));
/**
* GET /api/admin/logs/http-errors
* Hent alle HTTP fejl (4xx, 5xx)
*/
router.get('/http-errors', asyncHandler(async (req, res) => {
const { statusCode, startDate, endDate, limit = 100, offset = 0 } = req.query;
const conditions = ['category IN (?, ?, ?, ?, ?)'];
const params = ['HTTP_ERROR', 'CLIENT_ERROR', 'SERVER_ERROR', 'NOT_FOUND', 'ACCESS_DENIED'];
if (statusCode) {
conditions.push('error_code = ?');
params.push(`HTTP_${statusCode}`);
}
if (startDate) {
conditions.push('created_at >= ?');
params.push(startDate);
}
if (endDate) {
conditions.push('created_at <= ?');
params.push(endDate);
}
const whereClause = `WHERE ${conditions.join(' AND ')}`;
// Get total count
const [countResult] = await db.query(
`SELECT COUNT(*) as total FROM system_logs ${whereClause}`,
params
);
// Get logs
const [logs] = await db.query(
`SELECT
id, created_at, level, context, category, message, error_code,
request_method, request_url, user_id, user_email, metadata
FROM system_logs
${whereClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?`,
[...params, parseInt(limit), parseInt(offset)]
);
logger.logInfo('Admin.GetHTTPErrors', 'HTTP errors retrieved', {
category: 'ADMIN',
req,
metadata: { count: logs.length, total: countResult[0].total }
});
res.json({
success: true,
data: logs,
pagination: {
total: countResult[0].total,
limit: parseInt(limit),
offset: parseInt(offset)
}
});
}));
/**
* GET /api/admin/logs/session-errors
* Hent alle session-relaterede fejl
*/
router.get('/session-errors', asyncHandler(async (req, res) => {
const { startDate, endDate, limit = 100, offset = 0 } = req.query;
const conditions = ['category IN (?, ?, ?)'];
const params = ['SESSION', 'AUTHENTICATION', 'ACCESS_DENIED'];
if (startDate) {
conditions.push('created_at >= ?');
params.push(startDate);
}
if (endDate) {
conditions.push('created_at <= ?');
params.push(endDate);
}
const whereClause = `WHERE ${conditions.join(' AND ')}`;
// Get total count
const [countResult] = await db.query(
`SELECT COUNT(*) as total FROM system_logs ${whereClause}`,
params
);
// Get logs
const [logs] = await db.query(
`SELECT
id, created_at, level, context, category, message, error_code,
request_url, user_id, user_email, metadata
FROM system_logs
${whereClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?`,
[...params, parseInt(limit), parseInt(offset)]
);
logger.logInfo('Admin.GetSessionErrors', 'Session errors retrieved', {
category: 'ADMIN',
req,
metadata: { count: logs.length }
});
res.json({
success: true,
data: logs,
pagination: {
total: countResult[0].total,
limit: parseInt(limit),
offset: parseInt(offset)
}
});
}));
/**
* GET /api/admin/logs/stats/http-errors
* Statistik over HTTP fejl koder
*/
router.get('/stats/http-errors', asyncHandler(async (req, res) => {
const { startDate, endDate } = req.query;
const conditions = ['category IN (?, ?, ?, ?, ?)'];
const params = ['HTTP_ERROR', 'CLIENT_ERROR', 'SERVER_ERROR', 'NOT_FOUND', 'ACCESS_DENIED'];
if (startDate) {
conditions.push('created_at >= ?');
params.push(startDate);
}
if (endDate) {
conditions.push('created_at <= ?');
params.push(endDate);
}
const whereClause = `WHERE ${conditions.join(' AND ')}`;
// Count by status code
const [statusCodeStats] = await db.query(
`SELECT
error_code,
category,
COUNT(*) as count,
COUNT(DISTINCT user_id) as affected_users
FROM system_logs
${whereClause}
GROUP BY error_code, category
ORDER BY count DESC`,
params
);
// Most affected URLs
const [affectedUrls] = await db.query(
`SELECT
request_url as url,
COUNT(*) as error_count,
COUNT(DISTINCT user_id) as affected_users
FROM system_logs
${whereClause} AND request_url IS NOT NULL
GROUP BY request_url
ORDER BY error_count DESC
LIMIT 20`,
params
);
logger.logInfo('Admin.GetHTTPErrorStats', 'HTTP error statistics retrieved', {
category: 'ADMIN',
req
});
res.json({
success: true,
stats: {
byStatusCode: statusCodeStats,
affectedUrls: affectedUrls
}
});
}));
/**
* GET /api/admin/logs/frontend-errors
* Hent fejl fra frontend
*/
router.get('/frontend-errors', asyncHandler(async (req, res) => {
const { startDate, endDate, limit = 100, offset = 0 } = req.query;
const conditions = ['category IN (?, ?, ?)'];
const params = ['FRONTEND', 'FRONTEND_NETWORK', 'FRONTEND_SESSION'];
if (startDate) {
conditions.push('created_at >= ?');
params.push(startDate);
}
if (endDate) {
conditions.push('created_at <= ?');
params.push(endDate);
}
const whereClause = `WHERE ${conditions.join(' AND ')}`;
// Get total count
const [countResult] = await db.query(
`SELECT COUNT(*) as total FROM system_logs ${whereClause}`,
params
);
// Get logs
const [logs] = await db.query(
`SELECT
id, created_at, level, context, category, message, error_code,
user_id, user_email, metadata
FROM system_logs
${whereClause}
ORDER BY created_at DESC
LIMIT ? OFFSET ?`,
[...params, parseInt(limit), parseInt(offset)]
);
logger.logInfo('Admin.GetFrontendErrors', 'Frontend errors retrieved', {
category: 'ADMIN',
req,
metadata: { count: logs.length }
});
res.json({
success: true,
data: logs,
pagination: {
total: countResult[0].total,
limit: parseInt(limit),
offset: parseInt(offset)
}
});
}));
module.exports = router;