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.

189 lines (188 loc) • 7.34 kB
"use strict"; /** * @fileoverview Create Quiz Command - Application Layer * @version 1.0.0 * @since 2025-07-29 * @lastUpdated 2025-07-29 * @module CreateQuizCommand Application Command * @description Command for creating a new quiz with questions. * Part of CQRS pattern implementation in the application layer. * @contributors Claude Code Agent * @dependencies Domain entities and value objects * @requirements REQ-ARCH-001 (Clean Architecture Application Layer) * @testCoverage Unit tests for command validation and execution */ Object.defineProperty(exports, "__esModule", { value: true }); exports.InvalidCommandError = exports.CreateQuizCommand = void 0; const QuizId_1 = require("../../domain/value-objects/QuizId"); /** * Create Quiz Command * * @description Command that encapsulates all data needed to create a new quiz. * Commands are immutable data structures that represent user intent. * This command will be processed by CreateQuizCommandHandler. * * @example * ```typescript * const command = new CreateQuizCommand({ * title: "JavaScript Fundamentals", * description: "Test your JavaScript knowledge", * category: "programming", * difficulty: "medium", * questions: [ * { * question: "What is JavaScript?", * options: ["Language", "Framework", "Library"], * correctAnswer: "Language" * } * ] * }); * ``` * * @since 2025-07-29 * @author Claude Code Agent * @requirements REQ-ARCH-001 (Clean Architecture Application Layer) */ class CreateQuizCommand { constructor(data) { this.data = data; this.commandId = QuizId_1.QuizId.generate().value; // Reuse QuizId generation for command IDs this.timestamp = new Date(); // Validate command data on construction this.validate(); } /** * Get the expected quiz ID that will be generated * This allows for deterministic ID generation if needed */ get expectedQuizId() { return QuizId_1.QuizId.generate(); } /** * Validate command data */ validate() { var _a, _b; if (!((_a = this.data.title) === null || _a === void 0 ? void 0 : _a.trim())) { throw new InvalidCommandError('Quiz title is required'); } if (this.data.title.length > 255) { throw new InvalidCommandError('Quiz title cannot exceed 255 characters'); } if (!((_b = this.data.category) === null || _b === void 0 ? void 0 : _b.trim())) { throw new InvalidCommandError('Quiz category is required'); } if (!['easy', 'medium', 'hard', 'expert'].includes(this.data.difficulty)) { throw new InvalidCommandError('Invalid quiz difficulty level'); } if (this.data.timeLimit !== undefined && this.data.timeLimit < 1) { throw new InvalidCommandError('Time limit must be at least 1 minute'); } if (!Array.isArray(this.data.questions) || this.data.questions.length === 0) { throw new InvalidCommandError('Quiz must have at least one question'); } if (this.data.questions.length > 100) { throw new InvalidCommandError('Quiz cannot have more than 100 questions'); } // Validate each question this.data.questions.forEach((question, index) => { this.validateQuestion(question, index + 1); }); // Check for duplicate question texts const questionTexts = this.data.questions.map(q => q.question.toLowerCase().trim()); const uniqueTexts = new Set(questionTexts); if (questionTexts.length !== uniqueTexts.size) { throw new InvalidCommandError('Quiz cannot contain duplicate questions'); } } /** * Validate individual question data */ validateQuestion(question, questionNumber) { var _a, _b; const prefix = `Question ${questionNumber}`; if (!((_a = question.question) === null || _a === void 0 ? void 0 : _a.trim())) { throw new InvalidCommandError(`${prefix}: Question text is required`); } if (question.question.length > 1000) { throw new InvalidCommandError(`${prefix}: Question text cannot exceed 1000 characters`); } if (!Array.isArray(question.options) || question.options.length < 2) { throw new InvalidCommandError(`${prefix}: Must have at least 2 options`); } if (question.options.length > 10) { throw new InvalidCommandError(`${prefix}: Cannot have more than 10 options`); } // Validate options are not empty if (question.options.some(option => !(option === null || option === void 0 ? void 0 : option.trim()))) { throw new InvalidCommandError(`${prefix}: Options cannot be empty`); } // Check for duplicate options const uniqueOptions = new Set(question.options.map(opt => opt.trim().toLowerCase())); if (uniqueOptions.size !== question.options.length) { throw new InvalidCommandError(`${prefix}: Cannot have duplicate options`); } if (!((_b = question.correctAnswer) === null || _b === void 0 ? void 0 : _b.trim())) { throw new InvalidCommandError(`${prefix}: Correct answer is required`); } if (!question.options.includes(question.correctAnswer)) { throw new InvalidCommandError(`${prefix}: Correct answer must be one of the provided options`); } if (question.explanation && question.explanation.length > 2000) { throw new InvalidCommandError(`${prefix}: Explanation cannot exceed 2000 characters`); } if (question.points !== undefined) { if (question.points < 0) { throw new InvalidCommandError(`${prefix}: Points cannot be negative`); } if (question.points > 100) { throw new InvalidCommandError(`${prefix}: Points cannot exceed 100`); } } } /** * Convert to plain object for serialization/logging */ toPlainObject() { return { commandId: this.commandId, timestamp: this.timestamp.toISOString(), commandType: 'CreateQuizCommand', data: { ...this.data, // Don't include questions in summary for logging questionCount: this.data.questions.length, questionsIncluded: true, }, }; } /** * Create a summary for logging (without detailed question data) */ toSummary() { return `CreateQuizCommand(${this.commandId}): "${this.data.title}" with ${this.data.questions.length} questions in category "${this.data.category}"`; } /** * Check if command data has changed since creation */ isValid() { try { this.validate(); return true; } catch (_a) { return false; } } } exports.CreateQuizCommand = CreateQuizCommand; /** * Command validation error */ class InvalidCommandError extends Error { constructor(message) { super(message); this.name = 'InvalidCommandError'; } } exports.InvalidCommandError = InvalidCommandError;