2018-04-05 17:10:40 +03:00
|
|
|
'use strict';
|
|
|
|
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
// # Base Model
|
|
|
|
// This is the model from which all other Ghost models extend. The model is based on Bookshelf.Model, and provides
|
|
|
|
// several basic behaviours such as UUIDs, as well as a set of Data methods for accessing information from the database.
|
|
|
|
//
|
|
|
|
// The models are internal to Ghost, only the API and some internal functions such as migration and import/export
|
|
|
|
// accesses the models directly. All other parts of Ghost, including the blog frontend, admin UI, and apps are only
|
|
|
|
// allowed to access data via the API.
|
2018-04-05 17:10:40 +03:00
|
|
|
const _ = require('lodash'),
|
2017-04-19 16:53:23 +03:00
|
|
|
bookshelf = require('bookshelf'),
|
|
|
|
moment = require('moment'),
|
|
|
|
Promise = require('bluebird'),
|
Rewrite url service (#9550)
refs https://github.com/TryGhost/Team/issues/65
We are currently work on dynamic routing (aka channels).
An important piece of this feature is the url service, which always knows the url of a resource at any time.
Resources can belong to collections or taxonomies, which can be defined in a [routing yaml file](https://github.com/TryGhost/Ghost/issues/9528). We are currently shipping portions, which will at end form the full dynamic routing feature.
### Key Notes
- each routing type (collections, taxonomies, static pages) is registered in order - depending on the yaml routes file configuration
- static pages are an internal concept - they sit at the end of the subscriber queue
- we make use of a temporary [`Channels2`](https://github.com/TryGhost/Ghost/pull/9550/files#diff-9e7251409844521470c9829013cd1563) file, which simulates the current static routing in Ghost (this file will be modified, removed or whatever - this is one of the next steps)
- two way binding: you can ask for a resource url based on the resource id, you can ask for the resource based on the url
- in theory it's possible that multiple resources generate the same url: we don't handle this with collision (because this is error prone), we handle this with the order of serving content. if you ask the service for a resource, which lives behind e.g. /test/, you will get the resource which is served
- loose error handling -> log errors and handle instead of throw error and do nothing (we log the errors with a specific code, so we can react in case there is a bug)
- the url services fetches all resources on bootstrap. we only fetch and keep a reduced set of attributes (basically the main body of a resource)
- the bootstrap time will decrease a very little (depending on the amount of resources you have in your database)
- we still offer the option to disable url preloading (in your config `disableUrlPreload: true`) - this option will be removed as soon as the url service is connected. You can disable the service in case you encounter a problem
- **the url service is not yet connected, we will connect the service step by step. The first version should be released to pre-catch bugs. The next version will add 503 handling if the url service is not ready and it will consume urls for resources.**
----
- the url service generates urls based on resources (posts, pages, users, tags)
- the url service keeps track of resource changes
- the url service keeps track of resource removal/insert
- the architecture:
- each routing type is represented by a url generator
- a routing type is a collection, a taxonomiy or static pages
- a queue which ensures that urls are unique and can be owned by one url generator
- the hierarchy of registration defines that
- we query knex, because bookshelf is too slow
- removed old url service files + logic
- added temp channels alternative (Channels2) -> this file will look different soon, it's for now the temporary connector to the url service. Also the name of the file is not optimal, but that is not really important right now.
2018-04-17 12:29:04 +03:00
|
|
|
gql = require('ghost-gql'),
|
2017-04-19 16:53:23 +03:00
|
|
|
ObjectId = require('bson-objectid'),
|
Rewrite url service (#9550)
refs https://github.com/TryGhost/Team/issues/65
We are currently work on dynamic routing (aka channels).
An important piece of this feature is the url service, which always knows the url of a resource at any time.
Resources can belong to collections or taxonomies, which can be defined in a [routing yaml file](https://github.com/TryGhost/Ghost/issues/9528). We are currently shipping portions, which will at end form the full dynamic routing feature.
### Key Notes
- each routing type (collections, taxonomies, static pages) is registered in order - depending on the yaml routes file configuration
- static pages are an internal concept - they sit at the end of the subscriber queue
- we make use of a temporary [`Channels2`](https://github.com/TryGhost/Ghost/pull/9550/files#diff-9e7251409844521470c9829013cd1563) file, which simulates the current static routing in Ghost (this file will be modified, removed or whatever - this is one of the next steps)
- two way binding: you can ask for a resource url based on the resource id, you can ask for the resource based on the url
- in theory it's possible that multiple resources generate the same url: we don't handle this with collision (because this is error prone), we handle this with the order of serving content. if you ask the service for a resource, which lives behind e.g. /test/, you will get the resource which is served
- loose error handling -> log errors and handle instead of throw error and do nothing (we log the errors with a specific code, so we can react in case there is a bug)
- the url services fetches all resources on bootstrap. we only fetch and keep a reduced set of attributes (basically the main body of a resource)
- the bootstrap time will decrease a very little (depending on the amount of resources you have in your database)
- we still offer the option to disable url preloading (in your config `disableUrlPreload: true`) - this option will be removed as soon as the url service is connected. You can disable the service in case you encounter a problem
- **the url service is not yet connected, we will connect the service step by step. The first version should be released to pre-catch bugs. The next version will add 503 handling if the url service is not ready and it will consume urls for resources.**
----
- the url service generates urls based on resources (posts, pages, users, tags)
- the url service keeps track of resource changes
- the url service keeps track of resource removal/insert
- the architecture:
- each routing type is represented by a url generator
- a routing type is a collection, a taxonomiy or static pages
- a queue which ensures that urls are unique and can be owned by one url generator
- the hierarchy of registration defines that
- we query knex, because bookshelf is too slow
- removed old url service files + logic
- added temp channels alternative (Channels2) -> this file will look different soon, it's for now the temporary connector to the url service. Also the name of the file is not optimal, but that is not really important right now.
2018-04-17 12:29:04 +03:00
|
|
|
debug = require('ghost-ignition').debug('models:base'),
|
2017-04-19 16:53:23 +03:00
|
|
|
config = require('../../config'),
|
|
|
|
db = require('../../data/db'),
|
2017-12-12 00:47:46 +03:00
|
|
|
common = require('../../lib/common'),
|
2017-12-14 15:26:48 +03:00
|
|
|
security = require('../../lib/security'),
|
2017-04-19 16:53:23 +03:00
|
|
|
filters = require('../../filters'),
|
|
|
|
schema = require('../../data/schema'),
|
2017-12-11 21:14:05 +03:00
|
|
|
urlService = require('../../services/url'),
|
2015-06-14 18:58:49 +03:00
|
|
|
validation = require('../../data/validation'),
|
2018-04-05 17:10:40 +03:00
|
|
|
plugins = require('../plugins');
|
2016-05-19 14:49:22 +03:00
|
|
|
|
2018-04-05 17:10:40 +03:00
|
|
|
let ghostBookshelf,
|
2015-11-20 15:04:49 +03:00
|
|
|
proto;
|
2013-06-25 15:43:15 +04:00
|
|
|
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
// ### ghostBookshelf
|
|
|
|
// Initializes a new Bookshelf instance called ghostBookshelf, for reference elsewhere in Ghost.
|
2016-02-12 14:56:27 +03:00
|
|
|
ghostBookshelf = bookshelf(db.knex);
|
2014-08-14 01:58:12 +04:00
|
|
|
|
2015-06-17 16:55:39 +03:00
|
|
|
// Load the Bookshelf registry plugin, which helps us avoid circular dependencies
|
2014-07-13 15:17:18 +04:00
|
|
|
ghostBookshelf.plugin('registry');
|
2013-06-25 15:43:15 +04:00
|
|
|
|
2018-04-06 19:19:45 +03:00
|
|
|
// Add committed/rollback events.
|
|
|
|
ghostBookshelf.plugin(plugins.transactionEvents);
|
|
|
|
|
2015-11-11 20:52:44 +03:00
|
|
|
// Load the Ghost filter plugin, which handles applying a 'filter' to findPage requests
|
|
|
|
ghostBookshelf.plugin(plugins.filter);
|
|
|
|
|
2015-11-03 14:38:29 +03:00
|
|
|
// Load the Ghost include count plugin, which allows for the inclusion of cross-table counts
|
2015-11-11 21:24:24 +03:00
|
|
|
ghostBookshelf.plugin(plugins.includeCount);
|
2015-11-03 14:38:29 +03:00
|
|
|
|
2015-06-17 16:55:39 +03:00
|
|
|
// Load the Ghost pagination plugin, which gives us the `fetchPage` method on Models
|
2015-11-11 21:24:24 +03:00
|
|
|
ghostBookshelf.plugin(plugins.pagination);
|
2015-06-17 16:55:39 +03:00
|
|
|
|
2017-04-19 16:53:23 +03:00
|
|
|
// Update collision plugin
|
|
|
|
ghostBookshelf.plugin(plugins.collision);
|
|
|
|
|
2017-11-21 16:28:05 +03:00
|
|
|
// Manages nested updates (relationships)
|
|
|
|
ghostBookshelf.plugin('bookshelf-relations', {
|
2018-01-28 15:13:11 +03:00
|
|
|
allowedOptions: ['context', 'importing'],
|
2017-11-21 16:28:05 +03:00
|
|
|
unsetRelations: true,
|
|
|
|
hooks: {
|
|
|
|
belongsToMany: {
|
|
|
|
after: function (existing, targets, options) {
|
2018-03-27 17:16:15 +03:00
|
|
|
// reorder tags/authors
|
|
|
|
var queryOptions = {
|
|
|
|
query: {
|
|
|
|
where: {}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-11-21 16:28:05 +03:00
|
|
|
return Promise.each(targets.models, function (target, index) {
|
2018-03-27 17:16:15 +03:00
|
|
|
queryOptions.query.where[existing.relatedData.otherKey] = target.id;
|
|
|
|
|
2017-11-21 16:28:05 +03:00
|
|
|
return existing.updatePivot({
|
|
|
|
sort_order: index
|
2018-03-27 17:16:15 +03:00
|
|
|
}, _.extend({}, options, queryOptions));
|
2017-11-21 16:28:05 +03:00
|
|
|
});
|
|
|
|
},
|
|
|
|
beforeRelationCreation: function onCreatingRelation(model, data) {
|
|
|
|
data.id = ObjectId.generate();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2015-11-20 15:04:49 +03:00
|
|
|
// Cache an instance of the base model prototype
|
|
|
|
proto = ghostBookshelf.Model.prototype;
|
|
|
|
|
2015-06-17 16:55:39 +03:00
|
|
|
// ## ghostBookshelf.Model
|
2013-06-25 15:43:15 +04:00
|
|
|
// The Base Model which other Ghost objects will inherit from,
|
|
|
|
// including some convenience functions as static properties on the model.
|
2013-09-23 02:20:08 +04:00
|
|
|
ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
2015-06-17 16:55:39 +03:00
|
|
|
// Bookshelf `hasTimestamps` - handles created_at and updated_at properties
|
2013-09-14 23:01:46 +04:00
|
|
|
hasTimestamps: true,
|
|
|
|
|
2018-03-19 18:27:06 +03:00
|
|
|
// https://github.com/bookshelf/bookshelf/commit/a55db61feb8ad5911adb4f8c3b3d2a97a45bd6db
|
|
|
|
parsedIdAttribute: function () {
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2015-06-17 16:55:39 +03:00
|
|
|
// Ghost option handling - get permitted attributes from server/data/schema.js, where the DB schema is defined
|
2015-06-14 18:58:49 +03:00
|
|
|
permittedAttributes: function permittedAttributes() {
|
2014-02-19 17:57:26 +04:00
|
|
|
return _.keys(schema.tables[this.tableName]);
|
|
|
|
},
|
|
|
|
|
2016-07-18 23:21:47 +03:00
|
|
|
// When loading an instance, subclasses can specify default to fetch
|
|
|
|
defaultColumnsToFetch: function defaultColumnsToFetch() {
|
|
|
|
return [];
|
|
|
|
},
|
|
|
|
|
2018-04-06 20:10:59 +03:00
|
|
|
/**
|
|
|
|
* @NOTE
|
|
|
|
* We have to remember the `_previousAttributes` attributes, because when destroying resources
|
|
|
|
* We listen on the `onDestroyed` event and Bookshelf resets these properties right after the event.
|
|
|
|
* If the query runs in a txn, `_previousAttributes` will be empty.
|
|
|
|
*/
|
2018-04-06 19:19:45 +03:00
|
|
|
emitChange: function (model, event, options) {
|
2018-04-06 20:10:59 +03:00
|
|
|
const previousAttributes = model._previousAttributes;
|
|
|
|
|
2018-04-06 19:19:45 +03:00
|
|
|
if (!options.transacting) {
|
|
|
|
return common.events.emit(event, model, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!model.ghostEvents) {
|
|
|
|
model.ghostEvents = [];
|
|
|
|
|
|
|
|
if (options.importing) {
|
|
|
|
options.transacting.setMaxListeners(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
options.transacting.once('committed', (committed) => {
|
|
|
|
if (!committed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_.each(this.ghostEvents, (ghostEvent) => {
|
2018-04-06 20:10:59 +03:00
|
|
|
model._previousAttributes = previousAttributes;
|
2018-04-06 19:19:45 +03:00
|
|
|
common.events.emit(ghostEvent, model, _.omit(options, 'transacting'));
|
|
|
|
});
|
2018-04-06 20:10:59 +03:00
|
|
|
|
|
|
|
delete model.ghostEvents;
|
2018-04-06 19:19:45 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
model.ghostEvents.push(event);
|
|
|
|
},
|
|
|
|
|
2015-06-17 16:55:39 +03:00
|
|
|
// Bookshelf `initialize` - declare a constructor-like method for model creation
|
2015-06-14 18:58:49 +03:00
|
|
|
initialize: function initialize() {
|
2018-02-14 19:32:11 +03:00
|
|
|
var self = this;
|
2014-04-27 20:58:34 +04:00
|
|
|
|
2018-03-19 18:27:06 +03:00
|
|
|
// NOTE: triggered before `creating`/`updating`
|
|
|
|
this.on('saving', function onSaving(newObj, attrs, options) {
|
|
|
|
if (options.method === 'insert') {
|
|
|
|
// id = 0 is still a valid value for external usage
|
|
|
|
if (_.isUndefined(newObj.id) || _.isNull(newObj.id)) {
|
|
|
|
newObj.setId();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2017-04-19 16:53:23 +03:00
|
|
|
[
|
|
|
|
'fetching',
|
|
|
|
'fetching:collection',
|
|
|
|
'fetched',
|
2018-03-27 17:16:15 +03:00
|
|
|
'fetched:collection',
|
2017-04-19 16:53:23 +03:00
|
|
|
'creating',
|
|
|
|
'created',
|
|
|
|
'updating',
|
|
|
|
'updated',
|
|
|
|
'destroying',
|
|
|
|
'destroyed',
|
2018-03-19 18:27:06 +03:00
|
|
|
'saving',
|
2017-04-19 16:53:23 +03:00
|
|
|
'saved'
|
|
|
|
].forEach(function (eventName) {
|
|
|
|
var functionName = 'on' + eventName[0].toUpperCase() + eventName.slice(1);
|
|
|
|
|
|
|
|
if (functionName.indexOf(':') !== -1) {
|
|
|
|
functionName = functionName.slice(0, functionName.indexOf(':'))
|
|
|
|
+ functionName[functionName.indexOf(':') + 1].toUpperCase()
|
|
|
|
+ functionName.slice(functionName.indexOf(':') + 2);
|
|
|
|
functionName = functionName.replace(':', '');
|
|
|
|
}
|
2016-10-14 15:37:01 +03:00
|
|
|
|
2017-04-19 16:53:23 +03:00
|
|
|
if (!self[functionName]) {
|
|
|
|
return;
|
|
|
|
}
|
2016-10-14 15:37:01 +03:00
|
|
|
|
2018-04-05 17:10:40 +03:00
|
|
|
self.on(eventName, self[functionName]);
|
2017-04-19 16:53:23 +03:00
|
|
|
});
|
2016-10-14 15:37:01 +03:00
|
|
|
|
2017-11-21 16:28:05 +03:00
|
|
|
// NOTE: Please keep here. If we don't initialize the parent, bookshelf-relations won't work.
|
|
|
|
proto.initialize.call(this);
|
2014-02-19 17:57:26 +04:00
|
|
|
},
|
|
|
|
|
2018-02-16 02:49:15 +03:00
|
|
|
/**
|
|
|
|
* Do not call `toJSON`. This can remove properties e.g. password.
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
onValidate: function onValidate(model, columns, options) {
|
2018-03-05 11:10:27 +03:00
|
|
|
this.setEmptyValuesToNull();
|
|
|
|
|
2018-02-16 02:49:15 +03:00
|
|
|
return validation.validateSchema(this.tableName, this, options);
|
2013-09-14 23:01:46 +04:00
|
|
|
},
|
|
|
|
|
2017-11-14 02:18:59 +03:00
|
|
|
/**
|
|
|
|
* http://knexjs.org/#Builder-forUpdate
|
|
|
|
* https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
|
|
|
|
*
|
|
|
|
* Lock target collection/model for further update operations.
|
|
|
|
* This avoids collisions and possible content override cases.
|
|
|
|
*/
|
|
|
|
onFetching: function onFetching(model, columns, options) {
|
|
|
|
if (options.forUpdate && options.transacting) {
|
|
|
|
options.query.forUpdate();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
onFetchingCollection: function onFetchingCollection(model, columns, options) {
|
|
|
|
if (options.forUpdate && options.transacting) {
|
|
|
|
options.query.forUpdate();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2018-03-19 18:27:06 +03:00
|
|
|
onSaving: function onSaving(newObj) {
|
|
|
|
// Remove any properties which don't belong on the model
|
|
|
|
this.attributes = this.pick(this.permittedAttributes());
|
|
|
|
// Store the previous attributes so we can tell what was updated later
|
|
|
|
this._updatedAttributes = newObj.previousAttributes();
|
|
|
|
},
|
|
|
|
|
2017-09-28 15:59:42 +03:00
|
|
|
/**
|
|
|
|
* Adding resources implies setting these properties on the server side
|
|
|
|
* - set `created_by` based on the context
|
|
|
|
* - set `updated_by` based on the context
|
|
|
|
* - the bookshelf `timestamps` plugin sets `created_at` and `updated_at`
|
|
|
|
* - if plugin is disabled (e.g. import) we have a fallback condition
|
|
|
|
*
|
|
|
|
* Exceptions: internal context or importing
|
|
|
|
*/
|
2016-10-14 15:37:01 +03:00
|
|
|
onCreating: function onCreating(newObj, attr, options) {
|
2017-09-28 15:59:42 +03:00
|
|
|
if (schema.tables[this.tableName].hasOwnProperty('created_by')) {
|
|
|
|
if (!options.importing || (options.importing && !this.get('created_by'))) {
|
|
|
|
this.set('created_by', this.contextUser(options));
|
|
|
|
}
|
2013-09-14 23:01:46 +04:00
|
|
|
}
|
2017-05-23 19:18:13 +03:00
|
|
|
|
2018-03-19 18:27:06 +03:00
|
|
|
if (schema.tables[this.tableName].hasOwnProperty('updated_by')) {
|
|
|
|
if (!options.importing) {
|
|
|
|
this.set('updated_by', this.contextUser(options));
|
|
|
|
}
|
2018-01-28 15:13:11 +03:00
|
|
|
}
|
2017-09-28 15:59:42 +03:00
|
|
|
|
2018-03-19 18:27:06 +03:00
|
|
|
if (schema.tables[this.tableName].hasOwnProperty('created_at')) {
|
|
|
|
if (!newObj.get('created_at')) {
|
|
|
|
newObj.set('created_at', new Date());
|
|
|
|
}
|
2017-05-23 19:18:13 +03:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:27:06 +03:00
|
|
|
if (schema.tables[this.tableName].hasOwnProperty('updated_at')) {
|
|
|
|
if (!newObj.get('updated_at')) {
|
|
|
|
newObj.set('updated_at', new Date());
|
|
|
|
}
|
2017-05-23 19:18:13 +03:00
|
|
|
}
|
2013-09-14 23:01:46 +04:00
|
|
|
|
2018-03-19 18:27:06 +03:00
|
|
|
return Promise.resolve(this.onValidate(newObj, attr, options));
|
2017-09-28 15:59:42 +03:00
|
|
|
},
|
2014-04-22 05:04:30 +04:00
|
|
|
|
2017-09-28 15:59:42 +03:00
|
|
|
/**
|
|
|
|
* Changing resources implies setting these properties on the server side
|
|
|
|
* - set `updated_by` based on the context
|
|
|
|
* - ensure `created_at` never changes
|
|
|
|
* - ensure `created_by` never changes
|
|
|
|
* - the bookshelf `timestamps` plugin sets `updated_at` automatically
|
|
|
|
*
|
|
|
|
* Exceptions:
|
|
|
|
* - importing data
|
|
|
|
* - internal context
|
|
|
|
* - if no context
|
|
|
|
*/
|
|
|
|
onUpdating: function onUpdating(newObj, attr, options) {
|
2018-03-19 18:27:06 +03:00
|
|
|
if (schema.tables[this.tableName].hasOwnProperty('updated_by')) {
|
|
|
|
if (!options.importing) {
|
|
|
|
this.set('updated_by', this.contextUser(options));
|
|
|
|
}
|
2018-01-28 15:13:11 +03:00
|
|
|
}
|
2017-09-28 15:59:42 +03:00
|
|
|
|
|
|
|
if (options && options.context && !options.internal && !options.importing) {
|
2018-03-19 18:27:06 +03:00
|
|
|
if (schema.tables[this.tableName].hasOwnProperty('created_at')) {
|
|
|
|
if (newObj.hasDateChanged('created_at', {beforeWrite: true})) {
|
|
|
|
newObj.set('created_at', this.previous('created_at'));
|
|
|
|
}
|
2017-09-28 15:59:42 +03:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:27:06 +03:00
|
|
|
if (schema.tables[this.tableName].hasOwnProperty('created_by')) {
|
|
|
|
if (newObj.hasChanged('created_by')) {
|
|
|
|
newObj.set('created_by', this.previous('created_by'));
|
|
|
|
}
|
2017-09-28 15:59:42 +03:00
|
|
|
}
|
|
|
|
}
|
2018-03-19 18:27:06 +03:00
|
|
|
|
2018-04-05 17:25:49 +03:00
|
|
|
// CASE: do not allow setting only the `updated_at` field, exception: importing
|
|
|
|
if (schema.tables[this.tableName].hasOwnProperty('updated_at') && !options.importing) {
|
|
|
|
if (newObj.hasChanged() && Object.keys(newObj.changed).length === 1 && newObj.changed.updated_at) {
|
|
|
|
newObj.set('updated_at', newObj.previous('updated_at'));
|
|
|
|
}
|
2018-03-26 16:12:02 +03:00
|
|
|
}
|
|
|
|
|
2018-03-19 18:27:06 +03:00
|
|
|
return Promise.resolve(this.onValidate(newObj, attr, options));
|
2013-09-14 23:01:46 +04:00
|
|
|
},
|
|
|
|
|
2016-06-03 11:06:18 +03:00
|
|
|
/**
|
|
|
|
* before we insert dates into the database, we have to normalize
|
|
|
|
* date format is now in each db the same
|
|
|
|
*/
|
|
|
|
fixDatesWhenSave: function fixDates(attrs) {
|
2014-07-05 18:57:56 +04:00
|
|
|
var self = this;
|
|
|
|
|
2015-06-14 18:58:49 +03:00
|
|
|
_.each(attrs, function each(value, key) {
|
2014-07-24 12:17:10 +04:00
|
|
|
if (value !== null
|
2017-04-19 16:53:23 +03:00
|
|
|
&& schema.tables[self.tableName].hasOwnProperty(key)
|
|
|
|
&& schema.tables[self.tableName][key].type === 'dateTime') {
|
2016-06-03 11:06:18 +03:00
|
|
|
attrs[key] = moment(value).format('YYYY-MM-DD HH:mm:ss');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return attrs;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2017-09-26 19:16:46 +03:00
|
|
|
* all supported databases (sqlite, mysql) return different values
|
2016-06-03 11:06:18 +03:00
|
|
|
*
|
|
|
|
* sqlite:
|
Rewrite url service (#9550)
refs https://github.com/TryGhost/Team/issues/65
We are currently work on dynamic routing (aka channels).
An important piece of this feature is the url service, which always knows the url of a resource at any time.
Resources can belong to collections or taxonomies, which can be defined in a [routing yaml file](https://github.com/TryGhost/Ghost/issues/9528). We are currently shipping portions, which will at end form the full dynamic routing feature.
### Key Notes
- each routing type (collections, taxonomies, static pages) is registered in order - depending on the yaml routes file configuration
- static pages are an internal concept - they sit at the end of the subscriber queue
- we make use of a temporary [`Channels2`](https://github.com/TryGhost/Ghost/pull/9550/files#diff-9e7251409844521470c9829013cd1563) file, which simulates the current static routing in Ghost (this file will be modified, removed or whatever - this is one of the next steps)
- two way binding: you can ask for a resource url based on the resource id, you can ask for the resource based on the url
- in theory it's possible that multiple resources generate the same url: we don't handle this with collision (because this is error prone), we handle this with the order of serving content. if you ask the service for a resource, which lives behind e.g. /test/, you will get the resource which is served
- loose error handling -> log errors and handle instead of throw error and do nothing (we log the errors with a specific code, so we can react in case there is a bug)
- the url services fetches all resources on bootstrap. we only fetch and keep a reduced set of attributes (basically the main body of a resource)
- the bootstrap time will decrease a very little (depending on the amount of resources you have in your database)
- we still offer the option to disable url preloading (in your config `disableUrlPreload: true`) - this option will be removed as soon as the url service is connected. You can disable the service in case you encounter a problem
- **the url service is not yet connected, we will connect the service step by step. The first version should be released to pre-catch bugs. The next version will add 503 handling if the url service is not ready and it will consume urls for resources.**
----
- the url service generates urls based on resources (posts, pages, users, tags)
- the url service keeps track of resource changes
- the url service keeps track of resource removal/insert
- the architecture:
- each routing type is represented by a url generator
- a routing type is a collection, a taxonomiy or static pages
- a queue which ensures that urls are unique and can be owned by one url generator
- the hierarchy of registration defines that
- we query knex, because bookshelf is too slow
- removed old url service files + logic
- added temp channels alternative (Channels2) -> this file will look different soon, it's for now the temporary connector to the url service. Also the name of the file is not optimal, but that is not really important right now.
2018-04-17 12:29:04 +03:00
|
|
|
* - knex returns a UTC String (2018-04-12 20:50:35)
|
2016-06-03 11:06:18 +03:00
|
|
|
* mysql:
|
|
|
|
* - knex wraps the UTC value into a local JS Date
|
|
|
|
*/
|
|
|
|
fixDatesWhenFetch: function fixDates(attrs) {
|
2017-10-04 11:56:09 +03:00
|
|
|
var self = this, dateMoment;
|
2016-06-03 11:06:18 +03:00
|
|
|
|
|
|
|
_.each(attrs, function each(value, key) {
|
|
|
|
if (value !== null
|
|
|
|
&& schema.tables[self.tableName].hasOwnProperty(key)
|
|
|
|
&& schema.tables[self.tableName][key].type === 'dateTime') {
|
2017-10-04 11:56:09 +03:00
|
|
|
dateMoment = moment(value);
|
|
|
|
|
|
|
|
// CASE: You are somehow able to store e.g. 0000-00-00 00:00:00
|
|
|
|
// Protect the code base and return the current date time.
|
|
|
|
if (dateMoment.isValid()) {
|
|
|
|
attrs[key] = dateMoment.startOf('seconds').toDate();
|
|
|
|
} else {
|
|
|
|
attrs[key] = moment().startOf('seconds').toDate();
|
|
|
|
}
|
2013-07-08 15:39:11 +04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return attrs;
|
|
|
|
},
|
|
|
|
|
2014-04-25 11:55:53 +04:00
|
|
|
// Convert integers to real booleans
|
2015-06-14 18:58:49 +03:00
|
|
|
fixBools: function fixBools(attrs) {
|
2014-04-25 11:55:53 +04:00
|
|
|
var self = this;
|
2015-06-14 18:58:49 +03:00
|
|
|
_.each(attrs, function each(value, key) {
|
2014-07-24 12:17:10 +04:00
|
|
|
if (schema.tables[self.tableName].hasOwnProperty(key)
|
2017-04-19 16:53:23 +03:00
|
|
|
&& schema.tables[self.tableName][key].type === 'bool') {
|
2014-04-25 11:55:53 +04:00
|
|
|
attrs[key] = value ? true : false;
|
2014-01-05 02:16:29 +04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return attrs;
|
|
|
|
},
|
|
|
|
|
2018-03-05 11:10:27 +03:00
|
|
|
// Sets given values to `null`
|
|
|
|
setEmptyValuesToNull: function setEmptyValuesToNull() {
|
|
|
|
var self = this,
|
|
|
|
attr;
|
|
|
|
|
|
|
|
if (!this.emptyStringProperties) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
attr = this.emptyStringProperties();
|
|
|
|
|
|
|
|
_.each(attr, function (value) {
|
|
|
|
if (self.get(value) === '') {
|
|
|
|
self.set(value, null);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2014-07-15 15:03:12 +04:00
|
|
|
// Get the user from the options object
|
2015-06-14 18:58:49 +03:00
|
|
|
contextUser: function contextUser(options) {
|
2016-11-09 18:01:07 +03:00
|
|
|
options = options || {};
|
|
|
|
options.context = options.context || {};
|
|
|
|
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
if (options.context.user || ghostBookshelf.Model.isExternalUser(options.context.user)) {
|
2014-07-15 15:03:12 +04:00
|
|
|
return options.context.user;
|
2016-11-09 18:01:07 +03:00
|
|
|
} else if (options.context.internal) {
|
|
|
|
return ghostBookshelf.Model.internalUser;
|
|
|
|
} else if (this.get('id')) {
|
|
|
|
return this.get('id');
|
|
|
|
} else if (options.context.external) {
|
|
|
|
return ghostBookshelf.Model.externalUser;
|
2014-07-15 15:03:12 +04:00
|
|
|
} else {
|
2017-12-12 00:47:46 +03:00
|
|
|
throw new common.errors.NotFoundError({
|
|
|
|
message: common.i18n.t('errors.models.base.index.missingContext'),
|
2016-10-06 15:27:35 +03:00
|
|
|
level: 'critical'
|
|
|
|
});
|
2014-07-15 15:03:12 +04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2014-04-25 11:55:53 +04:00
|
|
|
// format date before writing to DB, bools work
|
2015-06-14 18:58:49 +03:00
|
|
|
format: function format(attrs) {
|
2016-06-03 11:06:18 +03:00
|
|
|
return this.fixDatesWhenSave(attrs);
|
2014-04-25 11:55:53 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
// format data and bool when fetching from DB
|
2015-06-14 18:58:49 +03:00
|
|
|
parse: function parse(attrs) {
|
2016-06-03 11:06:18 +03:00
|
|
|
return this.fixBools(this.fixDatesWhenFetch(attrs));
|
2013-07-08 15:39:11 +04:00
|
|
|
},
|
|
|
|
|
2018-02-14 19:32:11 +03:00
|
|
|
/**
|
|
|
|
* `shallow` - won't return relations
|
|
|
|
* `omitPivot` - won't return pivot fields
|
|
|
|
*
|
|
|
|
* `toJSON` calls `serialize`.
|
|
|
|
*
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
* @param unfilteredOptions
|
2018-02-14 19:32:11 +03:00
|
|
|
* @returns {*}
|
|
|
|
*/
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
toJSON: function toJSON(unfilteredOptions) {
|
Rewrite url service (#9550)
refs https://github.com/TryGhost/Team/issues/65
We are currently work on dynamic routing (aka channels).
An important piece of this feature is the url service, which always knows the url of a resource at any time.
Resources can belong to collections or taxonomies, which can be defined in a [routing yaml file](https://github.com/TryGhost/Ghost/issues/9528). We are currently shipping portions, which will at end form the full dynamic routing feature.
### Key Notes
- each routing type (collections, taxonomies, static pages) is registered in order - depending on the yaml routes file configuration
- static pages are an internal concept - they sit at the end of the subscriber queue
- we make use of a temporary [`Channels2`](https://github.com/TryGhost/Ghost/pull/9550/files#diff-9e7251409844521470c9829013cd1563) file, which simulates the current static routing in Ghost (this file will be modified, removed or whatever - this is one of the next steps)
- two way binding: you can ask for a resource url based on the resource id, you can ask for the resource based on the url
- in theory it's possible that multiple resources generate the same url: we don't handle this with collision (because this is error prone), we handle this with the order of serving content. if you ask the service for a resource, which lives behind e.g. /test/, you will get the resource which is served
- loose error handling -> log errors and handle instead of throw error and do nothing (we log the errors with a specific code, so we can react in case there is a bug)
- the url services fetches all resources on bootstrap. we only fetch and keep a reduced set of attributes (basically the main body of a resource)
- the bootstrap time will decrease a very little (depending on the amount of resources you have in your database)
- we still offer the option to disable url preloading (in your config `disableUrlPreload: true`) - this option will be removed as soon as the url service is connected. You can disable the service in case you encounter a problem
- **the url service is not yet connected, we will connect the service step by step. The first version should be released to pre-catch bugs. The next version will add 503 handling if the url service is not ready and it will consume urls for resources.**
----
- the url service generates urls based on resources (posts, pages, users, tags)
- the url service keeps track of resource changes
- the url service keeps track of resource removal/insert
- the architecture:
- each routing type is represented by a url generator
- a routing type is a collection, a taxonomiy or static pages
- a queue which ensures that urls are unique and can be owned by one url generator
- the hierarchy of registration defines that
- we query knex, because bookshelf is too slow
- removed old url service files + logic
- added temp channels alternative (Channels2) -> this file will look different soon, it's for now the temporary connector to the url service. Also the name of the file is not optimal, but that is not really important right now.
2018-04-17 12:29:04 +03:00
|
|
|
const options = ghostBookshelf.Model.filterOptions(unfilteredOptions, 'toJSON');
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
options.omitPivot = true;
|
2013-07-08 15:39:11 +04:00
|
|
|
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
return proto.toJSON.call(this, options);
|
2013-09-14 23:01:46 +04:00
|
|
|
},
|
|
|
|
|
2014-04-22 05:04:30 +04:00
|
|
|
// Get attributes that have been updated (values before a .save() call)
|
2015-06-14 18:58:49 +03:00
|
|
|
updatedAttributes: function updatedAttributes() {
|
2014-04-22 05:04:30 +04:00
|
|
|
return this._updatedAttributes || {};
|
|
|
|
},
|
|
|
|
|
|
|
|
// Get a specific updated attribute value
|
2015-06-14 18:58:49 +03:00
|
|
|
updated: function updated(attr) {
|
2014-04-22 05:04:30 +04:00
|
|
|
return this.updatedAttributes()[attr];
|
2016-05-19 14:49:22 +03:00
|
|
|
},
|
|
|
|
|
2017-04-06 19:49:59 +03:00
|
|
|
/**
|
|
|
|
* There is difference between `updated` and `previous`:
|
|
|
|
* Depending on the hook (before or after writing into the db), both fields have a different meaning.
|
|
|
|
* e.g. onSaving -> before db write (has to use previous)
|
|
|
|
* onUpdated -> after db write (has to use updated)
|
|
|
|
*
|
|
|
|
* hasDateChanged('attr', {beforeWrite: true})
|
|
|
|
*/
|
|
|
|
hasDateChanged: function (attr, options) {
|
|
|
|
options = options || {};
|
|
|
|
return moment(this.get(attr)).diff(moment(options.beforeWrite ? this.previous(attr) : this.updated(attr))) !== 0;
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* we auto generate a GUID for each resource
|
|
|
|
* no auto increment
|
|
|
|
*/
|
|
|
|
setId: function setId() {
|
|
|
|
this.set('id', ObjectId.generate());
|
2013-07-08 15:39:11 +04:00
|
|
|
}
|
2013-06-25 15:43:15 +04:00
|
|
|
}, {
|
Refactor API arguments
closes #2610, refs #2697
- cleanup API index.js, and add docs
- all API methods take consistent arguments: object & options
- browse, read, destroy take options, edit and add take object and options
- the context is passed as part of options, meaning no more .call
everywhere
- destroy expects an object, rather than an id all the way down to the model layer
- route params such as :id, :slug, and :key are passed as an option & used
to perform reads, updates and deletes where possible - settings / themes
may need work here still
- HTTP posts api can find a post by slug
- Add API utils for checkData
2014-05-08 16:41:19 +04:00
|
|
|
// ## Data Utility Functions
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
|
2016-11-09 18:01:07 +03:00
|
|
|
/**
|
|
|
|
* please use these static definitions when comparing id's
|
2017-07-31 12:37:37 +03:00
|
|
|
* we keep type Number, because we have too many check's where we rely on Number
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
* context.user ? true : false (if context.user is 0 as number, this condition is false)
|
2016-11-09 18:01:07 +03:00
|
|
|
*/
|
|
|
|
internalUser: 1,
|
|
|
|
externalUser: 0,
|
|
|
|
|
|
|
|
isInternalUser: function isInternalUser(id) {
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
return id === ghostBookshelf.Model.internalUser || id === ghostBookshelf.Model.internalUser.toString();
|
2016-11-09 18:01:07 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
isExternalUser: function isExternalUser(id) {
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
return id === ghostBookshelf.Model.externalUser || id === ghostBookshelf.Model.externalUser.toString();
|
2016-11-09 18:01:07 +03:00
|
|
|
},
|
|
|
|
|
2014-05-06 05:45:08 +04:00
|
|
|
/**
|
|
|
|
* Returns an array of keys permitted in every method's `options` hash.
|
|
|
|
* Can be overridden and added to by a model's `permittedOptions` method.
|
2016-06-03 11:06:18 +03:00
|
|
|
*
|
|
|
|
* importing: is used when import a JSON file or when migrating the database
|
|
|
|
*
|
2015-10-10 19:07:10 +03:00
|
|
|
* @return {Object} Keys allowed in the `options` hash of every model's method.
|
2014-05-06 05:45:08 +04:00
|
|
|
*/
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
permittedOptions: function permittedOptions(methodName) {
|
|
|
|
if (methodName === 'toJSON') {
|
|
|
|
return ['shallow', 'withRelated', 'context', 'columns'];
|
|
|
|
}
|
|
|
|
|
2014-05-06 05:45:08 +04:00
|
|
|
// terms to whitelist for all methods.
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
return ['context', 'withRelated', 'transacting', 'importing', 'forUpdate'];
|
2014-05-06 05:45:08 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Filters potentially unsafe model attributes, so you can pass them to Bookshelf / Knex.
|
2017-04-19 11:59:09 +03:00
|
|
|
* This filter should be called before each insert/update operation.
|
|
|
|
*
|
2014-05-06 05:45:08 +04:00
|
|
|
* @param {Object} data Has keys representing the model's attributes/fields in the database.
|
|
|
|
* @return {Object} The filtered results of the passed in data, containing only what's allowed in the schema.
|
|
|
|
*/
|
2015-06-14 18:58:49 +03:00
|
|
|
filterData: function filterData(data) {
|
2014-05-06 05:45:08 +04:00
|
|
|
var permittedAttributes = this.prototype.permittedAttributes(),
|
2017-04-19 11:59:09 +03:00
|
|
|
filteredData = _.pick(data, permittedAttributes),
|
|
|
|
sanitizedData = this.sanitizeData(filteredData);
|
|
|
|
|
|
|
|
return sanitizedData;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* `sanitizeData` ensures that client data is in the correct format for further operations.
|
|
|
|
*
|
|
|
|
* Dates:
|
2017-07-20 13:24:23 +03:00
|
|
|
* - client dates are sent as ISO 8601 format (moment(..).format())
|
2017-04-19 11:59:09 +03:00
|
|
|
* - server dates are in JS Date format
|
|
|
|
* >> when bookshelf fetches data from the database, all dates are in JS Dates
|
|
|
|
* >> see `parse`
|
|
|
|
* - Bookshelf updates the model with the new client data via the `set` function
|
|
|
|
* - Bookshelf uses a simple `isEqual` function from lodash to detect real changes
|
|
|
|
* - .previous(attr) and .get(attr) returns false obviously
|
|
|
|
* - internally we use our `hasDateChanged` if we have to compare previous/updated dates
|
|
|
|
* - but Bookshelf is not in our control for this case
|
|
|
|
*
|
|
|
|
* @IMPORTANT
|
2017-12-14 19:04:06 +03:00
|
|
|
* Before the new client data get's inserted again, the dates get's re-transformed into
|
2017-04-19 11:59:09 +03:00
|
|
|
* proper strings, see `format`.
|
2018-04-05 17:11:47 +03:00
|
|
|
*
|
|
|
|
* @IMPORTANT
|
|
|
|
* Sanitize relations.
|
2017-04-19 11:59:09 +03:00
|
|
|
*/
|
|
|
|
sanitizeData: function sanitizeData(data) {
|
2017-12-14 19:04:06 +03:00
|
|
|
var tableName = _.result(this.prototype, 'tableName'), date;
|
2017-04-19 11:59:09 +03:00
|
|
|
|
2018-04-05 17:11:47 +03:00
|
|
|
_.each(data, (value, property) => {
|
2017-04-19 11:59:09 +03:00
|
|
|
if (value !== null
|
2018-04-05 17:11:47 +03:00
|
|
|
&& schema.tables[tableName].hasOwnProperty(property)
|
|
|
|
&& schema.tables[tableName][property].type === 'dateTime'
|
2017-04-19 11:59:09 +03:00
|
|
|
&& typeof value === 'string'
|
|
|
|
) {
|
2017-12-14 19:04:06 +03:00
|
|
|
date = new Date(value);
|
2017-10-04 11:56:09 +03:00
|
|
|
|
|
|
|
// CASE: client sends `0000-00-00 00:00:00`
|
2017-12-14 19:04:06 +03:00
|
|
|
if (isNaN(date)) {
|
2017-12-12 00:47:46 +03:00
|
|
|
throw new common.errors.ValidationError({
|
2018-04-05 17:11:47 +03:00
|
|
|
message: common.i18n.t('errors.models.base.invalidDate', {key: property}),
|
2017-12-14 19:04:06 +03:00
|
|
|
code: 'DATE_INVALID'
|
2017-10-04 11:56:09 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-04-05 17:11:47 +03:00
|
|
|
data[property] = moment(value).toDate();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.prototype.relationships && this.prototype.relationships.indexOf(property) !== -1) {
|
|
|
|
_.each(data[property], (relation, indexInArr) => {
|
|
|
|
_.each(relation, (value, relationProperty) => {
|
|
|
|
if (value !== null
|
|
|
|
&& schema.tables[this.prototype.relationshipBelongsTo[property]].hasOwnProperty(relationProperty)
|
|
|
|
&& schema.tables[this.prototype.relationshipBelongsTo[property]][relationProperty].type === 'dateTime'
|
|
|
|
&& typeof value === 'string'
|
|
|
|
) {
|
|
|
|
date = new Date(value);
|
|
|
|
|
|
|
|
// CASE: client sends `0000-00-00 00:00:00`
|
|
|
|
if (isNaN(date)) {
|
|
|
|
throw new common.errors.ValidationError({
|
|
|
|
message: common.i18n.t('errors.models.base.invalidDate', {key: relationProperty}),
|
|
|
|
code: 'DATE_INVALID'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
data[property][indexInArr][relationProperty] = moment(value).toDate();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2017-04-19 11:59:09 +03:00
|
|
|
}
|
|
|
|
});
|
2014-05-06 05:45:08 +04:00
|
|
|
|
2017-04-19 11:59:09 +03:00
|
|
|
return data;
|
2014-05-06 05:45:08 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Filters potentially unsafe `options` in a model method's arguments, so you can pass them to Bookshelf / Knex.
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
* @param {Object} unfilteredOptions Represents options to filter in order to be passed to the Bookshelf query.
|
2014-05-06 05:45:08 +04:00
|
|
|
* @param {String} methodName The name of the method to check valid options for.
|
|
|
|
* @return {Object} The filtered results of `options`.
|
2017-04-19 16:53:23 +03:00
|
|
|
*/
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
filterOptions: function filterOptions(unfilteredOptions, methodName, filterConfig) {
|
|
|
|
unfilteredOptions = unfilteredOptions || {};
|
|
|
|
filterConfig = filterConfig || {};
|
|
|
|
|
|
|
|
if (unfilteredOptions.hasOwnProperty('include')) {
|
|
|
|
throw new common.errors.IncorrectUsageError({
|
|
|
|
message: 'The model layer expects using `withRelated`.'
|
|
|
|
});
|
|
|
|
}
|
2014-05-06 05:45:08 +04:00
|
|
|
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
var options = _.cloneDeep(unfilteredOptions),
|
|
|
|
extraAllowedProperties = filterConfig.extraAllowedProperties || [],
|
|
|
|
permittedOptions;
|
|
|
|
|
|
|
|
permittedOptions = this.permittedOptions(methodName, options);
|
|
|
|
permittedOptions = _.union(permittedOptions, extraAllowedProperties);
|
|
|
|
options = _.pick(options, permittedOptions);
|
|
|
|
|
2018-04-15 13:12:20 +03:00
|
|
|
if (this.defaultRelations) {
|
|
|
|
options = this.defaultRelations(methodName, options);
|
|
|
|
}
|
|
|
|
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
return options;
|
2014-05-06 05:45:08 +04:00
|
|
|
},
|
|
|
|
|
2015-05-01 00:14:19 +03:00
|
|
|
// ## Model Data Functions
|
Refactor API arguments
closes #2610, refs #2697
- cleanup API index.js, and add docs
- all API methods take consistent arguments: object & options
- browse, read, destroy take options, edit and add take object and options
- the context is passed as part of options, meaning no more .call
everywhere
- destroy expects an object, rather than an id all the way down to the model layer
- route params such as :id, :slug, and :key are passed as an option & used
to perform reads, updates and deletes where possible - settings / themes
may need work here still
- HTTP posts api can find a post by slug
- Add API utils for checkData
2014-05-08 16:41:19 +04:00
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
/**
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* ### Find All
|
2016-04-14 18:54:49 +03:00
|
|
|
* Fetches all the data for a particular model
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
* @param {Object} unfilteredOptions (optional)
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* @return {Promise(ghostBookshelf.Collection)} Collection of all Models
|
2013-06-25 15:43:15 +04:00
|
|
|
*/
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
findAll: function findAll(unfilteredOptions) {
|
|
|
|
var options = this.filterOptions(unfilteredOptions, 'findAll'),
|
|
|
|
itemCollection = this.forge();
|
2016-04-14 18:54:49 +03:00
|
|
|
|
|
|
|
// transforms fictive keywords like 'all' (status:all) into correct allowed values
|
|
|
|
if (this.processOptions) {
|
|
|
|
this.processOptions(options);
|
|
|
|
}
|
|
|
|
|
|
|
|
itemCollection.applyDefaultAndCustomFilters(options);
|
|
|
|
|
|
|
|
return itemCollection.fetchAll(options).then(function then(result) {
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
if (options.withRelated) {
|
2015-06-14 18:58:49 +03:00
|
|
|
_.each(result.models, function each(item) {
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
item.withRelated = options.withRelated;
|
2014-04-27 20:58:34 +04:00
|
|
|
});
|
|
|
|
}
|
2016-10-12 18:18:57 +03:00
|
|
|
|
2014-04-27 20:58:34 +04:00
|
|
|
return result;
|
|
|
|
});
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
|
|
|
|
2015-06-17 16:55:39 +03:00
|
|
|
/**
|
|
|
|
* ### Find Page
|
|
|
|
* Find results by page - returns an object containing the
|
|
|
|
* information about the request (page, limit), along with the
|
|
|
|
* info needed for pagination (pages, total).
|
|
|
|
*
|
2018-04-15 13:12:20 +03:00
|
|
|
* @TODO: This model function does return JSON O_O.
|
|
|
|
*
|
2015-06-17 16:55:39 +03:00
|
|
|
* **response:**
|
|
|
|
*
|
|
|
|
* {
|
|
|
|
* posts: [
|
|
|
|
* {...}, ...
|
|
|
|
* ],
|
|
|
|
* page: __,
|
|
|
|
* limit: __,
|
|
|
|
* pages: __,
|
|
|
|
* total: __
|
|
|
|
* }
|
|
|
|
*
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
* @param {Object} unfilteredOptions
|
2015-06-17 16:55:39 +03:00
|
|
|
*/
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
findPage: function findPage(unfilteredOptions) {
|
|
|
|
var options = this.filterOptions(unfilteredOptions, 'findPage'),
|
2018-01-26 02:35:39 +03:00
|
|
|
itemCollection = this.forge(),
|
2017-04-19 16:53:23 +03:00
|
|
|
tableName = _.result(this.prototype, 'tableName'),
|
2016-07-18 23:21:47 +03:00
|
|
|
requestedColumns = options.columns;
|
2015-06-17 16:55:39 +03:00
|
|
|
|
2015-11-12 17:21:04 +03:00
|
|
|
// Set this to true or pass ?debug=true as an API option to get output
|
2016-10-11 15:53:52 +03:00
|
|
|
itemCollection.debug = options.debug && config.get('env') !== 'production';
|
2015-11-12 17:21:04 +03:00
|
|
|
|
2015-11-11 22:31:52 +03:00
|
|
|
// 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.
|
2015-11-11 20:52:44 +03:00
|
|
|
this.processOptions(options);
|
2015-07-28 03:27:54 +03:00
|
|
|
|
2015-11-11 20:52:44 +03:00
|
|
|
// Add Filter behaviour
|
2016-04-14 18:54:49 +03:00
|
|
|
itemCollection.applyDefaultAndCustomFilters(options);
|
2015-06-17 16:55:39 +03:00
|
|
|
|
2015-11-11 22:31:52 +03:00
|
|
|
// Ensure only valid fields/columns are added to query
|
2016-07-18 23:21:47 +03:00
|
|
|
// and append default columns to fetch
|
2015-11-11 22:31:52 +03:00
|
|
|
if (options.columns) {
|
|
|
|
options.columns = _.intersection(options.columns, this.prototype.permittedAttributes());
|
2016-07-18 23:21:47 +03:00
|
|
|
options.columns = _.union(options.columns, this.prototype.defaultColumnsToFetch());
|
2015-11-11 22:31:52 +03:00
|
|
|
}
|
|
|
|
|
2015-10-22 15:49:15 +03:00
|
|
|
if (options.order) {
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
options.order = this.parseOrderOption(options.order, options.withRelated);
|
|
|
|
} else if (this.orderDefaultRaw) {
|
|
|
|
options.orderRaw = this.orderDefaultRaw();
|
2015-10-22 15:49:15 +03:00
|
|
|
} else {
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
options.order = this.orderDefaultOptions();
|
2015-10-22 15:49:15 +03:00
|
|
|
}
|
2016-07-15 13:04:10 +03:00
|
|
|
|
2015-10-24 23:39:47 +03:00
|
|
|
return itemCollection.fetchPage(options).then(function formatResponse(response) {
|
2017-04-19 16:53:23 +03:00
|
|
|
var data = {},
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
models;
|
2016-07-18 23:21:47 +03:00
|
|
|
|
|
|
|
options.columns = requestedColumns;
|
|
|
|
models = response.collection.toJSON(options);
|
2016-03-29 20:36:04 +03:00
|
|
|
|
|
|
|
// re-add any computed properties that were stripped out before the call to fetchPage
|
2016-07-18 23:21:47 +03:00
|
|
|
// pick only requested before returning JSON
|
|
|
|
data[tableName] = _.map(models, function transform(model) {
|
|
|
|
return options.columns ? _.pick(model, options.columns) : model;
|
|
|
|
});
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
|
2015-10-24 23:39:47 +03:00
|
|
|
data.meta = {pagination: response.pagination};
|
|
|
|
return data;
|
2015-11-03 14:38:29 +03:00
|
|
|
});
|
2015-06-17 16:55:39 +03:00
|
|
|
},
|
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
/**
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* ### Find One
|
|
|
|
* Naive find one where data determines what to match on
|
|
|
|
* @param {Object} data
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
* @param {Object} unfilteredOptions (optional)
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* @return {Promise(ghostBookshelf.Model)} Single Model
|
2013-06-25 15:43:15 +04:00
|
|
|
*/
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
findOne: function findOne(data, unfilteredOptions) {
|
|
|
|
var options = this.filterOptions(unfilteredOptions, 'findOne');
|
2014-05-06 05:45:08 +04:00
|
|
|
data = this.filterData(data);
|
2018-02-14 19:32:11 +03:00
|
|
|
return this.forge(data).fetch(options);
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* ### Edit
|
2013-06-25 15:43:15 +04:00
|
|
|
* Naive edit
|
2017-04-19 16:53:23 +03:00
|
|
|
*
|
|
|
|
* We always forward the `method` option to Bookshelf, see http://bookshelfjs.org/#Model-instance-save.
|
|
|
|
* Based on the `method` option Bookshelf and Ghost can determine if a query is an insert or an update.
|
|
|
|
*
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* @param {Object} data
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
* @param {Object} unfilteredOptions (optional)
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* @return {Promise(ghostBookshelf.Model)} Edited Model
|
2013-06-25 15:43:15 +04:00
|
|
|
*/
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
edit: function edit(data, unfilteredOptions) {
|
|
|
|
var options = this.filterOptions(unfilteredOptions, 'edit', {extraAllowedProperties: ['id']}),
|
|
|
|
id = options.id,
|
2016-06-03 11:06:18 +03:00
|
|
|
model = this.forge({id: id});
|
|
|
|
|
2014-05-06 05:45:08 +04:00
|
|
|
data = this.filterData(data);
|
Refactor API arguments
closes #2610, refs #2697
- cleanup API index.js, and add docs
- all API methods take consistent arguments: object & options
- browse, read, destroy take options, edit and add take object and options
- the context is passed as part of options, meaning no more .call
everywhere
- destroy expects an object, rather than an id all the way down to the model layer
- route params such as :id, :slug, and :key are passed as an option & used
to perform reads, updates and deletes where possible - settings / themes
may need work here still
- HTTP posts api can find a post by slug
- Add API utils for checkData
2014-05-08 16:41:19 +04:00
|
|
|
|
2016-06-03 11:06:18 +03:00
|
|
|
// We allow you to disable timestamps when run migration, so that the posts `updated_at` value is the same
|
|
|
|
if (options.importing) {
|
|
|
|
model.hasTimestamps = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return model.fetch(options).then(function then(object) {
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
if (object) {
|
2017-04-19 16:53:23 +03:00
|
|
|
return object.save(data, _.merge({method: 'update'}, options));
|
2014-04-16 14:09:03 +04:00
|
|
|
}
|
2013-06-25 15:43:15 +04:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* ### Add
|
|
|
|
* Naive add
|
|
|
|
* @param {Object} data
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
* @param {Object} unfilteredOptions (optional)
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* @return {Promise(ghostBookshelf.Model)} Newly Added Model
|
2013-06-25 15:43:15 +04:00
|
|
|
*/
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
add: function add(data, unfilteredOptions) {
|
|
|
|
var options = this.filterOptions(unfilteredOptions, 'add'),
|
|
|
|
model;
|
|
|
|
|
2014-05-06 05:45:08 +04:00
|
|
|
data = this.filterData(data);
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
model = this.forge(data);
|
2016-06-03 11:06:18 +03:00
|
|
|
|
Refactor API arguments
closes #2610, refs #2697
- cleanup API index.js, and add docs
- all API methods take consistent arguments: object & options
- browse, read, destroy take options, edit and add take object and options
- the context is passed as part of options, meaning no more .call
everywhere
- destroy expects an object, rather than an id all the way down to the model layer
- route params such as :id, :slug, and :key are passed as an option & used
to perform reads, updates and deletes where possible - settings / themes
may need work here still
- HTTP posts api can find a post by slug
- Add API utils for checkData
2014-05-08 16:41:19 +04:00
|
|
|
// 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
|
2013-12-26 07:48:16 +04:00
|
|
|
if (options.importing) {
|
2014-09-19 20:17:58 +04:00
|
|
|
model.hasTimestamps = false;
|
2013-12-26 07:48:16 +04:00
|
|
|
}
|
✨ replace auto increment id's by object id (#7495)
* 🛠 bookshelf tarball, bson-objectid
* 🎨 schema changes
- change increment type to string
- add a default fallback for string length 191 (to avoid adding this logic to every single column which uses an ID)
- remove uuid, because ID now represents a global resource identifier
- keep uuid for post, because we are using this as preview id
- keep uuid for clients for now - we are using this param for Ghost-Auth
* ✨ base model: generate ObjectId on creating event
- each new resource get's a auto generate ObjectId
- this logic won't work for attached models, this commit comes later
* 🎨 centralised attach method
When attaching models there are two things important two know
1. To be able to attach an ObjectId, we need to register the `onCreating` event the fetched model!This is caused by the Bookshelf design in general. On this target model we are attaching the new model.
2. We need to manually fetch the target model, because Bookshelf has a weird behaviour (which is known as a bug, see see https://github.com/tgriesser/bookshelf/issues/629). The most important property when attaching a model is `parentFk`, which is the foreign key. This can be null when fetching the model with the option `withRelated`. To ensure quality and consistency, the custom attach wrapper always fetches the target model manual. By fetching the target model (again) is a little performance decrease, but it also has advantages: we can register the event, and directly unregister the event again. So very clean code.
Important: please only use the custom attach wrapper in the future.
* 🎨 token model had overriden the onCreating function because of the created_at field
- we need to ensure that the base onCreating hook get's triggered for ALL models
- if not, they don't get an ObjectId assigned
- in this case: be smart and check if the target model has a created_at field
* 🎨 we don't have a uuid field anymore, remove the usages
- no default uuid creation in models
- i am pretty sure we have some more definitions in our tests (for example in the export json files), but that is too much work to delete them all
* 🎨 do not parse ID to Number
- we had various occurances of parsing all ID's to numbers
- we don't need this behaviour anymore
- ID is string
- i will adapt the ID validation in the next commit
* 🎨 change ID regex for validation
- we only allow: ID as ObjectId, ID as 1 and ID as me
- we need to keep ID 1, because our whole software relies on ID 1 (permissions etc)
* 🎨 owner fixture
- roles: [4] does not work anymore
- 4 means -> static id 4
- this worked in an auto increment system (not even in a system with distributed writes)
- with ObjectId we generate each ID automatically (for static and dynamic resources)
- it is possible to define all id's for static resources still, but that means we need to know which ID is already used and for consistency we have to define ObjectId's for these static resources
- so no static id's anymore, except of: id 1 for owner and id 0 for external usage (because this is required from our permission system)
- NOTE: please read through the comment in the user model
* 🎨 tests: DataGenerator and test utils
First of all: we need to ensure using ObjectId's in the tests. When don't, we can't ensure that ObjectId's work properly.
This commit brings lot's of dynamic into all the static defined id's.
In one of the next commits, i will adapt all the tests.
* 🚨 remove counter in Notification API
- no need to add a counter
- we simply generate ObjectId's (they are auto incremental as well)
- our id validator does only allow ObjectId as id,1 and me
* 🎨 extend contextUser in Base Model
- remove isNumber check, because id's are no longer numbers, except of id 0/1
- use existing isExternalUser
- support id 0/1 as string or number
* ✨ Ghost Owner has id 1
- ensure we define this id in the fixtures.json
- doesn't matter if number or string
* 🎨 functional tests adaptions
- use dynamic id's
* 🎨 fix unit tests
* 🎨 integration tests adaptions
* 🎨 change importer utils
- all our export examples (test/fixtures/exports) contain id's as numbers
- fact: but we ignore them anyway when inserting into the database, see https://github.com/TryGhost/Ghost/blob/master/core/server/data/import/utils.js#L249
- in https://github.com/TryGhost/Ghost/pull/7495/commits/0e6ed957cd54dc02a25cf6fb1ab7d7e723295e2c#diff-70f514a06347c048648be464819503c4L67 i removed parsing id's to integers
- i realised that this ^ check just existed, because the userIdToMap was an object key and object keys are always strings!
- i think this logic is a little bit complicated, but i don't want to refactor this now
- this commit ensures when trying to find the user, the id comparison works again
- i've added more documentation to understand this logic ;)
- plus i renamed an attribute to improve readability
* 🎨 Data-Generator: add more defaults to createUser
- if i use the function DataGenerator.forKnex.createUser i would like to get a full set of defaults
* 🎨 test utils: change/extend function set for functional tests
- functional tests work a bit different
- they boot Ghost and seed the database
- some functional tests have mis-used the test setup
- the test setup needs two sections: integration/unit and functional tests
- any functional test is allowed to either add more data or change data in the existing Ghost db
- but what it should not do is: add test fixtures like roles or users from our DataGenerator and cross fingers it will work
- this commit adds a clean method for functional tests to add extra users
* 🎨 functional tests adaptions
- use last commit to insert users for functional tests clean
- tidy up usage of testUtils.setup or testUtils.doAuth
* 🐛 test utils: reset database before init
- ensure we don't have any left data from other tests in the database when starting ghost
* 🐛 fix test (unrelated to this PR)
- fixes a random failure
- return statement was missing
* 🎨 make changes for invites
2016-11-17 12:09:11 +03:00
|
|
|
|
|
|
|
// Bookshelf determines whether an operation is an update or an insert based on the id
|
|
|
|
// Ghost auto-generates Object id's, so we need to tell Bookshelf here that we are inserting data
|
|
|
|
options.method = 'insert';
|
2014-09-19 20:17:58 +04:00
|
|
|
return model.save(null, options);
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* ### Destroy
|
2013-06-25 15:43:15 +04:00
|
|
|
* Naive destroy
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
* @param {Object} unfilteredOptions (optional)
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* @return {Promise(ghostBookshelf.Model)} Empty Model
|
2013-06-25 15:43:15 +04:00
|
|
|
*/
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
destroy: function destroy(unfilteredOptions) {
|
|
|
|
var options = this.filterOptions(unfilteredOptions, 'destroy', {extraAllowedProperties: ['id']}),
|
|
|
|
id = options.id;
|
2015-03-24 23:23:23 +03:00
|
|
|
|
|
|
|
// Fetch the object before destroying it, so that the changed data is available to events
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
return this.forge({id: id})
|
|
|
|
.fetch(options)
|
|
|
|
.then(function then(obj) {
|
|
|
|
return obj.destroy(options);
|
|
|
|
});
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
|
|
|
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
/**
|
2017-04-19 16:53:23 +03:00
|
|
|
* ### Generate Slug
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
* Create a string to act as the permalink for an object.
|
|
|
|
* @param {ghostBookshelf.Model} Model Model type to generate a slug for
|
|
|
|
* @param {String} base The string for which to generate a slug, usually a title or name
|
|
|
|
* @param {Object} options Options to pass to findOne
|
|
|
|
* @return {Promise(String)} Resolves to a unique slug string
|
2017-04-19 16:53:23 +03:00
|
|
|
*/
|
2015-06-14 18:58:49 +03:00
|
|
|
generateSlug: function generateSlug(Model, base, options) {
|
2013-12-20 17:36:00 +04:00
|
|
|
var slug,
|
|
|
|
slugTryCount = 1,
|
2014-03-23 22:52:25 +04:00
|
|
|
baseName = Model.prototype.tableName.replace(/s$/, ''),
|
2014-06-04 09:47:16 +04:00
|
|
|
// Look for a matching slug, append an incrementing number if so
|
2014-09-30 02:45:58 +04:00
|
|
|
checkIfSlugExists, longSlug;
|
2013-12-20 17:36:00 +04:00
|
|
|
|
2015-06-14 18:58:49 +03:00
|
|
|
checkIfSlugExists = function checkIfSlugExists(slugToFind) {
|
2014-03-23 22:52:25 +04:00
|
|
|
var args = {slug: slugToFind};
|
2016-10-14 20:24:38 +03:00
|
|
|
|
2014-09-10 08:06:24 +04:00
|
|
|
// status is needed for posts
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
if (options && options.status) {
|
|
|
|
args.status = options.status;
|
2014-03-23 22:52:25 +04:00
|
|
|
}
|
2016-10-14 20:24:38 +03:00
|
|
|
|
2015-06-14 18:58:49 +03:00
|
|
|
return Model.findOne(args, options).then(function then(found) {
|
2014-03-23 22:52:25 +04:00
|
|
|
var trimSpace;
|
2013-12-20 17:36:00 +04:00
|
|
|
|
2014-03-23 22:52:25 +04:00
|
|
|
if (!found) {
|
2014-08-17 10:17:23 +04:00
|
|
|
return slugToFind;
|
2014-03-23 22:52:25 +04:00
|
|
|
}
|
2013-12-20 17:36:00 +04:00
|
|
|
|
2014-03-23 22:52:25 +04:00
|
|
|
slugTryCount += 1;
|
2013-12-20 17:36:00 +04:00
|
|
|
|
2014-09-30 02:45:58 +04:00
|
|
|
// If we shortened, go back to the full version and try again
|
|
|
|
if (slugTryCount === 2 && longSlug) {
|
|
|
|
slugToFind = longSlug;
|
|
|
|
longSlug = null;
|
|
|
|
slugTryCount = 1;
|
|
|
|
return checkIfSlugExists(slugToFind);
|
|
|
|
}
|
|
|
|
|
2014-03-23 22:52:25 +04:00
|
|
|
// If this is the first time through, add the hyphen
|
|
|
|
if (slugTryCount === 2) {
|
|
|
|
slugToFind += '-';
|
|
|
|
} else {
|
|
|
|
// Otherwise, trim the number off the end
|
|
|
|
trimSpace = -(String(slugTryCount - 1).length);
|
|
|
|
slugToFind = slugToFind.slice(0, trimSpace);
|
|
|
|
}
|
2013-12-20 17:36:00 +04:00
|
|
|
|
2014-03-23 22:52:25 +04:00
|
|
|
slugToFind += slugTryCount;
|
|
|
|
|
|
|
|
return checkIfSlugExists(slugToFind);
|
|
|
|
});
|
|
|
|
};
|
2013-12-20 17:36:00 +04:00
|
|
|
|
2017-11-21 16:21:22 +03:00
|
|
|
// the slug may never be longer than the allowed limit of 191 chars, but should also
|
|
|
|
// take the counter into count. We reduce a too long slug to 185 so we're always on the
|
|
|
|
// safe side, also in terms of checking for existing slugs already.
|
2017-12-14 15:26:48 +03:00
|
|
|
slug = security.string.safe(base, options);
|
2013-12-20 17:36:00 +04:00
|
|
|
|
2017-11-21 16:21:22 +03:00
|
|
|
if (slug.length > 185) {
|
|
|
|
// CASE: don't cut the slug on import
|
|
|
|
if (!_.has(options, 'importing') || !options.importing) {
|
|
|
|
slug = slug.slice(0, 185);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-30 02:45:58 +04:00
|
|
|
// If it's a user, let's try to cut it down (unless this is a human request)
|
|
|
|
if (baseName === 'user' && options && options.shortSlug && slugTryCount === 1 && slug !== 'ghost-owner') {
|
|
|
|
longSlug = slug;
|
|
|
|
slug = (slug.indexOf('-') > -1) ? slug.substr(0, slug.indexOf('-')) : slug;
|
|
|
|
}
|
|
|
|
|
2016-06-11 18:12:04 +03:00
|
|
|
if (!_.has(options, 'importing') || !options.importing) {
|
|
|
|
// This checks if the first character of a tag name is a #. If it is, this
|
|
|
|
// is an internal tag, and as such we should add 'hash' to the beginning of the slug
|
2016-10-10 11:51:03 +03:00
|
|
|
if (baseName === 'tag' && /^#/.test(base)) {
|
2016-06-11 18:12:04 +03:00
|
|
|
slug = 'hash-' + slug;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-20 17:36:00 +04:00
|
|
|
// Check the filtered slug doesn't match any of the reserved keywords
|
2016-09-13 18:41:14 +03:00
|
|
|
return filters.doFilter('slug.reservedSlugs', config.get('slugs').reserved).then(function then(slugList) {
|
2014-09-07 00:16:14 +04:00
|
|
|
// Some keywords cannot be changed
|
2017-12-11 21:14:05 +03:00
|
|
|
slugList = _.union(slugList, urlService.utils.getProtectedSlugs());
|
2014-09-07 00:16:14 +04:00
|
|
|
|
2016-06-11 21:23:27 +03:00
|
|
|
return _.includes(slugList, slug) ? slug + '-' + baseName : slug;
|
2015-06-14 18:58:49 +03:00
|
|
|
}).then(function then(slug) {
|
2014-09-07 00:16:14 +04:00
|
|
|
// if slug is empty after trimming use the model name
|
|
|
|
if (!slug) {
|
|
|
|
slug = baseName;
|
|
|
|
}
|
|
|
|
// Test for duplicate slugs.
|
|
|
|
return checkIfSlugExists(slug);
|
|
|
|
});
|
2015-10-22 15:49:15 +03:00
|
|
|
},
|
|
|
|
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
parseOrderOption: function (order, withRelated) {
|
2015-10-22 15:49:15 +03:00
|
|
|
var permittedAttributes, result, rules;
|
|
|
|
|
|
|
|
permittedAttributes = this.prototype.permittedAttributes();
|
Sorted out the mixed usages of `include` and `withRelated` (#9425)
no issue
- this commit cleans up the usages of `include` and `withRelated`.
### API layer (`include`)
- as request parameter e.g. `?include=roles,tags`
- as theme API parameter e.g. `{{get .... include="author"}}`
- as internal API access e.g. `api.posts.browse({include: 'author,tags'})`
- the `include` notation is more readable than `withRelated`
- and it allows us to use a different easier format (comma separated list)
- the API utility transforms these more readable properties into model style (or into Ghost style)
### Model access (`withRelated`)
- e.g. `models.Post.findPage({withRelated: ['tags']})`
- driven by bookshelf
---
Commits explained.
* Reorder the usage of `convertOptions`
- 1. validation
- 2. options convertion
- 3. permissions
- the reason is simple, the permission layer access the model layer
- we have to prepare the options before talking to the model layer
- added `convertOptions` where it was missed (not required, but for consistency reasons)
* Use `withRelated` when accessing the model layer and use `include` when accessing the API layer
* Change `convertOptions` API utiliy
- API Usage
- ghost.api(..., {include: 'tags,authors'})
- `include` should only be used when calling the API (either via request or via manual usage)
- `include` is only for readability and easier format
- Ghost (Model Layer Usage)
- models.Post.findOne(..., {withRelated: ['tags', 'authors']})
- should only use `withRelated`
- model layer cannot read 'tags,authors`
- model layer has no idea what `include` means, speaks a different language
- `withRelated` is bookshelf
- internal usage
* include-count plugin: use `withRelated` instead of `include`
- imagine you outsource this plugin to git and publish it to npm
- `include` is an unknown option in bookshelf
* Updated `permittedOptions` in base model
- `include` is no longer a known option
* Remove all occurances of `include` in the model layer
* Extend `filterOptions` base function
- this function should be called as first action
- we clone the unfiltered options
- check if you are using `include` (this is a protection which could help us in the beginning)
- check for permitted and (later on default `withRelated`) options
- the usage is coming in next commit
* Ensure we call `filterOptions` as first action
- use `ghostBookshelf.Model.filterOptions` as first action
- consistent naming pattern for incoming options: `unfilteredOptions`
- re-added allowed options for `toJSON`
- one unsolved architecture problem:
- if you override a function e.g. `edit`
- then you should call `filterOptions` as first action
- the base implementation of e.g. `edit` will call it again
- future improvement
* Removed `findOne` from Invite model
- no longer needed, the base implementation is the same
2018-02-15 12:53:53 +03:00
|
|
|
if (withRelated && withRelated.indexOf('count.posts') > -1) {
|
2015-11-21 00:20:00 +03:00
|
|
|
permittedAttributes.push('count.posts');
|
|
|
|
}
|
2015-10-22 15:49:15 +03:00
|
|
|
result = {};
|
|
|
|
rules = order.split(',');
|
|
|
|
|
|
|
|
_.each(rules, function (rule) {
|
|
|
|
var match, field, direction;
|
|
|
|
|
|
|
|
match = /^([a-z0-9_\.]+)\s+(asc|desc)$/i.exec(rule.trim());
|
|
|
|
|
|
|
|
// invalid order syntax
|
|
|
|
if (!match) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
field = match[1].toLowerCase();
|
|
|
|
direction = match[2].toUpperCase();
|
|
|
|
|
|
|
|
if (permittedAttributes.indexOf(field) === -1) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
result[field] = direction;
|
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
2017-12-13 15:19:51 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* All models which have a visibility property, can use this static helper function.
|
|
|
|
* Filter models by visibility.
|
|
|
|
*
|
|
|
|
* @param {Array|Object} items
|
|
|
|
* @param {Array} visibility
|
|
|
|
* @param {Boolean} [explicit]
|
|
|
|
* @param {Function} [fn]
|
|
|
|
* @returns {Array|Object} filtered items
|
|
|
|
*/
|
|
|
|
filterByVisibility: function filterByVisibility(items, visibility, explicit, fn) {
|
|
|
|
var memo = _.isArray(items) ? [] : {};
|
|
|
|
|
|
|
|
if (_.includes(visibility, 'all')) {
|
|
|
|
return fn ? _.map(items, fn) : items;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't want to change the structure of what is returned
|
|
|
|
return _.reduce(items, function (items, item, key) {
|
|
|
|
if (!item.visibility && !explicit || _.includes(visibility, item.visibility)) {
|
|
|
|
var newItem = fn ? fn(item) : item;
|
|
|
|
if (_.isArray(items)) {
|
|
|
|
memo.push(newItem);
|
|
|
|
} else {
|
|
|
|
memo[key] = newItem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return memo;
|
|
|
|
}, memo);
|
|
|
|
},
|
2013-06-25 15:43:15 +04:00
|
|
|
|
2017-12-13 15:19:51 +03:00
|
|
|
/**
|
|
|
|
* Returns an Array of visibility values.
|
|
|
|
* e.g. public,all => ['public, 'all']
|
|
|
|
* @param visibility
|
|
|
|
* @returns {*}
|
|
|
|
*/
|
|
|
|
parseVisibilityString: function parseVisibilityString(visibility) {
|
|
|
|
if (!visibility) {
|
|
|
|
return ['public'];
|
|
|
|
}
|
|
|
|
|
|
|
|
return _.map(visibility.split(','), _.trim);
|
Rewrite url service (#9550)
refs https://github.com/TryGhost/Team/issues/65
We are currently work on dynamic routing (aka channels).
An important piece of this feature is the url service, which always knows the url of a resource at any time.
Resources can belong to collections or taxonomies, which can be defined in a [routing yaml file](https://github.com/TryGhost/Ghost/issues/9528). We are currently shipping portions, which will at end form the full dynamic routing feature.
### Key Notes
- each routing type (collections, taxonomies, static pages) is registered in order - depending on the yaml routes file configuration
- static pages are an internal concept - they sit at the end of the subscriber queue
- we make use of a temporary [`Channels2`](https://github.com/TryGhost/Ghost/pull/9550/files#diff-9e7251409844521470c9829013cd1563) file, which simulates the current static routing in Ghost (this file will be modified, removed or whatever - this is one of the next steps)
- two way binding: you can ask for a resource url based on the resource id, you can ask for the resource based on the url
- in theory it's possible that multiple resources generate the same url: we don't handle this with collision (because this is error prone), we handle this with the order of serving content. if you ask the service for a resource, which lives behind e.g. /test/, you will get the resource which is served
- loose error handling -> log errors and handle instead of throw error and do nothing (we log the errors with a specific code, so we can react in case there is a bug)
- the url services fetches all resources on bootstrap. we only fetch and keep a reduced set of attributes (basically the main body of a resource)
- the bootstrap time will decrease a very little (depending on the amount of resources you have in your database)
- we still offer the option to disable url preloading (in your config `disableUrlPreload: true`) - this option will be removed as soon as the url service is connected. You can disable the service in case you encounter a problem
- **the url service is not yet connected, we will connect the service step by step. The first version should be released to pre-catch bugs. The next version will add 503 handling if the url service is not ready and it will consume urls for resources.**
----
- the url service generates urls based on resources (posts, pages, users, tags)
- the url service keeps track of resource changes
- the url service keeps track of resource removal/insert
- the architecture:
- each routing type is represented by a url generator
- a routing type is a collection, a taxonomiy or static pages
- a queue which ensures that urls are unique and can be owned by one url generator
- the hierarchy of registration defines that
- we query knex, because bookshelf is too slow
- removed old url service files + logic
- added temp channels alternative (Channels2) -> this file will look different soon, it's for now the temporary connector to the url service. Also the name of the file is not optimal, but that is not really important right now.
2018-04-17 12:29:04 +03:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* If you want to fetch all data fast, i recommend using this function.
|
|
|
|
* Bookshelf is just too slow, too much ORM overhead.
|
|
|
|
*
|
|
|
|
* If we e.g. instantiate for each object a model, it takes twice long.
|
|
|
|
*/
|
|
|
|
raw_knex: {
|
|
|
|
fetchAll: function (options) {
|
|
|
|
options = options || {};
|
|
|
|
|
|
|
|
const modelName = options.modelName;
|
|
|
|
const tableNames = {
|
|
|
|
Post: 'posts',
|
|
|
|
User: 'users',
|
|
|
|
Tag: 'tags'
|
|
|
|
};
|
|
|
|
const reducedFields = options.reducedFields;
|
|
|
|
const exclude = {
|
|
|
|
Post: [
|
|
|
|
'title',
|
|
|
|
'mobiledoc',
|
|
|
|
'html',
|
|
|
|
'plaintext',
|
|
|
|
'amp',
|
|
|
|
'codeinjection_head',
|
|
|
|
'codeinjection_foot',
|
|
|
|
'meta_title',
|
|
|
|
'meta_description',
|
|
|
|
'custom_excerpt',
|
|
|
|
'og_image',
|
|
|
|
'og_title',
|
|
|
|
'og_description',
|
|
|
|
'twitter_image',
|
|
|
|
'twitter_title',
|
|
|
|
'twitter_description',
|
|
|
|
'custom_template'
|
|
|
|
],
|
|
|
|
User: [
|
|
|
|
'bio',
|
|
|
|
'website',
|
|
|
|
'location',
|
|
|
|
'facebook',
|
|
|
|
'twitter',
|
|
|
|
'accessibility',
|
|
|
|
'meta_title',
|
|
|
|
'meta_description',
|
|
|
|
'tour'
|
|
|
|
],
|
|
|
|
Tag: [
|
|
|
|
'description',
|
|
|
|
'meta_title',
|
|
|
|
'meta_description'
|
|
|
|
]
|
|
|
|
};
|
|
|
|
const filter = options.filter;
|
|
|
|
const withRelated = options.withRelated;
|
|
|
|
const withRelatedFields = options.withRelatedFields;
|
|
|
|
const relations = {
|
|
|
|
tags: {
|
|
|
|
targetTable: 'tags',
|
|
|
|
name: 'tags',
|
|
|
|
innerJoin: {
|
|
|
|
relation: 'posts_tags',
|
|
|
|
condition: ['posts_tags.tag_id', '=', 'tags.id']
|
|
|
|
},
|
|
|
|
select: ['posts_tags.post_id as post_id'],
|
|
|
|
whereIn: 'posts_tags.post_id',
|
|
|
|
whereInKey: 'post_id',
|
|
|
|
orderBy: 'sort_order'
|
|
|
|
},
|
|
|
|
authors: {
|
|
|
|
targetTable: 'users',
|
|
|
|
name: 'authors',
|
|
|
|
innerJoin: {
|
|
|
|
relation: 'posts_authors',
|
|
|
|
condition: ['posts_authors.author_id', '=', 'users.id']
|
|
|
|
},
|
|
|
|
select: ['posts_authors.post_id as post_id'],
|
|
|
|
whereIn: 'posts_authors.post_id',
|
|
|
|
whereInKey: 'post_id',
|
|
|
|
orderBy: 'sort_order'
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let query = ghostBookshelf.knex(tableNames[modelName]);
|
|
|
|
|
|
|
|
// exclude fields if enabled
|
|
|
|
if (reducedFields) {
|
|
|
|
const toSelect = _.keys(schema.tables[tableNames[modelName]]);
|
|
|
|
|
|
|
|
_.each(exclude[modelName], (key) => {
|
|
|
|
if (toSelect.indexOf(key) !== -1) {
|
|
|
|
toSelect.splice(toSelect.indexOf(key), 1);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
query.select(toSelect);
|
|
|
|
}
|
|
|
|
|
|
|
|
// filter data
|
|
|
|
gql.knexify(query, gql.parse(filter));
|
|
|
|
|
|
|
|
return query.then((objects) => {
|
|
|
|
debug('fetched', modelName, filter);
|
|
|
|
|
|
|
|
let props = {};
|
|
|
|
|
|
|
|
if (!withRelated) {
|
|
|
|
return _.map(objects, (object) => {
|
|
|
|
object = ghostBookshelf._models[modelName].prototype.toJSON.bind({
|
|
|
|
attributes: object,
|
|
|
|
related: function (key) {
|
|
|
|
return object[key];
|
|
|
|
},
|
|
|
|
serialize: ghostBookshelf._models[modelName].prototype.serialize,
|
|
|
|
formatsToJSON: ghostBookshelf._models[modelName].prototype.formatsToJSON
|
|
|
|
})();
|
|
|
|
|
|
|
|
object = ghostBookshelf._models[modelName].prototype.fixBools(object);
|
|
|
|
object = ghostBookshelf._models[modelName].prototype.fixDatesWhenFetch(object);
|
|
|
|
return object;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
_.each(withRelated, (withRelatedKey) => {
|
|
|
|
const relation = relations[withRelatedKey];
|
|
|
|
|
|
|
|
props[relation.name] = (() => {
|
|
|
|
debug('fetch withRelated', relation.name);
|
|
|
|
|
|
|
|
let query = db.knex(relation.targetTable);
|
|
|
|
|
|
|
|
// default fields to select
|
|
|
|
_.each(relation.select, (fieldToSelect) => {
|
|
|
|
query.select(fieldToSelect);
|
|
|
|
});
|
|
|
|
|
|
|
|
// custom fields to select
|
|
|
|
_.each(withRelatedFields[withRelatedKey], (toSelect) => {
|
|
|
|
query.select(toSelect);
|
|
|
|
});
|
|
|
|
|
|
|
|
query.innerJoin(
|
|
|
|
relation.innerJoin.relation,
|
|
|
|
relation.innerJoin.condition[0],
|
|
|
|
relation.innerJoin.condition[1],
|
|
|
|
relation.innerJoin.condition[2]
|
|
|
|
);
|
|
|
|
|
|
|
|
query.whereIn(relation.whereIn, _.map(objects, 'id'));
|
|
|
|
query.orderBy(relation.orderBy);
|
|
|
|
|
|
|
|
return query
|
|
|
|
.then((relations) => {
|
|
|
|
debug('fetched withRelated', relation.name);
|
|
|
|
|
|
|
|
// arr => obj[post_id] = [...] (faster access)
|
|
|
|
return relations.reduce((obj, item) => {
|
|
|
|
if (!obj[item[relation.whereInKey]]) {
|
|
|
|
obj[item[relation.whereInKey]] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
obj[item[relation.whereInKey]].push(_.omit(item, relation.select));
|
|
|
|
return obj;
|
|
|
|
}, {});
|
|
|
|
});
|
|
|
|
})();
|
|
|
|
});
|
|
|
|
|
|
|
|
return Promise.props(props)
|
|
|
|
.then((relations) => {
|
|
|
|
debug('attach relations', modelName);
|
|
|
|
|
|
|
|
objects = _.map(objects, (object) => {
|
|
|
|
_.each(Object.keys(relations), (relation) => {
|
|
|
|
if (!relations[relation][object.id]) {
|
|
|
|
object[relation] = [];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
object[relation] = relations[relation][object.id];
|
|
|
|
});
|
|
|
|
|
|
|
|
object = ghostBookshelf._models[modelName].prototype.toJSON.bind({
|
|
|
|
attributes: object,
|
|
|
|
_originalOptions: {
|
|
|
|
withRelated: Object.keys(relations)
|
|
|
|
},
|
|
|
|
related: function (key) {
|
|
|
|
return object[key];
|
|
|
|
},
|
|
|
|
serialize: ghostBookshelf._models[modelName].prototype.serialize,
|
|
|
|
formatsToJSON: ghostBookshelf._models[modelName].prototype.formatsToJSON
|
|
|
|
})();
|
|
|
|
|
|
|
|
object = ghostBookshelf._models[modelName].prototype.fixBools(object);
|
|
|
|
object = ghostBookshelf._models[modelName].prototype.fixDatesWhenFetch(object);
|
|
|
|
return object;
|
|
|
|
});
|
|
|
|
|
|
|
|
debug('attached relations', modelName);
|
|
|
|
|
|
|
|
return objects;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2017-12-13 15:19:51 +03:00
|
|
|
}
|
2013-06-25 15:43:15 +04:00
|
|
|
});
|
|
|
|
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 19:18:38 +04:00
|
|
|
// Export ghostBookshelf for use elsewhere
|
2013-09-23 02:20:08 +04:00
|
|
|
module.exports = ghostBookshelf;
|