UNPKG

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
"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; }