diff --git a/.gitignore b/.gitignore index 36e8cf3..08817c7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ sqz-force greenvironment.db logs logs* +config/* diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..c9d4d01 --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +config/default.toml diff --git a/CHANGELOG.md b/CHANGELOG.md index 40fdbcb..c6b3178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - gql field userVote requires a userId - default findUser param limit to 20 - only group admins can create group events +- config behaviour to use all files that reside in the ./config directory with the .toml format ### Fixed diff --git a/config/default.toml b/config/default.toml new file mode 100644 index 0000000..16dc426 --- /dev/null +++ b/config/default.toml @@ -0,0 +1,26 @@ +# Configuration of the database connection +[database] +connectionUri = "sqlite://greenvironment.db" + +# Configuration of the http server +[server] +port = 8080 +cors = false + +# Configuration for the sessions +[session] +secret = "REPLACE WITH SAFE RANDOM GENERATED SECRET" +cookieMaxAge = 6048e+5 # 7 days + +# Configuration for markdown rendering +[markdown] +plugins = ["markdown-it-emoji"] + +# Configuration for logging +[logging] +level = "info" + +# Configuration for the frontend files +[frontend] +angularIndex = "index.html" +publicPath = "./public" diff --git a/package.json b/package.json index 10ab79b..194c873 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,9 @@ "typescript": "^3.7.2" }, "dependencies": { + "@types/config": "^0.0.36", "compression": "^1.7.4", + "config": "^3.2.4", "connect-session-sequelize": "^6.0.0", "cookie-parser": "^1.4.4", "cors": "^2.8.5", @@ -82,6 +84,7 @@ "socket.io": "^2.2.0", "socket.io-redis": "^5.2.0", "sqlite3": "^4.1.0", + "toml": "^3.0.0", "uuid": "^3.3.3", "winston": "^3.2.1", "winston-daily-rotate-file": "^4.2.1" diff --git a/src/app.ts b/src/app.ts index d0e28dd..d5b8ec2 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,4 +1,5 @@ import * as compression from "compression"; +import * as config from "config"; import * as cookieParser from "cookie-parser"; import * as cors from "cors"; import {Request, Response} from "express"; @@ -64,8 +65,8 @@ class App { this.app = express(); this.server = new http.Server(this.app); this.io = socketIo(this.server); - this.sequelize = new Sequelize(globals.config.database.connectionUri); - this.publicPath = globals.config.frontend.publicPath; + this.sequelize = new Sequelize(config.get("database.connectionUri")); + this.publicPath = config.get("frontend.publicPath"); if (!path.isAbsolute(this.publicPath)) { this.publicPath = path.normalize(path.join(__dirname, this.publicPath)); } @@ -79,13 +80,13 @@ class App { const appSession = session({ cookie: { - maxAge: Number(globals.config.session.cookieMaxAge) || 604800000, + maxAge: Number(config.get("session.cookieMaxAge")), // @ts-ignore secure: "auto", }, resave: false, saveUninitialized: false, - secret: globals.config.session.secret, + secret: config.get("session.secret"), store: new SequelizeStore({db: this.sequelize}), }); @@ -112,7 +113,7 @@ class App { this.app.use(cookieParser()); this.app.use(appSession); // enable cross origin requests if enabled in the config - if (globals.config.server?.cors) { + if (config.get("server.cors")) { this.app.use(cors()); } // handle authentication via bearer in the Authorization header @@ -166,8 +167,8 @@ class App { }); // redirect all request to the angular file this.app.use((req: any, res: Response) => { - if (globals.config.frontend.angularIndex) { - const angularIndex = path.join(this.publicPath, globals.config.frontend.angularIndex); + if (config.get("frontend.angularIndex")) { + const angularIndex = path.join(this.publicPath, config.get("frontend.angularIndex")); if (fsx.existsSync(path.join(angularIndex))) { res.sendFile(angularIndex); } else { @@ -191,10 +192,10 @@ class App { * Starts the web server. */ public start(): void { - if (globals.config.server?.port) { + if (config.has("server.port")) { logger.info(`Starting server...`); - this.app.listen(globals.config.server.port); - logger.info(`Server running on port ${globals.config.server.port}`); + this.app.listen(config.get("server.port")); + logger.info(`Server running on port ${config.get("server.port")}`); } else { logger.error("No port specified in the config." + "Please configure a port in the config.yaml."); diff --git a/src/default-config.yaml b/src/default-config.yaml deleted file mode 100644 index 6794925..0000000 --- a/src/default-config.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# database connection info -database: - connectionUri: "sqlite://greenvironment.db" - -# http server configuration -server: - port: 8080 - cors: false - -# configuration of sessions -session: - secret: REPLACE WITH SAFE RANDOM GENERATED SECRET - cookieMaxAge: 604800000‬ # 7 days - -# configuration of the markdown parser -markdown: - plugins: - - 'markdown-it-emoji' - -# configuration for logging -logging: - level: info - -frontend: - angularIndex: index.html - publicPath: ./public diff --git a/src/index.ts b/src/index.ts index 5099ca5..9b9724f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +process.env.NODE_CONFIG_DIR = __dirname + "/../config"; // tslint:disable:no-console import * as cluster from "cluster"; import App from "./app"; diff --git a/src/lib/globals.ts b/src/lib/globals.ts index b6f0502..8847300 100644 --- a/src/lib/globals.ts +++ b/src/lib/globals.ts @@ -1,28 +1,13 @@ +import * as config from "config"; import {EventEmitter} from "events"; -import * as fsx from "fs-extra"; -import * as yaml from "js-yaml"; -import * as path from "path"; import * as winston from "winston"; require("winston-daily-rotate-file"); -const configPath = "config.yaml"; -const defaultConfig = path.join(__dirname, "/../default-config.yaml"); - -// ensure that the config exists by copying the default config. -if (!(fsx.pathExistsSync(configPath))) { - fsx.copySync(defaultConfig, configPath); -} else { - const conf = yaml.safeLoad(fsx.readFileSync(configPath, "utf-8")); - const defConf = yaml.safeLoad(fsx.readFileSync(defaultConfig, "utf-8")); - fsx.writeFileSync(configPath, yaml.safeDump(Object.assign(defConf, conf))); -} - /** * Defines global variables to be used. */ namespace globals { - export const config: IConfig = yaml.safeLoad(fsx.readFileSync("config.yaml", "utf-8")); // @ts-ignore export const logger: winston.Logger = winston.createLogger({ transports: [ @@ -34,7 +19,7 @@ namespace globals { return `${timestamp} ${level}: ${message}`; }), ), - level: config.logging?.level ?? "info", + level: config.get("logging.level"), }), // @ts-ignore new (winston.transports.DailyRotateFile)({ @@ -47,7 +32,7 @@ namespace globals { }), ), json: false, - level: config.logging?.level ?? "info", + level: config.get("logging.level"), maxFiles: "7d", zippedArchive: true, }), diff --git a/src/lib/markdown.ts b/src/lib/markdown.ts index 5515569..8dbbe03 100644 --- a/src/lib/markdown.ts +++ b/src/lib/markdown.ts @@ -1,3 +1,4 @@ +import * as config from "config"; import * as MarkdownIt from "markdown-it/lib"; import globals from "./globals"; @@ -5,7 +6,7 @@ namespace markdown { const md = new MarkdownIt(); - for (const pluginName of globals.config.markdown.plugins) { + for (const pluginName of config.get("markdown.plugins") as string[]) { try { const plugin = require(pluginName); if (plugin) { diff --git a/src/lib/models/Post.ts b/src/lib/models/Post.ts index af67f06..7aac135 100644 --- a/src/lib/models/Post.ts +++ b/src/lib/models/Post.ts @@ -1,6 +1,5 @@ import * as sqz from "sequelize"; import {BelongsTo, BelongsToMany, Column, CreatedAt, ForeignKey, Model, NotNull, Table} from "sequelize-typescript"; -import globals from "../globals"; import markdown from "../markdown"; import {Activity} from "./Activity"; import {PostVote, VoteType} from "./PostVote"; diff --git a/yarn.lock b/yarn.lock index ff3abbf..778cba0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -55,6 +55,11 @@ dependencies: "@types/express" "*" +"@types/config@^0.0.36": + version "0.0.36" + resolved "https://registry.yarnpkg.com/@types/config/-/config-0.0.36.tgz#bf53ca640f3a1a6a2072a9f33e02a44def07a40b" + integrity sha512-EoAeT1MyFWh2BJvBDEFInY714bQBbHOAucqxqqhprhbBFqr+B7fuN5T9CJqUIGDzvwubnKKRqmSo6yPo0aSpNw== + "@types/connect-pg-simple@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@types/connect-pg-simple/-/connect-pg-simple-4.2.0.tgz#cb0d85701fa73d16be7a4050e4c95fe070a6df7f" @@ -1200,6 +1205,13 @@ concat-stream@^1.6.0: readable-stream "^2.2.2" typedarray "^0.0.6" +config@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/config/-/config-3.2.4.tgz#e60a908582991e800852f9cb60fcf424f3274a6c" + integrity sha512-H1XIGfnU1EAkfjSLn9ZvYDRx9lOezDViuzLDgiJ/lMeqjYe3q6iQfpcLt2NInckJgpAeekbNhQkmnnbdEDs9rw== + dependencies: + json5 "^1.0.1" + connect-session-sequelize@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/connect-session-sequelize/-/connect-session-sequelize-6.0.0.tgz#afd4456c65d92c9b6b955fdff00afff795e8a420" @@ -2988,6 +3000,13 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -5320,6 +5339,11 @@ token-stream@0.0.1: resolved "https://registry.yarnpkg.com/token-stream/-/token-stream-0.0.1.tgz#ceeefc717a76c4316f126d0b9dbaa55d7e7df01a" integrity sha1-zu78cXp2xDFvEm0LnbqlXX598Bo= +toml@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/toml/-/toml-3.0.0.tgz#342160f1af1904ec9d204d03a5d61222d762c5ee" + integrity sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w== + toposort-class@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toposort-class/-/toposort-class-1.0.1.tgz#7ffd1f78c8be28c3ba45cd4e1a3f5ee193bd9988"