Source: Router/configRoute/service.js

/**
 * Configuration File Service Layer
 * 
 * Handles business logic for configuration file management.
 * This service manages YAML configuration files for different applications
 * in the CommTool system, including validation, storage, and real-time updates.
 * 
 * @module ConfigService
 */

import _ from 'lodash';
import { busboyUpload } from '../../utils/uploadBusboy.js';
import { s3, myMinioClient } from '../../utils/s3Client.js';
import { configs, readConfig, remergeConfig } from '../../utils/compileTemplates.js';
import { publishEvent } from '../../utils/events.js';
import { updateConfig } from '../../server.ws.js';


// Configuration constants
const BUCKET = process.env.bucket || 'kpe20';

/**
 * Generates the S3 key for uploaded config files
 * Files are initially uploaded to 'confignew' for validation
 * 
 * @param {Object} params - Upload parameters
 * @returns {string} S3 key path
 */
const generateConfigUploadKey = ({ fields, filename, extension, params }) => {
    return `confignew/${params.app}/${filename.filename}`;
};

/**
 * Processes config file after successful upload and validation
 * 
 * @param {string} uploadedKey - S3 key of uploaded file
 * @param {string} app - Application name
 * @param {string} configUID - Configuration UID from parsed YAML
 * @returns {Promise<Object>} Processing result
 */
const processValidatedConfig = async (uploadedKey, app, configUID) => {
    try {
        const finalKey = `config/${app}/${configUID}.yaml`;
        
        // Remove existing config file (if any)
        try {
            await myMinioClient.removeObject(BUCKET, finalKey);
            console.log(`Removed old config file: ${finalKey}`);
        } catch (removeError) {
            // File might not exist, continue processing
            console.log(`No existing config file to remove: ${finalKey}`);
        }
        
        // Copy validated file to final location
        await myMinioClient.copyObject(
            BUCKET, 
            finalKey, 
            `/${BUCKET}/${uploadedKey}`
        );
        
        // Clean up temporary upload
        await myMinioClient.removeObject(BUCKET, uploadedKey);
        
        return { success: true };
    } catch (error) {
        console.error('Error processing validated config:', error);
        throw new Error(`Failed to process config file: ${error.message}`);
    }
};

/**
 * Triggers config updates across applications in the ui
 * 
 * @param {string} app - Application that was updated
 * @param {string} configUID - Configuration UID
 */
export const triggerConfigUpdates = (app, configUID) => {
    try {
        if (app === 'admin') {
            // Admin configs affect all other apps
            const currentApps = process.env.apps
                .split('|')
                .filter(appName => appName !== 'admin');
            
            currentApps.forEach(appName => {
                remergeConfig(appName, configUID);
                updateConfig(appName, configUID);
                publishEvent(`/config/${appName}/${configUID}`, { organization: configUID, data: { UID: configUID } });
            });
            
            updateConfig('admin', configUID);
            publishEvent(`/config/admin/${configUID}`, { organization: configUID, data: { UID: configUID } });
        } else {
            // Regular app config update
            remergeConfig(app, configUID);
            updateConfig(app, configUID);
            publishEvent(`/config/${app}/${configUID}`, { organization: configUID, data: { UID: configUID } });
        }
    } catch (error) {
        console.error('Error triggering config updates:', error);
        throw error;
    }
};

/**
 * Configuration File Service
 */
export const configService = {
    /**
     * Upload and validate a configuration file
     * 
     * @param {Object} req - Express request object
     * @param {string} app - Application name
     * @returns {Promise<Object>} Upload result
     */
    async uploadAndValidateConfig(req, app) {
        try {
            const uploadedFileData = await busboyUpload(req, {
                s3,
                uploadUrlGen: generateConfigUploadKey,
                Bucket: BUCKET
            });

            if (uploadedFileData.length === 0) {
                return {
                    success: false,
                    message: 'No file uploaded'
                };
            }

            const filename = uploadedFileData[0].key;
            const result = await readConfig(filename, app);

            if (!result.success) {
                console.log('Error in config file upload or YAML parsing:', result);
                
                // Clean up invalid file
                try {
                    await myMinioClient.removeObject(BUCKET, filename);
                } catch (cleanupError) {
                    console.error('Error cleaning up invalid file:', cleanupError);
                }
                
                return result;
            }

            console.log('Config file upload and YAML parsing successful');
            
            const configUID = result.config.UID;
            await processValidatedConfig(filename, app, configUID);
            triggerConfigUpdates(app, configUID);
            const config = result.config;
            // publish config change event

            const timestamp = Math.floor(Date.now() / 1000);


            return result;
            
        } catch (error) {
            console.error('Error in uploadAndValidateConfig:', error);
            throw error;
        }
    },

    /**
     * Get a configuration file for download
     * 
     * @param {string} app - Application name
     * @param {string} UID - Configuration UID
     * @returns {Promise<Object>} File stream result
     */
    async getConfigFile(app, UID) {
        return new Promise((resolve, reject) => {
            const configPath = `config/${app}/${UID}.yaml`;
            
            myMinioClient.getObject(BUCKET, configPath, (err, data) => {
                if (err) {
                    console.error('Error retrieving config file:', err);
                    resolve({
                        success: false,
                        message: 'Configuration file not found'
                    });
                } else {
                    resolve({
                        success: true,
                        stream: data
                    });
                }
            });
        });
    },

    /**
     * Get list of configuration files for an application
     * 
     * @param {string} app - Application name
     * @returns {Promise<Object>} List of configuration files
     */
    async getConfigList(app) {
        try {
            const myConfigs = configs[app];
            
            if (!myConfigs || Object.keys(myConfigs).length === 0) {
                return {
                    success: false,
                    files: [],
                    message: 'No configs found. Did you supply a valid app? Have there been already uploaded configs for this app?'
                };
            }
            
            const configFiles = Object.entries(myConfigs)
                .filter(([key, value]) => value !== null)
                .map(([key, value]) => _.merge({}, configs['db'][key], value));
            
            return {
                success: true,
                files: configFiles
            };
            
        } catch (error) {
            console.error('Error in getConfigList:', error);
            throw error;
        }
    },

    /**
     * Delete a configuration file
     * 
     * @param {string} app - Application name
     * @param {string} UID - Configuration UID
     * @returns {Promise<Object>} Deletion result
     */
    async deleteConfigFile(app, UID) {
        try {
            const configPath = `config/${app}/${UID}.yaml`;
            
            const result = await myMinioClient.removeObject(BUCKET, configPath);
            
            // Trigger config updates after deletion
            triggerConfigUpdates(app, UID);
            
            return {
                success: true,
                result,
                message: `Configuration file ${UID} deleted successfully`
            };
            
        } catch (error) {
            console.error('Error in deleteConfigFile:', error);
            throw error;
        }
    }
};