// # 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'), pipeline = require('../utils/pipeline'), docName = 'posts', allowedIncludes = ['created_by', 'updated_by', 'published_by', 'author', 'tags', 'fields', 'next', 'previous'], posts; /** * ### 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 * * * @public * @param {{context, page, limit, status, staticPages, tag, featured}} options (optional) * @returns {Promise} Posts Collection with Meta */ browse: function browse(options) { var extraOptions = ['tag', 'author', 'status', 'staticPages', 'featured'], permittedOptions = utils.browseDefaultOptions.concat(extraOptions), tasks; /** * ### Handle Permissions * We need to either be an authorised user, or only return published posts. * @param {Object} options * @returns {Object} options */ function handlePermissions(options) { if (!(options.context && options.context.user)) { options.status = 'published'; } return options; } /** * ### Model Query * Make the call to the Model layer * @param {Object} options * @returns {Object} options */ function modelQuery(options) { return dataProvider.Post.findPage(options); } // Push all of our tasks into a `tasks` array in the correct order tasks = [ utils.validate(docName, {opts: permittedOptions}), handlePermissions, utils.convertOptions(allowedIncludes), modelQuery ]; // Pipeline calls each task passing the result of one to be the arguments for the next return pipeline(tasks, options); }, /** * ## Read * Find a post, by ID, UUID, or Slug * * @public * @param {Object} options * @return {Promise} Post */ read: function read(options) { var attrs = ['id', 'slug', 'status', 'uuid'], tasks; /** * ### Handle Permissions * We need to either be an authorised user, or only return published posts. * @param {Object} options * @returns {Object} options */ function handlePermissions(options) { if (!options.data.uuid && !(options.context && options.context.user)) { options.data.status = 'published'; } return options; } /** * ### Model Query * Make the call to the Model layer * @param {Object} options * @returns {Object} options */ function modelQuery(options) { return dataProvider.Post.findOne(options.data, _.omit(options, ['data'])); } // Push all of our tasks into a `tasks` array in the correct order tasks = [ utils.validate(docName, {attrs: attrs}), handlePermissions, utils.convertOptions(allowedIncludes), modelQuery ]; // Pipeline calls each task passing the result of one to be the arguments for the next return pipeline(tasks, options).then(function formatResponse(result) { // @TODO make this a formatResponse task? if (result) { return {posts: [result.toJSON(options)]}; } 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) { var tasks; /** * ### Handle Permissions * We need to be an authorised user to perform this action * @param {Object} options * @returns {Object} options */ function handlePermissions(options) { return canThis(options.context).edit.post(options.id).then(function permissionGranted() { return options; }).catch(function handleError(error) { return errors.handleAPIError(error, 'You do not have permission to edit posts.'); }); } /** * ### Model Query * Make the call to the Model layer * @param {Object} options * @returns {Object} options */ function modelQuery(options) { return dataProvider.Post.edit(options.data.posts[0], _.omit(options, ['data'])); } // Push all of our tasks into a `tasks` array in the correct order tasks = [ utils.validate(docName, {opts: utils.idDefaultOptions}), handlePermissions, utils.convertOptions(allowedIncludes), modelQuery ]; // Pipeline calls each task passing the result of one to be the arguments for the next return pipeline(tasks, object, options).then(function formatResponse(result) { if (result) { var post = result.toJSON(options); // 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.')); }); }, /** * ## 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) { var tasks; /** * ### Handle Permissions * We need to be an authorised user to perform this action * @param {Object} options * @returns {Object} options */ function handlePermissions(options) { return canThis(options.context).add.post().then(function permissionGranted() { return options; }).catch(function () { return Promise.reject(new errors.NoPermissionError('You do not have permission to add posts.')); }); } /** * ### Model Query * Make the call to the Model layer * @param {Object} options * @returns {Object} options */ function modelQuery(options) { return dataProvider.Post.add(options.data.posts[0], _.omit(options, ['data'])); } // Push all of our tasks into a `tasks` array in the correct order tasks = [ utils.validate(docName), handlePermissions, utils.convertOptions(allowedIncludes), modelQuery ]; // Pipeline calls each task passing the result of one to be the arguments for the next return pipeline(tasks, object, options).then(function formatResponse(result) { var post = result.toJSON(options); if (post.status === 'published') { // When creating a new post that is published right now, signal the change post.statusChanged = true; } return {posts: [post]}; }); }, /** * ## 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) { var tasks; /** * ### Handle Permissions * We need to be an authorised user to perform this action * @param {Object} options * @returns {Object} options */ function handlePermissions(options) { return canThis(options.context).destroy.post(options.id).then(function permissionGranted() { options.status = 'all'; return options; }).catch(function handleError(error) { return errors.handleAPIError(error, 'You do not have permission to remove posts.'); }); } /** * ### Model Query * Make the call to the Model layer * @param {Object} options * @returns {Object} options */ function modelQuery(options) { return posts.read(options).then(function (result) { return dataProvider.Post.destroy(options).then(function () { return result; }); }); } // Push all of our tasks into a `tasks` array in the correct order tasks = [ utils.validate(docName, {opts: utils.idDefaultOptions}), handlePermissions, utils.convertOptions(allowedIncludes), modelQuery ]; // Pipeline calls each task passing the result of one to be the arguments for the next return pipeline(tasks, options).then(function formatResponse(result) { var deletedObj = result; if (deletedObj.posts) { _.each(deletedObj.posts, function (post) { post.statusChanged = true; }); } return deletedObj; }); } }; module.exports = posts;