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.
116 lines (104 loc) • 4.13 kB
text/typescript
/**
* @moduleName: Quiz API Service - Frontend HTTP Client
* @version: 2.0.0
* @since: 2025-07-21
* @lastUpdated: 2025-07-27
* @projectSummary: Enhanced MCP Quiz Server - Frontend service for REST API communication
* @techStack: TypeScript, Fetch API, Singleton Pattern, REST Client
* @dependency: Quiz, QuizResult interfaces from types/index.js, backend REST API
* @interModuleDependency: Backend API endpoints (/quiz/*, /results/*), error handling
* @requirementsTraceability:
* {@link Requirements.REQ_API_001} (HTTP Endpoints)
* {@link Requirements.REQ_INTEGRATION_001} (Frontend-Backend Communication)
* @briefDescription: Singleton service managing HTTP communication with backend quiz API endpoints
* @methods: getAllQuizzes, getQuiz, submitResult, createQuiz
* @contributors: GitHub Copilot, Backend Integration Team
* @examples:
* - const quizzes = await QuizService.getInstance().getAllQuizzes()
* - await quizService.submitResult(quizId, answers)
* - const quiz = await quizService.getQuiz('quiz-123')
* @vulnerabilitiesAssessment: HTTP-only client, CSRF protection via SameSite cookies, input validation on server
*/
import { Quiz, QuizResult } from '../types/index';
export class QuizService {
private static instance: QuizService;
static getInstance(): QuizService {
if (!QuizService.instance) {
QuizService.instance = new QuizService();
}
return QuizService.instance;
}
async getAllQuizzes(): Promise<Quiz[]> {
try {
const response = await fetch('/quiz/all');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Handle both legacy (array) and new (object with quizzes property) response formats
if (Array.isArray(data)) {
return data;
} else if (data.quizzes && Array.isArray(data.quizzes)) {
return data.quizzes;
} else {
console.error('Unexpected API response format:', data);
throw new Error('Invalid response format from server');
}
} catch (error) {
console.error('Failed to fetch quizzes:', error);
throw new Error('Failed to load quizzes');
}
}
async getQuizById(id: string): Promise<Quiz> {
try {
const response = await fetch(`/quiz/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Handle both legacy (direct quiz object) and new (object with quiz property) response formats
if (data.quiz) {
return data.quiz;
} else if (data.id) {
// Direct quiz object
return data;
} else {
console.error('Unexpected API response format for quiz:', data);
throw new Error('Invalid response format from server');
}
} catch (error) {
console.error(`Failed to fetch quiz ${id}:`, error);
throw new Error('Failed to load quiz');
}
}
async submitQuiz(quizId: string, answers: Record<string, string>): Promise<QuizResult> {
try {
const response = await fetch('/quiz/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ quizId, answers }),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to submit quiz:', error);
throw new Error('Failed to submit quiz');
}
}
async searchQuizzes(term: string): Promise<Quiz[]> {
try {
const response = await fetch(`/quiz/search/${encodeURIComponent(term)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Failed to search quizzes:', error);
throw new Error('Failed to search quizzes');
}
}
}