From 499c66240bd40163f3b70fe8a071ef018e0e6909 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Fri, 5 Jun 2020 14:32:39 +0200 Subject: [PATCH] Add command line interface and allow rolling back migrations --- package.json | 2 +- src/Application.ts | 24 +++++++++++++++++ src/Logger.ts | 17 +++++++----- src/Mail.ts | 2 +- src/db/MysqlConnectionManager.ts | 44 +++++++++++++++++++++++++++++--- 5 files changed, 78 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index e55d1e4..6b018e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wms-core", - "version": "0.6.0", + "version": "0.6.8", "description": "Node web framework", "repository": "git@gitlab.com:ArisuOngaku/wms-core.git", "author": "Alice Gaudon ", diff --git a/src/Application.ts b/src/Application.ts index ef26f31..d9bdec9 100644 --- a/src/Application.ts +++ b/src/Application.ts @@ -48,6 +48,12 @@ export default abstract class Application { // Register migrations MysqlConnectionManager.registerMigrations(this.getMigrations()); + // Process command line + if (await this.processCommandLine()) { + await this.stop(); + return; + } + // Register all components and alike await this.init(); @@ -118,6 +124,24 @@ export default abstract class Application { this.ready = true; } + protected async processCommandLine(): Promise { + const args = process.argv; + for (let i = 2; i < args.length; i++) { + switch (args[i]) { + case '--verbose': + Logger.verbose(); + break; + case 'migration': + await MysqlConnectionManager.migrationCommand(args.slice(i + 1)); + return true; + default: + Logger.warn('Unrecognized argument', args[i]); + return true; + } + } + return false; + } + async stop(): Promise { Logger.info('Stopping application...'); diff --git a/src/Logger.ts b/src/Logger.ts index 9736e21..8d7db60 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -2,10 +2,15 @@ import config from "config"; import {v4 as uuid} from "uuid"; import Log from "./models/Log"; -const LOG_LEVEL: LogLevelKeys = config.get('log_level'); -const DB_LOG_LEVEL: LogLevelKeys = config.get('db_log_level'); - export default class Logger { + private static logLevel: LogLevelKeys = config.get('log_level'); + private static dbLogLevel: LogLevelKeys = config.get('db_log_level'); + + public static verbose() { + this.logLevel = LogLevel[LogLevel[this.logLevel] + 1] || this.logLevel; + this.dbLogLevel = LogLevel[LogLevel[this.dbLogLevel] + 1] || this.dbLogLevel; + } + public static silentError(error: Error, ...message: any[]): string { return this.log('ERROR', message, error, true) || ''; } @@ -32,7 +37,7 @@ export default class Logger { private static log(level: LogLevelKeys, message: any[], error?: Error, silent: boolean = false): string | null { const levelIndex = LogLevel[level]; - if (levelIndex <= LogLevel[LOG_LEVEL]) { + if (levelIndex <= LogLevel[this.logLevel]) { if (error) { if (levelIndex > LogLevel.ERROR) this.warn(`Wrong log level ${level} with attached error.`); } else { @@ -71,7 +76,7 @@ export default class Logger { let output = `[${level}] `; let pad = output.length; - if (levelIndex <= LogLevel[DB_LOG_LEVEL]) output += `${log.getLogID()} - `; + if (levelIndex <= LogLevel[this.dbLogLevel]) output += `${log.getLogID()} - `; output += computedMsg.replace(/\n/g, '\n' + ' '.repeat(pad)); switch (level) { @@ -94,7 +99,7 @@ export default class Logger { break; } - if (levelIndex <= LogLevel[DB_LOG_LEVEL]) { + if (levelIndex <= LogLevel[this.dbLogLevel]) { log.save().catch(err => { if (!silent && err.message.indexOf('ECONNREFUSED') < 0) { console.error({save_err: err, error}); diff --git a/src/Mail.ts b/src/Mail.ts index 3ce28e0..3355c44 100644 --- a/src/Mail.ts +++ b/src/Mail.ts @@ -45,7 +45,7 @@ export default class Mail { } public static end() { - this.transporter.close(); + if (this.transporter) this.transporter.close(); } public static parse(template: string, data: any, textOnly: boolean): string { diff --git a/src/db/MysqlConnectionManager.ts b/src/db/MysqlConnectionManager.ts index 48037cd..fa1d8bb 100644 --- a/src/db/MysqlConnectionManager.ts +++ b/src/db/MysqlConnectionManager.ts @@ -32,7 +32,7 @@ export default class MysqlConnectionManager { this.migrations.push(migration(this.migrations.length + 1)); } - public static async prepare() { + public static async prepare(runMigrations: boolean = true) { if (config.get('mysql.create_database_automatically') === true) { const dbName = config.get('mysql.database'); Logger.info(`Creating database ${dbName}...`); @@ -55,7 +55,7 @@ export default class MysqlConnectionManager { } this.databaseReady = true; - await this.handleMigrations(); + if (runMigrations) await this.handleMigrations(); } public static get pool(): Pool { @@ -152,7 +152,7 @@ export default class MysqlConnectionManager { }); } - private static async handleMigrations() { + public static async getCurrentMigrationVersion(): Promise { let currentVersion = 0; try { @@ -164,6 +164,11 @@ export default class MysqlConnectionManager { } } + return currentVersion; + } + + private static async handleMigrations() { + const currentVersion = await this.getCurrentMigrationVersion(); for (const migration of this.migrations) { if (await migration.shouldRun(currentVersion)) { Logger.info('Running migration ', migration.version, migration.constructor.name); @@ -177,4 +182,37 @@ export default class MysqlConnectionManager { } } } + + /** + * @param migrationID what migration to rollback. Use with caution. default=0 is for last registered migration. + */ + public static async rollbackMigration(migrationID: number = 0): Promise { + migrationID--; + const migration = this.migrations[migrationID]; + Logger.info('Rolling back migration ', migration.version, migration.constructor.name); + await MysqlConnectionManager.wrapTransaction(async c => { + await migration.rollback(c); + await query('DELETE FROM migrations WHERE id=?', [migration.version]); + }); + } + + public static async migrationCommand(args: string[]): Promise { + try { + Logger.info('Current migration:', await this.getCurrentMigrationVersion()); + + for (let i = 0; i < args.length; i++) { + if (args[i] === 'rollback') { + let migrationID = 0; + if (args.length > i + 1) { + migrationID = parseInt(args[i + 1]); + } + await this.prepare(false); + await this.rollbackMigration(migrationID); + return; + } + } + } finally { + await MysqlConnectionManager.endPool(); + } + } } \ No newline at end of file