var _ = require('lodash'), when = require('when'), path = require('path'), fs = require('fs'), nodefn = require('when/node'), errors = require('../../errors'), sequence = require('when/sequence'), commands = require('./commands'), versioning = require('../versioning'), models = require('../../models'), fixtures = require('../fixtures'), schema = require('../schema').tables, dataExport = require('../export'), utils = require('../utils'), config = require('../../config'), schemaTables = _.keys(schema), // private logInfo, populateDefaultSettings, backupDatabase, // public init, reset, migrateUp, migrateUpFreshDb; logInfo = function logInfo(message) { errors.logInfo('Migrations', message); }; populateDefaultSettings = function populateDefaultSettings() { // Initialise the default settings logInfo('Populating default settings'); return models.Settings.populateDefaults().then(function () { logInfo('Complete'); }); }; backupDatabase = function backupDatabase() { logInfo('Creating database backup'); return dataExport().then(function (exportedData) { // Save the exported data to the file system for download var fileName = path.resolve(config().paths.contentPath + '/data/exported-' + (new Date().getTime()) + '.json'); return nodefn.call(fs.writeFile, fileName, JSON.stringify(exportedData)).then(function () { logInfo('Database backup written to: ' + fileName); }); }); }; // Check for whether data is needed to be bootstrapped or not init = function () { var self = this; // There are 4 possibilities: // 1. The database exists and is up-to-date // 2. The database exists but is out of date // 3. The database exists but the currentVersion setting does not or cannot be understood // 4. The database has not yet been created return versioning.getDatabaseVersion().then(function (databaseVersion) { var defaultVersion = versioning.getDefaultDatabaseVersion(); if (databaseVersion < defaultVersion || process.env.FORCE_MIGRATION) { // 2. The database exists but is out of date // Migrate to latest version logInfo('Database upgrade required from version ' + databaseVersion + ' to ' + defaultVersion); return self.migrateUp(databaseVersion, defaultVersion).then(function () { // Finally update the databases current version return versioning.setDatabaseVersion(); }); } if (databaseVersion === defaultVersion) { // 1. The database exists and is up-to-date logInfo('Up to date at version ' + databaseVersion); return when.resolve(); } if (databaseVersion > defaultVersion) { // 3. The database exists but the currentVersion setting does not or cannot be understood // In this case we don't understand the version because it is too high errors.logErrorAndExit( 'Your database is not compatible with this version of Ghost', 'You will need to create a new database' ); } }, function (err) { if (err.message || err === 'Settings table does not exist') { // 4. The database has not yet been created // Bring everything up from initial version. logInfo('Database initialisation required for version ' + versioning.getDefaultDatabaseVersion()); return self.migrateUpFreshDb(); } // 3. The database exists but the currentVersion setting does not or cannot be understood // In this case the setting was missing or there was some other problem errors.logErrorAndExit('There is a problem with the database', err.message || err); }); }; // ### Reset // Delete all tables from the database in reverse order reset = function () { var tables = _.map(schemaTables, function (table) { return function () { return utils.deleteTable(table); }; }).reverse(); return sequence(tables); }; // Only do this if we have no database at all migrateUpFreshDb = function () { var tables = _.map(schemaTables, function (table) { return function () { logInfo('Creating table: ' + table); return utils.createTable(table); }; }); logInfo('Creating tables...'); return sequence(tables).then(function () { // Load the fixtures return fixtures.populate(); }).then(function () { return populateDefaultSettings(); }); }; // Migrate from a specific version to the latest migrateUp = function (fromVersion, toVersion) { var oldTables, modifyUniCommands = [], migrateOps = []; return backupDatabase().then(function () { return utils.getTables(); }).then(function (tables) { oldTables = tables; if (!_.isEmpty(oldTables)) { return utils.checkTables(); } }).then(function () { migrateOps = migrateOps.concat(commands.getDeleteCommands(oldTables, schemaTables)); migrateOps = migrateOps.concat(commands.getAddCommands(oldTables, schemaTables)); return when.all( _.map(oldTables, function (table) { return utils.getIndexes(table).then(function (indexes) { modifyUniCommands = modifyUniCommands.concat(commands.modifyUniqueCommands(table, indexes)); }); }) ); }).then(function () { return when.all( _.map(oldTables, function (table) { return utils.getColumns(table).then(function (columns) { migrateOps = migrateOps.concat(commands.addColumnCommands(table, columns)); }); }) ); }).then(function () { migrateOps = migrateOps.concat(_.compact(modifyUniCommands)); // execute the commands in sequence if (!_.isEmpty(migrateOps)) { logInfo('Running migrations'); return sequence(migrateOps); } return; }).then(function () { return fixtures.update(fromVersion, toVersion); }).then(function () { return populateDefaultSettings(); }); }; module.exports = { init: init, reset: reset, migrateUp: migrateUp, migrateUpFreshDb: migrateUpFreshDb };