import { Injectable, Logger } from '@nestjs/common'; import { CacheKeys } from '../cache/cache-keys'; import type { AdPoolEntry, AdType } from '../ads/ad-types'; import { AdType as PrismaAdType } from '@prisma/mongo/client'; import { RedisService } from '@box/db/redis/redis.service'; import { MongoPrismaService } from '@box/db/prisma/mongo-prisma.service'; @Injectable() export class AdPoolService { private readonly logger = new Logger(AdPoolService.name); constructor( private readonly redis: RedisService, private readonly mongoPrisma: MongoPrismaService, ) {} /** Rebuild all ad pools for every AdType. */ async rebuildAllAdPools(): Promise { const adTypes = Object.values(PrismaAdType) as AdType[]; for (const adType of adTypes) { try { const count = await this.rebuildAdPoolByType(adType); this.logger.log( `AdPool warmup succeeded for adType=${adType}, ads=${count}`, ); } catch (err) { this.logger.error( `AdPool warmup failed for adType=${adType}`, err instanceof Error ? err.stack : String(err), ); } } } /** Rebuild a single ad pool keyed by AdType. */ async rebuildAdPoolByType(adType: AdType): Promise { const now = BigInt(Date.now()); const ads = await this.mongoPrisma.ads.findMany({ where: { adType, status: 1, startDt: { lte: now }, OR: [{ expiryDt: BigInt(0) }, { expiryDt: { gte: now } }], }, orderBy: { seq: 'asc' }, select: { id: true, adType: true, advertiser: true, title: true, adsContent: true, adsCoverImg: true, adsUrl: true, imgSource: true, startDt: true, expiryDt: true, seq: true, }, }); const poolEntries: AdPoolEntry[] = ads.map((ad) => ({ id: ad.id, adType: ad.adType, advertiser: ad.advertiser, title: ad.title, adsContent: ad.adsContent ?? null, adsCoverImg: ad.adsCoverImg ?? null, adsUrl: ad.adsUrl ?? null, imgSource: ad.imgSource ?? null, startDt: ad.startDt, expiryDt: ad.expiryDt, seq: ad.seq, })); const key = CacheKeys.appAdPoolByType(adType); await this.redis.atomicSwapJson([{ key, value: poolEntries }]); return poolEntries.length; } }