diff --git a/core/server/api/canary/schedules.js b/core/server/api/canary/schedules.js index 9359437ae1..9546f79571 100644 --- a/core/server/api/canary/schedules.js +++ b/core/server/api/canary/schedules.js @@ -1,11 +1,6 @@ -const _ = require('lodash'); -const moment = require('moment'); -const config = require('../../../shared/config'); const models = require('../../models'); -const urlUtils = require('../../../shared/url-utils'); -const i18n = require('../../../shared/i18n'); -const errors = require('@tryghost/errors'); -const api = require('./index'); + +const postSchedulingService = require('../../services/posts/post-scheduling-service')('canary'); module.exports = { docName: 'schedules', @@ -32,11 +27,8 @@ module.exports = { permissions: { docName: 'posts' }, - query(frame) { - let resource; + async query(frame) { const resourceType = frame.options.resource; - const publishAPostBySchedulerToleranceInMinutes = config.get('times').publishAPostBySchedulerToleranceInMinutes; - const options = { status: 'scheduled', id: frame.options.id, @@ -45,53 +37,13 @@ module.exports = { } }; - return api[resourceType].read({id: frame.options.id}, options) - .then((result) => { - resource = result[resourceType][0]; - const publishedAtMoment = moment(resource.published_at); + const {scheduledResource, preScheduledResource} = await postSchedulingService.publish(resourceType, frame.options.id, frame.data.force, options); + const cacheInvalidate = postSchedulingService.handleCacheInvalidation(scheduledResource, preScheduledResource); + this.headers.cacheInvalidate = cacheInvalidate; - if (publishedAtMoment.diff(moment(), 'minutes') > publishAPostBySchedulerToleranceInMinutes) { - return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.job.notFound')})); - } - - if (publishedAtMoment.diff(moment(), 'minutes') < publishAPostBySchedulerToleranceInMinutes * -1 && frame.data.force !== true) { - return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.job.publishInThePast')})); - } - - const editedResource = {}; - editedResource[resourceType] = [{ - status: 'published', - updated_at: moment(resource.updated_at).toISOString(true) - }]; - - return api[resourceType].edit( - editedResource, - _.pick(options, ['context', 'id', 'transacting', 'forUpdate']) - ); - }) - .then((result) => { - const scheduledResource = result[resourceType][0]; - - if ( - (scheduledResource.status === 'published' && resource.status !== 'published') || - (scheduledResource.status === 'draft' && resource.status === 'published') - ) { - this.headers.cacheInvalidate = true; - } else if ( - (scheduledResource.status === 'draft' && resource.status !== 'published') || - (scheduledResource.status === 'scheduled' && resource.status !== 'scheduled') - ) { - this.headers.cacheInvalidate = { - value: urlUtils.urlFor({ - relativeUrl: urlUtils.urlJoin('/p', scheduledResource.uuid, '/') - }) - }; - } else { - this.headers.cacheInvalidate = false; - } - - return result; - }); + const response = {}; + response[resourceType] = [scheduledResource]; + return response; } }, diff --git a/core/server/services/posts/post-scheduling-service.js b/core/server/services/posts/post-scheduling-service.js new file mode 100644 index 0000000000..55467ebda3 --- /dev/null +++ b/core/server/services/posts/post-scheduling-service.js @@ -0,0 +1,96 @@ +const _ = require('lodash'); +const errors = require('@tryghost/errors'); +const moment = require('moment'); +const config = require('../../../shared/config'); +const i18n = require('../../../shared/i18n'); +const urlUtils = require('../../../shared/url-utils'); +const api = require('../../api'); + +class PostSchedulingService { + /** + * + * @param {Object} options + * @param {String} options.apiVersion - api version + */ + constructor({apiVersion}) { + this.api = api[apiVersion]; + } + + /** + * Publishes scheduled resource (a post or a page at the moment of writing) + * + * @param {String} resourceType one of 'post' or 'page' resources + * @param {String} id resource id + * @param {Boolean} force force publish flag + * @param {Object} options api query options + * @returns {Promise} + */ + async publish(resourceType, id, force, options) { + const publishAPostBySchedulerToleranceInMinutes = config.get('times').publishAPostBySchedulerToleranceInMinutes; + + const result = await this.api[resourceType].read({id}, options); + const preScheduledResource = result[resourceType][0]; + + const publishedAtMoment = moment(preScheduledResource.published_at); + + if (publishedAtMoment.diff(moment(), 'minutes') > publishAPostBySchedulerToleranceInMinutes) { + return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.job.notFound')})); + } + + if (publishedAtMoment.diff(moment(), 'minutes') < publishAPostBySchedulerToleranceInMinutes * -1 && force !== true) { + return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.job.publishInThePast')})); + } + + const editedResource = {}; + editedResource[resourceType] = [{ + status: 'published', + updated_at: moment(preScheduledResource.updated_at).toISOString(true) + }]; + + const editResult = await this.api[resourceType].edit( + editedResource, + _.pick(options, ['context', 'id', 'transacting', 'forUpdate']) + ); + const scheduledResource = editResult[resourceType][0]; + + return {scheduledResource, preScheduledResource}; + } + + /** + * + * @param {Object} scheduledResource post or page resource object + * @param {Object} preScheduledResource post or page resource object in state before publishing + * @returns {Boolean|Object} + */ + handleCacheInvalidation(scheduledResource, preScheduledResource) { + if ( + (scheduledResource.status === 'published' && preScheduledResource.status !== 'published') || + (scheduledResource.status === 'draft' && preScheduledResource.status === 'published') + ) { + return true; + } else if ( + (scheduledResource.status === 'draft' && preScheduledResource.status !== 'published') || + (scheduledResource.status === 'scheduled' && preScheduledResource.status !== 'scheduled') + ) { + return { + value: urlUtils.urlFor({ + relativeUrl: urlUtils.urlJoin('/p', scheduledResource.uuid, '/') + }) + }; + } else { + return false; + } + } +} + +/** + * @param {string} apiVersion - API version to use within the service + * @returns {PostSchedulingService} instance of the PostsService + */ +const getPostSchedulingServiceInstance = (apiVersion) => { + return new PostSchedulingService({ + apiVersion: apiVersion + }); +}; + +module.exports = getPostSchedulingServiceInstance;