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.

226 lines (223 loc) • 8.66 kB
"use strict"; /** * @fileoverview User-Facing Quiz Routes - Clean Architecture HTML interface routes * @version 2.0.0 * @since 2025-08-04 * @module UserQuizRoutes * @description Unified user-facing quiz routes serving HTML interfaces for quiz URLs. * Refactored from EnhancedQuizRoutes - Clean Architecture only, under 200 lines. * @contributors GitHub Copilot * @requirements REQ_ROUTES_001 (User-Facing Quiz URL System) * @testType integration * @maxLines 200 */ 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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createUserQuizRoutes = createUserQuizRoutes; const express = __importStar(require("express")); const path_1 = __importDefault(require("path")); const error_handler_1 = require("../../../shared/error-handler"); /** * Creates user-facing quiz routes that serve HTML interfaces. * * Provides: * - /quiz/start/:id - Direct quiz start * - /quiz/preview/:id - Quiz preview modal * - /quiz/embed/:id - Embeddable quiz * - /quiz/guest/:id - Guest mode interface */ function createUserQuizRoutes(config) { const router = express.Router(); const { controllerFactory } = config; /** * Direct Quiz Start - bypasses modal, auto-starts quiz * URL: /quiz/start/:id?autoStart=true&type=guest&timer=300 */ router.get('/start/:id', (0, error_handler_1.asyncHandler)(async (req, res) => { const { id: quizId } = req.params; const { autoStart = 'true', type = 'guest', timer } = req.query; if (!(controllerFactory === null || controllerFactory === void 0 ? void 0 : controllerFactory.hasCleanArchitecture())) { return res.status(503).send(getErrorHTML('Service unavailable - Clean Architecture required')); } try { // Fetch quiz data via Clean Architecture const quiz = await fetchQuizData(quizId, controllerFactory); if (!quiz) { return res.status(404).send(getErrorHTML('Quiz not found')); } const html = await generateQuizHTML({ quiz, mode: 'start', config: { autoStart: autoStart === 'true', userType: type, timer } }); res.send(html); } catch (error) { error_handler_1.Logger.error('Quiz start error:', error instanceof Error ? error : new Error(String(error))); res.status(500).send(getErrorHTML('Failed to load quiz')); } })); /** * Quiz Preview - shows modal interface * URL: /quiz/preview/:id?shared=true */ router.get('/preview/:id', (0, error_handler_1.asyncHandler)(async (req, res) => { const { id: quizId } = req.params; const { shared = 'false' } = req.query; if (!(controllerFactory === null || controllerFactory === void 0 ? void 0 : controllerFactory.hasCleanArchitecture())) { return res.status(503).send(getErrorHTML('Service unavailable')); } try { const html = await generateQuizHTML({ quizId, mode: 'preview', config: { showModal: true, fromShare: shared === 'true' } }); res.send(html); } catch (error) { error_handler_1.Logger.error('Quiz preview error:', error instanceof Error ? error : new Error(String(error))); res.status(500).send(getErrorHTML('Failed to load quiz preview')); } })); /** * Embedded Quiz - for iframe embedding * URL: /quiz/embed/:id?theme=minimal */ router.get('/embed/:id', (0, error_handler_1.asyncHandler)(async (req, res) => { const { id: quizId } = req.params; const embedConfig = req.query; const html = generateEmbedHTML(quizId, embedConfig); res.send(html); })); /** * Guest Mode - simplified interface * URL: /quiz/guest/:id */ router.get('/guest/:id', (0, error_handler_1.asyncHandler)(async (req, res) => { const { id: quizId } = req.params; const html = await generateQuizHTML({ quizId, mode: 'guest', config: { simplifiedUI: true, hideAdvanced: true } }); res.send(html); })); return router; } /** * Fetch quiz data using Clean Architecture */ async function fetchQuizData(quizId, controllerFactory) { // Mock request/response for controller const mockReq = { params: { id: quizId } }; const mockRes = { locals: {}, json: (data) => { mockRes.locals.data = data; }, status: () => mockRes }; const quizController = controllerFactory.createQuizController(); await quizController.getQuiz(mockReq, mockRes, () => { }); return mockRes.locals.data; } /** * Generate HTML for different quiz modes */ async function generateQuizHTML(options) { const baseHTML = await getBaseHTML(); const { quiz, quizId, mode, config } = options; const script = ` <script> window.QUIZ_CONFIG = { ${quiz ? `quiz: ${JSON.stringify(quiz)},` : `quizId: "${quizId}",`} mode: "${mode}", ...${JSON.stringify(config)} }; window.addEventListener('DOMContentLoaded', () => { const app = window.app; if (!app) return; switch("${mode}") { case 'start': if (${config.autoStart}) app.startQuizDirectly?.(window.QUIZ_CONFIG.quiz); break; case 'preview': app.showQuizModal?.(window.QUIZ_CONFIG.quizId); break; case 'guest': app.initGuestMode?.(window.QUIZ_CONFIG); break; } }); </script> `; const styles = mode === 'guest' ? '<style>.guest-mode .advanced-features { display: none; }</style>' : ''; const bodyClass = mode === 'guest' ? ' class="guest-mode"' : ''; return baseHTML .replace('</head>', `${script}${styles}</head>`) .replace('<body', `<body${bodyClass}`); } /** * Generate embed HTML (minimal standalone) */ function generateEmbedHTML(quizId, config) { return `<!DOCTYPE html> <html><head><title>Quiz Embed</title><link href="/output.css" rel="stylesheet"> <style>body{margin:0;padding:1rem;}.quiz-embed{max-width:100%;}</style></head> <body><div class="quiz-embed"><div id="quiz-container"></div></div> <script src="/dist/quiz-embed.js"></script> <script>window.embedQuiz('${quizId}', ${JSON.stringify(config)});</script></body></html>`; } /** * Get base HTML template */ async function getBaseHTML() { const fs = require('fs').promises; const htmlPath = path_1.default.join(__dirname, '../../../../public/index.html'); try { return await fs.readFile(htmlPath, 'utf-8'); } catch (_a) { return `<!DOCTYPE html><html><head><title>Quiz Platform</title><link href="/output.css" rel="stylesheet"></head><body><div id="app"></div><script src="/dist/app.js"></script></body></html>`; } } /** * Generate error HTML */ function getErrorHTML(message) { return `<!DOCTYPE html><html><head><title>Error</title></head><body><h1>Error</h1><p>${message}</p><a href="/">Return Home</a></body></html>`; }