diff --git a/ghost/collections/package.json b/ghost/collections/package.json index 564080d21d..77ffeaf397 100644 --- a/ghost/collections/package.json +++ b/ghost/collections/package.json @@ -28,6 +28,7 @@ "typescript": "5.1.3" }, "dependencies": { + "@tryghost/domain-events": "0.0.0", "@tryghost/errors": "^1.2.25", "@tryghost/in-memory-repository": "0.0.0", "@tryghost/tpl": "^0.1.25", diff --git a/ghost/collections/src/CollectionsService.ts b/ghost/collections/src/CollectionsService.ts index 24a9ae5548..03ce8c5c82 100644 --- a/ghost/collections/src/CollectionsService.ts +++ b/ghost/collections/src/CollectionsService.ts @@ -3,6 +3,7 @@ import {CollectionResourceChangeEvent} from './CollectionResourceChangeEvent'; import {CollectionRepository} from './CollectionRepository'; import tpl from '@tryghost/tpl'; import {MethodNotAllowedError, NotFoundError} from '@tryghost/errors'; +import DomainEvents from '@tryghost/domain-events'; const messages = { cannotDeleteBuiltInCollectionError: { @@ -132,6 +133,15 @@ export class CollectionsService { return mappedDTO; } + /** + * @description Subscribes to Domain events to update collections when posts are added, updated or deleted + */ + subscribeToEvents() { + DomainEvents.subscribe(CollectionResourceChangeEvent, async (event: CollectionResourceChangeEvent) => { + await this.updateCollections(event); + }); + } + async createCollection(data: CollectionInputDTO): Promise { const collection = await Collection.create({ title: data.title, diff --git a/ghost/collections/src/libraries.d.ts b/ghost/collections/src/libraries.d.ts index 0c51d8d42e..a0bfbbc86e 100644 --- a/ghost/collections/src/libraries.d.ts +++ b/ghost/collections/src/libraries.d.ts @@ -1,2 +1,3 @@ declare module '@tryghost/errors'; declare module '@tryghost/tpl'; +declare module '@tryghost/domain-events' diff --git a/ghost/collections/test/collections.test.ts b/ghost/collections/test/collections.test.ts index 4a702bbd6d..7c65e22530 100644 --- a/ghost/collections/test/collections.test.ts +++ b/ghost/collections/test/collections.test.ts @@ -1,4 +1,6 @@ -import assert from 'assert'; +import assert from 'assert/strict'; +import sinon from 'sinon'; +import DomainEvents from '@tryghost/domain-events'; import { CollectionsService, CollectionsRepositoryInMemory, @@ -259,6 +261,24 @@ describe('CollectionsService', function () { }); }); + describe('subscribeToEvents', function () { + it('Subscribes to Domain Events', function () { + const updateCollectionsSpy = sinon.spy(collectionsService, 'updateCollections'); + const collectionChangeEvent = CollectionResourceChangeEvent.create('post.added', { + id: 'test-id', + resource: 'post' + }); + + DomainEvents.dispatch(collectionChangeEvent); + assert.equal(updateCollectionsSpy.calledOnce, false, 'updateCollections should not be called yet'); + + collectionsService.subscribeToEvents(); + + DomainEvents.dispatch(collectionChangeEvent); + assert.equal(updateCollectionsSpy.calledOnce, true, 'updateCollections should be called'); + }); + }); + describe('Automatic Collections', function () { it('Can create an automatic collection', async function () { const collection = await collectionsService.createCollection({ diff --git a/ghost/core/core/server/services/collections/index.js b/ghost/core/core/server/services/collections/index.js index 07e6eeaa21..54c5dc2f37 100644 --- a/ghost/core/core/server/services/collections/index.js +++ b/ghost/core/core/server/services/collections/index.js @@ -1,7 +1,6 @@ const { CollectionsService, - CollectionsRepositoryInMemory, - CollectionResourceChangeEvent + CollectionsRepositoryInMemory } = require('@tryghost/collections'); const labs = require('../../../shared/labs'); @@ -26,8 +25,7 @@ class CollectionsServiceWrapper { return; } - const events = require('../../lib/common/events'); - + const translateModelEventsToDomainEvents = require('./model-to-domain-events-bridge'); const existingBuiltins = await this.api.getAll({filter: 'slug:featured'}); if (!existingBuiltins.data.length) { @@ -50,24 +48,8 @@ class CollectionsServiceWrapper { }); } - const ghostModelUpdateEvents = require('./update-events'); - - const collectionListener = (event, data) => { - const change = Object.assign({}, { - id: data.id, - resource: event.split('.')[0] - }, data._changed); - const collectionResourceChangeEvent = CollectionResourceChangeEvent.create(event, change); - // @NOTE: to avoid race conditions we need a queue here to make sure updates happen - // one by one and not in parallel - this.api.updateCollections(collectionResourceChangeEvent); - }; - - for (const event of ghostModelUpdateEvents) { - if (!events.hasRegisteredListener(event, 'collectionListener')) { - events.on(event, data => collectionListener(event, data)); - } - } + this.api.subscribeToEvents(); + translateModelEventsToDomainEvents(); } } diff --git a/ghost/core/core/server/services/collections/model-to-domain-events-bridge.js b/ghost/core/core/server/services/collections/model-to-domain-events-bridge.js new file mode 100644 index 0000000000..6d24221f54 --- /dev/null +++ b/ghost/core/core/server/services/collections/model-to-domain-events-bridge.js @@ -0,0 +1,41 @@ +const DomainEvents = require('@tryghost/domain-events'); +const { + CollectionResourceChangeEvent +} = require('@tryghost/collections'); + +const domainEventDispatcher = (modelEventName, data) => { + const change = Object.assign({}, { + id: data.id, + resource: modelEventName.split('.')[0] + }, data._changed); + const collectionResourceChangeEvent = CollectionResourceChangeEvent.create(modelEventName, change); + + DomainEvents.dispatch(collectionResourceChangeEvent); +}; + +const translateModelEventsToDomainEvents = () => { + const events = require('../../lib/common/events'); + const ghostModelUpdateEvents = [ + 'post.published', + 'post.published.edited', + 'post.unpublished', + 'tag.added', + 'tag.edited', + 'tag.attached', + 'tag.detached', + 'tag.deleted', + 'user.activated', + 'user.activated.edited', + 'user.attached', + 'user.detached', + 'user.deleted' + ]; + + for (const modelEvent of ghostModelUpdateEvents) { + if (!events.hasRegisteredListener(modelEvent, 'collectionListener')) { + events.on(modelEvent, data => domainEventDispatcher(modelEvent, data)); + } + } +}; + +module.exports = translateModelEventsToDomainEvents; diff --git a/ghost/core/core/server/services/collections/update-events.js b/ghost/core/core/server/services/collections/update-events.js deleted file mode 100644 index f0d66869c6..0000000000 --- a/ghost/core/core/server/services/collections/update-events.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = [ - 'post.published', - 'post.published.edited', - 'post.unpublished', - 'tag.added', - 'tag.edited', - 'tag.attached', - 'tag.detached', - 'tag.deleted', - 'user.activated', - 'user.activated.edited', - 'user.attached', - 'user.detached', - 'user.deleted' -];