# GoodGo Platform API — Hướng Dẫn K6 Load Testing ## 🎯 Tóm Tắt Nhanh **Base URL**: `http://localhost:3001/api/v1` **Phiên bản Node**: >= 22.0.0 **Testing Framework**: Playwright (E2E), Vitest (Unit) **Chưa có thiết lập K6 hoặc load testing nào tồn tại** --- ## 📋 Cấu Trúc Dự Án ### Thư Mục Gốc ``` 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 ``` ### Các Script Chính (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 ``` --- ## 🏗️ Cấu Trúc Module API ### Kiến Trúc Cơ Sở API: `apps/api/src/modules/` Mỗi module tuân theo các tầng DDD: `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 ``` --- ## 🔐 MODULE AUTH ### Controllers & Endpoints #### File: `apps/api/src/modules/auth/presentation/controllers/auth.controller.ts` | Method | Endpoint | Rate Limit | Auth | Mô tả | |--------|----------|-----------|------|-------------| | POST | `/auth/register` | 5/giờ | Không | Đăng ký người dùng mới | | POST | `/auth/login` | 5/giờ | LocalAuth | Đăng nhập bằng số điện thoại + mật khẩu | | POST | `/auth/refresh` | 5/giờ | Không | Làm mới access token | | POST | `/auth/logout` | Không giới hạn | Không | Xóa cookie xác thực | | POST | `/auth/exchange-token` | Không giới hạn | Không | Đổi OAuth token lấy cookie | | GET | `/auth/profile` | Không giới hạn | JWT | Lấy hồ sơ người dùng hiện tại | | GET | `/auth/profile/agent` | Không giới hạn | JWT | Lấy hồ sơ agent của người dùng | | PATCH | `/auth/kyc` | Không giới hạn | JWT+Admin | Xác minh KYC người dùng (chỉ admin) | ### 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 & Xác Thực **Access Token**: - Cookie: `access_token` - Max Age: 15 phút (900s) - HttpOnly: true - Secure: true (chỉ production) - SameSite: strict - Path: / **Refresh Token**: - Cookie: `refresh_token` - Max Age: 30 ngày - HttpOnly: true - Secure: true (chỉ production) - SameSite: strict - Path: `/auth` **Chỉ Báo Phiên**: - Cookie: `goodgo_authenticated` = "1" - HttpOnly: false (frontend nhìn thấy được) ### Hỗ Trợ OAuth - Google OAuth 2.0 - Zalo OAuth (nền tảng Việt Nam) - Environment: `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `ZALO_APP_ID`, `ZALO_APP_SECRET` --- ## 🏠 MODULE LISTINGS ### Controllers & Endpoints #### File: `apps/api/src/modules/listings/presentation/controllers/listings.controller.ts` | Method | Endpoint | Auth | Quota | Mô tả | |--------|----------|------|-------|-------------| | POST | `/listings` | JWT | Có | Tạo listing mới | | GET | `/listings` | Không | Không | Tìm kiếm/lọc listing (công khai) | | GET | `/listings/:id` | Không | Không | Lấy chi tiết listing | | GET | `/listings/pending` | JWT+Admin | Không | Lấy listing đang chờ kiểm duyệt | | PATCH | `/listings/:id/status` | JWT | Không | Cập nhật trạng thái listing | | POST | `/listings/:id/media` | JWT | Không | Tải lên ảnh/video | | PATCH | `/listings/:id/moderate` | JWT+Admin | Không | Kiểm duyệt 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, } ``` ### Cấu Trúc Phản Hồi #### ListingDetailData Chứa thông tin đầy đủ về listing bao gồm: - id, title, description - propertyType, transactionType - address, latitude, longitude, ward, district, city - priceVND, rentPriceMonthly - areaM2, usableAreaM2, bedrooms, bathrooms, floors - amenities, nearbyPOIs - legalStatus, yearBuilt, direction - mediaUrls (ảnh/video) - agentInfo - createdAt, updatedAt #### PaginatedResult ```typescript { items: ListingSearchItem[], total: number, page: number, limit: number, totalPages: number, } ``` --- ## 💳 MODULE PAYMENTS ### Controllers & Endpoints #### File: `apps/api/src/modules/payments/presentation/controllers/payments.controller.ts` | Method | Endpoint | Auth | Rate Limit | Mô tả | |--------|----------|------|-----------|-------------| | POST | `/payments` | JWT | Không | Tạo thanh toán | | GET | `/payments` | JWT | Không | Liệt kê giao dịch của người dùng | | GET | `/payments/:id` | JWT | Không | Lấy trạng thái thanh toán | | POST | `/payments/callback/:provider` | Không | 20/phút | Xử lý callback thanh toán (webhook) | | POST | `/payments/:id/refund` | JWT+Admin | Không | Hoàn tiền (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, } ``` ### Nhà Cung Cấp Thanh Toán - **VNPay** (Chính cho Việt Nam) - 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** (Ví di động) - Environment: `MOMO_PARTNER_CODE`, `MOMO_ACCESS_KEY`, `MOMO_SECRET_KEY` - Endpoint: `https://test-payment.momo.vn/v2/gateway/api` - **ZaloPay** (Tích hợp Zalo) - Environment: `ZALOPAY_APP_ID`, `ZALOPAY_KEY1`, `ZALOPAY_KEY2` - Endpoint: `https://sb-openapi.zalopay.vn/v2` ### Xử Lý Callback **Mẫu URL Webhook**: `/payments/callback/{provider}` Hỗ trợ cả: - Query parameters (VNPay) - Request body (MoMo, ZaloPay) - Xử lý dữ liệu hợp nhất nội bộ --- ## 🔍 MODULE SEARCH ### Controllers & Endpoints #### File: `apps/api/src/modules/search/presentation/controllers/search.controller.ts` | Method | Endpoint | Auth | Mô tả | |--------|----------|------|-------------| | GET | `/search` | Không | Tìm kiếm full-text (công khai) | | GET | `/search/geo` | Không | Tìm kiếm theo bán kính địa lý (công khai) | | POST | `/search/reindex` | JWT+Admin | Lập chỉ mục lại tất cả bất động sản (admin) | ### DTOs #### SearchPropertiesDto (Tìm kiếm full-text) ```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 (Tìm kiếm địa lý) ```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 Tích hợp **Typesense** cho tìm kiếm full-text & faceted nhanh - Environment: `TYPESENSE_HOST`, `TYPESENSE_PORT`, `TYPESENSE_API_KEY` - Mặc định: `http://localhost:8108` ### Cấu Trúc Phản Hồi #### 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 với 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 ``` ### Các Biến Môi Trường Chính ```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 ``` --- ## 🧪 Thiết Lập Test Hiện Có ### Cấu Hình Playwright **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 ``` ### Các Script Playwright ```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 ``` ### Database Test - CI sử dụng database `goodgo_test` - Local sử dụng `.env.test` cho URL database test - Migration & seed chạy trong `global-setup.ts` - Dọn dẹp trong `global-teardown.ts` ### Ví Dụ 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 ``` --- ## 🔄 Thiết Lập CI/CD ### Workflow GitHub Actions #### `ci.yml` - Lint → Typecheck → Test → Build - Chạy khi: `push main` và `pull_request` - Services: PostgreSQL 16 + PostGIS - Các bước: lint → typecheck → test → build #### `e2e.yml` - Playwright E2E Tests - Chạy khi: `push main` và `pull_request` - Services: - PostgreSQL 16 + PostGIS - Redis 7 - Typesense 27.1 - MinIO (lưu trữ tương thích S3) - Artifacts: HTML report + traces #### `security.yml` - Bảo Mật Mã Nguồn - Quét dependency - Phân tích SAST #### `deploy.yml` - Triển Khai Production - Build Docker - Push registry - Điều phối triển khai --- ## 📊 Mẫu Kiến Trúc ### Mẫu CQRS NestJS Mỗi module sử dụng: - **Commands** (Thao tác ghi) - `CommandBus.execute(command)` - Đặt trong `application/commands/` - Handlers trong `application/commands/{command}/` - **Queries** (Thao tác đọc) - `QueryBus.execute(query)` - Đặt trong `application/queries/` - Handlers trong `application/queries/{query}/` Ví dụ: ```typescript // In controller const result = await this.commandBus.execute( new CreateListingCommand(userId, ...) ); const profile = await this.queryBus.execute( new GetProfileQuery(userId) ); ``` ### Guards & Interceptors - `JwtAuthGuard` - Xác thực JWT token - `LocalAuthGuard` - Xác thực email/mật khẩu - `RolesGuard` - Kiểm soát truy cập dựa trên vai trò - `QuotaGuard` - Thi hành hạn ngạch subscription - `FileValidationPipe` - Xác thực file upload --- ## 🚀 Khởi Động API ### Phát Triển Cục Bộ ```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 ``` ### Với Docker ```bash docker-compose up # Services: PostgreSQL, Redis, Typesense, MinIO, API, Web ``` --- ## 🎯 Khuyến Nghị K6 Load Testing ### Các Endpoint Chính Cần Test 1. **Authentication** (Ưu tiên cao) - Register: `POST /auth/register` - Login: `POST /auth/login` - Refresh: `POST /auth/refresh` - Profile: `GET /auth/profile` (đã xác thực) 2. **Listings** (Ưu tiên cao) - Create: `POST /listings` (giới hạn quota) - Search: `GET /listings` (công khai, lưu lượng cao) - Detail: `GET /listings/:id` (công khai, lưu lượng cao) 3. **Search** (Ưu tiên cao) - Full-text: `GET /search?q=...` (công khai, lưu lượng cao) - Geo: `GET /search/geo?lat=...&lng=...` (công khai, lưu lượng cao) 4. **Payments** (Ưu tiên trung bình) - Create: `POST /payments` (đã xác thực) - List: `GET /payments` (đã xác thực) - Webhook: `POST /payments/callback/:provider` (không giới hạn throttle) 5. **Endpoint Admin** (Ưu tiên trung bình, hạn chế) - Kiểm duyệt listing: `PATCH /listings/:id/moderate` - Liệt kê pending: `GET /listings/pending` - Xác minh KYC: `PATCH /auth/kyc` - Reindex: `POST /search/reindex` ### Cấu Trúc Script K6 ```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 } ``` ### Mẹo Tạo Dữ Liệu - Sử dụng test fixture user từ Playwright tests - Tận dụng dữ liệu Prisma seed (quận, loại bất động sản) - Tạo các truy vấn tìm kiếm thực tế - Test với các tọa độ địa lý khác nhau (TP.HCM: ~10.77°N, 106.70°E) --- ## 📁 Tham Khảo Nhanh Vị Trí File ``` 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 ``` --- ## 🔗 Liên Kết & Tham Khảo Hữu Ích - **API Swagger Docs**: `http://localhost:3001/api/v1/docs` - **Tài Liệu Gốc Dự Án**: `CLAUDE.md` - **Phân Tích Hiện Có**: `CODEBASE_ANALYSIS.md`, `EXPLORATION_REPORT.md` - **Tài Liệu Frontend**: `docs/audits/FRONTEND_EXPLORATION.md` --- ## ✅ Tóm Tắt Triển Khai K6 **Chưa có thiết lập K6 nào** — bạn có một khởi đầu hoàn toàn mới! **Các endpoint chính** được xác định trên các module: - Auth (register, login, refresh, profile) - Listings (create, search, detail, moderate) - Search (full-text, geo) - Payments (create, callback, list, refund) - Admin (moderate, KYC, reindex) **Rate limit** cần xem xét: - Auth: 5/giờ mỗi endpoint - Payments callback: 20/phút - Khác: Không giới hạn (trừ quota guard cho thao tác create) **Hạ tầng sẵn sàng**: - Turbo monorepo cho quản lý dependency - PostgreSQL + PostGIS cho dữ liệu không gian - Typesense cho lập chỉ mục tìm kiếm - Redis cho caching - MinIO cho lưu trữ media - Endpoint Prometheus metrics **Tests có thể được tích hợp** vào CI/CD pipeline qua `.github/workflows/` (đề xuất: `load-test.yml` mới)