From 639c0d0627b30810437904dea6ec28fa54b0bb72 Mon Sep 17 00:00:00 2001 From: Sebastian Gierlinger Date: Mon, 18 Nov 2013 15:21:15 +0100 Subject: [PATCH] Add schema.js closes #1398 closes #1399 closes #1400 - added schema.js with database version '000' - refactored migration to use schema.js - if new table is added to schema.js and databaseVersion is increased, table will be added - if new table is deleted to schema.js and databaseVersion is increased, table will be deleted - alter table from issue #1400 is delayed until knex supports column modification - changed import pre checks to work again (will be refactored separately) - added basic PostgreSQL support (Attention: not supported/tested) - changed error handling in server.js --- core/server.js | 4 +- core/server/api/db.js | 16 +- core/server/data/export/index.js | 28 +-- core/server/data/migration/000.js | 269 -------------------- core/server/data/migration/index.js | 378 +++++++++++++++------------- core/server/data/schema.js | 117 +++++++++ core/test/unit/import_spec.js | 5 +- 7 files changed, 335 insertions(+), 482 deletions(-) delete mode 100644 core/server/data/migration/000.js create mode 100644 core/server/data/schema.js diff --git a/core/server.js b/core/server.js index 3ae4146c6b..326c5897b6 100644 --- a/core/server.js +++ b/core/server.js @@ -396,4 +396,6 @@ when(ghost.init()).then(function () { } }); -}).otherwise(errors.logAndThrowError); +}, function (err) { + errors.logErrorAndExit(err); +}); diff --git a/core/server/api/db.js b/core/server/api/db.js index e6dc577253..2a265769bd 100644 --- a/core/server/api/db.js +++ b/core/server/api/db.js @@ -7,6 +7,7 @@ var Ghost = require('../../ghost'), when = require('when'), nodefn = require('when/node/function'), _ = require('underscore'), + schema = require('../data/schema'), ghost = new Ghost(), db; @@ -75,8 +76,7 @@ db = { .then(function (fileContents) { var importData, error = "", - constraints = require('../data/migration/' + databaseVersion).constraints, - constraintkeys = _.keys(constraints); + tableKeys = _.keys(schema); // Parse the json data try { @@ -89,20 +89,20 @@ db = { return when.reject(new Error("Import data does not specify version")); } - _.each(constraintkeys, function (constkey) { + _.each(tableKeys, function (constkey) { _.each(importData.data[constkey], function (elem) { var prop; for (prop in elem) { if (elem.hasOwnProperty(prop)) { - if (constraints[constkey].hasOwnProperty(prop)) { + if (schema[constkey].hasOwnProperty(prop)) { if (elem.hasOwnProperty(prop)) { if (!_.isNull(elem[prop])) { - if (elem[prop].length > constraints[constkey][prop].maxlength) { + if (elem[prop].length > schema[constkey][prop].maxlength) { error += error !== "" ? "
" : ""; - error += "Property '" + prop + "' exceeds maximum length of " + constraints[constkey][prop].maxlength + " (element:" + constkey + " / id:" + elem.id + ")"; + error += "Property '" + prop + "' exceeds maximum length of " + schema[constkey][prop].maxlength + " (element:" + constkey + " / id:" + elem.id + ")"; } } else { - if (!constraints[constkey][prop].nullable) { + if (!schema[constkey][prop].nullable) { error += error !== "" ? "
" : ""; error += "Property '" + prop + "' is not nullable (element:" + constkey + " / id:" + elem.id + ")"; } @@ -156,4 +156,4 @@ db = { } }; -module.exports = db; +module.exports = db; \ No newline at end of file diff --git a/core/server/data/export/index.js b/core/server/data/export/index.js index 9c55c0c956..019983372e 100644 --- a/core/server/data/export/index.js +++ b/core/server/data/export/index.js @@ -1,37 +1,13 @@ var when = require('when'), _ = require('underscore'), migration = require('../migration'), - client = require('../../models/base').client, knex = require('../../models/base').knex, + schema = require('../schema'), exporter; -function getTablesFromSqlite3() { - return knex.raw("select * from sqlite_master where type = 'table'").then(function (response) { - return _.reject(_.pluck(response[0], 'tbl_name'), function (name) { - return name === 'sqlite_sequence'; - }); - }); -} - -function getTablesFromMySQL() { - return knex.raw("show tables").then(function (response) { - return _.flatten(_.map(response[0], function (entry) { - return _.values(entry); - })); - }); -} - exporter = function () { - var tablesToExport; - - if (client === 'sqlite3') { - tablesToExport = getTablesFromSqlite3(); - } else if (client === 'mysql') { - tablesToExport = getTablesFromMySQL(); - } else { - return when.reject("No exporter for database client " + client); - } + var tablesToExport = _.keys(schema); return when.join(migration.getDatabaseVersion(), tablesToExport).then(function (results) { var version = results[0], diff --git a/core/server/data/migration/000.js b/core/server/data/migration/000.js deleted file mode 100644 index 322e592c68..0000000000 --- a/core/server/data/migration/000.js +++ /dev/null @@ -1,269 +0,0 @@ -var when = require('when'), - knex = require('../../models/base').knex, - up, - down, - constraints = { - posts: { - id: {maxlength: 0, nullable: false}, - uuid: {maxlength: 36, nullable: false}, - title: {maxlength: 150, nullable: false}, - slug: {maxlength: 150, nullable: false}, - markdown: {maxlength: 16777215, nullable: true}, - html: {maxlength: 16777215, nullable: true}, - image: {maxlength: 2000, nullable: true}, - featured: {maxlength: 0, nullable: false}, - page: {maxlength: 0, nullable: false}, - status: {maxlength: 150, nullable: false}, - language: {maxlength: 6, nullable: false}, - meta_title: {maxlength: 150, nullable: true}, - meta_description: {maxlength: 200, nullable: true}, - author_id: {maxlength: 0, nullable: false}, - created_at: {maxlength: 0, nullable: false}, - created_by: {maxlength: 0, nullable: false}, - updated_at: {maxlength: 0, nullable: true}, - updated_by: {maxlength: 0, nullable: true}, - published_at: {maxlength: 0, nullable: true}, - published_by: {maxlength: 0, nullable: true} - }, - users: { - id: {maxlength: 0, nullable: false}, - uuid: {maxlength: 36, nullable: false}, - name: {maxlength: 150, nullable: false}, - slug: {maxlength: 150, nullable: false}, - password: {maxlength: 60, nullable: false}, - email: {maxlength: 254, nullable: false}, - image: {maxlength: 2000, nullable: true}, - cover: {maxlength: 2000, nullable: true}, - bio: {maxlength: 200, nullable: true}, - website: {maxlength: 2000, nullable: true}, - location: {maxlength: 65535, nullable: true}, - accessibility: {maxlength: 65535, nullable: true}, - status: {maxlength: 150, nullable: false}, - language: {maxlength: 6, nullable: false}, - meta_title: {maxlength: 150, nullable: true}, - meta_description: {maxlength: 200, nullable: true}, - last_login: {maxlength: 0, nullable: true}, - created_at: {maxlength: 0, nullable: false}, - created_by: {maxlength: 0, nullable: false}, - updated_at: {maxlength: 0, nullable: true}, - updated_by: {maxlength: 0, nullable: true} - }, - roles: { - id: {maxlength: 0, nullable: false}, - uuid: {maxlength: 36, nullable: false}, - name: {maxlength: 150, nullable: false}, - description: {maxlength: 200, nullable: true}, - created_at: {maxlength: 0, nullable: false}, - created_by: {maxlength: 0, nullable: false}, - updated_at: {maxlength: 0, nullable: true}, - updated_by: {maxlength: 0, nullable: true} - }, - roles_users: { - id: {maxlength: 0, nullable: false}, - role_id: {maxlength: 0, nullable: false}, - user_id: {maxlength: 0, nullable: false} - }, - permissions: { - id: {maxlength: 0, nullable: false}, - uuid: {maxlength: 36, nullable: false}, - name: {maxlength: 150, nullable: false}, - object_type: {maxlength: 150, nullable: false}, - action_type: {maxlength: 150, nullable: false}, - object_id: {maxlength: 0, nullable: true}, - created_at: {maxlength: 0, nullable: false}, - created_by: {maxlength: 0, nullable: false}, - updated_at: {maxlength: 0, nullable: true}, - updated_by: {maxlength: 0, nullable: true} - }, - permissions_users: { - id: {maxlength: 0, nullable: false}, - user_id: {maxlength: 0, nullable: false}, - permission_id: {maxlength: 0, nullable: false} - }, - permissions_roles: { - id: {maxlength: 0, nullable: false}, - role_id: {maxlength: 0, nullable: false}, - permission_id: {maxlength: 0, nullable: false} - }, - settings: { - id: {maxlength: 0, nullable: false}, - uuid: {maxlength: 36, nullable: false}, - key: {maxlength: 150, nullable: false}, - value: {maxlength: 65535, nullable: true}, - type: {maxlength: 150, nullable: false}, - created_at: {maxlength: 0, nullable: false}, - created_by: {maxlength: 0, nullable: false}, - updated_at: {maxlength: 0, nullable: true}, - updated_by: {maxlength: 0, nullable: true} - }, - tags: { - id: {maxlength: 0, nullable: false}, - uuid: {maxlength: 36, nullable: false}, - name: {maxlength: 150, nullable: false}, - slug: {maxlength: 150, nullable: false}, - description: {maxlength: 200, nullable: true}, - parent_id: {maxlength: 0, nullable: true}, - meta_title: {maxlength: 150, nullable: true}, - meta_description: {maxlength: 200, nullable: true}, - created_at: {maxlength: 0, nullable: false}, - created_by: {maxlength: 0, nullable: false}, - updated_at: {maxlength: 0, nullable: true}, - updated_by: {maxlength: 0, nullable: true} - }, - posts_tags: { - id: {maxlength: 0, nullable: false}, - post_id: {maxlength: 0, nullable: false}, - tag_id: {maxlength: 0, nullable: false} - } - }; - - -up = function () { - - return when.all([ - - knex.schema.createTable('posts', function (t) { - t.increments().primary(); - t.string('uuid', constraints.posts.uuid.maxlength).notNull(); - t.string('title', constraints.posts.title.maxlength).notNull(); - t.string('slug', constraints.posts.slug.maxlength).notNull().unique(); - t.text('markdown', 'medium').nullable(); // max-length 16777215 - t.text('html', 'medium').nullable(); // max-length 16777215 - t.text('image').nullable(); // max-length 2000 - t.bool('featured').notNull().defaultTo(false); - t.bool('page').notNull().defaultTo(false); - t.string('status', constraints.posts.status.maxlength).notNull().defaultTo('draft'); - t.string('language', constraints.posts.language.maxlength).notNull().defaultTo('en_US'); - t.string('meta_title', constraints.posts.meta_title.maxlength).nullable(); - t.string('meta_description', constraints.posts.meta_description.maxlength).nullable(); - t.integer('author_id').notNull(); - t.dateTime('created_at').notNull(); - t.integer('created_by').notNull(); - t.dateTime('updated_at').nullable(); - t.integer('updated_by').nullable(); - t.dateTime('published_at').nullable(); - t.integer('published_by').nullable(); - }), - - knex.schema.createTable('users', function (t) { - t.increments().primary(); - t.string('uuid', constraints.users.uuid.maxlength).notNull(); - t.string('name', constraints.users.name.maxlength).notNull(); - t.string('slug', constraints.users.slug.maxlength).notNull().unique(); - t.string('password', constraints.users.password.maxlength).notNull(); - t.string('email', constraints.users.email.maxlength).notNull().unique(); - t.text('image').nullable(); // max-length 2000 - t.text('cover').nullable(); // max-length 2000 - t.string('bio', constraints.users.bio.maxlength).nullable(); - t.text('website').nullable(); // max-length 2000 - t.text('location').nullable(); // max-length 65535 - t.text('accessibility').nullable(); // max-length 65535 - t.string('status', constraints.users.status.maxlength).notNull().defaultTo('active'); - t.string('language', constraints.users.language.maxlength).notNull().defaultTo('en_US'); - t.string('meta_title', constraints.users.meta_title.maxlength).nullable(); - t.string('meta_description', constraints.users.meta_description.maxlength).nullable(); - t.dateTime('last_login').nullable(); - t.dateTime('created_at').notNull(); - t.integer('created_by').notNull(); - t.dateTime('updated_at').nullable(); - t.integer('updated_by').nullable(); - }), - - knex.schema.createTable('roles', function (t) { - t.increments().primary(); - t.string('uuid', constraints.roles.uuid.maxlength).notNull(); - t.string('name', constraints.roles.name.maxlength).notNull(); - t.string('description', constraints.roles.description.maxlength).nullable(); - t.dateTime('created_at').notNull(); - t.integer('created_by').notNull(); - t.dateTime('updated_at').nullable(); - t.integer('updated_by').nullable(); - }), - - knex.schema.createTable('roles_users', function (t) { - t.increments().primary(); - t.integer('role_id').notNull(); - t.integer('user_id').notNull(); - }), - - knex.schema.createTable('permissions', function (t) { - t.increments().primary(); - t.string('uuid', constraints.permissions.uuid.maxlength).notNull(); - t.string('name', constraints.permissions.name.maxlength).notNull(); - t.string('object_type', constraints.permissions.object_type.maxlength).notNull(); - t.string('action_type', constraints.permissions.action_type.maxlength).notNull(); - t.integer('object_id').nullable(); - t.dateTime('created_at').notNull(); - t.integer('created_by').notNull(); - t.dateTime('updated_at').nullable(); - t.integer('updated_by').nullable(); - }), - - knex.schema.createTable('permissions_users', function (t) { - t.increments().primary(); - t.integer('user_id').notNull(); - t.integer('permission_id').notNull(); - }), - - knex.schema.createTable('permissions_roles', function (t) { - t.increments().primary(); - t.integer('role_id').notNull(); - t.integer('permission_id').notNull(); - }), - - knex.schema.createTable('settings', function (t) { - t.increments().primary(); - t.string('uuid', constraints.settings.uuid.maxlength).notNull(); - t.string('key', constraints.settings.key.maxlength).notNull().unique(); - t.text('value').nullable(); // max-length 65535 - t.string('type', constraints.settings.type.maxlength).notNull().defaultTo('core'); - t.dateTime('created_at').notNull(); - t.integer('created_by').notNull(); - t.dateTime('updated_at').nullable(); - t.integer('updated_by').nullable(); - }), - knex.schema.createTable('tags', function (t) { - t.increments().primary(); - t.string('uuid', constraints.tags.uuid.maxlength).notNull(); - t.string('name', constraints.tags.name.maxlength).notNull(); - t.string('slug', constraints.tags.slug.maxlength).notNull().unique(); - t.string('description', constraints.tags.description.maxlength).nullable(); - t.integer('parent_id').nullable(); - t.string('meta_title', constraints.tags.meta_title.maxlength).nullable(); - t.string('meta_description', constraints.tags.meta_description.maxlength).nullable(); - t.dateTime('created_at').notNull(); - t.integer('created_by').notNull(); - t.dateTime('updated_at').nullable(); - t.integer('updated_by').nullable(); - }) - ]).then(function () { - return knex.schema.createTable('posts_tags', function (t) { - t.increments().primary(); - t.integer('post_id').notNull().unsigned().references('id').inTable('posts'); - t.integer('tag_id').notNull().unsigned().references('id').inTable('tags'); - }); - }); -}; - -down = function () { - return when.all([ - knex.schema.dropTableIfExists('posts_tags'), - knex.schema.dropTableIfExists('roles_users'), - knex.schema.dropTableIfExists('permissions_users'), - knex.schema.dropTableIfExists('permissions_roles'), - knex.schema.dropTableIfExists('users') - - ]).then(function () { - return when.all([ - knex.schema.dropTableIfExists('roles'), - knex.schema.dropTableIfExists('settings'), - knex.schema.dropTableIfExists('permissions'), - knex.schema.dropTableIfExists('tags'), - knex.schema.dropTableIfExists('posts') - ]); - }); -}; - -exports.up = up; -exports.down = down; -exports.constraints = constraints; diff --git a/core/server/data/migration/index.js b/core/server/data/migration/index.js index 2d79a7f27c..c26d2165e0 100644 --- a/core/server/data/migration/index.js +++ b/core/server/data/migration/index.js @@ -1,15 +1,24 @@ var _ = require('underscore'), when = require('when'), - series = require('when/sequence'), errors = require('../../errorHandling'), + client = require('../../models/base').client, knex = require('../../models/base').knex, + sequence = require('when/sequence'), defaultSettings = require('../default-settings'), Settings = require('../../models/settings').Settings, fixtures = require('../fixtures'), + schema = require('../schema'), initialVersion = '000', - defaultDatabaseVersion; + schemaTables = _.keys(schema), + defaultDatabaseVersion, + + init, + reset, + migrateUp, + migrateUpFreshDb, + getTables; // Default Database Version // The migration version number according to the hardcoded default settings @@ -63,180 +72,199 @@ function setDatabaseVersion() { .update({ 'value': defaultDatabaseVersion }); } +function createTable(table) { + return knex.schema.createTable(table, function (t) { + var column, + columnKeys = _.keys(schema[table]); + _.each(columnKeys, function (key) { + if (schema[table][key].hasOwnProperty('maxlength')) { + column = t[schema[table][key].type](key, schema[table][key].maxlength); + } else { + column = t[schema[table][key].type](key); + } + if (schema[table][key].hasOwnProperty('nullable') && schema[table][key].nullable === true) { + column.nullable(); + } else { + column.notNullable(); + } + if (schema[table][key].hasOwnProperty('primary') && schema[table][key].primary === true) { + column.primary(); + } + if (schema[table][key].hasOwnProperty('unique') && schema[table][key].unique) { + column.unique(); + } + if (schema[table][key].hasOwnProperty('unsigned') && schema[table][key].unsigned) { + column.unsigned(); + } + if (schema[table][key].hasOwnProperty('references') && schema[table][key].hasOwnProperty('inTable')) { + //check if table exists? + column.references(schema[table][key].references); + column.inTable(schema[table][key].inTable); + } + if (schema[table][key].hasOwnProperty('defaultTo')) { + column.defaultTo(schema[table][key].defaultTo); + } + }); + }); +} + +function deleteTable(table) { + return knex.schema.dropTableIfExists(table); +} + +function getDeleteCommands(oldTables, newTables) { + var deleteTables = _.difference(oldTables, newTables); + if (!_.isEmpty(deleteTables)) { + return _.map(deleteTables, function (table) { + return function () { + return deleteTable(table); + }; + }); + } +} + +function getAddCommands(oldTables, newTables) { + var addTables = _.difference(newTables, oldTables); + if (!_.isEmpty(addTables)) { + return _.map(addTables, function (table) { + return function () { + return createTable(table); + }; + }); + } +} + +function getTablesFromSqlite3() { + return knex.raw("select * from sqlite_master where type = 'table'").then(function (response) { + return _.reject(_.pluck(response[0], 'tbl_name'), function (name) { + return name === 'sqlite_sequence'; + }); + }); +} + +// Basic suppport for PgSQL +// Attention: not officially tested/supported +function getTablesFromPgSQL() { + return knex.raw("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"); +} + +function getTablesFromMySQL() { + return knex.raw("show tables").then(function (response) { + return _.flatten(_.map(response[0], function (entry) { + return _.values(entry); + })); + }); +} + +// 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 + // Migrate to latest version + return self.migrateUp().then(function () { + // Finally update the databases current version + return setDatabaseVersion(); + }); + } + 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 +// Delete all tables from the database in reverse order +reset = function () { + var tables = []; + tables = _.map(schemaTables, function (table) { + return function () { + return deleteTable(table); + }; + }).reverse(); + + return sequence(tables); +}; + +// Only do this if we have no database at all +migrateUpFreshDb = function () { + var tables = []; + tables = _.map(schemaTables, function (table) { + return function () { + return createTable(table); + }; + }); + + return sequence(tables).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 +migrateUp = function () { + return getTables().then(function (oldTables) { + var deleteCommands = getDeleteCommands(oldTables, schemaTables), + addCommands = getAddCommands(oldTables, schemaTables), + commands = []; + if (!_.isEmpty(deleteCommands)) { + commands = commands.concat(deleteCommands); + } + if (!_.isEmpty(addCommands)) { + commands = commands.concat(addCommands); + } + if (!_.isEmpty(commands)) { + return sequence(commands); + } + return; + }); +}; + +getTables = function () { + if (client === 'sqlite3') { + return getTablesFromSqlite3(); + } + if (client === 'mysql') { + return getTablesFromMySQL(); + } + if (client === 'pg') { + return getTablesFromPgSQL(); + } + return when.reject("No support for database client " + client); +}; 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; - } + init: init, + reset: reset, + migrateUp: migrateUp, + migrateUpFreshDb: migrateUpFreshDb }; \ No newline at end of file diff --git a/core/server/data/schema.js b/core/server/data/schema.js new file mode 100644 index 0000000000..6417950065 --- /dev/null +++ b/core/server/data/schema.js @@ -0,0 +1,117 @@ +var db = { + posts: { + id: {type: 'increments', nullable: false, primary: true}, + uuid: {type: 'string', maxlength: 36, nullable: false}, + title: {type: 'string', maxlength: 150, nullable: false}, + slug: {type: 'string', maxlength: 150, nullable: false, unique: true}, + markdown: {type: 'text', maxlength: 16777215, nullable: true}, + html: {type: 'text', maxlength: 16777215, nullable: true}, + image: {type: 'text', maxlength: 2000, nullable: true}, + featured: {type: 'bool', nullable: false, defaultTo: false}, + page: {type: 'bool', nullable: false, defaultTo: false}, + status: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'draft'}, + language: {type: 'string', maxlength: 6, nullable: false, defaultTo: 'en_US'}, + meta_title: {type: 'string', maxlength: 150, nullable: true}, + meta_description: {type: 'string', maxlength: 200, nullable: true}, + author_id: {type: 'integer', nullable: false}, + created_at: {type: 'dateTime', nullable: false}, + created_by: {type: 'integer', nullable: false}, + updated_at: {type: 'dateTime', nullable: true}, + updated_by: {type: 'integer', nullable: true}, + published_at: {type: 'dateTime', nullable: true}, + published_by: {type: 'integer', nullable: true} + }, + users: { + id: {type: 'increments', nullable: false, primary: true}, + uuid: {type: 'string', maxlength: 36, nullable: false}, + name: {type: 'string', maxlength: 150, nullable: false, unique: true}, + slug: {type: 'string', maxlength: 150, nullable: false}, + password: {type: 'string', maxlength: 60, nullable: false}, + email: {type: 'string', maxlength: 254, nullable: false, unique: true}, + image: {type: 'text', maxlength: 2000, nullable: true}, + cover: {type: 'text', maxlength: 2000, nullable: true}, + bio: {type: 'string', maxlength: 200, nullable: true}, + website: {type: 'text', maxlength: 2000, nullable: true}, + location: {type: 'text', maxlength: 65535, nullable: true}, + accessibility: {type: 'text', maxlength: 65535, nullable: true}, + status: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'active'}, + language: {type: 'string', maxlength: 6, nullable: false, defaultTo: 'en_US'}, + meta_title: {type: 'string', maxlength: 150, nullable: true}, + meta_description: {type: 'string', maxlength: 200, nullable: true}, + last_login: {type: 'dateTime', nullable: true}, + created_at: {type: 'dateTime', nullable: false}, + created_by: {type: 'integer', nullable: false}, + updated_at: {type: 'dateTime', nullable: true}, + updated_by: {type: 'integer', nullable: true} + }, + roles: { + id: {type: 'increments', nullable: false, primary: true}, + uuid: {type: 'string', maxlength: 36, nullable: false}, + name: {type: 'string', maxlength: 150, nullable: false}, + description: {type: 'string', maxlength: 200, nullable: true}, + created_at: {type: 'dateTime', nullable: false}, + created_by: {type: 'integer', nullable: false}, + updated_at: {type: 'dateTime', nullable: true}, + updated_by: {type: 'integer', nullable: true} + }, + roles_users: { + id: {type: 'increments', nullable: false, primary: true}, + role_id: {type: 'integer', nullable: false}, + user_id: {type: 'integer', nullable: false} + }, + permissions: { + id: {type: 'increments', nullable: false, primary: true}, + uuid: {type: 'string', maxlength: 36, nullable: false}, + name: {type: 'string', maxlength: 150, nullable: false}, + object_type: {type: 'string', maxlength: 150, nullable: false}, + action_type: {type: 'string', maxlength: 150, nullable: false}, + object_id: {type: 'integer', nullable: true}, + created_at: {type: 'dateTime', nullable: false}, + created_by: {type: 'integer', nullable: false}, + updated_at: {type: 'dateTime', nullable: true}, + updated_by: {type: 'integer', nullable: true} + }, + permissions_users: { + id: {type: 'increments', nullable: false, primary: true}, + user_id: {type: 'integer', nullable: false}, + permission_id: {type: 'integer', nullable: false} + }, + permissions_roles: { + id: {type: 'increments', nullable: false, primary: true}, + role_id: {type: 'integer', nullable: false}, + permission_id: {type: 'integer', nullable: false} + }, + settings: { + id: {type: 'increments', nullable: false, primary: true}, + uuid: {type: 'string', maxlength: 36, nullable: false}, + key: {type: 'string', maxlength: 150, nullable: false, unique: true}, + value: {type: 'text', maxlength: 65535, nullable: true}, + type: {type: 'string', maxlength: 150, nullable: false, defaultTo: 'core'}, + created_at: {type: 'dateTime', nullable: false}, + created_by: {type: 'integer', nullable: false}, + updated_at: {type: 'dateTime', nullable: true}, + updated_by: {type: 'integer', nullable: true} + }, + tags: { + id: {type: 'increments', nullable: false, primary: true}, + uuid: {type: 'string', maxlength: 36, nullable: false}, + name: {type: 'string', maxlength: 150, nullable: false}, + slug: {type: 'string', maxlength: 150, nullable: false, unique: true}, + description: {type: 'string', maxlength: 200, nullable: true}, + parent_id: {type: 'integer', nullable: true}, + meta_title: {type: 'string', maxlength: 150, nullable: true}, + meta_description: {type: 'string', maxlength: 200, nullable: true}, + created_at: {type: 'dateTime', nullable: false}, + created_by: {type: 'integer', nullable: false}, + updated_at: {type: 'dateTime', nullable: true}, + updated_by: {type: 'integer', nullable: true} + }, + posts_tags: { + id: {type: 'increments', nullable: false, primary: true}, + post_id: {type: 'integer', nullable: false, unsigned: true, references: 'id', inTable: 'posts'}, + tag_id: {type: 'integer', nullable: false, unsigned: true, references: 'id', inTable: 'tags'} + } + }; + + +module.exports = db; \ No newline at end of file diff --git a/core/test/unit/import_spec.js b/core/test/unit/import_spec.js index 5e12ed1949..1f98a11a5c 100644 --- a/core/test/unit/import_spec.js +++ b/core/test/unit/import_spec.js @@ -48,9 +48,8 @@ describe("Import", function () { it("imports data from 000", function (done) { var exportData; - // initialise database to version 000 - confusingly we have to set the max version to be one higher - // than the migration version we want. Could just use migrate from fresh here... but this is more explicit - migration.migrateUpFromVersion('000', '001').then(function () { + // migrate to current version + migration.migrateUp().then(function () { // Load the fixtures return fixtures.populateFixtures(); }).then(function () {