diff --git a/core/server/api/clients.js b/core/server/api/clients.js index 145474e0f2..45b12a6404 100644 --- a/core/server/api/clients.js +++ b/core/server/api/clients.js @@ -52,6 +52,7 @@ clients = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {attrs: attrs}), + localUtils.convertOptions(), // TODO: add permissions // utils.handlePublicPermissions(docName, 'read'), doQuery diff --git a/core/server/api/invites.js b/core/server/api/invites.js index 69a2944c80..9841914401 100644 --- a/core/server/api/invites.js +++ b/core/server/api/invites.js @@ -23,8 +23,8 @@ invites = { tasks = [ localUtils.validate(docName, {opts: localUtils.browseDefaultOptions}), - localUtils.handlePublicPermissions(docName, 'browse'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePublicPermissions(docName, 'browse'), modelQuery ]; @@ -52,8 +52,8 @@ invites = { tasks = [ localUtils.validate(docName, {attrs: attrs}), - localUtils.handlePublicPermissions(docName, 'read'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePublicPermissions(docName, 'read'), modelQuery ]; @@ -76,8 +76,8 @@ invites = { tasks = [ localUtils.validate(docName, {opts: localUtils.idDefaultOptions}), - localUtils.handlePermissions(docName, 'destroy'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePermissions(docName, 'destroy'), modelQuery ]; @@ -221,7 +221,7 @@ invites = { } function fetchLoggedInUser(options) { - return models.User.findOne({id: loggedInUser}, _.merge({}, _.omit(options, 'data'), {include: ['roles']})) + return models.User.findOne({id: loggedInUser}, _.merge({}, _.omit(options, 'data'), {withRelated: ['roles']})) .then(function (user) { if (!user) { return Promise.reject(new common.errors.NotFoundError({message: common.i18n.t('errors.api.users.userNotFound')})); @@ -234,8 +234,8 @@ invites = { tasks = [ localUtils.validate(docName, {opts: ['email']}), - localUtils.handlePermissions(docName, 'add'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePermissions(docName, 'add'), fetchLoggedInUser, validation, checkIfUserExists, diff --git a/core/server/api/posts.js b/core/server/api/posts.js index f38915afb5..e8d1c15eda 100644 --- a/core/server/api/posts.js +++ b/core/server/api/posts.js @@ -59,8 +59,8 @@ posts = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: permittedOptions}), - localUtils.handlePublicPermissions(docName, 'browse', unsafeAttrs), localUtils.convertOptions(allowedIncludes, models.Post.allowedFormats), + localUtils.handlePublicPermissions(docName, 'browse', unsafeAttrs), modelQuery ]; @@ -106,8 +106,8 @@ posts = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {attrs: attrs, opts: extraAllowedOptions}), - localUtils.handlePublicPermissions(docName, 'read', unsafeAttrs), localUtils.convertOptions(allowedIncludes, models.Post.allowedFormats), + localUtils.handlePublicPermissions(docName, 'read', unsafeAttrs), modelQuery ]; @@ -162,8 +162,8 @@ posts = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: localUtils.idDefaultOptions.concat(extraAllowedOptions)}), - localUtils.handlePermissions(docName, 'edit', unsafeAttrs), localUtils.convertOptions(allowedIncludes), + localUtils.handlePermissions(docName, 'edit', unsafeAttrs), modelQuery ]; @@ -206,8 +206,8 @@ posts = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName), - localUtils.handlePermissions(docName, 'add', unsafeAttrs), localUtils.convertOptions(allowedIncludes), + localUtils.handlePermissions(docName, 'add', unsafeAttrs), modelQuery ]; @@ -245,8 +245,8 @@ posts = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: localUtils.idDefaultOptions}), - localUtils.handlePermissions(docName, 'destroy', unsafeAttrs), localUtils.convertOptions(allowedIncludes), + localUtils.handlePermissions(docName, 'destroy', unsafeAttrs), deletePost ]; diff --git a/core/server/api/roles.js b/core/server/api/roles.js index e47589608d..8aadb28316 100644 --- a/core/server/api/roles.js +++ b/core/server/api/roles.js @@ -66,6 +66,7 @@ roles = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: permittedOptions}), + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'browse'), modelQuery ]; diff --git a/core/server/api/slugs.js b/core/server/api/slugs.js index 4bb547a43e..0857c91674 100644 --- a/core/server/api/slugs.js +++ b/core/server/api/slugs.js @@ -73,6 +73,7 @@ slugs = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: opts, attrs: attrs}), + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'generate'), checkAllowedTypes, modelQuery diff --git a/core/server/api/subscribers.js b/core/server/api/subscribers.js index db89ca3b3a..ac67cf860d 100644 --- a/core/server/api/subscribers.js +++ b/core/server/api/subscribers.js @@ -38,6 +38,7 @@ subscribers = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: localUtils.browseDefaultOptions}), + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'browse'), doQuery ]; @@ -79,6 +80,7 @@ subscribers = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {attrs: attrs}), + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'read'), doQuery ]; @@ -129,6 +131,7 @@ subscribers = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName), + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'add'), doQuery ]; @@ -171,6 +174,7 @@ subscribers = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: localUtils.idDefaultOptions}), + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'edit'), doQuery ]; @@ -224,6 +228,7 @@ subscribers = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: ['id', 'email']}), + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'destroy'), getSubscriberByEmail, doQuery @@ -279,6 +284,7 @@ subscribers = { } tasks = [ + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'browse'), exportSubscribers ]; @@ -341,6 +347,7 @@ subscribers = { } tasks = [ + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'add'), importCSV ]; diff --git a/core/server/api/tags.js b/core/server/api/tags.js index 5d1ac470c7..4bf80abb8c 100644 --- a/core/server/api/tags.js +++ b/core/server/api/tags.js @@ -37,8 +37,8 @@ tags = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: localUtils.browseDefaultOptions}), - localUtils.handlePublicPermissions(docName, 'browse'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePublicPermissions(docName, 'browse'), doQuery ]; @@ -79,8 +79,8 @@ tags = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {attrs: attrs}), - localUtils.handlePublicPermissions(docName, 'read'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePublicPermissions(docName, 'read'), doQuery ]; @@ -114,8 +114,8 @@ tags = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName), - localUtils.handlePermissions(docName, 'add'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePermissions(docName, 'add'), doQuery ]; @@ -157,8 +157,8 @@ tags = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: localUtils.idDefaultOptions}), - localUtils.handlePermissions(docName, 'edit'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePermissions(docName, 'edit'), doQuery ]; @@ -188,8 +188,8 @@ tags = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: localUtils.idDefaultOptions}), - localUtils.handlePermissions(docName, 'destroy'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePermissions(docName, 'destroy'), deleteTag ]; diff --git a/core/server/api/users.js b/core/server/api/users.js index f5e4839611..23f1c9327c 100644 --- a/core/server/api/users.js +++ b/core/server/api/users.js @@ -42,8 +42,8 @@ users = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: permittedOptions}), - localUtils.handlePublicPermissions(docName, 'browse'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePublicPermissions(docName, 'browse'), doQuery ]; @@ -89,8 +89,8 @@ users = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {attrs: attrs}), - localUtils.handlePublicPermissions(docName, 'read'), localUtils.convertOptions(allowedIncludes), + localUtils.handlePublicPermissions(docName, 'read'), doQuery ]; @@ -153,7 +153,7 @@ users = { editedUserId = options.id; return models.User.findOne( - {id: options.context.user, status: 'all'}, {include: ['roles']} + {id: options.context.user, status: 'all'}, {withRelated: ['roles']} ).then(function (contextUser) { var contextRoleId = contextUser.related('roles').toJSON(options)[0].id; @@ -213,8 +213,8 @@ users = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: permittedOptions}), - handlePermissions, localUtils.convertOptions(allowedIncludes), + handlePermissions, doQuery ]; @@ -273,8 +273,8 @@ users = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: localUtils.idDefaultOptions}), - handlePermissions, localUtils.convertOptions(allowedIncludes), + handlePermissions, deleteUser ]; @@ -343,8 +343,8 @@ users = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ validateRequest, - handlePermissions, localUtils.convertOptions(allowedIncludes), + handlePermissions, doQuery ]; @@ -395,8 +395,8 @@ users = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate('owner'), - handlePermissions, localUtils.convertOptions(allowedIncludes), + handlePermissions, doQuery ]; diff --git a/core/server/api/utils.js b/core/server/api/utils.js index dfb879f896..c8534a9e16 100644 --- a/core/server/api/utils.js +++ b/core/server/api/utils.js @@ -277,7 +277,8 @@ utils = { */ return function doConversion(options) { if (options.include) { - options.include = utils.prepareInclude(options.include, allowedIncludes); + options.withRelated = utils.prepareInclude(options.include, allowedIncludes); + delete options.include; } if (options.fields) { diff --git a/core/server/api/webhooks.js b/core/server/api/webhooks.js index 4766718e84..117fdb0125 100644 --- a/core/server/api/webhooks.js +++ b/core/server/api/webhooks.js @@ -92,6 +92,7 @@ webhooks = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName), + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'add'), doQuery ]; @@ -122,6 +123,7 @@ webhooks = { // Push all of our tasks into a `tasks` array in the correct order tasks = [ localUtils.validate(docName, {opts: localUtils.idDefaultOptions}), + localUtils.convertOptions(), localUtils.handlePermissions(docName, 'destroy'), doQuery ]; diff --git a/core/server/models/base/index.js b/core/server/models/base/index.js index f7b386d79d..e813aaec65 100644 --- a/core/server/models/base/index.js +++ b/core/server/models/base/index.js @@ -322,14 +322,14 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ * * `toJSON` calls `serialize`. * - * @param options + * @param unfilteredOptions * @returns {*} */ - toJSON: function toJSON(options) { - var opts = _.cloneDeep(options || {}); - opts.omitPivot = true; + toJSON: function toJSON(unfilteredOptions) { + var options = ghostBookshelf.Model.filterOptions(unfilteredOptions, 'toJSON'); + options.omitPivot = true; - return proto.toJSON.call(this, opts); + return proto.toJSON.call(this, options); }, // Get attributes that have been updated (values before a .save() call) @@ -389,9 +389,13 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ * * @return {Object} Keys allowed in the `options` hash of every model's method. */ - permittedOptions: function permittedOptions() { + permittedOptions: function permittedOptions(methodName) { + if (methodName === 'toJSON') { + return ['shallow', 'withRelated', 'context', 'columns']; + } + // terms to whitelist for all methods. - return ['context', 'include', 'transacting', 'importing', 'forUpdate']; + return ['context', 'withRelated', 'transacting', 'importing', 'forUpdate']; }, /** @@ -455,15 +459,29 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ /** * Filters potentially unsafe `options` in a model method's arguments, so you can pass them to Bookshelf / Knex. - * @param {Object} options Represents options to filter in order to be passed to the Bookshelf query. + * @param {Object} unfilteredOptions Represents options to filter in order to be passed to the Bookshelf query. * @param {String} methodName The name of the method to check valid options for. * @return {Object} The filtered results of `options`. */ - filterOptions: function filterOptions(options, methodName) { - var permittedOptions = this.permittedOptions(methodName, options), - filteredOptions = _.pick(options, permittedOptions); + filterOptions: function filterOptions(unfilteredOptions, methodName, filterConfig) { + unfilteredOptions = unfilteredOptions || {}; + filterConfig = filterConfig || {}; - return filteredOptions; + if (unfilteredOptions.hasOwnProperty('include')) { + throw new common.errors.IncorrectUsageError({ + message: 'The model layer expects using `withRelated`.' + }); + } + + var options = _.cloneDeep(unfilteredOptions), + extraAllowedProperties = filterConfig.extraAllowedProperties || [], + permittedOptions; + + permittedOptions = this.permittedOptions(methodName, options); + permittedOptions = _.union(permittedOptions, extraAllowedProperties); + options = _.pick(options, permittedOptions); + + return options; }, // ## Model Data Functions @@ -471,14 +489,12 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ /** * ### Find All * Fetches all the data for a particular model - * @param {Object} options (optional) + * @param {Object} unfilteredOptions (optional) * @return {Promise(ghostBookshelf.Collection)} Collection of all Models */ - findAll: function findAll(options) { - options = this.filterOptions(options, 'findAll'); - options.withRelated = _.union(options.withRelated, options.include); - - var itemCollection = this.forge(); + findAll: function findAll(unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'findAll'), + itemCollection = this.forge(); // transforms fictive keywords like 'all' (status:all) into correct allowed values if (this.processOptions) { @@ -488,9 +504,9 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ itemCollection.applyDefaultAndCustomFilters(options); return itemCollection.fetchAll(options).then(function then(result) { - if (options.include) { + if (options.withRelated) { _.each(result.models, function each(item) { - item.include = options.include; + item.withRelated = options.withRelated; }); } @@ -516,12 +532,10 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ * total: __ * } * - * @param {Object} options + * @param {Object} unfilteredOptions */ - findPage: function findPage(options) { - options = options || {}; - - var self = this, + findPage: function findPage(unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'findPage'), itemCollection = this.forge(), tableName = _.result(this.prototype, 'tableName'), requestedColumns = options.columns; @@ -529,9 +543,6 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ // Set this to true or pass ?debug=true as an API option to get output itemCollection.debug = options.debug && config.get('env') !== 'production'; - // Filter options so that only permitted ones remain - options = this.filterOptions(options, 'findPage'); - // This applies default properties like 'staticPages' and 'status' // And then converts them to 'where' options... this behaviour is effectively deprecated in favour // of using filter - it's only be being kept here so that we can transition cleanly. @@ -540,10 +551,6 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ // Add Filter behaviour itemCollection.applyDefaultAndCustomFilters(options); - // Handle related objects - // TODO: this should just be done for all methods @ the API level - options.withRelated = _.union(options.withRelated, options.include); - // Ensure only valid fields/columns are added to query // and append default columns to fetch if (options.columns) { @@ -552,16 +559,16 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ } if (options.order) { - options.order = self.parseOrderOption(options.order, options.include); - } else if (self.orderDefaultRaw) { - options.orderRaw = self.orderDefaultRaw(); + options.order = this.parseOrderOption(options.order, options.withRelated); + } else if (this.orderDefaultRaw) { + options.orderRaw = this.orderDefaultRaw(); } else { - options.order = self.orderDefaultOptions(); + options.order = this.orderDefaultOptions(); } return itemCollection.fetchPage(options).then(function formatResponse(response) { var data = {}, - models = []; + models; options.columns = requestedColumns; models = response.collection.toJSON(options); @@ -571,6 +578,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ data[tableName] = _.map(models, function transform(model) { return options.columns ? _.pick(model, options.columns) : model; }); + data.meta = {pagination: response.pagination}; return data; }); @@ -580,13 +588,12 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ * ### Find One * Naive find one where data determines what to match on * @param {Object} data - * @param {Object} options (optional) + * @param {Object} unfilteredOptions (optional) * @return {Promise(ghostBookshelf.Model)} Single Model */ - findOne: function findOne(data, options) { + findOne: function findOne(data, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'findOne'); data = this.filterData(data); - options = this.filterOptions(options, 'findOne'); - return this.forge(data).fetch(options); }, @@ -598,15 +605,15 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ * Based on the `method` option Bookshelf and Ghost can determine if a query is an insert or an update. * * @param {Object} data - * @param {Object} options (optional) + * @param {Object} unfilteredOptions (optional) * @return {Promise(ghostBookshelf.Model)} Edited Model */ - edit: function edit(data, options) { - var id = options.id, + edit: function edit(data, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'edit', {extraAllowedProperties: ['id']}), + id = options.id, model = this.forge({id: id}); data = this.filterData(data); - options = this.filterOptions(options, 'edit'); // We allow you to disable timestamps when run migration, so that the posts `updated_at` value is the same if (options.importing) { @@ -624,13 +631,15 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ * ### Add * Naive add * @param {Object} data - * @param {Object} options (optional) + * @param {Object} unfilteredOptions (optional) * @return {Promise(ghostBookshelf.Model)} Newly Added Model */ - add: function add(data, options) { + add: function add(data, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'add'), + model; + data = this.filterData(data); - options = this.filterOptions(options, 'add'); - var model = this.forge(data); + model = this.forge(data); // We allow you to disable timestamps when importing posts so that the new posts `updated_at` value is the same // as the import json blob. More details refer to https://github.com/TryGhost/Ghost/issues/1696 @@ -647,17 +656,19 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ /** * ### Destroy * Naive destroy - * @param {Object} options (optional) + * @param {Object} unfilteredOptions (optional) * @return {Promise(ghostBookshelf.Model)} Empty Model */ - destroy: function destroy(options) { - var id = options.id; - options = this.filterOptions(options, 'destroy'); + destroy: function destroy(unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'destroy', {extraAllowedProperties: ['id']}), + id = options.id; // Fetch the object before destroying it, so that the changed data is available to events - return this.forge({id: id}).fetch(options).then(function then(obj) { - return obj.destroy(options); - }); + return this.forge({id: id}) + .fetch(options) + .then(function then(obj) { + return obj.destroy(options); + }); }, /** @@ -757,11 +768,11 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({ }); }, - parseOrderOption: function (order, include) { + parseOrderOption: function (order, withRelated) { var permittedAttributes, result, rules; permittedAttributes = this.prototype.permittedAttributes(); - if (include && include.indexOf('count.posts') > -1) { + if (withRelated && withRelated.indexOf('count.posts') > -1) { permittedAttributes.push('count.posts'); } result = {}; diff --git a/core/server/models/base/token.js b/core/server/models/base/token.js index 0b63434b1c..adf35de9b8 100644 --- a/core/server/models/base/token.js +++ b/core/server/models/base/token.js @@ -21,8 +21,9 @@ Basetoken = ghostBookshelf.Model.extend({ } }, { - destroyAllExpired: function destroyAllExpired(options) { - options = this.filterOptions(options, 'destroyAll'); + destroyAllExpired: function destroyAllExpired(unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'destroyAll'); + return ghostBookshelf.Collection.forge([], {model: this}) .query('where', 'expires', '<', Date.now()) .fetch(options) @@ -33,12 +34,11 @@ Basetoken = ghostBookshelf.Model.extend({ /** * ### destroyByUser - * @param {[type]} options has context and id. Context is the user doing the destroy, id is the user to destroy + * @param {[type]} unfilteredOptions has context and id. Context is the user doing the destroy, id is the user to destroy */ - destroyByUser: function destroyByUser(options) { - var userId = options.id; - - options = this.filterOptions(options, 'destroyByUser'); + destroyByUser: function destroyByUser(unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'destroyByUser', {extraAllowedProperties: ['id']}), + userId = options.id; if (userId) { return ghostBookshelf.Collection.forge([], {model: this}) @@ -54,12 +54,12 @@ Basetoken = ghostBookshelf.Model.extend({ /** * ### destroyByToken - * @param {[type]} options has token where token is the token to destroy + * @param {[type]} unfilteredOptions has token where token is the token to destroy */ - destroyByToken: function destroyByToken(options) { - var token = options.token; + destroyByToken: function destroyByToken(unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'destroyByToken', {extraAllowedProperties: ['token']}), + token = options.token; - options = this.filterOptions(options, 'destroyByUser'); options.require = true; return this.forge() diff --git a/core/server/models/invite.js b/core/server/models/invite.js index 2eabb72c77..81e972280a 100644 --- a/core/server/models/invite.js +++ b/core/server/models/invite.js @@ -1,7 +1,6 @@ 'use strict'; const crypto = require('crypto'), - _ = require('lodash'), constants = require('../lib/constants'), ghostBookshelf = require('./base'); @@ -11,10 +10,10 @@ let Invite, Invite = ghostBookshelf.Model.extend({ tableName: 'invites', - toJSON: function (options) { - options = options || {}; + toJSON: function (unfilteredOptions) { + var options = Invite.filterOptions(unfilteredOptions, 'toJSON'), + attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options); - var attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options); delete attrs.token; return attrs; } @@ -27,31 +26,10 @@ Invite = ghostBookshelf.Model.extend({ return options; }, - /** - * @TODO: can't use base class, because: - * options.withRelated = _.union(options.withRelated, options.include); is missing - * there are some weird self implementations in each model - * so adding this line, will destroy other models, because they rely on something else - * FIX ME!!!!! - */ - findOne: function findOne(data, options) { - options = options || {}; - - options = this.filterOptions(options, 'findOne'); - data = this.filterData(data, 'findOne'); - options.withRelated = _.union(options.withRelated, options.include); - - var invite = this.forge(data); - return invite.fetch(options); - }, - add: function add(data, options) { var hash = crypto.createHash('sha256'), text = ''; - options = this.filterOptions(options, 'add'); - options.withRelated = _.union(options.withRelated, options.include); - data.expires = Date.now() + constants.ONE_WEEK_MS; data.status = 'pending'; @@ -60,6 +38,7 @@ Invite = ghostBookshelf.Model.extend({ hash.update(data.email.toLocaleLowerCase()); text += [data.expires, data.email, hash.digest('base64')].join('|'); data.token = new Buffer(text).toString('base64'); + return ghostBookshelf.Model.add.call(this, data, options); } }); diff --git a/core/server/models/plugins/include-count.js b/core/server/models/plugins/include-count.js index c0667c531d..1b741a1aba 100644 --- a/core/server/models/plugins/include-count.js +++ b/core/server/models/plugins/include-count.js @@ -51,8 +51,8 @@ module.exports = function (Bookshelf) { var tableName = _.result(this, 'tableName'); - if (options.include && options.include.indexOf('count.posts') > -1) { - // remove post_count from withRelated and include + if (options.withRelated && options.withRelated.indexOf('count.posts') > -1) { + // remove post_count from withRelated options.withRelated = _.pull([].concat(options.withRelated), 'count.posts'); // Call the query builder diff --git a/core/server/models/post.js b/core/server/models/post.js index c3ff6319bf..d808cb6fa7 100644 --- a/core/server/models/post.js +++ b/core/server/models/post.js @@ -350,10 +350,9 @@ Post = ghostBookshelf.Model.extend({ return attrs; }, - toJSON: function toJSON(options) { - options = options || {}; - - var attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options), + toJSON: function toJSON(unfilteredOptions) { + var options = Post.filterOptions(unfilteredOptions, 'toJSON'), + attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options), oldPostId = attrs.amp, commentId; @@ -520,8 +519,6 @@ Post = ghostBookshelf.Model.extend({ * **See:** [ghostBookshelf.Model.findOne](base.js.html#Find%20One) */ findOne: function findOne(data, options) { - options = options || {}; - data = _.defaults(data || {}, { status: 'published' }); @@ -530,8 +527,6 @@ Post = ghostBookshelf.Model.extend({ delete data.status; } - options.withRelated = _.union(options.withRelated, options.include); - return ghostBookshelf.Model.findOne.call(this, data, options); }, @@ -542,15 +537,15 @@ Post = ghostBookshelf.Model.extend({ * @extends ghostBookshelf.Model.edit to handle returning the full object and manage _updatedAttributes * **See:** [ghostBookshelf.Model.edit](base.js.html#edit) */ - edit: function edit(data, options) { - let opts = _.cloneDeep(options || {}); + edit: function edit(data, unfilteredOptions) { + let options = this.filterOptions(unfilteredOptions, 'edit', {extraAllowedProperties: ['id']}); const editPost = () => { - opts.forUpdate = true; + options.forUpdate = true; - return ghostBookshelf.Model.edit.call(this, data, opts) + return ghostBookshelf.Model.edit.call(this, data, options) .then((post) => { - return this.findOne({status: 'all', id: opts.id}, opts) + return this.findOne({status: 'all', id: options.id}, options) .then((found) => { if (found) { // Pass along the updated attributes for checking status changes @@ -561,9 +556,9 @@ Post = ghostBookshelf.Model.extend({ }); }; - if (!opts.transacting) { + if (!options.transacting) { return ghostBookshelf.transaction((transacting) => { - opts.transacting = transacting; + options.transacting = transacting; return editPost(); }); } @@ -576,19 +571,19 @@ Post = ghostBookshelf.Model.extend({ * @extends ghostBookshelf.Model.add to handle returning the full object * **See:** [ghostBookshelf.Model.add](base.js.html#add) */ - add: function add(data, options) { - let opts = _.cloneDeep(options || {}); + add: function add(data, unfilteredOptions) { + let options = this.filterOptions(unfilteredOptions, 'add', {extraAllowedProperties: ['id']}); const addPost = (() => { - return ghostBookshelf.Model.add.call(this, data, opts) + return ghostBookshelf.Model.add.call(this, data, options) .then((post) => { - return this.findOne({status: 'all', id: post.id}, opts); + return this.findOne({status: 'all', id: post.id}, options); }); }); - if (!opts.transacting) { + if (!options.transacting) { return ghostBookshelf.transaction((transacting) => { - opts.transacting = transacting; + options.transacting = transacting; return addPost(); }); @@ -597,16 +592,16 @@ Post = ghostBookshelf.Model.extend({ return addPost(); }, - destroy: function destroy(options) { - let opts = _.cloneDeep(options || {}); + destroy: function destroy(unfilteredOptions) { + let options = this.filterOptions(unfilteredOptions, 'destroy', {extraAllowedProperties: ['id']}); const destroyPost = () => { - return ghostBookshelf.Model.destroy.call(this, opts); + return ghostBookshelf.Model.destroy.call(this, options); }; - if (!opts.transacting) { + if (!options.transacting) { return ghostBookshelf.transaction((transacting) => { - opts.transacting = transacting; + options.transacting = transacting; return destroyPost(); }); } @@ -618,13 +613,10 @@ Post = ghostBookshelf.Model.extend({ * ### destroyByAuthor * @param {[type]} options has context and id. Context is the user doing the destroy, id is the user to destroy */ - destroyByAuthor: function destroyByAuthor(options) { - let opts = _.cloneDeep(options || {}); - - let postCollection = Posts.forge(), - authorId = opts.id; - - opts = this.filterOptions(opts, 'destroyByAuthor'); + destroyByAuthor: function destroyByAuthor(unfilteredOptions) { + let options = this.filterOptions(unfilteredOptions, 'destroyByAuthor', {extraAllowedProperties: ['id']}), + postCollection = Posts.forge(), + authorId = options.id; if (!authorId) { throw new common.errors.NotFoundError({ @@ -635,16 +627,16 @@ Post = ghostBookshelf.Model.extend({ const destroyPost = (() => { return postCollection .query('where', 'author_id', '=', authorId) - .fetch(opts) - .call('invokeThen', 'destroy', opts) + .fetch(options) + .call('invokeThen', 'destroy', options) .catch((err) => { throw new common.errors.GhostError({err: err}); }); }); - if (!opts.transacting) { + if (!options.transacting) { return ghostBookshelf.transaction((transacting) => { - opts.transacting = transacting; + options.transacting = transacting; return destroyPost(); }); } diff --git a/core/server/models/settings.js b/core/server/models/settings.js index 7aa6781cf0..76c8d21202 100644 --- a/core/server/models/settings.js +++ b/core/server/models/settings.js @@ -98,9 +98,9 @@ Settings = ghostBookshelf.Model.extend({ return Promise.resolve(ghostBookshelf.Model.findOne.call(this, data, options)); }, - edit: function (data, options) { - var self = this; - options = this.filterOptions(options, 'edit'); + edit: function (data, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'edit'), + self = this; if (!Array.isArray(data)) { data = [data]; @@ -146,10 +146,13 @@ Settings = ghostBookshelf.Model.extend({ }); }, - populateDefaults: function populateDefaults(options) { - var self = this; + populateDefaults: function populateDefaults(unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'populateDefaults'), + self = this; - options = _.merge({}, options || {}, internalContext); + if (!options.context) { + options.context = internalContext.context; + } return this .findAll(options) diff --git a/core/server/models/subscriber.js b/core/server/models/subscriber.js index 20c9b0e526..36821f35eb 100644 --- a/core/server/models/subscriber.js +++ b/core/server/models/subscriber.js @@ -75,11 +75,11 @@ Subscriber = ghostBookshelf.Model.extend({ }, // TODO: This is a copy paste of models/user.js! - getByEmail: function getByEmail(email, options) { - options = options || {}; + getByEmail: function getByEmail(email, unfilteredOptions) { + var options = ghostBookshelf.Model.filterOptions(unfilteredOptions, 'getByEmail'); options.require = true; - return Subscribers.forge(options).fetch(options).then(function then(subscribers) { + return Subscribers.forge().fetch(options).then(function then(subscribers) { var subscriberWithEmail = subscribers.find(function findSubscriber(subscriber) { return subscriber.get('email').toLowerCase() === email.toLowerCase(); }); diff --git a/core/server/models/tag.js b/core/server/models/tag.js index 29afb13dc4..e9d667541c 100644 --- a/core/server/models/tag.js +++ b/core/server/models/tag.js @@ -1,5 +1,4 @@ -var _ = require('lodash'), - ghostBookshelf = require('./base'), +var ghostBookshelf = require('./base'), common = require('../lib/common'), Tag, Tags; @@ -54,10 +53,9 @@ Tag = ghostBookshelf.Model.extend({ return this.belongsToMany('Post'); }, - toJSON: function toJSON(options) { - options = options || {}; - - var attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options); + toJSON: function toJSON(unfilteredOptions) { + var options = Tag.filterOptions(unfilteredOptions, 'toJSON'), + attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options); attrs.parent = attrs.parent || attrs.parent_id; delete attrs.parent_id; @@ -94,29 +92,11 @@ Tag = ghostBookshelf.Model.extend({ return options; }, - /** - * ### Find One - * @overrides ghostBookshelf.Model.findOne - */ - findOne: function findOne(data, options) { - options = options || {}; + destroy: function destroy(unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'destroy', {extraAllowedProperties: ['id']}); + options.withRelated = ['posts']; - options = this.filterOptions(options, 'findOne'); - data = this.filterData(data, 'findOne'); - - var tag = this.forge(data); - - // Add related objects - options.withRelated = _.union(options.withRelated, options.include); - - return tag.fetch(options); - }, - - destroy: function destroy(options) { - var id = options.id; - options = this.filterOptions(options, 'destroy'); - - return this.forge({id: id}).fetch({withRelated: ['posts']}).then(function destroyTagsAndPost(tag) { + return this.forge({id: options.id}).fetch(options).then(function destroyTagsAndPost(tag) { return tag.related('posts').detach().then(function destroyTags() { return tag.destroy(options); }); diff --git a/core/server/models/user.js b/core/server/models/user.js index b7e444d423..3a91ccbc3b 100644 --- a/core/server/models/user.js +++ b/core/server/models/user.js @@ -201,10 +201,9 @@ User = ghostBookshelf.Model.extend({ return validation.validateSchema(this.tableName, userData); }, - toJSON: function toJSON(options) { - options = options || {}; - - var attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options); + toJSON: function toJSON(unfilteredOptions) { + var options = User.filterOptions(unfilteredOptions, 'toJSON'), + attrs = ghostBookshelf.Model.prototype.toJSON.call(this, options); // remove password hash for security reasons delete attrs.password; @@ -341,11 +340,11 @@ User = ghostBookshelf.Model.extend({ permittedOptionsToReturn = permittedOptionsToReturn.concat(validOptions[methodName]); } - // CASE: The `include` parameter is allowed when using the public API, but not the `roles` value. + // CASE: The `withRelated` parameter is allowed when using the public API, but not the `roles` value. // Otherwise we expose too much information. if (options && options.context && options.context.public) { - if (options.include && options.include.indexOf('roles') !== -1) { - options.include.splice(options.include.indexOf('roles'), 1); + if (options.withRelated && options.withRelated.indexOf('roles') !== -1) { + options.withRelated.splice(options.withRelated.indexOf('roles'), 1); } } @@ -358,11 +357,14 @@ User = ghostBookshelf.Model.extend({ * We have to clone the data, because we remove values from this object. * This is not expected from outside! * + * @TODO: use base class + * * @extends ghostBookshelf.Model.findOne to include roles * **See:** [ghostBookshelf.Model.findOne](base.js.html#Find%20One) */ - findOne: function findOne(dataToClone, options) { - var query, + findOne: function findOne(dataToClone, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'findOne'), + query, status, data = _.cloneDeep(dataToClone), lookupRole = data.role; @@ -376,15 +378,12 @@ User = ghostBookshelf.Model.extend({ delete data.status; data = this.filterData(data); - options = this.filterOptions(options, 'findOne'); - options.withRelated = _.union(options.withRelated, options.include); // Support finding by role if (lookupRole) { options.withRelated = _.union(options.withRelated, ['roles']); - options.include = _.union(options.include, ['roles']); - query = this.forge(data); + query.query('join', 'roles_users', 'users.id', '=', 'roles_users.user_id'); query.query('join', 'roles', 'roles_users.role_id', '=', 'roles.id'); query.query('where', 'roles.name', '=', lookupRole); @@ -409,8 +408,9 @@ User = ghostBookshelf.Model.extend({ * @extends ghostBookshelf.Model.edit to handle returning the full object * **See:** [ghostBookshelf.Model.edit](base.js.html#edit) */ - edit: function edit(data, options) { - var self = this, + edit: function edit(data, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'edit'), + self = this, ops = []; if (data.roles && data.roles.length > 1) { @@ -419,9 +419,6 @@ User = ghostBookshelf.Model.extend({ ); } - options = options || {}; - options.withRelated = _.union(options.withRelated, options.include); - if (data.email) { ops.push(function checkForDuplicateEmail() { return self.getByEmail(data.email, options).then(function then(user) { @@ -476,19 +473,17 @@ User = ghostBookshelf.Model.extend({ * This is not expected from outside! * * @param {object} dataToClone - * @param {object} options + * @param {object} unfilteredOptions * @extends ghostBookshelf.Model.add to manage all aspects of user signup * **See:** [ghostBookshelf.Model.add](base.js.html#Add) */ - add: function add(dataToClone, options) { - var self = this, + add: function add(dataToClone, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'add'), + self = this, data = _.cloneDeep(dataToClone), userData = this.filterData(data), roles; - options = this.filterOptions(options, 'add'); - options.withRelated = _.union(options.withRelated, options.include); - // check for too many roles if (data.roles && data.roles.length > 1) { return Promise.reject(new common.errors.ValidationError({message: common.i18n.t('errors.models.user.onlyOneRolePerUserSupported')})); @@ -556,8 +551,9 @@ User = ghostBookshelf.Model.extend({ * Owner already has a slug -> force setting a new one by setting slug to null * @TODO: kill setup function? */ - setup: function setup(data, options) { - var self = this, + setup: function setup(data, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'setup'), + self = this, userData = this.filterData(data), passwordValidation = {}; @@ -567,9 +563,6 @@ User = ghostBookshelf.Model.extend({ return Promise.reject(new common.errors.ValidationError({message: passwordValidation.message})); } - options = this.filterOptions(options, 'setup'); - options.withRelated = _.union(options.withRelated, options.include); - userData.slug = null; return self.edit(userData, options); }, @@ -624,7 +617,7 @@ User = ghostBookshelf.Model.extend({ return this.findOne({ id: userModelOrId, status: 'all' - }, {include: ['roles']}).then(function then(foundUserModel) { + }, {withRelated: ['roles']}).then(function then(foundUserModel) { if (!foundUserModel) { throw new common.errors.NotFoundError({ message: common.i18n.t('errors.models.user.userNotFound') @@ -753,17 +746,20 @@ User = ghostBookshelf.Model.extend({ /** * Naive change password method * @param {Object} object - * @param {Object} options + * @param {Object} unfilteredOptions */ - changePassword: function changePassword(object, options) { - var self = this, + changePassword: function changePassword(object, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'changePassword'), + self = this, newPassword = object.newPassword, userId = object.user_id, oldPassword = object.oldPassword, isLoggedInUser = userId === options.context.user, user; - return self.forge({id: userId}).fetch({require: true}) + options.require = true; + + return self.forge({id: userId}).fetch(options) .then(function then(_user) { user = _user; @@ -779,13 +775,14 @@ User = ghostBookshelf.Model.extend({ }); }, - transferOwnership: function transferOwnership(object, options) { - var ownerRole, + transferOwnership: function transferOwnership(object, unfilteredOptions) { + var options = ghostBookshelf.Model.filterOptions(unfilteredOptions, 'transferOwnership'), + ownerRole, contextUser; return Promise.join( ghostBookshelf.model('Role').findOne({name: 'Owner'}), - User.findOne({id: options.context.user}, {include: ['roles']}) + User.findOne({id: options.context.user}, {withRelated: ['roles']}) ) .then(function then(results) { ownerRole = results[0]; @@ -798,7 +795,7 @@ User = ghostBookshelf.Model.extend({ } return Promise.join(ghostBookshelf.model('Role').findOne({name: 'Administrator'}), - User.findOne({id: object.id}, {include: ['roles']})); + User.findOne({id: object.id}, {withRelated: ['roles']})); }) .then(function then(results) { var adminRole = results[0], @@ -820,7 +817,7 @@ User = ghostBookshelf.Model.extend({ .fetch({withRelated: ['roles']}); }) .then(function then(users) { - options.include = ['roles']; + options.withRelated = ['roles']; return users.toJSON(options); }); }, @@ -828,15 +825,16 @@ User = ghostBookshelf.Model.extend({ // Get the user by email address, enforces case insensitivity rejects if the user is not found // When multi-user support is added, email addresses must be deduplicated with case insensitivity, so that // joe@bloggs.com and JOE@BLOGGS.COM cannot be created as two separate users. - getByEmail: function getByEmail(email, options) { - options = options || {}; + getByEmail: function getByEmail(email, unfilteredOptions) { + var options = ghostBookshelf.Model.filterOptions(unfilteredOptions, 'getByEmail'); + // We fetch all users and process them in JS as there is no easy way to make this query across all DBs // Although they all support `lower()`, sqlite can't case transform unicode characters // This is somewhat mute, as validator.isEmail() also doesn't support unicode, but this is much easier / more // likely to be fixed in the near future. options.require = true; - return Users.forge(options).fetch(options).then(function then(users) { + return Users.forge().fetch(options).then(function then(users) { var userWithEmail = users.find(function findUser(user) { return user.get('email').toLowerCase() === email.toLowerCase(); }); diff --git a/core/server/models/webhook.js b/core/server/models/webhook.js index bba757e3ee..4be58dd04f 100644 --- a/core/server/models/webhook.js +++ b/core/server/models/webhook.js @@ -25,21 +25,20 @@ Webhook = ghostBookshelf.Model.extend({ model.emitChange('deleted', options); } }, { - findAllByEvent: function findAllByEvent(event, options) { - var webhooksCollection = Webhooks.forge(); - - options = this.filterOptions(options, 'findAll'); + findAllByEvent: function findAllByEvent(event, unfilteredOptions) { + var options = this.filterOptions(unfilteredOptions, 'findAll'), + webhooksCollection = Webhooks.forge(); return webhooksCollection .query('where', 'event', '=', event) .fetch(options); }, - getByEventAndTarget: function getByEventAndTarget(event, targetUrl, options) { - options = options || {}; + getByEventAndTarget: function getByEventAndTarget(event, targetUrl, unfilteredOptions) { + var options = ghostBookshelf.Model.filterOptions(unfilteredOptions, 'getByEventAndTarget'); options.require = true; - return Webhooks.forge(options).fetch(options).then(function then(webhooks) { + return Webhooks.forge().fetch(options).then(function then(webhooks) { var webhookWithEventAndTarget = webhooks.find(function findWebhook(webhook) { return webhook.get('event').toLowerCase() === event.toLowerCase() && webhook.get('target_url').toLowerCase() === targetUrl.toLowerCase(); diff --git a/core/server/services/auth/auth-strategies.js b/core/server/services/auth/auth-strategies.js index 76fc0bedfb..b2a774f053 100644 --- a/core/server/services/auth/auth-strategies.js +++ b/core/server/services/auth/auth-strategies.js @@ -19,7 +19,7 @@ strategies = { return models.Client.findOne({slug: clientId}, {withRelated: ['trustedDomains']}) .then(function then(model) { if (model) { - var client = model.toJSON({include: ['trustedDomains']}); + var client = model.toJSON({withRelated: ['trustedDomains']}); if (client.status === 'enabled' && client.secret === clientSecret) { return done(null, client); } diff --git a/core/server/services/permissions/providers.js b/core/server/services/permissions/providers.js index e1df4ea842..3b05b7a2d6 100644 --- a/core/server/services/permissions/providers.js +++ b/core/server/services/permissions/providers.js @@ -5,7 +5,7 @@ var _ = require('lodash'), module.exports = { user: function (id) { - return models.User.findOne({id: id, status: 'all'}, {include: ['permissions', 'roles', 'roles.permissions']}) + return models.User.findOne({id: id, status: 'all'}, {withRelated: ['permissions', 'roles', 'roles.permissions']}) .then(function (foundUser) { // CASE: {context: {user: id}} where the id is not in our database if (!foundUser) { diff --git a/core/test/integration/api/api_authentication_spec.js b/core/test/integration/api/api_authentication_spec.js index dfadb26c9a..362622bff3 100644 --- a/core/test/integration/api/api_authentication_spec.js +++ b/core/test/integration/api/api_authentication_spec.js @@ -286,7 +286,7 @@ describe('Authentication API', function () { should.not.exist(_invite); return models.User.findOne({ email: invite.get('email') - }, _.merge({include: ['roles']}, context.internal)); + }, _.merge({withRelated: ['roles']}, context.internal)); }) .then(function (user) { user.toJSON().roles.length.should.eql(1); diff --git a/core/test/integration/api/api_invites_spec.js b/core/test/integration/api/api_invites_spec.js index e64b44d1ec..14b8466471 100644 --- a/core/test/integration/api/api_invites_spec.js +++ b/core/test/integration/api/api_invites_spec.js @@ -317,7 +317,7 @@ describe('Invites API', function () { role_id: testUtils.roles.ids.admin } ] - }, _.merge({}, {include: ['roles']}, testUtils.context.admin)).then(function (response) { + }, _.merge({}, {include: 'roles'}, testUtils.context.admin)).then(function (response) { checkAddResponse(response); response.invites[0].role_id.should.equal(testUtils.roles.ids.admin); done(); diff --git a/core/test/integration/api/api_users_spec.js b/core/test/integration/api/api_users_spec.js index cc8147661c..de0071b7f1 100644 --- a/core/test/integration/api/api_users_spec.js +++ b/core/test/integration/api/api_users_spec.js @@ -864,7 +864,7 @@ describe('Users API', function () { return models.Post.findAll(_.merge({}, { context: context.editor.context, filter: 'author_id:' + userIdFor.editor, - include: ['tags'] + withRelated: ['tags'] }, options)); }).then(function (posts) { posts.models.length.should.eql(3); @@ -877,7 +877,7 @@ describe('Users API', function () { return models.Post.findAll(_.merge({ context: context.author.context, filter: 'author_id:' + userIdFor.author, - include: ['tags'] + withRelated: ['tags'] }, options)); }).then(function (posts) { posts.models.length.should.eql(3); diff --git a/core/test/integration/data/importer/importers/data_spec.js b/core/test/integration/data/importer/importers/data_spec.js index 92d8f77bf0..425cdf29fe 100644 --- a/core/test/integration/data/importer/importers/data_spec.js +++ b/core/test/integration/data/importer/importers/data_spec.js @@ -534,7 +534,7 @@ describe('Import', function () { // Grab the data from tables // NOTE: we have to return sorted data, sqlite can insert the posts in a different order return Promise.all([ - models.Post.findPage({include: ['tags']}), + models.Post.findPage({withRelated: ['tags']}), models.Tag.findAll() ]); }).then(function (importedData) { diff --git a/core/test/integration/migration_spec.js b/core/test/integration/migration_spec.js index 6b14b18518..2e2f7e1097 100644 --- a/core/test/integration/migration_spec.js +++ b/core/test/integration/migration_spec.js @@ -170,16 +170,16 @@ describe('Database Migration (special functions)', function () { it('should populate all fixtures correctly', function () { var props = { - posts: Models.Post.findAll({include: ['tags']}), + posts: Models.Post.findAll({withRelated: ['tags']}), tags: Models.Tag.findAll(), users: Models.User.findAll({ filter: 'status:inactive', context: {internal: true}, - include: ['roles'] + withRelated: ['roles'] }), clients: Models.Client.findAll(), roles: Models.Role.findAll(), - permissions: Models.Permission.findAll({include: ['roles']}) + permissions: Models.Permission.findAll({withRelated: ['roles']}) }; return Promise.props(props).then(function (result) { diff --git a/core/test/integration/model/model_permissions_spec.js b/core/test/integration/model/model_permissions_spec.js index d053e608f5..5d0edcd080 100644 --- a/core/test/integration/model/model_permissions_spec.js +++ b/core/test/integration/model/model_permissions_spec.js @@ -127,7 +127,7 @@ describe('Permission Model', function () { // return testUser.permissions().attach(testPermission); // }); // }).then(function () { - // return Models.User.findOne({id: 1}, { include: ['permissions']}); + // return Models.User.findOne({id: 1}, { withRelated: ['permissions']}); // }).then(function (updatedUser) { // should.exist(updatedUser); diff --git a/core/test/integration/model/model_posts_spec.js b/core/test/integration/model/model_posts_spec.js index fe96b2dacb..0c7119ab5d 100644 --- a/core/test/integration/model/model_posts_spec.js +++ b/core/test/integration/model/model_posts_spec.js @@ -105,7 +105,7 @@ describe('Post Model', function () { }); it('can findAll, returning all related data', function (done) { - PostModel.findAll({include: ['author', 'fields', 'tags', 'created_by', 'updated_by', 'published_by']}) + PostModel.findAll({withRelated: ['author', 'fields', 'tags', 'created_by', 'updated_by', 'published_by']}) .then(function (results) { should.exist(results); results.length.should.be.above(0); @@ -123,7 +123,7 @@ describe('Post Model', function () { it('can findAll, use formats option', function (done) { var options = { formats: ['mobiledoc', 'plaintext'], - include: ['author', 'fields', 'tags', 'created_by', 'updated_by', 'published_by'] + withRelated: ['author', 'fields', 'tags', 'created_by', 'updated_by', 'published_by'] }; PostModel.findAll(options) @@ -165,7 +165,7 @@ describe('Post Model', function () { }); it('can findPage, returning all related data', function (done) { - PostModel.findPage({include: ['author', 'fields', 'tags', 'created_by', 'updated_by', 'published_by']}) + PostModel.findPage({withRelated: ['author', 'fields', 'tags', 'created_by', 'updated_by', 'published_by']}) .then(function (results) { should.exist(results); @@ -361,7 +361,7 @@ describe('Post Model', function () { it('can findOne, returning all related data', function (done) { var firstPost; - PostModel.findOne({}, {include: ['author', 'fields', 'tags', 'created_by', 'updated_by', 'published_by']}) + PostModel.findOne({}, {withRelated: ['author', 'fields', 'tags', 'created_by', 'updated_by', 'published_by']}) .then(function (result) { should.exist(result); firstPost = result.toJSON(); @@ -1417,7 +1417,7 @@ describe('Post Model', function () { var firstItemData = {id: testUtils.DataGenerator.Content.posts[0].id}; // Test that we have the post we expect, with exactly one tag - PostModel.findOne(firstItemData, {include: ['tags']}).then(function (results) { + PostModel.findOne(firstItemData, {withRelated: ['tags']}).then(function (results) { var post; should.exist(results); post = results.toJSON(); @@ -1456,7 +1456,7 @@ describe('Post Model', function () { var firstItemData = {id: testUtils.DataGenerator.Content.posts[3].id, status: 'draft'}; // Test that we have the post we expect, with exactly one tag - PostModel.findOne(firstItemData, {include: ['tags']}).then(function (results) { + PostModel.findOne(firstItemData, {withRelated: ['tags']}).then(function (results) { var post; should.exist(results); post = results.toJSON(); @@ -1493,7 +1493,7 @@ describe('Post Model', function () { var firstItemData = {id: testUtils.DataGenerator.Content.posts[5].id}; // Test that we have the post we expect, with exactly one tag - PostModel.findOne(firstItemData, {include: ['tags']}).then(function (results) { + PostModel.findOne(firstItemData, {withRelated: ['tags']}).then(function (results) { var page; should.exist(results); page = results.toJSON(); @@ -1531,7 +1531,7 @@ describe('Post Model', function () { var firstItemData = {id: testUtils.DataGenerator.Content.posts[6].id, status: 'draft'}; // Test that we have the post we expect, with exactly one tag - PostModel.findOne(firstItemData, {include: ['tags']}).then(function (results) { + PostModel.findOne(firstItemData, {withRelated: ['tags']}).then(function (results) { var page; should.exist(results); page = results.toJSON(); @@ -1724,7 +1724,7 @@ describe('Post Model', function () { tag2: TagModel.add(extraTags[1], context), tag3: TagModel.add(extraTags[2], context) }).then(function (result) { - postJSON = result.post.toJSON({include: ['tags']}); + postJSON = result.post.toJSON({withRelated: ['tags']}); tagJSON.push(result.tag1.toJSON()); tagJSON.push(result.tag2.toJSON()); tagJSON.push(result.tag3.toJSON()); @@ -1765,7 +1765,7 @@ describe('Post Model', function () { // Edit the post return PostModel.edit(newJSON, editOptions).then(function (updatedPost) { - updatedPost = updatedPost.toJSON({include: ['tags']}); + updatedPost = updatedPost.toJSON({withRelated: ['tags']}); updatedPost.tags.should.have.lengthOf(1); updatedPost.tags[0].name.should.eql(postJSON.tags[0].name); @@ -1793,7 +1793,7 @@ describe('Post Model', function () { return PostModel.edit(newJSON, editOptions); }) .then(function (updatedPost) { - updatedPost = updatedPost.toJSON({include: ['tags']}); + updatedPost = updatedPost.toJSON({withRelated: ['tags']}); updatedPost.tags.should.have.lengthOf(1); updatedPost.tags[0].should.have.properties({ @@ -1823,7 +1823,7 @@ describe('Post Model', function () { // Edit the post return PostModel.edit(newJSON, editOptions).then(function (updatedPost) { - updatedPost = updatedPost.toJSON({include: ['tags']}); + updatedPost = updatedPost.toJSON({withRelated: ['tags']}); updatedPost.tags.should.have.lengthOf(3); updatedPost.tags[0].should.have.properties({ @@ -1853,7 +1853,7 @@ describe('Post Model', function () { // Edit the post return PostModel.edit(newJSON, editOptions).then(function (updatedPost) { - updatedPost = updatedPost.toJSON({include: ['tags']}); + updatedPost = updatedPost.toJSON({withRelated: ['tags']}); updatedPost.tags.should.have.lengthOf(3); @@ -1873,7 +1873,7 @@ describe('Post Model', function () { // Edit the post return PostModel.edit(newJSON, editOptions).then(function (updatedPost) { - updatedPost = updatedPost.toJSON({include: ['tags']}); + updatedPost = updatedPost.toJSON({withRelated: ['tags']}); updatedPost.tags.should.have.lengthOf(1); }); diff --git a/core/test/integration/model/model_tags_spec.js b/core/test/integration/model/model_tags_spec.js index 3736e61032..06a6f5ca67 100644 --- a/core/test/integration/model/model_tags_spec.js +++ b/core/test/integration/model/model_tags_spec.js @@ -47,7 +47,7 @@ describe('Tag Model', function () { it('returns count.posts if include count.posts', function (done) { testUtils.fixtures.insertPostsAndTags().then(function () { - TagModel.findOne({slug: 'kitchen-sink'}, {include: 'count.posts'}).then(function (tag) { + TagModel.findOne({slug: 'kitchen-sink'}, {withRelated: ['count.posts']}).then(function (tag) { should.exist(tag); tag.toJSON().count.posts.should.equal(2); @@ -75,7 +75,7 @@ describe('Tag Model', function () { }); it('with include count.posts', function (done) { - TagModel.findPage({limit: 'all', include: 'count.posts'}).then(function (results) { + TagModel.findPage({limit: 'all', withRelated: ['count.posts']}).then(function (results) { results.meta.pagination.page.should.equal(1); results.meta.pagination.limit.should.equal('all'); results.meta.pagination.pages.should.equal(1); diff --git a/core/test/integration/model/model_users_spec.js b/core/test/integration/model/model_users_spec.js index 0a21d6b35f..63df6d9fdf 100644 --- a/core/test/integration/model/model_users_spec.js +++ b/core/test/integration/model/model_users_spec.js @@ -348,7 +348,7 @@ describe('User Model', function run() { RoleModel.findOne().then(function (role) { userData.roles = [role.toJSON()]; - return UserModel.add(userData, _.extend({}, context, {include: ['roles']})); + return UserModel.add(userData, _.extend({}, context, {withRelated: ['roles']})); }).then(function (createdUser) { should.exist(createdUser); createdUser.get('password').should.not.equal(userData.password, 'password was hashed'); @@ -371,7 +371,7 @@ describe('User Model', function run() { RoleModel.findOne().then(function (role) { userData.roles = [role.toJSON()]; - return UserModel.add(userData, _.extend({}, context, {include: ['roles']})); + return UserModel.add(userData, _.extend({}, context, {withRelated: ['roles']})); }).then(function () { done(new Error('User was created with an invalid email address')); }).catch(function () { diff --git a/core/test/unit/api/utils_spec.js b/core/test/unit/api/utils_spec.js index 51eb896755..18cac3a1da 100644 --- a/core/test/unit/api/utils_spec.js +++ b/core/test/unit/api/utils_spec.js @@ -298,14 +298,15 @@ describe('API Utils', function () { allowed = ['a', 'b', 'c'], options = {include: 'a,b'}, actualResult; + actualResult = apiUtils.convertOptions(allowed)(_.clone(options)); prepareIncludeStub.calledOnce.should.be.true(); prepareIncludeStub.calledWith(options.include, allowed).should.be.true(); - actualResult.should.have.hasOwnProperty('include'); - actualResult.include.should.be.an.Array(); - actualResult.include.should.eql(expectedResult); + actualResult.should.have.hasOwnProperty('withRelated'); + actualResult.withRelated.should.be.an.Array(); + actualResult.withRelated.should.eql(expectedResult); }); }); diff --git a/core/test/unit/services/permissions/providers_spec.js b/core/test/unit/services/permissions/providers_spec.js index 674d0340c4..f2d64b557c 100644 --- a/core/test/unit/services/permissions/providers_spec.js +++ b/core/test/unit/services/permissions/providers_spec.js @@ -48,8 +48,9 @@ describe('Permission Providers', function () { roles: fakeAdminRole, permissions: fakeAdminRolePermissions }; + // We use this inside toJSON. - fakeUser.include = ['roles', 'permissions', 'roles.permissions']; + fakeUser.withRelated = ['roles', 'permissions', 'roles.permissions']; return Promise.resolve(fakeUser); }); @@ -96,7 +97,7 @@ describe('Permission Providers', function () { roles: fakeAdminRole }; // We use this inside toJSON. - fakeUser.include = ['roles', 'permissions', 'roles.permissions']; + fakeUser.withRelated = ['roles', 'permissions', 'roles.permissions']; return Promise.resolve(fakeUser); }); @@ -144,7 +145,7 @@ describe('Permission Providers', function () { permissions: fakeAdminRolePermissions }; // We use this inside toJSON. - fakeUser.include = ['roles', 'permissions', 'roles.permissions']; + fakeUser.withRelated = ['roles', 'permissions', 'roles.permissions']; return Promise.resolve(fakeUser); });