Wrap each migration in a transaction

This commit is contained in:
Alice Gaudon 2020-06-04 17:27:05 +02:00
parent 0970ff3116
commit 91b9e51a7d
7 changed files with 44 additions and 34 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "wms-core", "name": "wms-core",
"version": "0.5.0", "version": "0.6.0",
"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

@ -1,9 +1,9 @@
import Migration from "../../db/Migration"; import Migration from "../../db/Migration";
import {query} from "../../db/MysqlConnectionManager"; import {Connection} from "mysql";
export default class CreateMagicLinksTable extends Migration { export default class CreateMagicLinksTable extends Migration {
async install(): Promise<void> { async install(connection: Connection): Promise<void> {
await query('CREATE TABLE magic_links(' + await this.query('CREATE TABLE magic_links(' +
'id INT NOT NULL AUTO_INCREMENT,' + 'id INT NOT NULL AUTO_INCREMENT,' +
'session_id CHAR(32) UNIQUE NOT NULL,' + 'session_id CHAR(32) UNIQUE NOT NULL,' +
'email VARCHAR(254) NOT NULL,' + 'email VARCHAR(254) NOT NULL,' +
@ -13,11 +13,11 @@ export default class CreateMagicLinksTable extends Migration {
'generated_at DATETIME NOT NULL,' + 'generated_at DATETIME NOT NULL,' +
'authorized BOOLEAN NOT NULL,' + 'authorized BOOLEAN NOT NULL,' +
'PRIMARY KEY(id)' + 'PRIMARY KEY(id)' +
')'); ')', connection);
} }
async rollback(): Promise<void> { async rollback(connection: Connection): Promise<void> {
await query('DROP TABLE magic_links'); await this.query('DROP TABLE magic_links', connection);
} }
} }

View File

@ -1,17 +1,17 @@
import Migration from "../../db/Migration"; import Migration from "../../db/Migration";
import {query} from "../../db/MysqlConnectionManager"; import {Connection} from "mysql";
export default class CreateUsersAndUserEmailsTable extends Migration { export default class CreateUsersAndUserEmailsTable extends Migration {
async install(): Promise<void> { async install(connection: Connection): Promise<void> {
await query('CREATE TABLE users(' + await this.query('CREATE TABLE users(' +
'id INT NOT NULL AUTO_INCREMENT,' + 'id INT NOT NULL AUTO_INCREMENT,' +
'name VARCHAR(64),' + 'name VARCHAR(64),' +
'is_admin BOOLEAN NOT NULL DEFAULT false,' + 'is_admin BOOLEAN NOT NULL DEFAULT false,' +
'created_at DATETIME NOT NULL DEFAULT NOW(),' + 'created_at DATETIME NOT NULL DEFAULT NOW(),' +
'updated_at DATETIME NOT NULL DEFAULT NOW(),' + 'updated_at DATETIME NOT NULL DEFAULT NOW(),' +
'PRIMARY KEY(id)' + 'PRIMARY KEY(id)' +
')'); ')', connection);
await query('CREATE TABLE user_emails(' + await this.query('CREATE TABLE user_emails(' +
'id INT NOT NULL AUTO_INCREMENT,' + 'id INT NOT NULL AUTO_INCREMENT,' +
'user_id INT,' + 'user_id INT,' +
'email VARCHAR(254) UNIQUE NOT NULL,' + 'email VARCHAR(254) UNIQUE NOT NULL,' +
@ -19,11 +19,11 @@ export default class CreateUsersAndUserEmailsTable extends Migration {
'created_at DATETIME NOT NULL DEFAULT NOW(),' + 'created_at DATETIME NOT NULL DEFAULT NOW(),' +
'PRIMARY KEY(id),' + 'PRIMARY KEY(id),' +
'FOREIGN KEY user_fk (user_id) REFERENCES users (id) ON DELETE CASCADE' + 'FOREIGN KEY user_fk (user_id) REFERENCES users (id) ON DELETE CASCADE' +
')'); ')', connection);
} }
async rollback(): Promise<void> { async rollback(connection: Connection): Promise<void> {
await query('DROP TABLE user_emails'); await this.query('DROP TABLE user_emails', connection);
await query('DROP TABLE users'); await this.query('DROP TABLE users', connection);
} }
} }

View File

@ -1,3 +1,6 @@
import {Connection} from "mysql";
import MysqlConnectionManager from "./MysqlConnectionManager";
export default abstract class Migration { export default abstract class Migration {
public readonly version: number; public readonly version: number;
@ -9,7 +12,11 @@ export default abstract class Migration {
return this.version > currentVersion; return this.version > currentVersion;
} }
abstract async install(): Promise<void>; abstract async install(connection: Connection): Promise<void>;
abstract async rollback(): Promise<void>; abstract async rollback(connection: Connection): Promise<void>;
protected async query(queryString: string, connection: Connection): Promise<void> {
await MysqlConnectionManager.query(queryString, undefined, connection);
}
} }

View File

@ -167,11 +167,13 @@ export default class MysqlConnectionManager {
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);
await migration.install(); await MysqlConnectionManager.wrapTransaction<void>(async c => {
await migration.install(c);
await query('INSERT INTO migrations VALUES(?, ?, NOW())', [ await query('INSERT INTO migrations VALUES(?, ?, NOW())', [
migration.version, migration.version,
migration.constructor.name, migration.constructor.name,
]); ]);
});
} }
} }
} }

