🐛 Fixed cache invalidation header race conditions

refs https://linear.app/tryghost/issue/ENG-674/

This ensures that all of our dynamic cache invalidation header logic
is applied on a per-request basis!
This commit is contained in:
Fabien O'Carroll 2024-02-27 16:15:24 -05:00 committed by Fabien 'egg' O'Carroll
parent a177600b30
commit ec697051dc
10 changed files with 35 additions and 35 deletions

View File

@ -87,7 +87,7 @@ module.exports = {
edit: {
headers: {
cacheInvalidate: false
cacheInvalidate: true
},
options: [
'id'
@ -111,15 +111,6 @@ module.exports = {
});
}
// @NOTE: cache invalidation has to be done manually for now
// because the model's wasChanged is not returned from
// the service using in-memory repository layer
// if (model.wasChanged()) {
this.headers.cacheInvalidate = true;
// } else {
// this.headers.cacheInvalidate = false;
// }
return model;
}
},

View File

@ -129,9 +129,7 @@ module.exports = {
}
if (model.wasChanged()) {
this.headers.cacheInvalidate = true;
} else {
this.headers.cacheInvalidate = false;
frame.setHeader('X-Cache-Invalidate', '/*');
}
return model;

View File

@ -122,7 +122,7 @@ module.exports = {
return models.Post.add(frame.data.pages[0], frame.options)
.then((model) => {
if (model.get('status') === 'published') {
this.headers.cacheInvalidate = true;
frame.setHeader('X-Cache-Invalidate', '/*');
}
return model;
@ -166,7 +166,13 @@ module.exports = {
async query(frame) {
const model = await models.Post.edit(frame.data.pages[0], frame.options);
this.headers.cacheInvalidate = postsService.handleCacheInvalidation(model);
const cacheInvalidation = postsService.handleCacheInvalidation(model);
if (cacheInvalidation === true) {
frame.setHeader('X-Cache-Invalidate', '/*');
} else if (cacheInvalidation.value) {
frame.setHeader('X-Cache-Invalidate', cacheInvalidation.value);
}
return model;
}

View File

@ -167,10 +167,8 @@ module.exports = {
query(frame) {
return models.Post.add(frame.data.posts[0], frame.options)
.then((model) => {
if (model.get('status') !== 'published') {
this.headers.cacheInvalidate = false;
} else {
this.headers.cacheInvalidate = true;
if (model.get('status') === 'published') {
frame.setHeader('X-Cache-Invalidate', '/*');
}
return model;
@ -216,7 +214,12 @@ module.exports = {
async query(frame) {
let model = await postsService.editPost(frame, {
eventHandler: (event, dto) => {
this.headers.cacheInvalidate = getCacheHeaderFromEventString(event, dto);
const cacheInvalidate = getCacheHeaderFromEventString(event, dto);
if (cacheInvalidate === true) {
frame.setHeader('X-Cache-Invalidate', '/*');
} else if (cacheInvalidate?.value) {
frame.setHeader('X-Cache-Invalidate', cacheInvalidate.value);
}
}
});

View File

@ -40,8 +40,13 @@ module.exports = {
};
const {scheduledResource, preScheduledResource} = await postSchedulingService.publish(resourceType, frame.options.id, frame.data.force, options);
const cacheInvalidate = postSchedulingService.handleCacheInvalidation(scheduledResource, preScheduledResource);
this.headers.cacheInvalidate = cacheInvalidate;
const cacheInvalidation = postSchedulingService.handleCacheInvalidation(scheduledResource, preScheduledResource);
if (cacheInvalidation === true) {
frame.setHeader('X-Cache-Invalidate', '/*');
} else if (cacheInvalidation.value) {
frame.setHeader('X-Cache-Invalidate', cacheInvalidation.value);
}
const response = {};
response[resourceType] = [scheduledResource];

View File

@ -122,7 +122,7 @@ module.exports = {
edit: {
headers: {
cacheInvalidate: true
cacheInvalidate: false
},
permissions: {
unsafeAttrsObject(frame) {
@ -134,10 +134,8 @@ module.exports = {
let result = await settingsBREADService.edit(frame.data.settings, frame.options, stripeConnectData);
if (_.isEmpty(result)) {
this.headers.cacheInvalidate = false;
} else {
this.headers.cacheInvalidate = true;
if (!_.isEmpty(result)) {
frame.setHeader('X-Cache-Invalidate', '/*');
}
// We need to return all settings here, because we have calculated settings that might change

View File

@ -124,9 +124,7 @@ module.exports = {
}
if (model.wasChanged()) {
this.headers.cacheInvalidate = true;
} else {
this.headers.cacheInvalidate = false;
frame.setHeader('X-Cache-Invalidate', '/*');
}
return model;

View File

@ -91,7 +91,7 @@ module.exports = {
const {theme, themeOverridden} = await themeService.api.installFromGithub(frame.options.ref);
if (themeOverridden) {
this.headers.cacheInvalidate = true;
frame.setHeader('X-Cache-Invalidate', '/*');
}
events.emit('theme.uploaded', {name: theme.name});
@ -125,8 +125,7 @@ module.exports = {
return themeService.api.setFromZip(zip)
.then(({theme, themeOverridden}) => {
if (themeOverridden) {
// CASE: clear cache
this.headers.cacheInvalidate = true;
frame.setHeader('X-Cache-Invalidate', '/*');
}
events.emit('theme.uploaded', {name: theme.name});
return theme;

View File

@ -170,7 +170,9 @@ module.exports = {
}));
}
this.headers.cacheInvalidate = shouldInvalidateCacheAfterChange(model);
if (shouldInvalidateCacheAfterChange(model)) {
frame.setHeader('X-Cache-Invalidate', '/*');
}
return model;
});

View File

@ -55,7 +55,7 @@ class PostSchedulingService {
*
* @param {Object} scheduledResource post or page resource object
* @param {Object} preScheduledResource post or page resource object in state before publishing
* @returns {Boolean|Object}
* @returns {boolean|{value: string}}
*/
handleCacheInvalidation(scheduledResource, preScheduledResource) {
if (