From a462b6af1b729e48426e5bc2f465be7796b9a347 Mon Sep 17 00:00:00 2001 From: Axel Cocat Date: Mon, 14 Mar 2022 22:42:28 +0100 Subject: [PATCH] feat: read config from env if config.json is not found --- .env.example | 21 +++++++++ .gitignore | 1 + package-lock.json | 18 +++++++- package.json | 3 +- src/armory/Config.ts | 100 ++++++++++++++++++++++++++++++++++++++++++- src/armory/main.ts | 3 +- 6 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bc55db4 --- /dev/null +++ b/.env.example @@ -0,0 +1,21 @@ +ACORE_ARMORY_AOWOW_URL="https://wotlkdb.com" +ACORE_ARMORY_WEBSITE_URL="https://mywebsite.com" +ACORE_ARMORY_WEBSITE_NAME="My Website" +ACORE_ARMORY_IFRAME_MODE__ENABLED=0 +ACORE_ARMORY_IFRAME_MODE__URL="https://mywebsite.com/armory" +ACORE_ARMORY_LOAD_DBCS=1 +ACORE_ARMORY_HIDE_GAME_MASTERS=1 +ACORE_ARMORY_REALMS__0__NAME="AzerothCore" +ACORE_ARMORY_REALMS__0__REALM_ID=1 +ACORE_ARMORY_REALMS__0__AUTH_DATABASE="acore_auth" +ACORE_ARMORY_REALMS__0__CHARACTERS_DATABASE__HOST="localhost" +ACORE_ARMORY_REALMS__0__CHARACTERS_DATABASE__PORT=3306 +ACORE_ARMORY_REALMS__0__CHARACTERS_DATABASE__USER="root" +ACORE_ARMORY_REALMS__0__CHARACTERS_DATABASE__PASSWORD="root" +ACORE_ARMORY_REALMS__0__CHARACTERS_DATABASE__DATABASE="acore_characters" +ACORE_ARMORY_WORLD_DATABASE__HOST="localhost" +ACORE_ARMORY_WORLD_DATABASE__PORT=3306 +ACORE_ARMORY_WORLD_DATABASE__USER="root" +ACORE_ARMORY_WORLD_DATABASE__PASSWORD="root" +ACORE_ARMORY_WORLD_DATABASE__DATABASE="acore_world" +ACORE_ARMORY_DB_QUERY_TIMEOUT=10000 diff --git a/.gitignore b/.gitignore index be2a760..e68cfcd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ data/* # Configuration config.json +.env # Log files *.log diff --git a/package-lock.json b/package-lock.json index 74b0ebf..0118a38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "azerothcore-armory", - "version": "0.10.0", + "version": "0.11.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "azerothcore-armory", - "version": "0.10.0", + "version": "0.11.0", "license": "MIT", "dependencies": { "@supercharge/promise-pool": "^2.1.0", "camelcase": "^6.3.0", "cli-progress": "^3.10.0", + "dotenv": "^16.0.0", "express": "^4.17.3", "express-handlebars": "^6.0.2", "glob": "^7.2.0", @@ -1589,6 +1590,14 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==", + "engines": { + "node": ">=12" + } + }, "node_modules/duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -6394,6 +6403,11 @@ "is-obj": "^2.0.0" } }, + "dotenv": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.0.tgz", + "integrity": "sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==" + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", diff --git a/package.json b/package.json index 0552307..13468b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "azerothcore-armory", - "version": "0.10.0", + "version": "0.11.0", "description": "", "scripts": { "build": "tsc -p tsconfig.json", @@ -32,6 +32,7 @@ "@supercharge/promise-pool": "^2.1.0", "camelcase": "^6.3.0", "cli-progress": "^3.10.0", + "dotenv": "^16.0.0", "express": "^4.17.3", "express-handlebars": "^6.0.2", "glob": "^7.2.0", diff --git a/src/armory/Config.ts b/src/armory/Config.ts index 231c1f1..cca3e95 100644 --- a/src/armory/Config.ts +++ b/src/armory/Config.ts @@ -34,9 +34,19 @@ export class Config { public worldDatabase: IDatabaseConfig; public dbQueryTimeout: number; + private static envPrefix: string = "ACORE_ARMORY"; private static checkedMissingField: boolean = false; public static async load(logger: winston.Logger): Promise { + try { + await fsp.access("config.json"); + return await Config.loadFromFile(logger); + } catch (err) { + return await Config.loadFromEnv(logger); + } + } + + private static async loadFromFile(logger: winston.Logger): Promise { const json: Buffer = await fsp.readFile("config.json"); const config = JSON.parse(json.toString()) as Config; @@ -50,13 +60,101 @@ export class Config { return config; } + private static async loadFromEnv(logger: winston.Logger): Promise { + const config = {}; + const json = await fsp.readFile("config.default.json"); + const defaultConfig = JSON.parse(json.toString()); + Config.loadObjFromEnv(logger, config, defaultConfig); + Config.checkedMissingField = true; + return config as Config; + } + + private static loadObjFromEnv(logger: winston.Logger, obj: object, model: object, parentName: string = "") { + if (parentName !== "") { + parentName += "."; + } + + for (const field in model) { + if (!model.hasOwnProperty(field)) { + continue; + } + + if (Array.isArray(model[field])) { + obj[field] = Config.loadArrayFromEnv(logger, model[field][0], parentName + field); + } else if (typeof model[field] === "object") { + obj[field] = {}; + Config.loadObjFromEnv(logger, obj[field], model[field], parentName + field); + } else if (!obj.hasOwnProperty(field)) { + const key = Config.getEnvKey(parentName + field); + if (process.env.hasOwnProperty(key)) { + obj[field] = Config.parseEnvValue(process.env[key], model[field]); + } else if (!Config.checkedMissingField) { + logger.warn(`Config field ${key} is missing from .env!`); + } + } + } + } + + private static loadArrayFromEnv(logger: winston.Logger, model: any, parentName: string = ""): any[] { + if (parentName !== "") { + parentName += "."; + } + + const arr = []; + let i = 0; + while (true) { + const key = Config.getEnvKey(parentName + i); + const found = Object.keys(process.env).some(k => k.startsWith(key)); + if (!found) { + break; + } + + if (Array.isArray(model)) { + arr.push(Config.loadArrayFromEnv(logger, model[0], parentName + i)); + } else if (typeof model === "object") { + const obj = {}; + Config.loadObjFromEnv(logger, obj, model, parentName + i); + if (Object.keys(obj).length > 0) { + arr.push(obj); + } + } else if (process.env.hasOwnProperty(key)) { + arr.push(Config.parseEnvValue(process.env[key], model)); + } else { + break; + } + + ++i; + } + + return arr; + } + + private static getEnvKey(key: string): string { + return Config.envPrefix + "_" + key + .replace(/\./g, "__") + .replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) + .toUpperCase(); + } + + private static parseEnvValue(value: string, model: any): any { + const type = typeof model; + const lower = value.toLowerCase(); + if (type === "boolean") { + return lower === "true" || value === "1"; + } + if (type === "number") { + return parseFloat(value); + } + return value; + } + private static checkAllMissingFields(logger: winston.Logger, obj: object, model: object, parentName: string = "") { const missing = Config.hasMissingFields(obj, model); if (parentName !== "") { parentName += "."; } for (const field of missing) { - logger.warn(`Field ${parentName}${field} is missing in config.json!`); + logger.warn(`Field ${parentName}${field} is missing from config.json!`); } for (const key in model) { if (typeof model[key] === "object" && obj.hasOwnProperty(key)) { diff --git a/src/armory/main.ts b/src/armory/main.ts index 28fd0fa..cc8312e 100644 --- a/src/armory/main.ts +++ b/src/armory/main.ts @@ -1,10 +1,11 @@ +import "dotenv/config"; import { Armory } from "./Armory"; async function main(): Promise { require("source-map-support").install(); const armory = new Armory(); - armory.start(); + await armory.start(); } main();