Ghost/core/server/adapters/scheduling/post-scheduling/index.js
Nazar Gargol 00f95e7328 Migrated schedules controller to v2
closes #10060

- Implemented scheduling for posts and pages
- Added cache invalidation when scheduling
- Refactored admin token eneration function to accept existing key as parameter in tests
- Added Ghost Scheduler Integration fixture
- Added fixture for permissions for post publish action
- Migrated getScheduled method to v2
- Did not add support for 'from' and 'to' parameters as they were not used by DefaultScheduler
- This method needs rethinking in a long run as it's an ugly hack and should rather become proper endpoint that returns JSON data instead of models
- Removed unused auth middleware from v2 routes
- Added internal scheduler role
- Implemetnted transactions in v2 frame
- This takes into account scenario mentioned in c93f03b87e
- Specifically:
>if two queries happening in a transaction we have to signalise
  knex/mysql that we select for an update
  otherwise the following case happens:
  you fetch posts for an update
  a user requests comes in and updates the post (e.g. sets title to "X")
  you update the fetched posts, title would get overriden to the old one
2019-08-07 14:51:36 +02:00

121 lines
3.7 KiB
JavaScript

const Promise = require('bluebird'),
moment = require('moment'),
localUtils = require('../utils'),
common = require('../../../lib/common'),
models = require('../../../models'),
urlUtils = require('../../../lib/url-utils'),
_private = {};
/**
* @description Normalize model data into scheduler notation.
* @param {Object} options
* @return {Object}
*/
_private.normalize = function normalize(options) {
const {model, apiUrl, client} = options;
return {
// NOTE: The scheduler expects a unix timestamp.
time: moment(model.get('published_at')).valueOf(),
// @TODO: We are still using API v0.1
url: `${urlUtils.urlJoin(apiUrl, 'schedules', 'posts', model.get('id'))}?client_id=${client.get('slug')}&client_secret=${client.get('secret')}`,
extra: {
httpMethod: 'PUT',
oldTime: model.previous('published_at') ? moment(model.previous('published_at')).valueOf() : null
}
};
};
/**
* @description Load the client credentials for v0.1 API.
*
* @TODO: Remove when we drop v0.1. API v2 uses integrations.
* @return {Promise}
*/
_private.loadClient = function loadClient() {
return models.Client.findOne({slug: 'ghost-scheduler'}, {columns: ['slug', 'secret']});
};
/**
* @description Load all scheduled posts from database.
* @return {Promise}
*/
_private.loadScheduledPosts = function () {
// TODO: make this version aware?
const api = require('../../../api');
return api.schedules.getScheduledPosts()
.then((result) => {
return result.posts || [];
});
};
/**
* @description Initialise post scheduling.
* @param {Object} options
* @return {*}
*/
exports.init = function init(options = {}) {
const {apiUrl} = options;
let adapter = null,
client = null;
if (!Object.keys(options).length) {
return Promise.reject(new common.errors.IncorrectUsageError({message: 'post-scheduling: no config was provided'}));
}
if (!apiUrl) {
return Promise.reject(new common.errors.IncorrectUsageError({message: 'post-scheduling: no apiUrl was provided'}));
}
return _private.loadClient()
.then((_client) => {
client = _client;
return localUtils.createAdapter(options);
})
.then((_adapter) => {
adapter = _adapter;
if (!adapter.rescheduleOnBoot) {
return [];
}
return _private.loadScheduledPosts();
})
.then((scheduledPosts) => {
if (!scheduledPosts.length) {
return;
}
scheduledPosts.forEach((model) => {
// NOTE: We are using reschedule, because custom scheduling adapter could use a database, which needs to be updated
// and not an in-process implementation!
adapter.reschedule(_private.normalize({model, apiUrl, client}), {bootstrap: true});
});
})
.then(() => {
adapter.run();
})
.then(() => {
common.events.onMany([
'post.scheduled',
'page.scheduled'
], (model) => {
adapter.schedule(_private.normalize({model, apiUrl, client}));
});
common.events.onMany([
'post.rescheduled',
'page.rescheduled'
], (model) => {
adapter.reschedule(_private.normalize({model, apiUrl, client}));
});
common.events.onMany([
'post.unscheduled',
'page.unscheduled'
], (model) => {
adapter.unschedule(_private.normalize({model, apiUrl, client}));
});
});
};