/** * Unified Application Configuration System * * This file serves as the single source of truth for all application configuration. * It supports multiple environments (development, staging, production) and provides * type-safe configuration for both frontend and backend components. * * Configuration sources (in order of precedence): * 1. Environment variables * 2. Environment-specific config files * 3. Default values */ export type Environment = 'development' | 'staging' | 'production' | 'test'; export interface UnifiedConfig { // Application Identity app: { name: string; version: string; environment: Environment; baseUrl: string; port: number; }; // Database Configuration database: { url: string; username: string; password: string; name: string; useMock: boolean; connectionTimeout: number; retryAttempts: number; }; // Container & Registry Configuration container: { registry: string; repository: string; tag: string; imageUrl: string; }; // Kubernetes Configuration kubernetes: { namespace: string; ingressHost: string; ingressClass: string; certIssuer: string; storageClass: string; storageSize: string; replicas: { frontend: number; database: number; }; resources: { frontend: { requests: { memory: string; cpu: string }; limits: { memory: string; cpu: string }; }; database: { requests: { memory: string; cpu: string }; limits: { memory: string; cpu: string }; }; }; }; // Authentication Configuration auth: { jwtSecret: string; jwtExpiresIn: string; refreshTokenExpiresIn: string; emailVerificationExpiresIn: string; bcryptRounds: number; }; // Email Configuration email: { provider: 'mailgun' | 'smtp' | 'console'; mailgun?: { apiKey: string; domain: string; baseUrl: string; }; smtp?: { host: string; port: number; secure: boolean; username: string; password: string; }; fromName: string; fromEmail: string; }; // OAuth Configuration oauth: { google?: { clientId: string; clientSecret?: string; }; github?: { clientId: string; clientSecret?: string; }; }; // Feature Flags features: { enableEmailVerification: boolean; enableOAuth: boolean; enableAdminInterface: boolean; enableMonitoring: boolean; enableMetrics: boolean; enableTracing: boolean; enableRateLimiting: boolean; enableSecurityHeaders: boolean; debugMode: boolean; hotReload: boolean; }; // Performance & Monitoring performance: { cacheTimeout: number; requestTimeout: number; maxConnections: number; enableCors: boolean; corsOrigin: string | string[]; }; // Logging Configuration logging: { level: 'debug' | 'info' | 'warn' | 'error'; format: 'json' | 'text'; enableTimestamp: boolean; enableColors: boolean; }; // Security Configuration security: { enableHttps: boolean; enableHsts: boolean; enableCsp: boolean; sessionSecret: string; rateLimitRequests: number; rateLimitWindow: number; }; } /** * Environment-specific configuration overrides */ const environmentConfigs: Record> = { development: { app: { name: 'rxminder', version: '1.0.0', environment: 'development', baseUrl: 'http://localhost:5173', port: 5173, }, database: { url: 'http://localhost:5984', username: 'admin', password: 'password', name: 'rxminder_dev', useMock: false, connectionTimeout: 5000, retryAttempts: 3, }, kubernetes: { namespace: 'rxminder-dev', ingressHost: 'rxminder-dev.local', ingressClass: 'nginx', certIssuer: 'selfsigned', storageClass: 'standard', storageSize: '1Gi', replicas: { frontend: 1, database: 1, }, resources: { frontend: { requests: { memory: '128Mi', cpu: '50m' }, limits: { memory: '256Mi', cpu: '200m' }, }, database: { requests: { memory: '256Mi', cpu: '100m' }, limits: { memory: '512Mi', cpu: '500m' }, }, }, }, features: { enableEmailVerification: false, enableOAuth: false, enableAdminInterface: true, enableMonitoring: false, enableMetrics: false, enableTracing: false, enableRateLimiting: false, enableSecurityHeaders: false, debugMode: true, hotReload: true, }, logging: { level: 'debug', format: 'text', enableTimestamp: true, enableColors: true, }, performance: { cacheTimeout: 300, // 5 minutes requestTimeout: 30000, maxConnections: 100, enableCors: true, corsOrigin: '*', }, }, staging: { app: { name: 'rxminder', version: '1.0.0', environment: 'staging', baseUrl: 'https://staging.rxminder.com', port: 80, }, kubernetes: { namespace: 'rxminder-staging', ingressHost: 'staging.rxminder.com', ingressClass: 'nginx', certIssuer: 'letsencrypt-staging', storageClass: 'ssd', storageSize: '5Gi', replicas: { frontend: 2, database: 1, }, resources: { frontend: { requests: { memory: '256Mi', cpu: '100m' }, limits: { memory: '512Mi', cpu: '500m' }, }, database: { requests: { memory: '512Mi', cpu: '200m' }, limits: { memory: '1Gi', cpu: '1000m' }, }, }, }, features: { enableEmailVerification: true, enableOAuth: true, enableAdminInterface: true, enableMonitoring: true, enableMetrics: true, enableTracing: false, enableRateLimiting: true, enableSecurityHeaders: true, debugMode: false, hotReload: false, }, logging: { level: 'info', format: 'json', enableTimestamp: true, enableColors: false, }, performance: { cacheTimeout: 1800, // 30 minutes requestTimeout: 60000, maxConnections: 150, enableCors: true, corsOrigin: 'https://staging.rxminder.com', }, }, production: { app: { name: 'rxminder', version: '1.0.0', environment: 'production', baseUrl: 'https://rxminder.com', port: 80, }, database: { url: 'http://rxminder-couchdb-service:5984', username: 'admin', password: 'secure_password', name: 'rxminder_prod', useMock: false, connectionTimeout: 10000, retryAttempts: 5, }, kubernetes: { namespace: 'rxminder-prod', ingressHost: 'rxminder.com', ingressClass: 'nginx', certIssuer: 'letsencrypt-prod', storageClass: 'ssd', storageSize: '10Gi', replicas: { frontend: 3, database: 1, }, resources: { frontend: { requests: { memory: '256Mi', cpu: '100m' }, limits: { memory: '512Mi', cpu: '500m' }, }, database: { requests: { memory: '512Mi', cpu: '200m' }, limits: { memory: '1Gi', cpu: '1000m' }, }, }, }, features: { enableEmailVerification: true, enableOAuth: true, enableAdminInterface: true, enableMonitoring: true, enableMetrics: true, enableTracing: true, enableRateLimiting: true, enableSecurityHeaders: true, debugMode: false, hotReload: false, }, logging: { level: 'warn', format: 'json', enableTimestamp: true, enableColors: false, }, performance: { cacheTimeout: 3600, // 1 hour requestTimeout: 120000, maxConnections: 200, enableCors: true, corsOrigin: 'https://rxminder.com', }, security: { enableHttps: true, enableHsts: true, enableCsp: true, sessionSecret: 'secure-session-secret-key', rateLimitRequests: 100, rateLimitWindow: 900000, // 15 minutes }, }, test: { app: { name: 'rxminder', version: '1.0.0', environment: 'test', baseUrl: 'http://localhost:3000', port: 3000, }, database: { url: 'http://localhost:5984', username: 'test', password: 'test', name: 'rxminder_test', useMock: true, connectionTimeout: 3000, retryAttempts: 1, }, features: { enableEmailVerification: false, enableOAuth: false, enableAdminInterface: false, enableMonitoring: false, enableMetrics: false, enableTracing: false, enableRateLimiting: false, enableSecurityHeaders: false, debugMode: true, hotReload: false, }, logging: { level: 'error', format: 'text', enableTimestamp: false, enableColors: false, }, email: { provider: 'console' as const, fromName: 'Test RxMinder', fromEmail: 'test@rxminder.local', }, }, }; /** * Default configuration values */ const defaultConfig: UnifiedConfig = { app: { name: 'RxMinder', version: '1.0.0', environment: 'development', baseUrl: 'http://localhost:5173', port: 5173, }, database: { url: 'http://localhost:5984', username: 'admin', password: 'changeme', name: 'meds_app', useMock: false, connectionTimeout: 30000, retryAttempts: 3, }, container: { registry: 'gitea-http.taildb3494.ts.net', repository: 'will/meds', tag: 'latest', imageUrl: '', // Will be computed }, kubernetes: { namespace: 'rxminder', ingressHost: 'rxminder.local', ingressClass: 'nginx', certIssuer: 'letsencrypt-prod', storageClass: 'longhorn', storageSize: '1Gi', replicas: { frontend: 1, database: 1, }, resources: { frontend: { requests: { memory: '128Mi', cpu: '50m' }, limits: { memory: '256Mi', cpu: '200m' }, }, database: { requests: { memory: '256Mi', cpu: '100m' }, limits: { memory: '512Mi', cpu: '500m' }, }, }, }, auth: { jwtSecret: 'your-super-secret-jwt-key-change-in-production', jwtExpiresIn: '1h', refreshTokenExpiresIn: '7d', emailVerificationExpiresIn: '24h', bcryptRounds: 12, }, email: { provider: 'console', mailgun: { apiKey: '', domain: '', baseUrl: 'https://api.mailgun.net/v3', }, fromName: 'RxMinder', fromEmail: 'noreply@rxminder.com', }, oauth: { google: { clientId: '', }, github: { clientId: '', }, }, features: { enableEmailVerification: true, enableOAuth: true, enableAdminInterface: true, enableMonitoring: false, enableMetrics: false, enableTracing: false, enableRateLimiting: false, enableSecurityHeaders: false, debugMode: false, hotReload: false, }, performance: { cacheTimeout: 1800, requestTimeout: 30000, maxConnections: 100, enableCors: true, corsOrigin: '*', }, logging: { level: 'info', format: 'json', enableTimestamp: true, enableColors: false, }, security: { enableHttps: false, enableHsts: false, enableCsp: false, sessionSecret: 'your-session-secret-change-in-production', rateLimitRequests: 100, rateLimitWindow: 900000, // 15 minutes }, }; /** * Get environment variable with fallback * Reads from .env file via process.env or import.meta.env */ function getEnvVar(key: string, fallback: string = ''): string { // First try process.env (Node.js, includes .env via dotenv or build process) if (typeof process !== 'undefined' && process.env && process.env[key]) { return process.env[key]!; } // Then try import.meta.env (Vite/browser, includes .env via Vite's loadEnv) if ( typeof import.meta !== 'undefined' && import.meta.env && import.meta.env[key] ) { return import.meta.env[key]; } // Use fallback if not found return fallback; } /** * Get boolean environment variable */ function getBoolEnvVar(key: string, fallback: boolean = false): boolean { const value = getEnvVar(key); if (value === '') return fallback; return value.toLowerCase() === 'true' || value === '1'; } /** * Get number environment variable */ function getNumberEnvVar(key: string, fallback: number = 0): number { const value = getEnvVar(key); if (value === '') return fallback; const parsed = parseInt(value, 10); return isNaN(parsed) ? fallback : parsed; } /** * Load configuration from environment variables (.env file) * This provides environment-specific overrides for the unified config */ function loadFromEnvironment(config: UnifiedConfig): UnifiedConfig { return { ...config, app: { ...config.app, name: getEnvVar('APP_NAME', config.app.name), version: getEnvVar('APP_VERSION', config.app.version), baseUrl: getEnvVar('APP_BASE_URL', config.app.baseUrl), port: getNumberEnvVar('PORT', config.app.port), }, database: { ...config.database, url: getEnvVar('VITE_COUCHDB_URL') || getEnvVar('COUCHDB_URL', config.database.url), username: getEnvVar('VITE_COUCHDB_USER') || getEnvVar('COUCHDB_USER', config.database.username), password: getEnvVar('VITE_COUCHDB_PASSWORD') || getEnvVar('COUCHDB_PASSWORD', config.database.password), name: getEnvVar('COUCHDB_DATABASE_NAME', config.database.name), useMock: getBoolEnvVar('USE_MOCK_DB', config.database.useMock), }, container: { ...config.container, registry: getEnvVar('CONTAINER_REGISTRY', config.container.registry), repository: getEnvVar( 'CONTAINER_REPOSITORY', config.container.repository ), tag: getEnvVar('CONTAINER_TAG', config.container.tag), }, kubernetes: { ...config.kubernetes, ingressHost: getEnvVar('INGRESS_HOST', config.kubernetes.ingressHost), storageClass: getEnvVar('STORAGE_CLASS', config.kubernetes.storageClass), storageSize: getEnvVar('STORAGE_SIZE', config.kubernetes.storageSize), }, auth: { ...config.auth, jwtSecret: getEnvVar('JWT_SECRET', config.auth.jwtSecret), jwtExpiresIn: getEnvVar('JWT_EXPIRES_IN', config.auth.jwtExpiresIn), }, security: { ...config.security, sessionSecret: getEnvVar('SESSION_SECRET', config.security.sessionSecret), }, email: { ...config.email, provider: (getEnvVar('EMAIL_PROVIDER', config.email.provider) as | 'mailgun' | 'smtp' | 'console') || config.email.provider, mailgun: { ...config.email.mailgun!, apiKey: getEnvVar('VITE_MAILGUN_API_KEY') || getEnvVar('MAILGUN_API_KEY', config.email.mailgun?.apiKey || ''), domain: getEnvVar('VITE_MAILGUN_DOMAIN') || getEnvVar('MAILGUN_DOMAIN', config.email.mailgun?.domain || ''), }, fromName: getEnvVar('VITE_MAILGUN_FROM_NAME') || getEnvVar('MAILGUN_FROM_NAME', config.email.fromName), fromEmail: getEnvVar('VITE_MAILGUN_FROM_EMAIL') || getEnvVar('MAILGUN_FROM_EMAIL', config.email.fromEmail), }, oauth: { google: { clientId: getEnvVar('VITE_GOOGLE_CLIENT_ID') || getEnvVar('GOOGLE_CLIENT_ID', config.oauth.google?.clientId || ''), clientSecret: getEnvVar('GOOGLE_CLIENT_SECRET'), }, github: { clientId: getEnvVar('VITE_GITHUB_CLIENT_ID') || getEnvVar('GITHUB_CLIENT_ID', config.oauth.github?.clientId || ''), clientSecret: getEnvVar('GITHUB_CLIENT_SECRET'), }, }, features: { ...config.features, enableEmailVerification: getBoolEnvVar( 'ENABLE_EMAIL_VERIFICATION', config.features.enableEmailVerification ), enableOAuth: getBoolEnvVar('ENABLE_OAUTH', config.features.enableOAuth), enableAdminInterface: getBoolEnvVar( 'ENABLE_ADMIN_INTERFACE', config.features.enableAdminInterface ), enableMonitoring: getBoolEnvVar( 'ENABLE_MONITORING', config.features.enableMonitoring ), enableMetrics: getBoolEnvVar( 'ENABLE_METRICS', config.features.enableMetrics ), debugMode: getBoolEnvVar('DEBUG_MODE', config.features.debugMode), }, logging: { ...config.logging, level: (getEnvVar('LOG_LEVEL', config.logging.level) as | 'debug' | 'info' | 'warn' | 'error') || config.logging.level, }, }; } /** * Deep merge two objects */ function deepMerge(target: T, source: Partial): T { const result = { ...target }; for (const key in source) { if ( source[key] && typeof source[key] === 'object' && !Array.isArray(source[key]) ) { result[key as keyof T] = deepMerge( result[key as keyof T], source[key] as Partial ); } else if (source[key] !== undefined) { result[key as keyof T] = source[key] as T[keyof T]; } } return result; } /** * Validate configuration */ function validateConfig(config: UnifiedConfig): void { const errors: string[] = []; const warnings: string[] = []; // Production-specific validations (only when NODE_ENV is explicitly production) const isActualProduction = getEnvVar('NODE_ENV') === 'production'; if (config.app.environment === 'production' && isActualProduction) { if (config.auth.jwtSecret === defaultConfig.auth.jwtSecret) { errors.push('JWT_SECRET must be changed in production'); } if ( config.security.sessionSecret === defaultConfig.security.sessionSecret ) { errors.push('SESSION_SECRET must be changed in production'); } if (config.database.password === defaultConfig.database.password) { warnings.push( 'Consider changing default database password in production' ); } } // Email configuration validation if ( config.features.enableEmailVerification && config.email.provider === 'mailgun' ) { if (!config.email.mailgun?.apiKey) { warnings.push( 'MAILGUN_API_KEY not configured - email will fall back to console' ); } if (!config.email.mailgun?.domain) { warnings.push('MAILGUN_DOMAIN not configured - email may not work'); } } // OAuth configuration validation if (config.features.enableOAuth) { if (!config.oauth.google?.clientId && !config.oauth.github?.clientId) { warnings.push('No OAuth client IDs configured - OAuth will be disabled'); } } // Log warnings and throw errors if (warnings.length > 0) { console.warn('⚠️ Configuration warnings:', warnings); } if (errors.length > 0) { console.error('❌ Configuration errors:', errors); throw new Error(`Configuration validation failed: ${errors.join(', ')}`); } } /** * Create unified configuration * Uses .env file as primary source for environment-specific overrides */ export function createUnifiedConfig(): UnifiedConfig { // Determine environment from .env file or NODE_ENV const nodeEnv = getEnvVar('NODE_ENV', 'development') as Environment; const environment = ['development', 'staging', 'production', 'test'].includes( nodeEnv ) ? (nodeEnv as Environment) : 'development'; return createUnifiedConfigForEnvironment(environment); } /** * Create unified configuration for specific environment * Priority: .env file overrides > environment-specific config > defaults */ export function createUnifiedConfigForEnvironment( environment: Environment ): UnifiedConfig { // Start with default config let config = { ...defaultConfig }; config.app.environment = environment; // Apply environment-specific overrides (lower priority) if (environmentConfigs[environment]) { config = deepMerge(config, environmentConfigs[environment]); } // Apply .env file overrides (highest priority) config = loadFromEnvironment(config); // Compute derived values after all overrides config.container.imageUrl = `${config.container.registry}/${config.container.repository}:${config.container.tag}`; // Validate configuration if (environment !== 'test') { validateConfig(config); } return config; } /** * Lazy-loaded singleton configuration instance */ let _unifiedConfig: UnifiedConfig | null = null; export const unifiedConfig: UnifiedConfig = new Proxy({} as UnifiedConfig, { get(target, prop) { if (!_unifiedConfig) { _unifiedConfig = createUnifiedConfig(); } return _unifiedConfig[prop as keyof UnifiedConfig]; }, }); /** * Export specific configuration sections for convenience */ export const getAppConfig = () => unifiedConfig.app; export const getDatabaseConfig = () => unifiedConfig.database; export const getContainerConfig = () => unifiedConfig.container; export const getKubernetesConfig = () => unifiedConfig.kubernetes; export const getAuthConfig = () => unifiedConfig.auth; export const getEmailConfig = () => unifiedConfig.email; export const getOAuthConfig = () => unifiedConfig.oauth; export const getFeatureFlags = () => unifiedConfig.features; export const getPerformanceConfig = () => unifiedConfig.performance; export const getLoggingConfig = () => unifiedConfig.logging; export const getSecurityConfig = () => unifiedConfig.security; /** * Utility functions */ export const isProduction = () => unifiedConfig.app.environment === 'production'; export const isDevelopment = () => unifiedConfig.app.environment === 'development'; export const isStaging = () => unifiedConfig.app.environment === 'staging'; export const isTest = () => unifiedConfig.app.environment === 'test'; /** * Export configuration as environment variables (for scripts and Kubernetes) */ export function exportAsEnvVars( config?: UnifiedConfig ): Record { const configToUse = config || unifiedConfig; return { // Application APP_NAME: configToUse.app.name, APP_VERSION: configToUse.app.version, NODE_ENV: configToUse.app.environment, APP_BASE_URL: configToUse.app.baseUrl, PORT: configToUse.app.port.toString(), // Database COUCHDB_URL: configToUse.database.url, COUCHDB_USER: configToUse.database.username, COUCHDB_PASSWORD: configToUse.database.password, COUCHDB_DATABASE_NAME: configToUse.database.name, USE_MOCK_DB: configToUse.database.useMock.toString(), // Vite-compatible database vars VITE_COUCHDB_URL: configToUse.database.url, VITE_COUCHDB_USER: configToUse.database.username, VITE_COUCHDB_PASSWORD: configToUse.database.password, // Authentication & Security JWT_SECRET: configToUse.auth.jwtSecret, JWT_EXPIRES_IN: configToUse.auth.jwtExpiresIn, SESSION_SECRET: configToUse.security.sessionSecret, // Email EMAIL_PROVIDER: configToUse.email.provider, VITE_MAILGUN_API_KEY: configToUse.email.mailgun?.apiKey || '', VITE_MAILGUN_DOMAIN: configToUse.email.mailgun?.domain || '', MAILGUN_FROM_NAME: configToUse.email.fromName, MAILGUN_FROM_EMAIL: configToUse.email.fromEmail, // OAuth VITE_GOOGLE_CLIENT_ID: configToUse.oauth.google?.clientId || '', GOOGLE_CLIENT_SECRET: configToUse.oauth.google?.clientSecret || '', VITE_GITHUB_CLIENT_ID: configToUse.oauth.github?.clientId || '', GITHUB_CLIENT_SECRET: configToUse.oauth.github?.clientSecret || '', // Container CONTAINER_REGISTRY: configToUse.container.registry, CONTAINER_REPOSITORY: configToUse.container.repository, CONTAINER_TAG: configToUse.container.tag, DOCKER_IMAGE: configToUse.container.imageUrl, // Kubernetes KUBERNETES_NAMESPACE: configToUse.kubernetes.namespace, INGRESS_HOST: configToUse.kubernetes.ingressHost, INGRESS_CLASS: configToUse.kubernetes.ingressClass, CERT_MANAGER_ISSUER: configToUse.kubernetes.certIssuer, STORAGE_CLASS: configToUse.kubernetes.storageClass, STORAGE_SIZE: configToUse.kubernetes.storageSize, // Features ENABLE_EMAIL_VERIFICATION: configToUse.features.enableEmailVerification.toString(), ENABLE_OAUTH: configToUse.features.enableOAuth.toString(), ENABLE_ADMIN_INTERFACE: configToUse.features.enableAdminInterface.toString(), ENABLE_MONITORING: configToUse.features.enableMonitoring.toString(), ENABLE_METRICS: configToUse.features.enableMetrics.toString(), DEBUG_MODE: configToUse.features.debugMode.toString(), // Logging LOG_LEVEL: configToUse.logging.level, LOG_FORMAT: configToUse.logging.format, // Performance CACHE_TTL: configToUse.performance.cacheTimeout.toString(), REQUEST_TIMEOUT: configToUse.performance.requestTimeout.toString(), MAX_CONNECTIONS: configToUse.performance.maxConnections.toString(), ENABLE_CORS: configToUse.performance.enableCors.toString(), CORS_ORIGIN: Array.isArray(configToUse.performance.corsOrigin) ? configToUse.performance.corsOrigin.join(',') : configToUse.performance.corsOrigin, // Replicas (for Kubernetes) FRONTEND_REPLICAS: configToUse.kubernetes.replicas.frontend.toString(), DATABASE_REPLICAS: configToUse.kubernetes.replicas.database.toString(), // Resources (for Kubernetes) FRONTEND_MEMORY_REQUEST: configToUse.kubernetes.resources.frontend.requests.memory, FRONTEND_CPU_REQUEST: configToUse.kubernetes.resources.frontend.requests.cpu, FRONTEND_MEMORY_LIMIT: configToUse.kubernetes.resources.frontend.limits.memory, FRONTEND_CPU_LIMIT: configToUse.kubernetes.resources.frontend.limits.cpu, DATABASE_MEMORY_REQUEST: configToUse.kubernetes.resources.database.requests.memory, DATABASE_CPU_REQUEST: configToUse.kubernetes.resources.database.requests.cpu, DATABASE_MEMORY_LIMIT: configToUse.kubernetes.resources.database.limits.memory, DATABASE_CPU_LIMIT: configToUse.kubernetes.resources.database.limits.cpu, }; } /** * Debug helper to log current configuration */ export function logConfig(): void { if (unifiedConfig.features.debugMode) { console.warn('🔧 Unified Configuration (Single Source of Truth):', { environment: unifiedConfig.app.environment, app: unifiedConfig.app.name, version: unifiedConfig.app.version, baseUrl: unifiedConfig.app.baseUrl, database: { url: unifiedConfig.database.url, useMock: unifiedConfig.database.useMock, }, container: { registry: unifiedConfig.container.registry, repository: unifiedConfig.container.repository, imageUrl: unifiedConfig.container.imageUrl, }, features: unifiedConfig.features, configSource: '.env file overrides applied', }); } } // Auto-log in development if (isDevelopment() && typeof window !== 'undefined') { logConfig(); }