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
JavaScript
;
/**
* @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>`;
}