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>
This commit is contained in:
805
docs/load-testing/K6_LOAD_TESTING_GUIDE.md
Normal file
805
docs/load-testing/K6_LOAD_TESTING_GUIDE.md
Normal file
@@ -0,0 +1,805 @@
|
||||
# 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 14 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<ListingSearchItem>
|
||||
```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=<change_me>
|
||||
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=<openssl rand -base64 48>
|
||||
JWT_REFRESH_SECRET=<openssl rand -base64 48>
|
||||
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`)
|
||||
|
||||
Reference in New Issue
Block a user