Files
talk/services/migration.js
T
2017-06-07 11:08:39 -06:00

181 lines
4.9 KiB
JavaScript

const MigrationModel = require('../models/migration');
const fs = require('fs');
const path = require('path');
const Joi = require('joi');
const debug = require('debug')('talk:services:migration');
const sc = require('snake-case');
const {talk: {migration: {minVersion}}} = require('../package.json');
const migrationTemplate = `module.exports = {
async up() {
}
};
`;
class MigrationService {
/**
* Creates a new migration file.
*
* @param {String} name name of the migration
*/
static async create(name) {
if (!name || typeof name !== 'string' || name.length === 0) {
throw new Error('name must be defined');
}
// Create a new Migration based on the current time.
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}`);
}
/**
* Returns a list of all pending migrations.
*/
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;
}
/**
* Runs an list of migrations.
*
* @param {Array} migrations a list of migrations returned by `listPending`
*/
static async run(migrations) {
if (migrations.length === 0) {
console.log('No migrations to run!');
return;
}
for (let {filename, version, migration} of migrations) {
try {
console.log(`Starting migration ${filename}`);
await migration.up();
console.log(`Finished migration ${filename}`);
} catch (e) {
console.error(`Migration ${filename} failed`);
throw e;
}
try {
console.log(`Recording migration ${filename}`);
// Record that the migration was finished.
let m = new MigrationModel({version});
await m.save();
console.log(`Finished recording migration ${filename}`);
} catch (e) {
console.error(`Migration ${filename} could not be recorded`);
throw e;
}
}
console.log(`Database now at migration version ${migrations[migrations.length - 1].version}`);
}
/**
* Returns the latest migration version number that has been applied to the
* database, null if none were found.
*/
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() {
// If the requiredVersion isn't specified or is 0, then don't complain.
if (typeof minVersion !== 'number' || minVersion === 0) {
return;
}
// If the latest migration does not match the required version, then error
// out.
let latestVersion = await MigrationService.latestVersion();
if (!latestVersion || latestVersion < minVersion) {
throw new Error(`A database migration is required, version required ${minVersion}, found ${latestVersion}. Please run \`./bin/cli migration run\``);
}
debug(`minimum migration version ${minVersion} was met with version ${latestVersion}`);
return latestVersion;
}
}
module.exports = MigrationService;