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