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",
"version": "0.6.0",
"version": "0.6.8",
"description": "Node web framework",
"repository": "git@gitlab.com:ArisuOngaku/wms-core.git",
"author": "Alice Gaudon <alice@gaudon.pro>",

View File

@ -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<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> {
Logger.info('Stopping application...');

View File

@ -2,10 +2,15 @@ import config from "config";
import {v4 as uuid} from "uuid";
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 {
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 {
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});

View File

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

View File

@ -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<number> {
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<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();
}
}
}