318 lines
7.7 KiB
JavaScript
318 lines
7.7 KiB
JavaScript
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
|
||
};
|