mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -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:
parent
a8e5cbcc3d
commit
5dd6159ac6
8 changed files with 140 additions and 7 deletions
|
@ -21,8 +21,8 @@
|
||||||
"build"
|
"build"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"c8": "7.13.0",
|
|
||||||
"@tryghost/domain-events": "0.0.0",
|
"@tryghost/domain-events": "0.0.0",
|
||||||
|
"c8": "7.13.0",
|
||||||
"mocha": "10.2.0",
|
"mocha": "10.2.0",
|
||||||
"sinon": "15.0.4",
|
"sinon": "15.0.4",
|
||||||
"ts-node": "10.9.1",
|
"ts-node": "10.9.1",
|
||||||
|
@ -31,6 +31,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tryghost/errors": "^1.2.25",
|
"@tryghost/errors": "^1.2.25",
|
||||||
"@tryghost/in-memory-repository": "0.0.0",
|
"@tryghost/in-memory-repository": "0.0.0",
|
||||||
|
"@tryghost/logging": "^2.4.5",
|
||||||
"@tryghost/nql": "^0.11.0",
|
"@tryghost/nql": "^0.11.0",
|
||||||
"@tryghost/tpl": "^0.1.25",
|
"@tryghost/tpl": "^0.1.25",
|
||||||
"bson-objectid": "^2.0.4"
|
"bson-objectid": "^2.0.4"
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
import logging from '@tryghost/logging';
|
||||||
|
import tpl from '@tryghost/tpl';
|
||||||
import {Collection} from './Collection';
|
import {Collection} from './Collection';
|
||||||
import {CollectionResourceChangeEvent} from './CollectionResourceChangeEvent';
|
import {CollectionResourceChangeEvent} from './CollectionResourceChangeEvent';
|
||||||
import {CollectionRepository} from './CollectionRepository';
|
import {CollectionRepository} from './CollectionRepository';
|
||||||
import tpl from '@tryghost/tpl';
|
|
||||||
import {MethodNotAllowedError, NotFoundError} from '@tryghost/errors';
|
import {MethodNotAllowedError, NotFoundError} from '@tryghost/errors';
|
||||||
import {PostDeletedEvent} from './events/PostDeletedEvent';
|
import {PostDeletedEvent} from './events/PostDeletedEvent';
|
||||||
import {PostAddedEvent} from './events/PostAddedEvent';
|
import {PostAddedEvent} from './events/PostAddedEvent';
|
||||||
|
import {PostEditedEvent} from './events/PostEditedEvent';
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
cannotDeleteBuiltInCollectionError: {
|
cannotDeleteBuiltInCollectionError: {
|
||||||
|
@ -153,6 +155,10 @@ export class CollectionsService {
|
||||||
this.DomainEvents.subscribe(PostAddedEvent, async (event: PostAddedEvent) => {
|
this.DomainEvents.subscribe(PostAddedEvent, async (event: PostAddedEvent) => {
|
||||||
await this.addPostToMatchingCollections(event.data);
|
await this.addPostToMatchingCollections(event.data);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.DomainEvents.subscribe(PostEditedEvent, async (event: PostEditedEvent) => {
|
||||||
|
await this.updatePostInMatchingCollections(event.data);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createCollection(data: CollectionInputDTO): Promise<CollectionDTO> {
|
async createCollection(data: CollectionInputDTO): Promise<CollectionDTO> {
|
||||||
|
@ -228,7 +234,6 @@ export class CollectionsService {
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const collection of collections) {
|
for (const collection of collections) {
|
||||||
await collection.addPost(post);
|
|
||||||
const added = await collection.addPost(post);
|
const added = await collection.addPost(post);
|
||||||
|
|
||||||
if (added) {
|
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> {
|
async edit(data: any): Promise<CollectionDTO | null> {
|
||||||
const collection = await this.collectionsRepository.getById(data.id);
|
const collection = await this.collectionsRepository.getById(data.id);
|
||||||
|
|
||||||
|
|
31
ghost/collections/src/events/PostEditedEvent.ts
Normal file
31
ghost/collections/src/events/PostEditedEvent.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,3 +4,4 @@ export * from './Collection';
|
||||||
export * from './CollectionResourceChangeEvent';
|
export * from './CollectionResourceChangeEvent';
|
||||||
export * from './events/PostDeletedEvent';
|
export * from './events/PostDeletedEvent';
|
||||||
export * from './events/PostAddedEvent';
|
export * from './events/PostAddedEvent';
|
||||||
|
export * from './events/PostEditedEvent';
|
||||||
|
|
3
ghost/collections/src/libraries.d.ts
vendored
3
ghost/collections/src/libraries.d.ts
vendored
|
@ -1,4 +1,5 @@
|
||||||
declare module '@tryghost/errors';
|
declare module '@tryghost/errors';
|
||||||
declare module '@tryghost/tpl';
|
|
||||||
declare module '@tryghost/domain-events'
|
declare module '@tryghost/domain-events'
|
||||||
|
declare module '@tryghost/logging'
|
||||||
declare module '@tryghost/nql'
|
declare module '@tryghost/nql'
|
||||||
|
declare module '@tryghost/tpl';
|
||||||
|
|
|
@ -266,7 +266,8 @@ describe('Collection', function () {
|
||||||
it('Can match a post with a filter', async function () {
|
it('Can match a post with a filter', async function () {
|
||||||
const collection = await Collection.create({
|
const collection = await Collection.create({
|
||||||
title: 'Testing filtering posts',
|
title: 'Testing filtering posts',
|
||||||
type: 'automatic'
|
type: 'automatic',
|
||||||
|
filter: 'featured:true'
|
||||||
});
|
});
|
||||||
|
|
||||||
const featuredPost = {
|
const featuredPost = {
|
||||||
|
|
|
@ -6,7 +6,8 @@ import {
|
||||||
CollectionsRepositoryInMemory,
|
CollectionsRepositoryInMemory,
|
||||||
CollectionResourceChangeEvent,
|
CollectionResourceChangeEvent,
|
||||||
PostDeletedEvent,
|
PostDeletedEvent,
|
||||||
PostAddedEvent
|
PostAddedEvent,
|
||||||
|
PostEditedEvent
|
||||||
} from '../src/index';
|
} from '../src/index';
|
||||||
import {PostsRepositoryInMemory} from './fixtures/PostsRepositoryInMemory';
|
import {PostsRepositoryInMemory} from './fixtures/PostsRepositoryInMemory';
|
||||||
import {posts} from './fixtures/posts';
|
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(automaticNonFeaturedCollection.id))?.posts.length, 2);
|
||||||
assert.equal((await collectionsService.getById(manualCollection.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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,8 @@ const DomainEvents = require('@tryghost/domain-events');
|
||||||
const {
|
const {
|
||||||
CollectionResourceChangeEvent,
|
CollectionResourceChangeEvent,
|
||||||
PostDeletedEvent,
|
PostDeletedEvent,
|
||||||
PostAddedEvent
|
PostAddedEvent,
|
||||||
|
PostEditedEvent
|
||||||
} = require('@tryghost/collections');
|
} = require('@tryghost/collections');
|
||||||
|
|
||||||
const domainEventDispatcher = (modelEventName, data) => {
|
const domainEventDispatcher = (modelEventName, data) => {
|
||||||
|
@ -18,8 +19,23 @@ const domainEventDispatcher = (modelEventName, data) => {
|
||||||
event = PostAddedEvent.create({
|
event = PostAddedEvent.create({
|
||||||
id: data.id,
|
id: data.id,
|
||||||
featured: data.featured,
|
featured: data.featured,
|
||||||
|
status: data.attributes.status,
|
||||||
published_at: data.published_at
|
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 {
|
} else {
|
||||||
event = CollectionResourceChangeEvent.create(modelEventName, change);
|
event = CollectionResourceChangeEvent.create(modelEventName, change);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue