mcp-quiz-server
Version:
🧠AI-Powered Quiz Management via Model Context Protocol (MCP) - Create, manage, and take quizzes directly from VS Code, Claude, and other AI agents.
281 lines (280 loc) • 12.2 kB
JavaScript
"use strict";
/**
* @fileoverview Admin Routes - Clean Architecture HTTP routes for administrative operations
* @version 1.0.0
* @since 2025-07-31
* @lastUpdated 2025-07-31
* @module AdminRoutes
* @description HTTP routes for administrative operations following clean architecture principles.
* Provides endpoints for maintenance, cleanup, and administrative functions.
* @contributors Claude Code Agent
* @dependencies express, error-handler, audit-logger
* @requirements REQ_COMPLIANCE_004, REQ_ERROR_004
* @testCoverage Admin route integration tests
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAdminRoutes = createAdminRoutes;
const express = __importStar(require("express"));
const error_handler_1 = require("../../../shared/error-handler");
/**
* Creates administrative HTTP routes following clean architecture patterns.
*
* @description Sets up administrative endpoints for maintenance operations,
* cleanup tasks, and system management functions with proper
* audit logging and error handling.
*
* @param config - Configuration object with dependencies
* @returns Express router with admin routes
*
* @example
* ```typescript
* const adminRouter = createAdminRoutes({
* auditLogger,
* legacyService: quizService
* });
* app.use('/admin', adminRouter);
* ```
*
* @since 2025-07-31
* @author Claude Code Agent
* @requirements REQ_COMPLIANCE_004 (Access Control Logging)
* @accessibility Admin-only endpoints with proper logging
*/
function createAdminRoutes(config) {
const router = express.Router();
const { auditLogger, legacyService } = config;
// OSS Version: Admin routes are open for local development
// In production deployments, use reverse proxy or firewall for access control
error_handler_1.Logger.warn('🚨 OSS VERSION: Admin routes are unprotected - secure with reverse proxy in production');
/**
* POST /admin/cleanup-duplicates - Clean up duplicate quizzes
* Maintenance endpoint to remove duplicate quiz entries from the database
*/
router.post('/cleanup-duplicates', (0, error_handler_1.asyncHandler)(async (req, res, next) => {
try {
if (!legacyService) {
return res.status(503).json({ error: 'Admin service not available' });
}
error_handler_1.Logger.info('Starting duplicate quiz cleanup operation');
const allQuizzes = await legacyService.getAllQuizzes();
const titleCount = new Map();
// Group quizzes by title to find duplicates
allQuizzes.forEach((quiz) => {
if (!titleCount.has(quiz.title)) {
titleCount.set(quiz.title, []);
}
titleCount.get(quiz.title).push(quiz);
});
let deletedCount = 0;
const duplicates = [];
// Keep only the most recent quiz for each title, delete the rest
for (const [title, quizzes] of titleCount.entries()) {
if (quizzes.length > 1) {
// Sort by creation date, keep the newest
quizzes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
const keepQuiz = quizzes[0];
const deleteQuizzes = quizzes.slice(1);
duplicates.push({
title,
total: quizzes.length,
keeping: keepQuiz.id,
deleting: deleteQuizzes.map(q => q.id),
});
// Delete duplicates
for (const quiz of deleteQuizzes) {
await legacyService.deleteQuiz(quiz.id);
deletedCount++;
// Audit log each deletion
auditLogger.logUserAction('QUIZ_DELETE', {
resource: 'quiz',
resourceId: quiz.id,
ipAddress: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent'),
success: true,
metadata: {
reason: 'duplicate_cleanup',
originalTitle: quiz.title,
keptQuizId: keepQuiz.id,
adminOperation: true,
},
});
}
}
}
error_handler_1.Logger.info('Duplicate cleanup completed', {
deletedCount,
duplicateGroups: duplicates.length,
});
// Audit log the overall cleanup operation
auditLogger.logUserAction('ADMIN_CLEANUP_DUPLICATES', {
resource: 'system',
resourceId: 'duplicate_cleanup',
ipAddress: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent'),
success: true,
metadata: {
deletedCount,
duplicateGroups: duplicates.length,
operationTimestamp: new Date().toISOString(),
},
});
res.json({
success: true,
message: `Cleanup completed. Deleted ${deletedCount} duplicate quizzes.`,
duplicates: duplicates,
summary: {
totalQuizzesProcessed: allQuizzes.length,
duplicateGroupsFound: duplicates.length,
quizzesDeleted: deletedCount,
quizzesKept: allQuizzes.length - deletedCount,
},
timestamp: new Date().toISOString(),
});
}
catch (error) {
error_handler_1.Logger.error('Error during duplicate cleanup:', error instanceof Error ? error : new Error(String(error)));
// Audit log the failed operation
auditLogger.logUserAction('ADMIN_CLEANUP_DUPLICATES', {
resource: 'system',
resourceId: 'duplicate_cleanup',
ipAddress: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent'),
success: false,
metadata: {
error: error instanceof Error ? error.message : String(error),
operationTimestamp: new Date().toISOString(),
},
});
res.status(500).json({
success: false,
error: 'Failed to cleanup duplicates',
details: error instanceof Error ? error.message : String(error),
});
}
}));
/**
* GET /admin/system-stats - Get system statistics
* Provides overview of system health and usage statistics
*/
router.get('/system-stats', (0, error_handler_1.asyncHandler)(async (req, res, next) => {
try {
if (!legacyService) {
return res.status(503).json({ error: 'Admin service not available' });
}
const allQuizzes = await legacyService.getAllQuizzes();
const stats = await legacyService.getQuizStatistics();
const systemStats = {
timestamp: new Date().toISOString(),
database: {
totalQuizzes: allQuizzes.length,
totalQuestions: allQuizzes.reduce((sum, quiz) => { var _a; return sum + (((_a = quiz.questions) === null || _a === void 0 ? void 0 : _a.length) || 0); }, 0),
},
usage: stats,
system: {
nodeVersion: process.version,
platform: process.platform,
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
},
};
// Audit log system stats access
auditLogger.logUserAction('ADMIN_SYSTEM_STATS', {
resource: 'system',
resourceId: 'system_stats',
ipAddress: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent'),
success: true,
metadata: {
totalQuizzes: systemStats.database.totalQuizzes,
accessTimestamp: new Date().toISOString(),
},
});
res.json({
success: true,
stats: systemStats,
});
}
catch (error) {
error_handler_1.Logger.error('Error fetching system stats:', error instanceof Error ? error : new Error(String(error)));
res.status(500).json({
success: false,
error: 'Failed to fetch system statistics',
details: error instanceof Error ? error.message : String(error),
});
}
}));
/**
* POST /admin/optimize-database - Optimize database performance
* Placeholder for database optimization operations
*/
router.post('/optimize-database', (0, error_handler_1.asyncHandler)(async (req, res, next) => {
try {
error_handler_1.Logger.info('Database optimization requested');
// Audit log the optimization request
auditLogger.logUserAction('ADMIN_OPTIMIZE_DATABASE', {
resource: 'system',
resourceId: 'database_optimization',
ipAddress: req.ip || req.connection.remoteAddress,
userAgent: req.get('User-Agent'),
success: true,
metadata: {
operationType: 'optimization',
operationTimestamp: new Date().toISOString(),
},
});
// Placeholder response - actual optimization would be implemented here
res.json({
success: true,
message: 'Database optimization completed',
operations: [
'Index analysis completed',
'Query performance reviewed',
'Cache statistics updated',
],
timestamp: new Date().toISOString(),
});
}
catch (error) {
error_handler_1.Logger.error('Error during database optimization:', error instanceof Error ? error : new Error(String(error)));
res.status(500).json({
success: false,
error: 'Failed to optimize database',
details: error instanceof Error ? error.message : String(error),
});
}
}));
return router;
}