Invalidate cache header only for published posts

Closes #1563

- Add new updatedAttributes() functionality to base models
- Update Post.edit(...) to pass along _updatedAttributes values
- Update Post.delete to set statusChanged to true
- Add checking for statusChanged to cacheInvalidationHeader()
- Update route tests that check for cache invalidation header
This commit is contained in:
Jacob Gable 2014-04-21 20:04:30 -05:00
parent 9bf02c9e8c
commit a9cc252b45
5 changed files with 63 additions and 15 deletions

View File

@ -17,23 +17,37 @@ var _ = require('lodash'),
// ## Request Handlers
function cacheInvalidationHeader(req, result) {
//TODO: don't set x-cache-invalidate header for drafts
var parsedUrl = req._parsedUrl.pathname.replace(/\/$/, '').split('/'),
method = req.method,
endpoint = parsedUrl[4],
id = parsedUrl[5],
cacheInvalidate,
jsonResult = result.toJSON ? result.toJSON() : result;
jsonResult = result.toJSON ? result.toJSON() : result,
post,
wasPublished,
wasDeleted;
if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
if (endpoint === 'settings' || endpoint === 'users' || endpoint === 'db') {
cacheInvalidate = '/*';
} else if (endpoint === 'posts') {
cacheInvalidate = '/, /page/*, /rss/, /rss/*, /tag/*';
if (id && jsonResult.posts[0].slug) {
return config.urlForPost(settings, jsonResult.posts[0]).then(function (postUrl) {
return cacheInvalidate + ', ' + postUrl;
});
post = jsonResult.posts[0];
wasPublished = post.statusChanged && post.status === 'published';
wasDeleted = method === 'DELETE';
// Remove the statusChanged value from the response
if (post.statusChanged) {
delete post.statusChanged;
}
// Don't set x-cache-invalidate header for drafts
if (wasPublished || wasDeleted) {
cacheInvalidate = '/, /page/*, /rss/, /rss/*, /tag/*';
if (id && post.slug) {
return config.urlForPost(settings, post).then(function (postUrl) {
return cacheInvalidate + ', ' + postUrl;
});
}
}
}
}

View File

@ -73,6 +73,10 @@ posts = {
if (result) {
var omitted = result.toJSON();
omitted.author = _.omit(omitted.author, filteredUserAttributes);
// If previously was not published and now is, signal the change
if (result.updated('status') !== result.get('status')) {
omitted.statusChanged = true;
}
return { posts: [ omitted ]};
}
return when.reject({code: 404, message: 'Post not found'});
@ -93,6 +97,10 @@ posts = {
}).then(function (result) {
var omitted = result.toJSON();
omitted.author = _.omit(omitted.author, filteredUserAttributes);
if (omitted.status === 'published') {
// When creating a new post that is published right now, signal the change
omitted.statusChanged = true;
}
return { posts: [ omitted ]};
});
}, function () {
@ -110,6 +118,13 @@ posts = {
return posts.read.call({user: self.user}, {id : args.id, status: 'all'}).then(function (result) {
return dataProvider.Post.destroy(args.id).then(function () {
var deletedObj = result;
if (deletedObj.posts) {
_.each(deletedObj.posts, function (post) {
post.statusChanged = true;
});
}
return deletedObj;
});
});

View File

@ -56,6 +56,9 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
saving: function (newObj, attr, options) {
// Remove any properties which don't belong on the model
this.attributes = this.pick(this.permittedAttributes());
// Store the previous attributes so we can tell what was updated later
this._updatedAttributes = newObj.previousAttributes();
this.set('updated_by', options.user);
},
@ -106,6 +109,16 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
sanitize: function (attr) {
return sanitize(this.get(attr)).xss();
},
// Get attributes that have been updated (values before a .save() call)
updatedAttributes: function () {
return this._updatedAttributes || {};
},
// Get a specific updated attribute value
updated: function (attr) {
return this.updatedAttributes()[attr];
}
}, {

View File

@ -444,7 +444,12 @@ Post = ghostBookshelf.Model.extend({
return ghostBookshelf.Model.edit.call(this, editedPost, options).then(function (post) {
if (post) {
return self.findOne({status: 'all', id: post.id}, options);
return self.findOne({status: 'all', id: post.id}, options)
.then(function (found) {
// Pass along the updated attributes for checking status changes
found._updatedAttributes = post._updatedAttributes;
return found;
});
}
});
},

View File

@ -316,6 +316,7 @@ describe('Post API', function () {
}
var publishedPost = res.body;
_.has(res.headers, 'x-cache-invalidate').should.equal(true);
res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + publishedPost.posts[0].slug + '/');
res.should.be.json;
publishedPost.should.exist;
@ -334,7 +335,7 @@ describe('Post API', function () {
}
var updatedPost = res.body;
res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + updatedPost.posts[0].slug + '/');
_.has(res.headers, 'x-cache-invalidate').should.equal(false);
res.should.be.json;
updatedPost.should.exist;
updatedPost.posts.should.exist;
@ -373,7 +374,7 @@ describe('Post API', function () {
}
var putBody = res.body;
res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.posts[0].slug + '/');
_.has(res.headers, 'x-cache-invalidate').should.equal(false);
res.should.be.json;
putBody.should.exist;
putBody.posts[0].title.should.eql(changedValue);
@ -407,7 +408,7 @@ describe('Post API', function () {
}
var putBody = res.body;
res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.posts[0].slug + '/');
_.has(res.headers, 'x-cache-invalidate').should.equal(false);
res.should.be.json;
putBody.should.exist;
putBody.posts[0].page.should.eql(changedValue);
@ -442,7 +443,7 @@ describe('Post API', function () {
}
var putBody = res.body;
res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.posts[0].slug + '/');
_.has(res.headers, 'x-cache-invalidate').should.equal(false);
res.should.be.json;
putBody.should.exist;
putBody.posts[0].page.should.eql(changedValue);
@ -498,7 +499,7 @@ describe('Post API', function () {
}
var putBody = res.body;
res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, /' + putBody.posts[0].slug + '/');
_.has(res.headers, 'x-cache-invalidate').should.equal(false);
res.should.be.json;
putBody.should.exist;
putBody.posts.should.exist;
@ -539,7 +540,7 @@ describe('Post API', function () {
}
var putBody = res.body;
should.not.exist(res.headers['x-cache-invalidate']);
_.has(res.headers, 'x-cache-invalidate').should.equal(false);
res.should.be.json;
testUtils.API.checkResponseValue(putBody, ['error']);
done();
@ -724,7 +725,7 @@ describe('Post API', function () {
yyyy = today.getFullYear(),
postLink = '/' + yyyy + '/' + mm + '/' + dd + '/' + putBody.posts[0].slug + '/';
res.headers['x-cache-invalidate'].should.eql('/, /page/*, /rss/, /rss/*, /tag/*, ' + postLink);
_.has(res.headers, 'x-cache-invalidate').should.equal(false);
res.should.be.json;
putBody.should.exist;
putBody.posts[0].title.should.eql(changedValue);