2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

feature: when timezone changes, reschedule all posts

closes #6406
- created listeners.js connector
- merged listeners.js with events.js (in models/base)
- set a post to draft when published_at would be in the past
- reschedule a post when published_at would be in the future
This commit is contained in:
kirrg001 2016-06-10 09:37:55 +02:00
parent 88dc7b3e99
commit 16fc0d29bf
6 changed files with 196 additions and 31 deletions

View file

@ -1,17 +0,0 @@
var config = require('../../config'),
moment = require('moment'),
events = require(config.paths.corePath + '/server/events'),
models = require(config.paths.corePath + '/server/models'),
errors = require(config.paths.corePath + '/server/errors');
/**
* WHEN access token is created we will update last_login for user.
*/
events.on('token.added', function (tokenModel) {
models.User.edit(
{last_login: moment().utc()}, {id: tokenModel.get('user_id')}
)
.catch(function (err) {
errors.logError(err);
});
});

View file

@ -19,7 +19,6 @@ var _ = require('lodash'),
validation = require('../../data/validation'),
plugins = require('../plugins'),
i18n = require('../../i18n'),
ghostBookshelf,
proto;

View file

@ -0,0 +1,69 @@
var config = require('../../config'),
events = require(config.paths.corePath + '/server/events'),
models = require(config.paths.corePath + '/server/models'),
errors = require(config.paths.corePath + '/server/errors'),
Promise = require('bluebird'),
moment = require('moment-timezone');
/**
* WHEN access token is created we will update last_login for user.
*/
events.on('token.added', function (tokenModel) {
models.User.edit({last_login: moment().toDate()}, {id: tokenModel.get('user_id')})
.catch(function (err) {
errors.logError(err);
});
});
/**
* WHEN timezone changes, we will:
* - reschedule all scheduled posts
* - draft scheduled posts, when the published_at would be in the past
*/
events.on('settings.activeTimezone.edited', function (settingModel) {
var newTimezone = settingModel.attributes.value,
previousTimezone = settingModel._updatedAttributes.value,
timezoneOffset = moment.tz(newTimezone).utcOffset();
// CASE: TZ was updated, but did not change
if (previousTimezone === newTimezone) {
return;
}
models.Post.findAll({filter: 'status:scheduled', context: {internal: true}})
.then(function (results) {
if (!results.length) {
return;
}
return Promise.mapSeries(results.map(function (post) {
var newPublishedAtMoment = moment(post.get('published_at')).add(timezoneOffset, 'minutes');
/**
* CASE:
* - your configured TZ is GMT+01:00
* - now is 10AM +01:00 (9AM UTC)
* - your post should be published 8PM +01:00 (7PM UTC)
* - you reconfigure your blog TZ to GMT+08:00
* - now is 5PM +08:00 (9AM UTC)
* - if we don't change the published_at, 7PM + 8 hours === next day 5AM
* - so we update published_at to 7PM - 480minutes === 11AM UTC
* - 11AM UTC === 7PM +08:00
*/
if (newPublishedAtMoment.isBefore(moment().add(5, 'minutes'))) {
post.set('status', 'draft');
} else {
post.set('published_at', newPublishedAtMoment.toDate());
}
return models.Post.edit(post.toJSON(), {id: post.id, context: {internal: true}}).reflect();
})).each(function (result) {
if (!result.isFulfilled()) {
errors.logError(result.reason());
}
});
})
.catch(function (err) {
errors.logError(err);
});
});

View file

