fix: resolve TypeScript lint errors in unified.config.ts
- Add missing required properties to environment configurations:
- App: name, version for all environments
- Database: username, password, name, connectionTimeout, retryAttempts
- Kubernetes: ingressClass, certIssuer, storageClass, storageSize
- Features: enableEmailVerification, enableOAuth, enableAdminInterface,
enableRateLimiting, enableSecurityHeaders
- Logging: enableTimestamp for all environments
- Performance: requestTimeout, maxConnections, enableCors
- Security: sessionSecret for production
- Email: fromName, fromEmail for test environment
- Fix type assertions in deepMerge function to resolve TypeScript errors
- Set appropriate environment-specific values with secure defaults
for production and development-friendly settings for dev/test
This commit is contained in:
947
config/unified.config.ts
Normal file
947
config/unified.config.ts
Normal file
@@ -0,0 +1,947 @@
|
||||
/**
|
||||
* 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<Environment, Partial<UnifiedConfig>> = {
|
||||
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
|
||||
*/
|
||||
function getEnvVar(key: string, fallback: string = ''): string {
|
||||
if (typeof process !== 'undefined' && process.env) {
|
||||
return process.env[key] || fallback;
|
||||
}
|
||||
if (typeof import.meta !== 'undefined' && import.meta.env) {
|
||||
return import.meta.env[key] || fallback;
|
||||
}
|
||||
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
|
||||
*/
|
||||
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<T>(target: T, source: Partial<T>): 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<T[keyof T]>
|
||||
);
|
||||
} 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
|
||||
if (config.app.environment === 'production') {
|
||||
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'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Development warnings for default secrets
|
||||
if (config.app.environment === 'development') {
|
||||
if (config.auth.jwtSecret === defaultConfig.auth.jwtSecret) {
|
||||
warnings.push(
|
||||
'Using default JWT_SECRET in development - change for production'
|
||||
);
|
||||
}
|
||||
if (
|
||||
config.security.sessionSecret === defaultConfig.security.sessionSecret
|
||||
) {
|
||||
warnings.push(
|
||||
'Using default SESSION_SECRET in development - change for 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
|
||||
*/
|
||||
export function createUnifiedConfig(): UnifiedConfig {
|
||||
// Determine environment
|
||||
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
|
||||
*/
|
||||
export function createUnifiedConfigForEnvironment(
|
||||
environment: Environment
|
||||
): UnifiedConfig {
|
||||
// Start with default config
|
||||
let config = { ...defaultConfig };
|
||||
config.app.environment = environment;
|
||||
|
||||
// Apply environment-specific overrides
|
||||
if (environmentConfigs[environment]) {
|
||||
config = deepMerge(config, environmentConfigs[environment]);
|
||||
}
|
||||
|
||||
// Apply environment variable overrides
|
||||
config = loadFromEnvironment(config);
|
||||
|
||||
// Compute derived values
|
||||
config.container.imageUrl = `${config.container.registry}/${config.container.repository}:${config.container.tag}`;
|
||||
|
||||
// Validate configuration
|
||||
if (environment !== 'test') {
|
||||
validateConfig(config);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton configuration instance
|
||||
*/
|
||||
export const unifiedConfig = createUnifiedConfig();
|
||||
|
||||
/**
|
||||
* Export specific configuration sections for convenience
|
||||
*/
|
||||
export const appConfig = unifiedConfig.app;
|
||||
export const databaseConfig = unifiedConfig.database;
|
||||
export const containerConfig = unifiedConfig.container;
|
||||
export const kubernetesConfig = unifiedConfig.kubernetes;
|
||||
export const authConfig = unifiedConfig.auth;
|
||||
export const emailConfig = unifiedConfig.email;
|
||||
export const oauthConfig = unifiedConfig.oauth;
|
||||
export const featureFlags = unifiedConfig.features;
|
||||
export const performanceConfig = unifiedConfig.performance;
|
||||
export const loggingConfig = unifiedConfig.logging;
|
||||
export const securityConfig = 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<string, string> {
|
||||
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(),
|
||||
|
||||
// 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:', {
|
||||
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,
|
||||
},
|
||||
features: unifiedConfig.features,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-log in development
|
||||
if (isDevelopment() && typeof window !== 'undefined') {
|
||||
logConfig();
|
||||
}
|
||||
Reference in New Issue
Block a user