Compare commits

...

10 Commits

Author SHA1 Message Date
Ho Ngoc Hai
e5f7acf7da feat: production infra — nginx configs, deploy script, security hardening
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 58s
Deploy / Build Web Image (push) Failing after 14s
Deploy / Rollback Production (push) Has been skipped
CI / E2E Tests (push) Has been skipped
Deploy / Build API Image (push) Failing after 3m8s
Deploy / Build AI Services Image (push) Failing after 10s
E2E Tests / Playwright E2E (push) Failing after 1m21s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
- Add Nginx reverse-proxy configs for api.goodgo.vn and platform.goodgo.vn
  with SSL, gzip, rate limiting, security headers, and WebSocket support
- Add Cloudflare DNS setup script for A/AAAA/CNAME records
- Add server-setup.sh for Ubuntu provisioning (Docker, fail2ban, UFW,
  swap, unattended-upgrades)
- Add deploy-production.sh for manual production deployments
- Add env.production.example with all required environment variables
- Bind container ports to 127.0.0.1 in docker-compose.prod.yml
  (security: prevent direct access bypassing Nginx)
- Fix deploy workflow: add -T flag to exec, sync Nginx configs,
  copy pgbouncer and backup configs to server

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 14:11:25 +07:00
Ho Ngoc Hai
b93c28fa01 chore: organize docs — move 37 files from root into docs/ subfolders
Root now contains only essential files:
  README.md, CLAUDE.md, CHANGELOG.md, CONTRIBUTING.md

Reorganized into:
  docs/audits/       — all audit reports & checklists (71 files)
  docs/architecture/  — codebase overview, implementation plan
  docs/guides/        — auth guide, implementation checklist
  docs/load-testing/  — k6 load test guides & endpoints
  docs/security/      — payment & security reviews

Also removed 5 untracked debug/investigation files and
cleaned up playwright-report/ & test-results/ artifacts.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 12:09:14 +07:00
Ho Ngoc Hai
ccfc176e40 fix: valuation page Vietnamese diacritics, correct API routes, update tests
- Add proper Vietnamese diacritics to all valuation components
  (form, results, history) and their test assertions
- Fix valuation API client to use /analytics/valuation endpoint
- Return empty history gracefully (no server endpoint yet)

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 12:03:47 +07:00
Ho Ngoc Hai
f373f7b1e2 fix: BigInt JSON serialization, pricing table dark mode
- Add BigInt.prototype.toJSON polyfill in main.ts so Express can
  serialize Prisma BigInt fields (priceVND, revenue amounts)
- Fix: admin/moderation and admin/revenue returning 500 Internal Error
- Fix pricing compare table: Enterprise column text invisible in dark
  mode (bg-green-50 without dark variant → add dark:bg-green-950/40)

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 11:29:06 +07:00
Ho Ngoc Hai
1ebdc5f0b3 fix: auth cookies cross-origin, async params, CSRF/web-vitals errors
- Set SameSite=lax for auth & CSRF cookies in development (cross-port)
- Set refresh_token cookie path to / (was /auth, preventing cross-port send)
- Await params in Next.js 15 async server components (layout, listings, agents)
- Add CSRF token to web-vitals POST requests
- Fix: 401 Unauthorized on all authenticated API calls from web app
- Fix: CSRF token missing on POST requests from different port
- Fix: params.locale sync access warning in generateMetadata

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 11:24:45 +07:00
Ho Ngoc Hai
a9fa214544 feat: comprehensive seed, Lucide icons, grouped dashboard nav, API fixes
- Rewrite prisma/seed.ts to populate all 27 models with realistic
  Vietnamese real estate data (8 users with login, 10 properties,
  10 listings, orders, payments, reviews, notifications, etc.)
- Replace all emoji icons with Lucide React SVG icons across frontend
  for consistent rendering, sizing, and accessibility
- Redesign dashboard nav: grouped sidebar with section headers,
  primary/secondary split on desktop, icon-only secondary items
- Replace language switcher flag emoji with Globe icon
- Replace SVG theme toggle with Lucide Moon/Sun icons
- Fix API startup: graceful fallback for Sentry profiling, Google OAuth,
  and Zalo OAuth when credentials are not configured
- Relax rate limiting in development mode (10k req/min)
- Fix listings API to include media[] array in search response
- Add optional chaining for property.media across frontend components
- Update OAuth strategy tests to match graceful fallback behavior

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 11:13:04 +07:00
Ho Ngoc Hai
db0fe8b9b7 fix(e2e): unblock E2E test environment — CSP, CORS, and env var fixes
Root causes of web E2E failures:
1. CSP connect-src only included API origin for NODE_ENV=development,
   blocking test mode (NODE_ENV=test) from fetching API data
2. CORS_ORIGINS missing the test web port (3010), so API rejected
   cross-origin requests from the web app
3. NEXT_PUBLIC_API_URL not set in .env.test or playwright config,
   causing web app to default to port 3001 instead of test port 3011
4. Playwright webServer config didn't inherit parent env vars,
   so API server lacked Redis/Typesense/MinIO connection info

Fixes:
- next.config.js: CSP connect-src allows API origins for all non-prod envs
- next.config.js: image remotePatterns allow localhost in test mode
- .env.test: add NEXT_PUBLIC_API_URL and CORS_ORIGINS
- playwright.config.ts: spread process.env into webServer env configs
- e2e.yml: add NEXT_PUBLIC_API_URL, API_PORT, WEB_PORT to GH Actions env
- homepage.spec.ts: update stale assertions to match current UI

Result: 147/202 tests passing (111 API + 36 web), up from 37/91.
Remaining 55 web failures are stale UI assertions needing frontend update.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-13 01:55:04 +07:00
Ho Ngoc Hai
25420720e7 fix(api,ci): remove type-only imports for DI and isolate CI ports from dev
- Remove `type` keyword from NestJS injectable class imports across all
  modules to fix runtime DI resolution (330+ handler/listener files)
- Offset CI docker-compose ports (5433/6380/8109/9002) to avoid
  conflicts with running dev containers
- Update .env.test, playwright.config.ts, and e2e workflow to use
  isolated CI ports with configurable overrides
- Fix prisma/seed.ts to use deterministic IDs for Prisma 7 upsert
  compatibility (phoneHash replaced phone as unique index)
- Add dedicated Docker bridge network for CI service containers

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
2026-04-13 01:40:14 +07:00
Ho Ngoc Hai
1617921993 feat(payments): add Order & Escrow repository tests, prisma config, docs
Add 26 unit tests for PrismaOrderRepository and PrismaEscrowRepository
covering CRUD operations, pagination, domain mapping (bigint → Money),
idempotency key lookup, and escrow dispute workflow fields.

Update prisma.config.ts with dotenv import and seed command for Prisma 7.
Add architecture summary and codebase overview documentation files.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-13 00:36:49 +07:00
Ho Ngoc Hai
50b2eea4a2 fix(listings): return 404 instead of 500 for non-existent listing detail
Move not-found handling from the query handler to the controller layer
following DDD conventions: the handler now returns null when a listing
is not found, and the controller maps that to NotFoundException (404).

Key changes:
- Handler returns ListingDetailData | null instead of throwing
- Use ListingNotFoundSignal to prevent caching null results
- Add `return await` to properly catch errors in try/catch
- Controller throws NotFoundException with listing ID in message
- Strengthen E2E test to assert exactly 404 (was [404, 400])
- Add unit tests: not-found returns null, unexpected error → 500
- Fix missing LoggerService mock in handler test

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-13 00:21:42 +07:00
425 changed files with 7940 additions and 2250 deletions

6
.env.ci Normal file
View File

@@ -0,0 +1,6 @@
# Port mappings for CI containers — offset from dev defaults to avoid conflicts.
# Docker Compose reads this file via `env_file` in docker-compose.ci.yml.
DB_PORT=5433
REDIS_PORT=6380
TYPESENSE_PORT=8109
MINIO_PORT=9002

View File

@@ -1,23 +1,34 @@
# ============================================================================= # =============================================================================
# GoodGo Platform — Test Environment Variables # GoodGo Platform — Test Environment Variables
# Used by E2E tests (Playwright globalSetup loads this automatically) # Used by E2E tests (Playwright globalSetup loads this automatically)
#
# These values MUST match docker-compose.ci.yml service credentials.
# Ports use CI_* offsets to avoid conflicts with dev containers.
# ============================================================================= # =============================================================================
# Test database — separate from development DB for isolation # Test database — matches docker-compose.ci.yml postgres service
DATABASE_URL=postgresql://goodgo:goodgo_secret@localhost:5432/goodgo_test?schema=public # Port 5433 avoids conflict with dev postgres on 5432
DATABASE_URL=postgresql://goodgo:goodgo_test_secret@localhost:5433/goodgo_test?schema=public
# Services (same as dev, adjust if your test infra differs) # Redis — matches docker-compose.ci.yml redis service
REDIS_URL=redis://localhost:6379 # Port 6380 avoids conflict with dev redis on 6379
REDIS_URL=redis://localhost:6380
REDIS_HOST=localhost
REDIS_PORT=6380
# Typesense — matches docker-compose.ci.yml typesense service
# Port 8109 avoids conflict with dev typesense on 8108
TYPESENSE_HOST=localhost TYPESENSE_HOST=localhost
TYPESENSE_PORT=8108 TYPESENSE_PORT=8109
TYPESENSE_PROTOCOL=http TYPESENSE_PROTOCOL=http
TYPESENSE_API_KEY=ts_dev_key_change_me TYPESENSE_API_KEY=ts_ci_key
# MinIO # MinIO — matches docker-compose.ci.yml minio service
# Port 9002 avoids conflict with dev minio on 9000
MINIO_ENDPOINT=localhost MINIO_ENDPOINT=localhost
MINIO_PORT=9000 MINIO_PORT=9002
MINIO_ACCESS_KEY=test_minio_user MINIO_ACCESS_KEY=ci_minio_user
MINIO_SECRET_KEY=test_minio_secret_key_32chars!! MINIO_SECRET_KEY=ci_minio_secret_key_32chars!!
MINIO_BUCKET=goodgo-uploads MINIO_BUCKET=goodgo-uploads
# Auth (deterministic secrets for test reproducibility) # Auth (deterministic secrets for test reproducibility)
@@ -27,6 +38,16 @@ JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d JWT_REFRESH_EXPIRES_IN=7d
NODE_ENV=test NODE_ENV=test
# Server ports — offset to avoid conflicts with dev
API_PORT=3011
WEB_PORT=3010
API_BASE_URL=http://localhost:3011/api/v1/
WEB_BASE_URL=http://localhost:3010
NEXT_PUBLIC_API_URL=http://localhost:3011/api/v1
# CORS — allow web test origin
CORS_ORIGINS=http://localhost:3010,http://localhost:3000
# Bcrypt (fast rounds for test — production uses 12+) # Bcrypt (fast rounds for test — production uses 12+)
BCRYPT_ROUNDS=4 BCRYPT_ROUNDS=4

View File

