Add command line interface and allow rolling back migrations

This commit is contained in:
Alice Gaudon 2020-06-05 14:32:39 +02:00
parent 91b9e51a7d
commit 499c66240b
5 changed files with 78 additions and 11 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "wms-core", "name": "wms-core",
"version": "0.6.0", "version": "0.6.8",
"description": "Node web framework", "description": "Node web framework",
"repository": "git@gitlab.com:ArisuOngaku/wms-core.git", "repository": "git@gitlab.com:ArisuOngaku/wms-core.git",
"author": "Alice Gaudon <alice@gaudon.pro>", "author": "Alice Gaudon <alice@gaudon.pro>",

View File

@ -48,6 +48,12 @@ export default abstract class Application {
// Register migrations // Register migrations
MysqlConnectionManager.registerMigrations(this.getMigrations()); MysqlConnectionManager.registerMigrations(this.getMigrations());
// Process command line
if (await this.processCommandLine()) {
await this.stop();
return;
}
// Register all components and alike // Register all components and alike
await this.init(); await this.init();
@ -118,6 +124,24 @@ export default abstract class Application {
this.ready = true; this.ready = true;
} }
protected async processCommandLine(): Promise<boolean> {
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<void> { async stop(): Promise<void> {
Logger.info('Stopping application...'); Logger.info('Stopping application...');

View File

@ -2,10 +2,15 @@ import config from "config";
import {v4 as uuid} from "uuid"; import {v4 as uuid} from "uuid";
import Log from "./models/Log"; import Log from "./models/Log";
const LOG_LEVEL: LogLevelKeys = <LogLevelKeys>config.get<string>('log_level');
const DB_LOG_LEVEL: LogLevelKeys = <LogLevelKeys>config.get<string>('db_log_level');
export default class Logger { export default class Logger {
private static logLevel: LogLevelKeys = <LogLevelKeys>config.get<string>('log_level');
private static dbLogLevel: LogLevelKeys = <LogLevelKeys>config.get<string>('db_log_level');
public static verbose() {
this.logLevel = <LogLevelKeys>LogLevel[LogLevel[this.logLevel] + 1] || this.logLevel;
this.dbLogLevel = <LogLevelKeys>LogLevel[LogLevel[this.dbLogLevel] + 1] || this.dbLogLevel;
}
public static silentError(error: Error, ...message: any[]): string { public static silentError(error: Error, ...message: any[]): string {
return this.log('ERROR', message, error, true) || ''; 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 { private static log(level: LogLevelKeys, message: any[], error?: Error, silent: boolean = false): string | null {
const levelIndex = LogLevel[level]; const levelIndex = LogLevel[level];
if (levelIndex <= LogLevel[LOG_LEVEL]) { if (levelIndex <= LogLevel[this.logLevel]) {
if (error) { if (error) {
if (levelIndex > LogLevel.ERROR) this.warn(`Wrong log level ${level} with attached error.`); if (levelIndex > LogLevel.ERROR) this.warn(`Wrong log level ${level} with attached error.`);
} else { } else {
@ -71,7 +76,7 @@ export default class Logger {
let output = `[${level}] `; let output = `[${level}] `;
let pad = output.length; 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)); output += computedMsg.replace(/\n/g, '\n' + ' '.repeat(pad));
switch (level) { switch (level) {
@ -94,7 +99,7 @@ export default class Logger {
break; break;
} }
if (levelIndex <= LogLevel[DB_LOG_LEVEL]) { if (levelIndex <= LogLevel[this.dbLogLevel]) {
log.save().catch(err => { log.save().catch(err => {
if (!silent && err.message.indexOf('ECONNREFUSED') < 0) { if (!silent && err.message.indexOf('ECONNREFUSED') < 0) {
console.error({save_err: err, error}); console.error({save_err: err, error});

View File

@ -45,7 +45,7 @@ export default class Mail {
} }
public static end() { public static end() {
this.transporter.close(); if (this.transporter) this.transporter.close();
} }
public static parse(template: string, data: any, textOnly: boolean): string { public static parse(template: string, data: any, textOnly: boolean): string {

View File

@ -32,7 +32,7 @@ export default class MysqlConnectionManager {
this.migrations.push(migration(this.migrations.length + 1)); 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) { if (config.get('mysql.create_database_automatically') === true) {
const dbName = config.get('mysql.database'); const dbName = config.get('mysql.database');
Logger.info(`Creating database ${dbName}...`); Logger.info(`Creating database ${dbName}...`);
@ -55,7 +55,7 @@ export default class MysqlConnectionManager {
} }
this.databaseReady = true; this.databaseReady = true;
await this.handleMigrations(); if (runMigrations) await this.handleMigrations();
} }
public static get pool(): Pool { public static get pool(): Pool {
@ -152,7 +152,7 @@ export default class MysqlConnectionManager {
}); });
} }
private static async handleMigrations() { public static async getCurrentMigrationVersion(): Promise<number> {
let currentVersion = 0; let currentVersion = 0;
try { 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) { for (const migration of this.migrations) {
if (await migration.shouldRun(currentVersion)) { if (await migration.shouldRun(currentVersion)) {
Logger.info('Running migration ', migration.version, migration.constructor.name); 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<void> {
migrationID--;
const migration = this.migrations[migrationID];
Logger.info('Rolling back migration ', migration.version, migration.constructor.name);
await MysqlConnectionManager.wrapTransaction<void>(async c => {
await migration.rollback(c);
await query('DELETE FROM migrations WHERE id=?', [migration.version]);
});
}
public static async migrationCommand(args: string[]): Promise<void> {
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();
}
}
} }