diff --git a/ghost/collections/src/CollectionsService.ts b/ghost/collections/src/CollectionsService.ts index 56508cde4a..a8dcbbbd7b 100644 --- a/ghost/collections/src/CollectionsService.ts +++ b/ghost/collections/src/CollectionsService.ts @@ -7,6 +7,7 @@ import {MethodNotAllowedError} from '@tryghost/errors'; import {PostDeletedEvent} from './events/PostDeletedEvent'; import {PostAddedEvent} from './events/PostAddedEvent'; import {PostEditedEvent} from './events/PostEditedEvent'; +import {PostsBulkDestroyedEvent} from '@tryghost/post-events'; import {RepositoryUniqueChecker} from './RepositoryUniqueChecker'; import {TagDeletedEvent} from './events/TagDeletedEvent'; @@ -178,6 +179,11 @@ export class CollectionsService { await this.updatePostInMatchingCollections(event.data); }); + this.DomainEvents.subscribe(PostsBulkDestroyedEvent, async (event: PostsBulkDestroyedEvent) => { + logging.info(`BulkDestroyEvent received, removing posts ${event.data} from all collections`); + await this.removePostsFromAllCollections(event.data); + }); + this.DomainEvents.subscribe(TagDeletedEvent, async (event: TagDeletedEvent) => { logging.info(`TagDeletedEvent received for ${event.data.id}, updating all collections`); await this.updateAllAutomaticCollections(); @@ -269,6 +275,21 @@ export class CollectionsService { }); } + private async removePostsFromAllCollections(postIds: string[]) { + return await this.collectionsRepository.createTransaction(async (transaction) => { + const collections = await this.collectionsRepository.getAll({transaction}); + + for (const collection of collections) { + for (const postId of postIds) { + if (collection.includesPost(postId)) { + collection.removePost(postId); + } + } + await this.collectionsRepository.save(collection, {transaction}); + } + }); + } + private async addPostToMatchingCollections(post: CollectionPost) { return await this.collectionsRepository.createTransaction(async (transaction) => { const collections = await this.collectionsRepository.getAll({ diff --git a/ghost/collections/test/collections.test.ts b/ghost/collections/test/collections.test.ts index 475092405c..8078ad34a8 100644 --- a/ghost/collections/test/collections.test.ts +++ b/ghost/collections/test/collections.test.ts @@ -8,6 +8,7 @@ import { PostEditedEvent, TagDeletedEvent } from '../src/index'; +import {PostsBulkDestroyedEvent} from '@tryghost/post-events'; import {PostsRepositoryInMemory} from './fixtures/PostsRepositoryInMemory'; import {posts as postFixtures} from './fixtures/posts'; import {CollectionPost} from '../src/CollectionPost'; @@ -403,6 +404,25 @@ describe('CollectionsService', function () { assert.equal((await collectionsService.getById(manualCollection.id))?.posts.length, 1); }); + it('Updates all collections when posts are deleted in bulk', async function () { + assert.equal((await collectionsService.getById(automaticFeaturedCollection.id))?.posts?.length, 2); + assert.equal((await collectionsService.getById(automaticNonFeaturedCollection.id))?.posts.length, 2); + assert.equal((await collectionsService.getById(manualCollection.id))?.posts.length, 2); + + collectionsService.subscribeToEvents(); + const postDeletedEvent = PostsBulkDestroyedEvent.create([ + posts[0].id, + posts[1].id + ]); + + DomainEvents.dispatch(postDeletedEvent); + await DomainEvents.allSettled(); + + assert.equal((await collectionsService.getById(automaticFeaturedCollection.id))?.posts?.length, 2); + assert.equal((await collectionsService.getById(automaticNonFeaturedCollection.id))?.posts.length, 0); + assert.equal((await collectionsService.getById(manualCollection.id))?.posts.length, 0); + }); + it('Updates only index collection when a non-featured post is added', async function () { assert.equal((await collectionsService.getById(automaticFeaturedCollection.id))?.posts?.length, 2); assert.equal((await collectionsService.getById(automaticNonFeaturedCollection.id))?.posts.length, 2); diff --git a/ghost/posts-service/lib/PostsService.js b/ghost/posts-service/lib/PostsService.js index 0d72d6c44d..da49228caf 100644 --- a/ghost/posts-service/lib/PostsService.js +++ b/ghost/posts-service/lib/PostsService.js @@ -4,6 +4,8 @@ const tpl = require('@tryghost/tpl'); const errors = require('@tryghost/errors'); const ObjectId = require('bson-objectid').default; const pick = require('lodash/pick'); +const DomainEvents = require('@tryghost/domain-events/lib/DomainEvents'); +const {PostsBulkDestroyedEvent} = require('@tryghost/post-events'); const messages = { invalidVisibilityFilter: 'Invalid visibility filter.', @@ -385,7 +387,12 @@ class PostsService { // Posts and emails await this.models.Post.bulkDestroy(deleteEmailIds, 'emails', {transacting: options.transacting, throwErrors: true}); - return await this.models.Post.bulkDestroy(deleteIds, 'posts', {...options, throwErrors: true}); + const result = await this.models.Post.bulkDestroy(deleteIds, 'posts', {...options, throwErrors: true}); + + const event = PostsBulkDestroyedEvent.create(deleteIds); + DomainEvents.dispatch(event); + + return result; } async export(frame) {