- 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>
410 lines
12 KiB
Markdown
410 lines
12 KiB
Markdown
# 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](/GOO/issues/GOO-91) (chỉ thị từ CEO qua [GOO-88](/GOO/issues/GOO-88)).
|
|
|
|
1. **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.
|
|
2. **Pull/rebase trước khi push** — luôn chạy `git pull --rebase origin <branch>` trước `git push` để giảm merge conflict.
|
|
3. **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.
|
|
4. **Conventional Commits** — bắt buộc (`feat:`, `fix:`, `chore:`, `refactor:`, `docs:`, `test:`, `style:`, `perf:`). Xem [Quy Ước Commit](#quy-ước-commit) bên dưới.
|
|
5. **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.
|
|
6. **PR phải pass CI** (`lint` → `typecheck` → `test` → `build`) trước khi merge. PR đỏ CI không được merge dù đã approve.
|
|
7. **Squash-merge khi merge PR** — giữ history trên `main` sạch, mỗi PR = một commit logic.
|
|
8. **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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```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
|
|
|
|
### 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ừ:
|
|
|
|
```typescript
|
|
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.
|
|
|
|
```typescript
|
|
// 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`:
|
|
|
|
```typescript
|
|
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
|
|
|
|
```typescript
|
|
// 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`, `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! 🚀**
|
|
|