feat(api): add listing search caching and apply @Cacheable decorator

- Add Redis caching to SearchListingsHandler (2 min TTL, query-based key)
- Refactor GetDistrictStatsHandler to use @Cacheable decorator
- Update search-listings test to provide mock CacheService

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-10 05:14:58 +07:00
parent eaa4925653
commit 4e71036ddd
3 changed files with 61 additions and 28 deletions

View File

@@ -1,6 +1,6 @@
import { Inject } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs';
import { CacheService, CachePrefix, CacheTTL } from '@modules/shared';
import { CacheService, CachePrefix, CacheTTL, Cacheable } from '@modules/shared';
import {
MARKET_INDEX_REPOSITORY,
type IMarketIndexRepository,
@@ -18,20 +18,20 @@ export interface DistrictStatsDto {
export class GetDistrictStatsHandler implements IQueryHandler<GetDistrictStatsQuery> {
constructor(
@Inject(MARKET_INDEX_REPOSITORY) private readonly marketIndexRepo: IMarketIndexRepository,
private readonly cache: CacheService,
private readonly cacheService: CacheService,
) {}
@Cacheable({
prefix: CachePrefix.MARKET_DISTRICT,
ttl: CacheTTL.DISTRICT_STATS,
resource: 'district_stats',
keyFrom: (query: unknown) => {
const q = query as GetDistrictStatsQuery;
return [q.city, q.period];
},
})
async execute(query: GetDistrictStatsQuery): Promise<DistrictStatsDto> {
const cacheKey = CacheService.buildKey(CachePrefix.MARKET_DISTRICT, query.city, query.period);
return this.cache.getOrSet(
cacheKey,
async () => {
const districts = await this.marketIndexRepo.getDistrictStats(query.city, query.period);
return { city: query.city, period: query.period, districts };
},
CacheTTL.DISTRICT_STATS,
'district_stats',
);
const districts = await this.marketIndexRepo.getDistrictStats(query.city, query.period);
return { city: query.city, period: query.period, districts };
}
}