feat(backend): new config handling based on file
* Config should be provided as Yaml file now * Other way to provide configuration are now deprectaed * The config format is checked agaist a schema with the json-schema standard * Tests are are added to the project (in the backend for config parsing)
This commit is contained in:
parent
4476ce3284
commit
0b1d1943ec
17
config.default.yml
Normal file
17
config.default.yml
Normal file
@ -0,0 +1,17 @@
|
||||
backend:
|
||||
# TODO
|
||||
accessToken: ""
|
||||
# TODO
|
||||
webdav: false
|
||||
performance:
|
||||
# Whiteboard information broadcasting frequency (in /s)
|
||||
# => diminishing this will result in more latency
|
||||
whiteboardInfoBroadcastFreq: 1
|
||||
|
||||
frontend:
|
||||
# When an editable whiteboard is loading in a client,
|
||||
# should it be started in read-only mode.
|
||||
setReadOnlyOnWhiteboardLoad: false
|
||||
# Show smallest screen indicator
|
||||
showSmallestScreenIndicator: true
|
||||
# performance:
|
4033
package-lock.json
generated
4033
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@
|
||||
"build": "webpack --config config/webpack.build.js",
|
||||
"start:dev": "node scripts/server.js --mode=development",
|
||||
"start:prod": "npm run build && node scripts/server.js --mode=production",
|
||||
"test": "echo \"No tests needed!\" && exit 1",
|
||||
"test": "jest",
|
||||
"pretty-quick": "pretty-quick",
|
||||
"format": "prettier --write .",
|
||||
"style": "prettier --check ."
|
||||
@ -28,10 +28,12 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "6.12.2",
|
||||
"dompurify": "^2.0.7",
|
||||
"express": "4.*",
|
||||
"formidable": "1.*",
|
||||
"fs-extra": "7.*",
|
||||
"js-yaml": "3.13.1",
|
||||
"jsdom": "^14.0.0",
|
||||
"pdfjs-dist": "^2.3.200",
|
||||
"socket.io": "2.*",
|
||||
@ -54,6 +56,7 @@
|
||||
"css-loader": "^3.5.2",
|
||||
"html-webpack-plugin": "^4.2.0",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "26.0.1",
|
||||
"jquery": "^3.2.1",
|
||||
"jquery-ui": "^1.12.1",
|
||||
"keymage": "^1.1.3",
|
||||
|
@ -85,7 +85,7 @@ class WhiteboardServerSideInfo {
|
||||
nbConnectedUsers: this._nbConnectedUsers,
|
||||
};
|
||||
|
||||
if (!config.disableSmallestScreen) {
|
||||
if (config.frontend.showSmallestScreenIndicator) {
|
||||
out.smallestScreenResolution = this.getSmallestScreenResolution();
|
||||
}
|
||||
|
||||
|
46
scripts/config-schema.json
Normal file
46
scripts/config-schema.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"$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": ["setReadOnlyOnWhiteboardLoad", "showSmallestScreenIndicator"],
|
||||
"properties": {
|
||||
"setReadOnlyOnWhiteboardLoad": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"showSmallestScreenIndicator": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["backend", "frontend"],
|
||||
"additionalProperties": false
|
||||
}
|
@ -1,45 +1,84 @@
|
||||
const { getArgs } = require("./utils");
|
||||
const util = require("util");
|
||||
|
||||
const config = {
|
||||
accessToken: "",
|
||||
disableSmallestScreen: false,
|
||||
webdav: false,
|
||||
const {
|
||||
getArgs,
|
||||
getDefaultConfig,
|
||||
getConfig,
|
||||
deepMergeConfigs,
|
||||
isConfigValid,
|
||||
} = require("./utils");
|
||||
|
||||
whiteboardInfoBroadcastFreq: 1, // once per second
|
||||
};
|
||||
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) {
|
||||
if (startArgs["accesstoken"]) {
|
||||
config.accessToken = startArgs["accesstoken"];
|
||||
}
|
||||
if (startArgs["disablesmallestscreen"]) {
|
||||
config.disableSmallestScreen = true;
|
||||
}
|
||||
if (startArgs["webdav"]) {
|
||||
config.webdav = true;
|
||||
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() {
|
||||
if (process.env.accesstoken) {
|
||||
config.accessToken = process.env.accesstoken;
|
||||
}
|
||||
if (process.env.disablesmallestscreen) {
|
||||
config.disablesmallestscreen = true;
|
||||
}
|
||||
if (process.env.webdav) {
|
||||
config.webdav = true;
|
||||
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();
|
||||
updateConfigFromStartArgs(getArgs());
|
||||
// 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;
|
||||
|
@ -4,8 +4,6 @@ const config = require("./config");
|
||||
const WhiteboardServerSideInfo = require("./WhiteboardServerSideInfo");
|
||||
|
||||
function startBackendServer(port) {
|
||||
console.info("Starting backend server with config", config);
|
||||
|
||||
var fs = require("fs-extra");
|
||||
var express = require("express");
|
||||
var formidable = require("formidable"); //form upload processing
|
||||
@ -27,7 +25,7 @@ function startBackendServer(port) {
|
||||
var io = require("socket.io")(server, { path: "/ws-api" });
|
||||
console.log("Webserver & socketserver running on port:" + port);
|
||||
|
||||
const { accessToken, webdav } = config;
|
||||
const { accessToken, webdav } = config.backend;
|
||||
|
||||
app.get("/api/loadwhiteboard", function (req, res) {
|
||||
var wid = req["query"]["wid"];
|
||||
@ -191,7 +189,7 @@ function startBackendServer(port) {
|
||||
info.infoWasSent();
|
||||
}
|
||||
});
|
||||
}, (1 / config.whiteboardInfoBroadcastFreq) * 1000);
|
||||
}, (1 / config.backend.performance.whiteboardInfoBroadcastFreq) * 1000);
|
||||
|
||||
io.on("connection", function (socket) {
|
||||
var whiteboardId = null;
|
||||
|
@ -1,3 +1,12 @@
|
||||
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");
|
||||
|
||||
function getArgs() {
|
||||
const args = {};
|
||||
process.argv.slice(2, process.argv.length).forEach((arg) => {
|
||||
@ -17,4 +26,68 @@ function getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
*
|
||||
* @param path
|
||||
* @return {any}
|
||||
*/
|
||||
function getConfig(path) {
|
||||
return yaml.safeLoad(fs.readFileSync(path, "utf8"));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @param config
|
||||
* @param warn
|
||||
* @return {*}
|
||||
*/
|
||||
function isConfigValid(config, warn = true) {
|
||||
const validate = ajv.compile(configSchema);
|
||||
const isValid = validate(config);
|
||||
|
||||
if (!isValid && warn) console.warn(validate.errors);
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* @return {*}
|
||||
*/
|
||||
function getDefaultConfig() {
|
||||
const defaultConfigPath = path.join(__dirname, "..", "config.default.yml");
|
||||
return getConfig(defaultConfigPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Deep merges objects, not arrays.
|
||||
*
|
||||
* @param baseConfig
|
||||
* @param overrideConfig
|
||||
* @return {{}}
|
||||
*/
|
||||
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.getArgs = getArgs;
|
||||
module.exports.getConfig = getConfig;
|
||||
module.exports.getDefaultConfig = getDefaultConfig;
|
||||
module.exports.deepMergeConfigs = deepMergeConfigs;
|
||||
module.exports.isConfigValid = isConfigValid;
|
||||
|
48
scripts/utils.test.js
Normal file
48
scripts/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: { setReadOnlyOnWhiteboardLoad: true } };
|
||||
|
||||
expect(
|
||||
deepMergeConfigs(defaultConfig, overrideConfig1).frontend.setReadOnlyOnWhiteboardLoad
|
||||
).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);
|
||||
});
|
Loading…
Reference in New Issue
Block a user