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