Files
goodgo-platform/CONTRIBUTING.md
Ho Ngoc Hai b4bb05479e feat(web): add lib/phone.ts with formatPhone/normalizePhone/zaloHref helpers
- 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>
2026-04-24 12:01:14 +07:00

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).

  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 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 (linttypechecktestbuild) 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

# 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

maindevelop 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 whyhow:

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

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, 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

# 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?

Happy coding! 🚀