0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Added handling for 'post.edited' Ghost model event

refs https://github.com/TryGhost/Team/issues/3169

- Adds optomized collection update handling for when post.edited model event is emitted.
This commit is contained in:
Naz 2023-06-26 16:29:32 +07:00 committed by naz
parent a8e5cbcc3d
commit 5dd6159ac6
8 changed files with 140 additions and 7 deletions

View file

@ -21,8 +21,8 @@
"build"
],
"devDependencies": {
"c8": "7.13.0",
"@tryghost/domain-events": "0.0.0",
"c8": "7.13.0",
"mocha": "10.2.0",
"sinon": "15.0.4",
"ts-node": "10.9.1",
@ -31,6 +31,7 @@
"dependencies": {
"@tryghost/errors": "^1.2.25",
"@tryghost/in-memory-repository": "0.0.0",
"@tryghost/logging": "^2.4.5",
"@tryghost/nql": "^0.11.0",
"@tryghost/tpl": "^0.1.25",
"bson-objectid": "^2.0.4"

View file

@ -1,10 +1,12 @@
import logging from '@tryghost/logging';
import tpl from '@tryghost/tpl';
import {Collection} from './Collection';
import {CollectionResourceChangeEvent} from './CollectionResourceChangeEvent';
import {CollectionRepository} from './CollectionRepository';
import tpl from '@tryghost/tpl';
import {MethodNotAllowedError, NotFoundError} from '@tryghost/errors';
import {PostDeletedEvent} from './events/PostDeletedEvent';
import {PostAddedEvent} from './events/PostAddedEvent';
import {PostEditedEvent} from './events/PostEditedEvent';
const messages = {
cannotDeleteBuiltInCollectionError: {
@ -153,6 +155,10 @@ export class CollectionsService {
this.DomainEvents.subscribe(PostAddedEvent, async (event: PostAddedEvent) => {
await this.addPostToMatchingCollections(event.data);
});
this.DomainEvents.subscribe(PostEditedEvent, async (event: PostEditedEvent) => {
await this.updatePostInMatchingCollections(event.data);
});
}
async createCollection(data: CollectionInputDTO): Promise<CollectionDTO> {
@ -228,7 +234,6 @@ export class CollectionsService {
});
for (const collection of collections) {
await collection.addPost(post);
const added = await collection.addPost(post);
if (added) {
@ -252,6 +257,31 @@ export class CollectionsService {
}
}
async updatePostInMatchingCollections(postEdit: PostEditedEvent['data']) {
const collections = await this.collectionsRepository.getAll({
filter: 'type:automatic'
});
for (const collection of collections) {
if (collection.includesPost(postEdit.id) && !collection.postMatchesFilter(postEdit.current)) {
await collection.removePost(postEdit.id);
await this.collectionsRepository.save(collection);
logging.info(`[Collections] Post ${postEdit.id} was updated and removed from collection ${collection.id} with filter ${collection.filter}`);
} else if (!collection.includesPost(postEdit.id) && collection.postMatchesFilter(postEdit.current)) {
const added = await collection.addPost(postEdit.current);
if (added) {
await this.collectionsRepository.save(collection);
}
logging.info(`[Collections] Post ${postEdit.id} was updated and added to collection ${collection.id} with filter ${collection.filter}`);
} else {
logging.info(`[Collections] Post ${postEdit.id} was updated but did not update any collections`);
}
}
}
async edit(data: any): Promise<CollectionDTO | null> {
const collection = await this.collectionsRepository.getById(data.id);

View file

@ -0,0 +1,31 @@
type PostEditData = {
id: string;
current: {
id: string;
title: string;
featured: boolean;
published_at: Date;
},
previous: {
id: string;
title: string;
featured: boolean;
published_at: Date;
}
};
export class PostEditedEvent {
id: string;
data: PostEditData;
timestamp: Date;
constructor(data: any, timestamp: Date) {
this.id = data.id;
this.data = data;
this.timestamp = timestamp;
}
static create(data: any, timestamp = new Date()) {
return new PostEditedEvent(data, timestamp);
}
}

View file

@ -4,3 +4,4 @@ export * from './Collection';
export * from './CollectionResourceChangeEvent';
export * from './events/PostDeletedEvent';
export * from './events/PostAddedEvent';
export * from './events/PostEditedEvent';

View file

@ -1,4 +1,5 @@
declare module '@tryghost/errors';
declare module '@tryghost/tpl';
declare module '@tryghost/domain-events'
declare module '@tryghost/logging'
declare module '@tryghost/nql'
declare module '@tryghost/tpl';

View file

@ -266,7 +266,8 @@ describe('Collection', function () {
it('Can match a post with a filter', async function () {
const collection = await Collection.create({
title: 'Testing filtering posts',
type: 'automatic'
type: 'automatic',
filter: 'featured:true'
});
const featuredPost = {

View file

@ -6,7 +6,8 @@ import {
CollectionsRepositoryInMemory,
CollectionResourceChangeEvent,
PostDeletedEvent,
PostAddedEvent
PostAddedEvent,
PostEditedEvent
} from '../src/index';
import {PostsRepositoryInMemory} from './fixtures/PostsRepositoryInMemory';
import {posts} from './fixtures/posts';
@ -417,6 +418,57 @@ describe('CollectionsService', function () {
assert.equal((await collectionsService.getById(automaticNonFeaturedCollection.id))?.posts.length, 2);
assert.equal((await collectionsService.getById(manualCollection.id))?.posts.length, 2);
});
it('Moves post form featured to non featured collection when the featured attribute is changed', async function () {
collectionsService.subscribeToEvents();
const newFeaturedPost = {
id: 'post-featured',
title: 'Post Featured',
slug: 'post-featured',
featured: false,
published_at: new Date('2023-03-16T07:19:07.447Z'),
deleted: false
};
await postsRepository.save(newFeaturedPost);
const updateCollectionEvent = PostEditedEvent.create({
id: newFeaturedPost.id,
current: {
id: newFeaturedPost.id,
featured: false
},
previous: {
id: newFeaturedPost.id,
featured: true
}
});
DomainEvents.dispatch(updateCollectionEvent);
await DomainEvents.allSettled();
assert.equal((await collectionsService.getById(automaticFeaturedCollection.id))?.posts?.length, 2);
assert.equal((await collectionsService.getById(automaticNonFeaturedCollection.id))?.posts.length, 3);
assert.equal((await collectionsService.getById(manualCollection.id))?.posts.length, 2);
// change featured back to true
const updateCollectionEventBackToFeatured = PostEditedEvent.create({
id: newFeaturedPost.id,
current: {
id: newFeaturedPost.id,
featured: true
},
previous: {
id: newFeaturedPost.id,
featured: false
}
});
DomainEvents.dispatch(updateCollectionEventBackToFeatured);
await DomainEvents.allSettled();
assert.equal((await collectionsService.getById(automaticFeaturedCollection.id))?.posts?.length, 3);
assert.equal((await collectionsService.getById(automaticNonFeaturedCollection.id))?.posts.length, 2);
assert.equal((await collectionsService.getById(manualCollection.id))?.posts.length, 2);
});
});
});
});

View file

@ -2,7 +2,8 @@ const DomainEvents = require('@tryghost/domain-events');
const {
CollectionResourceChangeEvent,
PostDeletedEvent,
PostAddedEvent
PostAddedEvent,
PostEditedEvent
} = require('@tryghost/collections');
const domainEventDispatcher = (modelEventName, data) => {
@ -18,8 +19,23 @@ const domainEventDispatcher = (modelEventName, data) => {
event = PostAddedEvent.create({
id: data.id,
featured: data.featured,
status: data.attributes.status,
published_at: data.published_at
});
} if (modelEventName === 'post.edited') {
event = PostEditedEvent.create({
id: data.id,
current: {
title: data.attributes.title,
status: data.attributes.status,
featured: data.attributes.featured,
published_at: data.attributes.published_at
},
// @NOTE: this will need to represent the previous state of the post
// will be needed to optimize the query for the collection
previous: {
}
});
} else {
event = CollectionResourceChangeEvent.create(modelEventName, change);
}