Files
talk/services/migration.js
T
2017-06-06 17:31:52 -06:00

143 lines
3.8 KiB
JavaScript

const MigrationModel = require('../models/migration');
const fs = require('fs');
const path = require('path');
const Joi = require('joi');
const sc = require('snake-case');
const migrationTemplate = `
module.exports = {
async up() {
}
};
`;
module.exports = class MigrationService {
static async create(name) {
if (!name || typeof name !== 'string' || name.length === 0) {
throw new Error('name must be defined');
}
let version = Math.round(Date.now() / 1000, 0);
let filename = path.join(__dirname, '..', 'migrations', `${version}_${sc(name)}.js`);
fs.writeFileSync(filename, migrationTemplate, 'utf8');
console.log(`Created migration ${version} in ${filename}`);
}
static async listPending() {
// Get all the migration files.
let migrationFiles = fs.readdirSync(path.join(__dirname, '..', 'migrations'));
// Ensure that all migrations follow this format.
const migrationSchema = Joi.object({
up: Joi.func().required(),
down: Joi.func()
});
// Extract the version from the filename with this regex.
const versionRe = /(\d+)_([\S_]+)\.js/;
// Get the latest version.
let latestVersion = await MigrationService.latestVersion();
// Parse the migrations from the file listing.
let migrations = migrationFiles
.map((filename) => {
// Parse the version from the filename.
let matches = filename.match(versionRe);
if (matches.length !== 3) {
return null;
}
let version = parseInt(matches[1]);
// Don't rerun migrations from versions already ran.
if (version <= latestVersion) {
return null;
}
// Read the migration from the filesystem.
let migration = require(`../migrations/${filename}`);
Joi.assert(migration, migrationSchema, `Migration ${filename} does did not pass validation`);
return {
filename,
version,
migration
};
})
.filter((migration) => migration !== null)
.sort((a, b) => {
if (a.version < b.version) {
return -1;
}
if (a.version > b.version) {
return 1;
}
return 0;
});
return migrations;
}
static async run(migrations) {
if (migrations.length === 0) {
console.log('No migrations to run!');
return;
}
for (let {version, migration} of migrations) {
console.log(`Starting migration ${version}.js`);
await migration.up();
console.log(`Finished migration ${version}.js`);
console.log(`Recording migration ${version}.js`);
// Record that the migration was finished.
let m = new MigrationModel({version});
await m.save();
console.log(`Finished recording migration ${version}.js`);
}
}
static async latestVersion() {
// Load the latest migration details from the database.
let latestMigration = await MigrationModel
.find({})
.sort({version: -1})
.limit(1)
.exec();
// If there weren't any migrations at all, then error out.
if (latestMigration.length === 0) {
return null;
}
// If the latest migration does not match the required version, then error
// out.
return latestMigration[0].version;
}
static async verify(requiredVersion) {
// If the requiredVersion isn't specified or is 0, then don't complain.
if (typeof requiredVersion !== 'number' || requiredVersion === 0) {
return;
}
// If the latest migration does not match the required version, then error
// out.
let latestVersion = await MigrationService.latestVersion();
if (!latestVersion || latestVersion < requiredVersion) {
throw new Error(`A database migration is required, version required ${requiredVersion}, found ${latestVersion}. Please run \`./bin/cli migration run\``);
}
}
};