From 191c9faa642f02ad06a0d8e1df9511e7dbb68e3b Mon Sep 17 00:00:00 2001 From: Wesley Finck Date: Fri, 23 Jan 2026 15:47:35 -0800 Subject: [PATCH] finish handling card removed from collection domain event implementation --- .../RemoveCardFromCollectionUseCase.ts | 38 +++++++++++++------ .../RemoveCardFromCollectionUseCase.test.ts | 5 +++ .../events/BullMQEventPublisher.ts | 2 + .../infrastructure/events/EventMapper.ts | 36 ++++++++++++++++++ .../http/factories/UseCaseFactory.ts | 1 + 5 files changed, 70 insertions(+), 12 deletions(-) diff --git a/src/modules/cards/application/useCases/commands/RemoveCardFromCollectionUseCase.ts b/src/modules/cards/application/useCases/commands/RemoveCardFromCollectionUseCase.ts index c41fb3c8..9aada0ab 100644 --- a/src/modules/cards/application/useCases/commands/RemoveCardFromCollectionUseCase.ts +++ b/src/modules/cards/application/useCases/commands/RemoveCardFromCollectionUseCase.ts @@ -1,5 +1,5 @@ import { Result, ok, err } from '../../../../../shared/core/Result'; -import { UseCase } from '../../../../../shared/core/UseCase'; +import { BaseUseCase } from '../../../../../shared/core/UseCase'; import { UseCaseError } from '../../../../../shared/core/UseCaseError'; import { AppError } from '../../../../../shared/core/AppError'; import { ICardRepository } from '../../../domain/ICardRepository'; @@ -8,6 +8,7 @@ import { CollectionId } from '../../../domain/value-objects/CollectionId'; import { CuratorId } from '../../../domain/value-objects/CuratorId'; import { CardCollectionService } from '../../../domain/services/CardCollectionService'; import { AuthenticationError } from '../../../../../shared/core/AuthenticationError'; +import { IEventPublisher } from '../../../../../shared/application/events/IEventPublisher'; export interface RemoveCardFromCollectionDTO { cardId: string; @@ -25,20 +26,20 @@ export class ValidationError extends UseCaseError { } } -export class RemoveCardFromCollectionUseCase - implements - UseCase< - RemoveCardFromCollectionDTO, - Result< - RemoveCardFromCollectionResponseDTO, - ValidationError | AuthenticationError | AppError.UnexpectedError - > - > -{ +export class RemoveCardFromCollectionUseCase extends BaseUseCase< + RemoveCardFromCollectionDTO, + Result< + RemoveCardFromCollectionResponseDTO, + ValidationError | AuthenticationError | AppError.UnexpectedError + > +> { constructor( private cardRepository: ICardRepository, private cardCollectionService: CardCollectionService, - ) {} + eventPublisher: IEventPublisher, + ) { + super(eventPublisher); + } async execute( request: RemoveCardFromCollectionDTO, @@ -117,6 +118,19 @@ export class RemoveCardFromCollectionUseCase ); } + // Publish events for each updated collection + const updatedCollections = removeFromCollectionsResult.value; + for (const collection of updatedCollections) { + const publishResult = await this.publishEventsForAggregate(collection); + if (publishResult.isErr()) { + console.error( + 'Failed to publish events for collection:', + publishResult.error, + ); + // Don't fail the operation if event publishing fails + } + } + return ok({ cardId: card.cardId.getStringValue(), }); diff --git a/src/modules/cards/tests/application/RemoveCardFromCollectionUseCase.test.ts b/src/modules/cards/tests/application/RemoveCardFromCollectionUseCase.test.ts index 555f0f1b..9c44d43d 100644 --- a/src/modules/cards/tests/application/RemoveCardFromCollectionUseCase.test.ts +++ b/src/modules/cards/tests/application/RemoveCardFromCollectionUseCase.test.ts @@ -2,6 +2,7 @@ import { RemoveCardFromCollectionUseCase } from '../../application/useCases/comm import { InMemoryCardRepository } from '../utils/InMemoryCardRepository'; import { InMemoryCollectionRepository } from '../utils/InMemoryCollectionRepository'; import { FakeCollectionPublisher } from '../utils/FakeCollectionPublisher'; +import { FakeEventPublisher } from '../utils/FakeEventPublisher'; import { CardCollectionService } from '../../domain/services/CardCollectionService'; import { CuratorId } from '../../domain/value-objects/CuratorId'; import { CardBuilder } from '../utils/builders/CardBuilder'; @@ -13,6 +14,7 @@ describe('RemoveCardFromCollectionUseCase', () => { let cardRepository: InMemoryCardRepository; let collectionRepository: InMemoryCollectionRepository; let collectionPublisher: FakeCollectionPublisher; + let eventPublisher: FakeEventPublisher; let cardCollectionService: CardCollectionService; let curatorId: CuratorId; let otherCuratorId: CuratorId; @@ -21,6 +23,7 @@ describe('RemoveCardFromCollectionUseCase', () => { cardRepository = InMemoryCardRepository.getInstance(); collectionRepository = InMemoryCollectionRepository.getInstance(); collectionPublisher = new FakeCollectionPublisher(); + eventPublisher = new FakeEventPublisher(); cardCollectionService = new CardCollectionService( collectionRepository, collectionPublisher, @@ -30,6 +33,7 @@ describe('RemoveCardFromCollectionUseCase', () => { useCase = new RemoveCardFromCollectionUseCase( cardRepository, cardCollectionService, + eventPublisher, ); curatorId = CuratorId.create('did:plc:testcurator').unwrap(); @@ -40,6 +44,7 @@ describe('RemoveCardFromCollectionUseCase', () => { cardRepository.clear(); collectionRepository.clear(); collectionPublisher.clear(); + eventPublisher.clear(); }); const createCard = async (type: CardTypeEnum = CardTypeEnum.URL) => { diff --git a/src/shared/infrastructure/events/BullMQEventPublisher.ts b/src/shared/infrastructure/events/BullMQEventPublisher.ts index fde9cc28..97f0e6b5 100644 --- a/src/shared/infrastructure/events/BullMQEventPublisher.ts +++ b/src/shared/infrastructure/events/BullMQEventPublisher.ts @@ -58,6 +58,8 @@ export class BullMQEventPublisher implements IEventPublisher { return [QueueNames.FEEDS, QueueNames.NOTIFICATIONS]; case EventNames.CARD_REMOVED_FROM_LIBRARY: return [QueueNames.NOTIFICATIONS]; + case EventNames.CARD_REMOVED_FROM_COLLECTION: + return [QueueNames.NOTIFICATIONS]; default: return [QueueNames.FEEDS]; } diff --git a/src/shared/infrastructure/events/EventMapper.ts b/src/shared/infrastructure/events/EventMapper.ts index 4b3e4a56..387e5e77 100644 --- a/src/shared/infrastructure/events/EventMapper.ts +++ b/src/shared/infrastructure/events/EventMapper.ts @@ -2,6 +2,7 @@ import { IDomainEvent } from '../../domain/events/IDomainEvent'; import { CardAddedToLibraryEvent } from '../../../modules/cards/domain/events/CardAddedToLibraryEvent'; import { CardAddedToCollectionEvent } from '../../../modules/cards/domain/events/CardAddedToCollectionEvent'; import { CardRemovedFromLibraryEvent } from '../../../modules/cards/domain/events/CardRemovedFromLibraryEvent'; +import { CardRemovedFromCollectionEvent } from '../../../modules/cards/domain/events/CardRemovedFromCollectionEvent'; import { CollectionCreatedEvent } from '../../../modules/cards/domain/events/CollectionCreatedEvent'; import { CardId } from '../../../modules/cards/domain/value-objects/CardId'; import { CollectionId } from '../../../modules/cards/domain/value-objects/CollectionId'; @@ -33,6 +34,14 @@ export interface SerializedCardRemovedFromLibraryEvent extends SerializedEvent { curatorId: string; } +export interface SerializedCardRemovedFromCollectionEvent + extends SerializedEvent { + eventType: typeof EventNames.CARD_REMOVED_FROM_COLLECTION; + cardId: string; + collectionId: string; + removedBy: string; +} + export interface SerializedCollectionCreatedEvent extends SerializedEvent { eventType: typeof EventNames.COLLECTION_CREATED; collectionId: string; @@ -44,6 +53,7 @@ export type SerializedEventUnion = | SerializedCardAddedToLibraryEvent | SerializedCardAddedToCollectionEvent | SerializedCardRemovedFromLibraryEvent + | SerializedCardRemovedFromCollectionEvent | SerializedCollectionCreatedEvent; export class EventMapper { @@ -79,6 +89,17 @@ export class EventMapper { }; } + if (event instanceof CardRemovedFromCollectionEvent) { + return { + eventType: EventNames.CARD_REMOVED_FROM_COLLECTION, + aggregateId: event.getAggregateId().toString(), + dateTimeOccurred: event.dateTimeOccurred.toISOString(), + cardId: event.cardId.getValue().toString(), + collectionId: event.collectionId.getValue().toString(), + removedBy: event.removedBy.value, + }; + } + if (event instanceof CollectionCreatedEvent) { return { eventType: EventNames.COLLECTION_CREATED, @@ -134,6 +155,21 @@ export class EventMapper { dateTimeOccurred, ).unwrap(); } + case EventNames.CARD_REMOVED_FROM_COLLECTION: { + const cardId = CardId.createFromString(eventData.cardId).unwrap(); + const collectionId = CollectionId.createFromString( + eventData.collectionId, + ).unwrap(); + const removedBy = CuratorId.create(eventData.removedBy).unwrap(); + const dateTimeOccurred = new Date(eventData.dateTimeOccurred); + + return CardRemovedFromCollectionEvent.reconstruct( + cardId, + collectionId, + removedBy, + dateTimeOccurred, + ).unwrap(); + } case EventNames.COLLECTION_CREATED: { const collectionId = CollectionId.createFromString( eventData.collectionId, diff --git a/src/shared/infrastructure/http/factories/UseCaseFactory.ts b/src/shared/infrastructure/http/factories/UseCaseFactory.ts index 92787894..f0510a87 100644 --- a/src/shared/infrastructure/http/factories/UseCaseFactory.ts +++ b/src/shared/infrastructure/http/factories/UseCaseFactory.ts @@ -196,6 +196,7 @@ export class UseCaseFactory { removeCardFromCollectionUseCase: new RemoveCardFromCollectionUseCase( repositories.cardRepository, services.cardCollectionService, + services.eventPublisher, ), getUrlMetadataUseCase: new GetUrlMetadataUseCase( services.metadataService, -- 2.51.2