Ghost/core/server/api/posts.js
Eugene Kulabuhov 0e2709c42f [API] Retrieve next and previous post
closes #4262
- implementation based on #1545
- added integration test. Modified mocked posts because code requires published_at timestamps to be different.
- fixed 2 broken tests that depended on mocked posts to have "new Date()" as their timestamps
- added checks to only query db if next/previous post requested
2015-01-13 12:49:16 +00:00

197 lines
6.2 KiB
JavaScript

// # Posts API
// RESTful API for the Post resource
var Promise = require('bluebird'),
_ = require('lodash'),
dataProvider = require('../models'),
canThis = require('../permissions').canThis,
errors = require('../errors'),
utils = require('./utils'),
docName = 'posts',
allowedIncludes = ['created_by', 'updated_by', 'published_by', 'author', 'tags', 'fields', 'next', 'previous'],
posts;
// ## Helpers
function prepareInclude(include) {
var index;
include = include || '';
include = _.intersection(include.split(','), allowedIncludes);
index = include.indexOf('author');
if (index !== -1) {
include[index] = 'author_id';
}
return include;
}
/**
* ## Posts API Methods
*
* **See:** [API Methods](index.js.html#api%20methods)
*/
posts = {
/**
* ### Browse
* Find a paginated set of posts
*
* Will only return published posts unless we have an authenticated user and an alternative status
* parameter.
*
* Will return without static pages unless told otherwise
*
* Can return posts for a particular tag by passing a tag slug in
*
* @public
* @param {{context, page, limit, status, staticPages, tag}} options (optional)
* @returns {Promise(Posts)} Posts Collection with Meta
*/
browse: function browse(options) {
options = options || {};
if (!(options.context && options.context.user)) {
options.status = 'published';
}
if (options.include) {
options.include = prepareInclude(options.include);
}
return dataProvider.Post.findPage(options);
},
/**
* ### Read
* Find a post, by ID or Slug
*
* @public
* @param {{id_or_slug (required), context, status, include, ...}} options
* @return {Promise(Post)} Post
*/
read: function read(options) {
var attrs = ['id', 'slug', 'status'],
data = _.pick(options, attrs);
options = _.omit(options, attrs);
// only published posts if no user is present
if (!(options.context && options.context.user)) {
data.status = 'published';
}
if (options.include) {
options.include = prepareInclude(options.include);
}
return dataProvider.Post.findOne(data, options).then(function (result) {
if (result) {
return {posts: [result.toJSON()]};
}
return Promise.reject(new errors.NotFoundError('Post not found.'));
});
},
/**
* ### Edit
* Update properties of a post
*
* @public
* @param {Post} object Post or specific properties to update
* @param {{id (required), context, include,...}} options
* @return {Promise(Post)} Edited Post
*/
edit: function edit(object, options) {
return canThis(options.context).edit.post(options.id).then(function () {
return utils.checkObject(object, docName).then(function (checkedPostData) {
if (options.include) {
options.include = prepareInclude(options.include);
}
return dataProvider.Post.edit(checkedPostData.posts[0], options);
}).then(function (result) {
if (result) {
var post = result.toJSON();
// If previously was not published and now is (or vice versa), signal the change
post.statusChanged = false;
if (result.updated('status') !== result.get('status')) {
post.statusChanged = true;
}
return {posts: [post]};
}
return Promise.reject(new errors.NotFoundError('Post not found.'));
});
}, function () {
return Promise.reject(new errors.NoPermissionError('You do not have permission to edit posts.'));
});
},
/**
* ### Add
* Create a new post along with any tags
*
* @public
* @param {Post} object
* @param {{context, include,...}} options
* @return {Promise(Post)} Created Post
*/
add: function add(object, options) {
options = options || {};
return canThis(options.context).add.post().then(function () {
return utils.checkObject(object, docName).then(function (checkedPostData) {
if (options.include) {
options.include = prepareInclude(options.include);
}
return dataProvider.Post.add(checkedPostData.posts[0], options);
}).then(function (result) {
var post = result.toJSON();
if (post.status === 'published') {
// When creating a new post that is published right now, signal the change
post.statusChanged = true;
}
return {posts: [post]};
});
}, function () {
return Promise.reject(new errors.NoPermissionError('You do not have permission to add posts.'));
});
},
/**
* ### Destroy
* Delete a post, cleans up tag relations, but not unused tags
*
* @public
* @param {{id (required), context,...}} options
* @return {Promise(Post)} Deleted Post
*/
destroy: function destroy(options) {
return canThis(options.context).destroy.post(options.id).then(function () {
var readOptions = _.extend({}, options, {status: 'all'});
return posts.read(readOptions).then(function (result) {
return dataProvider.Post.destroy(options).then(function () {
var deletedObj = result;
if (deletedObj.posts) {
_.each(deletedObj.posts, function (post) {
post.statusChanged = true;
});
}
return deletedObj;
});
});
}, function () {
return Promise.reject(new errors.NoPermissionError('You do not have permission to remove posts.'));
});
}
};
module.exports = posts;