Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
10 KiB
10 KiB
Module Yêu Cầu - Tài Liệu Tham Khảo Nhanh
📊 Tổng Quan Module
Đường dẫn: apps/api/src/modules/inquiries/
Kiến trúc: CQRS + DDD
Tổng số tệp: 25
Tổng số dòng mã: 1,212
Độ phủ kiểm thử: 6 bộ kiểm thử, 24 bài kiểm thử
📁 CÁC TỆP THEO TẦNG
TRÌNH BÀY (5 tệp)
presentation/
├── controllers/inquiries.controller.ts [4 endpoints]
├── dto/create-inquiry.dto.ts [3 properties]
├── dto/list-inquiries.dto.ts [2 properties]
└── __tests__/inquiries.controller.spec.ts [6 tests]
ỨNG DỤNG (8 tệp)
application/
├── commands/create-inquiry/
│ ├── create-inquiry.command.ts
│ └── create-inquiry.handler.ts
├── commands/mark-inquiry-read/
│ ├── mark-inquiry-read.command.ts
│ └── mark-inquiry-read.handler.ts
├── queries/get-inquiries-by-agent/
│ ├── get-inquiries-by-agent.query.ts
│ └── get-inquiries-by-agent.handler.ts
├── queries/get-inquiries-by-listing/
│ ├── get-inquiries-by-listing.query.ts
│ └── get-inquiries-by-listing.handler.ts
└── __tests__/ [4 test files, 13 tests]
MIỀN (6 tệp)
domain/
├── entities/inquiry.entity.ts [Aggregate Root]
├── events/
│ ├── inquiry-created.event.ts
│ └── inquiry-read.event.ts
├── repositories/
│ ├── inquiry.repository.ts [Interface + Symbol]
│ └── inquiry-read.dto.ts [Read DTO]
└── __tests__/inquiry-domain.spec.ts [5 tests]
HẠ TẦNG (1 tệp)
infrastructure/
└── repositories/
└── prisma-inquiry.repository.ts [6 methods]
MODULE (2 tệp)
inquiries.module.ts [NestJS Module]
index.ts [Barrel export]
🔄 LUỒNG YÊU CẦU
TẠO YÊU CẦU
POST /inquiries { listingId, message, phone? }
↓
InquiriesController.createInquiry()
↓
CommandBus.execute(CreateInquiryCommand)
↓
CreateInquiryHandler
1. Validate listing exists (Prisma)
2. Create InquiryEntity
3. Save to PrismaInquiryRepository
4. Publish InquiryCreatedEvent
↓
Response: { id, listingId, createdAt }
ĐÁNH DẤU ĐÃ ĐỌC
PATCH /inquiries/:id/read (AGENT only)
↓
InquiriesController.markAsRead()
↓
CommandBus.execute(MarkInquiryReadCommand)
↓
MarkInquiryReadHandler
1. Load inquiry entity
2. Verify agent owns listing
3. Call inquiry.markAsRead()
4. Update in repository
5. Publish InquiryReadEvent
↓
Response: { success: true }
DANH SÁCH THEO TIN ĐĂNG
GET /inquiries/listing/:listingId?page=1&limit=20
↓
InquiriesController.getByListing()
↓
QueryBus.execute(GetInquiriesByListingQuery)
↓
GetInquiriesByListingHandler
↓
PrismaInquiryRepository.findByListing()
↓
Response: PaginatedResult<InquiryReadDto>
DANH SÁCH THEO MÔI GIỚI
GET /inquiries/agent/me?page=1&limit=20 (AGENT only)
↓
InquiriesController.getMyInquiries()
↓
QueryBus.execute(GetInquiriesByAgentQuery)
↓
GetInquiriesByAgentHandler
1. Resolve agent from userId
2. Delegate to repository
↓
PrismaInquiryRepository.findByAgent()
↓
Response: PaginatedResult<InquiryReadDto>
🔑 CÁC LỚP CHÍNH
| Lớp | Vị trí | Mục đích |
|---|---|---|
| InquiryEntity | domain/entities/ | Gốc tổng hợp với logic nghiệp vụ |
| CreateInquiryHandler | application/commands/create-inquiry/ | Thực thi lệnh tạo mới |
| MarkInquiryReadHandler | application/commands/mark-inquiry-read/ | Thực thi lệnh đánh dấu đã đọc |
| GetInquiriesByListingHandler | application/queries/get-inquiries-by-listing/ | Phân giải yêu cầu theo tin đăng |
| GetInquiriesByAgentHandler | application/queries/get-inquiries-by-agent/ | Phân giải yêu cầu theo môi giới |
| PrismaInquiryRepository | infrastructure/repositories/ | Triển khai lưu trữ dữ liệu |
| InquiriesController | presentation/controllers/ | Các endpoint HTTP |
📝 CÁC GIAO DIỆN CHÍNH
// Domain interface (repository contract)
interface IInquiryRepository {
findById(id: string): Promise<InquiryEntity | null>
save(inquiry: InquiryEntity): Promise<void>
markAsRead(id: string): Promise<void>
findByListing(listingId, page, limit): Promise<PaginatedResult<InquiryReadDto>>
findByAgent(agentId, page, limit): Promise<PaginatedResult<InquiryReadDto>>
countUnreadByAgent(agentId): Promise<number>
}
// Read DTO (queries only)
interface InquiryReadDto {
id: string
listingId: string
listingTitle: string
userId: string
userName: string
userPhone: string
message: string
phone: string | null
isRead: boolean
createdAt: string
}
// Pagination result
interface PaginatedResult<T> {
data: T[]
total: number
page: number
limit: number
totalPages: number
}
🧪 CÁC TỆP KIỂM THỬ TỔNG QUAN
| Tệp kiểm thử | Số bài | Nội dung |
|---|---|---|
domain/__tests__/inquiry-domain.spec.ts |
5 | Tạo thực thể, sự kiện |
application/__tests__/create-inquiry.handler.spec.ts |
4 | Thành công của handler, kiểm tra đầu vào |
application/__tests__/mark-inquiry-read.handler.spec.ts |
5 | Thành công của handler, kiểm tra xác thực |
application/__tests__/get-inquiries-by-listing.handler.spec.ts |
2 | Kết quả truy vấn, trạng thái rỗng |
application/__tests__/get-inquiries-by-agent.handler.spec.ts |
2 | Kết quả truy vấn, tra cứu môi giới |
presentation/__tests__/inquiries.controller.spec.ts |
6 | Tất cả endpoint, giá trị mặc định |
| TỔNG | 24 | Độ phủ toàn diện |
🔐 Ma Trận Phân Quyền
| Endpoint | Xác thực | Vai trò | Truy vấn |
|---|---|---|---|
POST /inquiries |
JWT | Bất kỳ | - |
GET /listing/:id |
JWT | Bất kỳ | page, limit |
GET /agent/me |
JWT | AGENT | page, limit |
PATCH /:id/read |
JWT | AGENT | - |
Kiểm tra quyền:
- MarkInquiryReadHandler: Xác minh người dùng là môi giới, môi giới sở hữu tin đăng
🎯 NGUYÊN TẮC DDD
Đóng Gói Thực Thể Miền
// Factory method (controlled creation)
static createNew(id, listingId, userId, message, phone): InquiryEntity
→ Creates entity with isRead=false
→ Emits InquiryCreatedEvent
// Domain methods (state transitions)
markAsRead(): void
→ Sets isRead=true
→ Emits InquiryReadEvent
Mẫu Repository
- Giao diện trong Miền →
IInquiryRepository - Triển khai trong Hạ tầng →
PrismaInquiryRepository - Tiêm phụ thuộc → ký hiệu
INQUIRY_REPOSITORY
Mô Hình Đọc/Ghi Tách Biệt
- Mô hình Ghi:
InquiryEntity(tổng hợp) - Mô hình Đọc:
InquiryReadDto(DTO truy vấn)
🔄 Sự Kiện Miền
| Sự kiện | Thời điểm | Dữ liệu |
|---|---|---|
| InquiryCreatedEvent | Yêu cầu được tạo | aggregateId, listingId, userId |
| InquiryReadEvent | Được đánh dấu đã đọc | aggregateId, listingId, userId |
💾 Thao Tác Cơ Sở Dữ Liệu
Các Prisma Model được sử dụng:
inquiry- Thực thể chínhlisting- Cho khóa ngoại & tra cứu môi giớiproperty- Cho tiêu đề tin đănguser- Cho tên & số điện thoại người mua
Các Truy Vấn Chính:
inquiry.create()- Tạo yêu cầu mớiinquiry.update()- Đánh dấu đã đọcinquiry.findMany()- Phân tranginquiry.count()- Đếm tổng số
🚀 Điểm Vào
// Module export
export { InquiriesModule }
// Exported interfaces
export { INQUIRY_REPOSITORY, type IInquiryRepository }
export { InquiryEntity }
// Usage in other modules
import { InquiriesModule } from '@modules/inquiries'
🎓 Tóm Tắt Kiến Trúc
KIẾN TRÚC SẠCH với CQRS + DDD
Tầng Trình Bày (Controllers + DTOs)
↓
Tầng Ứng Dụng (CQRS Handlers)
↓
Tầng Miền (Entities + Events + Interfaces)
↓
Tầng Hạ Tầng (Prisma Repository)
↓
Cơ Sở Dữ Liệu
Đặc Điểm Chính:
✅ Đảo Ngược Phụ Thuộc - Miền định nghĩa các hợp đồng
✅ Phân Tách Mối Quan Tâm - Mỗi tầng có trách nhiệm rõ ràng
✅ Khả Năng Kiểm Thử - Triển khai giả lập tại mỗi tầng
✅ Hướng Sự Kiện - Sự kiện miền cho kiểm toán & tích hợp
✅ CQRS - Tách biệt lệnh & truy vấn để mở rộng quy mô
✅ An Toàn Kiểu - TypeScript đầy đủ với giao diện nghiêm ngặt
📌 Các Mẫu Thường Dùng
Mẫu Command:
// Send command
commandBus.execute(new CreateInquiryCommand(...))
// Handler processes
@CommandHandler(CreateInquiryCommand)
class CreateInquiryHandler { ... }
Mẫu Query:
// Send query
queryBus.execute(new GetInquiriesByListingQuery(...))
// Handler processes
@QueryHandler(GetInquiriesByListingQuery)
class GetInquiriesByListingHandler { ... }
Mẫu Tiêm Phụ Thuộc:
@Injectable()
export class Handler {
constructor(
@Inject(INQUIRY_REPOSITORY) private repo: IInquiryRepository,
private prisma: PrismaService,
) {}
}
🔍 Nơi Tìm Kiếm...
| Nhu cầu | Tệp |
|---|---|
| Thêm endpoint mới | presentation/controllers/inquiries.controller.ts |
| Thêm command | application/commands/[name]/[name].command.ts + [name].handler.ts |
| Thêm query | application/queries/[name]/[name].query.ts + [name].handler.ts |
| Logic nghiệp vụ | domain/entities/inquiry.entity.ts |
| Sự kiện miền mới | domain/events/[name].event.ts |
| Truy vấn cơ sở dữ liệu | infrastructure/repositories/prisma-inquiry.repository.ts |
| Kiểm tra đầu vào | presentation/dto/*.ts |
| Viết kiểm thử | [layer]/__tests__/* |
Cập Nhật Lần Cuối: 11 tháng 4, 2026