mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Run import run operations in order.
Closes #1977, Refs #3473 - Ensure that import operations are run in sequence. Previously the operations were started in order but subsequent ops were allowed to begin before the previous finished, which would result in out-of-order execution. - Fix bug in attach() where a model property was being passed in instead of a transaction object. If the call was made when a transaction was in process, it could cause bookshelf/knex to hang and never finish the transaction.
This commit is contained in:
parent
efddc98f53
commit
35e2387541
4 changed files with 89 additions and 42 deletions
|
@ -93,10 +93,9 @@ Importer000.prototype.doUserImport = function (t, tableData, users, errors) {
|
|||
|
||||
Importer000.prototype.doImport = function (data) {
|
||||
var self = this,
|
||||
ops = [],
|
||||
errors = [],
|
||||
tableData = data.data,
|
||||
imported = {},
|
||||
errors = [],
|
||||
users = {},
|
||||
owner = {};
|
||||
|
||||
|
@ -108,6 +107,8 @@ Importer000.prototype.doImport = function (data) {
|
|||
|
||||
// Step 1: Attempt to handle adding new users
|
||||
self.doUserImport(t, tableData, users, errors).then(function (result) {
|
||||
var importResults = [];
|
||||
|
||||
imported.users = result;
|
||||
|
||||
_.each(imported.users, function (user) {
|
||||
|
@ -126,33 +127,28 @@ Importer000.prototype.doImport = function (data) {
|
|||
tableData = utils.preProcessPostTags(tableData);
|
||||
}
|
||||
|
||||
// Import things in the right order:
|
||||
if (tableData.tags && tableData.tags.length) {
|
||||
utils.importTags(ops, tableData.tags, t);
|
||||
}
|
||||
// Import things in the right order
|
||||
|
||||
if (tableData.posts && tableData.posts.length) {
|
||||
utils.importPosts(ops, tableData.posts, t);
|
||||
}
|
||||
return utils.importTags(tableData.tags, t).then(function (results) {
|
||||
if (results) {
|
||||
importResults = importResults.concat(results);
|
||||
}
|
||||
|
||||
if (tableData.settings && tableData.settings.length) {
|
||||
utils.importSettings(ops, tableData.settings, t);
|
||||
}
|
||||
return utils.importPosts(tableData.posts, t);
|
||||
}).then(function (results) {
|
||||
if (results) {
|
||||
importResults = importResults.concat(results);
|
||||
}
|
||||
|
||||
/** do nothing with these tables, the data shouldn't have changed from the fixtures
|
||||
* permissions
|
||||
* roles
|
||||
* permissions_roles
|
||||
* permissions_users
|
||||
*/
|
||||
|
||||
// Write changes to DB, if successful commit, otherwise rollback
|
||||
Promise.settle(ops).then(function (descriptors) {
|
||||
var errors = [];
|
||||
|
||||
descriptors.forEach(function (d) {
|
||||
if (d.isRejected()) {
|
||||
errors = errors.concat(d.reason());
|
||||
return utils.importSettings(tableData.settings, t);
|
||||
}).then(function (results) {
|
||||
if (results) {
|
||||
importResults = importResults.concat(results);
|
||||
}
|
||||
}).then(function () {
|
||||
importResults.forEach(function (p) {
|
||||
if (p.isRejected()) {
|
||||
errors = errors.concat(p.reason());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -162,6 +158,13 @@ Importer000.prototype.doImport = function (data) {
|
|||
t.rollback(errors);
|
||||
}
|
||||
});
|
||||
|
||||
/** do nothing with these tables, the data shouldn't have changed from the fixtures
|
||||
* permissions
|
||||
* roles
|
||||
* permissions_roles
|
||||
* permissions_users
|
||||
*/
|
||||
});
|
||||
}).then(function () {
|
||||
//TODO: could return statistics of imported items
|
||||
|
|
|
@ -30,11 +30,13 @@ cleanError = function cleanError(error) {
|
|||
offendingProperty = temp.length === 2 ? temp[1] : error.model;
|
||||
temp = offendingProperty.split('.');
|
||||
value = temp.length === 2 ? error.data[temp[1]] : 'unknown';
|
||||
} else if (error.raw.detail) {
|
||||
value = error.raw.detail;
|
||||
offendingProperty = error.model;
|
||||
}
|
||||
message = 'Duplicate entry found. Multiple values of "' + value + '" found for ' + offendingProperty + '.';
|
||||
}
|
||||
|
||||
|
||||
offendingProperty = offendingProperty || error.model;
|
||||
value = value || 'unknown';
|
||||
message = message || error.raw.message;
|
||||
|
|
|
@ -149,7 +149,13 @@ utils = {
|
|||
return tableData;
|
||||
},
|
||||
|
||||
importTags: function importTags(ops, tableData, transaction) {
|
||||
importTags: function importTags(tableData, transaction) {
|
||||
if (!tableData) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var ops = [];
|
||||
|
||||
tableData = stripProperties(['id'], tableData);
|
||||
_.each(tableData, function (tag) {
|
||||
// Validate minimum tag fields
|
||||
|
@ -160,7 +166,6 @@ utils = {
|
|||
ops.push(models.Tag.findOne({name: tag.name}, {transacting: transaction}).then(function (_tag) {
|
||||
if (!_tag) {
|
||||
return models.Tag.add(tag, _.extend(internal, {transacting: transaction}))
|
||||
// add pass-through error handling so that bluebird doesn't think we've dropped it
|
||||
.catch(function (error) {
|
||||
return Promise.reject({raw: error, model: 'tag', data: tag});
|
||||
});
|
||||
|
@ -169,21 +174,46 @@ utils = {
|
|||
return _tag;
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.settle(ops);
|
||||
},
|
||||
|
||||
importPosts: function importPosts(ops, tableData, transaction) {
|
||||
importPosts: function importPosts(tableData, transaction) {
|
||||
if (!tableData) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var ops = [];
|
||||
|
||||
tableData = stripProperties(['id'], tableData);
|
||||
|
||||
_.each(tableData, function (post) {
|
||||
// Validate minimum post fields
|
||||
if (areEmpty(post, 'title', 'slug', 'markdown')) {
|
||||
return;
|
||||
}
|
||||
ops.push(models.Post.add(post, _.extend(internal, {transacting: transaction, importing: true}))
|
||||
// add pass-through error handling so that bluebird doesn't think we've dropped it
|
||||
.catch(function (error) {
|
||||
return Promise.reject({raw: error, model: 'post', data: post});
|
||||
}));
|
||||
|
||||
ops.push(function () {
|
||||
return models.Post.add(post, _.extend(internal, {transacting: transaction, importing: true}))
|
||||
.catch(function (error) {
|
||||
return Promise.reject({raw: error, model: 'post', data: post});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.reduce(ops, function (results, op) {
|
||||
return op().then(function (result) {
|
||||
results.push(result);
|
||||
|
||||
return results;
|
||||
}).catch(function (error) {
|
||||
if (error) {
|
||||
results.push(error);
|
||||
}
|
||||
|
||||
return results;
|
||||
});
|
||||
}, []).settle();
|
||||
},
|
||||
|
||||
importUsers: function importUsers(tableData, existingUsers, transaction) {
|
||||
|
@ -206,7 +236,6 @@ utils = {
|
|||
user.status = 'locked';
|
||||
|
||||
ops.push(models.User.add(user, _.extend(internal, {transacting: transaction}))
|
||||
// add pass-through error handling so that bluebird doesn't think we've dropped it
|
||||
.catch(function (error) {
|
||||
return Promise.reject({raw: error, model: 'user', data: user});
|
||||
}));
|
||||
|
@ -215,11 +244,16 @@ utils = {
|
|||
return ops;
|
||||
},
|
||||
|
||||
importSettings: function importSettings(ops, tableData, transaction) {
|
||||
importSettings: function importSettings(tableData, transaction) {
|
||||
if (!tableData) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// for settings we need to update individual settings, and insert any missing ones
|
||||
// settings we MUST NOT update are 'core' and 'theme' settings
|
||||
// as all of these will cause side effects which don't make sense for an import
|
||||
var blackList = ['core', 'theme'];
|
||||
var blackList = ['core', 'theme'],
|
||||
ops = [];
|
||||
|
||||
tableData = stripProperties(['id'], tableData);
|
||||
tableData = _.filter(tableData, function (data) {
|
||||
|
@ -232,21 +266,27 @@ utils = {
|
|||
});
|
||||
|
||||
ops.push(models.Settings.edit(tableData, _.extend(internal, {transacting: transaction}))
|
||||
// add pass-through error handling so that bluebird doesn't think we've dropped it
|
||||
.catch(function (error) {
|
||||
return Promise.reject({raw: error, model: 'setting', data: tableData});
|
||||
}));
|
||||
|
||||
return Promise.settle(ops);
|
||||
},
|
||||
|
||||
/** For later **/
|
||||
importApps: function importApps(ops, tableData, transaction) {
|
||||
importApps: function importApps(tableData, transaction) {
|
||||
if (!tableData) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var ops = [];
|
||||
|
||||
tableData = stripProperties(['id'], tableData);
|
||||
_.each(tableData, function (app) {
|
||||
// Avoid duplicates
|
||||
ops.push(models.App.findOne({name: app.name}, {transacting: transaction}).then(function (_app) {
|
||||
if (!_app) {
|
||||
return models.App.add(app, _.extend(internal, {transacting: transaction}))
|
||||
// add pass-through error handling so that bluebird doesn't think we've dropped it
|
||||
.catch(function (error) {
|
||||
return Promise.reject({raw: error, model: 'app', data: app});
|
||||
});
|
||||
|
@ -255,6 +295,8 @@ utils = {
|
|||
return _app;
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.settle(ops);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -144,7 +144,7 @@ Post = ghostBookshelf.Model.extend({
|
|||
createdTag = createdTag.toJSON();
|
||||
// _.omit(options, 'query') is a fix for using bookshelf 0.6.8
|
||||
// (https://github.com/tgriesser/bookshelf/issues/294)
|
||||
return post.tags().attach(createdTag.id, createdTag.name, _.omit(options, 'query'));
|
||||
return post.tags().attach(createdTag.id, _.omit(options, 'query'));
|
||||
});
|
||||
|
||||
tagOps.push(createAndAttachOperation);
|
||||
|
|
Loading…
Add table
Reference in a new issue