@@ -197,9 +197,11 @@ jobs:
DEPLOY_KEY: ${{ secrets.STAGING_SSH_KEY }} DEPLOY_KEY: ${{ secrets.STAGING_SSH_KEY }}
IMAGE_TAG: ${{ github.sha }} IMAGE_TAG: ${{ github.sha }}
run: | run: |
# Copy production compose and deploy # Copy production compose, monitoring, and infra configs
scp -i ~/.ssh/deploy_key docker-compose.prod.yml "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/" scp -i ~/.ssh/deploy_key docker-compose.prod.yml "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/"
scp -i ~/.ssh/deploy_key -r monitoring/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/monitoring/" scp -i ~/.ssh/deploy_key -r monitoring/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/monitoring/"
scp -i ~/.ssh/deploy_key -r infra/pgbouncer/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/infra/pgbouncer/"
scp -i ~/.ssh/deploy_key -r scripts/backup/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/scripts/backup/"
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << DEPLOY_SCRIPT ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << DEPLOY_SCRIPT
cd ~/goodgo cd ~/goodgo
@@ -218,12 +220,29 @@ jobs:
docker compose -f docker-compose.prod.yml up -d --no-deps --wait ai-services docker compose -f docker-compose.prod.yml up -d --no-deps --wait ai-services
# Run database migrations # Run database migrations
docker compose -f docker-compose.prod.yml exec api npx prisma migrate deploy docker compose -f docker-compose.prod.yml exec -T api npx prisma migrate deploy
# Cleanup old images # Cleanup old images
docker image prune -f docker image prune -f
DEPLOY_SCRIPT DEPLOY_SCRIPT
- name: Sync Nginx configs
env:
DEPLOY_HOST: ${{ secrets.STAGING_HOST }}
DEPLOY_USER: ${{ secrets.STAGING_USER }}
DEPLOY_KEY: ${{ secrets.STAGING_SSH_KEY }}
run: |
scp -i ~/.ssh/deploy_key infra/nginx/*.conf \
"$DEPLOY_USER@$DEPLOY_HOST:/tmp/goodgo-nginx/"
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << 'NGINX_SCRIPT'
sudo mkdir -p /tmp/goodgo-nginx
sudo cp /tmp/goodgo-nginx/*.conf /etc/nginx/sites-available/ 2>/dev/null || true
for conf in /etc/nginx/sites-available/*goodgo*; do
[ -f "$conf" ] && sudo ln -sf "$conf" /etc/nginx/sites-enabled/
done
sudo nginx -t && sudo systemctl reload nginx
NGINX_SCRIPT
- name: Verify staging deployment - name: Verify staging deployment
env: env:
STAGING_URL: ${{ secrets.STAGING_URL }} STAGING_URL: ${{ secrets.STAGING_URL }}
@@ -372,8 +391,11 @@ jobs:
chmod 600 ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null ssh-keyscan -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null
# Copy configs
scp -i ~/.ssh/deploy_key docker-compose.prod.yml "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/" scp -i ~/.ssh/deploy_key docker-compose.prod.yml "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/"
scp -i ~/.ssh/deploy_key -r monitoring/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/monitoring/" scp -i ~/.ssh/deploy_key -r monitoring/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/monitoring/"
scp -i ~/.ssh/deploy_key -r infra/pgbouncer/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/infra/pgbouncer/"
scp -i ~/.ssh/deploy_key -r scripts/backup/ "$DEPLOY_USER@$DEPLOY_HOST:~/goodgo/scripts/backup/"
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << DEPLOY_SCRIPT ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << DEPLOY_SCRIPT
cd ~/goodgo cd ~/goodgo
@@ -389,11 +411,27 @@ jobs:
docker compose -f docker-compose.prod.yml up -d --no-deps --wait web docker compose -f docker-compose.prod.yml up -d --no-deps --wait web
docker compose -f docker-compose.prod.yml up -d --no-deps --wait ai-services docker compose -f docker-compose.prod.yml up -d --no-deps --wait ai-services
docker compose -f docker-compose.prod.yml exec api npx prisma migrate deploy docker compose -f docker-compose.prod.yml exec -T api npx prisma migrate deploy
docker image prune -f docker image prune -f
DEPLOY_SCRIPT DEPLOY_SCRIPT
- name: Sync Nginx configs (production)
env:
DEPLOY_HOST: ${{ secrets.PRODUCTION_HOST }}
DEPLOY_USER: ${{ secrets.PRODUCTION_USER }}
DEPLOY_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}
run: |
scp -i ~/.ssh/deploy_key infra/nginx/*.conf \
"$DEPLOY_USER@$DEPLOY_HOST:/tmp/goodgo-nginx/"
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" << 'NGINX_SCRIPT'
sudo cp /tmp/goodgo-nginx/*.conf /etc/nginx/sites-available/ 2>/dev/null || true
for conf in /etc/nginx/sites-available/*goodgo*; do
[ -f "$conf" ] && sudo ln -sf "$conf" /etc/nginx/sites-enabled/
done
sudo nginx -t && sudo systemctl reload nginx
NGINX_SCRIPT
- name: Verify production deployment - name: Verify production deployment
env: env:
PRODUCTION_URL: ${{ secrets.PRODUCTION_URL }} PRODUCTION_URL: ${{ secrets.PRODUCTION_URL }}

View File

@@ -71,9 +71,12 @@ jobs:
env: env:
DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test DATABASE_URL: postgresql://goodgo:goodgo_test_secret@localhost:5432/goodgo_test
REDIS_URL: redis://localhost:6379 REDIS_URL: redis://localhost:6379
REDIS_HOST: localhost
REDIS_PORT: 6379
TYPESENSE_URL: http://localhost:8108 TYPESENSE_URL: http://localhost:8108
TYPESENSE_HOST: localhost TYPESENSE_HOST: localhost
TYPESENSE_PORT: 8108 TYPESENSE_PORT: 8108
TYPESENSE_PROTOCOL: http
TYPESENSE_API_KEY: ts_ci_key TYPESENSE_API_KEY: ts_ci_key
MINIO_ENDPOINT: localhost MINIO_ENDPOINT: localhost
MINIO_PORT: 9000 MINIO_PORT: 9000
@@ -81,12 +84,28 @@ jobs:
MINIO_SECRET_KEY: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }} MINIO_SECRET_KEY: ${{ vars.CI_MINIO_SECRET_KEY || 'ci_minio_secret_key_32chars!!' }}
MINIO_BUCKET: goodgo-uploads MINIO_BUCKET: goodgo-uploads
NODE_ENV: test NODE_ENV: test
JWT_SECRET: e2e-test-jwt-secret-key CI: true
JWT_REFRESH_SECRET: e2e-test-refresh-secret-key # API and Web ports for Playwright webServer
API_PORT: 3001
WEB_PORT: 3000
API_BASE_URL: http://localhost:3001/api/v1/
WEB_BASE_URL: http://localhost:3000
NEXT_PUBLIC_API_URL: http://localhost:3001/api/v1
JWT_SECRET: e2e-test-jwt-secret-key-minimum-32-chars-long-enough
JWT_REFRESH_SECRET: e2e-test-refresh-secret-key-minimum-32-chars-ok
JWT_EXPIRES_IN: 15m
JWT_REFRESH_EXPIRES_IN: 7d
BCRYPT_ROUNDS: 4
VNPAY_TMN_CODE: TESTCODE VNPAY_TMN_CODE: TESTCODE
VNPAY_HASH_SECRET: TESTHASHSECRET VNPAY_HASH_SECRET: TESTHASHSECRETTESTHASHSECRETTEST
VNPAY_URL: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html VNPAY_URL: https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
VNPAY_RETURN_URL: http://localhost:3000/payment/return VNPAY_RETURN_URL: http://localhost:3000/payment/return
GOOGLE_CLIENT_ID: test-google-client-id
GOOGLE_CLIENT_SECRET: test-google-client-secret
GOOGLE_CALLBACK_URL: http://localhost:3001/api/v1/auth/google/callback
ZALO_APP_ID: test-zalo-app-id
ZALO_APP_SECRET: test-zalo-app-secret
ZALO_CALLBACK_URL: http://localhost:3001/api/v1/auth/zalo/callback
steps: steps:
- name: Checkout - name: Checkout

View File

@@ -1,291 +0,0 @@
# GoodGo Platform AI — Audit Reports Index
**Generated**: 2026-04-11 | **Status**: Wave 10 (Active Development)
---
## Quick Links
### 📋 Main Audit Reports
1. **[COMPREHENSIVE_AUDIT_2026-04-11.md](COMPREHENSIVE_AUDIT_2026-04-11.md)** (768 lines)
- Complete codebase analysis with all 10 required sections
- Detailed module inventory, architecture breakdown, metrics
- Strengths, weaknesses, and actionable recommendations
2. **[AUDIT_SUMMARY_2026-04-11.txt](AUDIT_SUMMARY_2026-04-11.txt)** (Quick Reference)
- Executive summary with key metrics and scores
- Visual breakdown of codebase structure
- Priority recommendations at a glance
---
## Audit Scope (All 10 Requirements Covered)
### ✅ 1. Top-Level Structure
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 1
- **Coverage**: All root directories, 10 config files, monorepo setup
- **Status**: Complete
### ✅ 2. Apps/API Module Analysis
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 2
- **Coverage**: 16 API modules, layer analysis, 788 TypeScript files, 229 tests
- **Findings**: 13 full-stack modules, 3 incomplete (health, metrics, mcp)
### ✅ 3. Apps/Web Frontend
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 3
- **Coverage**: 28 routes across 4 layout groups, 66 components, 16,568 LOC
- **Findings**: Full Next.js 14 implementation, limited unit tests (6 only)
### ✅ 4. Prisma Database Layer
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 4
- **Coverage**: 21 models, 18 enums, 12 migrations, 78 indexes
- **Findings**: Production-ready schema with GDPR compliance, audit logging
### ✅ 5. Shared Libraries (libs/)
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 5
- **Coverage**: AI services (21 Python files), MCP servers (12 TypeScript files)
- **Findings**: AI services minimal, MCP servers are stubs needing implementation
### ✅ 6. E2E Testing
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 6
- **Coverage**: 31 Playwright specs (16 API, 15 Web), test organization
- **Findings**: Good E2E coverage, global setup/teardown configured
### ✅ 7. Configuration Files
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 7
- **Coverage**: 10 root config files, 178-line .env.example, Docker stacks
- **Findings**: Comprehensive configuration documentation
### ✅ 8. Test Coverage Analysis
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 8
- **Coverage**: 745 total test files breakdown by layer and module
- **Findings**: 229 API tests, 6 web tests, 31 E2E specs
### ✅ 9. Documentation
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 9
- **Coverage**: 89 core docs + 81 audit reports in docs/audits/
- **Findings**: Comprehensive documentation trail
### ✅ 10. CI/CD Pipeline
- **File**: COMPREHENSIVE_AUDIT_2026-04-11.md, Section 10
- **Coverage**: 7 GitHub Actions workflows, 13-service Docker stack
- **Findings**: Production-ready DevOps, Kubernetes-ready
---
## Key Findings Summary
### 📊 Codebase Metrics
```
Total Lines of Code: 76,402 LOC
├─ API Backend: 23,926 LOC (31%)
├─ Web Frontend: 16,568 LOC (22%)
├─ Test Files: ~34,100 LOC (45%)
├─ MCP Servers: 984 LOC (1%)
└─ AI Services: 824 LOC (1%)
TypeScript Files: 1,038
Test Files: 745
Documentation: 89 files + 81 audits
Git Commits: 203
```
### 🏗️ Architecture Summary
- **16 NestJS API modules** (13 full-stack with ADIP layers)
- **28 Next.js routes** (public, auth, dashboard, admin)
- **21 Prisma models** (comprehensive domain model)
- **12 database migrations** (schema evolution tracked)
- **7 GitHub Actions workflows** (CI/CD complete)
### 📈 Quality Scores
| Aspect | Score | Status |
|--------|-------|--------|
| Architecture | 9/10 | ✅ Excellent |
| Code Quality | 8/10 | ✅ Good |
| Test Coverage | 7/10 | ⚠️ Needs web tests |
| Documentation | 8/10 | ✅ Comprehensive |
| CI/CD | 9/10 | ✅ Excellent |
| Database | 9/10 | ✅ Excellent |
| Error Handling | 8/10 | ⚠️ Some gaps |
| Performance | 8/10 | ✅ Good |
| Security | 7/10 | ⚠️ Add MFA |
| DevOps | 9/10 | ✅ Excellent |
| **OVERALL** | **8.2/10** | **✅ Production-Ready** |
### 🎯 Key Strengths
1. ✅ Mature DDD + CQRS architecture
2. ✅ 76K LOC of real implementation
3. ✅ 745+ test files (229 API, 31 E2E)
4. ✅ Modern tech stack (NestJS 11, Next.js 14, PostgreSQL 16)
5. ✅ Strong DevOps (Docker, K8s, GitHub Actions)
6. ✅ Excellent documentation (89 docs + 81 audits)
7. ✅ Type-safe TypeScript (strict mode)
8. ✅ 21 models with 78 indexes (optimized)
### ⚠️ Areas for Improvement
1. ⚠️ Incomplete modules (3): health, metrics, mcp
2. ⚠️ Web unit tests: only 6 (needs 50% coverage)
3. ⚠️ MCP servers: stubs only (~50 lines each)
4. ⚠️ Error handling: some CQRS handlers incomplete
5. ⚠️ Security: add field encryption, MFA, rate limiting
---
## Recommendations Priority Matrix
### 🔴 High Priority (DO NOW) — 30-40 hours
1. **Complete incomplete modules** (health, metrics, mcp)
- Implement full ADIP layers for health/metrics
- Real MCP server implementations
- Effort: 5-10 hours
2. **Expand web unit tests to 50% coverage**
- Focus on critical components (auth, listings, search)
- Effort: 10-15 hours
3. **Audit & complete error handling**
- Review remaining CQRS handlers
- Ensure consistent error responses
- Effort: 5 hours
### 🟡 Medium Priority (DO SOON) — 40-60 hours
1. **Add field-level encryption** (PII, payments)
2. **Implement API rate limiting** (per-endpoint quotas)
3. **Add OpenTelemetry tracing** (distributed tracing)
4. **Expand monitoring dashboards** (Grafana)
5. **Performance optimization** (query analysis)
### 🟢 Low Priority (DO LATER) — Future phases
1. GraphQL API (optional)
2. Mobile app (React Native/Flutter)
3. Advanced ML features
4. Multi-tenant support
---
## Development Status
### Current Milestone: Wave 10 (Beta Phase)
- **MVP Phase**: ✅ COMPLETE (Core modules, DDD architecture)
- **Beta Phase**: 🔄 IN PROGRESS (Testing, refinement, monitoring)
- **Production Phase**: ⏳ READY (Pending validation)
- **Scale Phase**: 📋 PLANNED
### Recent Progress (Last 10 commits)
- ✅ Added comprehensive alerting rules (Alertmanager)
- ✅ K6 load testing coverage expanded
- ✅ Error handling added to 51 CQRS handlers
- ✅ Login endpoint fixed (prevented 500 errors)
- ✅ Email alert templates for saved searches
- ✅ Unit tests added for MCP, Inquiries, Leads modules
### Development Velocity
- 203 total commits on master
- ~2 commits/day average
- Consistent feature delivery & bug fixes
---
## Deployment Status
### Ready for:
**MVP Launch** — All core features implemented
**Staging Deployment** — Full CI/CD pipeline configured
**Production** — Pending final validation & load testing
### Infrastructure Status
✅ Local development (docker-compose.yml, 13 services)
✅ CI environment (docker-compose.ci.yml)
✅ Production stack (docker-compose.prod.yml)
✅ Kubernetes manifests (infra/)
✅ Monitoring (Prometheus + Grafana)
✅ Backup/restore (pg-backup + verification)
✅ Load testing (K6 suite)
---
## Technology Stack Summary
| Layer | Technology | Version |
|-------|-----------|---------|
| Backend | NestJS | 11 |
| Frontend | Next.js | 14 |
| Runtime | Node.js | 22+ |
| Database | PostgreSQL | 16 + PostGIS 3.4 |
| Search | Typesense | 27 |
| Cache | Redis | 7 |
| Storage | MinIO | Latest |
| AI/ML | FastAPI | + XGBoost |
| Testing | Playwright | 1.59 |
| Testing | Vitest | Latest |
| CI/CD | GitHub Actions | - |
| Monitoring | Prometheus/Grafana | Latest |
| Package Manager | pnpm | 10.27.0 |
| Build Tool | Turbo | 2.9.4 |
---
## How to Use These Reports
### For Project Managers
- Read: **AUDIT_SUMMARY_2026-04-11.txt** (quick overview)
- Then: **COMPREHENSIVE_AUDIT_2026-04-11.md** sections 1, 8-10
### For Developers
- Read: **COMPREHENSIVE_AUDIT_2026-04-11.md** entire document
- Reference: **AUDIT_SUMMARY_2026-04-11.txt** for quick stats
### For Architects
- Focus: Sections 1-5, 7 of comprehensive audit
- Review: Module completeness, architecture patterns
### For QA/Testers
- Focus: Sections 6, 8 of comprehensive audit
- Review: Test coverage, E2E test organization
### For DevOps/Infrastructure
- Focus: Sections 7, 10 of comprehensive audit
- Review: CI/CD workflows, Docker stack, monitoring
---
## Additional Resources
### In Repository
- `docs/architecture.md` — Detailed system design
- `docs/api-endpoints.md` — REST API reference
- `docs/api-error-codes.md` — Error handling guide
- `docs/deployment.md` — Production deployment guide
- `IMPLEMENTATION_PLAN.md` — Remaining work
- `PROJECT_TRACKER.md` — Development roadmap
- `docs/audits/` — 81 specialized audit reports
### Key Files
- `README.md` — Project overview & quick start
- `CONTRIBUTING.md` — Development conventions
- `CHANGELOG.md` — Version history
---
## Audit Verification Checklist
- [x] Top-level structure reviewed (all root directories)
- [x] apps/api module analysis complete (16 modules, 788 files)
- [x] apps/web frontend mapped (28 routes, 66 components)
- [x] prisma schema analyzed (21 models, 12 migrations)
- [x] libs/ libraries reviewed (AI + MCP servers)
- [x] E2E testing evaluated (31 Playwright specs)
- [x] Configuration files documented (10 root configs)
- [x] Test coverage analyzed (745 total files)
- [x] Documentation surveyed (89 docs + 81 audits)
- [x] CI/CD pipeline reviewed (7 workflows, 13 services)
---
**Audit Conducted**: 2026-04-11
**Status**: ✅ COMPLETE
**Quality Score**: 8.2/10 (Production-Ready)
**Next Review**: Recommend after Wave 10 completion
---
*For questions or clarifications, refer to the comprehensive audit document or contact the development team.*

View File

@@ -1,415 +0,0 @@
# Quick Reference: Pricing/Subscription/Payment System
## Files at a Glance
### 🎨 Frontend
| File | Purpose | Status |
|------|---------|--------|
| `apps/web/app/[locale]/(public)/pricing/page.tsx` | Main pricing page | ✅ Complete |
| `apps/web/lib/subscription-api.ts` | Subscription API client | ✅ Complete |
| `apps/web/lib/payment-api.ts` | Payment API client | ✅ Complete |
| `apps/web/lib/hooks/use-subscription.ts` | Subscription hooks | ✅ Complete |
| `apps/web/lib/hooks/use-payments.ts` | Payment hooks | ✅ Complete |
| `apps/web/app/.../dashboard/payments/page.tsx` | Payment history | ✅ Complete |
### 🔧 Backend
| Directory | Purpose | Status |
|-----------|---------|--------|
| `apps/api/src/modules/subscriptions/` | Subscription CQRS module | ✅ Complete |
| `apps/api/src/modules/payments/` | Payment CQRS module | ✅ Complete |
| `apps/api/src/modules/payments/infrastructure/services/` | Payment gateways (VNPay, MoMo, ZaloPay) | ✅ Complete |
### 📦 Database
| Model | Fields | Relationships |
|-------|--------|---|
| `Plan` | id, tier (unique), name, prices, features, isActive | 1→M Subscription |
| `Subscription` | id, userId (unique), planId, status, periods, cancelledAt | M←1 Plan, 1←1 User |
| `Payment` | id, userId, provider, type, amountVND, status, providerTxId, idempotencyKey | M←1 User |
| `UsageRecord` | id, subscriptionId, metric, count, periods | M←1 Subscription |
---
## Key API Endpoints
### Plans (Public)
```
GET /subscriptions/plans
GET /subscriptions/plans/:tier
```
### Subscriptions (Auth Required)
```
POST /subscriptions # Create new
PUT /subscriptions/upgrade # Upgrade
DELETE /subscriptions # Cancel
GET /subscriptions/quota/:metric # Check quota
POST /subscriptions/usage # Record usage
GET /subscriptions/billing # View history
```
### Payments (Auth + Webhook)
```
POST /payments # Create payment → returns paymentUrl
POST /payments/callback/:provider # Webhook from gateway
GET /payments/:id # Check status
GET /payments # List transactions
POST /payments/:id/refund # Refund (admin)
```
---
## Type Definitions
### Frontend Types
```typescript
// From subscription-api.ts
interface PlanDto {
id: string;
tier: string; // FREE, AGENT_PRO, INVESTOR, ENTERPRISE
name: string;
priceMonthlyVND: string; // In VND
priceYearlyVND: string; // In VND
maxListings: number;
maxSavedSearches: number;
features: Record<string, boolean | number | string>;
isActive: boolean;
}
interface CreateSubscriptionResult {
subscriptionId: string;
planTier: string;
status: string; // ACTIVE, PAST_DUE, CANCELLED, EXPIRED
currentPeriodStart: string; // ISO datetime
currentPeriodEnd: string; // ISO datetime
}
// From payment-api.ts
interface CreatePaymentPayload {
provider: 'VNPAY' | 'MOMO' | 'ZALOPAY' | 'BANK_TRANSFER';
type: 'SUBSCRIPTION' | 'LISTING_FEE' | 'DEPOSIT' | 'FEATURED_LISTING';
amountVND: number; // 1 to 100,000,000,000
description: string;
returnUrl: string; // Redirect after payment
idempotencyKey?: string; // Prevent duplicates
transactionId?: string; // External transaction ID
}
interface CreatePaymentResult {
paymentId: string;
paymentUrl: string; // Redirect user here
providerTxId: string;
}
interface PaymentStatusDto {
id: string;
provider: string;
type: string;
amountVND: string;
status: string; // PENDING, PROCESSING, COMPLETED, FAILED, REFUNDED
providerTxId: string | null;
createdAt: string;
updatedAt: string;
}
```
---
## How to Use in Frontend
### Get Plans
```typescript
import { usePlans } from '@/lib/hooks/use-subscription';
export function MyComponent() {
const { data: plans, isLoading } = usePlans();
return (
<div>
{isLoading ? 'Loading...' : plans?.map(plan => <div>{plan.name}</div>)}
</div>
);
}
```
### Create Payment
```typescript
import { paymentApi } from '@/lib/payment-api';
import { useMutation } from '@tanstack/react-query';
const createPaymentMutation = useMutation({
mutationFn: (payload) => paymentApi.createPayment(payload),
});
// When user clicks "Pay Now"
const handlePayment = async (planTier: string, provider: 'VNPAY' | 'MOMO' | 'ZALOPAY') => {
const result = await createPaymentMutation.mutateAsync({
provider,
type: 'SUBSCRIPTION',
amountVND: 499000,
description: `Subscription to ${planTier}`,
returnUrl: `${window.location.origin}/payment-return`,
idempotencyKey: crypto.randomUUID(),
});
// Redirect to payment gateway
window.location = result.paymentUrl;
};
```
### Check Payment Status (on return page)
```typescript
import { paymentApi } from '@/lib/payment-api';
import { useEffect, useState } from 'react';
export function PaymentReturnPage() {
const searchParams = new URLSearchParams(window.location.search);
const paymentId = searchParams.get('paymentId');
const [status, setStatus] = useState<string>('loading');
useEffect(() => {
if (!paymentId) return;
const poll = async () => {
const payment = await paymentApi.getPaymentStatus(paymentId);
if (payment.status === 'COMPLETED') {
// Create subscription
await subscriptionApi.createSubscription('AGENT_PRO', 'monthly');
setStatus('success');
// Redirect to dashboard
window.location = '/dashboard';
} else if (payment.status === 'FAILED') {
setStatus('failed');
} else {
// Poll again in 2 seconds
setTimeout(poll, 2000);
}
};
poll();
}, [paymentId]);
return <div>{status === 'loading' ? 'Processing payment...' : status}</div>;
}
```
### Create Subscription
```typescript
import { subscriptionApi } from '@/lib/subscription-api';
const result = await subscriptionApi.createSubscription('AGENT_PRO', 'monthly');
console.log(result);
// {
// subscriptionId: 'cuid...',
// planTier: 'AGENT_PRO',
// status: 'ACTIVE',
// currentPeriodStart: '2024-04-12T...',
// currentPeriodEnd: '2024-05-12T...'
// }
```
---
## How to Use in Backend
### Create Plan (Admin)
```sql
INSERT INTO "Plan" (id, tier, name, "priceMonthlyVND", "priceYearlyVND", "maxListings", features, "isActive")
VALUES (
'cuid123',
'AGENT_PRO',
'Agent Pro',
499000,
4990000,
50,
'{"analytics": true, "aiValuation": true}',
true
);
```
### Create Payment (via API)
```
POST /payments
Authorization: Bearer <jwt_token>
Content-Type: application/json
{
"provider": "VNPAY",
"type": "SUBSCRIPTION",
"amountVND": 499000,
"description": "Agent Pro - Monthly",
"returnUrl": "https://goodgo.vn/payment-return",
"idempotencyKey": "550e8400-e29b-41d4-a716-446655440000"
}
Response:
{
"paymentId": "cuid456",
"paymentUrl": "https://sandbox.vnpayment.vn/paymentv2/vpcpay.html?...",
"providerTxId": "cuid456"
}
```
### Handle Payment Callback (Webhook)
```
POST /payments/callback/vnpay?vnp_TxnRef=cuid456&vnp_ResponseCode=00&vnp_SecureHash=...
Response:
{
"orderId": "cuid456",
"isSuccess": true,
"status": "COMPLETED"
}
```
### Create Subscription (via API)
```
POST /subscriptions
Authorization: Bearer <jwt_token>
Content-Type: application/json
{
"planTier": "AGENT_PRO",
"billingCycle": "monthly"
}
Response:
{
"subscriptionId": "cuid789",
"planTier": "AGENT_PRO",
"status": "ACTIVE",
"currentPeriodStart": "2024-04-12T...",
"currentPeriodEnd": "2024-05-12T..."
}
```
---
## Pricing Structure
```
FREE (0 VND)
├── 3 listings
├── 5 saved searches
└── Basic features
AGENT_PRO (499,000 VND/month | 4,990,000/year = -17%)
├── 50 listings
├── 30 saved searches
├── Analytics
├── AI Valuation
├── Priority support
└── Lead management
INVESTOR (999,000 VND/month | 9,990,000/year = -17%)
├── 20 listings
├── 100 saved searches
├── Analytics
├── AI Valuation
├── Market reports
├── Price alerts
└── Portfolio tracking
ENTERPRISE (4,990,000 VND/month | 49,900,000/year = -17%)
├── Unlimited listings
├── Unlimited searches
├── All INVESTOR features
├── API access
├── White label
└── Dedicated support
```
---
## Environment Variables
```bash
# Backend (.env)
VNPAY_TMN_CODE=your_tmn_code
VNPAY_HASH_SECRET=your_hash_secret
VNPAY_BASE_URL=https://sandbox.vnpayment.vn/paymentv2/vpcpay.html
VNPAY_API_URL=https://sandbox.vnpayment.vn/merchant_webapi/api/transaction
MOMO_PARTNER_CODE=your_partner_code
MOMO_ACCESS_KEY=your_access_key
MOMO_SECRET_KEY=your_secret_key
MOMO_ENDPOINT=https://test-payment.momo.vn/v2/gateway/api
ZALOPAY_APP_ID=your_app_id
ZALOPAY_KEY1=your_key1
ZALOPAY_KEY2=your_key2
ZALOPAY_ENDPOINT=https://sandbox.zalopay.com.vn
# Frontend (.env.local)
NEXT_PUBLIC_APP_URL=https://goodgo.vn
```
---
## Testing Credentials
### VNPay Sandbox
```
Terminal: 0
Account: 0968323286
Password: 123456
Card: 9704198526191432198
OTP: 123456
```
### MoMo Sandbox
```
Phone: 0987654321
Password: 123456
OTP: 123456
```
### ZaloPay Sandbox
```
Phone: 0987654321
OTP: 123456
```
---
## Common Errors
| Error | Cause | Solution |
|-------|-------|----------|
| `ConflictException: User already has active subscription` | User trying to create 2nd subscription | Check existing subscription first |
| `ValidationException: Số tiền phải lớn hơn 0` | Amount is 0 or negative | Ensure amount > 0 |
| `NotFoundException: Plan not found` | Plan tier doesn't exist in DB | Check plan is created and isActive=true |
| `Payment gateway failed` | Payment gateway credentials wrong | Verify ENV vars |
| `Cannot complete payment in status X` | Payment already completed/failed | Check idempotencyKey |
| `Idempotency check failed` | Same idempotencyKey used twice | Generate unique UUID each time |
---
## Debugging Checklist
- [ ] Check payment provider credentials in .env
- [ ] Verify idempotencyKey is unique per request
- [ ] Ensure amountVND matches plan price
- [ ] Check returnUrl is publicly accessible
- [ ] Verify JWT token is valid when calling protected endpoints
- [ ] Check payment status with `GET /payments/:id`
- [ ] Review payment provider logs/dashboard
- [ ] Test with sandbox credentials first
- [ ] Verify callback signature matches gateway requirements
- [ ] Check subscription was created after successful payment
---
## Links
- Detailed Audit: `PRICING_CHECKOUT_AUDIT.md`
- Summary: `PRICING_AUDIT_SUMMARY.md`
- Pricing Page: `apps/web/app/[locale]/(public)/pricing/page.tsx`
- Subscriptions Module: `apps/api/src/modules/subscriptions/`
- Payments Module: `apps/api/src/modules/payments/`
- Schema: `prisma/schema.prisma` (lines 451-514)

259
SEED_GENERATION_SCRIPT.ts Normal file
View File

@@ -0,0 +1,259 @@
/**
* GoodGo Platform - Seed User Generation Script
*
* Creates seed users with full login capability (passwords + PII hashing)
*
* Usage:
* export FIELD_ENCRYPTION_KEY='hex-encoded-32-byte-key'
* npx tsx scripts/seed-with-auth.ts
*/
import * as bcrypt from 'bcrypt';
import crypto from 'node:crypto';
import { PrismaClient, UserRole, KYCStatus } from '@prisma/client';
const prisma = new PrismaClient();
// ============================================================================
// Configuration
// ============================================================================
interface SeedUserConfig {
id: string;
phone: string;
email: string;
fullName: string;
password: string;
role: UserRole;
kycStatus: KYCStatus;
isActive: boolean;
}
const SEED_USERS: SeedUserConfig[] = [
{
id: 'seed-admin-001',
phone: '0900000001',
email: 'admin@goodgo.vn',
fullName: 'Admin GoodGo',
password: 'AdminPassword123',
role: UserRole.ADMIN,
kycStatus: 'VERIFIED',
isActive: true,
},
{
id: 'seed-agent-001',
phone: '0900000002',
email: 'agent.nguyen@goodgo.vn',
fullName: 'Nguyễn Văn An',
password: 'AgentPassword123',
role: UserRole.AGENT,
kycStatus: 'VERIFIED',
isActive: true,
},
{
id: 'seed-seller-001',
phone: '0900000005',
email: 'seller.pham@gmail.com',
fullName: 'Phạm Đức Dũng',
password: 'SellerPassword123',
role: UserRole.SELLER,
kycStatus: 'VERIFIED',
isActive: true,
},
{
id: 'seed-buyer-001',
phone: '0900000004',
email: 'buyer.le@gmail.com',
fullName: 'Lê Minh Cường',
password: 'BuyerPassword123',
role: UserRole.BUYER,
kycStatus: 'NONE',
isActive: true,
},
];
// ============================================================================
// Helper Functions
// ============================================================================
/**
* Normalize Vietnamese phone number to +84... format
*/
function normalizeVietnamPhone(phone: string): string {
const cleaned = phone.replace(/[\s.-]/g, '');
if (cleaned.startsWith('+84')) return cleaned;
if (cleaned.startsWith('84')) return `+${cleaned}`;
if (cleaned.startsWith('0')) return `+84${cleaned.slice(1)}`;
throw new Error(`Invalid phone format: ${phone}`);
}
/**
* Derive HMAC key from encryption key (same as field-encryption.ts)
*/
function deriveHmacKey(encryptionKeyHex: string): Buffer {
return crypto.hkdfSync(
'sha256',
Buffer.from(encryptionKeyHex, 'hex'),
Buffer.alloc(0),
Buffer.from('goodgo-field-hash', 'utf8'),
32,
) as unknown as Buffer;
}
/**
* Compute HMAC-SHA256 hash for searchable fields
*/
function computeHash(value: string, hmacKey: Buffer): string {
const normalized = value.toLowerCase().trim();
return crypto.createHmac('sha256', hmacKey).update(normalized).digest('hex');
}
/**
* Hash password with bcrypt
*/
async function hashPassword(password: string): Promise<string> {
if (password.length < 8) {
throw new Error('Password must be at least 8 characters');
}
return bcrypt.hash(password, 12);
}
// ============================================================================
// Main Seeding Function
// ============================================================================
async function seedUsersWithAuth() {
const encryptionKey = process.env['FIELD_ENCRYPTION_KEY'];
if (!encryptionKey) {
throw new Error('FIELD_ENCRYPTION_KEY environment variable is required');
}
const hmacKey = deriveHmacKey(encryptionKey);
const stats = {
created: 0,
skipped: 0,
errors: 0,
};
console.log('🌱 Seeding users with authentication...\n');
for (const userConfig of SEED_USERS) {
try {
// Check if user already exists
const existing = await prisma.user.findUnique({
where: { id: userConfig.id },
});
if (existing) {
console.log(`⏭️ Skipping ${userConfig.fullName} (already exists)`);
stats.skipped++;
continue;
}
// 1. Normalize phone
const normalizedPhone = normalizeVietnamPhone(userConfig.phone);
// 2. Compute hashes
const phoneHash = computeHash(normalizedPhone, hmacKey);
const emailHash = computeHash(userConfig.email, hmacKey);
// 3. Hash password
const passwordHash = await hashPassword(userConfig.password);
// 4. Create user
const user = await prisma.user.create({
data: {
id: userConfig.id,
phone: normalizedPhone,
phoneHash,
email: userConfig.email,
emailHash,
passwordHash,
fullName: userConfig.fullName,
role: userConfig.role,
kycStatus: userConfig.kycStatus,
isActive: userConfig.isActive,
totpEnabled: false,
totpBackupCodes: [],
},
});
console.log(`✅ Created ${user.fullName} (${user.role})`);
console.log(` 📞 Phone: ${normalizedPhone}`);
console.log(` 📧 Email: ${user.email}`);
console.log(` 🔑 Can login with password: ${userConfig.password}\n`);
stats.created++;
} catch (error) {
console.error(
`❌ Error creating ${userConfig.fullName}:`,
error instanceof Error ? error.message : error,
);
stats.errors++;
}
}
// Summary
console.log('📊 Seed Summary');
console.log(` Created: ${stats.created}`);
console.log(` Skipped: ${stats.skipped}`);
console.log(` Errors: ${stats.errors}`);
if (stats.errors === 0 && stats.created > 0) {
console.log('\n✅ Seed completed successfully!');
}
}
// ============================================================================
// Test Login Function (optional)
// ============================================================================
/**
* Verify that a created user can actually log in
*/
async function testLogin(userId: string, password: string): Promise<boolean> {
const user = await prisma.user.findUnique({
where: { id: userId },
});
if (!user || !user.passwordHash) {
console.error('User not found or has no password');
return false;
}
const isValid = await bcrypt.compare(password, user.passwordHash);
return isValid;
}
// ============================================================================
// CLI Entry Point
// ============================================================================
async function main() {
try {
await seedUsersWithAuth();
// Optionally test login
const adminUser = SEED_USERS.find((u) => u.role === UserRole.ADMIN);
if (adminUser) {
console.log('\n🔐 Testing login...');
const loginWorks = await testLogin(adminUser.id, adminUser.password);
if (loginWorks) {
console.log(`✅ Login test passed for ${adminUser.fullName}`);
} else {
console.error(`❌ Login test failed for ${adminUser.fullName}`);
}
}
} catch (error) {
console.error('Fatal error:', error);
process.exit(1);
} finally {
await prisma.$disconnect();
}
}
if (require.main === module) {
main();
}
export { seedUsersWithAuth, testLogin };

