const Promise = require('bluebird'), _ = require('lodash'), moment = require('moment'), pipeline = require('../../lib/promise/pipeline'), localUtils = require('./utils'), models = require('../../models'), config = require('../../config'), common = require('../../lib/common'), postsAPI = require('./posts'); /** * Publish a scheduled post * * `apiPosts.read` and `apiPosts.edit` must happen in one transaction. * We lock the target row on fetch by using the `forUpdate` option. * Read more in models/post.js - `onFetching` * * object.force: you can force publishing a post in the past (for example if your service was down) */ exports.publishPost = function publishPost(object, options) { if (_.isEmpty(options)) { options = object || {}; object = {}; } let post, publishedAtMoment, publishAPostBySchedulerToleranceInMinutes = config.get('times').publishAPostBySchedulerToleranceInMinutes; // CASE: only the scheduler client is allowed to publish (hardcoded because of missing client permission system) if (!options.context || !options.context.client || options.context.client !== 'ghost-scheduler') { return Promise.reject(new common.errors.NoPermissionError({message: common.i18n.t('errors.permissions.noPermissionToAction')})); } options.context = {internal: true}; return pipeline([ localUtils.validate('posts', {opts: localUtils.idDefaultOptions}), (cleanOptions) => { cleanOptions.status = 'scheduled'; return models.Base.transaction((transacting) => { cleanOptions.transacting = transacting; cleanOptions.forUpdate = true; // CASE: extend allowed options, see api/zip-folder.js cleanOptions.opts = ['forUpdate', 'transacting']; return postsAPI.read(cleanOptions) .then((result) => { post = result.posts[0]; publishedAtMoment = moment(post.published_at); if (publishedAtMoment.diff(moment(), 'minutes') > publishAPostBySchedulerToleranceInMinutes) { return Promise.reject(new common.errors.NotFoundError({message: common.i18n.t('errors.api.job.notFound')})); } if (publishedAtMoment.diff(moment(), 'minutes') < publishAPostBySchedulerToleranceInMinutes * -1 && object.force !== true) { return Promise.reject(new common.errors.NotFoundError({message: common.i18n.t('errors.api.job.publishInThePast')})); } return postsAPI.edit( { posts: [{status: 'published'}] }, _.pick(cleanOptions, ['context', 'id', 'transacting', 'forUpdate', 'opts']) ); }); }); } ], options); }; /** * get all scheduled posts/pages * permission check not needed, because route is not exposed */ exports.getScheduledPosts = function readPosts(options) { options = options || {}; options.context = {internal: true}; return pipeline([ localUtils.validate('posts', {opts: ['from', 'to']}), (cleanOptions) => { cleanOptions.filter = 'status:scheduled'; cleanOptions.columns = ['id', 'published_at', 'created_at']; if (cleanOptions.from) { cleanOptions.filter += `+created_at:>='${moment(cleanOptions.from).format('YYYY-MM-DD HH:mm:ss')}'`; } if (cleanOptions.to) { cleanOptions.filter += `+created_at:<='${moment(cleanOptions.to).format('YYYY-MM-DD HH:mm:ss')}'`; } return models.Post.findAll(cleanOptions) .then((result) => { return Promise.resolve({posts: result.models}); }); } ], options); };