- Create apps/web/lib/phone.ts with VN_PHONE_REGEX, normalizePhone, formatPhone, and zaloHref helpers - Deduplicate phone regex: auth.ts and inquiry.ts now import VN_PHONE_REGEX from @/lib/phone instead of defining their own local patterns - Replace raw .replace(/^0/, '84') in inquiry-detail-dialog.tsx and lead-detail-dialog.tsx with zaloHref(); use formatPhone() for display Resolves GOO-209 Co-Authored-By: Paperclip <noreply@paperclip.ing>
12 KiB
Hướng Dẫn Đóng Góp
Kỷ Luật Commit & Push (Bắt Buộc)
Để tránh conflict khi nhiều agent/engineer làm việc song song, toàn bộ team PHẢI tuân thủ các quy định sau. Nguồn: GOO-91 (chỉ thị từ CEO qua GOO-88).
- Commit ngay khi hoàn thành task — mỗi task = một commit (hoặc một chuỗi commit nhỏ liên quan). Không gom nhiều task không liên quan vào một commit lớn.
- Pull/rebase trước khi push — luôn chạy
git pull --rebase origin <branch>trướcgit pushđể giảm merge conflict. - Push ngay sau commit — không giữ commit local quá 1 ngày làm việc. Commit không push = rủi ro mất việc + conflict tăng.
- Conventional Commits — bắt buộc (
feat:,fix:,chore:,refactor:,docs:,test:,style:,perf:). Xem Quy Ước Commit bên dưới. - KHÔNG push trực tiếp lên
main/master— luôn dùng feature branch + Pull Request. Branch chính được bảo vệ bằng GitHub branch protection rules. - PR phải pass CI (
lint→typecheck→test→build) trước khi merge. PR đỏ CI không được merge dù đã approve. - Squash-merge khi merge PR — giữ history trên
mainsạch, mỗi PR = một commit logic. - Xóa feature branch sau khi merge — tránh branch sprawl. GitHub có auto-delete branch sau merge; bật nó trong repo settings.
Flow nhanh cho mỗi task
# 1. Tạo/chuyển sang feature branch (KHÔNG commit trực tiếp vào main)
git checkout -b feature/goo-xx-short-description
# 2. Làm việc, khi hoàn thành task:
git add <files>
git commit -m "feat(scope): mô tả ngắn"
# 3. Đồng bộ & push
git pull --rebase origin main # hoặc develop
git push -u origin feature/goo-xx-short-description
# 4. Mở PR, chờ CI xanh + review, squash-merge, xóa branch
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
# 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
# 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:
<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:
# 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
## 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
Tổng Quan
Toàn bộ xử lý lỗi ở tầng ứng dụng sử dụng domain exceptions từ @modules/shared/domain/domain-exception. Không bao giờ import các lớp exception từ @nestjs/common trong handlers — hãy dùng domain exceptions của dự án.
GlobalExceptionFilter bắt tất cả các exception và chuẩn hóa chúng thành một JSON response nhất quán với error codes có cấu trúc.
Domain Exceptions
| Exception | HTTP Status | Khi nào dùng |
|---|---|---|
NotFoundException(entity, id?) |
404 | Không tìm thấy entity trong database |
ValidationException(message, details?) |
400 | Dữ liệu đầu vào không hợp lệ, vi phạm business rule, lỗi tạo value object |
ConflictException(message) |
409 | Tài nguyên bị trùng lặp, vi phạm idempotency |
UnauthorizedException(message?) |
401 | Thông tin xác thực hoặc token không hợp lệ/đã hết hạn |
ForbiddenException(message?) |
403 | Đã xác thực nhưng không được phép thực hiện hành động |
Import từ:
import { NotFoundException, ValidationException } from '@modules/shared/domain/domain-exception';
Các Mẫu Theo Từng Tầng
Command/Query Handlers
Handlers ném domain exceptions trực tiếp. Không cần bọc try-catch — GlobalExceptionFilter xử lý các exception chưa được bắt.
// Good: domain exception with entity context
const user = await this.userRepo.findById(id);
if (!user) throw new NotFoundException('User', id);
// Good: Result pattern from value objects
const phoneResult = Phone.create(command.phone);
if (phoneResult.isErr) {
throw new ValidationException(phoneResult.unwrapErr());
}
const phone = phoneResult.unwrap();
// Good: business rule validation
if (subscription.status === 'CANCELLED') {
throw new ValidationException('Subscription da bi huy');
}
Controllers
Controllers là các tầng ủy quyền mỏng — chúng dispatch tới command/query bus và trả về kết quả. Không cần xử lý lỗi ở tầng controller.
Domain Services / Value Objects
Sử dụng mẫu Result<T, E> từ @modules/shared/domain/result:
static create(value: string): Result<Phone, string> {
if (!isValid(value)) return Result.err('So dien thoai khong hop le');
return Result.ok(new Phone({ value }));
}
Handlers sử dụng Result bằng cách kiểm tra .isErr và ném ValidationException.
Những Điều KHÔNG Nên Làm
// Bad: NestJS built-in exceptions (missing errorCode in response)
import { NotFoundException } from '@nestjs/common';
// Bad: object-style exceptions (inconsistent with domain exception API)
throw new NotFoundException({ code: ErrorCode.NOT_FOUND, message: '...' });
// Bad: generic try-catch swallowing infrastructure errors as domain errors
try {
await this.prisma.plan.findFirst({ where: { tier } });
} catch {
throw new NotFoundException('Plan not found'); // hides DB connection errors
}
// Bad: returning Result from handlers to controllers
// Handlers should unwrap Result and throw on error
Kiểu Trả Về 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.
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,TODOwithout 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
# 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.mdfor system design - 🏗️ Read
/docs/QUICK_REFERENCE.mdfor patterns - 💬 Ask on Slack
#devchannel - 🐛 File an issue: https://github.com/hongochai10/goodgo-bds-platform-ai/issues
Happy coding! 🚀