Add database adapter system, production deployment configs, and new dashboard components

- Add PostgreSQL and SQLite database adapters with factory pattern
- Add migration script for SQLite to PostgreSQL
- Add production Dockerfiles and docker-compose configs
- Add deployment documentation and scripts
- Add BIA sync dashboard and matching service
- Add data completeness configuration and components
- Add new dashboard components (BusinessImportanceComparison, ComplexityDynamics, etc.)
- Update various services and routes
- Remove deprecated management-parameters.json and taxonomy files
This commit is contained in:
2026-01-14 00:38:40 +01:00
parent ca21b9538d
commit a7f8301196
73 changed files with 12878 additions and 2003 deletions

View File

@@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
import { logger } from '../services/logger.js';
import { clearEffortCalculationConfigCache, getEffortCalculationConfigV25 } from '../services/effortCalculation.js';
import type { EffortCalculationConfig, EffortCalculationConfigV25 } from '../config/effortCalculation.js';
import type { DataCompletenessConfig } from '../types/index.js';
// Get __dirname equivalent for ES modules
const __filename = fileURLToPath(import.meta.url);
@@ -15,6 +16,7 @@ const router = Router();
// Path to the configuration files
const CONFIG_FILE_PATH = join(__dirname, '../../data/effort-calculation-config.json');
const CONFIG_FILE_PATH_V25 = join(__dirname, '../../data/effort-calculation-config-v25.json');
const COMPLETENESS_CONFIG_FILE_PATH = join(__dirname, '../../data/data-completeness-config.json');
/**
* Get the current effort calculation configuration (legacy)
@@ -122,5 +124,143 @@ router.put('/effort-calculation-v25', async (req: Request, res: Response) => {
}
});
/**
* Get the data completeness configuration
*/
router.get('/data-completeness', async (req: Request, res: Response) => {
try {
// Try to read from JSON file, fallback to default config
try {
const fileContent = await readFile(COMPLETENESS_CONFIG_FILE_PATH, 'utf-8');
const config = JSON.parse(fileContent) as DataCompletenessConfig;
res.json(config);
} catch (fileError) {
// If file doesn't exist, return default config
const defaultConfig: DataCompletenessConfig = {
metadata: {
version: '1.0.0',
description: 'Configuration for Data Completeness Score fields',
lastUpdated: new Date().toISOString(),
},
categories: {
general: {
name: 'General',
description: 'General application information fields',
fields: [
{ name: 'Organisation', fieldPath: 'organisation', enabled: true },
{ name: 'ApplicationFunction', fieldPath: 'applicationFunctions', enabled: true },
{ name: 'Status', fieldPath: 'status', enabled: true },
{ name: 'Business Impact Analyse', fieldPath: 'businessImpactAnalyse', enabled: true },
{ name: 'Application Component Hosting Type', fieldPath: 'hostingType', enabled: true },
{ name: 'Supplier Product', fieldPath: 'supplierProduct', enabled: true },
{ name: 'Business Owner', fieldPath: 'businessOwner', enabled: true },
{ name: 'System Owner', fieldPath: 'systemOwner', enabled: true },
{ name: 'Functional Application Management', fieldPath: 'functionalApplicationManagement', enabled: true },
{ name: 'Technical Application Management', fieldPath: 'technicalApplicationManagement', enabled: true },
],
},
applicationManagement: {
name: 'Application Management',
description: 'Application management classification fields',
fields: [
{ name: 'ICT Governance Model', fieldPath: 'governanceModel', enabled: true },
{ name: 'Application Management - Application Type', fieldPath: 'applicationType', enabled: true },
{ name: 'Application Management - Hosting', fieldPath: 'applicationManagementHosting', enabled: true },
{ name: 'Application Management - TAM', fieldPath: 'applicationManagementTAM', enabled: true },
{ name: 'Application Management - Dynamics Factor', fieldPath: 'dynamicsFactor', enabled: true },
{ name: 'Application Management - Complexity Factor', fieldPath: 'complexityFactor', enabled: true },
{ name: 'Application Management - Number of Users', fieldPath: 'numberOfUsers', enabled: true },
],
},
},
};
res.json(defaultConfig);
}
} catch (error) {
logger.error('Failed to get data completeness configuration', error);
res.status(500).json({ error: 'Failed to get configuration' });
}
});
/**
* Update the data completeness configuration
*/
router.put('/data-completeness', async (req: Request, res: Response) => {
try {
const config = req.body as DataCompletenessConfig;
// Validate the configuration structure
if (!config.categories || !Array.isArray(config.categories)) {
res.status(400).json({ error: 'Invalid configuration: categories must be an array' });
return;
}
if (config.categories.length === 0) {
res.status(400).json({ error: 'Invalid configuration: must have at least one category' });
return;
}
// Validate each category
for (const category of config.categories) {
if (!category.id || typeof category.id !== 'string') {
res.status(400).json({ error: 'Invalid configuration: each category must have an id' });
return;
}
if (!category.name || typeof category.name !== 'string') {
res.status(400).json({ error: 'Invalid configuration: each category must have a name' });
return;
}
if (!Array.isArray(category.fields)) {
res.status(400).json({ error: 'Invalid configuration: category fields must be arrays' });
return;
}
// Validate each field
for (const field of category.fields) {
if (!field.id || typeof field.id !== 'string') {
res.status(400).json({ error: 'Invalid configuration: each field must have an id' });
return;
}
if (!field.name || typeof field.name !== 'string') {
res.status(400).json({ error: 'Invalid configuration: each field must have a name' });
return;
}
if (!field.fieldPath || typeof field.fieldPath !== 'string') {
res.status(400).json({ error: 'Invalid configuration: each field must have a fieldPath' });
return;
}
if (typeof field.enabled !== 'boolean') {
res.status(400).json({ error: 'Invalid configuration: each field must have an enabled boolean' });
return;
}
}
}
// Update metadata
config.metadata = {
...config.metadata,
lastUpdated: new Date().toISOString(),
};
// Write to JSON file
await writeFile(COMPLETENESS_CONFIG_FILE_PATH, JSON.stringify(config, null, 2), 'utf-8');
// Clear the cache so the new config is loaded on next request
const { clearDataCompletenessConfigCache } = await import('../services/dataCompletenessConfig.js');
clearDataCompletenessConfigCache();
logger.info('Data completeness configuration updated');
res.json({ success: true, message: 'Configuration saved successfully' });
} catch (error) {
logger.error('Failed to update data completeness configuration', error);
res.status(500).json({ error: 'Failed to save configuration' });
}
});
export default router;