feat(api): complete domain event publishing with aggregate root pattern
- Add getUncommittedEvents() and commit() to AggregateRoot base class - Create 6 new domain events: SubscriptionExpired, SubscriptionRenewed, ListingStatusChanged, UserKycUpdated, UserDeactivated, PaymentRefunded - Wire events into entity state changes: SubscriptionEntity (markExpired, renewPeriod), ListingEntity (all transitions), UserEntity (KYC, deactivate), PaymentEntity (markRefunded) - Add 7 new event listeners across notifications, admin, and search modules (25 total @OnEvent handlers) - Fix ReviewDeletedListener to handle LISTING target type - Restore watcher notifications in ListingSoldListener - Update barrel exports and module registrations Resolves: TEC-1564 Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -1 +1,2 @@
|
||||
export { ListingApprovedEventHandler } from './listing-approved.handler';
|
||||
export { ListingStatusChangedHandler } from './listing-status-changed.handler';
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { type ListingStatusChangedEvent } from '@modules/listings';
|
||||
import { CacheService, CachePrefix, type LoggerService } from '@modules/shared';
|
||||
import { type ListingIndexerService } from '../services/listing-indexer.service';
|
||||
|
||||
@Injectable()
|
||||
export class ListingStatusChangedHandler {
|
||||
constructor(
|
||||
private readonly indexer: ListingIndexerService,
|
||||
private readonly cache: CacheService,
|
||||
private readonly logger: LoggerService,
|
||||
) {}
|
||||
|
||||
@OnEvent('listing.status_changed', { async: true })
|
||||
async handle(event: ListingStatusChangedEvent): Promise<void> {
|
||||
this.logger.log(
|
||||
`Handling listing.status_changed: ${event.previousStatus} → ${event.newStatus} for ${event.aggregateId}`,
|
||||
'ListingStatusChangedHandler',
|
||||
);
|
||||
|
||||
// Remove from search index when listing becomes inactive
|
||||
const removeStatuses = ['REJECTED', 'EXPIRED', 'SOLD', 'RENTED'];
|
||||
if (removeStatuses.includes(event.newStatus)) {
|
||||
await this.indexer.removeListing(event.aggregateId);
|
||||
}
|
||||
|
||||
// Invalidate caches for any status change
|
||||
await Promise.all([
|
||||
this.cache.invalidate(CacheService.buildKey(CachePrefix.LISTING, event.aggregateId)),
|
||||
this.cache.invalidateByPrefix(CachePrefix.SEARCH),
|
||||
this.cache.invalidateByPrefix(CachePrefix.GEO_SEARCH),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { GeoSearchHandler } from './application/queries/geo-search/geo-search.ha
|
||||
import { SearchPropertiesHandler } from './application/queries/search-properties/search-properties.handler';
|
||||
import { SEARCH_REPOSITORY } from './domain/repositories/search.repository';
|
||||
import { ListingApprovedEventHandler } from './infrastructure/event-handlers/listing-approved.handler';
|
||||
import { ListingStatusChangedHandler } from './infrastructure/event-handlers/listing-status-changed.handler';
|
||||
import { ListingIndexerService } from './infrastructure/services/listing-indexer.service';
|
||||
import { TypesenseClientService } from './infrastructure/services/typesense-client.service';
|
||||
import { TypesenseSearchRepository } from './infrastructure/services/typesense-search.repository';
|
||||
@@ -27,6 +28,7 @@ const QueryHandlers = [SearchPropertiesHandler, GeoSearchHandler];
|
||||
|
||||
// Event handlers
|
||||
ListingApprovedEventHandler,
|
||||
ListingStatusChangedHandler,
|
||||
|
||||
// CQRS
|
||||
...CommandHandlers,
|
||||
|
||||
Reference in New Issue
Block a user