diff --git a/apps/api/src/modules/analytics/application/queries/get-district-stats/get-district-stats.handler.ts b/apps/api/src/modules/analytics/application/queries/get-district-stats/get-district-stats.handler.ts index 2b458f4..38214b1 100644 --- a/apps/api/src/modules/analytics/application/queries/get-district-stats/get-district-stats.handler.ts +++ b/apps/api/src/modules/analytics/application/queries/get-district-stats/get-district-stats.handler.ts @@ -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 { 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 { - 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 }; } } diff --git a/apps/api/src/modules/listings/application/__tests__/search-listings.handler.spec.ts b/apps/api/src/modules/listings/application/__tests__/search-listings.handler.spec.ts index 0cda314..dd656ba 100644 --- a/apps/api/src/modules/listings/application/__tests__/search-listings.handler.spec.ts +++ b/apps/api/src/modules/listings/application/__tests__/search-listings.handler.spec.ts @@ -1,10 +1,12 @@ import { type IListingRepository } from '@modules/listings/domain/repositories/listing.repository'; +import { type CacheService } from '@modules/shared'; import { SearchListingsHandler } from '../queries/search-listings/search-listings.handler'; import { SearchListingsQuery } from '../queries/search-listings/search-listings.query'; describe('SearchListingsHandler', () => { let handler: SearchListingsHandler; let mockListingRepo: { [K in keyof IListingRepository]: ReturnType }; + let mockCacheService: { [K in keyof CacheService]: ReturnType }; beforeEach(() => { mockListingRepo = { @@ -17,7 +19,14 @@ describe('SearchListingsHandler', () => { findBySellerId: vi.fn(), }; - handler = new SearchListingsHandler(mockListingRepo as any); + mockCacheService = { + getOrSet: vi.fn((_key, loader) => loader()), + invalidate: vi.fn(), + invalidateByPrefix: vi.fn(), + onModuleInit: vi.fn(), + }; + + handler = new SearchListingsHandler(mockListingRepo as any, mockCacheService as any); }); it('searches with all filters', async () => { diff --git a/apps/api/src/modules/listings/application/queries/search-listings/search-listings.handler.ts b/apps/api/src/modules/listings/application/queries/search-listings/search-listings.handler.ts index 63897ed..ca94548 100644 --- a/apps/api/src/modules/listings/application/queries/search-listings/search-listings.handler.ts +++ b/apps/api/src/modules/listings/application/queries/search-listings/search-listings.handler.ts @@ -1,5 +1,6 @@ import { Inject } from '@nestjs/common'; import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; +import { CacheService, CachePrefix, CacheTTL } from '@modules/shared'; import { type ListingSearchItem } from '../../../domain/repositories/listing-read.dto'; import { LISTING_REPOSITORY, type IListingRepository, type PaginatedResult } from '../../../domain/repositories/listing.repository'; import { SearchListingsQuery } from './search-listings.query'; @@ -8,22 +9,45 @@ import { SearchListingsQuery } from './search-listings.query'; export class SearchListingsHandler implements IQueryHandler { constructor( @Inject(LISTING_REPOSITORY) private readonly listingRepo: IListingRepository, + private readonly cacheService: CacheService, ) {} async execute(query: SearchListingsQuery): Promise> { - return this.listingRepo.search({ - status: query.status, - transactionType: query.transactionType, - propertyType: query.propertyType, - city: query.city, - district: query.district, - minPrice: query.minPrice, - maxPrice: query.maxPrice, - minArea: query.minArea, - maxArea: query.maxArea, - bedrooms: query.bedrooms, - page: query.page, - limit: query.limit, - }); + const cacheKey = CacheService.buildKey( + CachePrefix.SEARCH, + query.status, + query.transactionType, + query.propertyType, + query.city, + query.district, + query.minPrice?.toString(), + query.maxPrice?.toString(), + query.minArea?.toString(), + query.maxArea?.toString(), + query.bedrooms?.toString(), + String(query.page), + String(query.limit), + ); + + return this.cacheService.getOrSet( + cacheKey, + async () => + this.listingRepo.search({ + status: query.status, + transactionType: query.transactionType, + propertyType: query.propertyType, + city: query.city, + district: query.district, + minPrice: query.minPrice, + maxPrice: query.maxPrice, + minArea: query.minArea, + maxArea: query.maxArea, + bedrooms: query.bedrooms, + page: query.page, + limit: query.limit, + }), + CacheTTL.SEARCH_RESULTS, + 'listing_search', + ); } }