docs(GOO-33): comprehensive documentation sprint
Create/update all Sprint 6 documentation: - CHANGELOG.md: document GOO-33 and recent audit findings - CONTRIBUTING.md: add branching, PR, commit conventions - docs/ci-cd.md: GitHub Actions pipeline documentation - docs/onboarding.md: developer setup & onboarding guide - docs/mcp-servers.md: MCP servers API documentation - docs/PROJECT_TRACKER.md: mark GOO-33 as in_progress - docs/QA_TRACKER.md: test status and verification plans Curate audit reports (reduce ~103 → 12 canonical files): - Keep canonical audit reports with descriptive index - Archive obsolete/duplicate audit exploration files Acceptance Criteria: - [x] QA_TRACKER.md exists with current test status - [x] CHANGELOG.md updated to today - [x] PROJECT_TRACKER.md reflects current sprint status - [x] CI/CD pipeline documented - [x] CONTRIBUTING.md has branching, PR, commit conventions - [x] docs/audits/ reduced to canonical reports Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
37
CHANGELOG.md
37
CHANGELOG.md
@@ -7,6 +7,43 @@ và dự án này tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### GOO-33 Documentation Sprint (2026-04-22)
|
||||||
|
|
||||||
|
#### Đã hoàn thành
|
||||||
|
- QA_TRACKER.md — cập nhật test status baseline + Sprint 1-2 test plans
|
||||||
|
- CHANGELOG.md — cập nhật changelog lần cuối (2026-04-22)
|
||||||
|
- PROJECT_TRACKER.md — cập nhật GOO-33 status → in_progress
|
||||||
|
- CONTRIBUTING.md — thêm branching strategy, PR conventions, commit message format
|
||||||
|
- docs/ci-cd.md — tài liệu đầy đủ GitHub Actions pipeline (lint → typecheck → test → build)
|
||||||
|
- docs/onboarding.md — hướng dẫn setup dành cho developer mới
|
||||||
|
- docs/mcp-servers.md — tài liệu 3 MCP servers (search, analytics, valuation)
|
||||||
|
- docs/audits/ — curate từ ~103 → 12 canonical audit reports + archive old files
|
||||||
|
|
||||||
|
### GOO-2 Lead Orchestrator Audit (2026-04-22)
|
||||||
|
|
||||||
|
#### Audit & Planning
|
||||||
|
- Kiểm tra toàn diện codebase: 51 findings (4 blockers, 24 high, 13 medium, 10 low)
|
||||||
|
- Nghiên cứu thị trường BĐS VN: 23 findings (3 P0, 10 P1, 8 P2, 1 P3)
|
||||||
|
- Ma trận đề xuất: 25 cải thiện (Nhóm A) + 20 tính năng mới (Nhóm B) + 10 docs gaps (Nhóm C)
|
||||||
|
- Tạo 32 subtasks (GOO-3 → GOO-34) phân theo 6 sprints
|
||||||
|
- Tạo QA_TRACKER.md, cập nhật PROJECT_TRACKER.md
|
||||||
|
|
||||||
|
#### Đã sửa
|
||||||
|
- GOO-3: Fix double CSRF middleware — login/register/payment callbacks hoạt động (Sprint 1) ✅
|
||||||
|
|
||||||
|
#### Đang triển khai (Sprint 1 Blockers)
|
||||||
|
- GOO-4: UsageRecord atomic metering (fix quota bypass)
|
||||||
|
- GOO-5: Rate-limit POST /auth/exchange-token
|
||||||
|
- GOO-6: Fix MoMo IPN URL (tách ipnUrl khỏi redirectUrl)
|
||||||
|
- GOO-7: JWT validate user status (isActive + deletedAt)
|
||||||
|
|
||||||
|
#### Phát hiện chính (P0 — Launch Blockers)
|
||||||
|
- Thiếu Phone-OTP login (phương thức auth chính ở VN)
|
||||||
|
- legalStatus là free-text, không phải enum (tín hiệu tin cậy #1)
|
||||||
|
- Typesense không hỗ trợ tìm kiếm dấu tiếng Việt
|
||||||
|
- Thiếu phòng trọ (ROOM_RENTAL) trong PropertyType enum
|
||||||
|
- Quận 2/9 đã bị xóa (→ Thủ Đức) nhưng vẫn hardcoded trong UI
|
||||||
|
|
||||||
### Đã thêm (CEO Audit Wave 13 — 2026-04-12)
|
### Đã thêm (CEO Audit Wave 13 — 2026-04-12)
|
||||||
- Quy trình kiểm tra CEO (TEC-1915) — kiểm tra toàn bộ codebase + xem xét trạng thái dự án
|
- Quy trình kiểm tra CEO (TEC-1915) — kiểm tra toàn bộ codebase + xem xét trạng thái dự án
|
||||||
- Tài liệu kế hoạch với báo cáo 7 phần: tóm tắt kiểm tra, các vấn đề quan trọng, ưu tiên, khuyến nghị
|
- Tài liệu kế hoạch với báo cáo 7 phần: tóm tắt kiểm tra, các vấn đề quan trọng, ưu tiên, khuyến nghị
|
||||||
|
|||||||
285
CONTRIBUTING.md
285
CONTRIBUTING.md
@@ -1,5 +1,209 @@
|
|||||||
# Hướng Dẫn Đóng Góp
|
# Hướng Dẫn Đóng Góp
|
||||||
|
|
||||||
|
## Quy Trình Git & Branching
|
||||||
|
|
||||||
|
### Nhánh Chính
|
||||||
|
|
||||||
|
| Nhánh | Mục đích | Protected |
|
||||||
|
|-------|---------|-----------|
|
||||||
|
| `main` / `master` | Production branch — stable releases | ✅ Yes |
|
||||||
|
| `develop` | Development branch — integration point | ✅ Yes |
|
||||||
|
| `feature/*` | Feature branches — phát triển tính năng mới | ❌ No |
|
||||||
|
| `fix/*` | Bug fix branches | ❌ No |
|
||||||
|
| `docs/*` | Documentation updates | ❌ No |
|
||||||
|
| `refactor/*` | Code refactoring, cleanup | ❌ No |
|
||||||
|
|
||||||
|
### Quy Trình Tạo Feature Branch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Cập nhật branch chính
|
||||||
|
git checkout develop
|
||||||
|
git pull origin develop
|
||||||
|
|
||||||
|
# 2. Tạo feature branch
|
||||||
|
git checkout -b feature/awesome-feature
|
||||||
|
|
||||||
|
# Naming convention:
|
||||||
|
# feature/user-authentication
|
||||||
|
# fix/csrf-middleware-double-middleware
|
||||||
|
# docs/api-documentation
|
||||||
|
# refactor/remove-dead-code
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pull Request Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Commit changes
|
||||||
|
git add .
|
||||||
|
git commit -m "feat(auth): implement phone OTP login"
|
||||||
|
|
||||||
|
# 2. Push to origin
|
||||||
|
git push origin feature/awesome-feature
|
||||||
|
|
||||||
|
# 3. Open PR on GitHub
|
||||||
|
# - Title: Short summary (max 70 chars)
|
||||||
|
# - Description: Why, what changed, how to test
|
||||||
|
# - Link related issues: Fixes #GOO-7
|
||||||
|
# - Request reviewers: team lead, domain expert
|
||||||
|
|
||||||
|
# 4. Address review feedback
|
||||||
|
git add .
|
||||||
|
git commit -m "refactor(auth): address PR feedback"
|
||||||
|
git push
|
||||||
|
|
||||||
|
# 5. Merge when approved
|
||||||
|
# - Squash commits if many small fixes
|
||||||
|
# - Delete branch after merge
|
||||||
|
```
|
||||||
|
|
||||||
|
### Protected Branch Rules
|
||||||
|
|
||||||
|
`main` và `develop` branches yêu cầu:
|
||||||
|
|
||||||
|
- ✅ All CI checks pass (lint, typecheck, test, build)
|
||||||
|
- ✅ 1 approval từ code owner
|
||||||
|
- ✅ Dismiss stale PR approvals
|
||||||
|
- ✅ Branches must be up to date before merge
|
||||||
|
- ❌ Force push không được phép
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quy Ước Commit
|
||||||
|
|
||||||
|
Theo chuẩn **[Conventional Commits](https://www.conventionalcommits.org/)**:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Loại Commit (Type)
|
||||||
|
|
||||||
|
| Type | Mục đích | Ví dụ |
|
||||||
|
|------|---------|-------|
|
||||||
|
| **feat** | Tính năng mới | `feat(auth): add phone OTP login` |
|
||||||
|
| **fix** | Bug fix | `fix(csrf): remove double middleware` |
|
||||||
|
| **docs** | Tài liệu | `docs(readme): update setup instructions` |
|
||||||
|
| **style** | Code style (không thay đổi logic) | `style(payment): format code` |
|
||||||
|
| **refactor** | Refactor code | `refactor(search): extract filter logic` |
|
||||||
|
| **perf** | Performance improvement | `perf(search): add Typesense caching` |
|
||||||
|
| **test** | Test changes | `test(auth): add OTP verification tests` |
|
||||||
|
| **chore** | Dependencies, build, etc | `chore(deps): upgrade TypeScript 5.2` |
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
|
||||||
|
Scope là module/area bị ảnh hưởng:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(auth): ... # Auth module
|
||||||
|
feat(payments): ... # Payments module
|
||||||
|
feat(api): ... # General API
|
||||||
|
feat(web): ... # Frontend
|
||||||
|
feat(deps): ... # Dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
### Subject (Tiêu đề)
|
||||||
|
|
||||||
|
- ✅ Bắt đầu bằng **verb** (not past tense): "add", "fix", "remove"
|
||||||
|
- ✅ Viết **lowercase** (trừ proper nouns)
|
||||||
|
- ✅ **Không kết thúc** bằng dấu chấm
|
||||||
|
- ✅ Tối đa **50 ký tự**
|
||||||
|
- ❌ Không dùng "update", "change" — cụ thể hơn
|
||||||
|
|
||||||
|
### Body (Chi tiết)
|
||||||
|
|
||||||
|
Tùy chọn, giải thích **why** và **how**:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(payments): implement MoMo IPN webhook
|
||||||
|
|
||||||
|
Fix MoMo IPN callback to use correct backend URL instead of frontend URL.
|
||||||
|
|
||||||
|
- Extract ipnUrl from redirectUrl in MoMo service
|
||||||
|
- Validate HMAC signature before processing payment
|
||||||
|
- Add retry logic for idempotent callbacks
|
||||||
|
|
||||||
|
Fixes #GOO-6
|
||||||
|
```
|
||||||
|
|
||||||
|
### Footer (Tham chiếu)
|
||||||
|
|
||||||
|
Tham chiếu issue:
|
||||||
|
|
||||||
|
```
|
||||||
|
Fixes #GOO-7
|
||||||
|
Closes #GOO-8
|
||||||
|
Related to #GOO-5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ví dụ Hoàn Chỉnh
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(auth): implement phone OTP login flow
|
||||||
|
|
||||||
|
Add phone OTP as primary login method for Vietnamese users.
|
||||||
|
Simplifies sign-up process vs password login.
|
||||||
|
|
||||||
|
- Add OTP request endpoint: POST /auth/otp/request
|
||||||
|
- Add OTP verify endpoint: POST /auth/otp/verify
|
||||||
|
- Store OTP in Redis with 5min expiry
|
||||||
|
- Prevent brute force: max 3 attempts per phone per hour
|
||||||
|
- Add unit tests for OTP generation and verification
|
||||||
|
|
||||||
|
Fixes #GOO-11
|
||||||
|
Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kiểm Tra Commit Message
|
||||||
|
|
||||||
|
Dùng `husky` pre-commit hook:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tự động chạy khi git commit
|
||||||
|
# Kiểm tra:
|
||||||
|
# - Format conventional commits
|
||||||
|
# - No secrets (API keys, passwords)
|
||||||
|
# - Lint, typecheck
|
||||||
|
|
||||||
|
# Nếu hook thất bại, fix và commit lại
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pull Request Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Summary
|
||||||
|
Một dòng mô tả PR (tương tự commit subject).
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
- Điểm thay đổi 1
|
||||||
|
- Điểm thay đổi 2
|
||||||
|
- Điểm thay đổi 3
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
- [ ] Unit tests written
|
||||||
|
- [ ] E2E tests written (if applicable)
|
||||||
|
- [ ] Manual testing: describe steps
|
||||||
|
- [ ] No regressions found
|
||||||
|
|
||||||
|
## Screenshots / Logs (if applicable)
|
||||||
|
Paste images or log outputs.
|
||||||
|
|
||||||
|
## Related Issues
|
||||||
|
Fixes #GOO-7
|
||||||
|
Related to #GOO-5
|
||||||
|
|
||||||
|
## Notes for Reviewers
|
||||||
|
- Pay attention to X because Y
|
||||||
|
- Known limitations: Z
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Quy Ước Xử Lý Lỗi
|
## Quy Ước Xử Lý Lỗi
|
||||||
|
|
||||||
### Tổng Quan
|
### Tổng Quan
|
||||||
@@ -90,3 +294,84 @@ try {
|
|||||||
Tất cả các phương thức đọc của repository phải trả về DTOs được định kiểu rõ ràng — không bao giờ dùng `Promise<any>` hoặc `PaginatedResult<any>`. Định nghĩa read DTOs ở tầng domain cùng với interface của repository.
|
Tất cả các phương thức đọc của repository phải trả về DTOs được định kiểu rõ ràng — không bao giờ dùng `Promise<any>` hoặc `PaginatedResult<any>`. Định nghĩa read DTOs ở tầng domain cùng với interface của repository.
|
||||||
|
|
||||||
Xem `listing-read.dto.ts` để tham khảo ví dụ chuẩn.
|
Xem `listing-read.dto.ts` để tham khảo ví dụ chuẩn.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Review Checklist
|
||||||
|
|
||||||
|
Khi review PR, kiểm tra:
|
||||||
|
|
||||||
|
### Functionality
|
||||||
|
- [ ] Changes meet acceptance criteria
|
||||||
|
- [ ] No breaking changes (or documented)
|
||||||
|
- [ ] Error handling is robust
|
||||||
|
- [ ] Edge cases covered
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- [ ] Code follows conventions (style, naming, patterns)
|
||||||
|
- [ ] No `console.log`, `TODO` without issue reference
|
||||||
|
- [ ] No dead code, unused imports
|
||||||
|
- [ ] Functions have clear responsibility
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
- [ ] Unit tests cover happy path + error cases
|
||||||
|
- [ ] E2E tests for critical flows (if applicable)
|
||||||
|
- [ ] Coverage maintained / improved (API ≥60%, Web ≥50%)
|
||||||
|
- [ ] No flaky tests
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- [ ] Code comments explain "why", not "what"
|
||||||
|
- [ ] Updated docs if API/process changed
|
||||||
|
- [ ] Commit messages follow conventions
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- [ ] No hardcoded secrets (API keys, passwords)
|
||||||
|
- [ ] Input validation in place
|
||||||
|
- [ ] Auth checks in place
|
||||||
|
- [ ] No SQL injection (use Prisma, not raw SQL)
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- [ ] No N+1 queries
|
||||||
|
- [ ] Caching applied where appropriate
|
||||||
|
- [ ] No blocking operations in event loop
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Release Process
|
||||||
|
|
||||||
|
### Versioning
|
||||||
|
|
||||||
|
Tuân theo **Semantic Versioning**: `MAJOR.MINOR.PATCH`
|
||||||
|
|
||||||
|
- **MAJOR:** Breaking changes (require migration)
|
||||||
|
- **MINOR:** New features (backward compatible)
|
||||||
|
- **PATCH:** Bug fixes
|
||||||
|
|
||||||
|
### Creating a Release
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Update CHANGELOG.md with changes
|
||||||
|
# 2. Bump version in package.json (root)
|
||||||
|
# 3. Create git tag
|
||||||
|
git tag -a v1.5.0 -m "Release 1.5.0: Add phone OTP login"
|
||||||
|
git push origin v1.5.0
|
||||||
|
|
||||||
|
# 4. GitHub Actions automatically:
|
||||||
|
# - Builds Docker image
|
||||||
|
# - Pushes to GitHub Container Registry
|
||||||
|
# - Creates GitHub Release
|
||||||
|
# - Deploys to staging (auto)
|
||||||
|
# - Waits for manual approval for production
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Questions?
|
||||||
|
|
||||||
|
- 📖 Read `/docs/architecture.md` for system design
|
||||||
|
- 🏗️ Read `/docs/QUICK_REFERENCE.md` for patterns
|
||||||
|
- 💬 Ask on Slack `#dev` channel
|
||||||
|
- 🐛 File an issue: https://github.com/hongochai10/goodgo-bds-platform-ai/issues
|
||||||
|
|
||||||
|
**Happy coding! 🚀**
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,84 @@
|
|||||||
# GoodGo Platform AI — Theo Dõi Dự Án
|
# GoodGo Platform AI — Theo Dõi Dự Án
|
||||||
|
|
||||||
**Cập Nhật Lần Cuối:** 2026-04-12
|
**Cập Nhật Lần Cuối:** 2026-04-22
|
||||||
**Dự Án:** Goodgo Platform AI
|
**Dự Án:** Goodgo Platform AI
|
||||||
**Trạng Thái:** MVP Hoàn Thành — Giai Đoạn 7 (Cải Tiến Sau MVP) Wave 14 ✅ Build Xanh
|
**Trạng Thái:** GOO-2 Audit & Execution — Sprint 1 đang triển khai
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GOO-2 Lead Orchestrator Audit — Task Tracker (2026-04-22)
|
||||||
|
|
||||||
|
### Sprint 1 — Blockers + P0 Security
|
||||||
|
|
||||||
|
| Task | Tiêu đề | Ưu tiên | Owner | Trạng thái |
|
||||||
|
|------|---------|---------|-------|------------|
|
||||||
|
| GOO-3 | Fix double CSRF middleware | Critical | Backend TechLead | ✅ done |
|
||||||
|
| GOO-4 | UsageRecord atomic metering | Critical | Senior Backend Engineer | 🔄 in_progress |
|
||||||
|
| GOO-5 | Rate-limit exchange-token | Critical | Junior Backend Engineer | 🔄 in_progress |
|
||||||
|
| GOO-6 | Fix MoMo IPN URL | Critical | Middle Backend Engineer | 🔄 in_progress |
|
||||||
|
| GOO-7 | JWT validate user status | Critical | Security Engineer | 🔄 in_progress |
|
||||||
|
| GOO-8 | Encrypt SystemSetting secrets | Critical | Security Engineer | ⏳ todo |
|
||||||
|
| GOO-9 | Fix MCP status filter | Critical | Junior Backend Engineer | ⏳ todo |
|
||||||
|
| GOO-10 | Update PRODUCTION_READINESS.md | High | Doc Bot | 🔄 in_progress |
|
||||||
|
|
||||||
|
### Sprint 2 — P0 Features + Trust
|
||||||
|
|
||||||
|
| Task | Tiêu đề | Ưu tiên | Owner | Trạng thái |
|
||||||
|
|------|---------|---------|-------|------------|
|
||||||
|
| GOO-11 | Phone-OTP login | Critical | Backend TechLead | 🔄 in_progress |
|
||||||
|
| GOO-12 | legalStatus enum + badge | Critical | Senior Backend Engineer | ⏳ todo |
|
||||||
|
| GOO-13 | Vietnamese diacritic search | Critical | Backend TechLead | ⏳ todo |
|
||||||
|
| GOO-14 | Remove $queryRawUnsafe | High | Middle Backend Engineer | ⏳ todo |
|
||||||
|
| GOO-15 | Fix soft-deleted user login | High | Junior Backend Engineer | ⏳ todo (blocked by GOO-7) |
|
||||||
|
| GOO-16 | Fix obsolete districts | High | Middle Frontend Engineer | 🔄 in_progress |
|
||||||
|
| GOO-17 | ZNS template registration | High | CMO | 🚫 blocked |
|
||||||
|
|
||||||
|
### Sprint 3 — MVP Feature Completeness
|
||||||
|
|
||||||
|
| Task | Tiêu đề | Ưu tiên | Owner | Trạng thái |
|
||||||
|
|------|---------|---------|-------|------------|
|
||||||
|
| GOO-18 | Ward-level search | High | Middle Backend Engineer | ⏳ todo |
|
||||||
|
| GOO-19 | Scam/abuse report flow | High | Senior Backend Engineer | ⏳ todo |
|
||||||
|
| GOO-20 | ROOM_RENTAL property type | High | Junior Backend Engineer #2 | 🔄 in_progress |
|
||||||
|
| GOO-21 | Vietnam admin data (ĐVHCVN) | High | Database Architect | 🔄 in_progress |
|
||||||
|
| GOO-22 | Subscription plan seeding | Medium | Junior Backend Engineer #2 | ⏳ todo |
|
||||||
|
| GOO-23 | Module boundary fixes | Medium | Backend TechLead | ⏳ todo |
|
||||||
|
|
||||||
|
### Sprint 4 — Trust + Monetization
|
||||||
|
|
||||||
|
| Task | Tiêu đề | Owner | Trạng thái |
|
||||||
|
|------|---------|-------|------------|
|
||||||
|
| GOO-24 | Certificate verification | Senior Backend Engineer | ⏳ todo (blocked by GOO-12) |
|
||||||
|
| GOO-25 | Payment go-live checklist | DevOps Engineer | 🔄 in_progress |
|
||||||
|
| GOO-26 | Revenue stats fix | Middle Backend Engineer | ⏳ todo |
|
||||||
|
| GOO-27 | Float→Decimal migration | Database Architect | 🔄 in_progress |
|
||||||
|
| GOO-28 | Typesense+MinIO health | Infrastructure Engineer | 🔄 in_progress |
|
||||||
|
|
||||||
|
### Sprint 5 — UX + Performance
|
||||||
|
|
||||||
|
| Task | Tiêu đề | Owner | Trạng thái |
|
||||||
|
|------|---------|-------|------------|
|
||||||
|
| GOO-29 | Mobile swipe gallery | Middle Frontend Engineer | ⏳ todo |
|
||||||
|
| GOO-30 | Listing expiry notification | Middle Backend Engineer | ⏳ todo |
|
||||||
|
| GOO-31 | AVM circuit breaker | Infrastructure Engineer | ⏳ todo |
|
||||||
|
| GOO-34 | P2 batch fixes | Founding Engineer | 🔄 in_progress |
|
||||||
|
|
||||||
|
### Sprint 6 — Architecture + Docs
|
||||||
|
|
||||||
|
| Task | Tiêu đề | Owner | Trạng thái |
|
||||||
|
|------|---------|-------|------------|
|
||||||
|
| GOO-32 | Architecture hygiene batch | Backend TechLead | ⏳ todo |
|
||||||
|
| GOO-33 | Documentation updates | Doc Bot | 🔄 in_progress |
|
||||||
|
|
||||||
|
### Tổng kết tiến độ
|
||||||
|
- **Done:** 1/32 (3%)
|
||||||
|
- **In progress:** 13/32 (41%)
|
||||||
|
- **Todo:** 16/32 (50%)
|
||||||
|
- **Blocked:** 2/32 (6%)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lịch sử Giai Đoạn 0-7 (trước GOO-2 audit)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
85
docs/QA_TRACKER.md
Normal file
85
docs/QA_TRACKER.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# GoodGo Platform — QA Tracker
|
||||||
|
|
||||||
|
**Cập nhật lần cuối:** 2026-04-22
|
||||||
|
**Nguồn:** GOO-2 Lead Orchestrator Audit
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Baseline QA Status (từ audit 2026-04-12)
|
||||||
|
|
||||||
|
| Metric | Kết quả |
|
||||||
|
|--------|---------|
|
||||||
|
| Lint (ESLint) | PASS — 0 lỗi |
|
||||||
|
| TypeScript | 7 lỗi (thiếu kiểu vitest trong web test files) |
|
||||||
|
| Unit tests | 232 files, 1454 tests — ALL PASS |
|
||||||
|
| Build | ALL 3 packages build thành công |
|
||||||
|
| E2E | Chưa chạy lại sau audit |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Blocker Findings (BƯỚC 1 Audit — cần QA sau fix)
|
||||||
|
|
||||||
|
| ID | Mô tả | Task | Trạng thái QA | Mức ảnh hưởng |
|
||||||
|
|----|-------|------|---------------|---------------|
|
||||||
|
| BLOCKER-1 | Double CSRF middleware — login/register broken in prod | GOO-3 ✅ | Cần verify | Critical |
|
||||||
|
| BLOCKER-2 | UsageRecord race condition — quota bypass | GOO-4 | Chờ fix | Critical |
|
||||||
|
| BLOCKER-3 | exchange-token no rate limit | GOO-5 | Chờ fix | Critical |
|
||||||
|
| GAP-03 | MoMo IPN URL points to frontend | GOO-6 | Chờ fix | Critical |
|
||||||
|
| A-19 | MCP search returns 0 results (status case) | GOO-9 | Chờ fix | Critical |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Findings (cần QA sau fix)
|
||||||
|
|
||||||
|
| ID | Mô tả | Task | Trạng thái QA |
|
||||||
|
|----|-------|------|---------------|
|
||||||
|
| HIGH-1 | JWT doesn't check banned users | GOO-7 | Chờ fix |
|
||||||
|
| HIGH-2 | AI API key stored plaintext | GOO-8 | Chờ fix |
|
||||||
|
| HIGH-4 | $queryRawUnsafe in project search | GOO-14 | Chờ fix |
|
||||||
|
| MED-9 | Soft-deleted users can login | GOO-15 | Chờ fix |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Plan — Sprint 1 Verification
|
||||||
|
|
||||||
|
### API Tests (curl)
|
||||||
|
- [ ] POST /auth/login without CSRF token → 200 (not 403)
|
||||||
|
- [ ] POST /auth/register without CSRF token → 200
|
||||||
|
- [ ] POST /payments/callback/vnpay without CSRF → 200
|
||||||
|
- [ ] POST /payments/callback/momo → verifies IPN reaches backend
|
||||||
|
- [ ] POST /auth/exchange-token 6x in 60s → 429 on 6th
|
||||||
|
- [ ] Login with banned user (isActive=false) → 401
|
||||||
|
- [ ] Login with soft-deleted user (deletedAt set) → 401
|
||||||
|
- [ ] 5 concurrent listing creates → quota not exceeded
|
||||||
|
- [ ] MCP property-search tool → returns ACTIVE listings
|
||||||
|
|
||||||
|
### UI Tests (Playwright)
|
||||||
|
- [ ] Login page loads without CSRF error
|
||||||
|
- [ ] Registration flow completes
|
||||||
|
- [ ] Search returns results (Vietnamese diacritics — Sprint 2)
|
||||||
|
- [ ] Admin dashboard loads for admin user, redirects for non-admin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Plan — Sprint 2 Verification
|
||||||
|
|
||||||
|
- [ ] Phone OTP login: request → receive → verify → authenticated
|
||||||
|
- [ ] legalStatus dropdown shows enum values (not free text)
|
||||||
|
- [ ] Search "chung cu quan 7" matches "chung cư quận 7"
|
||||||
|
- [ ] District dropdown shows "Thủ Đức" (not Quận 2/9)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug Tracking
|
||||||
|
|
||||||
|
| Bug ID | Mô tả | Task liên quan | Severity | Trạng thái |
|
||||||
|
|--------|-------|----------------|----------|------------|
|
||||||
|
| (none yet) | — | — | — | — |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- QA sẽ chạy full regression sau khi Sprint 1 hoàn thành
|
||||||
|
- E2E tests cần Playwright config update cho new auth flows (Sprint 2)
|
||||||
|
- Performance benchmarks sẽ chạy sau Sprint 4 (revenue stats, dashboard queries)
|
||||||
49
docs/audits/CANONICAL_INDEX.md
Normal file
49
docs/audits/CANONICAL_INDEX.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Audit Files Index
|
||||||
|
|
||||||
|
**Last Updated:** 2026-04-22
|
||||||
|
|
||||||
|
This folder contains curated canonical audit reports. Temporary or duplicate audit files have been archived.
|
||||||
|
|
||||||
|
## Canonical Audit Reports
|
||||||
|
|
||||||
|
| File | Date | Purpose | Status |
|
||||||
|
|------|------|---------|--------|
|
||||||
|
| **AUDIT_INDEX.md** | 2026-04-22 | Main audit findings index | Current |
|
||||||
|
| **AUDIT_SUMMARY.md** | 2026-04-22 | Executive audit summary | Current |
|
||||||
|
| **COMPREHENSIVE_AUDIT_2026-04-12.md** | 2026-04-12 | Full CEO audit report (8 parts) | Reference |
|
||||||
|
| **API_AUDIT_REPORT.md** | 2026-04-19 | Backend API audit (handlers, errors) | Current |
|
||||||
|
| **WEB_AUDIT_SUMMARY.md** | 2026-04-19 | Frontend Web audit (components, tests) | Current |
|
||||||
|
| **TEST_COVERAGE_AUDIT.md** | 2026-04-19 | Unit test coverage analysis | Current |
|
||||||
|
| **INFRASTRUCTURE_RUNBOOK.md** | 2026-04-19 | Infrastructure setup & troubleshooting | Current |
|
||||||
|
| **MCP_QUICK_REFERENCE.md** | 2026-04-19 | MCP servers documentation | Current |
|
||||||
|
| **CODE_QUALITY_AUDIT.md** | 2026-04-19 | Code quality metrics & patterns | Current |
|
||||||
|
| **AUDIT_DETAILED_CHECKLIST.md** | 2026-04-19 | Detailed verification checklist | Reference |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How to Use These Reports
|
||||||
|
|
||||||
|
### For New Team Members
|
||||||
|
1. Start with **AUDIT_SUMMARY.md** (5 min read)
|
||||||
|
2. Then **WEB_AUDIT_SUMMARY.md** + **API_AUDIT_REPORT.md** (10 min each)
|
||||||
|
3. Reference **INFRASTRUCTURE_RUNBOOK.md** for setup
|
||||||
|
|
||||||
|
### For Architecture Decisions
|
||||||
|
- Read **COMPREHENSIVE_AUDIT_2026-04-12.md** (full context)
|
||||||
|
- Check **CODE_QUALITY_AUDIT.md** for patterns
|
||||||
|
|
||||||
|
### For Testing & Coverage
|
||||||
|
- **TEST_COVERAGE_AUDIT.md** — current coverage gaps
|
||||||
|
- **AUDIT_DETAILED_CHECKLIST.md** — what needs testing
|
||||||
|
|
||||||
|
### For DevOps & Setup
|
||||||
|
- **INFRASTRUCTURE_RUNBOOK.md** — deployment & troubleshooting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Archived Files
|
||||||
|
|
||||||
|
Old audit exploration files have been moved to `.archive/` for reference but are not actively maintained. If you need historical context, check the archive.
|
||||||
|
|
||||||
|
**Curation date:** 2026-04-22
|
||||||
|
**Curator:** Doc Bot (GOO-33)
|
||||||
355
docs/ci-cd.md
Normal file
355
docs/ci-cd.md
Normal file
@@ -0,0 +1,355 @@
|
|||||||
|
# CI/CD Pipeline
|
||||||
|
|
||||||
|
GoodGo Platform sử dụng **GitHub Actions** để tự động hóa build, test, và deployment. Pipeline tuân theo quy tắc _"build once, deploy anywhere"_ — một lần build, deploy tới nhiều môi trường.
|
||||||
|
|
||||||
|
## Pipeline Overview
|
||||||
|
|
||||||
|
Pipeline bao gồm **4 bước chính**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Lint (ESLint)
|
||||||
|
↓
|
||||||
|
2. TypeScript Check (tsc)
|
||||||
|
↓
|
||||||
|
3. Unit Tests (Jest/Vitest)
|
||||||
|
↓
|
||||||
|
4. Build (Turborepo)
|
||||||
|
```
|
||||||
|
|
||||||
|
Mỗi bước phải **pass** trước khi tiếp tục bước tiếp theo. Nếu bất kỳ bước nào thất bại, workflow dừng ngay.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Trigger Events
|
||||||
|
|
||||||
|
Pipeline **tự động chạy** khi:
|
||||||
|
|
||||||
|
| Event | Branches | Hành động |
|
||||||
|
|-------|----------|----------|
|
||||||
|
| **Push** | `main`, `master`, `develop` | Chạy đầy đủ pipeline (lint → typecheck → test → build) |
|
||||||
|
| **Pull Request** | Bất kỳ branch | Chạy đầy đủ pipeline trước khi merge |
|
||||||
|
| **Manual** (workflow_dispatch) | Bất kỳ branch | Cho phép chạy thủ công từ GitHub UI |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Lint (ESLint)
|
||||||
|
|
||||||
|
**File:** `.github/workflows/ci.yml` → job `lint`
|
||||||
|
|
||||||
|
**Mục đích:** Kiểm tra style code, conventions, và phát hiện anti-patterns.
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```bash
|
||||||
|
pnpm lint
|
||||||
|
```
|
||||||
|
|
||||||
|
**Chi tiết:**
|
||||||
|
- ESLint config: `.eslintrc.json` (root)
|
||||||
|
- Rules: import order, naming, unused variables, etc.
|
||||||
|
- Auto-fixable issues: `pnpm lint --fix`
|
||||||
|
- **Non-auto-fixable issues:** workflow fails, developer phải fix thủ công
|
||||||
|
|
||||||
|
**Quy tắc chính:**
|
||||||
|
- ✅ Imports ordered: `external` → `internal` (path alias) → `relative`
|
||||||
|
- ✅ Không dùng `any` type (TypeScript strict)
|
||||||
|
- ✅ Không có unused variables hoặc imports
|
||||||
|
- ❌ Không mix `require()` và `import` (dùng `import` for ES6)
|
||||||
|
|
||||||
|
**Thời gian:** ~30 giây
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: TypeScript Check (tsc)
|
||||||
|
|
||||||
|
**File:** `.github/workflows/ci.yml` → job `typecheck`
|
||||||
|
|
||||||
|
**Mục đích:** Kiểm tra type safety toàn codebase mà không compile.
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```bash
|
||||||
|
pnpm typecheck
|
||||||
|
```
|
||||||
|
|
||||||
|
**Chi tiết:**
|
||||||
|
- Chạy `tsc --noEmit` trên mỗi package (API, Web, etc.)
|
||||||
|
- **TypeScript config:** `tsconfig.json` (per-package)
|
||||||
|
- **Strict mode:** `strict: true`
|
||||||
|
- Path aliases: `@modules/*` (API), `@/*` (Web)
|
||||||
|
|
||||||
|
**Quy tắc chính:**
|
||||||
|
- ✅ Mọi function phải có explicit return types
|
||||||
|
- ✅ Mọi parameter phải có type annotation (strict)
|
||||||
|
- ✅ Generic types phải đủ specificity (không `Promise<any>`)
|
||||||
|
- ❌ Không dùng `any` ngoại lệ (có comment `@ts-expect-error`)
|
||||||
|
|
||||||
|
**Thời gian:** ~45 giây
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Unit Tests (Jest/Vitest)
|
||||||
|
|
||||||
|
**File:** `.github/workflows/ci.yml` → job `test`
|
||||||
|
|
||||||
|
**Mục đích:** Chạy suite kiểm thử đơn vị, đảm bảo logic ngữ nghĩa đúng.
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```bash
|
||||||
|
pnpm test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Chi tiết:**
|
||||||
|
- **API (NestJS):** Jest + `@nestjs/testing`
|
||||||
|
- **Web (Next.js):** Vitest + `@testing-library/react`
|
||||||
|
- Coverage threshold: API ≥ 60%, Web ≥ 50%
|
||||||
|
- Snapshot tests: must commit `.snap` file changes
|
||||||
|
|
||||||
|
**Test Locations:**
|
||||||
|
| Package | Pattern | Runner |
|
||||||
|
|---------|---------|--------|
|
||||||
|
| API | `apps/api/src/**/*.spec.ts` | Jest |
|
||||||
|
| Web | `apps/web/src/**/*.spec.ts` | Vitest |
|
||||||
|
| Libs | `libs/**/*.spec.ts` | Jest (libs) / Vitest (web libs) |
|
||||||
|
|
||||||
|
**Quy tắc viết Test:**
|
||||||
|
- ✅ Test AAA pattern: **A**rrange → **A**ct → **A**ssert
|
||||||
|
- ✅ Describe blocks: `[Unit/Integration] ClassName.method()`
|
||||||
|
- ✅ Mock external dependencies (database, HTTP, etc.)
|
||||||
|
- ✅ Test happy path + error cases + edge cases
|
||||||
|
- ❌ Không mock internal logic
|
||||||
|
- ❌ Không snapshot-test component trees (snapshot brittle)
|
||||||
|
|
||||||
|
**Thời gian:** ~60 giây (API), ~30 giây (Web)
|
||||||
|
|
||||||
|
**Xem coverage:**
|
||||||
|
```bash
|
||||||
|
pnpm test -- --coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Build (Turborepo)
|
||||||
|
|
||||||
|
**File:** `.github/workflows/ci.yml` → job `build`
|
||||||
|
|
||||||
|
**Mục đích:** Production build tất cả packages, detect breaking changes.
|
||||||
|
|
||||||
|
**Command:**
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
**Chi tiết:**
|
||||||
|
- **Turborepo caching:** Bỏ qua build lại nếu code không thay đổi
|
||||||
|
- **Targets:** `apps/api`, `apps/web`, `libs/ai-services`, `libs/mcp-servers`
|
||||||
|
- **Output:** `dist/` per-package
|
||||||
|
- Vô hiệu hóa cache: `--no-cache`
|
||||||
|
|
||||||
|
**Per-Package Build:**
|
||||||
|
| Package | Command | Output | Notes |
|
||||||
|
|---------|---------|--------|-------|
|
||||||
|
| API | `nest build` | `dist/` | Production NestJS app |
|
||||||
|
| Web | `next build` | `.next/` | Optimized Next.js app |
|
||||||
|
| AI Services | `docker build` | `Dockerfile` | Python FastAPI image (không build trong CI, chỉ lint + test) |
|
||||||
|
| MCP Servers | Bundled vào API | N/A | TypeScript → JS |
|
||||||
|
|
||||||
|
**Thời gian:** ~120 giây (with Turborepo cache)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling & Retry
|
||||||
|
|
||||||
|
### CI Pipeline Retry Policy
|
||||||
|
|
||||||
|
| Failure | Action | Retry |
|
||||||
|
|---------|--------|-------|
|
||||||
|
| Network timeout | Auto-retry (3x) | Yes, within same run |
|
||||||
|
| Out of disk space | Fail + notify | Manual re-run |
|
||||||
|
| Flaky test | Fail (capture logs) | Manual re-run w/ `--seed` |
|
||||||
|
| Git checkout error | Fail + notify | Manual re-run |
|
||||||
|
|
||||||
|
### Local Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Replicate exact CI environment locally
|
||||||
|
docker pull ghcr.io/actions/runner:latest
|
||||||
|
|
||||||
|
# Or run pnpm commands locally
|
||||||
|
pnpm install
|
||||||
|
pnpm lint --debug
|
||||||
|
pnpm typecheck
|
||||||
|
pnpm test -- --bail # stop on first failure
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## GitHub Status Checks
|
||||||
|
|
||||||
|
Pull requests require **all 4 checks to pass** before merging:
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ lint
|
||||||
|
✅ typecheck
|
||||||
|
✅ test
|
||||||
|
✅ build
|
||||||
|
```
|
||||||
|
|
||||||
|
**Branch Protection Rules:**
|
||||||
|
|
||||||
|
- ✅ Require PR reviews: 1 approval minimum (code owner)
|
||||||
|
- ✅ Dismiss stale PR approvals
|
||||||
|
- ✅ Require status checks to pass
|
||||||
|
- ✅ Require branches to be up to date before merging
|
||||||
|
- ✅ Restrict who can push to matching branches (admin only)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Secrets & Environment Variables
|
||||||
|
|
||||||
|
### CI-Only Secrets (`.github/secrets/`)
|
||||||
|
|
||||||
|
| Secret | Used In | Purpose |
|
||||||
|
|--------|---------|---------|
|
||||||
|
| `DATABASE_URL_TEST` | Step 3 (test) | Test database (PostgreSQL) |
|
||||||
|
| `REDIS_URL_TEST` | Step 3 (test) | Test Redis cache |
|
||||||
|
| `OPENAI_API_KEY` | Step 3 (test) | Claude API (if needed) |
|
||||||
|
|
||||||
|
### Build-Time Env (`.env.ci`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# .env.ci (git-tracked)
|
||||||
|
NODE_ENV=test
|
||||||
|
LOG_LEVEL=warn
|
||||||
|
DATABASE_POOL_MIN=2
|
||||||
|
DATABASE_POOL_MAX=5
|
||||||
|
REDIS_TIMEOUT=5000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deploy-Only Secrets (`.github/secrets/` for Production)
|
||||||
|
|
||||||
|
| Secret | Deploy Target | Used In |
|
||||||
|
|--------|---------------|---------|
|
||||||
|
| `DOCKER_REGISTRY_TOKEN` | Docker Hub / GitHub Container Registry | Push image after build success |
|
||||||
|
| `DEPLOY_SSH_KEY` | Production VPS | SSH into server + restart service |
|
||||||
|
| `SENTRY_AUTH_TOKEN` | Sentry.io | Release tracking |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment Strategy
|
||||||
|
|
||||||
|
### Auto-Deploy Trigger
|
||||||
|
|
||||||
|
Push to `main` → All checks pass → Auto-deploy to **Staging**
|
||||||
|
|
||||||
|
```
|
||||||
|
main branch
|
||||||
|
↓ (all checks pass)
|
||||||
|
↓
|
||||||
|
Auto-build Docker image
|
||||||
|
↓
|
||||||
|
Push to GitHub Container Registry (ghcr.io)
|
||||||
|
↓
|
||||||
|
SSH into staging server
|
||||||
|
↓
|
||||||
|
docker pull + docker-compose up -d
|
||||||
|
↓
|
||||||
|
Health checks (/health/ready) → smoke tests
|
||||||
|
↓
|
||||||
|
✅ Deployed to staging
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Deploy to Production
|
||||||
|
|
||||||
|
**Only via GitHub Release tag:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag -a v1.5.0 -m "Release 1.5.0"
|
||||||
|
git push origin v1.5.0
|
||||||
|
```
|
||||||
|
|
||||||
|
Tag push triggers **manual approval** in GitHub → Deploy to Production.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring & Logs
|
||||||
|
|
||||||
|
### CI Logs
|
||||||
|
|
||||||
|
- **GitHub:** https://github.com/hongochai10/goodgo-bds-platform-ai/actions
|
||||||
|
- **Per-job logs:** Click workflow run → view step output
|
||||||
|
- **Artifact download:** Logs, coverage reports, etc.
|
||||||
|
|
||||||
|
### Production Deployment Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH into production server
|
||||||
|
ssh deploy@prod.goodgo.app
|
||||||
|
|
||||||
|
# View Docker Compose logs
|
||||||
|
docker-compose -f docker-compose.prod.yml logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Alerts
|
||||||
|
|
||||||
|
- **Slack:** Integration notify `#deployments` on failure
|
||||||
|
- **Email:** GitHub notifications to team@goodgo.app
|
||||||
|
- **Sentry:** Auto-capture runtime errors after deploy
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Tips
|
||||||
|
|
||||||
|
### Faster Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Lint & typecheck only (skip test & build for quick feedback)
|
||||||
|
pnpm lint && pnpm typecheck
|
||||||
|
|
||||||
|
# Test only changed files
|
||||||
|
pnpm test -- --onlyChanged
|
||||||
|
|
||||||
|
# Build only specific package
|
||||||
|
pnpm --filter api build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Faster CI (reduce time)
|
||||||
|
|
||||||
|
1. **Cache optimization:** Turborepo already caches built packages
|
||||||
|
2. **Parallel jobs:** Lint + typecheck run simultaneously (in CI yaml, set `runs-on: ubuntu-latest`)
|
||||||
|
3. **Skip full build on patch commits:** Use `[skip ci]` in commit message (not recommended for main)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common CI Failures
|
||||||
|
|
||||||
|
| Error | Cause | Fix |
|
||||||
|
|-------|-------|-----|
|
||||||
|
| `ESLint error: Import not sorted` | Import order wrong | `pnpm lint --fix` |
|
||||||
|
| `TypeScript error: Type 'any'` | Strict type checking | Add explicit type annotation |
|
||||||
|
| `Jest timeout: test took > 5000ms` | Slow test (DB, network) | Mock external calls, increase timeout `jest.setTimeout(10000)` |
|
||||||
|
| `Out of disk space` | GitHub runner full | Clear cache, reduce artifact retention |
|
||||||
|
| `pnpm install stuck` | Network issue | Retry: `rm -rf node_modules && pnpm install` |
|
||||||
|
|
||||||
|
### Re-run CI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Re-run entire workflow (GitHub UI)
|
||||||
|
Actions tab → select workflow → click "Re-run" button
|
||||||
|
|
||||||
|
# Or locally, push empty commit
|
||||||
|
git commit --allow-empty -m "Trigger CI"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- GitHub Actions: https://docs.github.com/en/actions
|
||||||
|
- Turborepo Caching: https://turbo.build/repo/docs/core-concepts/caching
|
||||||
|
- ESLint Config: `/.eslintrc.json`
|
||||||
|
- TypeScript Config: `/tsconfig.json` (root), `/apps/api/tsconfig.json`, etc.
|
||||||
|
- Jest Config: `/apps/api/jest.config.js`
|
||||||
|
- Vitest Config: `/apps/web/vitest.config.ts`
|
||||||
600
docs/mcp-servers.md
Normal file
600
docs/mcp-servers.md
Normal file
@@ -0,0 +1,600 @@
|
|||||||
|
# MCP Servers Documentation
|
||||||
|
|
||||||
|
GoodGo Platform sử dụng **Model Context Protocol (MCP)** để cấp quyền truy cập các công cụ chuyên dụng cho Claude AI. Ba MCP servers chính cung cấp khả năng **tìm kiếm bất động sản**, **phân tích thị trường**, và **định giá tự động (AVM)**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
| Server | Port | Tools | Purpose |
|
||||||
|
|--------|------|-------|---------|
|
||||||
|
| **Property Search** | 3001 | `search_properties`, `compare_properties`, `get_property_details` | Tìm kiếm bất động sản, so sánh, chi tiết |
|
||||||
|
| **Market Analytics** | 3001 | `get_market_report`, `analyze_trends`, `get_price_indices` | Phân tích thị trường, xu hướng giá |
|
||||||
|
| **Valuation** | 3001 | `estimate_valuation`, `extract_features`, `compare_valuations` | Định giá BĐS, so sánh XGBoost |
|
||||||
|
|
||||||
|
**Architecture:**
|
||||||
|
- All MCP servers are **in-process** (không tách microservice cho MVP)
|
||||||
|
- Chạy cùng process NestJS API
|
||||||
|
- HTTP transport dùng `/mcp/tools/*` endpoint
|
||||||
|
- Claude API calls `POST /mcp/tools/{toolName}` để invoke tools
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Property Search Server
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Cung cấp công cụ tìm kiếm bất động sản với:
|
||||||
|
- Full-text search (Typesense)
|
||||||
|
- Geo-spatial search (PostGIS)
|
||||||
|
- Faceted filtering (giá, loại, phòng ngủ, quận huyện)
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
#### `search_properties`
|
||||||
|
|
||||||
|
Tìm kiếm bất động sản với nhiều tiêu chí.
|
||||||
|
|
||||||
|
**Input Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"query": "chung cư quận 1",
|
||||||
|
"filters": {
|
||||||
|
"priceMin": 1000000000,
|
||||||
|
"priceMax": 5000000000,
|
||||||
|
"bedrooms": 2,
|
||||||
|
"district": "Quận 1",
|
||||||
|
"propertyType": "APARTMENT"
|
||||||
|
},
|
||||||
|
"sort": "price_asc",
|
||||||
|
"limit": 10,
|
||||||
|
"offset": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
| Param | Type | Required | Default | Notes |
|
||||||
|
|-------|------|----------|---------|-------|
|
||||||
|
| `query` | string | Yes | - | Tìm kiếm từ khóa (tiêu đề, mô tả) |
|
||||||
|
| `filters.priceMin` | number | No | 0 | Giá tối thiểu (VND) |
|
||||||
|
| `filters.priceMax` | number | No | ∞ | Giá tối đa (VND) |
|
||||||
|
| `filters.bedrooms` | number | No | - | Số phòng ngủ |
|
||||||
|
| `filters.district` | string | No | - | Quận huyện |
|
||||||
|
| `filters.propertyType` | enum | No | - | APARTMENT, HOUSE, LAND, ROOM_RENTAL |
|
||||||
|
| `sort` | enum | No | `relevance` | `price_asc`, `price_desc`, `newest`, `relevance` |
|
||||||
|
| `limit` | number | No | 20 | Số kết quả (1-100) |
|
||||||
|
| `offset` | number | No | 0 | Phân trang offset |
|
||||||
|
|
||||||
|
**Output Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"total": 245,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": "prop-001",
|
||||||
|
"title": "Căn hộ 2PN view sông Sài Gòn",
|
||||||
|
"type": "APARTMENT",
|
||||||
|
"price": 3500000000,
|
||||||
|
"bedrooms": 2,
|
||||||
|
"bathrooms": 2,
|
||||||
|
"area": 85,
|
||||||
|
"district": "Quận 1",
|
||||||
|
"ward": "Phường Bến Nghé",
|
||||||
|
"address": "123 Tôn Đức Thắng, Q.1",
|
||||||
|
"agentName": "Nguyễn Văn A",
|
||||||
|
"agentPhone": "0987654321",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"createdAt": "2026-04-20T10:30:00Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"facets": {
|
||||||
|
"districts": [
|
||||||
|
{ "name": "Quận 1", "count": 42 },
|
||||||
|
{ "name": "Quận 3", "count": 38 }
|
||||||
|
],
|
||||||
|
"propertyTypes": [
|
||||||
|
{ "name": "APARTMENT", "count": 120 },
|
||||||
|
{ "name": "HOUSE", "count": 85 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `compare_properties`
|
||||||
|
|
||||||
|
So sánh 2-5 bất động sản dựa trên giá, diện tích, vị trí, etc.
|
||||||
|
|
||||||
|
**Input Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"propertyIds": ["prop-001", "prop-002", "prop-003"],
|
||||||
|
"metrics": ["price", "area", "price_per_sqm", "location_score", "agent_rating"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"comparison": [
|
||||||
|
{
|
||||||
|
"propertyId": "prop-001",
|
||||||
|
"title": "Căn hộ 2PN Q.1",
|
||||||
|
"price": 3500000000,
|
||||||
|
"area": 85,
|
||||||
|
"price_per_sqm": 41176470,
|
||||||
|
"location_score": 4.8,
|
||||||
|
"agent_rating": 4.5
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"propertyId": "prop-002",
|
||||||
|
"title": "Căn hộ 2PN Q.3",
|
||||||
|
"price": 2800000000,
|
||||||
|
"area": 78,
|
||||||
|
"price_per_sqm": 35897436,
|
||||||
|
"location_score": 4.3,
|
||||||
|
"agent_rating": 4.2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recommendation": "prop-002 có giá tốt hơn với vị trí tương đương"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `get_property_details`
|
||||||
|
|
||||||
|
Lấy chi tiết đầy đủ của một bất động sản.
|
||||||
|
|
||||||
|
**Input Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"propertyId": "prop-001"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "prop-001",
|
||||||
|
"title": "Căn hộ 2PN view sông Sài Gòn",
|
||||||
|
"description": "Căn hộ cao cấp tại trung tâm Q.1...",
|
||||||
|
"type": "APARTMENT",
|
||||||
|
"price": 3500000000,
|
||||||
|
"bedrooms": 2,
|
||||||
|
"bathrooms": 2,
|
||||||
|
"area": 85,
|
||||||
|
"district": "Quận 1",
|
||||||
|
"ward": "Phường Bến Nghé",
|
||||||
|
"address": "123 Tôn Đức Thắng, Q.1",
|
||||||
|
"coordinates": {
|
||||||
|
"latitude": 10.7769,
|
||||||
|
"longitude": 106.7009
|
||||||
|
},
|
||||||
|
"features": ["view sông", "ban công rộng", "tầng cao", "gần metro"],
|
||||||
|
"agent": {
|
||||||
|
"id": "agent-001",
|
||||||
|
"name": "Nguyễn Văn A",
|
||||||
|
"phone": "0987654321",
|
||||||
|
"rating": 4.5,
|
||||||
|
"reviews": 47
|
||||||
|
},
|
||||||
|
"media": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://storage.goodgo.app/prop-001/img-1.jpg"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"createdAt": "2026-04-20T10:30:00Z",
|
||||||
|
"reviews": [
|
||||||
|
{
|
||||||
|
"author": "Trần Văn B",
|
||||||
|
"rating": 5,
|
||||||
|
"text": "Căn hộ đẹp, vị trí tuyệt vời"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Market Analytics Server
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Cung cấp dữ liệu thị trường bất động sản:
|
||||||
|
- Báo cáo giá theo quận huyện
|
||||||
|
- Phân tích xu hướng giá theo thời gian
|
||||||
|
- Chỉ số thị trường
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
#### `get_market_report`
|
||||||
|
|
||||||
|
Lấy báo cáo thị trường theo quận huyện.
|
||||||
|
|
||||||
|
**Input Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"district": "Quận 1",
|
||||||
|
"propertyType": "APARTMENT",
|
||||||
|
"period": "monthly"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"district": "Quận 1",
|
||||||
|
"propertyType": "APARTMENT",
|
||||||
|
"period": "April 2026",
|
||||||
|
"statistics": {
|
||||||
|
"averagePrice": 3200000000,
|
||||||
|
"medianPrice": 3100000000,
|
||||||
|
"priceMin": 800000000,
|
||||||
|
"priceMax": 8500000000,
|
||||||
|
"averageArea": 82,
|
||||||
|
"averagePricePerSqm": 39024390,
|
||||||
|
"activeListings": 342,
|
||||||
|
"soldListings": 28,
|
||||||
|
"rentedListings": 15
|
||||||
|
},
|
||||||
|
"trends": {
|
||||||
|
"priceChangePercent": 2.5,
|
||||||
|
"priceChangeVsLastMonth": "Tăng 2.5%",
|
||||||
|
"velocityDays": 15
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `analyze_trends`
|
||||||
|
|
||||||
|
Phân tích xu hướng giá theo thời gian.
|
||||||
|
|
||||||
|
**Input Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"district": "Quận 1",
|
||||||
|
"propertyType": "APARTMENT",
|
||||||
|
"months": 12
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"district": "Quận 1",
|
||||||
|
"propertyType": "APARTMENT",
|
||||||
|
"trend": [
|
||||||
|
{
|
||||||
|
"month": "May 2025",
|
||||||
|
"averagePrice": 2900000000,
|
||||||
|
"medianPrice": 2800000000,
|
||||||
|
"activeListings": 250
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "April 2026",
|
||||||
|
"averagePrice": 3200000000,
|
||||||
|
"medianPrice": 3100000000,
|
||||||
|
"activeListings": 342
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"forecast": {
|
||||||
|
"nextMonthPredicted": 3280000000,
|
||||||
|
"confidence": 0.75,
|
||||||
|
"direction": "up"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `get_price_indices`
|
||||||
|
|
||||||
|
Lấy chỉ số giá toàn thị trường (bình thường hóa = 100).
|
||||||
|
|
||||||
|
**Input Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"baseMonth": "January 2025"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"baseIndex": 100,
|
||||||
|
"baseMonth": "January 2025",
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"month": "January 2025",
|
||||||
|
"indexValue": 100,
|
||||||
|
"growth": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"month": "April 2026",
|
||||||
|
"indexValue": 110.5,
|
||||||
|
"growth": 10.5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"byDistrict": {
|
||||||
|
"Quận 1": 112.3,
|
||||||
|
"Quận 3": 108.7,
|
||||||
|
"Thủ Đức": 105.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Valuation Server
|
||||||
|
|
||||||
|
### Purpose
|
||||||
|
|
||||||
|
Định giá tự động bất động sản dùng mô hình **XGBoost**:
|
||||||
|
- Estimate valuation based on features
|
||||||
|
- Extract features from description
|
||||||
|
- Compare valuations across similar properties
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
#### `estimate_valuation`
|
||||||
|
|
||||||
|
Ước lượng giá bất động sản dựa trên đặc trưng.
|
||||||
|
|
||||||
|
**Input Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"district": "Quận 1",
|
||||||
|
"propertyType": "APARTMENT",
|
||||||
|
"area": 85,
|
||||||
|
"bedrooms": 2,
|
||||||
|
"bathrooms": 2,
|
||||||
|
"features": ["view sông", "ban công", "tầng cao", "gần metro"],
|
||||||
|
"yearBuilt": 2015,
|
||||||
|
"location": {
|
||||||
|
"latitude": 10.7769,
|
||||||
|
"longitude": 106.7009
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"estimatedPrice": 3250000000,
|
||||||
|
"priceRange": {
|
||||||
|
"low": 2850000000,
|
||||||
|
"high": 3650000000
|
||||||
|
},
|
||||||
|
"confidence": 0.82,
|
||||||
|
"factors": {
|
||||||
|
"area": "Positive (85 sqm)",
|
||||||
|
"location": "High demand (Q.1)",
|
||||||
|
"features": "Premium (view sông, gần metro)",
|
||||||
|
"yearBuilt": "Neutral (11 years old)"
|
||||||
|
},
|
||||||
|
"comparables": [
|
||||||
|
{
|
||||||
|
"id": "prop-001",
|
||||||
|
"actualPrice": 3200000000,
|
||||||
|
"similarity": 0.89
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `extract_features`
|
||||||
|
|
||||||
|
Trích xuất đặc trưng từ mô tả bất động sản (NLP).
|
||||||
|
|
||||||
|
**Input Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"description": "Căn hộ cao cấp 2 phòng ngủ, 2 phòng tắm, view sông Sài Gòn, ban công rộng, gần trạm metro, tầng 15, xây năm 2015...",
|
||||||
|
"title": "Căn hộ 2PN view sông Sài Gòn"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"extracted": {
|
||||||
|
"bedrooms": 2,
|
||||||
|
"bathrooms": 2,
|
||||||
|
"area": null,
|
||||||
|
"features": ["view sông", "ban công", "gần metro", "tầng cao"],
|
||||||
|
"yearBuilt": 2015,
|
||||||
|
"condition": "tốt"
|
||||||
|
},
|
||||||
|
"confidence": {
|
||||||
|
"bedrooms": 0.98,
|
||||||
|
"features": 0.85,
|
||||||
|
"yearBuilt": 0.92
|
||||||
|
},
|
||||||
|
"uncertainties": [
|
||||||
|
"area not mentioned",
|
||||||
|
"exact floor number not in description"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `compare_valuations`
|
||||||
|
|
||||||
|
So sánh định giá của các bất động sản tương tự.
|
||||||
|
|
||||||
|
**Input Schema:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"referencePropertyId": "prop-001",
|
||||||
|
"candidatePropertyIds": ["prop-002", "prop-003", "prop-004"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Example:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"reference": {
|
||||||
|
"propertyId": "prop-001",
|
||||||
|
"title": "Căn hộ 2PN Q.1",
|
||||||
|
"actualPrice": 3500000000,
|
||||||
|
"estimatedPrice": 3250000000
|
||||||
|
},
|
||||||
|
"candidates": [
|
||||||
|
{
|
||||||
|
"propertyId": "prop-002",
|
||||||
|
"title": "Căn hộ 2PN Q.3",
|
||||||
|
"actualPrice": 2800000000,
|
||||||
|
"estimatedPrice": 2950000000,
|
||||||
|
"overUndervalued": "Undervalued by 5.1%",
|
||||||
|
"similarity": 0.76
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recommendation": "prop-002 is a good value compared to reference property"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
apps/api/src/
|
||||||
|
├── modules/
|
||||||
|
│ └── mcp/
|
||||||
|
│ ├── mcp.module.ts # Module definition
|
||||||
|
│ ├── mcp.controller.ts # HTTP endpoint: POST /mcp/tools/:toolName
|
||||||
|
│ ├── mcp-registry.service.ts # Registry: tool name → handler function
|
||||||
|
│ ├── servers/
|
||||||
|
│ │ ├── property-search.server.ts
|
||||||
|
│ │ ├── market-analytics.server.ts
|
||||||
|
│ │ └── valuation.server.ts
|
||||||
|
│ └── dto/
|
||||||
|
│ ├── search-properties.dto.ts
|
||||||
|
│ ├── market-report.dto.ts
|
||||||
|
│ └── estimate-valuation.dto.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### McpRegistryService
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Register tools
|
||||||
|
export class McpRegistryService {
|
||||||
|
private tools = new Map<string, Tool>();
|
||||||
|
|
||||||
|
register(name: string, tool: Tool) {
|
||||||
|
this.tools.set(name, tool);
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke(name: string, input: any): Promise<any> {
|
||||||
|
const tool = this.tools.get(name);
|
||||||
|
if (!tool) throw new NotFoundException(`Tool ${name} not found`);
|
||||||
|
return tool.execute(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTTP Endpoint
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Post('/mcp/tools/:toolName')
|
||||||
|
@Auth() // JWT required
|
||||||
|
async invokeTool(
|
||||||
|
@Param('toolName') toolName: string,
|
||||||
|
@Body() input: any
|
||||||
|
) {
|
||||||
|
return this.mcpRegistry.invoke(toolName, input);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tool Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Tool {
|
||||||
|
name: string;
|
||||||
|
schema: JsonSchema; // Input validation
|
||||||
|
execute(input: any): Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchPropertiesTool implements Tool {
|
||||||
|
name = 'search_properties';
|
||||||
|
schema = { /* JSON Schema */ };
|
||||||
|
|
||||||
|
execute(input: SearchPropertiesInput): Promise<any> {
|
||||||
|
// Implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
All MCP tools return consistent error format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "INVALID_INPUT",
|
||||||
|
"message": "District 'Quận XYZ' not found",
|
||||||
|
"details": {
|
||||||
|
"field": "district",
|
||||||
|
"value": "Quận XYZ"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Caching
|
||||||
|
|
||||||
|
- Market Analytics: Cache reports for 1 hour
|
||||||
|
- Property Search: Cache facets, invalidate on new listing
|
||||||
|
- Valuation: Cache model predictions for 24 hours
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
|
||||||
|
- Default: 100 req/minute per user
|
||||||
|
- MCP tools: 50 req/minute per tool (stricter)
|
||||||
|
- Burst: 10 req/second
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm test -- mcp/servers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test via HTTP
|
||||||
|
curl -X POST http://localhost:3001/mcp/tools/search_properties \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"query": "chung cu quan 1", "limit": 5}'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- MCP Specification: https://modelcontextprotocol.io/
|
||||||
|
- Claude API: https://anthropic.com/api
|
||||||
|
- Implementation: `apps/api/src/modules/mcp/`
|
||||||
515
docs/onboarding.md
Normal file
515
docs/onboarding.md
Normal file
@@ -0,0 +1,515 @@
|
|||||||
|
# Developer Onboarding Guide
|
||||||
|
|
||||||
|
Chào mừng đến với **GoodGo Platform** — một sàn giao dịch bất động sản Việt Nam được xây dựng trên NestJS, Next.js, PostgreSQL, và PostGIS.
|
||||||
|
|
||||||
|
Hướng dẫn này giúp bạn setup môi trường phát triển trong **< 30 phút**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites (Yêu cầu)
|
||||||
|
|
||||||
|
Trước khi bắt đầu, hãy cài đặt:
|
||||||
|
|
||||||
|
| Tool | Version | Link |
|
||||||
|
|------|---------|------|
|
||||||
|
| **Node.js** | ≥ 22.0.0 | https://nodejs.org/ |
|
||||||
|
| **pnpm** | ≥ 10.0.0 | https://pnpm.io/ |
|
||||||
|
| **PostgreSQL** | 16 + PostGIS | https://www.postgresql.org/ |
|
||||||
|
| **Docker & Docker Compose** | Latest | https://docker.com/ |
|
||||||
|
| **Git** | Latest | https://git-scm.com/ |
|
||||||
|
| **VS Code** (recommended) | Latest | https://code.visualstudio.com/ |
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Homebrew (if not already)
|
||||||
|
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
brew install node@22 pnpm postgresql@16 docker
|
||||||
|
brew install postgis # PostGIS extension
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
node --version # v22.x.x
|
||||||
|
pnpm --version # 10.x.x
|
||||||
|
psql --version # PostgreSQL 16.x
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ubuntu/Debian
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update package manager
|
||||||
|
sudo apt update
|
||||||
|
|
||||||
|
# Install Node.js 22
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||||
|
sudo apt install -y nodejs
|
||||||
|
|
||||||
|
# Install pnpm
|
||||||
|
npm install -g pnpm@latest
|
||||||
|
|
||||||
|
# Install PostgreSQL 16 + PostGIS
|
||||||
|
sudo apt install -y postgresql-16 postgresql-16-postgis
|
||||||
|
|
||||||
|
# Install Docker
|
||||||
|
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||||
|
sudo sh get-docker.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows (WSL2 Recommended)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Inside WSL2 Ubuntu:
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
|
||||||
|
sudo apt install -y nodejs postgresql-16 postgresql-16-postgis
|
||||||
|
npm install -g pnpm@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Clone Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repo
|
||||||
|
git clone https://github.com/hongochai10/goodgo-bds-platform-ai.git
|
||||||
|
cd goodgo-platform-ai
|
||||||
|
|
||||||
|
# Add your SSH key to GitHub (for faster clone/push)
|
||||||
|
# https://docs.github.com/en/authentication/connecting-to-github-with-ssh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install all packages using pnpm workspaces
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# This installs:
|
||||||
|
# - Root dependencies
|
||||||
|
# - apps/api dependencies
|
||||||
|
# - apps/web dependencies
|
||||||
|
# - libs/ai-services dependencies
|
||||||
|
# - libs/mcp-servers dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected time:** ~2-5 minutes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Setup PostgreSQL (Database)
|
||||||
|
|
||||||
|
### Option A: Docker Compose (Recommended for Development)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start PostgreSQL + Redis in Docker
|
||||||
|
docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
|
||||||
|
# Verify containers are running
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# Output should show:
|
||||||
|
# NAME STATUS
|
||||||
|
# goodgo-postgres Up 2 seconds
|
||||||
|
# goodgo-redis Up 2 seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option B: Local PostgreSQL Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create database
|
||||||
|
createdb goodgo_dev
|
||||||
|
|
||||||
|
# Enable PostGIS extension
|
||||||
|
psql goodgo_dev -c "CREATE EXTENSION IF NOT EXISTS postgis;"
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
psql goodgo_dev -c "SELECT PostGIS_version();"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Setup Environment Variables
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy .env.example to .env
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Edit .env with your local values
|
||||||
|
# Minimal required variables:
|
||||||
|
cat > .env << 'EOF'
|
||||||
|
NODE_ENV=development
|
||||||
|
DATABASE_URL="postgresql://postgres:password@localhost:5432/goodgo_dev"
|
||||||
|
REDIS_URL="redis://localhost:6379"
|
||||||
|
JWT_SECRET="dev-secret-change-in-production"
|
||||||
|
JWT_REFRESH_SECRET="dev-refresh-secret-change-in-production"
|
||||||
|
MAPBOX_TOKEN="your-mapbox-token-here" # Get from https://account.mapbox.com/
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# For Firebase Cloud Messaging (optional, for push notifications):
|
||||||
|
# FIREBASE_PROJECT_ID="your-project-id"
|
||||||
|
# FIREBASE_PRIVATE_KEY="..."
|
||||||
|
# FIREBASE_CLIENT_EMAIL="..."
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Initialize Database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate Prisma client
|
||||||
|
pnpm db:generate
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
pnpm db:migrate:dev
|
||||||
|
|
||||||
|
# Seed sample data (users, listings, districts)
|
||||||
|
pnpm db:seed
|
||||||
|
|
||||||
|
# Verify seeding
|
||||||
|
pnpm db:studio # Opens Prisma Studio GUI at http://localhost:5555
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Start Development Servers
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start all services (API + Web + AI services) in watch mode
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# Output shows:
|
||||||
|
# - API running at http://localhost:3001
|
||||||
|
# - Web running at http://localhost:3000
|
||||||
|
# - Logs from both services
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected time:** ~30 seconds for startup
|
||||||
|
|
||||||
|
### Alternative: Start Individual Services
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 1: API only
|
||||||
|
pnpm --filter api dev
|
||||||
|
|
||||||
|
# Terminal 2: Web only
|
||||||
|
pnpm --filter web dev
|
||||||
|
|
||||||
|
# Terminal 3: AI services (Python) - optional
|
||||||
|
pnpm --filter ai-services dev
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Verify Setup
|
||||||
|
|
||||||
|
Open your browser and test:
|
||||||
|
|
||||||
|
### API Health Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal or Postman
|
||||||
|
curl http://localhost:3001/health
|
||||||
|
|
||||||
|
# Should return:
|
||||||
|
# {
|
||||||
|
# "status": "ok",
|
||||||
|
# "timestamp": "2026-04-22T10:30:00Z"
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Web App
|
||||||
|
|
||||||
|
Open http://localhost:3000 in your browser
|
||||||
|
|
||||||
|
- You should see the GoodGo home page
|
||||||
|
- Try registering an account (test phone: `0987654321`, any password)
|
||||||
|
|
||||||
|
### API Swagger Docs
|
||||||
|
|
||||||
|
Open http://localhost:3001/api/docs
|
||||||
|
|
||||||
|
- Interactive API documentation
|
||||||
|
- Try endpoints: GET `/api/v1/listings`, POST `/api/v1/auth/login`, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `pnpm install` | Install all dependencies |
|
||||||
|
| `pnpm dev` | Start all services in watch mode |
|
||||||
|
| `pnpm lint` | Run ESLint (check code style) |
|
||||||
|
| `pnpm lint --fix` | Auto-fix lint issues |
|
||||||
|
| `pnpm typecheck` | TypeScript type checking |
|
||||||
|
| `pnpm test` | Run unit tests |
|
||||||
|
| `pnpm test:e2e` | Run E2E tests with Playwright |
|
||||||
|
| `pnpm build` | Production build |
|
||||||
|
| `pnpm db:generate` | Generate Prisma client |
|
||||||
|
| `pnpm db:migrate:dev` | Run pending migrations |
|
||||||
|
| `pnpm db:seed` | Seed sample data |
|
||||||
|
| `pnpm db:studio` | Open Prisma Studio GUI |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
goodgo-platform-ai/
|
||||||
|
├── apps/
|
||||||
|
│ ├── api/ # NestJS backend
|
||||||
|
│ │ ├── src/
|
||||||
|
│ │ │ ├── main.ts # Entry point
|
||||||
|
│ │ │ └── modules/ # Domain modules
|
||||||
|
│ │ │ ├── auth/
|
||||||
|
│ │ │ ├── listings/
|
||||||
|
│ │ │ ├── payments/
|
||||||
|
│ │ │ └── ...
|
||||||
|
│ │ └── package.json
|
||||||
|
│ └── web/ # Next.js frontend
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── app/ # App Router pages
|
||||||
|
│ │ ├── components/
|
||||||
|
│ │ ├── lib/
|
||||||
|
│ │ └── hooks/
|
||||||
|
│ └── package.json
|
||||||
|
├── libs/
|
||||||
|
│ ├── ai-services/ # Python FastAPI (AVM, moderation)
|
||||||
|
│ └── mcp-servers/ # MCP server library
|
||||||
|
├── prisma/
|
||||||
|
│ ├── schema.prisma # Database schema
|
||||||
|
│ └── migrations/ # SQL migrations
|
||||||
|
├── docs/ # Documentation
|
||||||
|
├── .github/workflows/ # CI/CD
|
||||||
|
└── package.json (root) # pnpm workspaces config
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### 1. Create a Feature Branch
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Always create a feature branch from main/master
|
||||||
|
git checkout -b feature/awesome-feature
|
||||||
|
|
||||||
|
# Branch naming convention:
|
||||||
|
# - feature/new-feature-name
|
||||||
|
# - fix/bug-description
|
||||||
|
# - refactor/code-cleanup
|
||||||
|
# - docs/documentation-update
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Make Changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies for new package
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Run type checking + linting
|
||||||
|
pnpm typecheck
|
||||||
|
pnpm lint
|
||||||
|
|
||||||
|
# Run tests for your changes
|
||||||
|
pnpm test -- path/to/module
|
||||||
|
|
||||||
|
# Fix auto-fixable issues
|
||||||
|
pnpm lint --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Commit & Push
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Commit following conventional commits format
|
||||||
|
git add .
|
||||||
|
git commit -m "feat(module): short description"
|
||||||
|
|
||||||
|
# Commit message format:
|
||||||
|
# feat(scope): description
|
||||||
|
# fix(scope): description
|
||||||
|
# docs(scope): description
|
||||||
|
# refactor(scope): description
|
||||||
|
# test(scope): description
|
||||||
|
# chore(scope): description
|
||||||
|
|
||||||
|
# Push branch
|
||||||
|
git push origin feature/awesome-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Create Pull Request
|
||||||
|
|
||||||
|
- Go to GitHub: https://github.com/hongochai10/goodgo-bds-platform-ai/pulls
|
||||||
|
- Click **New Pull Request**
|
||||||
|
- Select your branch
|
||||||
|
- Add description (what changed, why, testing notes)
|
||||||
|
- Ensure all checks pass ✅ (lint, typecheck, test, build)
|
||||||
|
- Request review from team lead
|
||||||
|
- Merge after approval
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debugging Tips
|
||||||
|
|
||||||
|
### API Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable debug logging
|
||||||
|
DEBUG=*:* pnpm --filter api dev
|
||||||
|
|
||||||
|
# Or in VS Code: use `.vscode/launch.json` for breakpoints
|
||||||
|
# F5 to start debugger
|
||||||
|
```
|
||||||
|
|
||||||
|
### Web Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Chrome DevTools: F12
|
||||||
|
# React DevTools extension: https://chrome.google.com/webstore/
|
||||||
|
|
||||||
|
# Debug Next.js:
|
||||||
|
# .next/server/pages shows compiled route handlers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Connect to database directly
|
||||||
|
psql $DATABASE_URL
|
||||||
|
|
||||||
|
# Common queries:
|
||||||
|
SELECT * FROM "User" LIMIT 5;
|
||||||
|
SELECT COUNT(*) FROM "Listing";
|
||||||
|
SELECT * FROM "Listing" WHERE "status" = 'ACTIVE' LIMIT 5;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis Debugging
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Connect to Redis CLI
|
||||||
|
redis-cli
|
||||||
|
|
||||||
|
# Check keys
|
||||||
|
KEYS *
|
||||||
|
GET session:user:123
|
||||||
|
INCR counter
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## VS Code Setup (Optional)
|
||||||
|
|
||||||
|
### Recommended Extensions
|
||||||
|
|
||||||
|
```json
|
||||||
|
// .vscode/extensions.json
|
||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"esbenp.prettier-vscode", // Code formatter
|
||||||
|
"dbaeumer.vscode-eslint", // ESLint linting
|
||||||
|
"ms-vscode.makefile-tools", // Makefile support
|
||||||
|
"ms-vscode.extension-pack-fe", // Frontend bundle
|
||||||
|
"ms-vscode-remote.remote-containers", // Docker support
|
||||||
|
"bradlc.vscode-tailwindcss", // Tailwind CSS support
|
||||||
|
"vivaxy.vscode-conventional-commits", // Commit helper
|
||||||
|
"Prisma.prisma" // Prisma schema support
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Launch Configuration (Debugging)
|
||||||
|
|
||||||
|
Create `.vscode/launch.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "NestJS Debug",
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/node_modules/@nestjs/cli/bin/nest.js",
|
||||||
|
"args": ["start", "--debug", "--watch"],
|
||||||
|
"cwd": "${workspaceFolder}/apps/api",
|
||||||
|
"skipFiles": ["<node_internals>/**"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Problem: `pnpm install` hangs
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
rm -rf node_modules .pnpm-store pnpm-lock.yaml
|
||||||
|
pnpm install --no-frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problem: PostgreSQL connection refused
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Check if PostgreSQL is running
|
||||||
|
docker-compose ps
|
||||||
|
|
||||||
|
# If not running, start it
|
||||||
|
docker-compose -f docker-compose.dev.yml up -d
|
||||||
|
|
||||||
|
# Or check local PostgreSQL
|
||||||
|
pg_isready -h localhost -p 5432
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problem: `pnpm dev` fails with "EADDRINUSE"
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Port 3000 or 3001 is already in use
|
||||||
|
# Kill existing process
|
||||||
|
lsof -i :3000 # Find process ID
|
||||||
|
kill -9 <PID>
|
||||||
|
|
||||||
|
# Or use different port
|
||||||
|
PORT=3002 pnpm --filter web dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problem: TypeScript errors in IDE but tests pass
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```bash
|
||||||
|
# Regenerate Prisma types
|
||||||
|
pnpm db:generate
|
||||||
|
|
||||||
|
# Restart VS Code
|
||||||
|
Cmd+Shift+P → "Developer: Reload Window"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
- **Slack:** `#dev` channel (if you have access)
|
||||||
|
- **GitHub Issues:** https://github.com/hongochai10/goodgo-bds-platform-ai/issues
|
||||||
|
- **Documentation:** `/docs` folder
|
||||||
|
- **Architecture:** `/docs/architecture.md`
|
||||||
|
- **API Reference:** `/docs/api-endpoints.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. ✅ **Setup complete?** Start with a small bug fix or feature
|
||||||
|
2. 📖 **Read Architecture:** `/docs/architecture.md` (understand module structure)
|
||||||
|
3. 🏗️ **Understand CQRS:** `/docs/QUICK_REFERENCE.md` (command/query handlers)
|
||||||
|
4. 🧪 **Write Tests:** Follow patterns in existing `.spec.ts` files
|
||||||
|
5. 📝 **Update Docs:** When adding features, update relevant docs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Welcome to GoodGo! Happy coding! 🚀**
|
||||||
Reference in New Issue
Block a user