161 lines
4.8 KiB
TypeScript
161 lines
4.8 KiB
TypeScript
/**
|
|
* Configuration Reader Service
|
|
* Reads admin configurations from database for use in backend logic
|
|
*/
|
|
|
|
import { sequelize } from '@config/database';
|
|
import { QueryTypes } from 'sequelize';
|
|
import logger from '@utils/logger';
|
|
|
|
// Cache configurations in memory for performance
|
|
let configCache: Map<string, string> = new Map();
|
|
let cacheExpiry: Date | null = null;
|
|
const CACHE_DURATION_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
|
// Sensitive config keys that should be masked in logs
|
|
const SENSITIVE_CONFIG_PATTERNS = [
|
|
'API_KEY', 'SECRET', 'PASSWORD', 'TOKEN', 'CREDENTIAL',
|
|
'PRIVATE', 'AUTH', 'KEY', 'VAPID'
|
|
];
|
|
|
|
/**
|
|
* Check if a config key contains sensitive data
|
|
*/
|
|
function isSensitiveConfig(configKey: string): boolean {
|
|
const upperKey = configKey.toUpperCase();
|
|
return SENSITIVE_CONFIG_PATTERNS.some(pattern => upperKey.includes(pattern));
|
|
}
|
|
|
|
/**
|
|
* Mask sensitive value for logging (show first 4 and last 2 chars)
|
|
*/
|
|
function maskSensitiveValue(value: string): string {
|
|
if (!value || value.length <= 8) {
|
|
return '***REDACTED***';
|
|
}
|
|
return `${value.substring(0, 4)}****${value.substring(value.length - 2)}`;
|
|
}
|
|
|
|
/**
|
|
* Get a configuration value from database (with caching)
|
|
*/
|
|
export async function getConfigValue(configKey: string, defaultValue: string = ''): Promise<string> {
|
|
try {
|
|
// Check cache first
|
|
if (configCache.has(configKey) && cacheExpiry && new Date() < cacheExpiry) {
|
|
return configCache.get(configKey)!;
|
|
}
|
|
|
|
// Query database
|
|
const result = await sequelize.query(`
|
|
SELECT config_value
|
|
FROM admin_configurations
|
|
WHERE config_key = :configKey
|
|
LIMIT 1
|
|
`, {
|
|
replacements: { configKey },
|
|
type: QueryTypes.SELECT
|
|
});
|
|
|
|
if (result && result.length > 0) {
|
|
const value = (result[0] as any).config_value;
|
|
configCache.set(configKey, value);
|
|
|
|
// Always update cache expiry when loading from database
|
|
cacheExpiry = new Date(Date.now() + CACHE_DURATION_MS);
|
|
|
|
// Mask sensitive values in logs for security
|
|
const logValue = isSensitiveConfig(configKey) ? maskSensitiveValue(value) : value;
|
|
logger.info(`[ConfigReader] Loaded config '${configKey}' = '${logValue}' from database (cached for 5min)`);
|
|
|
|
return value;
|
|
}
|
|
|
|
// Mask sensitive default values in logs for security
|
|
const logDefault = isSensitiveConfig(configKey) ? maskSensitiveValue(defaultValue) : defaultValue;
|
|
logger.warn(`[ConfigReader] Config key '${configKey}' not found, using default: ${logDefault}`);
|
|
return defaultValue;
|
|
} catch (error) {
|
|
logger.error(`[ConfigReader] Error reading config '${configKey}':`, error);
|
|
return defaultValue;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get number configuration
|
|
*/
|
|
export async function getConfigNumber(configKey: string, defaultValue: number): Promise<number> {
|
|
const value = await getConfigValue(configKey, String(defaultValue));
|
|
return parseFloat(value) || defaultValue;
|
|
}
|
|
|
|
/**
|
|
* Get boolean configuration
|
|
*/
|
|
export async function getConfigBoolean(configKey: string, defaultValue: boolean): Promise<boolean> {
|
|
const value = await getConfigValue(configKey, String(defaultValue));
|
|
return value === 'true' || value === '1';
|
|
}
|
|
|
|
/**
|
|
* Get TAT thresholds from database
|
|
*/
|
|
export async function getTatThresholds(): Promise<{ first: number; second: number }> {
|
|
const first = await getConfigNumber('TAT_REMINDER_THRESHOLD_1', 50);
|
|
const second = await getConfigNumber('TAT_REMINDER_THRESHOLD_2', 75);
|
|
|
|
return { first, second };
|
|
}
|
|
|
|
/**
|
|
* Get working hours from database
|
|
*/
|
|
export async function getWorkingHours(): Promise<{ startHour: number; endHour: number }> {
|
|
const startHour = await getConfigNumber('WORK_START_HOUR', 9);
|
|
const endHour = await getConfigNumber('WORK_END_HOUR', 18);
|
|
|
|
return { startHour, endHour };
|
|
}
|
|
|
|
/**
|
|
* Clear configuration cache (call after updating configs)
|
|
*/
|
|
export function clearConfigCache(): void {
|
|
configCache.clear();
|
|
cacheExpiry = null;
|
|
logger.info('[ConfigReader] Configuration cache cleared');
|
|
}
|
|
|
|
/**
|
|
* Preload all configurations into cache
|
|
*/
|
|
export async function preloadConfigurations(): Promise<void> {
|
|
try {
|
|
const results = await sequelize.query(`
|
|
SELECT config_key, config_value
|
|
FROM admin_configurations
|
|
`, { type: QueryTypes.SELECT });
|
|
|
|
results.forEach((row: any) => {
|
|
configCache.set(row.config_key, row.config_value);
|
|
});
|
|
|
|
cacheExpiry = new Date(Date.now() + CACHE_DURATION_MS);
|
|
logger.info(`[ConfigReader] Preloaded ${results.length} configurations into cache`);
|
|
} catch (error) {
|
|
logger.error('[ConfigReader] Error preloading configurations:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get Vertex AI configurations
|
|
*/
|
|
export async function getVertexAIConfig(): Promise<{
|
|
enabled: boolean;
|
|
}> {
|
|
const enabled = await getConfigBoolean('AI_ENABLED', true);
|
|
|
|
return { enabled };
|
|
}
|
|
|