Ghost/core/server/data/migration/index.js
Hannah Wolfe 71a92194ca Improved error messaging
closes #748

- Removed the alpha software warning
- Better error message output for the whole app - can now specify an error, a context, and a help message
- Improved invalid node version, start and stop messaging
- Listens for Ctrl+C and exits nicely
- Minor improvements to handling and errors with old DBs (temporary)
2013-09-15 13:52:58 +01:00

245 lines
8.5 KiB
JavaScript

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
// TODO: remove before release
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;
});
}
return when.reject('Settings table does not exist');
});
}
function setDatabaseVersion() {
return knex('settings')
.where('key', 'databaseVersion')
.update({ 'value': defaultDatabaseVersion });
}
module.exports = {
// 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 === '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;
}
};