var _ = require('underscore'), when = require('when'), series = require('when/sequence'), errors = require('../../errorHandling'), knex = require('../../models/base').knex, defaultSettings = require('../default-settings'), Settings = require('../../models/settings').Settings, fixtures = require('../fixtures'), initialVersion = '000', defaultDatabaseVersion; // Default Database Version // The migration version number according to the hardcoded default settings // This is the version the database should be at or migrated to function getDefaultDatabaseVersion() { if (!defaultDatabaseVersion) { // This be the current version according to the software defaultDatabaseVersion = _.find(defaultSettings.core, function (setting) { return setting.key === 'databaseVersion'; }).defaultValue; } return defaultDatabaseVersion; } // Database Current Version // The migration version number according to the database // This is what the database is currently at and may need to be updated function getDatabaseVersion() { return knex.schema.hasTable('settings').then(function (exists) { // Check for the current version from the settings table if (exists) { // Temporary code to deal with old databases with currentVersion settings return knex('settings') .where('key', 'databaseVersion') .orWhere('key', 'currentVersion') .select('value') .then(function (versions) { var databaseVersion = _.reduce(versions, function (memo, version) { if (isNaN(version.value)) { errors.throwError('Database version is not recognised'); } return parseInt(version.value, 10) > parseInt(memo, 10) ? version.value : memo; }, initialVersion); if (!databaseVersion || databaseVersion.length === 0) { // we didn't get a response we understood, assume initialVersion databaseVersion = initialVersion; } return databaseVersion; }); } throw new Error('Settings table does not exist'); }); } function setDatabaseVersion() { return knex('settings') .where('key', 'databaseVersion') .update({ 'value': defaultDatabaseVersion }); } module.exports = { getDatabaseVersion: getDatabaseVersion, // 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 getDatabaseVersion().then(function (databaseVersion) { var defaultVersion = getDefaultDatabaseVersion(); if (databaseVersion === defaultVersion) { // 1. The database exists and is up-to-date return when.resolve(); } if (databaseVersion < defaultVersion) { // 2. The database exists but is out of date return self.migrateUpFromVersion(databaseVersion); } 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. 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 // Migrate from where we are down to nothing. reset: function () { var self = this; return getDatabaseVersion().then(function (databaseVersion) { // bring everything down from the current version return self.migrateDownFromVersion(databaseVersion); }, function () { // If the settings table doesn't exist, bring everything down from initial version. return self.migrateDownFromVersion(initialVersion); }); }, // Only do this if we have no database at all migrateUpFreshDb: function () { var migration = require('./' + initialVersion); return migration.up().then(function () { // Load the fixtures return fixtures.populateFixtures(); }).then(function () { // Initialise the default settings return Settings.populateDefaults(); }); }, // Migrate from a specific version to the latest migrateUpFromVersion: function (version, max) { var versions = [], maxVersion = max || this.getVersionAfter(getDefaultDatabaseVersion()), currVersion = version, tasks = []; // Aggregate all the versions we need to do migrations for while (currVersion !== maxVersion) { versions.push(currVersion); currVersion = this.getVersionAfter(currVersion); } // Aggregate all the individual up calls to use in the series(...) below tasks = _.map(versions, function (taskVersion) { return function () { try { var migration = require('./' + taskVersion); return migration.up(); } catch (e) { errors.logError(e); return when.reject(e); } }; }); // Run each migration in series return series(tasks).then(function () { // Finally update the databases current version return setDatabaseVersion(); }); }, migrateDownFromVersion: function (version) { var self = this, versions = [], minVersion = this.getVersionBefore(initialVersion), currVersion = version, tasks = []; // Aggregate all the versions we need to do migrations for while (currVersion !== minVersion) { versions.push(currVersion); currVersion = this.getVersionBefore(currVersion); } // Aggregate all the individual up calls to use in the series(...) below tasks = _.map(versions, function (taskVersion) { return function () { try { var migration = require('./' + taskVersion); return migration.down(); } catch (e) { errors.logError(e); return self.migrateDownFromVersion(initialVersion); } }; }); // Run each migration in series return series(tasks); }, // Get the following version based on the current getVersionAfter: function (currVersion) { var currVersionNum = parseInt(currVersion, 10), nextVersion; // Default to initialVersion if not parsed if (isNaN(currVersionNum)) { currVersionNum = parseInt(initialVersion, 10); } currVersionNum += 1; nextVersion = String(currVersionNum); // Pad with 0's until 3 digits while (nextVersion.length < 3) { nextVersion = "0" + nextVersion; } return nextVersion; }, getVersionBefore: function (currVersion) { var currVersionNum = parseInt(currVersion, 10), prevVersion; if (isNaN(currVersionNum)) { currVersionNum = parseInt(initialVersion, 10); } currVersionNum -= 1; prevVersion = String(currVersionNum); // Pad with 0's until 3 digits while (prevVersion.length < 3) { prevVersion = "0" + prevVersion; } return prevVersion; } };