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.

179 lines (178 loc) โ€ข 7.83 kB
"use strict"; /** * @moduleName: Database Configuration * @version: 1.0.0 * @since: 2025-07-22 * @lastUpdated: 2025-07-22 * @projectSummary: TypeORM database configuration for quiz management system with SQLite default and easy migration path * @techStack: TypeORM, SQLite, TypeScript, Repository Pattern * @dependency: typeorm, sqlite3, reflect-metadata * @interModuleDependency: ./entities (Quiz, Question, Result entities) * @requirementsTraceability: * {@link Requirements.REQ_DATA_001} (TypeORM Quiz Schema) * {@link Requirements.REQ_DATA_003} (Automated Data Validation) * @briefDescription: Centralized database configuration supporting multiple environments with SQLite for development/testing and easy PostgreSQL migration path * @contributors: Jorge the Database Architect * @vulnerabilitiesAssessment: * - Connection string validation implemented * - Environment-based configuration isolation * - SQL injection prevention through ORM * - Database file security considerations documented */ Object.defineProperty(exports, "__esModule", { value: true }); exports.initializeDatabase = initializeDatabase; exports.initializeDatabaseSafe = initializeDatabaseSafe; exports.createTestDatabase = createTestDatabase; exports.getDataSource = getDataSource; exports.getSafeDataSource = getSafeDataSource; exports.isDatabaseAvailable = isDatabaseAvailable; exports.logConnectionPoolStatus = logConnectionPoolStatus; exports.executeWithLogging = executeWithLogging; const typeorm_1 = require("typeorm"); const Question_1 = require("./entities/Question"); const Quiz_1 = require("./entities/Quiz"); const QuizResult_1 = require("./entities/QuizResult"); const Result_1 = require("./entities/Result"); // SQLite configuration factory function createSQLiteDataSource() { return new typeorm_1.DataSource({ type: 'sqlite', database: process.env.DATABASE_PATH || './data/quiz.db', synchronize: process.env.NODE_ENV !== 'production', // Auto-sync in development logging: process.env.NODE_ENV === 'development', entities: [Quiz_1.Quiz, Question_1.Question, Result_1.Result, QuizResult_1.QuizResult], migrations: ['src/database/migrations/*.ts'], subscribers: ['src/database/subscribers/*.ts'], }); } // PostgreSQL configuration factory (for future use) function createPostgreSQLDataSource() { return new typeorm_1.DataSource({ type: 'postgres', host: process.env.DB_HOST || 'localhost', port: parseInt(process.env.DB_PORT || '5432'), username: process.env.DB_USERNAME || 'quiz_user', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'quiz_db', synchronize: false, // Always use migrations in PostgreSQL logging: process.env.NODE_ENV === 'development', entities: [Quiz_1.Quiz, Question_1.Question, Result_1.Result, QuizResult_1.QuizResult], migrations: ['dist/database/migrations/*'], subscribers: ['dist/database/subscribers/*'], }); } // Global data source instance let dataSource = null; // Database initialization function with timeout and enhanced logging async function initializeDatabase() { const startTime = Date.now(); console.log('๐Ÿ”„ [DB-INIT] Starting database initialization...'); if (dataSource && dataSource.isInitialized) { console.log('โœ… [DB-INIT] Database already initialized'); return dataSource; } // For now, always use SQLite dataSource = createSQLiteDataSource(); try { // Add connection timeout protection const connectionPromise = dataSource.initialize(); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Database connection timeout after 10s')), 10000)); console.log('๐Ÿ”— [DB-INIT] Attempting connection...'); await Promise.race([connectionPromise, timeoutPromise]); const duration = Date.now() - startTime; console.log(`โœ… [DB-INIT] Database connected in ${duration}ms`); console.log(`๐Ÿ“Š [DB-INIT] Connection state: ${dataSource.isInitialized}`); console.log(`๐Ÿ“Š [DB-INIT] Database: ${dataSource.options.database}`); // Log connection pool status logConnectionPoolStatus(); return dataSource; } catch (error) { const duration = Date.now() - startTime; console.error(`โŒ [DB-INIT] Failed after ${duration}ms:`, error); // Cleanup failed connection if (dataSource) { try { await dataSource.destroy(); console.log('๐Ÿงน [DB-INIT] Cleaned up failed connection'); } catch (cleanupError) { console.error('โš ๏ธ [DB-INIT] Cleanup failed:', cleanupError); } dataSource = null; } throw error; } } // Safe database initialization that doesn't throw async function initializeDatabaseSafe() { try { return await initializeDatabase(); } catch (error) { console.warn('Database initialization failed - continuing without database:', error instanceof Error ? error.message : error); return null; } } // Helper function for testing async function createTestDatabase() { const testDataSource = new typeorm_1.DataSource({ type: 'sqlite', database: ':memory:', // In-memory for testing synchronize: true, logging: false, entities: [Quiz_1.Quiz, Question_1.Question, Result_1.Result, QuizResult_1.QuizResult], }); await testDataSource.initialize(); // CRITICAL FIX: Enable foreign key constraints in test database await testDataSource.query('PRAGMA foreign_keys = ON'); console.log('โœ… Foreign key constraints enabled for test database'); return testDataSource; } // Get the current data source (mainly for repositories) function getDataSource() { if (!dataSource) { throw new Error('Database not initialized. Call initializeDatabase() first.'); } return dataSource; } // Safe data source getter that doesn't throw function getSafeDataSource() { return dataSource && dataSource.isInitialized ? dataSource : null; } // Check if database is available function isDatabaseAvailable() { return dataSource !== null && dataSource.isInitialized; } // Connection pool health monitoring function logConnectionPoolStatus() { if (!(dataSource === null || dataSource === void 0 ? void 0 : dataSource.isInitialized)) { console.log('๐Ÿ”Œ [DB-POOL] Database not initialized'); return; } console.log(`๐Ÿ“Š [DB-POOL] Status: ${dataSource.isInitialized ? 'Connected' : 'Disconnected'}`); console.log(`๐Ÿ“Š [DB-POOL] Database: ${dataSource.options.database}`); // Memory usage const memUsage = process.memoryUsage(); console.log(`๐Ÿ“Š [DB-POOL] Memory: ${Math.round(memUsage.heapUsed / 1024 / 1024)}MB heap`); } // Enhanced query execution wrapper with timeout async function executeWithLogging(operation, queryFn) { const operationId = `${operation}-${Date.now()}`; const startTime = Date.now(); console.log(`๐Ÿš€ [DB-QUERY] Starting: ${operationId}`); try { // Add query timeout const resultPromise = queryFn(); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Query timeout: ${operation}`)), 15000)); const result = await Promise.race([resultPromise, timeoutPromise]); const duration = Date.now() - startTime; console.log(`โœ… [DB-QUERY] Completed: ${operationId} in ${duration}ms`); return result; } catch (error) { const duration = Date.now() - startTime; console.error(`โŒ [DB-QUERY] Failed: ${operationId} after ${duration}ms:`, error); throw error; } }