Refresh project documentation to reflect current state of the platform
including recent features, test improvements, and QA status.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Move 36 root-level audit/analysis documents and 7 web app audit documents
into docs/audits/ directory to declutter the project root. Remove stale
EXPLORATION_SUMMARY.txt.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Trigger deploy workflow on push to `develop` branch (in addition to `master`)
- Add `staging-latest` Docker image tag for develop branch builds
- Add `rollback-staging` job: auto-reverts to previous images on smoke test failure
- Add Slack success notification for staging deploys (previously only failure was notified)
- Record pre-deploy image digests for rollback capability
- Update deployment docs with CI/CD pipeline details, rollback procedures, and required secrets
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Remove unused imports (waitFor, useAuthStore) in dashboard test files
- Convert import() type annotation to import type in comparison-store spec
- Add next-env.d.ts to ESLint ignores (auto-generated file)
- Fix empty object pattern in auth.fixture.ts
- Sort import order alphabetically in 5 API test files
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Implement @EndpointRateLimit() decorator and EndpointRateLimitGuard for
granular per-endpoint rate limiting using a Redis sorted-set sliding window.
This prevents brute force attacks on auth endpoints, replay attacks on
payment callbacks, and scraping on search endpoints.
Applied rate limits:
- /auth/login: 5 req/min per IP
- /auth/register: 3 req/min per IP
- /listings POST: 10 req/min per user
- /search: 30 req/min per user
- /payments/callback/*: 100 req/min per IP
Features:
- True sliding window (sorted set) for accurate rate measurement
- Configurable key strategy (IP or authenticated user)
- Admin bypass support (enabled by default)
- Fail-open on Redis errors
- Proper 429 response with Retry-After header
- Rate limit headers (X-RateLimit-Limit/Remaining/Reset)
- 22 unit tests covering all scenarios
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Create comprehensive docs/RUNBOOK.md covering all 14 production services,
health checks, 10 common incident scenarios with diagnosis/resolution,
recovery procedures (DB restore, Redis flush, rolling restart, rollback),
escalation matrix, monitoring dashboards, and PromQL queries.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Streamline developer onboarding with a single bootstrap.sh that checks
prerequisites, configures .env with generated secrets, installs deps,
starts Docker services, and runs migrations + seeding.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Implements a public-facing agent profile page with:
- Backend: new GET /agents/:agentId/profile public API endpoint with
agent info, active listings, quality score, and review stats
- Frontend: server-rendered profile page with generateMetadata for SEO,
JSON-LD structured data (RealEstateAgent schema), breadcrumbs
- Agent profile displays bio, service areas, quality score gauge,
active listing cards, reviews with star ratings, and contact CTA
- Mobile responsive layout with sticky contact sidebar on desktop
- Vietnamese UI text throughout, consistent with existing patterns
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Build a complete property comparison feature at /compare:
- Zustand store with localStorage persistence for selected listings (2-5)
- Side-by-side comparison table (price, area, price/m², amenities, location, etc.)
- Summary statistics banner (price range, area range, price/m² range)
- "Add to Compare" button on property cards and detail pages
- Floating comparison bar for quick access when listings are selected
- Bilingual i18n support (Vietnamese + English)
- 18 unit tests for store logic and comparison stats computation
- Mobile-responsive layout with horizontal scroll on comparison table
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Create a single `currency.ts` utility with `formatPrice`, `formatVND`,
`formatPricePerM2`, and `parseVND` to replace 9+ duplicated inline
formatters. This fixes inconsistent decimal handling (1.5M was truncated
to "1 triệu") and standardises price/m² display. Integrated across
property cards, listing detail, dashboard, analytics, payments, pricing,
and admin moderation pages with 19 new unit tests.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Fix port numbers across all docs (API :3001, Web :3000)
- Add 6 missing modules to README, CLAUDE.md, and architecture doc
(agents, health, inquiries, leads, reviews, metrics/web-vitals)
- Add Swagger UI reference and /api/v1 prefix notes
- Create docs/api-endpoints.md with complete REST API reference
- Create docs/README.md as documentation index
- Update deployment guide with Loki, Promtail, pg-backup services
- Update health check table with all current endpoints
Co-Authored-By: Paperclip <noreply@paperclip.ing>
HashedPassword.vo.spec.ts was timing out because SALT_ROUNDS=12 is too
expensive for the test runner. Make bcrypt rounds configurable via
BCRYPT_ROUNDS env var (default 12 for production), and set BCRYPT_ROUNDS=4
in vitest config for fast unit tests.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Root directory had accumulated audit/exploration markdown files cluttering
the project root. Moved all audit-related files to docs/audits/ with a
README.md index, and updated cross-references in K6_LOAD_TESTING_GUIDE.md
and README_FRONTEND_DOCS.md.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Complete public pricing page showing all 4 subscription plans (FREE,
AGENT_PRO, INVESTOR, ENTERPRISE) with billing cycle toggle, feature
comparison table, VND formatting, and Vietnamese/English i18n support.
Also adds pricing link to public navigation header and footer.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Prisma errors (P2025 record not found, P2002 unique constraint, P2003 foreign key)
were falling through to the catch-all handler and returning 500 Internal Server Error
instead of appropriate 404/409/400. This caused GET /listings/:id with a non-existent
ID to return 500 when the Prisma layer threw before the application null check.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Prisma errors (P2025 record not found, P2002 unique constraint, P2003 foreign key)
were falling through to the catch-all handler and returning 500 Internal Server Error
instead of appropriate 404/409/400. This caused GET /listings/:id with a non-existent
ID to return 500 when the Prisma layer threw before the application null check.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Change circuit-breaker import in resilient-search.repository.ts to use
@modules/shared barrel export instead of deep path, fixing no-restricted-imports
error. Replace console.log with console.warn in encrypt-existing-kyc.ts script
to satisfy no-console rule.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
LocalStrategy and auth controllers were importing UnauthorizedException
from @nestjs/common instead of @modules/shared. While both return 401,
only the custom DomainException-based version produces the structured
error format (errorCode, correlationId, timestamp) expected by the
GlobalExceptionFilter's primary code path.
Also adds handleRequest() override to LocalAuthGuard to ensure custom
exceptions from the strategy propagate directly without Passport
transforming them.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Root cause: HealthController used @Controller() (empty prefix) with @Get('health')
and @Get('ready') flat routes. The global prefix exclusion for 'health' and 'ready'
was unreliable for module-scoped controllers.
Changes:
- Set @Controller('health') prefix so routes are /health, /health/ready, /health/db, /health/redis
- Update global prefix exclusion to use 'health/(.*)' wildcard pattern
- Exclude health endpoints from CSRF middleware (K8s probes don't send cookies)
- Add dedicated /health/db and /health/redis endpoints per acceptance criteria
- Expand unit tests to cover all 4 health endpoints (15 tests passing)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
The ReviewsModule routes returned 404 because TypeScript `type` imports
(`import { type CommandBus }`) are erased at compile time, causing
`emitDecoratorMetadata` to emit `Function` instead of the actual class
reference. NestJS DI relies on `design:paramtypes` metadata to resolve
constructor dependencies; with `Function` as the token, it cannot match
providers and the module fails to initialize silently.
Changed all DI-injected classes (CommandBus, QueryBus, EventBus,
LoggerService, PrismaService) from `type` imports to value imports
across the reviews module. Added eslint-disable comments to suppress
the `consistent-type-imports` rule on those lines, since NestJS DI
fundamentally requires runtime class references.
Also added ReviewsController unit tests covering all 5 endpoints.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Add comprehensive SEO support for property listing pages to improve
organic search visibility and social sharing.
Changes:
- Convert listing detail page from client-only to server component wrapper
with generateMetadata() for per-listing title, description, OG tags,
canonical URLs, and hreflang alternates
- Add JSON-LD structured data (Schema.org RealEstateListing) with price,
location, property specs, and breadcrumb markup
- Add Website JSON-LD with SearchAction to root layout
- Upgrade sitemap.xml to dynamically include all active listings across
both locales (vi, en) with ISR revalidation
- Improve robots.txt with pagination/sort exclusions and GPTBot block
- Create server-side fetch utility (listings-server.ts) for SSR data
- Extract client UI into ListingDetailClient component
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Introduces PgBouncer as a connection pooler between the API service and
PostgreSQL in docker-compose.prod.yml, reducing connection overhead and
improving concurrency under production load.
- Add PgBouncer service (edoburu/pgbouncer:1.23.1-p2) with transaction
pool mode, max_client_conn=200, default_pool_size=20
- Route API DATABASE_URL through PgBouncer (port 6432), keep direct
connection (DATABASE_URL_DIRECT) for Prisma migrations/introspection
- Create infra/pgbouncer/ config: pgbouncer.ini, userlist template,
and entrypoint script with runtime env-var substitution
- Update prisma.config.ts to prefer DATABASE_URL_DIRECT for migrations
- Add K6 load test (e2e/load/pgbouncer-pool-test.js) with ramp-up to
200 VUs, pool exhaustion detection, and p95 < 2s threshold
- Add PgBouncer env vars to .env.example
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Document all 33 structured errorCode values from DomainException/ErrorCode
enum across all modules (auth, user, listing, property, media, payment,
subscription, course). Includes HTTP status mapping, Vietnamese error
messages, usage examples per module, alphabetical quick-reference table,
and TypeScript integration guide for frontend error handling.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
All three GitHub Actions workflows (CI, E2E, Deploy) referenced
branches: [main] but the repository default branch is master.
This meant CI never triggered on pushes or PRs to master.
- ci.yml: push/PR triggers → master
- e2e.yml: push/PR triggers → master
- deploy.yml: push trigger + latest tag condition → master
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Remove unused `registerUser` import in e2e/api/inquiries.spec.ts
- Add `override` modifier to class methods in query-provider.tsx
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- FileValidationPipe now supports maxSizeByMimeType for per-MIME-type size limits
- Images: max 10MB, Video (MP4): max 100MB
- Oversized files return 413 Payload Too Large instead of 400 Bad Request
- MIME type validation runs before size check for clearer error messages
- Multer module limit raised to 100MB (per-type enforcement in pipe)
- Added 413 ApiResponse to Swagger docs on upload endpoint
- Added comprehensive unit tests for FileValidationPipe (16 test cases)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
MCP endpoints already had JwtAuthGuard applied but lacked per-route rate
limiting and test coverage for security behavior. Add @Throttle decorators
with appropriate limits (5 req/min for SSE connections, 30 req/min for
server list and messages), unit tests verifying guard/throttle metadata,
and E2E tests confirming 401 rejection for unauthenticated requests.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Add global QueryErrorResetBoundary wrapping the app so TanStack Query
errors are caught with a retry UI instead of crashing. Enable
throwOnError in QueryClient defaults. Update ListingMap to use real
latitude/longitude from API when available, falling back to city-based
jitter for listings without coordinates.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Migrate 30 files from `new Logger(ClassName.name)` to injected LoggerService
for consistent PII masking and centralized logging config
- Split prisma-admin-query.repository.ts (313→121 lines) into admin-stats.queries.ts
and admin-user.queries.ts
- Split admin.controller.ts (285→154 lines) into admin-moderation.controller.ts
- Split prisma-listing.repository.ts (274→111 lines) into listing-read.queries.ts
- Update 28 test files with mock LoggerService
- All 831 tests passing, zero direct new Logger() calls remaining
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- 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>
- Create @Cacheable method decorator for declarative cache-aside pattern
with configurable prefix, TTL, resource label, and key extraction
- Add PLAN_LIST (1h TTL) and REFERENCE_DATA (24h TTL) cache constants
- Add CachePrefix.PLAN_LIST and CachePrefix.REFERENCE entries
- Cache subscription plan queries in GetPlanHandler (single + list)
- Export Cacheable decorator from shared module barrel
- Add comprehensive tests for decorator and handler caching
The caching infrastructure (CacheService, Redis, Prometheus metrics,
event-driven invalidation) was already production-ready with 10+ hot
paths cached. This commit adds the missing declarative decorator and
plan list caching.
Resolves: TEC-1567
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Add missing auth and search translation namespaces to vi.json and en.json
that are required by login/register pages and search filter-bar component.
Update filter-bar with useTranslations('search'), aria-labels, and
role="search" for WCAG 2.1 AA compliance.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>