feat: implement configuration management system
- Add comprehensive TypeScript interfaces for all config types - Implement ConfigManager singleton class with layered loading - Support for default, environment-specific, and environment variable configs - Add example .env file for sensitive configuration Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,8 +8,8 @@
|
||||
"timeout": 30000
|
||||
},
|
||||
"erp": {
|
||||
"baseUrl": "https://68.11.34.30:8082",
|
||||
"ignoreHttpsErrors": true
|
||||
"baseUrl": "",
|
||||
"ignoreHttpsErrors": false
|
||||
},
|
||||
"paths": {
|
||||
"tempDir": "./data/temp",
|
||||
|
||||
@@ -10,37 +10,53 @@ export class ConfigManager {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
// Load default config
|
||||
const defaultConfig = this.getDefaultConfig();
|
||||
try {
|
||||
// Load default config (minimal, no sensitive data)
|
||||
const defaultConfig = this.getDefaultConfig();
|
||||
|
||||
// Load environment-specific config
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const envConfigPath = path.join(__dirname, `../../../config/${env}.json`);
|
||||
// Load environment-specific config
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const envConfigPath = path.join(__dirname, `../../../config/${env}.json`);
|
||||
|
||||
let envConfig: Partial<AppConfig> = {};
|
||||
if (fs.existsSync(envConfigPath)) {
|
||||
envConfig = JSON.parse(fs.readFileSync(envConfigPath, 'utf-8'));
|
||||
let envConfig: Partial<AppConfig> = {};
|
||||
if (fs.existsSync(envConfigPath)) {
|
||||
try {
|
||||
const envConfigContent = fs.readFileSync(envConfigPath, 'utf-8');
|
||||
envConfig = JSON.parse(envConfigContent);
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load env config from ${envConfigPath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load environment variables (with validation)
|
||||
const envVars = this.loadFromEnv();
|
||||
|
||||
// Deep merge configurations
|
||||
this.instance = this.deepMerge(defaultConfig, envConfig, envVars);
|
||||
|
||||
// Validate required configuration
|
||||
this.validateConfig(this.instance);
|
||||
|
||||
return this.instance;
|
||||
} catch (error) {
|
||||
console.error('Failed to load configuration:', error);
|
||||
throw new Error('Configuration loading failed');
|
||||
}
|
||||
|
||||
// Load environment variables
|
||||
const envVars: Partial<AppConfig> = this.loadFromEnv();
|
||||
|
||||
this.instance = {
|
||||
...defaultConfig,
|
||||
...envConfig,
|
||||
...envVars,
|
||||
};
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
private static getDefaultConfig(): AppConfig {
|
||||
const appConfigPath = path.join(__dirname, '../../../config/app.json');
|
||||
if (fs.existsSync(appConfigPath)) {
|
||||
return JSON.parse(fs.readFileSync(appConfigPath, 'utf-8'));
|
||||
|
||||
try {
|
||||
if (fs.existsSync(appConfigPath)) {
|
||||
const configContent = fs.readFileSync(appConfigPath, 'utf-8');
|
||||
return JSON.parse(configContent);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Failed to load app.json, using minimal defaults:`, error);
|
||||
}
|
||||
|
||||
// Fallback defaults
|
||||
// Minimal fallback defaults (NO sensitive data)
|
||||
return {
|
||||
appName: 'ERPAuto',
|
||||
version: '1.0.0',
|
||||
@@ -52,9 +68,9 @@ export class ConfigManager {
|
||||
},
|
||||
databases: {
|
||||
sqlServer: {
|
||||
server: '192.168.110.114',
|
||||
database: 'CompanyDB',
|
||||
username: 'peng',
|
||||
server: '', // MUST be set via env var
|
||||
database: '',
|
||||
username: '',
|
||||
password: '',
|
||||
driver: 'ODBC Driver 18 for SQL Server',
|
||||
trustServerCertificate: 'yes',
|
||||
@@ -62,14 +78,14 @@ export class ConfigManager {
|
||||
mysql: {
|
||||
host: 'localhost',
|
||||
port: 3306,
|
||||
database: 'erp_db',
|
||||
username: 'root',
|
||||
database: '',
|
||||
username: '',
|
||||
password: '',
|
||||
},
|
||||
},
|
||||
erp: {
|
||||
baseUrl: 'https://68.11.34.30:8082',
|
||||
ignoreHttpsErrors: true,
|
||||
baseUrl: '', // MUST be set via env var
|
||||
ignoreHttpsErrors: false,
|
||||
},
|
||||
paths: {
|
||||
tempDir: './data/temp',
|
||||
@@ -80,24 +96,63 @@ export class ConfigManager {
|
||||
}
|
||||
|
||||
private static loadFromEnv(): Partial<AppConfig> {
|
||||
const sqlServer = {
|
||||
server: process.env.SQL_SERVER_SERVER || '',
|
||||
database: process.env.SQL_SERVER_DATABASE || '',
|
||||
username: process.env.SQL_SERVER_USERNAME || '',
|
||||
password: process.env.SQL_SERVER_PASSWORD || '',
|
||||
driver: process.env.SQL_SERVER_DRIVER || 'ODBC Driver 18 for SQL Server',
|
||||
trustServerCertificate: process.env.SQL_SERVER_TRUST_CERT || 'yes',
|
||||
};
|
||||
|
||||
const mysql = {
|
||||
host: process.env.MYSQL_HOST || 'localhost',
|
||||
port: parseInt(process.env.MYSQL_PORT || '3306'),
|
||||
database: process.env.MYSQL_DATABASE || '',
|
||||
username: process.env.MYSQL_USERNAME || '',
|
||||
password: process.env.MYSQL_PASSWORD || '',
|
||||
};
|
||||
|
||||
return {
|
||||
databases: {
|
||||
sqlServer: {
|
||||
server: process.env.SQL_SERVER_SERVER || '',
|
||||
database: process.env.SQL_SERVER_DATABASE || '',
|
||||
username: process.env.SQL_SERVER_USERNAME || '',
|
||||
password: process.env.SQL_SERVER_PASSWORD || '',
|
||||
driver: process.env.SQL_SERVER_DRIVER || '',
|
||||
trustServerCertificate: process.env.SQL_SERVER_TRUST_CERT || 'yes',
|
||||
},
|
||||
mysql: {
|
||||
host: process.env.MYSQL_HOST || 'localhost',
|
||||
port: parseInt(process.env.MYSQL_PORT || '3306'),
|
||||
database: process.env.MYSQL_DATABASE || '',
|
||||
username: process.env.MYSQL_USERNAME || '',
|
||||
password: process.env.MYSQL_PASSWORD || '',
|
||||
},
|
||||
sqlServer,
|
||||
mysql,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static deepMerge(...configs: Partial<AppConfig>[]): AppConfig {
|
||||
const result = configs[0] as AppConfig;
|
||||
|
||||
for (let i = 1; i < configs.length; i++) {
|
||||
const config = configs[i];
|
||||
for (const key in config) {
|
||||
if (Object.prototype.hasOwnProperty.call(config, key)) {
|
||||
const value = (config as any)[key];
|
||||
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||
(result as any)[key] = { ...(result as any)[key], ...value };
|
||||
} else {
|
||||
(result as any)[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static validateConfig(config: AppConfig): void {
|
||||
// Validate critical configuration
|
||||
if (!config.databases.sqlServer.server && process.env.NODE_ENV === 'production') {
|
||||
throw new Error('SQL Server server address must be configured via environment variable');
|
||||
}
|
||||
|
||||
if (!config.databases.sqlServer.database) {
|
||||
throw new Error('SQL Server database name must be configured');
|
||||
}
|
||||
|
||||
if (!config.erp.baseUrl) {
|
||||
console.warn('Warning: ERP base URL not configured, ERP features will not work');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user