Update of collection posts on bulk destroy

refs https://github.com/TryGhost/Arch/issues/16

- When the bulk destroy is done on posts Collections need to know about the update and remove the stored posts from collections.
This commit is contained in:
Naz 2023-07-25 20:22:32 +08:00 committed by naz
parent ffa101b550
commit 9b257f3966
3 changed files with 49 additions and 1 deletions

View File

@ -7,6 +7,7 @@ import {MethodNotAllowedError} 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'; import {PostEditedEvent} from './events/PostEditedEvent';
import {PostsBulkDestroyedEvent} from '@tryghost/post-events';
import {RepositoryUniqueChecker} from './RepositoryUniqueChecker'; import {RepositoryUniqueChecker} from './RepositoryUniqueChecker';
import {TagDeletedEvent} from './events/TagDeletedEvent'; import {TagDeletedEvent} from './events/TagDeletedEvent';
@ -178,6 +179,11 @@ export class CollectionsService {
await this.updatePostInMatchingCollections(event.data); 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) => { this.DomainEvents.subscribe(TagDeletedEvent, async (event: TagDeletedEvent) => {
logging.info(`TagDeletedEvent received for ${event.data.id}, updating all collections`); logging.info(`TagDeletedEvent received for ${event.data.id}, updating all collections`);
await this.updateAllAutomaticCollections(); 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) { private async addPostToMatchingCollections(post: CollectionPost) {
return await this.collectionsRepository.createTransaction(async (transaction) => { return await this.collectionsRepository.createTransaction(async (transaction) => {
const collections = await this.collectionsRepository.getAll({ const collections = await this.collectionsRepository.getAll({

View File

@ -8,6 +8,7 @@ import {
PostEditedEvent, PostEditedEvent,
TagDeletedEvent TagDeletedEvent
} from '../src/index'; } from '../src/index';
import {PostsBulkDestroyedEvent} from '@tryghost/post-events';
import {PostsRepositoryInMemory} from './fixtures/PostsRepositoryInMemory'; import {PostsRepositoryInMemory} from './fixtures/PostsRepositoryInMemory';
import {posts as postFixtures} from './fixtures/posts'; import {posts as postFixtures} from './fixtures/posts';
import {CollectionPost} from '../src/CollectionPost'; import {CollectionPost} from '../src/CollectionPost';
@ -403,6 +404,25 @@ describe('CollectionsService', function () {
assert.equal((await collectionsService.getById(manualCollection.id))?.posts.length, 1); 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 () { 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(automaticFeaturedCollection.id))?.posts?.length, 2);
assert.equal((await collectionsService.getById(automaticNonFeaturedCollection.id))?.posts.length, 2); assert.equal((await collectionsService.getById(automaticNonFeaturedCollection.id))?.posts.length, 2);

View File

@ -4,6 +4,8 @@ const tpl = require('@tryghost/tpl');
const errors = require('@tryghost/errors'); const errors = require('@tryghost/errors');
const ObjectId = require('bson-objectid').default; const ObjectId = require('bson-objectid').default;
const pick = require('lodash/pick'); const pick = require('lodash/pick');
const DomainEvents = require('@tryghost/domain-events/lib/DomainEvents');
const {PostsBulkDestroyedEvent} = require('@tryghost/post-events');
const messages = { const messages = {
invalidVisibilityFilter: 'Invalid visibility filter.', invalidVisibilityFilter: 'Invalid visibility filter.',
@ -385,7 +387,12 @@ class PostsService {
// Posts and emails // Posts and emails
await this.models.Post.bulkDestroy(deleteEmailIds, 'emails', {transacting: options.transacting, throwErrors: true}); 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) { async export(frame) {