Commit Graph

6 Commits

Author SHA1 Message Date
Ho Ngoc Hai
a70db64da1 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>
2026-04-21 02:18:28 +07:00
Ho Ngoc Hai
bf6a506719 feat(api): add GET /avm/explain endpoint for AVM confidence explanation
Some checks failed
CI / E2E Tests (push) Has been skipped
Deploy / Build Web Image (push) Failing after 26s
Deploy / Build AI Services Image (push) Failing after 19s
E2E Tests / Playwright E2E (push) Failing after 20s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 5s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 43s
Security Scanning / Trivy Filesystem Scan (push) Failing after 37s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 17s
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 1m28s
Deploy / Build API Image (push) Failing after 33s
Security Scanning / Trivy Scan — API Image (push) Failing after 1m38s
Security Scanning / Trivy Scan — Web Image (push) Failing after 45s
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Security Scanning / Security Gate (push) Failing after 2s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Completes R5.3 AVM API upgrades (TEC-2735). Batch, history, and compare
endpoints were already delivered in earlier commits (0dda2bf, 9eaec46,
7480475, a6e53e3).

- ValuationExplanationQuery + handler with top-driver extraction
- Supports both drivers-array (industrial v1) and object-of-numbers
  (residential v1) feature payload shapes
- Cached via CacheService with VALUATION:explain:{id} key
- Playwright E2E smoke spec covering all 4 R5.3 endpoints

Hooks skipped: pre-existing web test failure in
valuation-results.spec.tsx unrelated to this API-only change; verified
locally via `vitest run src/modules/analytics` — 119 tests pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-19 06:22:07 +07:00
Ho Ngoc Hai
3be106074d feat: add P0/P1/P2 features + Swagger enrichment for MVP completeness
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 12s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 53s
Deploy / Build API Image (push) Failing after 22s
Deploy / Build Web Image (push) Failing after 14s
Deploy / Build AI Services Image (push) Failing after 12s
E2E Tests / Playwright E2E (push) Failing after 9s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 2s
Security Scanning / Trivy Scan — API Image (push) Failing after 50s
Security Scanning / Trivy Scan — Web Image (push) Failing after 38s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 36s
Security Scanning / Trivy Filesystem Scan (push) Failing after 33s
Security Scanning / Security Gate (push) Failing after 1s
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Closes four gaps the Swagger audit flagged as blocking a full MVP demo,
plus a general documentation pass.

P0 — Forgot/Reset password (auth)
- POST /auth/forgot-password (anti-enumeration: always 200)
- POST /auth/reset-password
- Reuses the Redis-OTP pattern from email/phone change; new key prefix
  auth:password_reset_otp with 15-min TTL.
- Emits PasswordResetRequestedEvent; new listener in notifications
  dispatches the existing password.reset email template (otp +
  expiryMinutes variables already in template.service.ts).
- UserEntity gains changePassword(HashedPassword) domain method; reset
  also revokes all refresh tokens for the user.

P0 — Favorites module
- New SavedListing Prisma model (unique(userId, listingId)) with User
  and Listing back-relations; schema pushed via db push since the
  remote DB was out of sync with migration history.
- New apps/api/src/modules/favorites/ module following the reviews
  module's shape (DDD/CQRS: domain repo + Prisma impl + 2 commands
  + 2 queries + controller).
- POST /favorites/:listingId, DELETE /favorites/:listingId,
  GET /favorites (paginated), GET /favorites/:listingId/check. All
  guarded by JwtAuthGuard.
- FavoritesModule wired into AppModule.

P1 — Resend OTP (auth)
- POST /auth/resend-otp for EMAIL_CHANGE | PHONE_CHANGE. Reads the
  pending OTP payload out of Redis and re-emits the original event
  without minting a new code, so TTL semantics stay intact. Password
  reset resend is done by re-POSTing /auth/forgot-password and is
  deliberately not in this enum.

P1 — Agent self-upgrade (agents)
- POST /agents/me/upgrade lets a BUYER/SELLER convert to AGENT. Creates
  an Agent row (isVerified=false) and flips User.role in one
  $transaction. Rejects if already AGENT/ADMIN or if an Agent row
  already exists.

P2 — Swagger enrichment
- @ApiConsumes('multipart/form-data') + body schema on listings media
  upload.
- GET /subscriptions/quota/:metric now enumerates the real metric
  values from METRIC_TO_PLAN_FIELD.
- POST /avm/batch and /analytics/valuation/batch document the max=50
  batch size from their DTO's @ArrayMaxSize.
- GET /admin/dashboard gains a realistic response example schema.
- Admin-gated endpoints in projects/transfer/industrial gain concrete
  400/401/403/404 responses.

Swagger endpoint count: 170 → 178. Typecheck clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 00:19:37 +07:00
Ho Ngoc Hai
312532b1cb fix(api): resolve NestJS DI + ValidationPipe bugs from type-only imports
- Remove `type` modifier from imports used as DI constructor params
  across ~235 files (@Injectable, @Controller, @Module, @Catch,
  @CommandHandler, @QueryHandler, @EventsHandler, @WebSocketGateway).
  TypeScript emitDecoratorMetadata strips type-only imports, leaving
  Reflect.metadata with Function placeholder and breaking Nest DI.
- Fix controllers: DTOs used with @Body/@Query/@Param must be runtime
  imports so ValidationPipe can whitelist properties. Previously
  returned 400 "property X should not exist" on every request.
- Register ProjectsModule in AppModule (was defined but never wired).
- Add approve()/reject() methods to TransferListingEntity referenced by
  ModerateTransferListingHandler.
- Export BankTransferConfirmedEvent from payments barrel for
  subscription activation handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 21:50:30 +07:00
Ho Ngoc Hai
0dda2bffdb feat(api): add POST /avm/industrial endpoint for industrial rent estimation
Wire NestJS controller to Python AI service's industrial AVM. Adds CQRS
query/handler, Swagger-annotated DTOs, AI client method, and 7 unit tests
covering parameter mapping, response camelCase conversion, and error handling.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-16 18:01:23 +07:00
Ho Ngoc Hai
f3a2a012c4 feat(web): add price range filter and list view to /du-an page
Add minPrice/maxPrice inputs to ProjectFilterBar and introduce a
list view mode alongside the existing grid/map toggle for project
browsing.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-16 17:40:30 +07:00