feat(listings): add cron to auto-expire featured listings (TEC-2924)

- New FeaturedListingExpiryCronService runs every 5 minutes and clears
  Listing.featuredUntil when the promotion period has ended
- Uses a single atomic UPDATE ... RETURNING so concurrent instances do not
  double-process rows (idempotent)
- Publishes ListingFeaturedExpiredEvent via CQRS EventBus for downstream
  cache/search index invalidation
- Unit test covers event emission, no-op path, error path, and concurrency

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-20 08:20:40 +07:00
parent 283984b2f2
commit 366815b350
5 changed files with 170 additions and 0 deletions

View File

@@ -21,6 +21,7 @@ import { PROPERTY_REPOSITORY } from './domain/repositories/property.repository';
import { DUPLICATE_DETECTOR } from './domain/services/duplicate-detector';
import { ModerationService } from './domain/services/moderation.service';
import { PRICE_VALIDATOR } from './domain/services/price-validator';
import { FeaturedListingExpiryCronService } from './infrastructure/cron/featured-listing-expiry-cron.service';
import { PrismaListingRepository } from './infrastructure/repositories/prisma-listing.repository';
import { PrismaPropertyRepository } from './infrastructure/repositories/prisma-property.repository';
import { MEDIA_STORAGE_SERVICE, MinioMediaStorageService } from './infrastructure/services/media-storage.service';
@@ -75,6 +76,9 @@ const EventHandlers = [
...CommandHandlers,
...QueryHandlers,
...EventHandlers,
// Cron
FeaturedListingExpiryCronService,
],
exports: [LISTING_REPOSITORY, PROPERTY_REPOSITORY],
})