const { config } = require('@proceed/system');
const { data } = require('@proceed/system'); // only used for logging_meta_data. not for the config itself
const routes = require('./routes/configRoutes');
const defaultProfile = require('./defaultProfile.json');
let configHandlerSingleton;
/**
* @memberof module:@proceed/machine
* @class
*
* Reads and writes to config and logging_meta_data
* Also keeps track of callbacks set for config changes
*/
class ConfigHandler {
/**
* @hideconstructor
*/
constructor() {
this.callbacks = new Map();
config.constructor._setConfigModule(this);
}
/**
* Configures the HTTP routes
*/
init() {
this.config = config.getConfig();
}
start() {
routes(this);
}
/**
* Stores callbacks to be executed when a certain config-key changes
* @param {string} key the config key
* @param {function} callback executed once the value for the key changes
*/
registerForConfigChange(key, callback) {
if (!this.callbacks.has(key)) {
this.callbacks.set(key, [callback]);
} else {
const values = this.callbacks.get(key);
values.push(callback);
this.callbacks.set(key, values);
}
}
/**
* Checks if the obj is a valid key path (checks recursively in case of nested
* objects)
* @param {string} obj The obj that is to be checked
* @returns {boolean} If the key path(s) are part of the configuration
* @private
*/
_isExistingKeyPath(obj, _config) {
const configKeys = Object.keys(_config);
if (!Object.keys(obj).every((key) => configKeys.includes(key))) {
return false;
}
return Object.keys(obj)
.map((key) => {
if (typeof obj[key] === 'object' && Array.isArray(obj[key]) === false) {
return this._isExistingKeyPath(obj[key], _config[key]);
}
return true;
})
.every((ret) => ret === true);
}
/**
* returns the value for a given key stored in the config
* @param {string} key
* @returns {object}
*/
async readConfig(key) {
const defaultProfileConfig = defaultProfile;
const nativeConfig = await this.config;
let configuration = { ...defaultProfileConfig, ...nativeConfig };
if (key) {
// Translate object keyPath to value
configuration = key.split('.').reduce((o, i) => o[i], configuration);
}
return configuration;
}
// private
/**
* Calls all callbacks stored for a given key
* @param {string} key A configuration key
* @private
*/
_executeCallbacks(key, value) {
const keyCallbacks = this.callbacks.get(key);
if (keyCallbacks) {
keyCallbacks.forEach((c) => c(value));
}
}
/**
* Changes a value in the config, stores it and calls all callbacks associated with the given key
* @param key A config key
* @param value The new config value
* @returns {object} The new values in the config table
*/
async writeConfigValue(key, value) {
const configuration = await this.config;
const keyExists = this._isExistingKeyPath({ [key]: value }, configuration);
if (!keyExists) {
throw new Error('Key does not exist on config object!');
}
const changed = await config.writeConfig({ [key]: value });
this.config = Promise.resolve(changed);
this._executeCallbacks(key, value);
return changed;
}
async writeConfig(configObj) {
// remove all profile entries to avoid errors
Object.keys(defaultProfile).forEach((key) => {
delete configObj[key];
});
const configuration = await this.config;
const keyExists = this._isExistingKeyPath(configObj, configuration);
if (!keyExists) {
throw new Error('Key does not exist on config object!');
}
const changed = await config.writeConfig(configObj, true);
this.config = Promise.resolve(changed);
await Object.entries(configObj).forEach(async ([key, value]) => {
this._executeCallbacks(key, value);
});
const mergedConfig = await this.readConfig();
return mergedConfig;
}
/**
* Returns the value for a given key that is stored in the logging_meta_data
* @param key A logging_meta_data key
* @returns {object} Returns a logging_meta_data value
*/
// eslint-disable-next-line class-methods-use-this
async readConfigData(key) {
const configData = await data.read('logging_meta_data');
if (configData) {
return JSON.parse(configData.config)[key];
}
return null;
}
/**
* Creates a new logging_meta_data table
* @returns {object} Returns a new logging_meta_data table
*/
// eslint-disable-next-line class-methods-use-this
async createConfigData() {
const initData = {};
initData.rotationStartTime = new Date().getTime();
initData.standardLogs = 0;
initData.processLogs = [];
return data.write('logging_meta_data/config', JSON.stringify(initData));
}
}
/*
* Returns the singleton instance of the config handler and creates it if necessary
* returns Returns an instance of the ConfigHandler class.
*/
function getInstance() {
if (!configHandlerSingleton) {
configHandlerSingleton = new ConfigHandler();
}
return configHandlerSingleton;
}
module.exports = getInstance();