require('dotenv').config(); /** * Environment Configuration Validator * Validates all required environment variables and their formats */ const REQUIRED_VARS = { // Server Configuration NODE_ENV: { required: true, type: 'string', allowedValues: ['development', 'test', 'production'], default: 'development' }, PORT: { required: true, type: 'number', min: 1000, max: 65535, default: 3000 }, API_PREFIX: { required: true, type: 'string', default: '/api' }, // Database Configuration DB_HOST: { required: true, type: 'string', default: 'localhost' }, DB_PORT: { required: true, type: 'number', default: 3306 }, DB_NAME: { required: true, type: 'string', minLength: 3 }, DB_USER: { required: true, type: 'string' }, DB_PASSWORD: { required: false, // Optional for development type: 'string', warning: 'Database password is not set. This is only acceptable in development.' }, DB_DIALECT: { required: true, type: 'string', allowedValues: ['mysql', 'postgres', 'sqlite', 'mssql'], default: 'mysql' }, // Database Pool Configuration DB_POOL_MAX: { required: false, type: 'number', default: 10 }, DB_POOL_MIN: { required: false, type: 'number', default: 0 }, DB_POOL_ACQUIRE: { required: false, type: 'number', default: 30000 }, DB_POOL_IDLE: { required: false, type: 'number', default: 10000 }, // JWT Configuration JWT_SECRET: { required: true, type: 'string', minLength: 32, warning: 'JWT_SECRET should be a long, random string (64+ characters recommended)' }, JWT_EXPIRE: { required: true, type: 'string', default: '24h' }, // Rate Limiting RATE_LIMIT_WINDOW_MS: { required: false, type: 'number', default: 900000 }, RATE_LIMIT_MAX_REQUESTS: { required: false, type: 'number', default: 100 }, // CORS Configuration CORS_ORIGIN: { required: true, type: 'string', default: 'http://localhost:4200' }, // Guest Configuration GUEST_SESSION_EXPIRE_HOURS: { required: false, type: 'number', default: 24 }, GUEST_MAX_QUIZZES: { required: false, type: 'number', default: 3 }, // Logging LOG_LEVEL: { required: false, type: 'string', allowedValues: ['error', 'warn', 'info', 'debug'], default: 'info' } }; class ValidationError extends Error { constructor(variable, message) { super(`${variable}: ${message}`); this.variable = variable; } } /** * Validate a single environment variable */ function validateVariable(name, config) { const value = process.env[name]; const errors = []; const warnings = []; // Check if required and missing if (config.required && !value) { if (config.default !== undefined) { warnings.push(`${name} is not set. Using default: ${config.default}`); process.env[name] = String(config.default); return { errors, warnings }; } errors.push(`${name} is required but not set`); return { errors, warnings }; } // If not set and not required, use default if available if (!value && config.default !== undefined) { process.env[name] = String(config.default); return { errors, warnings }; } // Skip further validation if not set and not required if (!value && !config.required) { return { errors, warnings }; } // Type validation if (config.type === 'number') { const numValue = Number(value); if (isNaN(numValue)) { errors.push(`${name} must be a number. Got: ${value}`); } else { if (config.min !== undefined && numValue < config.min) { errors.push(`${name} must be >= ${config.min}. Got: ${numValue}`); } if (config.max !== undefined && numValue > config.max) { errors.push(`${name} must be <= ${config.max}. Got: ${numValue}`); } } } // String length validation if (config.type === 'string' && config.minLength && value.length < config.minLength) { errors.push(`${name} must be at least ${config.minLength} characters. Got: ${value.length}`); } // Allowed values validation if (config.allowedValues && !config.allowedValues.includes(value)) { errors.push(`${name} must be one of: ${config.allowedValues.join(', ')}. Got: ${value}`); } // Custom warnings if (config.warning && value) { const needsWarning = config.minLength ? value.length < 64 : true; if (needsWarning) { warnings.push(`${name}: ${config.warning}`); } } // Warning for missing optional password in production if (name === 'DB_PASSWORD' && !value && process.env.NODE_ENV === 'production') { errors.push('DB_PASSWORD is required in production'); } return { errors, warnings }; } /** * Validate all environment variables */ function validateEnvironment() { console.log('\nšŸ” Validating Environment Configuration...\n'); const allErrors = []; const allWarnings = []; let validCount = 0; // Validate each variable Object.entries(REQUIRED_VARS).forEach(([name, config]) => { const { errors, warnings } = validateVariable(name, config); if (errors.length > 0) { allErrors.push(...errors); console.log(`āŒ ${name}: INVALID`); errors.forEach(err => console.log(` ${err}`)); } else if (warnings.length > 0) { allWarnings.push(...warnings); console.log(`āš ļø ${name}: WARNING`); warnings.forEach(warn => console.log(` ${warn}`)); validCount++; } else { console.log(`āœ… ${name}: OK`); validCount++; } }); // Summary console.log('\n' + '='.repeat(60)); console.log('VALIDATION SUMMARY'); console.log('='.repeat(60)); console.log(`Total Variables: ${Object.keys(REQUIRED_VARS).length}`); console.log(`āœ… Valid: ${validCount}`); console.log(`āš ļø Warnings: ${allWarnings.length}`); console.log(`āŒ Errors: ${allErrors.length}`); console.log('='.repeat(60)); if (allWarnings.length > 0) { console.log('\nāš ļø WARNINGS:'); allWarnings.forEach(warning => console.log(` - ${warning}`)); } if (allErrors.length > 0) { console.log('\nāŒ ERRORS:'); allErrors.forEach(error => console.log(` - ${error}`)); console.log('\nPlease fix the above errors before starting the application.\n'); return false; } console.log('\nāœ… All environment variables are valid!\n'); return true; } /** * Get current environment configuration summary */ function getEnvironmentSummary() { return { environment: process.env.NODE_ENV || 'development', server: { port: process.env.PORT || 3000, apiPrefix: process.env.API_PREFIX || '/api' }, database: { host: process.env.DB_HOST || 'localhost', port: process.env.DB_PORT || 3306, name: process.env.DB_NAME, dialect: process.env.DB_DIALECT || 'mysql', pool: { max: parseInt(process.env.DB_POOL_MAX) || 10, min: parseInt(process.env.DB_POOL_MIN) || 0 } }, security: { jwtExpire: process.env.JWT_EXPIRE || '24h', corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:4200' }, guest: { maxQuizzes: parseInt(process.env.GUEST_MAX_QUIZZES) || 3, sessionExpireHours: parseInt(process.env.GUEST_SESSION_EXPIRE_HOURS) || 24 } }; } // Run validation if called directly if (require.main === module) { const isValid = validateEnvironment(); if (isValid) { console.log('Current Configuration:'); console.log(JSON.stringify(getEnvironmentSummary(), null, 2)); console.log('\n'); } process.exit(isValid ? 0 : 1); } module.exports = { validateEnvironment, getEnvironmentSummary, REQUIRED_VARS };