feat: read config from env if config.json is not found

This commit is contained in:
Axel Cocat 2022-03-14 22:42:28 +01:00
parent 05706007f1
commit a462b6af1b
6 changed files with 141 additions and 5 deletions

21
.env.example Normal file
View file

@ -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

1
.gitignore vendored
View file

@ -11,6 +11,7 @@ data/*
# Configuration # Configuration
config.json config.json
.env
# Log files # Log files
*.log *.log

18
package-lock.json generated
View file

@ -1,17 +1,18 @@
{ {
"name": "azerothcore-armory", "name": "azerothcore-armory",
"version": "0.10.0", "version": "0.11.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "azerothcore-armory", "name": "azerothcore-armory",
"version": "0.10.0", "version": "0.11.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@supercharge/promise-pool": "^2.1.0", "@supercharge/promise-pool": "^2.1.0",
"camelcase": "^6.3.0", "camelcase": "^6.3.0",
"cli-progress": "^3.10.0", "cli-progress": "^3.10.0",
"dotenv": "^16.0.0",
"express": "^4.17.3", "express": "^4.17.3",
"express-handlebars": "^6.0.2", "express-handlebars": "^6.0.2",
"glob": "^7.2.0", "glob": "^7.2.0",
@ -1589,6 +1590,14 @@
"node": ">=8" "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": { "node_modules/duplexer3": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
@ -6394,6 +6403,11 @@
"is-obj": "^2.0.0" "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": { "duplexer3": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",

View file

@ -1,6 +1,6 @@
{ {
"name": "azerothcore-armory", "name": "azerothcore-armory",
"version": "0.10.0", "version": "0.11.0",
"description": "", "description": "",
"scripts": { "scripts": {
"build": "tsc -p tsconfig.json", "build": "tsc -p tsconfig.json",
@ -32,6 +32,7 @@
"@supercharge/promise-pool": "^2.1.0", "@supercharge/promise-pool": "^2.1.0",
"camelcase": "^6.3.0", "camelcase": "^6.3.0",
"cli-progress": "^3.10.0", "cli-progress": "^3.10.0",
"dotenv": "^16.0.0",
"express": "^4.17.3", "express": "^4.17.3",
"express-handlebars": "^6.0.2", "express-handlebars": "^6.0.2",
"glob": "^7.2.0", "glob": "^7.2.0",

View file

@ -34,9 +34,19 @@ export class Config {
public worldDatabase: IDatabaseConfig; public worldDatabase: IDatabaseConfig;
public dbQueryTimeout: number; public dbQueryTimeout: number;
private static envPrefix: string = "ACORE_ARMORY";
private static checkedMissingField: boolean = false; private static checkedMissingField: boolean = false;
public static async load(logger: winston.Logger): Promise<Config> { public static async load(logger: winston.Logger): Promise<Config> {
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<Config> {
const json: Buffer = await fsp.readFile("config.json"); const json: Buffer = await fsp.readFile("config.json");
const config = JSON.parse(json.toString()) as Config; const config = JSON.parse(json.toString()) as Config;
@ -50,13 +60,101 @@ export class Config {
return config; return config;
} }
private static async loadFromEnv(logger: winston.Logger): Promise<Config> {
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 = "") { private static checkAllMissingFields(logger: winston.Logger, obj: object, model: object, parentName: string = "") {
const missing = Config.hasMissingFields(obj, model); const missing = Config.hasMissingFields(obj, model);
if (parentName !== "") { if (parentName !== "") {
parentName += "."; parentName += ".";
} }
for (const field of missing) { 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) { for (const key in model) {
if (typeof model[key] === "object" && obj.hasOwnProperty(key)) { if (typeof model[key] === "object" && obj.hasOwnProperty(key)) {

View file

@ -1,10 +1,11 @@
import "dotenv/config";
import { Armory } from "./Armory"; import { Armory } from "./Armory";
async function main(): Promise<void> { async function main(): Promise<void> {
require("source-map-support").install(); require("source-map-support").install();
const armory = new Armory(); const armory = new Armory();
armory.start(); await armory.start();
} }
main(); main();