migrations: seeding is part of init db task (#7545)

* 🎨  move heart of fixtures to schema folder and change user model

- add fixtures.json to schema folder
- add fixture utils to schema folder
- keep all the logic!

--> FIXTURE.JSON
- add owner user with roles

--> USER MODEL
- add password as default
- findAll: allow querying inactive users when internal context (defaultFilters)
- findOne: do not remove values from original object!
- add: do not remove values from original object!

* 🔥  remove migrations key from default_settings.json

- this was a temporary invention for an older migration script
- sephiroth keep alls needed information in a migration collection

* 🔥   add code property to errors

- add code property to errors
- IMPORTANT: please share your opinion about that
- this is a copy paste behaviour of how node is doing that (errno, code etc.)
- so code specifies a GhostError

* 🎨  change error handling in versioning

- no need to throw specific database errors anymore (this was just a temporary solution)
- now: we are throwing real DatabaseVersionErrors
- specified by a code
- background: the versioning unit has not idea about seeding and population of the database
- it just throws what it knows --> database version does not exist or settings table does not exist

* 🎨  sephiroth optimisations

- added getPath function to get the path to init scripts and migration scripts
- migrationPath is still hardcoded (see TODO)
- tidy up database naming to transacting

*   migration init scripts are now complete

- 1. add tables
- 2. add fixtures
- 3. add default settings

* 🎨  important: make bootup script smaller!

- remove all TODO'S except of one
- no seeding logic in bootup script anymore 🕵🏻

*   sephiroth: allow params for init command

- param: skip (do not run this script)
- param: only (only run this script)
- very simple way

* 🎨  adapt tests and test env

- do not use migrate.populate anymore
- use sephiroth instead
- jscs/jshint

* 🎨  fix User model status checks
This commit is contained in:
Katharina Irrgang 2016-10-12 17:18:57 +02:00 committed by Hannah Wolfe
parent 22589e8b91
commit 869a35c97d
18 changed files with 783 additions and 86 deletions

View File

@ -5,10 +5,10 @@ var Promise = require('bluebird'),
schemaTables = Object.keys(schema);
module.exports = function createTables(options) {
var database = options.database;
var transacting = options.transacting;
return Promise.mapSeries(schemaTables, function createTable(table) {
logging.info('Creating table: ' + table);
return commands.createTable(table, database);
return commands.createTable(table, transacting);
});
};

View File

@ -0,0 +1,24 @@
var Promise = require('bluebird'),
_ = require('lodash'),
fixtures = require('../../schema/fixtures'),
models = require('../../../models'),
logging = require('../../../logging');
// @TODO: models.init
module.exports = function insertFixtures(options) {
var localOptions = _.merge({
context: {internal: true}
}, options);
models.init();
return Promise.mapSeries(fixtures.models, function (model) {
logging.info('Model: ' + model.name);
return fixtures.utils.addFixturesForModel(model, localOptions);
}).then(function () {
return Promise.mapSeries(fixtures.relations, function (relation) {
logging.info('Relation: ' + relation.from.model + ' to ' + relation.to.model);
return fixtures.utils.addFixturesForRelation(relation, localOptions);
});
});
};

View File

@ -0,0 +1,23 @@
var _ = require('lodash'),
models = require('../../../models'),
errors = require('../../../errors'),
versioning = require('../../schema/versioning');
// @TODO: models.init
module.exports = function insertSettings(options) {
var localOptions = _.merge({context: {internal: true}}, options);
models.init();
return models.Settings.populateDefaults(localOptions)
.then(function () {
return versioning.getDatabaseVersion(localOptions);
})
.catch(function (err) {
if (err instanceof errors.DatabaseVersionError && err.code === 'VERSION_DOES_NOT_EXIST') {
return versioning.setDatabaseVersion(localOptions);
}
throw err;
});
};

View File

@ -1,43 +1,12 @@
var Promise = require('bluebird'),
Sephiroth = require('../sephiroth'),
db = require('../db'),
var Sephiroth = require('../sephiroth'),
config = require('./../../config'),
errors = require('./../../errors'),
models = require('./../../models'),
versioning = require('./../../data/schema/versioning'),
logging = require('./../../logging'),
fixtures = require('../migration/fixtures'),
sephiroth = new Sephiroth({database: config.get('database')});
/**
* @TODO:
* - move this file out of schema folder
* - this file right now takes over seeding, which get's fixed in one of the next PR's
* - remove fixtures.populate
* - remove versioning.setDatabaseVersion(transaction);
* - remove models.Settings.populateDefaults(_.merge({}, {transacting: transaction}, modelOptions));
* - key: migrations-kate
*/
module.exports = function bootUp() {
var modelOptions = {
context: {
internal: true
}
};
return sephiroth.utils.isDatabaseOK()
.then(function () {
return models.Settings.populateDefaults(modelOptions);
})
.then(function () {
return versioning.setDatabaseVersion(db.knex);
})
.then(function () {
return fixtures.populate(logging, modelOptions);
})
.catch(function (err) {
return Promise.reject(new errors.GhostError({
err: err
}));
});
return sephiroth.utils.isDatabaseOK();
};

View File

@ -14,9 +14,6 @@
},
"seenNotifications": {
"defaultValue": "[]"
},
"migrations": {
"defaultValue": "{}"
}
},
"blog": {

View File

@ -0,0 +1,397 @@
{
"models": [
{
"name": "Post",
"entries": [
{
"title": "Welcome to Ghost",
"slug": "welcome-to-ghost",
"markdown": "You're live! Nice. We've put together a little post to introduce you to the Ghost editor and get you started. You can manage your content by signing in to the admin area at `<your blog URL>/ghost/`. When you arrive, you can select this post from a list on the left and see a preview of it on the right. Click the little pencil icon at the top of the preview to edit this post and read the next section!\n\n## Getting Started\n\nGhost uses something called Markdown for writing. Essentially, it's a shorthand way to manage your post formatting as you write!\n\nWriting in Markdown is really easy. In the left hand panel of Ghost, you simply write as you normally would. Where appropriate, you can use *shortcuts* to **style** your content. For example, a list:\n\n* Item number one\n* Item number two\n * A nested item\n* A final item\n\nor with numbers!\n\n1. Remember to buy some milk\n2. Drink the milk\n3. Tweet that I remembered to buy the milk, and drank it\n\n### Links\n\nWant to link to a source? No problem. If you paste in a URL, like http://ghost.org - it'll automatically be linked up. But if you want to customise your anchor text, you can do that too! Here's a link to [the Ghost website](http://ghost.org). Neat.\n\n### What about Images?\n\nImages work too! Already know the URL of the image you want to include in your article? Simply paste it in like this to make it show up:\n\n![The Ghost Logo](https://ghost.org/images/ghost.png)\n\nNot sure which image you want to use yet? That's ok too. Leave yourself a descriptive placeholder and keep writing. Come back later and drag and drop the image in to upload:\n\n![A bowl of bananas]\n\n\n### Quoting\n\nSometimes a link isn't enough, you want to quote someone on what they've said. Perhaps you've started using a new blogging platform and feel the sudden urge to share their slogan? A quote might be just the way to do it!\n\n> Ghost - Just a blogging platform\n\n### Working with Code\n\nGot a streak of geek? We've got you covered there, too. You can write inline `<code>` blocks really easily with back ticks. Want to show off something more comprehensive? 4 spaces of indentation gets you there.\n\n .awesome-thing {\n display: block;\n width: 100%;\n }\n\n### Ready for a Break? \n\nThrow 3 or more dashes down on any new line and you've got yourself a fancy new divider. Aw yeah.\n\n---\n\n### Advanced Usage\n\nThere's one fantastic secret about Markdown. If you want, you can write plain old HTML and it'll still work! Very flexible.\n\n<input type=\"text\" placeholder=\"I'm an input field!\" />\n\nThat should be enough to get you started. Have fun - and let us know what you think :)",
"image": null,
"featured": false,
"page": false,
"status": "published",
"language": "en_US",
"meta_title": null,
"meta_description": null
}
]
},
{
"name": "Tag",
"entries": [
{
"name": "Getting Started",
"slug": "getting-started",
"description": null,
"parent_id": null,
"meta_title": null,
"meta_description": null
}
]
},
{
"name": "Client",
"entries": [
{
"name": "Ghost Admin",
"slug": "ghost-admin",
"status": "enabled"
},
{
"name": "Ghost Frontend",
"slug": "ghost-frontend",
"status": "enabled"
},
{
"name": "Ghost Scheduler",
"slug": "ghost-scheduler",
"status": "enabled",
"type": "web"
}
]
},
{
"name": "Role",
"entries": [
{
"name": "Administrator",
"description": "Administrators"
},
{
"name": "Editor",
"description": "Editors"
},
{
"name": "Author",
"description": "Authors"
},
{
"name": "Owner",
"description": "Blog Owner"
}
]
},
{
"name": "Permission",
"entries": [
{
"name": "Export database",
"action_type": "exportContent",
"object_type": "db"
},
{
"name": "Import database",
"action_type": "importContent",
"object_type": "db"
},
{
"name": "Delete all content",
"action_type": "deleteAllContent",
"object_type": "db"
},
{
"name": "Send mail",
"action_type": "send",
"object_type": "mail"
},
{
"name": "Browse notifications",
"action_type": "browse",
"object_type": "notification"
},
{
"name": "Add notifications",
"action_type": "add",
"object_type": "notification"
},
{
"name": "Delete notifications",
"action_type": "destroy",
"object_type": "notification"
},
{
"name": "Browse posts",
"action_type": "browse",
"object_type": "post"
},
{
"name": "Read posts",
"action_type": "read",
"object_type": "post"
},
{
"name": "Edit posts",
"action_type": "edit",
"object_type": "post"
},
{
"name": "Add posts",
"action_type": "add",
"object_type": "post"
},
{
"name": "Delete posts",
"action_type": "destroy",
"object_type": "post"
},
{
"name": "Browse settings",
"action_type": "browse",
"object_type": "setting"
},
{
"name": "Read settings",
"action_type": "read",
"object_type": "setting"
},
{
"name": "Edit settings",
"action_type": "edit",
"object_type": "setting"
},
{
"name": "Generate slugs",
"action_type": "generate",
"object_type": "slug"
},
{
"name": "Browse tags",
"action_type": "browse",
"object_type": "tag"
},
{
"name": "Read tags",
"action_type": "read",
"object_type": "tag"
},
{
"name": "Edit tags",
"action_type": "edit",
"object_type": "tag"
},
{
"name": "Add tags",
"action_type": "add",
"object_type": "tag"
},
{
"name": "Delete tags",
"action_type": "destroy",
"object_type": "tag"
},
{
"name": "Browse themes",
"action_type": "browse",
"object_type": "theme"
},
{
"name": "Edit themes",
"action_type": "edit",
"object_type": "theme"
},
{
"name": "Upload themes",
"action_type": "add",
"object_type": "theme"
},
{
"name": "Download themes",
"action_type": "read",
"object_type": "theme"
},
{
"name": "Delete themes",
"action_type": "destroy",
"object_type": "theme"
},
{
"name": "Browse users",
"action_type": "browse",
"object_type": "user"
},
{
"name": "Read users",
"action_type": "read",
"object_type": "user"
},
{
"name": "Edit users",
"action_type": "edit",
"object_type": "user"
},
{
"name": "Add users",
"action_type": "add",
"object_type": "user"
},
{
"name": "Delete users",
"action_type": "destroy",
"object_type": "user"
},
{
"name": "Assign a role",
"action_type": "assign",
"object_type": "role"
},
{
"name": "Browse roles",
"action_type": "browse",
"object_type": "role"
},
{
"name": "Browse clients",
"action_type": "browse",
"object_type": "client"
},
{
"name": "Read clients",
"action_type": "read",
"object_type": "client"
},
{
"name": "Edit clients",
"action_type": "edit",
"object_type": "client"
},
{
"name": "Add clients",
"action_type": "add",
"object_type": "client"
},
{
"name": "Delete clients",
"action_type": "destroy",
"object_type": "client"
},
{
"name": "Browse subscribers",
"action_type": "browse",
"object_type": "subscriber"
},
{
"name": "Read subscribers",
"action_type": "read",
"object_type": "subscriber"
},
{
"name": "Edit subscribers",
"action_type": "edit",
"object_type": "subscriber"
},
{
"name": "Add subscribers",
"action_type": "add",
"object_type": "subscriber"
},
{
"name": "Delete subscribers",
"action_type": "destroy",
"object_type": "subscriber"
},
{
"name": "Browse invites",
"action_type": "browse",
"object_type": "invite"
},
{
"name": "Read invites",
"action_type": "read",
"object_type": "invite"
},
{
"name": "Add invites",
"action_type": "add",
"object_type": "invite"
},
{
"name": "Edit invites",
"action_type": "edit",
"object_type": "invite"
},
{
"name": "Delete invites",
"action_type": "destroy",
"object_type": "invite"
}
]
},
{
"name": "User",
"entries": [
{
"name": "Ghost Owner",
"email": "ghost@ghost.org",
"status": "inactive",
"roles": [4]
}
]
}
],
"relations": [
{
"from": {
"model": "Role",
"match": "name",
"relation": "permissions"
},
"to": {
"model": "Permission",
"match": ["object_type", "action_type"]
},
"entries": {
"Administrator": {
"db": "all",
"mail": "all",
"notification": "all",
"post": "all",
"setting": "all",
"slug": "all",
"tag": "all",
"theme": "all",
"user": "all",
"role": "all",
"client": "all",
"subscriber": "all",
"invite": "all"
},
"Editor": {
"post": "all",
"setting": ["browse", "read"],
"slug": "all",
"tag": "all",
"user": "all",
"role": "all",
"client": "all",
"subscriber": ["add"]
},
"Author": {
"post": ["browse", "read", "add"],
"setting": ["browse", "read"],
"slug": "all",
"tag": ["browse", "read", "add"],
"user": ["browse", "read"],
"role": ["browse"],
"client": "all",
"subscriber": ["add"]
}
}
},
{
"from": {
"model": "Post",
"match": "title",
"relation": "tags"
},
"to": {
"model": "Tag",
"match": "name"
},
"entries": {
"Welcome to Ghost": ["Getting Started"]
}
}
]
}

View File

@ -0,0 +1,2 @@
module.exports = require('./fixtures');
module.exports.utils = require('./utils');

View File

@ -0,0 +1,230 @@
// # Fixture Utils
// Standalone file which can be required to help with advanced operations on the fixtures.json file
var _ = require('lodash'),
Promise = require('bluebird'),
models = require('../../../models'),
sequence = require('../../../utils/sequence'),
fixtures = require('./fixtures'),
// Private
matchFunc,
matchObj,
fetchRelationData,
findRelationFixture,
findModelFixture,
addFixturesForModel,
addFixturesForRelation,
findModelFixtureEntry,
findModelFixtures,
findPermissionRelationsForObject;
/**
* ### Match Func
* Figures out how to match across various combinations of keys and values.
* Match can be a string or an array containing 2 strings
* Key and Value are the values to be found
* Value can also be an array, in which case we look for a match in the array.
* @api private
* @param {String|Array} match
* @param {String|Integer} key
* @param {String|Array} [value]
* @returns {Function} matching function
*/
matchFunc = function matchFunc(match, key, value) {
if (_.isArray(match)) {
return function (item) {
var valueTest = true;
if (_.isArray(value)) {
valueTest = value.indexOf(item.get(match[1])) > -1;
} else if (value !== 'all') {
valueTest = item.get(match[1]) === value;
}
return item.get(match[0]) === key && valueTest;
};
}
return function (item) {
key = key === 0 && value ? value : key;
return item.get(match) === key;
};
};
matchObj = function matchObj(match, item) {
var matchObj = {};
if (_.isArray(match)) {
_.each(match, function (matchProp) {
matchObj[matchProp] = item.get(matchProp);
});
} else {
matchObj[match] = item.get(match);
}
return matchObj;
};
/**
* ### Fetch Relation Data
* Before we build relations we need to fetch all of the models from both sides so that we can
* use filter and find to quickly locate the correct models.
* @api private
* @param {{from, to, entries}} relation
* @returns {Promise<*>}
*/
fetchRelationData = function fetchRelationData(relation, options) {
var fromOptions = _.extend({}, options, {withRelated: [relation.from.relation]}),
props = {
from: models[relation.from.model].findAll(fromOptions),
to: models[relation.to.model].findAll(options)
};
return Promise.props(props);
};
/**
* ### Add Fixtures for Model
* Takes a model fixture, with a name and some entries and processes these
* into a sequence of promises to get each fixture added.
*
* @param {{name, entries}} modelFixture
* @returns {Promise.<*>}
*/
addFixturesForModel = function addFixturesForModel(modelFixture, options) {
return Promise.mapSeries(modelFixture.entries, function (entry) {
return models[modelFixture.name].findOne(entry, options).then(function (found) {
if (!found) {
return models[modelFixture.name].add(entry, options);
}
});
}).then(function (results) {
return {expected: modelFixture.entries.length, done: _.compact(results).length};
});
};
/**
* ## Add Fixtures for Relation
* Takes a relation fixtures object, with a from, to and some entries and processes these
* into a sequence of promises, to get each fixture added.
*
* @param {{from, to, entries}} relationFixture
* @returns {Promise.<*>}
*/
addFixturesForRelation = function addFixturesForRelation(relationFixture, options) {
var ops = [], max = 0;
return fetchRelationData(relationFixture, options).then(function getRelationOps(data) {
_.each(relationFixture.entries, function processEntries(entry, key) {
var fromItem = data.from.find(matchFunc(relationFixture.from.match, key));
_.each(entry, function processEntryValues(value, key) {
var toItems = data.to.filter(matchFunc(relationFixture.to.match, key, value));
max += toItems.length;
// Remove any duplicates that already exist in the collection
toItems = _.reject(toItems, function (item) {
return fromItem
.related(relationFixture.from.relation)
.findWhere(matchObj(relationFixture.to.match, item));
});
if (toItems && toItems.length > 0) {
ops.push(function addRelationItems() {
return fromItem[relationFixture.from.relation]().attach(toItems, options);
});
}
});
});
return sequence(ops);
}).then(function (result) {
return {expected: max, done: _(result).map('length').sum()};
});
};
/**
* ### Find Model Fixture
* Finds a model fixture based on model name
* @api private
* @param {String} modelName
* @returns {Object} model fixture
*/
findModelFixture = function findModelFixture(modelName) {
return _.find(fixtures.models, function (modelFixture) {
return modelFixture.name === modelName;
});
};
/**
* ### Find Model Fixture Entry
* Find a single model fixture entry by model name & a matching expression for the FIND function
* @param {String} modelName
* @param {String|Object|Function} matchExpr
* @returns {Object} model fixture entry
*/
findModelFixtureEntry = function findModelFixtureEntry(modelName, matchExpr) {
return _.find(findModelFixture(modelName).entries, matchExpr);
};
/**
* ### Find Model Fixtures
* Find a model fixture name & a matching expression for the FILTER function
* @param {String} modelName
* @param {String|Object|Function} matchExpr
* @returns {Object} model fixture
*/
findModelFixtures = function findModelFixtures(modelName, matchExpr) {
var foundModel = _.cloneDeep(findModelFixture(modelName));
foundModel.entries = _.filter(foundModel.entries, matchExpr);
return foundModel;
};
/**
* ### Find Relation Fixture
* Find a relation fixture by from & to models
* @api private
* @param {String} from
* @param {String} to
* @returns {Object} relation fixture
*/
findRelationFixture = function findRelationFixture(from, to) {
return _.find(fixtures.relations, function (relation) {
return relation.from.model === from && relation.to.model === to;
});
};
/**
* ### Find Permission Relations For Object
* Specialist function can return the permission relation fixture with only entries for a particular object.model
* @param {String} objName
* @returns {Object} fixture relation
*/
findPermissionRelationsForObject = function findPermissionRelationsForObject(objName) {
// Make a copy and delete any entries we don't want
var foundRelation = _.cloneDeep(findRelationFixture('Role', 'Permission'));
_.each(foundRelation.entries, function (entry, role) {
_.each(entry, function (perm, obj) {
if (obj !== objName) {
delete entry[obj];
}
});
if (_.isEmpty(entry)) {
delete foundRelation.entries[role];
}
});
return foundRelation;
};
module.exports = {
addFixturesForModel: addFixturesForModel,
addFixturesForRelation: addFixturesForRelation,
findModelFixtureEntry: findModelFixtureEntry,
findModelFixtures: findModelFixtures,
findPermissionRelationsForObject: findPermissionRelationsForObject
};

View File

@ -2,7 +2,6 @@ var path = require('path'),
Promise = require('bluebird'),
db = require('../db'),
errors = require('../../errors'),
i18n = require('../../i18n'),
ghostVersion = require('../../utils/ghost-version');
/**
@ -14,8 +13,8 @@ var path = require('path'),
*/
function validateDatabaseVersion(version) {
if (version === null) {
throw new errors.DatabaseNotSeededError({
message: i18n.t('errors.data.versioning.index.databaseNotSeeded')
throw new errors.DatabaseVersionError({
code: 'VERSION_DOES_NOT_EXIST'
});
}
@ -34,21 +33,24 @@ function validateDatabaseVersion(version) {
* If the database version is null, the database was never seeded.
* The seed migration script will set your database to current Ghost Version.
*/
function getDatabaseVersion() {
return db.knex.schema.hasTable('settings').then(function (exists) {
if (!exists) {
return Promise.reject(new errors.DatabaseNotPopulatedError({
message: i18n.t('errors.data.versioning.index.databaseNotPopulated')
}));
}
function getDatabaseVersion(options) {
options = options || {};
return db.knex('settings')
.where('key', 'databaseVersion')
.first('value')
.then(function (version) {
return validateDatabaseVersion(version.value);
});
});
return (options.transacting || db.knex).schema.hasTable('settings')
.then(function (exists) {
if (!exists) {
return Promise.reject(new errors.DatabaseVersionError({
code: 'SETTINGS_TABLE_DOES_NOT_EXIST'
}));
}
return (options.transacting || db.knex)('settings')
.where('key', 'databaseVersion')
.first('value')
.then(function (version) {
return validateDatabaseVersion(version ? version.value : null);
});
});
}
function getNewestDatabaseVersion() {
@ -59,8 +61,10 @@ function getNewestDatabaseVersion() {
* Database version cannot set from outside.
* If this function get called, we set the database version to your current Ghost version.
*/
function setDatabaseVersion(transaction) {
return (transaction || db.knex)('settings')
function setDatabaseVersion(options) {
options = options || {};
return (options.transacting || db.knex)('settings')
.where('key', 'databaseVersion')
.update({
value: getNewestDatabaseVersion()

View File

@ -9,3 +9,4 @@ function Sephiroth(options) {
}
module.exports = Sephiroth;
module.exports.errors = require('./lib/errors');

View File

@ -1,5 +1,4 @@
var Promise = require('bluebird'),
path = require('path'),
utils = require('../utils'),
errors = require('../errors'),
logging = require('../../../../logging');
@ -9,28 +8,40 @@ var Promise = require('bluebird'),
* - better error handling
* - prettier code please
* - dirty requires
*
* sephiroth init --only 1 (execute first script only)
* sephiroth init --skip 2 (execute all except of 2)
* sephiroth migrate --version 1.0 --only 1
*/
module.exports = function init(options) {
options = options || {};
var migrationFolder = options.migrationFolder || path.join(__dirname, '../../../migrations'),
dbInitTasks = utils.readTasks(migrationFolder + '/init');
var initPath = utils.getPath({type: 'init'}),
dbInitTasks = utils.readTasks(initPath),
skip = options.skip || null,
only = options.only || null;
return utils.createTransaction(function executeTasks(transaction) {
if (only !== null) {
dbInitTasks = [dbInitTasks[only - 1]];
} else if (skip !== null) {
dbInitTasks.splice(skip - 1, 1);
}
return utils.createTransaction(function executeTasks(transacting) {
return Promise.each(dbInitTasks, function executeInitTask(task) {
return utils.preTask({
database: transaction,
transacting: transacting,
task: task.name,
type: 'init'
}).then(function () {
logging.info('Running: ' + task.name);
return task.execute({
database: transaction
transacting: transacting
});
}).then(function () {
return utils.postTask({
database: transaction,
transacting: transacting,
task: task.name,
type: 'init'
});

View File

@ -36,7 +36,7 @@ exports.createTransaction = function createTransaction(callback) {
exports.preTask = function preTask(options) {
options = options || {};
var localDatabase = options.database,
var localDatabase = options.transacting,
task = options.task,
type = options.type;
@ -71,7 +71,7 @@ exports.preTask = function preTask(options) {
exports.postTask = function postTask(options) {
options = options || {};
var localDatabase = options.database,
var localDatabase = options.transacting,
task = options.task,
type = options.type;
@ -83,19 +83,21 @@ exports.postTask = function postTask(options) {
};
/**
* - check init
* - check seed
* DB health depends on the amount of executed init scripts right now
*
* @TODO: optimise!
* @TODO:
* - alternative for checking length of init scripts?
*/
exports.isDatabaseOK = function isDatabaseOK(options) {
options = options || {};
var localDatabase = options.database;
var localDatabase = options.transacting,
initPath = exports.getPath({type: 'init'}),
dbInitTasksLength = exports.readTasks(initPath).length;
return (localDatabase || database.knex)('migrations')
.then(function (migrations) {
if (_.find(migrations, {type: 'init'})) {
if (_.filter(migrations, {type: 'init'}).length === dbInitTasksLength) {
return;
}
@ -117,3 +119,21 @@ exports.isDatabaseOK = function isDatabaseOK(options) {
});
});
};
/**
* @TODO:
* - make migrationPath configureable
*/
exports.getPath = function getPath(options) {
options = options || {};
var migrationsPath = path.join(__dirname, '../../migrations');
switch (options.type) {
case 'init':
migrationsPath += '/init';
break;
}
return migrationsPath;
};

View File

@ -29,6 +29,7 @@ function GhostError(options) {
this.help = options.help || this.help;
this.errorType = this.name = options.errorType || this.errorType;
this.errorDetails = options.errorDetails;
this.code = options.code || null;
// @TODO: ?
this.property = options.property;

View File

@ -298,6 +298,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
item.include = options.include;
});
}
return result;
});
},

View File

@ -182,6 +182,10 @@ User = ghostBookshelf.Model.extend({
},
defaultFilters: function defaultFilters() {
if (this.isInternalContext()) {
return null;
}
return this.isPublicContext() ? null : 'status:[' + activeStates.join(',') + ']';
}
}, {
@ -253,17 +257,21 @@ User = ghostBookshelf.Model.extend({
/**
* ### Find One
*
* We have to clone the data, because we remove values from this object.
* This is not expected from outside!
*
* @extends ghostBookshelf.Model.findOne to include roles
* **See:** [ghostBookshelf.Model.findOne](base.js.html#Find%20One)
*/
findOne: function findOne(data, options) {
findOne: function findOne(dataToClone, options) {
var query,
status,
optInc,
data = _.cloneDeep(dataToClone),
lookupRole = data.role;
delete data.role;
data = _.defaults(data || {}, {
status: 'active'
});
@ -294,7 +302,7 @@ User = ghostBookshelf.Model.extend({
if (status === 'active') {
query.query('whereIn', 'status', activeStates);
} else if (status !== 'all') {
query.query('where', {status: options.status});
query.query('where', {status: status});
}
options = this.filterOptions(options, 'findOne');
@ -355,16 +363,24 @@ User = ghostBookshelf.Model.extend({
* Naive user add
* Hashes the password provided before saving to the database.
*
* @param {object} data
* We have to clone the data, because we remove values from this object.
* This is not expected from outside!
*
* @param {object} dataToClone
* @param {object} options
* @extends ghostBookshelf.Model.add to manage all aspects of user signup
* **See:** [ghostBookshelf.Model.add](base.js.html#Add)
*/
add: function add(data, options) {
add: function add(dataToClone, options) {
var self = this,
data = _.cloneDeep(dataToClone),
userData = this.filterData(data),
roles;
if (!userData.password) {
userData.password = utils.uid(50);
}
userData.password = _.toString(userData.password);
options = this.filterOptions(options, 'add');

View File

@ -10,6 +10,7 @@ var should = require('should'),
errors = require(config.get('paths').corePath + '/server/errors'),
permissions = require(config.get('paths').corePath + '/server/permissions'),
api = require(config.get('paths').corePath + '/server/api'),
Sephiroth = require(config.get('paths').corePath + '/server/data/sephiroth'),
apps = require(config.get('paths').corePath + '/server/apps'),
i18n = require(config.get('paths').corePath + '/server/i18n'),
xmlrpc = require(config.get('paths').corePath + '/server/data/xml/xmlrpc'),
@ -64,13 +65,14 @@ describe('server bootstrap', function () {
.catch(function (err) {
migration.populate.calledOnce.should.eql(false);
config.get('maintenance').enabled.should.eql(false);
(err instanceof errors.GhostError).should.eql(true);
(err instanceof Sephiroth.errors.DatabaseIsNotOkError).should.eql(true);
err.code.should.eql('MIGRATION_TABLE_IS_MISSING');
done();
});
});
// @TODO fix these two tests once we've decided on a new migration
// @TODO kate-migrations
// versioning scheme
// the tests do not work right now because if the version isn't an
// alpha version, we error. I've added two temporary tests to show this.

View File

@ -80,7 +80,7 @@ describe('Versioning', function () {
done('Should throw an error if the settings table does not exist');
}).catch(function (err) {
should.exist(err);
(err instanceof errors.DatabaseNotPopulatedError).should.eql(true);
(err instanceof errors.DatabaseVersionError).should.eql(true);
knexStub.get.calledOnce.should.be.true();
knexMock.schema.hasTable.calledOnce.should.be.true();

View File

@ -399,7 +399,7 @@ fixtures = {
/** Test Utility Functions **/
initData = function initData() {
return migration.populate();
return sephiroth.commands.init();
};
clearData = function clearData() {
@ -473,13 +473,12 @@ getFixtureOps = function getFixtureOps(toDos) {
// Database initialisation
if (toDos.init || toDos.default) {
fixtureOps.push(function initDB() {
return new Promise(function initDb(resolve, reject) {
migration.populate({tablesOnly: tablesOnly})
.then(function () {
resolve();
})
.catch(reject);
});
// skip adding all fixtures!
if (tablesOnly) {
return sephiroth.commands.init({skip: 2});
}
return sephiroth.commands.init();
});
delete toDos.default;