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.

278 lines (277 loc) • 10.9 kB
"use strict"; /** * @fileoverview Create Quiz Command Handler - Application Layer * @version 1.0.0 * @since 2025-07-29 * @lastUpdated 2025-07-29 * @module CreateQuizCommandHandler Application Service * @description Handles the creation of new quizzes with validation and business rules. * Orchestrates domain entities, services, and repositories. * @contributors Claude Code Agent * @dependencies Domain entities, repositories, services * @requirements REQ-ARCH-001 (Clean Architecture Application Layer) * @testCoverage Unit tests for command handling and business logic */ Object.defineProperty(exports, "__esModule", { value: true }); exports.InvalidCommandError = exports.DuplicateQuizTitleError = exports.QuizValidationFailedError = exports.QuizCreationError = exports.CreateQuizCommandHandler = void 0; const Quiz_1 = require("../../domain/entities/Quiz"); const Question_1 = require("../../domain/entities/Question"); /** * Create Quiz Command Handler * * @description Application service that handles quiz creation commands. * Orchestrates domain logic, validation, and persistence. * Follows Clean Architecture principles with dependency inversion. * * @example * ```typescript * const handler = new CreateQuizCommandHandler( * quizRepository, * questionRepository, * validationService, * eventBus * ); * * const result = await handler.handle(createQuizCommand); * ``` * * @since 2025-07-29 * @author Claude Code Agent * @requirements REQ-ARCH-001 (Clean Architecture Application Layer) */ class CreateQuizCommandHandler { constructor(quizRepository, questionRepository, validationService, eventBus) { this.quizRepository = quizRepository; this.questionRepository = questionRepository; this.validationService = validationService; this.eventBus = eventBus; } /** * Handle the create quiz command */ async handle(command, options = {}) { const { validateStrict = false, checkDuplicateTitle = true, publishImmediately = true, notifyAuthor = false, } = options; try { // 1. Pre-validation checks await this.performPreValidation(command, checkDuplicateTitle); // 2. Create domain entities const { quiz, questions } = await this.createDomainEntities(command); // 3. Validate quiz using domain service const validationResult = this.validationService.validateQuiz(quiz, { strictMode: validateStrict, educationalStandards: true, accessibilityCheck: true, contentQualityCheck: true, }); // 4. Handle validation results if (!validationResult.isValid) { throw new QuizValidationFailedError('Quiz validation failed', validationResult.errors, validationResult.warnings); } // 5. Persist entities within transaction await this.persistQuizWithQuestions(quiz, questions); // 6. Publish domain events await this.publishDomainEvents(quiz, questions); // 7. Handle post-creation tasks await this.handlePostCreationTasks(quiz, options); // 8. Return result return { quizId: quiz.id.value, title: quiz.title, questionsCount: questions.length, validationScore: validationResult.score, warnings: validationResult.warnings.map(w => w.message), createdAt: quiz.createdAt, }; } catch (error) { // Log error with command context console.error('Quiz creation failed:', { commandId: command.commandId, error: error instanceof Error ? error.message : 'Unknown error', command: command.toSummary(), }); // Re-throw with additional context if (error instanceof QuizCreationError) { throw error; } throw new QuizCreationError(`Failed to create quiz: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : new Error('Unknown error')); } } /** * Perform pre-validation checks */ async performPreValidation(command, checkDuplicateTitle) { // Check for duplicate title if requested if (checkDuplicateTitle) { const titleExists = await this.quizRepository.isTitleUnique(command.data.title); if (!titleExists) { throw new DuplicateQuizTitleError(command.data.title); } } // Additional business rule validations can go here // For example: author permissions, rate limiting, etc. } /** * Create domain entities from command data */ async createDomainEntities(command) { // Create questions first const questions = command.data.questions.map((questionData, index) => { if (questionData.options.length === 2 && (questionData.options.includes('True') || questionData.options.includes('False') || questionData.options.includes('true') || questionData.options.includes('false'))) { // Create true/false question const isTrue = questionData.correctAnswer.toLowerCase() === 'true'; return Question_1.Question.createTrueFalse({ questionText: questionData.question, correctAnswer: isTrue, explanation: questionData.explanation, order: index + 1, points: questionData.points || 1, }); } else { // Create multiple choice question return Question_1.Question.createMultipleChoice({ questionText: questionData.question, options: questionData.options, correctAnswer: questionData.correctAnswer, explanation: questionData.explanation, order: index + 1, points: questionData.points || 1, }); } }); // Create quiz with questions const quiz = Quiz_1.Quiz.create({ title: command.data.title, description: command.data.description, category: command.data.category, difficulty: command.data.difficulty, timeLimit: command.data.timeLimit, questions: questions, metadata: { ...command.data.metadata, createdByCommand: command.commandId, authorId: command.data.authorId, }, }); return { quiz, questions }; } /** * Persist quiz and questions within a transaction */ async persistQuizWithQuestions(quiz, questions) { // Use repository transaction to ensure atomicity await this.quizRepository.transaction(async (transactionalQuizRepo) => { // Save quiz first await transactionalQuizRepo.save(quiz); // Save questions within the same transaction await this.questionRepository.transaction(async (transactionalQuestionRepo) => { await transactionalQuestionRepo.saveMany(questions); }); }); } /** * Publish domain events from entities */ async publishDomainEvents(quiz, questions) { if (!this.eventBus) { return; // Event bus is optional } // Publish quiz events const quizEvents = quiz.getDomainEvents(); for (const event of quizEvents) { await this.eventBus.publish(event); } quiz.clearDomainEvents(); // Publish question events for (const question of questions) { const questionEvents = question.getDomainEvents(); for (const event of questionEvents) { await this.eventBus.publish(event); } question.clearDomainEvents(); } } /** * Handle post-creation tasks */ async handlePostCreationTasks(quiz, options) { var _a; const tasks = []; // Notification task if (options.notifyAuthor && ((_a = quiz.metadata) === null || _a === void 0 ? void 0 : _a.authorId)) { tasks.push(this.notifyAuthor(quiz.metadata.authorId, quiz)); } // Additional post-creation tasks can be added here // For example: analytics tracking, cache warming, etc. // Execute all tasks in parallel await Promise.allSettled(tasks); } /** * Notify author of successful quiz creation */ async notifyAuthor(authorId, quiz) { try { // This would integrate with a notification service console.log(`Notifying author ${authorId} of quiz creation: ${quiz.title}`); // In a real implementation, this would send an email, push notification, etc. // await this.notificationService.send({ // userId: authorId, // type: 'quiz_created', // data: { quizId: quiz.id.value, title: quiz.title } // }); } catch (error) { // Log but don't fail the command for notification errors console.warn('Failed to notify author:', error); } } /** * Validate command before processing */ validateCommand(command) { if (!command.isValid()) { throw new InvalidCommandError('Command validation failed'); } // Additional command-level validations can go here } } exports.CreateQuizCommandHandler = CreateQuizCommandHandler; /** * Error Types */ class QuizCreationError extends Error { constructor(message, cause) { super(message); this.cause = cause; this.name = 'QuizCreationError'; } } exports.QuizCreationError = QuizCreationError; class QuizValidationFailedError extends QuizCreationError { constructor(message, errors, warnings) { super(message); this.errors = errors; this.warnings = warnings; this.name = 'QuizValidationFailedError'; } } exports.QuizValidationFailedError = QuizValidationFailedError; class DuplicateQuizTitleError extends QuizCreationError { constructor(title) { super(`Quiz with title "${title}" already exists`); this.name = 'DuplicateQuizTitleError'; } } exports.DuplicateQuizTitleError = DuplicateQuizTitleError; class InvalidCommandError extends QuizCreationError { constructor(message) { super(message); this.name = 'InvalidCommandError'; } } exports.InvalidCommandError = InvalidCommandError;