# GoodGo Platform API โ€” K6 Load Testing Guide ## ๐ŸŽฏ Quick Summary **Base URL**: `http://localhost:3001/api/v1` **Node Version**: >= 22.0.0 **Testing Framework**: Playwright (E2E), Vitest (Unit) **No existing K6 or load testing setup found** --- ## ๐Ÿ“‹ Project Structure ### Root Directory ``` goodgo-platform/ โ”œโ”€โ”€ apps/api # NestJS backend (port 3001) โ”œโ”€โ”€ apps/web # Next.js 15 frontend (port 3000) โ”œโ”€โ”€ libs/mcp-servers # MCP tool server library โ”œโ”€โ”€ prisma/ # Database schema & migrations โ”œโ”€โ”€ e2e/ # Playwright E2E tests (api + web) โ”œโ”€โ”€ turbo.json # Turborepo config โ”œโ”€โ”€ package.json # Root workspace scripts โ”œโ”€โ”€ .env.example # Environment variables template โ””โ”€โ”€ playwright.config.ts # Playwright configuration ``` ### Key Scripts (package.json) ```bash pnpm dev # Start all apps (API :3001, Web :3000) pnpm test # Unit tests via Vitest (API only) pnpm test:e2e # Playwright E2E tests pnpm test:e2e:api # API E2E tests only pnpm test:e2e:web # Web E2E tests only pnpm build # Production build pnpm lint # ESLint pnpm typecheck # TypeScript checking ``` --- ## ๐Ÿ—๏ธ API Module Structure ### API Base Architecture: `apps/api/src/modules/` Each module follows DDD layers: `domain/` โ†’ `application/` โ†’ `infrastructure/` โ†’ `presentation/` ``` modules/ โ”œโ”€โ”€ auth/ # Authentication & JWT โ”œโ”€โ”€ listings/ # Property listings CRUD โ”œโ”€โ”€ payments/ # Payment processing (VNPay, MoMo, ZaloPay) โ”œโ”€โ”€ search/ # Full-text & geo search (Typesense) โ”œโ”€โ”€ subscriptions/ # Plans, quotas, usage tracking โ”œโ”€โ”€ admin/ # Moderation, KYC, user management โ”œโ”€โ”€ analytics/ # Market data, heatmaps, price trends โ”œโ”€โ”€ reviews/ # User reviews โ”œโ”€โ”€ notifications/ # Email, push (FCM), in-app โ”œโ”€โ”€ metrics/ # Prometheus metrics โ”œโ”€โ”€ health/ # Health checks โ”œโ”€โ”€ shared/ # Domain primitives, guards, pipes, logging โ””โ”€โ”€ mcp/ # MCP tool server endpoints ``` --- ## ๐Ÿ” AUTH MODULE ### Controllers & Endpoints #### File: `apps/api/src/modules/auth/presentation/controllers/auth.controller.ts` | Method | Endpoint | Rate Limit | Auth | Description | |--------|----------|-----------|------|-------------| | POST | `/auth/register` | 5/hour | No | Register new user | | POST | `/auth/login` | 5/hour | LocalAuth | Login with phone + password | | POST | `/auth/refresh` | 5/hour | No | Refresh access token | | POST | `/auth/logout` | No limit | No | Clear auth cookies | | POST | `/auth/exchange-token` | No limit | No | Exchange OAuth tokens for cookies | | GET | `/auth/profile` | No limit | JWT | Get current user profile | | GET | `/auth/profile/agent` | No limit | JWT | Get agent profile for user | | PATCH | `/auth/kyc` | No limit | JWT+Admin | Verify user KYC (admin only) | ### DTOs #### LoginDto ```typescript { phone: string // Required, example: "0901234567" password: string // Required, example: "P@ssw0rd!" } ``` #### RegisterDto ```typescript { phone: string // Required, example: "0901234567" password: string // Required, min 8 chars, example: "P@ssw0rd!" fullName: string // Required, example: "Nguyen Van A" email?: string // Optional, valid email format } ``` #### RefreshTokenDto ```typescript { refreshToken?: string // Optional if using cookie } ``` #### VerifyKycDto ```typescript { userId: string kycStatus: string kycData?: object } ``` ### Cookies & Authentication **Access Token**: - Cookie: `access_token` - Max Age: 15 minutes (900s) - HttpOnly: true - Secure: true (production only) - SameSite: strict - Path: / **Refresh Token**: - Cookie: `refresh_token` - Max Age: 30 days - HttpOnly: true - Secure: true (production only) - SameSite: strict - Path: `/auth` **Session Indicator**: - Cookie: `goodgo_authenticated` = "1" - HttpOnly: false (visible to frontend) ### OAuth Support - Google OAuth 2.0 - Zalo OAuth (Vietnamese platform) - Environment: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `ZALO_APP_ID`, `ZALO_APP_SECRET` --- ## ๐Ÿ  LISTINGS MODULE ### Controllers & Endpoints #### File: `apps/api/src/modules/listings/presentation/controllers/listings.controller.ts` | Method | Endpoint | Auth | Quota | Description | |--------|----------|------|-------|-------------| | POST | `/listings` | JWT | Yes | Create new listing | | GET | `/listings` | No | No | Search/filter listings (public) | | GET | `/listings/:id` | No | No | Get listing detail | | GET | `/listings/pending` | JWT+Admin | No | Get listings pending moderation | | PATCH | `/listings/:id/status` | JWT | No | Update listing status | | POST | `/listings/:id/media` | JWT | No | Upload photo/video | | PATCH | `/listings/:id/moderate` | JWT+Admin | No | Moderate a listing (admin) | ### DTOs #### CreateListingDto ```typescript { transactionType: 'SALE' | 'RENT', priceVND: bigint | string, propertyType: 'APARTMENT' | 'HOUSE' | 'LAND' | etc., title: string, // Min 5 chars description: string, // Min 10 chars address: string, ward: string, district: string, city: string, latitude: number, // -90 to 90 longitude: number, // -180 to 180 areaM2: number, // Total area usableAreaM2?: number, bedrooms?: number, bathrooms?: number, floors?: number, // For houses floor?: number, // For apartments totalFloors?: number, direction?: 'EAST' | 'WEST' | 'NORTH' | 'SOUTH' | etc., yearBuilt?: number, legalStatus?: string, amenities?: string[], // e.g., ['Hแป“ bฦกi', 'Gym'] nearbyPOIs?: object, // e.g., { schools: [], hospitals: [] } metroDistanceM?: number, projectName?: string, agentId?: string, rentPriceMonthly?: bigint | string, commissionPct?: number, } ``` #### SearchListingsDto ```typescript { status?: 'ACTIVE' | 'INACTIVE' | 'ARCHIVED', transactionType?: 'SALE' | 'RENT', propertyType?: 'APARTMENT' | 'HOUSE' | 'LAND' | etc., city?: string, district?: string, minPrice?: bigint | string, maxPrice?: bigint | string, minArea?: number, maxArea?: number, bedrooms?: number, page?: number, // Default: 1 limit?: number, // Default: 20, Max: 100 } ``` #### UpdateListingStatusDto ```typescript { status: string, moderationNotes?: string, } ``` #### ModerateListingDto ```typescript { action: 'APPROVE' | 'REJECT', moderationScore?: number, notes?: string, } ``` ### Response Structures #### ListingDetailData Contains full listing information including: - id, title, description - propertyType, transactionType - address, latitude, longitude, ward, district, city - priceVND, rentPriceMonthly - areaM2, usableAreaM2, bedrooms, bathrooms, floors - amenities, nearbyPOIs - legalStatus, yearBuilt, direction - mediaUrls (photos/videos) - agentInfo - createdAt, updatedAt #### PaginatedResult ```typescript { items: ListingSearchItem[], total: number, page: number, limit: number, totalPages: number, } ``` --- ## ๐Ÿ’ณ PAYMENTS MODULE ### Controllers & Endpoints #### File: `apps/api/src/modules/payments/presentation/controllers/payments.controller.ts` | Method | Endpoint | Auth | Rate Limit | Description | |--------|----------|------|-----------|-------------| | POST | `/payments` | JWT | No | Create payment | | GET | `/payments` | JWT | No | List user transactions | | GET | `/payments/:id` | JWT | No | Get payment status | | POST | `/payments/callback/:provider` | No | 20/min | Handle payment callback (webhook) | | POST | `/payments/:id/refund` | JWT+Admin | No | Refund payment (admin) | ### DTOs #### CreatePaymentDto ```typescript { provider: 'VNPAY' | 'MOMO' | 'ZALOPAY', type: 'LISTING_FEE' | 'SUBSCRIPTION' | 'AGENT_COMMISSION', amountVND: number, // 1 to 100,000,000,000 description: string, // Payment description returnUrl: string, // URL (must be valid) transactionId?: string, // External ID idempotencyKey?: string, // For idempotency } ``` #### ListTransactionsDto ```typescript { status?: string, limit?: number, offset?: number, } ``` #### RefundPaymentDto ```typescript { reason: string, } ``` ### Payment Providers - **VNPay** (Primary for Vietnam) - Environment: `VNPAY_TMN_CODE`, `VNPAY_HASH_SECRET` - Sandbox: `https://sandbox.vnpayment.vn/paymentv2/vpcpay.html` - API: `https://sandbox.vnpayment.vn/merchant_webapi/api/transaction` - **MoMo** (Mobile wallet) - Environment: `MOMO_PARTNER_CODE`, `MOMO_ACCESS_KEY`, `MOMO_SECRET_KEY` - Endpoint: `https://test-payment.momo.vn/v2/gateway/api` - **ZaloPay** (Zalo integrated) - Environment: `ZALOPAY_APP_ID`, `ZALOPAY_KEY1`, `ZALOPAY_KEY2` - Endpoint: `https://sb-openapi.zalopay.vn/v2` ### Callback Processing **Webhook URL Pattern**: `/payments/callback/{provider}` Supports both: - Query parameters (VNPay) - Request body (MoMo, ZaloPay) - Merged data handling internally --- ## ๐Ÿ” SEARCH MODULE ### Controllers & Endpoints #### File: `apps/api/src/modules/search/presentation/controllers/search.controller.ts` | Method | Endpoint | Auth | Description | |--------|----------|------|-------------| | GET | `/search` | No | Full-text search (public) | | GET | `/search/geo` | No | Geographic radius search (public) | | POST | `/search/reindex` | JWT+Admin | Reindex all properties (admin) | ### DTOs #### SearchPropertiesDto (Full-text search) ```typescript { q?: string, // Free-text query, e.g., 'chung cu quan 7' propertyType?: string, // Filter by type transactionType?: string, // 'sale' or 'rent' priceMin?: number, // Min price in VND priceMax?: number, // Max price in VND areaMin?: number, // Min area in mยฒ areaMax?: number, // Max area in mยฒ bedrooms?: number, // Number of bedrooms district?: string, // District name city?: string, // City name sortBy?: 'price_asc' | 'price_desc' | 'date_desc' | 'relevance', page?: number, // 1-based, default: 1 perPage?: number, // Default: 20, Max: 100 } ``` #### GeoSearchDto (Geographic search) ```typescript { lat: number, // Latitude, -90 to 90 lng: number, // Longitude, -180 to 180 radiusKm: number, // Radius, 0.1 to 100 propertyType?: string, transactionType?: string, priceMin?: number, priceMax?: number, sortBy?: 'distance' | 'price_asc' | 'price_desc' | 'date_desc', page?: number, // Default: 1 perPage?: number, // Default: 20, Max: 100 } ``` ### Search Engine **Typesense** integration for fast full-text & faceted search - Environment: `TYPESENSE_HOST`, `TYPESENSE_PORT`, `TYPESENSE_API_KEY` - Default: `http://localhost:8108` ### Response Structure #### SearchResult ```typescript { results: SearchHit[], facets?: { propertyType?: { value: string; count: number }[], district?: { value: string; count: number }[], transactionType?: { value: string; count: number }[], }, total: number, page: number, perPage: number, totalPages: number, } ``` --- ## ๐Ÿ—„๏ธ Database & Environment ### PostgreSQL with PostGIS ``` DB_HOST=localhost DB_PORT=5432 DB_NAME=goodgo DB_USER=goodgo DB_PASSWORD= DATABASE_URL=postgresql://goodgo:password@localhost:5432/goodgo?schema=public ``` ### Redis Cache ``` REDIS_URL=redis://localhost:6379 ``` ### Key Environment Variables ```bash # JWT Secrets (REQUIRED) JWT_SECRET= JWT_REFRESH_SECRET= JWT_EXPIRES_IN=15m JWT_REFRESH_EXPIRES_IN=7d # CORS CORS_ORIGINS=http://localhost:3000,http://localhost:3001 # Node Environment NODE_ENV=development|test|production PORT=3001 # API port # OAuth GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= ZALO_APP_ID= ZALO_APP_SECRET= # Typesense Search TYPESENSE_HOST=localhost TYPESENSE_PORT=8108 TYPESENSE_API_KEY= # MinIO/S3 Storage MINIO_ENDPOINT=localhost MINIO_PORT=9000 MINIO_ACCESS_KEY= MINIO_SECRET_KEY= MINIO_BUCKET=goodgo-media # Payment Gateways VNPAY_TMN_CODE= VNPAY_HASH_SECRET= MOMO_PARTNER_CODE= ZALOPAY_APP_ID= # Logging LOG_LEVEL=info ``` --- ## ๐Ÿงช Existing Test Setup ### Playwright Configuration **File**: `playwright.config.ts` ```typescript testDir: './e2e' globalSetup: './e2e/global-setup.ts' globalTeardown: './e2e/global-teardown.ts' Projects: - "api": Tests NestJS API (port 3001) baseURL: http://localhost:3001/api/v1 - "web": Tests Next.js frontend (port 3000) baseURL: http://localhost:3000 ``` ### Playwright Scripts ```bash pnpm test:e2e # Run all E2E tests pnpm test:e2e:api # API tests only pnpm test:e2e:web # Web tests only pnpm test:e2e:report # Show HTML report ``` ### Test Database - CI uses `goodgo_test` database - Local uses `.env.test` for test database URL - Migrations & seed run in `global-setup.ts` - Cleanup in `global-teardown.ts` ### Example E2E Test **File**: `e2e/api/auth-register.spec.ts` ```typescript import { test, expect } from '@playwright/test'; import { createTestUser } from '../fixtures'; test.describe('POST /auth/register', () => { test('registers a new user and returns token pair', async ({ request }) => { const user = createTestUser(); const res = await request.post('/auth/register', { data: user }); expect(res.status()).toBe(201); const body = await res.json(); expect(body).toHaveProperty('accessToken'); expect(body).toHaveProperty('refreshToken'); }); }); ``` ### Unit Tests (Vitest) ```bash pnpm test # Run unit tests (API only) pnpm test:integration # Integration tests ``` --- ## ๐Ÿ”„ CI/CD Setup ### GitHub Actions Workflows #### `ci.yml` - Lint โ†’ Typecheck โ†’ Test โ†’ Build - Runs on: `push main` and `pull_request` - Services: PostgreSQL 16 + PostGIS - Steps: lint โ†’ typecheck โ†’ test โ†’ build #### `e2e.yml` - Playwright E2E Tests - Runs on: `push main` and `pull_request` - Services: - PostgreSQL 16 + PostGIS - Redis 7 - Typesense 27.1 - MinIO (S3-compatible storage) - Artifacts: HTML report + traces #### `security.yml` - Code Security - Dependency scanning - SAST analysis #### `deploy.yml` - Production Deployment - Docker builds - Registry push - Deployment orchestration --- ## ๐Ÿ“Š Architecture Patterns ### NestJS CQRS Pattern Each module uses: - **Commands** (Write operations) - `CommandBus.execute(command)` - Located in `application/commands/` - Handlers in `application/commands/{command}/` - **Queries** (Read operations) - `QueryBus.execute(query)` - Located in `application/queries/` - Handlers in `application/queries/{query}/` Example: ```typescript // In controller const result = await this.commandBus.execute( new CreateListingCommand(userId, ...) ); const profile = await this.queryBus.execute( new GetProfileQuery(userId) ); ``` ### Guards & Interceptors - `JwtAuthGuard` - Validates JWT token - `LocalAuthGuard` - Email/password validation - `RolesGuard` - Role-based access control - `QuotaGuard` - Subscription quota enforcement - `FileValidationPipe` - File upload validation --- ## ๐Ÿš€ Starting the API ### Local Development ```bash # Install dependencies pnpm install # Generate Prisma client pnpm db:generate # Run migrations pnpm db:migrate:dev # Seed data (users, listings, etc.) pnpm db:seed # Start API (and Web) pnpm dev # API will be available at: # http://localhost:3001/api/v1 # Swagger UI: http://localhost:3001/api/v1/docs ``` ### With Docker ```bash docker-compose up # Services: PostgreSQL, Redis, Typesense, MinIO, API, Web ``` --- ## ๐ŸŽฏ K6 Load Testing Recommendations ### Key Endpoints to Test 1. **Authentication** (High priority) - Register: `POST /auth/register` - Login: `POST /auth/login` - Refresh: `POST /auth/refresh` - Profile: `GET /auth/profile` (authenticated) 2. **Listings** (High priority) - Create: `POST /listings` (quota-gated) - Search: `GET /listings` (public, high volume) - Detail: `GET /listings/:id` (public, high volume) 3. **Search** (High priority) - Full-text: `GET /search?q=...` (public, high volume) - Geo: `GET /search/geo?lat=...&lng=...` (public, high volume) 4. **Payments** (Medium priority) - Create: `POST /payments` (authenticated) - List: `GET /payments` (authenticated) - Webhook: `POST /payments/callback/:provider` (unthrottled) 5. **Admin Endpoints** (Medium priority, restricted) - Moderate listings: `PATCH /listings/:id/moderate` - List pending: `GET /listings/pending` - Verify KYC: `PATCH /auth/kyc` - Reindex: `POST /search/reindex` ### K6 Script Structure ```javascript import http from 'k6/http'; import { check, group, sleep } from 'k6'; const BASE_URL = 'http://localhost:3001/api/v1'; // Stage-based load: ramp up โ†’ sustained โ†’ ramp down export const options = { stages: [ { duration: '2m', target: 100 }, // Ramp up { duration: '5m', target: 100 }, // Sustained { duration: '2m', target: 0 }, // Ramp down ], thresholds: { http_req_duration: ['p(95)<500', 'p(99)<1000'], http_req_failed: ['rate<0.1'], }, }; export default function() { // Test scenarios here } ``` ### Data Generation Tips - Use test fixture users from Playwright tests - Leverage Prisma seed data (districts, property types) - Generate realistic search queries - Test with various geo coordinates (Ho Chi Minh City: ~10.77ยฐN, 106.70ยฐE) --- ## ๐Ÿ“ File Locations Quick Reference ``` apps/api/ โ”œโ”€โ”€ src/ โ”‚ โ”œโ”€โ”€ main.ts # API entry point (port 3001) โ”‚ โ”œโ”€โ”€ app.module.ts # Root module โ”‚ โ””โ”€โ”€ modules/ โ”‚ โ”œโ”€โ”€ auth/ โ”‚ โ”‚ โ”œโ”€โ”€ presentation/controllers/auth.controller.ts โ”‚ โ”‚ โ”œโ”€โ”€ presentation/dto/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ login.dto.ts โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ register.dto.ts โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ refresh-token.dto.ts โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ verify-kyc.dto.ts โ”‚ โ”‚ โ”œโ”€โ”€ application/commands/ โ”‚ โ”‚ โ”œโ”€โ”€ application/queries/ โ”‚ โ”‚ โ”œโ”€โ”€ infrastructure/services/token.service.ts โ”‚ โ”‚ โ””โ”€โ”€ domain/ โ”‚ โ”œโ”€โ”€ listings/ โ”‚ โ”‚ โ”œโ”€โ”€ presentation/controllers/listings.controller.ts โ”‚ โ”‚ โ”œโ”€โ”€ presentation/dto/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ create-listing.dto.ts โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ search-listings.dto.ts โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ update-listing-status.dto.ts โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ moderate-listing.dto.ts โ”‚ โ”‚ โ””โ”€โ”€ ... โ”‚ โ”œโ”€โ”€ payments/ โ”‚ โ”‚ โ”œโ”€โ”€ presentation/controllers/payments.controller.ts โ”‚ โ”‚ โ”œโ”€โ”€ presentation/dto/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ create-payment.dto.ts โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ list-transactions.dto.ts โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ refund-payment.dto.ts โ”‚ โ”‚ โ””โ”€โ”€ ... โ”‚ โ”œโ”€โ”€ search/ โ”‚ โ”‚ โ”œโ”€โ”€ presentation/controllers/search.controller.ts โ”‚ โ”‚ โ”œโ”€โ”€ presentation/dto/ โ”‚ โ”‚ โ”‚ โ”œโ”€โ”€ search-properties.dto.ts โ”‚ โ”‚ โ”‚ โ””โ”€โ”€ geo-search.dto.ts โ”‚ โ”‚ โ””โ”€โ”€ ... โ”‚ โ””โ”€โ”€ ... โ”‚ โ””โ”€โ”€ package.json # Dependencies, scripts e2e/ โ”œโ”€โ”€ api/ # Playwright API tests โ”‚ โ”œโ”€โ”€ auth-register.spec.ts โ”‚ โ”œโ”€โ”€ auth-refresh.spec.ts โ”‚ โ””โ”€โ”€ ... โ”œโ”€โ”€ web/ # Playwright web tests โ”œโ”€โ”€ fixtures.ts # Test data generators โ”œโ”€โ”€ global-setup.ts # DB setup before tests โ””โ”€โ”€ global-teardown.ts # DB cleanup after tests playwright.config.ts # Playwright config .github/workflows/ โ”œโ”€โ”€ ci.yml # Lint โ†’ typecheck โ†’ test โ†’ build โ”œโ”€โ”€ e2e.yml # Playwright E2E โ”œโ”€โ”€ security.yml # Security scanning โ””โ”€โ”€ deploy.yml # Production deployment .env.example # Environment variable template .env.test # Test database connection ``` --- ## ๐Ÿ”— Useful Links & References - **API Swagger Docs**: `http://localhost:3001/api/v1/docs` - **Project Root Docs**: `CLAUDE.md` - **Existing Analysis**: `CODEBASE_ANALYSIS.md`, `EXPLORATION_REPORT.md` - **Frontend Docs**: `docs/audits/FRONTEND_EXPLORATION.md` --- ## โœ… Summary for K6 Implementation **No existing K6 setup** โ€” you have a clean slate! **Key endpoints** identified across: - Auth (register, login, refresh, profile) - Listings (create, search, detail, moderate) - Search (full-text, geo) - Payments (create, callback, list, refund) - Admin (moderate, KYC, reindex) **Rate limits** to consider: - Auth: 5/hour per endpoint - Payments callback: 20/min - Others: No limit (except quota guards on create operations) **Infrastructure ready**: - Turbo monorepo for dependency management - PostgreSQL + PostGIS for spatial data - Typesense for search indexing - Redis for caching - MinIO for media storage - Prometheus metrics endpoint **Tests can be integrated** into CI/CD pipeline via `.github/workflows/` (suggested: new `load-test.yml`)