View File

@@ -55,17 +55,17 @@ import { AppController } from './app.controller';
{ {
name: 'default', name: 'default',
ttl: 60_000, ttl: 60_000,
limit: process.env['NODE_ENV'] === 'test' ? 10_000 : 60, limit: process.env['NODE_ENV'] === 'test' || process.env['NODE_ENV'] === 'development' ? 10_000 : 60,
}, },
{ {
name: 'auth', name: 'auth',
ttl: 60_000, ttl: 60_000,
limit: process.env['NODE_ENV'] === 'test' ? 10_000 : 10, limit: process.env['NODE_ENV'] === 'test' || process.env['NODE_ENV'] === 'development' ? 10_000 : 10,
}, },
{ {
name: 'payment-callback', name: 'payment-callback',
ttl: 60_000, ttl: 60_000,
limit: process.env['NODE_ENV'] === 'test' ? 10_000 : 20, limit: process.env['NODE_ENV'] === 'test' || process.env['NODE_ENV'] === 'development' ? 10_000 : 20,
}, },
], ],
}), }),

View File

@@ -7,9 +7,14 @@ const isTest = process.env['NODE_ENV'] === 'test';
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const integrations: any[] = []; const integrations: any[] = [];
if (!isTest) { if (!isTest) {
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/consistent-type-imports try {
const { nodeProfilingIntegration } = require('@sentry/profiling-node') as typeof import('@sentry/profiling-node'); // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/consistent-type-imports
integrations.push(nodeProfilingIntegration()); const { nodeProfilingIntegration } = require('@sentry/profiling-node') as typeof import('@sentry/profiling-node');
integrations.push(nodeProfilingIntegration());
} catch {
// Native CPU profiler binary not available — skip profiling gracefully.
console.warn('[Sentry] Profiling skipped — native module not available');
}
} }
Sentry.init({ Sentry.init({

View File

@@ -1,5 +1,11 @@
import './instrument'; import './instrument';
// BigInt cannot be serialized by JSON.stringify by default.
// Polyfill toJSON so Express/NestJS can serialize Prisma BigInt fields.
(BigInt.prototype as any).toJSON = function () {
return this.toString();
};
import { RequestMethod, ValidationPipe } from '@nestjs/common'; import { RequestMethod, ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

View File

@@ -1,8 +1,8 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { type PlanTier } from '@prisma/client'; import { PlanTier } from '@prisma/client';
import { DomainException, NotFoundException, ValidationException, type PrismaService, type LoggerService } from '@modules/shared'; import { DomainException, NotFoundException, ValidationException, PrismaService, LoggerService } from '@modules/shared';
import { SUBSCRIPTION_REPOSITORY, type ISubscriptionRepository } from '@modules/subscriptions'; import { SUBSCRIPTION_REPOSITORY, ISubscriptionRepository } from '@modules/subscriptions';
import { SubscriptionAdjustedEvent } from '../../../domain/events/subscription-adjusted.event'; import { SubscriptionAdjustedEvent } from '../../../domain/events/subscription-adjusted.event';
import { AdjustSubscriptionCommand } from './adjust-subscription.command'; import { AdjustSubscriptionCommand } from './adjust-subscription.command';

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth'; import { USER_REPOSITORY, IUserRepository } from '@modules/auth';
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared'; import { DomainException, NotFoundException, ValidationException, LoggerService } from '@modules/shared';
import { KycApprovedEvent } from '../../../domain/events/kyc-approved.event'; import { KycApprovedEvent } from '../../../domain/events/kyc-approved.event';
import { ApproveKycCommand } from './approve-kyc.command'; import { ApproveKycCommand } from './approve-kyc.command';

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings'; import { LISTING_REPOSITORY, IListingRepository } from '@modules/listings';
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared'; import { DomainException, NotFoundException, ValidationException, LoggerService } from '@modules/shared';
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event'; import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
import { ApproveListingCommand } from './approve-listing.command'; import { ApproveListingCommand } from './approve-listing.command';

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth'; import { USER_REPOSITORY, IUserRepository } from '@modules/auth';
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared'; import { DomainException, NotFoundException, ValidationException, LoggerService } from '@modules/shared';
import { UserBannedEvent } from '../../../domain/events/user-banned.event'; import { UserBannedEvent } from '../../../domain/events/user-banned.event';
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event'; import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
import { BanUserCommand } from './ban-user.command'; import { BanUserCommand } from './ban-user.command';

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings'; import { LISTING_REPOSITORY, IListingRepository } from '@modules/listings';
import { DomainException, ValidationException, type LoggerService } from '@modules/shared'; import { DomainException, ValidationException, LoggerService } from '@modules/shared';
import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event'; import { ListingApprovedEvent } from '../../../domain/events/listing-approved.event';
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event'; import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
import { BulkModerateListingsCommand } from './bulk-moderate-listings.command'; import { BulkModerateListingsCommand } from './bulk-moderate-listings.command';

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth'; import { USER_REPOSITORY, IUserRepository } from '@modules/auth';
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared'; import { DomainException, NotFoundException, ValidationException, LoggerService } from '@modules/shared';
import { KycRejectedEvent } from '../../../domain/events/kyc-rejected.event'; import { KycRejectedEvent } from '../../../domain/events/kyc-rejected.event';
import { RejectKycCommand } from './reject-kyc.command'; import { RejectKycCommand } from './reject-kyc.command';

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { LISTING_REPOSITORY, type IListingRepository } from '@modules/listings'; import { LISTING_REPOSITORY, IListingRepository } from '@modules/listings';
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared'; import { DomainException, NotFoundException, ValidationException, LoggerService } from '@modules/shared';
import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event'; import { ListingRejectedEvent } from '../../../domain/events/listing-rejected.event';
import { RejectListingCommand } from './reject-listing.command'; import { RejectListingCommand } from './reject-listing.command';

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { USER_REPOSITORY, type IUserRepository } from '@modules/auth'; import { USER_REPOSITORY, IUserRepository } from '@modules/auth';
import { DomainException, NotFoundException, ValidationException, type LoggerService } from '@modules/shared'; import { DomainException, NotFoundException, ValidationException, LoggerService } from '@modules/shared';
import { UserBannedEvent } from '../../../domain/events/user-banned.event'; import { UserBannedEvent } from '../../../domain/events/user-banned.event';
import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event'; import { UserUnbannedEvent } from '../../../domain/events/user-unbanned.event';
import { UpdateUserStatusCommand } from './update-user-status.command'; import { UpdateUserStatusCommand } from './update-user-status.command';

View File

@@ -1,16 +1,16 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
import { type LoggerService } from '@modules/shared'; import { LoggerService } from '@modules/shared';
import { type KycApprovedEvent } from '../../domain/events/kyc-approved.event'; import { KycApprovedEvent } from '../../domain/events/kyc-approved.event';
import { type KycRejectedEvent } from '../../domain/events/kyc-rejected.event'; import { KycRejectedEvent } from '../../domain/events/kyc-rejected.event';
import { type ListingApprovedEvent } from '../../domain/events/listing-approved.event'; import { ListingApprovedEvent } from '../../domain/events/listing-approved.event';
import { type ListingRejectedEvent } from '../../domain/events/listing-rejected.event'; import { ListingRejectedEvent } from '../../domain/events/listing-rejected.event';
import { type SubscriptionAdjustedEvent } from '../../domain/events/subscription-adjusted.event'; import { SubscriptionAdjustedEvent } from '../../domain/events/subscription-adjusted.event';
import { type UserBannedEvent } from '../../domain/events/user-banned.event'; import { UserBannedEvent } from '../../domain/events/user-banned.event';
import { type UserUnbannedEvent } from '../../domain/events/user-unbanned.event'; import { UserUnbannedEvent } from '../../domain/events/user-unbanned.event';
import { import {
AUDIT_LOG_REPOSITORY, AUDIT_LOG_REPOSITORY,
type IAuditLogRepository, IAuditLogRepository,
} from '../../domain/repositories/audit-log.repository'; } from '../../domain/repositories/audit-log.repository';
@Injectable() @Injectable()

View File

@@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs'; import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
import { SendNotificationCommand } from '@modules/notifications'; import { SendNotificationCommand } from '@modules/notifications';
import { type LoggerService, type PrismaService } from '@modules/shared'; import { LoggerService, PrismaService } from '@modules/shared';
import { type UserBannedEvent } from '../../domain/events/user-banned.event'; import { UserBannedEvent } from '../../domain/events/user-banned.event';
@Injectable() @Injectable()
export class UserBannedListener { export class UserBannedListener {

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
import { type UserDeactivatedEvent } from '@modules/auth'; import { UserDeactivatedEvent } from '@modules/auth';
import { type LoggerService, type PrismaService } from '@modules/shared'; import { LoggerService, PrismaService } from '@modules/shared';
@Injectable() @Injectable()
export class UserDeactivatedListener { export class UserDeactivatedListener {

View File

@@ -1,9 +1,9 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { import {
AUDIT_LOG_REPOSITORY, AUDIT_LOG_REPOSITORY,
type IAuditLogRepository, IAuditLogRepository,
type AuditLogListResult, type AuditLogListResult,
} from '../../../domain/repositories/audit-log.repository'; } from '../../../domain/repositories/audit-log.repository';
import { GetAuditLogsQuery } from './get-audit-logs.query'; import { GetAuditLogsQuery } from './get-audit-logs.query';

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type DashboardStats } from '../../../domain/repositories/admin-query.repository'; import { ADMIN_QUERY_REPOSITORY, IAdminQueryRepository, DashboardStats } from '../../../domain/repositories/admin-query.repository';
import { GetDashboardStatsQuery } from './get-dashboard-stats.query'; import { GetDashboardStatsQuery } from './get-dashboard-stats.query';
@QueryHandler(GetDashboardStatsQuery) @QueryHandler(GetDashboardStatsQuery)

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type KycQueueResult } from '../../../domain/repositories/admin-query.repository'; import { ADMIN_QUERY_REPOSITORY, IAdminQueryRepository, KycQueueResult } from '../../../domain/repositories/admin-query.repository';
import { GetKycQueueQuery } from './get-kyc-queue.query'; import { GetKycQueueQuery } from './get-kyc-queue.query';
@QueryHandler(GetKycQueueQuery) @QueryHandler(GetKycQueueQuery)

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type ModerationQueueResult } from '../../../domain/repositories/admin-query.repository'; import { ADMIN_QUERY_REPOSITORY, IAdminQueryRepository, ModerationQueueResult } from '../../../domain/repositories/admin-query.repository';
import { GetModerationQueueQuery } from './get-moderation-queue.query'; import { GetModerationQueueQuery } from './get-moderation-queue.query';
@QueryHandler(GetModerationQueueQuery) @QueryHandler(GetModerationQueueQuery)

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type RevenueStatsItem } from '../../../domain/repositories/admin-query.repository'; import { ADMIN_QUERY_REPOSITORY, IAdminQueryRepository, RevenueStatsItem } from '../../../domain/repositories/admin-query.repository';
import { GetRevenueStatsQuery } from './get-revenue-stats.query'; import { GetRevenueStatsQuery } from './get-revenue-stats.query';
@QueryHandler(GetRevenueStatsQuery) @QueryHandler(GetRevenueStatsQuery)

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, NotFoundException, type LoggerService } from '@modules/shared'; import { DomainException, NotFoundException, LoggerService } from '@modules/shared';
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserDetail } from '../../../domain/repositories/admin-query.repository'; import { ADMIN_QUERY_REPOSITORY, IAdminQueryRepository, UserDetail } from '../../../domain/repositories/admin-query.repository';
import { GetUserDetailQuery } from './get-user-detail.query'; import { GetUserDetailQuery } from './get-user-detail.query';
@QueryHandler(GetUserDetailQuery) @QueryHandler(GetUserDetailQuery)

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository, type UserListResult } from '../../../domain/repositories/admin-query.repository'; import { ADMIN_QUERY_REPOSITORY, IAdminQueryRepository, UserListResult } from '../../../domain/repositories/admin-query.repository';
import { GetUsersQuery } from './get-users.query'; import { GetUsersQuery } from './get-users.query';
@QueryHandler(GetUsersQuery) @QueryHandler(GetUsersQuery)

View File

@@ -1,4 +1,4 @@
import { type DomainEvent } from '@modules/shared'; import { DomainEvent } from '@modules/shared';
export class KycApprovedEvent implements DomainEvent { export class KycApprovedEvent implements DomainEvent {
readonly eventName = 'kyc.approved'; readonly eventName = 'kyc.approved';

View File

@@ -1,4 +1,4 @@
import { type DomainEvent } from '@modules/shared'; import { DomainEvent } from '@modules/shared';
export class KycRejectedEvent implements DomainEvent { export class KycRejectedEvent implements DomainEvent {
readonly eventName = 'kyc.rejected'; readonly eventName = 'kyc.rejected';

View File

@@ -1,4 +1,4 @@
import { type DomainEvent } from '@modules/shared'; import { DomainEvent } from '@modules/shared';
export class ListingApprovedEvent implements DomainEvent { export class ListingApprovedEvent implements DomainEvent {
readonly eventName = 'listing.approved_by_admin'; readonly eventName = 'listing.approved_by_admin';

View File

@@ -1,4 +1,4 @@
import { type DomainEvent } from '@modules/shared'; import { DomainEvent } from '@modules/shared';
export class ListingRejectedEvent implements DomainEvent { export class ListingRejectedEvent implements DomainEvent {
readonly eventName = 'listing.rejected_by_admin'; readonly eventName = 'listing.rejected_by_admin';

View File

@@ -1,4 +1,4 @@
import { type DomainEvent } from '@modules/shared'; import { DomainEvent } from '@modules/shared';
export class SubscriptionAdjustedEvent implements DomainEvent { export class SubscriptionAdjustedEvent implements DomainEvent {
readonly eventName = 'subscription.adjusted_by_admin'; readonly eventName = 'subscription.adjusted_by_admin';

View File

@@ -1,4 +1,4 @@
import { type DomainEvent } from '@modules/shared'; import { DomainEvent } from '@modules/shared';
export class UserBannedEvent implements DomainEvent { export class UserBannedEvent implements DomainEvent {
readonly eventName = 'user.banned'; readonly eventName = 'user.banned';

View File

@@ -1,4 +1,4 @@
import { type DomainEvent } from '@modules/shared'; import { DomainEvent } from '@modules/shared';
export class UserUnbannedEvent implements DomainEvent { export class UserUnbannedEvent implements DomainEvent {
readonly eventName = 'user.unbanned'; readonly eventName = 'user.unbanned';

View File

@@ -1,4 +1,4 @@
export { ADMIN_QUERY_REPOSITORY, type IAdminQueryRepository } from './admin-query.repository'; export { ADMIN_QUERY_REPOSITORY, IAdminQueryRepository } from './admin-query.repository';
export type { export type {
ModerationQueueItem, ModerationQueueItem,
ModerationQueueResult, ModerationQueueResult,
@@ -9,7 +9,7 @@ export type {
} from './admin-query.repository'; } from './admin-query.repository';
export { export {
AUDIT_LOG_REPOSITORY, AUDIT_LOG_REPOSITORY,
type IAuditLogRepository, IAuditLogRepository,
type AuditLogEntry, type AuditLogEntry,
type AuditLogListResult, type AuditLogListResult,
type CreateAuditLogInput, type CreateAuditLogInput,

View File

@@ -3,7 +3,7 @@ export { ListingApprovedEvent } from './domain/events/listing-approved.event';
export { ListingRejectedEvent } from './domain/events/listing-rejected.event'; export { ListingRejectedEvent } from './domain/events/listing-rejected.event';
export { export {
AUDIT_LOG_REPOSITORY, AUDIT_LOG_REPOSITORY,
type IAuditLogRepository, IAuditLogRepository,
type AuditLogEntry, type AuditLogEntry,
type AuditLogListResult, type AuditLogListResult,
} from './domain/repositories/audit-log.repository'; } from './domain/repositories/audit-log.repository';

View File

@@ -1,4 +1,4 @@
import { type PrismaService } from '@modules/shared'; import { PrismaService } from '@modules/shared';
import { import {
type DashboardStats, type DashboardStats,
type RevenueStatsItem, type RevenueStatsItem,

View File

@@ -1,5 +1,5 @@
import { type Prisma, type UserRole } from '@prisma/client'; import { Prisma, UserRole } from '@prisma/client';
import { type PrismaService } from '@modules/shared'; import { PrismaService } from '@modules/shared';
import { import {
type UserListResult, type UserListResult,
type UserDetail, type UserDetail,

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type PrismaService } from '@modules/shared'; import { PrismaService } from '@modules/shared';
import { import {
type IAdminQueryRepository, IAdminQueryRepository,
type ModerationQueueResult, type ModerationQueueResult,
type DashboardStats, type DashboardStats,
type RevenueStatsItem, type RevenueStatsItem,

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type AdminAction, type AuditTargetType, type Prisma } from '@prisma/client'; import { AdminAction, AuditTargetType, Prisma } from '@prisma/client';
import { type PrismaService } from '@modules/shared'; import { PrismaService } from '@modules/shared';
import { import {
type IAuditLogRepository, IAuditLogRepository,
type AuditLogEntry, type AuditLogEntry,
type AuditLogListResult, type AuditLogListResult,
type CreateAuditLogInput, type CreateAuditLogInput,

View File

@@ -6,30 +6,30 @@ import {
Query, Query,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { type CommandBus, type QueryBus } from '@nestjs/cqrs'; import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
import { type JwtPayload, CurrentUser, Roles, JwtAuthGuard, RolesGuard } from '@modules/auth'; import { JwtPayload, CurrentUser, Roles, JwtAuthGuard, RolesGuard } from '@modules/auth';
import { ApproveKycCommand } from '../../application/commands/approve-kyc/approve-kyc.command'; import { ApproveKycCommand } from '../../application/commands/approve-kyc/approve-kyc.command';
import { type ApproveKycResult } from '../../application/commands/approve-kyc/approve-kyc.handler'; import { ApproveKycResult } from '../../application/commands/approve-kyc/approve-kyc.handler';
import { ApproveListingCommand } from '../../application/commands/approve-listing/approve-listing.command'; import { ApproveListingCommand } from '../../application/commands/approve-listing/approve-listing.command';
import { type ApproveListingResult } from '../../application/commands/approve-listing/approve-listing.handler'; import { ApproveListingResult } from '../../application/commands/approve-listing/approve-listing.handler';
import { BulkModerateListingsCommand } from '../../application/commands/bulk-moderate-listings/bulk-moderate-listings.command'; import { BulkModerateListingsCommand } from '../../application/commands/bulk-moderate-listings/bulk-moderate-listings.command';
import { type BulkModerateResult } from '../../application/commands/bulk-moderate-listings/bulk-moderate-listings.handler'; import { BulkModerateResult } from '../../application/commands/bulk-moderate-listings/bulk-moderate-listings.handler';
import { RejectKycCommand } from '../../application/commands/reject-kyc/reject-kyc.command'; import { RejectKycCommand } from '../../application/commands/reject-kyc/reject-kyc.command';
import { type RejectKycResult } from '../../application/commands/reject-kyc/reject-kyc.handler'; import { RejectKycResult } from '../../application/commands/reject-kyc/reject-kyc.handler';
import { RejectListingCommand } from '../../application/commands/reject-listing/reject-listing.command'; import { RejectListingCommand } from '../../application/commands/reject-listing/reject-listing.command';
import { type RejectListingResult } from '../../application/commands/reject-listing/reject-listing.handler'; import { RejectListingResult } from '../../application/commands/reject-listing/reject-listing.handler';
import { GetKycQueueQuery } from '../../application/queries/get-kyc-queue/get-kyc-queue.query'; import { GetKycQueueQuery } from '../../application/queries/get-kyc-queue/get-kyc-queue.query';
import { GetModerationQueueQuery } from '../../application/queries/get-moderation-queue/get-moderation-queue.query'; import { GetModerationQueueQuery } from '../../application/queries/get-moderation-queue/get-moderation-queue.query';
import { import {
type ModerationQueueResult, type ModerationQueueResult,
type KycQueueResult, type KycQueueResult,
} from '../../domain/repositories/admin-query.repository'; } from '../../domain/repositories/admin-query.repository';
import { type ApproveKycDto } from '../dto/approve-kyc.dto'; import { ApproveKycDto } from '../dto/approve-kyc.dto';
import { type ApproveListingDto } from '../dto/approve-listing.dto'; import { ApproveListingDto } from '../dto/approve-listing.dto';
import { type BulkModerateDto } from '../dto/bulk-moderate.dto'; import { BulkModerateDto } from '../dto/bulk-moderate.dto';
import { type RejectKycDto } from '../dto/reject-kyc.dto'; import { RejectKycDto } from '../dto/reject-kyc.dto';
import { type RejectListingDto } from '../dto/reject-listing.dto'; import { RejectListingDto } from '../dto/reject-listing.dto';
@ApiTags('admin') @ApiTags('admin')
@ApiBearerAuth('JWT') @ApiBearerAuth('JWT')

View File

@@ -8,15 +8,15 @@ import {
Query, Query,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { type CommandBus, type QueryBus } from '@nestjs/cqrs'; import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery, ApiParam } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery, ApiParam } from '@nestjs/swagger';
import { type JwtPayload, CurrentUser, Roles, JwtAuthGuard, RolesGuard } from '@modules/auth'; import { JwtPayload, CurrentUser, Roles, JwtAuthGuard, RolesGuard } from '@modules/auth';
import { AdjustSubscriptionCommand } from '../../application/commands/adjust-subscription/adjust-subscription.command'; import { AdjustSubscriptionCommand } from '../../application/commands/adjust-subscription/adjust-subscription.command';
import { type AdjustSubscriptionResult } from '../../application/commands/adjust-subscription/adjust-subscription.handler'; import { AdjustSubscriptionResult } from '../../application/commands/adjust-subscription/adjust-subscription.handler';
import { BanUserCommand } from '../../application/commands/ban-user/ban-user.command'; import { BanUserCommand } from '../../application/commands/ban-user/ban-user.command';
import { type BanUserResult } from '../../application/commands/ban-user/ban-user.handler'; import { BanUserResult } from '../../application/commands/ban-user/ban-user.handler';
import { UpdateUserStatusCommand } from '../../application/commands/update-user-status/update-user-status.command'; import { UpdateUserStatusCommand } from '../../application/commands/update-user-status/update-user-status.command';
import { type UpdateUserStatusResult } from '../../application/commands/update-user-status/update-user-status.handler'; import { UpdateUserStatusResult } from '../../application/commands/update-user-status/update-user-status.handler';
import { GetAuditLogsQuery } from '../../application/queries/get-audit-logs/get-audit-logs.query'; import { GetAuditLogsQuery } from '../../application/queries/get-audit-logs/get-audit-logs.query';
import { GetDashboardStatsQuery } from '../../application/queries/get-dashboard-stats/get-dashboard-stats.query'; import { GetDashboardStatsQuery } from '../../application/queries/get-dashboard-stats/get-dashboard-stats.query';
import { GetRevenueStatsQuery } from '../../application/queries/get-revenue-stats/get-revenue-stats.query'; import { GetRevenueStatsQuery } from '../../application/queries/get-revenue-stats/get-revenue-stats.query';
@@ -28,13 +28,13 @@ import {
type UserListResult, type UserListResult,
type UserDetail, type UserDetail,
} from '../../domain/repositories/admin-query.repository'; } from '../../domain/repositories/admin-query.repository';
import { type AuditLogListResult } from '../../domain/repositories/audit-log.repository'; import { AuditLogListResult } from '../../domain/repositories/audit-log.repository';
import { type AdjustSubscriptionDto } from '../dto/adjust-subscription.dto'; import { AdjustSubscriptionDto } from '../dto/adjust-subscription.dto';
import { type BanUserDto } from '../dto/ban-user.dto'; import { BanUserDto } from '../dto/ban-user.dto';
import { type GetAuditLogsQueryDto } from '../dto/get-audit-logs-query.dto'; import { GetAuditLogsQueryDto } from '../dto/get-audit-logs-query.dto';
import { type GetUsersQueryDto } from '../dto/get-users-query.dto'; import { GetUsersQueryDto } from '../dto/get-users-query.dto';
import { type RevenueStatsDto } from '../dto/revenue-stats.dto'; import { RevenueStatsDto } from '../dto/revenue-stats.dto';
import { type UpdateUserStatusDto } from '../dto/update-user-status.dto'; import { UpdateUserStatusDto } from '../dto/update-user-status.dto';
@ApiTags('admin') @ApiTags('admin')
@ApiBearerAuth('JWT') @ApiBearerAuth('JWT')

View File

@@ -1,9 +1,9 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { import {
AGENT_REPOSITORY, AGENT_REPOSITORY,
type IAgentRepository, IAgentRepository,
} from '../../../domain/repositories/agent.repository'; } from '../../../domain/repositories/agent.repository';
import { QualityScoreCalculator } from '../../../domain/services/quality-score.service'; import { QualityScoreCalculator } from '../../../domain/services/quality-score.service';
import { QualityScore } from '../../../domain/value-objects/quality-score.vo'; import { QualityScore } from '../../../domain/value-objects/quality-score.vo';

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs'; import { CommandBus } from '@nestjs/cqrs';
import { OnEvent } from '@nestjs/event-emitter'; import { OnEvent } from '@nestjs/event-emitter';
import { type LoggerService } from '@modules/shared'; import { LoggerService } from '@modules/shared';
import { RecalculateQualityScoreCommand } from '../commands/recalculate-quality-score/recalculate-quality-score.command'; import { RecalculateQualityScoreCommand } from '../commands/recalculate-quality-score/recalculate-quality-score.command';
@Injectable() @Injectable()

View File

@@ -1,10 +1,10 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { DomainException, NotFoundException, type LoggerService } from '@modules/shared'; import { DomainException, NotFoundException, LoggerService } from '@modules/shared';
import { import {
AGENT_REPOSITORY, AGENT_REPOSITORY,
type AgentDashboardData, type AgentDashboardData,
type IAgentRepository, IAgentRepository,
} from '../../../domain/repositories/agent.repository'; } from '../../../domain/repositories/agent.repository';
import { GetAgentDashboardQuery } from './get-agent-dashboard.query'; import { GetAgentDashboardQuery } from './get-agent-dashboard.query';

View File

@@ -1,10 +1,10 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { import {
AGENT_REPOSITORY, AGENT_REPOSITORY,
type AgentPublicProfileData, type AgentPublicProfileData,
type IAgentRepository, IAgentRepository,
} from '../../../domain/repositories/agent.repository'; } from '../../../domain/repositories/agent.repository';
import { GetAgentPublicProfileQuery } from './get-agent-public-profile.query'; import { GetAgentPublicProfileQuery } from './get-agent-public-profile.query';

View File

@@ -1,6 +1,6 @@
import { AggregateRoot } from '@modules/shared'; import { AggregateRoot } from '@modules/shared';
import { QualityScoreUpdatedEvent } from '../events/quality-score-updated.event'; import { QualityScoreUpdatedEvent } from '../events/quality-score-updated.event';
import { type QualityScore } from '../value-objects/quality-score.vo'; import { QualityScore } from '../value-objects/quality-score.vo';
export interface AgentProps { export interface AgentProps {
userId: string; userId: string;

View File

@@ -1,4 +1,4 @@
import { type DomainEvent } from '@modules/shared'; import { DomainEvent } from '@modules/shared';
export class QualityScoreUpdatedEvent implements DomainEvent { export class QualityScoreUpdatedEvent implements DomainEvent {
readonly eventName = 'agent.quality_score_updated'; readonly eventName = 'agent.quality_score_updated';

View File

@@ -1,4 +1,4 @@
import { type AgentEntity } from '../entities/agent.entity'; import { AgentEntity } from '../entities/agent.entity';
export const AGENT_REPOSITORY = Symbol('AGENT_REPOSITORY'); export const AGENT_REPOSITORY = Symbol('AGENT_REPOSITORY');

View File

@@ -1,6 +1,6 @@
export { export {
AGENT_REPOSITORY, AGENT_REPOSITORY,
type IAgentRepository, IAgentRepository,
type AgentDashboardData, type AgentDashboardData,
type AgentPublicProfileData, type AgentPublicProfileData,
type AgentPublicListingItem, type AgentPublicListingItem,

View File

@@ -5,7 +5,7 @@ export { QualityScoreUpdatedEvent } from './domain/events/quality-score-updated.
export { QualityScoreCalculator } from './domain/services/quality-score.service'; export { QualityScoreCalculator } from './domain/services/quality-score.service';
export { export {
AGENT_REPOSITORY, AGENT_REPOSITORY,
type IAgentRepository, IAgentRepository,
type AgentDashboardData, type AgentDashboardData,
type AgentPublicProfileData, type AgentPublicProfileData,
type AgentPublicListingItem, type AgentPublicListingItem,

View File

@@ -1,4 +1,4 @@
import { type PrismaService } from '@modules/shared'; import { PrismaService } from '@modules/shared';
import { import {
type AgentPublicProfileData, type AgentPublicProfileData,
type AgentPublicListingItem, type AgentPublicListingItem,

View File

@@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type PrismaService } from '@modules/shared'; import { PrismaService } from '@modules/shared';
import { AgentEntity } from '../../domain/entities/agent.entity'; import { AgentEntity } from '../../domain/entities/agent.entity';
import { import {
type AgentDashboardData, type AgentDashboardData,
type AgentPublicProfileData, type AgentPublicProfileData,
type IAgentRepository, IAgentRepository,
type QualityScoreInputData, type QualityScoreInputData,
} from '../../domain/repositories/agent.repository'; } from '../../domain/repositories/agent.repository';
import { QualityScore } from '../../domain/value-objects/quality-score.vo'; import { QualityScore } from '../../domain/value-objects/quality-score.vo';

View File

@@ -1,5 +1,5 @@
import { Controller, Get, NotFoundException, Param, Post, UseGuards } from '@nestjs/common'; import { Controller, Get, NotFoundException, Param, Post, UseGuards } from '@nestjs/common';
import { type CommandBus, type QueryBus } from '@nestjs/cqrs'; import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { import {
ApiBearerAuth, ApiBearerAuth,
ApiOperation, ApiOperation,
@@ -17,7 +17,7 @@ import {
import { RecalculateQualityScoreCommand } from '../../application/commands/recalculate-quality-score/recalculate-quality-score.command'; import { RecalculateQualityScoreCommand } from '../../application/commands/recalculate-quality-score/recalculate-quality-score.command';
import { GetAgentDashboardQuery } from '../../application/queries/get-agent-dashboard/get-agent-dashboard.query'; import { GetAgentDashboardQuery } from '../../application/queries/get-agent-dashboard/get-agent-dashboard.query';
import { GetAgentPublicProfileQuery } from '../../application/queries/get-agent-public-profile/get-agent-public-profile.query'; import { GetAgentPublicProfileQuery } from '../../application/queries/get-agent-public-profile/get-agent-public-profile.query';
import { type AgentDashboardData, type AgentPublicProfileData } from '../../domain/repositories/agent.repository'; import { AgentDashboardData, AgentPublicProfileData } from '../../domain/repositories/agent.repository';
@ApiTags('agents') @ApiTags('agents')
@Controller('agents') @Controller('agents')

View File

@@ -1,4 +1,4 @@
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
export class GenerateReportCommand { export class GenerateReportCommand {
constructor( constructor(

View File

@@ -1,9 +1,9 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { import {
MARKET_INDEX_REPOSITORY, MARKET_INDEX_REPOSITORY,
type IMarketIndexRepository, IMarketIndexRepository,
type MarketReportResult, type MarketReportResult,
} from '../../../domain/repositories/market-index.repository'; } from '../../../domain/repositories/market-index.repository';
import { GenerateReportCommand } from './generate-report.command'; import { GenerateReportCommand } from './generate-report.command';

View File

@@ -1,6 +1,6 @@
import { InternalServerErrorException } from '@nestjs/common'; import { InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService } from '@modules/shared'; import { DomainException, LoggerService } from '@modules/shared';
import { TrackEventCommand } from './track-event.command'; import { TrackEventCommand } from './track-event.command';
export interface TrackEventResult { export interface TrackEventResult {

View File

@@ -1,4 +1,4 @@
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
export class UpdateMarketIndexCommand { export class UpdateMarketIndexCommand {
constructor( constructor(

View File

@@ -1,10 +1,10 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type CacheService, CachePrefix, type LoggerService } from '@modules/shared'; import { DomainException, CacheService, CachePrefix, LoggerService } from '@modules/shared';
import { MarketIndexEntity } from '../../../domain/entities/market-index.entity'; import { MarketIndexEntity } from '../../../domain/entities/market-index.entity';
import { import {
MARKET_INDEX_REPOSITORY, MARKET_INDEX_REPOSITORY,
type IMarketIndexRepository, IMarketIndexRepository,
} from '../../../domain/repositories/market-index.repository'; } from '../../../domain/repositories/market-index.repository';
import { UpdateMarketIndexCommand } from './update-market-index.command'; import { UpdateMarketIndexCommand } from './update-market-index.command';

View File

@@ -1,10 +1,10 @@
import { Inject } from '@nestjs/common'; import { Inject } from '@nestjs/common';
import { EventsHandler, type IEventHandler, type CommandBus } from '@nestjs/cqrs'; import { EventsHandler, IEventHandler, CommandBus } from '@nestjs/cqrs';
import { ListingCreatedEvent, ModerateListingCommand } from '@modules/listings'; import { ListingCreatedEvent, ModerateListingCommand } from '@modules/listings';
import { type PrismaService, type LoggerService } from '@modules/shared'; import { PrismaService, LoggerService } from '@modules/shared';
import { import {
AI_SERVICE_CLIENT, AI_SERVICE_CLIENT,
type IAiServiceClient, IAiServiceClient,
} from '../../infrastructure/services/ai-service.client'; } from '../../infrastructure/services/ai-service.client';
const AUTO_REJECT_THRESHOLD = 0.8; const AUTO_REJECT_THRESHOLD = 0.8;

View File

@@ -1,9 +1,9 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, type CacheService, CachePrefix, CacheTTL, Cacheable, type LoggerService } from '@modules/shared'; import { DomainException, CacheService, CachePrefix, CacheTTL, Cacheable, LoggerService } from '@modules/shared';
import { import {
MARKET_INDEX_REPOSITORY, MARKET_INDEX_REPOSITORY,
type IMarketIndexRepository, IMarketIndexRepository,
type DistrictStatsResult, type DistrictStatsResult,
} from '../../../domain/repositories/market-index.repository'; } from '../../../domain/repositories/market-index.repository';
import { GetDistrictStatsQuery } from './get-district-stats.query'; import { GetDistrictStatsQuery } from './get-district-stats.query';

View File

@@ -1,9 +1,9 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, CacheService, CachePrefix, CacheTTL, type LoggerService } from '@modules/shared'; import { DomainException, CacheService, CachePrefix, CacheTTL, LoggerService } from '@modules/shared';
import { import {
MARKET_INDEX_REPOSITORY, MARKET_INDEX_REPOSITORY,
type IMarketIndexRepository, IMarketIndexRepository,
type HeatmapDataPoint, type HeatmapDataPoint,
} from '../../../domain/repositories/market-index.repository'; } from '../../../domain/repositories/market-index.repository';
import { GetHeatmapQuery } from './get-heatmap.query'; import { GetHeatmapQuery } from './get-heatmap.query';

View File

@@ -1,9 +1,9 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, CacheService, CachePrefix, CacheTTL, type LoggerService } from '@modules/shared'; import { DomainException, CacheService, CachePrefix, CacheTTL, LoggerService } from '@modules/shared';
import { import {
MARKET_INDEX_REPOSITORY, MARKET_INDEX_REPOSITORY,
type IMarketIndexRepository, IMarketIndexRepository,
type MarketReportResult, type MarketReportResult,
} from '../../../domain/repositories/market-index.repository'; } from '../../../domain/repositories/market-index.repository';
import { GetMarketReportQuery } from './get-market-report.query'; import { GetMarketReportQuery } from './get-market-report.query';

View File

@@ -1,4 +1,4 @@
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
export class GetMarketReportQuery { export class GetMarketReportQuery {
constructor( constructor(

View File

@@ -1,9 +1,9 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, CacheService, CachePrefix, CacheTTL, type LoggerService } from '@modules/shared'; import { DomainException, CacheService, CachePrefix, CacheTTL, LoggerService } from '@modules/shared';
import { import {
MARKET_INDEX_REPOSITORY, MARKET_INDEX_REPOSITORY,
type IMarketIndexRepository, IMarketIndexRepository,
type PriceTrendPoint, type PriceTrendPoint,
} from '../../../domain/repositories/market-index.repository'; } from '../../../domain/repositories/market-index.repository';
import { GetPriceTrendQuery } from './get-price-trend.query'; import { GetPriceTrendQuery } from './get-price-trend.query';

View File

@@ -1,4 +1,4 @@
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
export class GetPriceTrendQuery { export class GetPriceTrendQuery {
constructor( constructor(

View File

@@ -1,9 +1,9 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { QueryHandler, type IQueryHandler } from '@nestjs/cqrs'; import { QueryHandler, IQueryHandler } from '@nestjs/cqrs';
import { DomainException, CacheService, CachePrefix, CacheTTL, type LoggerService } from '@modules/shared'; import { DomainException, CacheService, CachePrefix, CacheTTL, LoggerService } from '@modules/shared';
import { import {
AVM_SERVICE, AVM_SERVICE,
type IAVMService, IAVMService,
type ValuationResult, type ValuationResult,
} from '../../../domain/services/avm-service'; } from '../../../domain/services/avm-service';
import { GetValuationQuery } from './get-valuation.query'; import { GetValuationQuery } from './get-valuation.query';

View File

@@ -1,4 +1,4 @@
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
export class GetValuationQuery { export class GetValuationQuery {
constructor( constructor(

View File

@@ -1,4 +1,4 @@
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
import { AggregateRoot } from '@modules/shared'; import { AggregateRoot } from '@modules/shared';
import { MarketIndexUpdatedEvent } from '../events/market-index-updated.event'; import { MarketIndexUpdatedEvent } from '../events/market-index-updated.event';

View File

@@ -1,4 +1,4 @@
import { type DomainEvent } from '@modules/shared'; import { DomainEvent } from '@modules/shared';
export class MarketIndexUpdatedEvent implements DomainEvent { export class MarketIndexUpdatedEvent implements DomainEvent {
readonly eventName = 'market-index.updated'; readonly eventName = 'market-index.updated';

View File

@@ -1,2 +1,2 @@
export { MARKET_INDEX_REPOSITORY, type IMarketIndexRepository, type MarketReportResult, type HeatmapDataPoint, type PriceTrendPoint, type DistrictStatsResult } from './market-index.repository'; export { MARKET_INDEX_REPOSITORY, IMarketIndexRepository, type MarketReportResult, type HeatmapDataPoint, type PriceTrendPoint, type DistrictStatsResult } from './market-index.repository';
export { VALUATION_REPOSITORY, type IValuationRepository } from './valuation.repository'; export { VALUATION_REPOSITORY, IValuationRepository } from './valuation.repository';

View File

@@ -1,5 +1,5 @@
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
import { type MarketIndexEntity } from '../entities/market-index.entity'; import { MarketIndexEntity } from '../entities/market-index.entity';
export const MARKET_INDEX_REPOSITORY = Symbol('MARKET_INDEX_REPOSITORY'); export const MARKET_INDEX_REPOSITORY = Symbol('MARKET_INDEX_REPOSITORY');

View File

@@ -1,4 +1,4 @@
import { type ValuationEntity } from '../entities/valuation.entity'; import { ValuationEntity } from '../entities/valuation.entity';
export const VALUATION_REPOSITORY = Symbol('VALUATION_REPOSITORY'); export const VALUATION_REPOSITORY = Symbol('VALUATION_REPOSITORY');

View File

@@ -1,4 +1,4 @@
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
export const AVM_SERVICE = Symbol('AVM_SERVICE'); export const AVM_SERVICE = Symbol('AVM_SERVICE');

View File

@@ -1 +1 @@
export { AVM_SERVICE, type IAVMService, type AVMParams, type ValuationResult, type Comparable } from './avm-service'; export { AVM_SERVICE, IAVMService, type AVMParams, type ValuationResult, type Comparable } from './avm-service';

View File

@@ -1,3 +1,3 @@
export { AnalyticsModule } from './analytics.module'; export { AnalyticsModule } from './analytics.module';
export { MARKET_INDEX_REPOSITORY, type IMarketIndexRepository } from './domain/repositories/market-index.repository'; export { MARKET_INDEX_REPOSITORY, IMarketIndexRepository } from './domain/repositories/market-index.repository';
export { VALUATION_REPOSITORY, type IValuationRepository } from './domain/repositories/valuation.repository'; export { VALUATION_REPOSITORY, IValuationRepository } from './domain/repositories/valuation.repository';

View File

@@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type MarketIndex as PrismaMarketIndex, type PropertyType } from '@prisma/client'; import { MarketIndex as PrismaMarketIndex, PropertyType } from '@prisma/client';
import { type PrismaService } from '@modules/shared'; import { PrismaService } from '@modules/shared';
import { MarketIndexEntity, type MarketIndexProps } from '../../domain/entities/market-index.entity'; import { MarketIndexEntity, MarketIndexProps } from '../../domain/entities/market-index.entity';
import { import {
type IMarketIndexRepository, IMarketIndexRepository,
type MarketReportResult, type MarketReportResult,
type HeatmapDataPoint, type HeatmapDataPoint,
type PriceTrendPoint, type PriceTrendPoint,

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type Prisma, type Valuation as PrismaValuation } from '@prisma/client'; import { Prisma, Valuation as PrismaValuation } from '@prisma/client';
import { type PrismaService } from '@modules/shared'; import { PrismaService } from '@modules/shared';
import { ValuationEntity, type ValuationProps } from '../../domain/entities/valuation.entity'; import { ValuationEntity, ValuationProps } from '../../domain/entities/valuation.entity';
import { type IValuationRepository } from '../../domain/repositories/valuation.repository'; import { IValuationRepository } from '../../domain/repositories/valuation.repository';
@Injectable() @Injectable()
export class PrismaValuationRepository implements IValuationRepository { export class PrismaValuationRepository implements IValuationRepository {

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type LoggerService } from '@modules/shared'; import { LoggerService } from '@modules/shared';
export interface AiPredictRequest { export interface AiPredictRequest {
area: number; area: number;

View File

@@ -1,5 +1,5 @@
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
import { type Comparable } from '../../domain/services/avm-service'; import { Comparable } from '../../domain/services/avm-service';
const DEFAULT_RADIUS_METERS = 2000; const DEFAULT_RADIUS_METERS = 2000;

View File

@@ -1,17 +1,17 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { type PrismaService, type LoggerService } from '@modules/shared'; import { PrismaService, LoggerService } from '@modules/shared';
import { import {
type IAVMService, IAVMService,
type AVMParams, type AVMParams,
type ValuationResult, type ValuationResult,
type Comparable, type Comparable,
} from '../../domain/services/avm-service'; } from '../../domain/services/avm-service';
import { import {
AI_SERVICE_CLIENT, AI_SERVICE_CLIENT,
type IAiServiceClient, IAiServiceClient,
type AiPredictRequest, type AiPredictRequest,
} from './ai-service.client'; } from './ai-service.client';
import { type PrismaAVMService } from './prisma-avm.service'; import { PrismaAVMService } from './prisma-avm.service';
@Injectable() @Injectable()
export class HttpAVMService implements IAVMService { export class HttpAVMService implements IAVMService {

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type CommandBus } from '@nestjs/cqrs'; import { CommandBus } from '@nestjs/cqrs';
import { Cron, CronExpression } from '@nestjs/schedule'; import { Cron, CronExpression } from '@nestjs/schedule';
import { PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
import { type PrismaService, type LoggerService } from '@modules/shared'; import { PrismaService, LoggerService } from '@modules/shared';
import { UpdateMarketIndexCommand } from '../../application/commands/update-market-index/update-market-index.command'; import { UpdateMarketIndexCommand } from '../../application/commands/update-market-index/update-market-index.command';
interface MarketStats { interface MarketStats {

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { type PropertyType } from '@prisma/client'; import { PropertyType } from '@prisma/client';
import { type PrismaService } from '@modules/shared'; import { PrismaService } from '@modules/shared';
import { import {
type IAVMService, IAVMService,
type AVMParams, type AVMParams,
type ValuationResult, type ValuationResult,
type Comparable, type Comparable,

View File

@@ -4,25 +4,25 @@ import {
Query, Query,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { type QueryBus } from '@nestjs/cqrs'; import { QueryBus } from '@nestjs/cqrs';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '@modules/auth'; import { JwtAuthGuard } from '@modules/auth';
import { RequireQuota, QuotaGuard } from '@modules/subscriptions'; import { RequireQuota, QuotaGuard } from '@modules/subscriptions';
import { type DistrictStatsDto } from '../../application/queries/get-district-stats/get-district-stats.handler'; import { DistrictStatsDto } from '../../application/queries/get-district-stats/get-district-stats.handler';
import { GetDistrictStatsQuery } from '../../application/queries/get-district-stats/get-district-stats.query'; import { GetDistrictStatsQuery } from '../../application/queries/get-district-stats/get-district-stats.query';
import { type HeatmapDto } from '../../application/queries/get-heatmap/get-heatmap.handler'; import { HeatmapDto } from '../../application/queries/get-heatmap/get-heatmap.handler';
import { GetHeatmapQuery } from '../../application/queries/get-heatmap/get-heatmap.query'; import { GetHeatmapQuery } from '../../application/queries/get-heatmap/get-heatmap.query';
import { type MarketReportDto } from '../../application/queries/get-market-report/get-market-report.handler'; import { MarketReportDto } from '../../application/queries/get-market-report/get-market-report.handler';
import { GetMarketReportQuery } from '../../application/queries/get-market-report/get-market-report.query'; import { GetMarketReportQuery } from '../../application/queries/get-market-report/get-market-report.query';
import { type PriceTrendDto } from '../../application/queries/get-price-trend/get-price-trend.handler'; import { PriceTrendDto } from '../../application/queries/get-price-trend/get-price-trend.handler';
import { GetPriceTrendQuery } from '../../application/queries/get-price-trend/get-price-trend.query'; import { GetPriceTrendQuery } from '../../application/queries/get-price-trend/get-price-trend.query';
import { type ValuationDto } from '../../application/queries/get-valuation/get-valuation.handler'; import { ValuationDto } from '../../application/queries/get-valuation/get-valuation.handler';
import { GetValuationQuery } from '../../application/queries/get-valuation/get-valuation.query'; import { GetValuationQuery } from '../../application/queries/get-valuation/get-valuation.query';
import { type GetDistrictStatsDto } from '../dto/get-district-stats.dto'; import { GetDistrictStatsDto } from '../dto/get-district-stats.dto';
import { type GetHeatmapDto } from '../dto/get-heatmap.dto'; import { GetHeatmapDto } from '../dto/get-heatmap.dto';
import { type GetMarketReportDto } from '../dto/get-market-report.dto'; import { GetMarketReportDto } from '../dto/get-market-report.dto';
import { type GetPriceTrendDto } from '../dto/get-price-trend.dto'; import { GetPriceTrendDto } from '../dto/get-price-trend.dto';
import { type GetValuationDto } from '../dto/get-valuation.dto'; import { GetValuationDto } from '../dto/get-valuation.dto';
@ApiTags('analytics') @ApiTags('analytics')
@Controller('analytics') @Controller('analytics')

View File

@@ -1,6 +1,6 @@
import { InternalServerErrorException } from '@nestjs/common'; import { InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { type LoggerService, type PrismaService, DomainException, NotFoundException, ValidationException } from '@modules/shared'; import { LoggerService, PrismaService, DomainException, NotFoundException, ValidationException } from '@modules/shared';
import { CancelUserDeletionCommand } from './cancel-user-deletion.command'; import { CancelUserDeletionCommand } from './cancel-user-deletion.command';
@CommandHandler(CancelUserDeletionCommand) @CommandHandler(CancelUserDeletionCommand)

View File

@@ -1,8 +1,8 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService, UnauthorizedException, ValidationException } from '@modules/shared'; import { DomainException, LoggerService, UnauthorizedException, ValidationException } from '@modules/shared';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository'; import { USER_REPOSITORY, IUserRepository } from '../../../domain/repositories/user.repository';
import { type MfaService } from '../../../infrastructure/services/mfa.service'; import { MfaService } from '../../../infrastructure/services/mfa.service';
import { DisableMfaCommand } from './disable-mfa.command'; import { DisableMfaCommand } from './disable-mfa.command';
@CommandHandler(DisableMfaCommand) @CommandHandler(DisableMfaCommand)

View File

@@ -1,6 +1,6 @@
import { InternalServerErrorException } from '@nestjs/common'; import { InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { type LoggerService, type PrismaService, DomainException, NotFoundException } from '@modules/shared'; import { LoggerService, PrismaService, DomainException, NotFoundException } from '@modules/shared';
import { ExportUserDataCommand } from './export-user-data.command'; import { ExportUserDataCommand } from './export-user-data.command';
export interface UserDataExport { export interface UserDataExport {

View File

@@ -1,7 +1,7 @@
import { InternalServerErrorException } from '@nestjs/common'; import { InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { type LoggerService, type PrismaService, DomainException, NotFoundException } from '@modules/shared'; import { LoggerService, PrismaService, DomainException, NotFoundException } from '@modules/shared';
import { ForceDeleteUserCommand } from './force-delete-user.command'; import { ForceDeleteUserCommand } from './force-delete-user.command';
@CommandHandler(ForceDeleteUserCommand) @CommandHandler(ForceDeleteUserCommand)

View File

@@ -1,12 +1,12 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { createId } from '@paralleldrive/cuid2'; import { createId } from '@paralleldrive/cuid2';
import { type LoggerService, DomainException } from '@modules/shared'; import { LoggerService, DomainException } from '@modules/shared';
import { import {
MFA_CHALLENGE_REPOSITORY, MFA_CHALLENGE_REPOSITORY,
type IMfaChallengeRepository, IMfaChallengeRepository,
} from '../../../domain/repositories/mfa-challenge.repository'; } from '../../../domain/repositories/mfa-challenge.repository';
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service'; import { TokenService, TokenPair } from '../../../infrastructure/services/token.service';
import { LoginUserCommand } from './login-user.command'; import { LoginUserCommand } from './login-user.command';
const MFA_CHALLENGE_TTL_MINUTES = 5; const MFA_CHALLENGE_TTL_MINUTES = 5;

View File

@@ -1,6 +1,6 @@
import { InternalServerErrorException } from '@nestjs/common'; import { InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type CommandBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, CommandBus, ICommandHandler } from '@nestjs/cqrs';
import { type LoggerService, type PrismaService, DomainException } from '@modules/shared'; import { LoggerService, PrismaService, DomainException } from '@modules/shared';
import { ForceDeleteUserCommand } from '../force-delete-user/force-delete-user.command'; import { ForceDeleteUserCommand } from '../force-delete-user/force-delete-user.command';
import { ProcessScheduledDeletionsCommand } from './process-scheduled-deletions.command'; import { ProcessScheduledDeletionsCommand } from './process-scheduled-deletions.command';

View File

@@ -1,8 +1,8 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { type LoggerService, DomainException, UnauthorizedException } from '@modules/shared'; import { LoggerService, DomainException, UnauthorizedException } from '@modules/shared';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository'; import { USER_REPOSITORY, IUserRepository } from '../../../domain/repositories/user.repository';
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service'; import { TokenService, TokenPair } from '../../../infrastructure/services/token.service';
import { RefreshTokenCommand } from './refresh-token.command'; import { RefreshTokenCommand } from './refresh-token.command';
@CommandHandler(RefreshTokenCommand) @CommandHandler(RefreshTokenCommand)

View File

@@ -1,13 +1,13 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs';
import { createId } from '@paralleldrive/cuid2'; import { createId } from '@paralleldrive/cuid2';
import { ConflictException, DomainException, type LoggerService, ValidationException } from '@modules/shared'; import { ConflictException, DomainException, LoggerService, ValidationException } from '@modules/shared';
import { UserEntity } from '../../../domain/entities/user.entity'; import { UserEntity } from '../../../domain/entities/user.entity';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository'; import { USER_REPOSITORY, IUserRepository } from '../../../domain/repositories/user.repository';
import { Email } from '../../../domain/value-objects/email.vo'; import { Email } from '../../../domain/value-objects/email.vo';
import { HashedPassword } from '../../../domain/value-objects/hashed-password.vo'; import { HashedPassword } from '../../../domain/value-objects/hashed-password.vo';
import { Phone } from '../../../domain/value-objects/phone.vo'; import { Phone } from '../../../domain/value-objects/phone.vo';
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service'; import { TokenService, TokenPair } from '../../../infrastructure/services/token.service';
import { RegisterUserCommand } from './register-user.command'; import { RegisterUserCommand } from './register-user.command';
@CommandHandler(RegisterUserCommand) @CommandHandler(RegisterUserCommand)

View File

@@ -1,6 +1,6 @@
import { InternalServerErrorException } from '@nestjs/common'; import { InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { type LoggerService, type PrismaService, DomainException, NotFoundException, ValidationException } from '@modules/shared'; import { LoggerService, PrismaService, DomainException, NotFoundException, ValidationException } from '@modules/shared';
import { RequestUserDeletionCommand } from './request-user-deletion.command'; import { RequestUserDeletionCommand } from './request-user-deletion.command';
const DELETION_GRACE_PERIOD_DAYS = 30; const DELETION_GRACE_PERIOD_DAYS = 30;

View File

@@ -1,8 +1,8 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService, ValidationException } from '@modules/shared'; import { DomainException, LoggerService, ValidationException } from '@modules/shared';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository'; import { USER_REPOSITORY, IUserRepository } from '../../../domain/repositories/user.repository';
import { type MfaService, type MfaSetupResult } from '../../../infrastructure/services/mfa.service'; import { MfaService, MfaSetupResult } from '../../../infrastructure/services/mfa.service';
import { SetupMfaCommand } from './setup-mfa.command'; import { SetupMfaCommand } from './setup-mfa.command';
export interface SetupMfaResultDto { export interface SetupMfaResultDto {

View File

@@ -1,13 +1,13 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService, UnauthorizedException } from '@modules/shared'; import { DomainException, LoggerService, UnauthorizedException } from '@modules/shared';
import { import {
MFA_CHALLENGE_REPOSITORY, MFA_CHALLENGE_REPOSITORY,
type IMfaChallengeRepository, IMfaChallengeRepository,
} from '../../../domain/repositories/mfa-challenge.repository'; } from '../../../domain/repositories/mfa-challenge.repository';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository'; import { USER_REPOSITORY, IUserRepository } from '../../../domain/repositories/user.repository';
import { type MfaService } from '../../../infrastructure/services/mfa.service'; import { MfaService } from '../../../infrastructure/services/mfa.service';
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service'; import { TokenService, TokenPair } from '../../../infrastructure/services/token.service';
import { UseBackupCodeCommand } from './use-backup-code.command'; import { UseBackupCodeCommand } from './use-backup-code.command';
@CommandHandler(UseBackupCodeCommand) @CommandHandler(UseBackupCodeCommand)

View File

@@ -1,7 +1,7 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService, NotFoundException, CacheService, CachePrefix } from '@modules/shared'; import { DomainException, LoggerService, NotFoundException, CacheService, CachePrefix } from '@modules/shared';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository'; import { USER_REPOSITORY, IUserRepository } from '../../../domain/repositories/user.repository';
import { VerifyKycCommand } from './verify-kyc.command'; import { VerifyKycCommand } from './verify-kyc.command';
@CommandHandler(VerifyKycCommand) @CommandHandler(VerifyKycCommand)

View File

@@ -1,13 +1,13 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService, UnauthorizedException } from '@modules/shared'; import { DomainException, LoggerService, UnauthorizedException } from '@modules/shared';
import { import {
MFA_CHALLENGE_REPOSITORY, MFA_CHALLENGE_REPOSITORY,
type IMfaChallengeRepository, IMfaChallengeRepository,
} from '../../../domain/repositories/mfa-challenge.repository'; } from '../../../domain/repositories/mfa-challenge.repository';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository'; import { USER_REPOSITORY, IUserRepository } from '../../../domain/repositories/user.repository';
import { type MfaService } from '../../../infrastructure/services/mfa.service'; import { MfaService } from '../../../infrastructure/services/mfa.service';
import { type TokenService, type TokenPair } from '../../../infrastructure/services/token.service'; import { TokenService, TokenPair } from '../../../infrastructure/services/token.service';
import { VerifyMfaChallengeCommand } from './verify-mfa-challenge.command'; import { VerifyMfaChallengeCommand } from './verify-mfa-challenge.command';
@CommandHandler(VerifyMfaChallengeCommand) @CommandHandler(VerifyMfaChallengeCommand)

View File

@@ -1,8 +1,8 @@
import { Inject, InternalServerErrorException } from '@nestjs/common'; import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { DomainException, type LoggerService, ValidationException } from '@modules/shared'; import { DomainException, LoggerService, ValidationException } from '@modules/shared';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository'; import { USER_REPOSITORY, IUserRepository } from '../../../domain/repositories/user.repository';
import { type MfaService } from '../../../infrastructure/services/mfa.service'; import { MfaService } from '../../../infrastructure/services/mfa.service';
import { VerifyMfaSetupCommand } from './verify-mfa-setup.command'; import { VerifyMfaSetupCommand } from './verify-mfa-setup.command';
export interface VerifyMfaSetupResultDto { export interface VerifyMfaSetupResultDto {

View File

@@ -1,6 +1,6 @@
import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { Injectable, InternalServerErrorException } from '@nestjs/common';
import { type IQueryHandler, QueryHandler } from '@nestjs/cqrs'; import { IQueryHandler, QueryHandler } from '@nestjs/cqrs';
import { type PrismaService, DomainException, type LoggerService } from '@modules/shared'; import { PrismaService, DomainException, LoggerService } from '@modules/shared';
import { GetAgentByUserIdQuery } from './get-agent-by-user-id.query'; import { GetAgentByUserIdQuery } from './get-agent-by-user-id.query';
export interface AgentDto { export interface AgentDto {

Some files were not shown because too many files have changed in this diff Show More