View File

@ -1,12 +1,12 @@
import Migration from "../db/Migration"; import Migration from "../db/Migration";
import {query} from "../db/MysqlConnectionManager"; import {Connection} from "mysql";
/** /**
* Must be the first migration * Must be the first migration
*/ */
export default class CreateLogsTable extends Migration { export default class CreateLogsTable extends Migration {
async install(): Promise<void> { async install(connection: Connection): Promise<void> {
await query('CREATE TABLE logs(' + await this.query('CREATE TABLE logs(' +
'id INT NOT NULL AUTO_INCREMENT,' + 'id INT NOT NULL AUTO_INCREMENT,' +
'level TINYINT UNSIGNED NOT NULL,' + 'level TINYINT UNSIGNED NOT NULL,' +
'message TEXT NOT NULL,' + 'message TEXT NOT NULL,' +
@ -16,10 +16,10 @@ export default class CreateLogsTable extends Migration {
'error_stack TEXT,' + 'error_stack TEXT,' +
'created_at DATETIME NOT NULL DEFAULT NOW(),' + 'created_at DATETIME NOT NULL DEFAULT NOW(),' +
'PRIMARY KEY (id)' + 'PRIMARY KEY (id)' +
')'); ')', connection);
} }
async rollback(): Promise<void> { async rollback(connection: Connection): Promise<void> {
await query('DROP TABLE logs'); await this.query('DROP TABLE logs', connection);
} }
} }

View File

@ -1,4 +1,5 @@
import Migration from "../db/Migration"; import Migration from "../db/Migration";
import {Connection} from "mysql";
import {query} from "../db/MysqlConnectionManager"; import {query} from "../db/MysqlConnectionManager";
/** /**
@ -17,16 +18,16 @@ export default class CreateMigrationsTable extends Migration {
return await super.shouldRun(currentVersion); return await super.shouldRun(currentVersion);
} }
async install(): Promise<void> { async install(connection: Connection): Promise<void> {
await query('CREATE TABLE migrations(' + await this.query('CREATE TABLE migrations(' +
'id INT NOT NULL,' + 'id INT NOT NULL,' +
'name VARCHAR(64) NOT NULL,' + 'name VARCHAR(64) NOT NULL,' +
'migration_date DATE,' + 'migration_date DATE,' +
'PRIMARY KEY (id)' + 'PRIMARY KEY (id)' +
')'); ')', connection);
} }
async rollback(): Promise<void> { async rollback(connection: Connection): Promise<void> {
await query('DROP TABLE migrations'); await this.query('DROP TABLE migrations', connection);
} }
} }