mirror of
https://github.com/wassname/talk.git
synced 2026-06-28 13:55:22 +08:00
181 lines
4.9 KiB
JavaScript
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;
|