mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Merge pull request #6962 from kirrg001/feature/when-timezone-changes-reschedule-posts
feature: when timezone changes, reschedule all posts
This commit is contained in:
commit
471edf0ea9
6 changed files with 196 additions and 31 deletions
|
@ -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);
|
||||
});
|
||||
});
|
|
@ -19,7 +19,6 @@ var _ = require('lodash'),
|
|||
validation = require('../../data/validation'),
|
||||
plugins = require('../plugins'),
|
||||
i18n = require('../../i18n'),
|
||||
|
||||
ghostBookshelf,
|
||||
proto;
|
||||
|
||||
|
|
69
core/server/models/base/listeners.js
Normal file
69
core/server/models/base/listeners.js
Normal 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);
|
||||
});
|
||||
});
|
|
@ -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 = [
|
||||
|
|
121
core/test/integration/model/base/listeners_spec.js
Normal file
121
core/test/integration/model/base/listeners_spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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'),
|
Loading…
Reference in a new issue