- Add TOTP-based MFA with setup, verify, disable, backup codes, and challenge flow - Add PII field encryption middleware with AES-256-GCM and deterministic search hashes - Add agents, inquiries, and leads domain modules with entities, events, value objects - Add web dashboard pages for inquiries and leads with detail dialogs - Add 30+ component tests (valuation, charts, listings, search, providers, UI) - Add Prisma migrations for encryption hash columns and MFA TOTP support - Fix all ESLint errors (unused imports, duplicate imports, lint auto-fixes) - Update dependencies and lock file - Clean up obsolete exploration/QA docs, add audit documentation Co-Authored-By: Paperclip <noreply@paperclip.ing>
16 KiB
16 KiB
GoodGo Platform AI - Technical Reference & Deep Dive
For Developers & Architects
BACKEND MODULE HIERARCHY
Core Module Dependencies
SharedModule (lowest level)
├── Infrastructure Services
├── Middleware & Guards
├── Decorators & Utilities
└── Domain Enums & Types
↓
├→ AuthModule
├→ HealthModule
└→ All Feature Modules
├→ AdminModule (audit, user management)
├→ AgentsModule (agent profiles, specialized deals)
├→ AnalyticsModule (market reports, valuation history)
├→ InquiriesModule (property inquiries)
├→ LeadsModule (agent leads management)
├→ ListingsModule (property listings)
├→ NotificationsModule (FCM push, email)
├→ PaymentsModule (VNPay integration)
├→ ReviewsModule (property reviews)
├→ SearchModule (Typesense full-text search)
├→ SubscriptionsModule (billing, usage metering)
└→ MetricsModule (Prometheus metrics)
DOMAIN MODELS - RELATIONSHIPS
User Role Hierarchy
User (root entity)
├── Role: BUYER → Can browse, search, inquire, purchase
├── Role: SELLER → Can create listings, receive inquiries, sell
├── Role: AGENT → Extends Seller + lead management
└── Role: ADMIN → All permissions + moderation
Listing Workflow
User (SELLER)
↓ creates
Property + PropertyMedia
↓ associated with
Listing (status: DRAFT → PUBLISHED → SOLD → ARCHIVED)
↓ receives
Inquiry (from BUYER/AGENT)
↓ converts to
Transaction (buyer-seller exchange)
↓ followed by
Review + UsageRecord (analytics)
Payment Flow
User (Subscription Start)
↓
Plan (monthly/yearly pricing)
↓
Subscription (active/cancelled/expired)
↓
Payment (processed via VNPay)
├── Idempotency Key (prevents duplicates)
└── Status Tracking
↓
UsageRecord (track consumed resources)
AUTHENTICATION FLOW
JWT Token Lifecycle
1. User Login (email + password OR OAuth)
└→ Verify credentials (bcrypt hash)
2. Generate Tokens
├→ AccessToken (15 min, bearer auth)
└→ RefreshToken (7 days, stored in DB)
└→ Token Family (refresh rotation)
3. Return to Client
└→ Set Secure HTTP-Only Cookie (refresh token)
4. API Access
├→ Authorization: Bearer <accessToken>
├→ Guard validates JWT signature
└→ Inject user context into request
5. Token Refresh
├→ Client sends refresh token
├→ Verify token family (revocation check)
├→ Rotate token (issue new family)
└→ Return new access token
DATABASE SCHEMA - KEY INDEXES
Query Optimization Strategy
User Table:
├── idx_user_role (BUYER/SELLER/AGENT/ADMIN filtering)
├── idx_user_kyc_status (compliance checks)
├── idx_user_active (active user queries)
├── idx_user_deleted_at (soft delete filtering)
└── idx_role_active_created (complex queries: role + active + order by)
Listing Table:
├── idx_listing_status (published, archived, sold filtering)
├── idx_listing_user_created (user's listings ordered)
└── idx_listing_location_geo (PostGIS spatial queries)
Payment Table:
├── idx_payment_user_status (user's payment history)
├── idx_payment_idempotency (duplicate prevention)
└── idx_payment_external_ref (payment gateway reconciliation)
Search Optimization:
└── Typesense (full-text + geo-search, delegated from DB)
SECURITY LAYERS - DETAILED
Layer 1: Network Level
HTTP Request
↓
Helmet (Express middleware)
├── Content-Security-Policy
│ └── Blocks inline scripts, restricts origins
├── X-Frame-Options: DENY
│ └── Prevents clickjacking
├── Strict-Transport-Security (HSTS)
│ └── Forces HTTPS for 31536000 seconds
├── X-Content-Type-Options: nosniff
│ └── Prevents MIME-sniffing
└── Referrer-Policy: strict-origin-when-cross-origin
└── Controls referrer leaks
Layer 2: Application Level
Request Processing
↓
1. CORS Validation
└── Whitelist check (process.env.CORS_ORIGINS)
2. CSRF Protection
├── Read (GET): Set __Host-X-CSRF-Token cookie
└── Write (POST/PUT/PATCH/DELETE):
├── Verify X-CSRF-Token header
└── Validate cookie matches header (double-submit)
3. Input Sanitization
├── Remove XSS vectors (sanitize-html)
├── Whitelist validation (class-validator)
└── Type coercion (class-transformer)
4. Rate Limiting
├── Global: 60 req/min per IP
├── Auth: 10 req/min per IP (login brute-force protection)
└── Payments: 20 req/min per IP (webhook replay protection)
Layer 3: Data Level
Field Encryption (PII Protection)
├── FieldEncryptionService
│ ├── AES-256-GCM encryption
│ ├── Field-level (can query by hash)
│ └── Key derivation from master secret
├── Email: Encrypted + hashed (both in DB)
├── Phone: Encrypted + hashed (both in DB)
└── KYC Data: Encrypted JSON storage
Audit Trail
├── AdminAuditLog captures:
│ ├── User ID (who)
│ ├── Action (what)
│ ├── Target entity (where)
│ ├── Changes (before/after)
│ └── Timestamp (when)
└── Queryable for compliance
Layer 4: Authorization
Route Handler
↓
@UseGuards(JwtGuard, RoleGuard)
├── Extract JWT from Authorization header
├── Validate signature (HS256)
├── Check token expiration
├── Inject user context (request.user)
└── Verify role (BUYER/SELLER/AGENT/ADMIN)
└── Reject if insufficient permissions
CQRS PATTERN IMPLEMENTATION
Command Pattern (State Changes)
CreateListingCommand
├── Input: CreateListingDTO
├── Handler: CreateListingCommandHandler
│ ├── Validate inputs
│ ├── Check user permissions
│ ├── Create Property entity
│ ├── Create Listing entity
│ ├── Emit ListingCreatedEvent
│ └── Update search index
└── Output: CreatedListingDTO
Flow:
Controller → Command → CommandHandler → Domain → Event → Repository → Cache invalidate
Query Pattern (Read-only)
GetListingQuery
├── Input: ListingId
├── Handler: GetListingQueryHandler
│ ├── Check cache (Redis)
│ ├── If hit: return cached
│ └── If miss:
│ ├── Query database
│ ├── Cache result (TTL-based)
│ └── Return to client
└── Output: ListingDTO
Flow:
Controller → Query → QueryHandler → Repository → Cache store → Response
CACHING STRATEGY
Multi-Level Caching
Level 1: Browser Cache
├── Static assets (CSS, JS)
├── Max-Age: 31536000 (1 year)
└── Immutable: true
Level 2: CDN Cache (if deployed)
├── JSON responses
├── Max-Age: 300 (5 min)
└── Surrogate-Key invalidation
Level 3: Application Cache (Redis)
├── User objects (TTL: 1 hour)
├── Listing details (TTL: 30 min)
├── Search results (TTL: 5 min)
└── Rate limit counters (TTL: per window)
Cache Invalidation Triggers:
├── Event-based: ListingUpdatedEvent → invalidate key
├── Time-based: TTL expiration
├── Manual: Cache.delete(key) on batch operations
└── Circuit breaker: If Redis down, bypass to DB
ERROR HANDLING & OBSERVABILITY
Exception Hierarchy
GlobalExceptionFilter (catches all)
│
├→ HttpException (known errors)
│ ├── BadRequestException (400)
│ ├── UnauthorizedException (401)
│ ├── ForbiddenException (403)
│ ├── NotFoundException (404)
│ ├── ConflictException (409)
│ └── InternalServerErrorException (500)
│
└→ Unknown Error
└→ Sentry.captureException(error)
├── Capture stack trace
├── Attach request context
├── Tag by module/operation
└── Alert ops team (if severity > WARN)
Structured Logging (Pino)
├── JSON format for log aggregation
├── Context injection (request ID, user ID)
├── Log levels: trace, debug, info, warn, error, fatal
└── Destination: stdout (collected by Loki/Promtail)
Monitoring Points
Metrics (Prometheus)
├── HTTP request latency
├── Database query time
├── Cache hit/miss ratio
├── Error rate by endpoint
├── Queue depth (background jobs)
└── Payment processing success rate
Logs (Loki)
├── Searchable by timestamp, level, service, user
├── Retention: 30 days
└── Queries: error trends, user activity, audit trail
Traces (Sentry)
├── Request waterfall
├── Database call chains
└── Error context snapshot
BACKGROUND JOBS & EVENTS
Event System
Domain Event
├── ListingCreatedEvent
├── PaymentProcessedEvent
├── NotificationScheduledEvent
└── UserDeletedEvent
↓
EventEmitter.emit()
↓
Event Subscribers (consume in order)
├── ListingCreatedEventSubscriber
│ └→ Index in Typesense
├── PaymentProcessedEventSubscriber
│ └→ Send email receipt
├── NotificationScheduledEventSubscriber
│ └→ Queue FCM push
└── UserDeletedEventSubscriber
└→ Archive data + audit trail
Error Handling:
├── Retry policy (3 retries, exponential backoff)
├── Dead letter queue (failed events)
└── Monitoring alert (critical events failed)
FRONTEND STATE MANAGEMENT
Zustand Store Pattern
// auth-store.ts
const useAuthStore = create((set) => ({
user: null,
tokens: { accessToken: null, refreshToken: null },
actions: {
setUser: (user) => set({ user }),
setTokens: (tokens) => set({ tokens }),
logout: () => set({ user: null, tokens: null }),
}
}))
// Component Usage
const { user, setUser } = useAuthStore()
// Persistence (automatic)
├── localStorage (client-side)
├── Hydration on page load
└── Sync across tabs (storage event)
React Query Integration
// Hook Pattern
const useListings = (filters) => {
return useQuery({
queryKey: ['listings', filters],
queryFn: () => listingsApi.search(filters),
staleTime: 5 * 60 * 1000, // 5 min
gcTime: 10 * 60 * 1000, // 10 min (old: cacheTime)
retry: 3,
retryDelay: exponentialBackoff,
})
}
// Features
├── Automatic caching by queryKey
├── Background refetching
├── Optimistic updates
├── Pagination support
└── Dependency tracking
DEPLOYMENT ARCHITECTURE
Local Development
docker-compose.yml
├── PostgreSQL (5432)
├── Redis (6379)
├── Typesense (8108)
├── MinIO (9000)
└── PgBouncer (6432 - optional)
API Server: http://localhost:3001/api/v1
Web Server: http://localhost:3000
Swagger Docs: http://localhost:3001/api/v1/docs
Production Deployment
Kubernetes Cluster
├── API Pod (NestJS)
│ ├── Port: 3001
│ ├── Resources: 2 CPU, 2GB RAM
│ ├── Replicas: 3+ (autoscaling)
│ ├── Probes: liveness + readiness
│ └── Limits: enforce resource quotas
├── Web Pod (Next.js)
│ ├── Port: 3000
│ ├── Replicas: 2+
│ └── CDN: CloudFront/Cloudflare
├── PostgreSQL (managed RDS or Kubernetes StatefulSet)
├── Redis (managed ElastiCache or Kubernetes)
└── Typesense (managed or self-hosted cluster)
Ingress → Load Balancer → Service → Pods
CI/CD PIPELINE
Automated Stages
1. Code Push to master/PR
└→ GitHub Actions triggered
2. Lint Stage (2 min)
├── ESLint check
└── Prettier validation
3. Type Check Stage (3 min)
└── TypeScript compilation (no emit)
4. Unit Test Stage (5 min)
├── Backend: Vitest (pnpm test)
└── Frontend: Vitest + RTL
5. Integration Test Stage (8 min)
├── Test database setup
└── Vitest integration config
6. Build Stage (10 min)
├── NestJS build (tsc + webpack)
├── Next.js build (.next folder)
└── Artifact storage
7. E2E Test Stage (15 min) - if CI passes
├── Service startup (Postgres, Redis, Typesense)
├── Database migration
├── Seed data
├── Playwright tests (Chromium)
└── Report generation
8. Deploy Stage (5 min) - if all pass
├── Docker image build
├── Registry push
└── Kubernetes rollout
Total: ~50 min (sequential) or ~15 min (parallel)
PERFORMANCE TUNING CHECKLIST
Database
- Query analysis (EXPLAIN ANALYZE)
- Missing indexes (pg_stat_statements)
- Connection pooling tuned (PgBouncer)
- Replication lag monitored
- Backup tested (recovery time < 1 hour)
Application
- Memory usage profiled (Node.js heap)
- CPU throttling identified
- Garbage collection tuned (heap snapshots)
- Logging overhead measured
- Dependency versions updated
Frontend
- Bundle size analyzed (webpack analyzer)
- Code splitting implemented (routes)
- Images optimized (Next.js Image)
- Critical CSS inlined
- Web vitals tracked (LCP, FID, CLS)
Infrastructure
- Load balancer health checks tuned
- Autoscaling policies tested
- Cache hit rates > 80%
- Network latency acceptable (< 100ms)
- Monitoring alert thresholds realistic
TROUBLESHOOTING GUIDE
"Database Connection Timeout"
Diagnosis:
1. Check if PostgreSQL container is running: docker-compose ps
2. Verify DATABASE_URL in .env
3. Check PgBouncer if production: psql -h localhost -p 6432 -U pgbouncer
4. Look for connection limit reached: SELECT count(*) FROM pg_stat_activity
Fix:
├── Restart: docker-compose restart postgres
├── Increase PgBouncer pool: PGBOUNCER_POOL_SIZE=30
└── Check slow queries: pg_stat_statements
"Redis Connection Refused"
Diagnosis:
1. Check Redis container: docker-compose ps redis
2. Verify REDIS_URL in .env
3. Check port: redis-cli -p 6379 ping
4. Check memory: redis-cli INFO memory
Fix:
├── Restart: docker-compose restart redis
├── Flush if needed: redis-cli FLUSHALL (dev only!)
└── Monitor: redis-cli --stat
"Typesense Index Not Found"
Diagnosis:
1. Check Typesense container: docker-compose ps typesense
2. Verify TYPESENSE_API_KEY in .env
3. List indexes: curl http://localhost:8108/collections -H "X-TYPESENSE-API-KEY: <key>"
4. Check sync job logs
Fix:
├── Re-seed: pnpm db:seed
├── Reindex: DELETE /listings index, then rebuild
└── Monitor: Typesense dashboard http://localhost:8108/dashboard
"Tests Failing with 'Port Already in Use'"
Diagnosis:
1. Check running processes: lsof -i :3001 (macOS) or netstat -ano (Windows)
2. Docker containers: docker ps
Fix:
├── Kill process: kill -9 <PID>
├── Stop containers: docker-compose down
├── Update port in .env.test
└── Ensure cleanup in global-teardown.ts
SECURITY CHECKLIST - PRE-DEPLOYMENT
- JWT secrets rotated and unique
- CORS_ORIGINS finalized (no localhost in prod)
- Database credentials strong (> 16 chars, random)
- MinIO/AWS S3 credentials secure (IAM policy restricted)
- OAuth client secrets masked
- SSL certificate installed (HTTPS)
- HSTS preload submitted
- Security headers tested (securityheaders.com)
- OWASP Top 10 reviewed
- Penetration test scheduled
- Rate limits tuned (no bypass possible)
- Audit logging verified
- Backup encryption enabled
- Incident response plan documented
- On-call rotation configured