feat(analytics): add cacheMeta to all /analytics/* and /avm/* responses (TEC-3056)

- Add CacheMetaStore (AsyncLocalStorage) in shared/infrastructure so
  cache metadata can propagate across async call stacks per-request
- Extend CacheService.getOrSet to store { __v, cachedAt, ttlSeconds }
  envelopes in Redis; reads back envelope to compute nextRefreshAt.
  Legacy plain-JSON entries are served transparently (cachedAt: null)
- Add CacheMetaInterceptor that wraps every analytics response as
  { data: T, cacheMeta: { cachedAt, nextRefreshAt, source } } using
  the per-request ALS store populated by CacheService
- Apply @UseInterceptors(CacheMetaInterceptor) on both
  AnalyticsController and AvmController (class-level)
- Update cache.service.spec.ts to expect envelope format on write
- Add cache-meta.interceptor.spec.ts with 6 tests covering market-report,
  price-trend, heatmap endpoints, cache-hit path, and ALS isolation
- Add analytics module README documenting the pattern for future devs

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-21 02:18:28 +07:00
parent 641e91f4d4
commit a70db64da1
9 changed files with 359 additions and 6 deletions

View File

@@ -6,6 +6,7 @@ import {
Post,
Query,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { QueryBus } from '@nestjs/cqrs';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiBody, ApiParam, ApiQuery } from '@nestjs/swagger';
@@ -26,9 +27,11 @@ import { AvmCompareQueryDto } from '../dto/avm-compare-query.dto';
import { AvmExplainQueryDto } from '../dto/avm-explain-query.dto';
import { BatchValuationDto } from '../dto/batch-valuation.dto';
import { IndustrialValuationDto } from '../dto/industrial-valuation.dto';
import { CacheMetaInterceptor } from '../interceptors/cache-meta.interceptor';
import { ValuationHistoryDto } from '../dto/valuation-history.dto';
@ApiTags('avm')
@UseInterceptors(CacheMetaInterceptor)
@Controller('avm')
export class AvmController {
constructor(