@ -3,17 +3,15 @@
*/
var _ = require('lodash'),
exports,
models;
// Initialise model events
require('./base/events');
// enable event listeners
require('./base/listeners');
/**
* Expose all models
*/
exports = module.exports;
models = [

View file

@ -0,0 +1,121 @@
/*globals describe, before, beforeEach, afterEach, it*/
/*jshint unused:false*/
var should = require('should'),
Promise = require('bluebird'),
moment = require('moment'),
sinon = require('sinon'),
rewire = require('rewire'),
_ = require('lodash'),
config = require('../../../../server/config'),
testUtils = require(config.paths.corePath + '/test/utils'),
events = require(config.paths.corePath + '/server/events'),
models = require(config.paths.corePath + '/server/models');
describe('Models: listeners', function () {
var eventsToRemember = {},
scope = {
posts: [],
publishedAtFutureMoment: moment().add(2, 'days').startOf('hour'),
timezoneOffset: 420,
newTimezone: 'America/Los_Angeles',
oldTimezone: 'Europe/London'
};
beforeEach(testUtils.teardown);
beforeEach(testUtils.setup());
beforeEach(function () {
sinon.stub(events, 'on', function (eventName, callback) {
eventsToRemember[eventName] = callback;
});
rewire(config.paths.corePath + '/server/models/base/listeners');
});
afterEach(function (done) {
events.on.restore();
testUtils.teardown(done);
});
describe('on timezone changed', function () {
var posts;
describe('db has scheduled posts', function () {
beforeEach(function (done) {
// will get rescheduled
scope.posts.push(testUtils.DataGenerator.forKnex.createPost({
published_at: scope.publishedAtFutureMoment.toDate(),
status: 'scheduled',
title: '1',
slug: '1'
}));
// will get drafted
scope.posts.push(testUtils.DataGenerator.forKnex.createPost({
published_at: moment().add(2, 'hours').toDate(),
status: 'scheduled',
title: '2',
slug: '2'
}));
Promise.all(scope.posts.map(function (post) {
return models.Post.add(post, testUtils.context.owner);
})).then(function (result) {
result.length.should.eql(2);
posts = result;
done();
}).catch(function (err) {
return done(err);
});
});
it('activeTimezone changes change', function (done) {
var timeout;
eventsToRemember['settings.activeTimezone.edited']({
attributes: {value: scope.newTimezone},
_updatedAttributes: {value: scope.oldTimezone}
});
(function retry() {
models.Post.findAll({context: {internal: true}})
.then(function (results) {
var post1 = _.find(results.models, function (post) {
return post.get('title') === '1';
}),
post2 = _.find(results.models, function (post) {
return post.get('title') === '2';
});
if (results.models.length === posts.length &&
post2.get('status') === 'draft' &&
moment(post1.get('published_at')).diff(scope.publishedAtFutureMoment.clone().subtract(scope.timezoneOffset, 'minutes')) === 0) {
return done();
}
clearTimeout(timeout);
timeout = setTimeout(retry, 500);
})
.catch(done);
})();
});
});
describe('db has no scheduled posts', function () {
it('no scheduled posts', function (done) {
eventsToRemember['settings.activeTimezone.edited']({
attributes: {value: scope.newTimezone},
_updatedAttributes: {value: scope.oldTimezone}
});
models.Post.findAll({context: {internal: true}})
.then(function (results) {
results.length.should.eql(0);
done();
})
.catch(done);
});
});
});
});

View file

@ -3,13 +3,13 @@ var should = require('should'),
sinon = require('sinon'),
rewire = require('rewire'),
sandbox = sinon.sandbox.create(),
events = require('../../server/events'),
Models = require('../../server/models');
events = require('../../../../server/events/index'),
Models = require('../../../../server/models/index');
// To stop jshint complaining
should.equal(true, true);
describe('Model Events', function () {
describe('Models: listeners', function () {
var eventsToRemember = {};
before(function () {
@ -17,9 +17,7 @@ describe('Model Events', function () {
eventsToRemember[name] = callback;
});
rewire('../../server/models/base/events');
// Loads all the models
rewire('../../../../server/models/base/listeners');
Models.init();
});
@ -29,13 +27,10 @@ describe('Model Events', function () {
describe('on token added', function () {
it('calls User edit when event is emitted', function (done) {
// Setup
var userModelSpy = sandbox.spy(Models.User, 'edit');
// Test
eventsToRemember['token.added']({get: function () { return 1; }});
// Assert
userModelSpy.calledOnce.should.be.true();
userModelSpy.calledWith(
sinon.match.has('last_login'),