refacto(backend): regrouped config related handling
This commit is contained in:
80
scripts/config/config-schema.json
Normal file
80
scripts/config/config-schema.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Whiteboard config",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backend": {
|
||||
"type": "object",
|
||||
"required": ["accessToken", "performance", "webdav"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"accessToken": {
|
||||
"type": "string"
|
||||
},
|
||||
"webdav": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"performance": {
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": ["whiteboardInfoBroadcastFreq"],
|
||||
"properties": {
|
||||
"whiteboardInfoBroadcastFreq": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"frontend": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["readOnlyOnWhiteboardLoad", "showSmallestScreenIndicator", "performance"],
|
||||
"properties": {
|
||||
"readOnlyOnWhiteboardLoad": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"showSmallestScreenIndicator": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"performance": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["pointerEventsThrottling", "refreshInfoFreq"],
|
||||
"properties": {
|
||||
"pointerEventsThrottling": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["fromNbUser", "minDistDelta", "maxFreq"],
|
||||
"properties": {
|
||||
"fromNbUser": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"minDistDelta": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"maxFreq": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"refreshInfoFreq": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["backend", "frontend"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
80
scripts/config/config.js
Normal file
80
scripts/config/config.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const util = require("util");
|
||||
|
||||
const { getDefaultConfig, getConfig, deepMergeConfigs, isConfigValid } = require("./utils");
|
||||
|
||||
const { getArgs } = require("./../utils");
|
||||
|
||||
const defaultConfig = getDefaultConfig();
|
||||
|
||||
const cliArgs = getArgs();
|
||||
let userConfig = {};
|
||||
|
||||
if (cliArgs["config"]) {
|
||||
userConfig = getConfig(cliArgs["config"]);
|
||||
}
|
||||
|
||||
const config = deepMergeConfigs(defaultConfig, userConfig);
|
||||
|
||||
/**
|
||||
* Update the config based on the CLI args
|
||||
* @param {object} startArgs
|
||||
*/
|
||||
function updateConfigFromStartArgs(startArgs) {
|
||||
function deprecateCliArg(key, callback) {
|
||||
const val = startArgs[key];
|
||||
if (val) {
|
||||
console.warn(
|
||||
"\x1b[33m\x1b[1m",
|
||||
`Setting config values (${key}) from the CLI is deprecated. ` +
|
||||
"This ability will be removed in the next major version. " +
|
||||
"You should use the config file. "
|
||||
);
|
||||
callback(val);
|
||||
}
|
||||
}
|
||||
|
||||
deprecateCliArg("accesstoken", (val) => (config.backend.accessToken = val));
|
||||
deprecateCliArg(
|
||||
"disablesmallestscreen",
|
||||
() => (config.backend.showSmallestScreenIndicator = false)
|
||||
);
|
||||
deprecateCliArg("webdav", () => (config.backend.webdav = true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the config based on the env variables
|
||||
*/
|
||||
function updateConfigFromEnv() {
|
||||
function deprecateEnv(key, callback) {
|
||||
const val = process.env[key];
|
||||
if (val) {
|
||||
console.warn(
|
||||
"\x1b[33m\x1b[1m",
|
||||
`Setting config values (${key}) from the environment is deprecated. ` +
|
||||
"This ability will be removed in the next major version. " +
|
||||
"You should use the config file. "
|
||||
);
|
||||
callback(val);
|
||||
}
|
||||
}
|
||||
|
||||
deprecateEnv("accesstoken", (val) => (config.backend.accessToken = val));
|
||||
deprecateEnv(
|
||||
"disablesmallestscreen",
|
||||
() => (config.backend.showSmallestScreenIndicator = false)
|
||||
);
|
||||
deprecateEnv("webdav", () => (config.backend.webdav = true));
|
||||
}
|
||||
|
||||
// compatibility layer
|
||||
// FIXME: remove this in next major
|
||||
updateConfigFromEnv();
|
||||
// FIXME: remove this in next major
|
||||
updateConfigFromStartArgs(cliArgs);
|
||||
|
||||
if (!isConfigValid(config, true)) {
|
||||
throw new Error("Config is not valid. Check logs for details");
|
||||
}
|
||||
console.info(util.inspect(config, { showHidden: false, depth: null, colors: true }));
|
||||
|
||||
module.exports = config;
|
||||
92
scripts/config/utils.js
Normal file
92
scripts/config/utils.js
Normal file
@@ -0,0 +1,92 @@
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const yaml = require("js-yaml");
|
||||
|
||||
const Ajv = require("ajv");
|
||||
const ajv = new Ajv({ allErrors: true });
|
||||
|
||||
const configSchema = require("./config-schema.json");
|
||||
|
||||
/**
|
||||
* Load a yaml config file from a given path.
|
||||
*
|
||||
* @param path
|
||||
* @return {Object}
|
||||
*/
|
||||
function getConfig(path) {
|
||||
return yaml.safeLoad(fs.readFileSync(path, "utf8"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a config object is valid.
|
||||
*
|
||||
* @param {Object} config Config object
|
||||
* @param {boolean} warn Should we warn in console for errors
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isConfigValid(config, warn = true) {
|
||||
const validate = ajv.compile(configSchema);
|
||||
const isValidAgainstSchema = validate(config);
|
||||
|
||||
if (!isValidAgainstSchema && warn) console.warn(validate.errors);
|
||||
|
||||
let structureIsValid = false;
|
||||
try {
|
||||
structureIsValid = config.frontend.performance.pointerEventsThrottling.some(
|
||||
(item) => item.fromNbUser === 0
|
||||
);
|
||||
} catch (e) {
|
||||
if (!e instanceof TypeError) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
if (!structureIsValid && warn)
|
||||
console.warn(
|
||||
"At least one item under frontend.performance.pointerEventsThrottling" +
|
||||
"must have fromNbUser set to 0"
|
||||
);
|
||||
|
||||
return isValidAgainstSchema && structureIsValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the default project config
|
||||
* @return {Object}
|
||||
*/
|
||||
function getDefaultConfig() {
|
||||
const defaultConfigPath = path.join(__dirname, "..", "..", "config.default.yml");
|
||||
return getConfig(defaultConfigPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deep merge of project config
|
||||
*
|
||||
* Objects are merged, not arrays
|
||||
*
|
||||
* @param baseConfig
|
||||
* @param overrideConfig
|
||||
* @return {Object}
|
||||
*/
|
||||
function deepMergeConfigs(baseConfig, overrideConfig) {
|
||||
const out = {};
|
||||
|
||||
Object.entries(baseConfig).forEach(([key, val]) => {
|
||||
out[key] = val;
|
||||
if (overrideConfig.hasOwnProperty(key)) {
|
||||
const overrideVal = overrideConfig[key];
|
||||
if (typeof val === "object" && !Array.isArray(val) && val !== null) {
|
||||
out[key] = deepMergeConfigs(val, overrideVal);
|
||||
} else {
|
||||
out[key] = overrideVal;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
module.exports.getConfig = getConfig;
|
||||
module.exports.getDefaultConfig = getDefaultConfig;
|
||||
module.exports.deepMergeConfigs = deepMergeConfigs;
|
||||
module.exports.isConfigValid = isConfigValid;
|
||||
48
scripts/config/utils.test.js
Normal file
48
scripts/config/utils.test.js
Normal file
@@ -0,0 +1,48 @@
|
||||
const { getDefaultConfig, deepMergeConfigs, isConfigValid } = require("./utils");
|
||||
|
||||
test("Load default config", () => {
|
||||
const defaultConfig = getDefaultConfig();
|
||||
expect(typeof defaultConfig).toBe("object");
|
||||
});
|
||||
|
||||
test("Full config override", () => {
|
||||
const defaultConfig = getDefaultConfig();
|
||||
expect(deepMergeConfigs(defaultConfig, defaultConfig)).toEqual(defaultConfig);
|
||||
});
|
||||
|
||||
test("Simple partial config override", () => {
|
||||
expect(deepMergeConfigs({ test: true }, { test: false }).test).toBe(false);
|
||||
expect(deepMergeConfigs({ test: false }, { test: true }).test).toBe(true);
|
||||
});
|
||||
|
||||
test("Simple deep config override", () => {
|
||||
expect(deepMergeConfigs({ stage1: { stage2: true } }, { stage1: { stage2: false } })).toEqual({
|
||||
stage1: { stage2: false },
|
||||
});
|
||||
});
|
||||
|
||||
test("Complex object config override", () => {
|
||||
expect(
|
||||
deepMergeConfigs({ stage1: { stage2: true, stage2b: true } }, { stage1: { stage2: false } })
|
||||
).toEqual({
|
||||
stage1: { stage2: false, stage2b: true },
|
||||
});
|
||||
});
|
||||
|
||||
test("Override default config", () => {
|
||||
const defaultConfig = getDefaultConfig();
|
||||
const overrideConfig1 = { frontend: { readOnlyOnWhiteboardLoad: true } };
|
||||
|
||||
expect(deepMergeConfigs(defaultConfig, overrideConfig1).frontend.readOnlyOnWhiteboardLoad).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test("Dumb config is not valid", () => {
|
||||
expect(isConfigValid({}, false)).toBe(false);
|
||||
});
|
||||
|
||||
test("Default config is valid", () => {
|
||||
const defaultConfig = getDefaultConfig();
|
||||
expect(isConfigValid(defaultConfig)).toBe(true);
|
||||
});
|
||||
Reference in New Issue
Block a user