chore: update project documentation, audit reports, and initialize IDE configuration files
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

This commit is contained in:
Ho Ngoc Hai
2026-04-19 03:12:54 +07:00
parent 3be106074d
commit 11f2bf26e6
101 changed files with 21312 additions and 20672 deletions

View File

@@ -1,24 +1,24 @@
# Inquiries Module - Complete Exploration
# Module Inquiries - Khám Phá Toàn Diện
**GoodGo Platform Backend API**
**Module Location:** `apps/api/src/modules/inquiries/`
**Total Lines of Code:** 1,212 lines
**Date Explored:** April 11, 2026
**Vị Trí Module:** `apps/api/src/modules/inquiries/`
**Tổng Số Dòng Code:** 1.212 dòng
**Ngày Khám Phá:** 11 tháng 4 năm 2026
---
## 📋 TABLE OF CONTENTS
## 📋 MỤC LỤC
1. [Directory Structure](#directory-structure)
2. [Complete File Listing](#complete-file-listing)
3. [Module Architecture](#module-architecture)
4. [Key Classes & Handlers](#key-classes--handlers)
5. [DDD Layer Analysis](#ddd-layer-analysis)
6. [Test Files Summary](#test-files-summary)
1. [Cấu Trúc Thư Mục](#directory-structure)
2. [Danh Sách File Đầy Đủ](#complete-file-listing)
3. [Kiến Trúc Module](#module-architecture)
4. [Các Lớp & Handler Chính](#key-classes--handlers)
5. [Phân Tích Tầng DDD](#ddd-layer-analysis)
6. [Tóm Tắt File Test](#test-files-summary)
---
## 📁 DIRECTORY STRUCTURE
## 📁 CẤU TRÚC THƯ MỤC
```
apps/api/src/modules/inquiries/
@@ -70,120 +70,120 @@ apps/api/src/modules/inquiries/
---
## 📄 COMPLETE FILE LISTING
## 📄 DANH SÁCH FILE ĐẦY ĐỦ
### **APPLICATION LAYER** (8 files)
### **TẦNG APPLICATION** (8 file)
#### Commands (4 files)
#### Commands (4 file)
| File Path | Type | Description |
| Đường Dẫn File | Loại | Mô Tả |
|-----------|------|-------------|
| `application/commands/create-inquiry/create-inquiry.command.ts` | Command | DTO for creating inquiry - contains userId, listingId, message, phone |
| `application/commands/create-inquiry/create-inquiry.handler.ts` | Command Handler | Executes CreateInquiryCommand; validates listing exists, creates entity, publishes event |
| `application/commands/mark-inquiry-read/mark-inquiry-read.command.ts` | Command | DTO for marking inquiry as read - contains inquiryId, agentUserId |
| `application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts` | Command Handler | Executes MarkInquiryReadCommand; validates permissions, marks inquiry read, publishes event |
| `application/commands/create-inquiry/create-inquiry.command.ts` | Command | DTO để tạo inquiry - chứa userId, listingId, message, phone |
| `application/commands/create-inquiry/create-inquiry.handler.ts` | Command Handler | Thực thi CreateInquiryCommand; xác thực listing tồn tại, tạo entity, phát sự kiện |
| `application/commands/mark-inquiry-read/mark-inquiry-read.command.ts` | Command | DTO để đánh dấu inquiry đã đọc - chứa inquiryId, agentUserId |
| `application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts` | Command Handler | Thực thi MarkInquiryReadCommand; xác thực quyền hạn, đánh dấu inquiry đã đọc, phát sự kiện |
#### Queries (4 files)
#### Queries (4 file)
| File Path | Type | Description |
| Đường Dẫn File | Loại | Mô Tả |
|-----------|------|-------------|
| `application/queries/get-inquiries-by-agent/get-inquiries-by-agent.query.ts` | Query | DTO for listing agent's inquiries - contains agentUserId, page, limit |
| `application/queries/get-inquiries-by-agent/get-inquiries-by-agent.handler.ts` | Query Handler | Resolves agent by userId, delegates to repository findByAgent |
| `application/queries/get-inquiries-by-listing/get-inquiries-by-listing.query.ts` | Query | DTO for listing inquiries by listing - contains listingId, page, limit |
| `application/queries/get-inquiries-by-listing/get-inquiries-by-listing.handler.ts` | Query Handler | Delegates directly to repository findByListing |
| `application/queries/get-inquiries-by-agent/get-inquiries-by-agent.query.ts` | Query | DTO để liệt kê inquiry của agent - chứa agentUserId, page, limit |
| `application/queries/get-inquiries-by-agent/get-inquiries-by-agent.handler.ts` | Query Handler | Tra cứu agent theo userId, ủy quyền cho repository findByAgent |
| `application/queries/get-inquiries-by-listing/get-inquiries-by-listing.query.ts` | Query | DTO để liệt kê inquiry theo listing - chứa listingId, page, limit |
| `application/queries/get-inquiries-by-listing/get-inquiries-by-listing.handler.ts` | Query Handler | Ủy quyền trực tiếp cho repository findByListing |
#### Application Tests (4 files)
#### Test Tầng Application (4 file)
| File Path | Type | Test Count | Coverage |
| Đường Dẫn File | Loại | Số Test | Phạm Vi |
|-----------|------|-----------|----------|
| `application/__tests__/create-inquiry.handler.spec.ts` | Jest | 4 tests | Happy path, missing listing, domain event publishing |
| `application/__tests__/mark-inquiry-read.handler.spec.ts` | Jest | 5 tests | Happy path, missing inquiry, missing listing, forbidden access, agent not found |
| `application/__tests__/get-inquiries-by-listing.handler.spec.ts` | Jest | 2 tests | Paginated results, empty results |
| `application/__tests__/get-inquiries-by-agent.handler.spec.ts` | Jest | 2 tests | Paginated results, agent not found |
| `application/__tests__/create-inquiry.handler.spec.ts` | Jest | 4 test | Happy path, listing không tồn tại, phát domain event |
| `application/__tests__/mark-inquiry-read.handler.spec.ts` | Jest | 5 test | Happy path, inquiry không tồn tại, listing không tồn tại, truy cập bị từ chối, agent không tồn tại |
| `application/__tests__/get-inquiries-by-listing.handler.spec.ts` | Jest | 2 test | Kết quả phân trang, kết quả rỗng |
| `application/__tests__/get-inquiries-by-agent.handler.spec.ts` | Jest | 2 test | Kết quả phân trang, agent không tồn tại |
---
### **DOMAIN LAYER** (6 files)
### **TẦNG DOMAIN** (6 file)
#### Entities (1 file)
| File Path | Type | Description |
| Đường Dẫn File | Loại | Mô Tả |
|-----------|------|-------------|
| `domain/entities/inquiry.entity.ts` | Aggregate Root | Core domain entity with id, listingId, userId, message, phone, isRead; methods: createNew(), markAsRead() |
| `domain/entities/inquiry.entity.ts` | Aggregate Root | Entity domain cốt lõi với id, listingId, userId, message, phone, isRead; các phương thức: createNew(), markAsRead() |
#### Events (2 files)
#### Events (2 file)
| File Path | Type | Description |
| Đường Dẫn File | Loại | Mô Tả |
|-----------|------|-------------|
| `domain/events/inquiry-created.event.ts` | Domain Event | Published when inquiry is created - contains aggregateId, listingId, userId |
| `domain/events/inquiry-read.event.ts` | Domain Event | Published when inquiry is marked read - contains aggregateId, listingId, userId |
| `domain/events/inquiry-created.event.ts` | Domain Event | Phát khi inquiry được tạo - chứa aggregateId, listingId, userId |
| `domain/events/inquiry-read.event.ts` | Domain Event | Phát khi inquiry được đánh dấu đã đọc - chứa aggregateId, listingId, userId |
#### Repositories (2 files)
#### Repositories (2 file)
| File Path | Type | Description |
| Đường Dẫn File | Loại | Mô Tả |
|-----------|------|-------------|
| `domain/repositories/inquiry.repository.ts` | Interface + Symbol | IInquiryRepository interface with 6 methods; INQUIRY_REPOSITORY symbol for DI; PaginatedResult interface |
| `domain/repositories/inquiry-read.dto.ts` | Interface | Read DTO for queries - includes listing title, user details, timestamps |
| `domain/repositories/inquiry.repository.ts` | Interface + Symbol | Interface IInquiryRepository với 6 phương thức; symbol INQUIRY_REPOSITORY cho DI; interface PaginatedResult |
| `domain/repositories/inquiry-read.dto.ts` | Interface | Read DTO cho queries - bao gồm tiêu đề listing, thông tin người dùng, timestamps |
#### Domain Tests (1 file)
#### Test Tầng Domain (1 file)
| File Path | Type | Test Count | Coverage |
| Đường Dẫn File | Loại | Số Test | Phạm Vi |
|-----------|------|-----------|----------|
| `domain/__tests__/inquiry-domain.spec.ts` | Jest | 5 tests | Entity creation, null phone, domain events, markAsRead |
| `domain/__tests__/inquiry-domain.spec.ts` | Jest | 5 test | Tạo entity, phone null, domain events, markAsRead |
---
### **INFRASTRUCTURE LAYER** (1 file)
### **TẦNG INFRASTRUCTURE** (1 file)
#### Repository Implementation
#### Triển Khai Repository
| File Path | Type | Description |
| Đường Dẫn File | Loại | Mô Tả |
|-----------|------|-------------|
| `infrastructure/repositories/prisma-inquiry.repository.ts` | Service | Implements IInquiryRepository using Prisma; 6 methods: findById, save, markAsRead, findByListing, findByAgent, countUnreadByAgent |
| `infrastructure/repositories/prisma-inquiry.repository.ts` | Service | Triển khai IInquiryRepository ng Prisma; 6 phương thức: findById, save, markAsRead, findByListing, findByAgent, countUnreadByAgent |
---
### **PRESENTATION LAYER** (5 files)
### **TẦNG PRESENTATION** (5 file)
#### Controller (1 file)
| File Path | Type | Routes | Auth |
| Đường Dẫn File | Loại | Routes | Auth |
|-----------|------|--------|------|
| `presentation/controllers/inquiries.controller.ts` | NestJS Controller | POST /, GET /listing/:id, GET /agent/me, PATCH /:id/read | JWT + RBAC |
**Endpoints:**
- `POST /inquiries` - Create inquiry (BUYER)
- `GET /inquiries/listing/:listingId` - Get inquiries by listing
- `GET /inquiries/agent/me` - Get inquiries for logged-in agent (AGENT only)
- `PATCH /inquiries/:id/read` - Mark inquiry as read (AGENT only)
**Các Endpoint:**
- `POST /inquiries` - Tạo inquiry (BUYER)
- `GET /inquiries/listing/:listingId` - Lấy inquiry theo listing
- `GET /inquiries/agent/me` - Lấy inquiry của agent đang đăng nhập (chỉ AGENT)
- `PATCH /inquiries/:id/read` - Đánh dấu inquiry đã đọc (chỉ AGENT)
#### Data Transfer Objects (2 files)
#### Data Transfer Objects (2 file)
| File Path | Type | Properties | Validation |
| Đường Dẫn File | Loại | Thuộc Tính | Validation |
|-----------|------|-----------|------------|
| `presentation/dto/create-inquiry.dto.ts` | DTO | listingId, message, phone? | Required string, max 2000 char message |
| `presentation/dto/list-inquiries.dto.ts` | DTO | page?, limit? | Int, min 1, max 100, defaults 1/20 |
| `presentation/dto/create-inquiry.dto.ts` | DTO | listingId, message, phone? | Chuỗi bắt buộc, message tối đa 2000 ký tự |
| `presentation/dto/list-inquiries.dto.ts` | DTO | page?, limit? | Số nguyên, tối thiểu 1, tối đa 100, mặc định 1/20 |
#### Presentation Tests (1 file)
#### Test Tầng Presentation (1 file)
| File Path | Type | Test Count | Coverage |
| Đường Dẫn File | Loại | Số Test | Phạm Vi |
|-----------|------|-----------|----------|
| `presentation/__tests__/inquiries.controller.spec.ts` | Jest | 6 tests | All 4 endpoints, null phone handling, pagination defaults |
| `presentation/__tests__/inquiries.controller.spec.ts` | Jest | 6 test | Cả 4 endpoint, xử lý phone null, giá trị mặc định phân trang |
---
### **MODULE FILES** (2 files)
### **FILE MODULE** (2 file)
| File Path | Type | Description |
| Đường Dẫn File | Loại | Mô Tả |
|-----------|------|-------------|
| `inquiries.module.ts` | NestJS Module | Exports: InquiriesController; Providers: PrismaInquiryRepository, 2 command handlers, 2 query handlers |
| `index.ts` | Barrel Export | Exports: InquiriesModule, INQUIRY_REPOSITORY symbol, IInquiryRepository interface, InquiryEntity |
| `inquiries.module.ts` | NestJS Module | Exports: InquiriesController; Providers: PrismaInquiryRepository, 2 command handler, 2 query handler |
| `index.ts` | Barrel Export | Exports: InquiriesModule, symbol INQUIRY_REPOSITORY, interface IInquiryRepository, InquiryEntity |
---
## 🏗️ MODULE ARCHITECTURE
## 🏗️ KIẾN TRÚC MODULE
### **Design Pattern: CQRS + Event Sourcing + DDD**
### **Mẫu Thiết Kế: CQRS + Event Sourcing + DDD**
```
┌─────────────────────────────────────────────────────────────┐
@@ -227,57 +227,57 @@ apps/api/src/modules/inquiries/
└──────────────────────┘
```
### **Data Flow**
### **Luồng Dữ Liệu**
**Creating Inquiry:**
**Tạo Inquiry:**
```
POST /inquiries (CreateInquiryDto)
→ InquiriesController.createInquiry()
→ CommandBus.execute(CreateInquiryCommand)
→ CreateInquiryHandler.execute()
- Validate listing exists (Prisma)
- Create InquiryEntity
- Save to repository (Prisma)
- Publish InquiryCreatedEvent via EventBus
Return { id, listingId, createdAt }
- Xác thực listing tồn tại (Prisma)
- Tạo InquiryEntity
- Lưu vào repository (Prisma)
- Phát InquiryCreatedEvent qua EventBus
Trả về { id, listingId, createdAt }
```
**Marking as Read:**
**Đánh Dấu Đã Đọc:**
```
PATCH /inquiries/:id/read (agent only)
PATCH /inquiries/:id/read (chỉ agent)
→ InquiriesController.markAsRead()
→ CommandBus.execute(MarkInquiryReadCommand)
→ MarkInquiryReadHandler.execute()
- Find inquiry entity
- Verify agent is listing owner
- Call entity.markAsRead()
- Update in repository
- Publish InquiryReadEvent via EventBus
Return { success: true }
- Tìm inquiry entity
- Xác minh agent là chủ sở hữu listing
- Gọi entity.markAsRead()
- Cập nhật trong repository
- Phát InquiryReadEvent qua EventBus
Trả về { success: true }
```
**Getting Inquiries:**
**Lấy Danh Sách Inquiry:**
```
GET /inquiries/listing/:id or /agent/me (with pagination)
→ InquiriesController.getByListing() or getMyInquiries()
→ QueryBus.execute(GetInquiriesByListingQuery or GetInquiriesByAgentQuery)
→ Handler delegates to repository
→ Repository.findByListing() or findByAgent()
- Queries Prisma with joins
- Maps to InquiryReadDto
- Returns PaginatedResult
Return paginated data
GET /inquiries/listing/:id hoặc /agent/me (có phân trang)
→ InquiriesController.getByListing() hoặc getMyInquiries()
→ QueryBus.execute(GetInquiriesByListingQuery hoặc GetInquiriesByAgentQuery)
→ Handler ủy quyền cho repository
→ Repository.findByListing() hoặc findByAgent()
- Truy vấn Prisma với join
- Ánh xạ sang InquiryReadDto
- Trả về PaginatedResult
Trả về dữ liệu phân trang
```
---
## 🔑 KEY CLASSES & HANDLERS
## 🔑 CÁC LỚP & HANDLER CHÍNH
### **Domain Entity: InquiryEntity**
```typescript
export class InquiryEntity extends AggregateRoot<string> {
// Properties
// Thuộc tính
private _listingId: string;
private _userId: string;
private _message: string;
@@ -286,13 +286,13 @@ export class InquiryEntity extends AggregateRoot<string> {
// Factory Method
static createNew(id, listingId, userId, message, phone): InquiryEntity
Creates new inquiry with isRead=false
Emits InquiryCreatedEvent
Tạo inquiry mới với isRead=false
Phát InquiryCreatedEvent
// Business Logic
markAsRead(): void
Sets isRead to true
Emits InquiryReadEvent
Đt isRead thành true
Phát InquiryReadEvent
}
```
@@ -302,17 +302,17 @@ export class InquiryEntity extends AggregateRoot<string> {
```typescript
class CreateInquiryHandler implements ICommandHandler<CreateInquiryCommand> {
async execute(command: CreateInquiryCommand): Promise<CreateInquiryResult> {
// 1. Validate listing exists
// 1. Xác thực listing tồn tại
const listing = await prisma.listing.findUnique(...)
if (!listing) throw NotFoundException
// 2. Create entity with factory
// 2. Tạo entity qua factory
const inquiry = InquiryEntity.createNew(...)
// 3. Persist to database
// 3. Lưu vào database
await inquiryRepo.save(inquiry)
// 4. Publish domain events
// 4. Phát domain events
const events = inquiry.clearDomainEvents()
events.forEach(e => eventBus.publish(e))
@@ -325,22 +325,22 @@ class CreateInquiryHandler implements ICommandHandler<CreateInquiryCommand> {
```typescript
class MarkInquiryReadHandler implements ICommandHandler<MarkInquiryReadCommand> {
async execute(command: MarkInquiryReadCommand): Promise<void> {
// 1. Load aggregate root
// 1. Tải aggregate root
const inquiry = await inquiryRepo.findById(...)
if (!inquiry) throw NotFoundException
// 2. Verify authorization
// 2. Xác minh quyền hạn
const listing = await prisma.listing.findUnique(...)
const agent = await prisma.agent.findUnique(...)
if (agent.id !== listing.agentId) throw ForbiddenException
// 3. Update domain state
// 3. Cập nhật trạng thái domain
inquiry.markAsRead()
// 4. Persist state
// 4. Lưu trạng thái
await inquiryRepo.markAsRead(...)
// 5. Publish events
// 5. Phát sự kiện
const events = inquiry.clearDomainEvents()
events.forEach(e => eventBus.publish(e))
}
@@ -366,155 +366,155 @@ class GetInquiriesByListingHandler implements IQueryHandler<GetInquiriesByListin
```typescript
class GetInquiriesByAgentHandler implements IQueryHandler<GetInquiriesByAgentQuery> {
async execute(query: GetInquiriesByAgentQuery): Promise<PaginatedResult<InquiryReadDto>> {
// 1. Resolve agent ID from user ID
// 1. Tra cứu agent ID từ user ID
const agent = await prisma.agent.findUnique({ where: { userId } })
if (!agent) throw NotFoundException
// 2. Delegate to repository
// 2. Ủy quyền cho repository
return this.inquiryRepo.findByAgent(agent.id, page, limit)
}
}
```
### **Repository Implementation**
### **Triển Khai Repository**
#### PrismaInquiryRepository
**Methods:**
1. `findById(id)` - Single inquiry lookup
2. `save(entity)` - Create inquiry
3. `markAsRead(id)` - Update isRead flag
4. `findByListing(listingId, page, limit)` - Paginated search with joins
5. `findByAgent(agentId, page, limit)` - Paginated search via listing agent
6. `countUnreadByAgent(agentId)` - Unread count aggregation
**Các Phương Thức:**
1. `findById(id)` - Tra cứu inquiry đơn lẻ
2. `save(entity)` - Tạo inquiry
3. `markAsRead(id)` - Cập nhật cờ isRead
4. `findByListing(listingId, page, limit)` - Tìm kiếm phân trang với join
5. `findByAgent(agentId, page, limit)` - Tìm kiếm phân trang qua agent của listing
6. `countUnreadByAgent(agentId)` - Tổng hợp số lượng chưa đọc
**Prisma Relations Used:**
- `inquiry.listing` → property (for title)
**Quan Hệ Prisma Sử Dụng:**
- `inquiry.listing` → property (lấy tiêu đề)
- `inquiry.user` → fullName, phone
- `listing.agentId`agent filter
- Pagination: skip/take with orderBy descending
- `listing.agentId`lọc theo agent
- Phân trang: skip/take với orderBy giảm dần
---
## 🎯 DDD LAYER STRUCTURE
## 🎯 CẤU TRÚC TẦNG DDD
### **DOMAIN LAYER** (`domain/`)
### **TẦNG DOMAIN** (`domain/`)
**Purpose:** Pure business logic, independent of frameworks
**Mục Đích:** Logic nghiệp vụ thuần túy, độc lập với framework
**Contains:**
**Bao Gồm:**
- **Entities** - `inquiry.entity.ts` (Aggregate Root)
- Pure TypeScript class
- Encapsulates business rules (isRead flag, field validation)
- Factory method for creation
- Methods for state transitions
- Domain events collection
- Lớp TypeScript thuần túy
- Đóng gói các quy tắc nghiệp vụ (cờ isRead, xác thực trường)
- Factory method để tạo mới
- Các phương thức chuyển đổi trạng thái
- Tập hợp domain events
- **Events** - `inquiry-created.event.ts`, `inquiry-read.event.ts`
- Record significant business occurrences
- Used for event sourcing & audit trails
- Plain data classes
- Ghi lại các sự kiện nghiệp vụ quan trọng
- Dùng cho event sourcing audit trail
- Lớp dữ liệu đơn giản
- **Repositories** - `inquiry.repository.ts`, `inquiry-read.dto.ts`
- Interface defines contract (dependency inversion)
- Symbol for DI token
- Read DTO separate from write entity
- Pagination result interface
- Interface định nghĩa hợp đồng (đảo ngược phụ thuộc)
- Symbol cho DI token
- Read DTO tách biệt với write entity
- Interface kết quả phân trang
**Isolation:**
- Zero NestJS dependencies
- Zero database dependencies
- Zero external service dependencies
**Sự Cô Lập:**
- Không có dependency NestJS
- Không có dependency database
- Không có dependency dịch vụ bên ngoài
---
### **APPLICATION LAYER** (`application/`)
### **TẦNG APPLICATION** (`application/`)
**Purpose:** Use cases & coordination
**Mục Đích:** Use case và điều phối
**Contains:**
- **Commands** - Mutable operations
**Bao Gồm:**
- **Commands** - Các thao tác thay đổi dữ liệu
- `CreateInquiryCommand` - Input DTO
- `MarkInquiryReadCommand` - Input DTO
- Handlers orchestrate domain operations
- Các handler điều phối thao tác domain
- **Queries** - Immutable reads
- **Queries** - Các đọc bất biến
- `GetInquiriesByListingQuery`
- `GetInquiriesByAgentQuery`
- Handlers delegate to repository
- Các handler ủy quyền cho repository
**Responsibilities:**
- Validate preconditions (listing exists, agent authorized)
- Coordinate domain entity operations
- Publish domain events
- Handle cross-cutting concerns (logging, etc.)
**Trách Nhiệm:**
- Xác thực điều kiện tiên quyết (listing tồn tại, agent được phép)
- Điều phối các thao tác domain entity
- Phát domain events
- Xử lý các mối quan tâm xuyên suốt (logging, v.v.)
**NestJS Integration:**
- `@CommandHandler()` decorator
- `@QueryHandler()` decorator
- Dependency injection via constructor
**Tích Hợp NestJS:**
- Decorator `@CommandHandler()`
- Decorator `@QueryHandler()`
- Dependency injection qua constructor
---
### **INFRASTRUCTURE LAYER** (`infrastructure/`)
### **TẦNG INFRASTRUCTURE** (`infrastructure/`)
**Purpose:** Database & persistence details
**Mục Đích:** Chi tiết database persistence
**Contains:**
**Bao Gồm:**
- **Repositories** - `prisma-inquiry.repository.ts`
- Implements domain repository interface
- Uses Prisma client for queries
- Maps database records ↔ domain entities
- Handles pagination logic
- Triển khai interface repository của domain
- Dùng Prisma client cho các truy vấn
- Ánh xạ bản ghi database ↔ domain entity
- Xử lý logic phân trang
**Responsibilities:**
- Query building
- Result mapping
- Pagination calculation
- Join relationships
- Database-specific optimizations
**Trách Nhiệm:**
- Xây dựng truy vấn
- Ánh xạ kết quả
- Tính toán phân trang
- Quan hệ join
- Tối ưu hóa riêng cho database
**Isolation:**
- Swappable implementations (could use TypeORM, MongoDB, etc.)
- Domain code unaffected by database changes
**Sự Cô Lập:**
- Triển khai có thể hoán đổi (có thể dùng TypeORM, MongoDB, v.v.)
- Code domain không bị ảnh hưởng khi thay đổi database
---
### **PRESENTATION LAYER** (`presentation/`)
### **TẦNG PRESENTATION** (`presentation/`)
**Purpose:** HTTP interface & I/O
**Mục Đích:** Giao diện HTTP và I/O
**Contains:**
**Bao Gồm:**
- **Controllers** - `inquiries.controller.ts`
- NestJS `@Controller` decorator
- HTTP route handlers
- Dispatch to CQRS bus
- Return HTTP responses
- Decorator NestJS `@Controller`
- Các handler route HTTP
- Phân phối đến CQRS bus
- Trả về HTTP response
- **DTOs** - `create-inquiry.dto.ts`, `list-inquiries.dto.ts`
- Input validation (class-validator)
- Swagger documentation (@ApiProperty)
- Separate from domain entities
- Xác thực đầu vào (class-validator)
- Tài liệu Swagger (@ApiProperty)
- Tách biệt với domain entity
**Responsibilities:**
- Route handling
- Request validation
- Authentication/Authorization
- Response formatting
- API documentation
**Trách Nhiệm:**
- Xử lý route
- Xác thực request
- Xác thực danh tính/Phân quyền
- Định dạng response
- Tài liệu API
**NestJS Integration:**
- Decorators: `@Post`, `@Get`, `@Patch`
- Guards: `JwtAuthGuard`, `RolesGuard`
**Tích Hợp NestJS:**
- Decorator: `@Post`, `@Get`, `@Patch`
- Guard: `JwtAuthGuard`, `RolesGuard`
- Middleware: `@CurrentUser`, `@Roles`
---
## 📊 TEST FILES SUMMARY
## 📊 TÓM TẮT FILE TEST
### **Test Statistics**
### **Thống Kê Test**
| Layer | File | Tests | Type |
| Tầng | File | Số Test | Loại |
|-------|------|-------|------|
| Domain | `domain/__tests__/inquiry-domain.spec.ts` | 5 | Jest |
| Application | `application/__tests__/create-inquiry.handler.spec.ts` | 4 | Jest |
@@ -522,114 +522,114 @@ class GetInquiriesByAgentHandler implements IQueryHandler<GetInquiriesByAgentQue
| Application | `application/__tests__/get-inquiries-by-listing.handler.spec.ts` | 2 | Jest |
| Application | `application/__tests__/get-inquiries-by-agent.handler.spec.ts` | 2 | Jest |
| Presentation | `presentation/__tests__/inquiries.controller.spec.ts` | 6 | Jest |
| **TOTAL** | **6 files** | **24 tests** | Jest + Vitest |
| **TỔNG** | **6 file** | **24 test** | Jest + Vitest |
### **Test Coverage by File**
### **Phạm Vi Test Theo File**
#### Domain Tests (`inquiry-domain.spec.ts`) - 5 tests
#### Test Domain (`inquiry-domain.spec.ts`) - 5 test
```
✓ InquiryEntity.createNew() with phone
✓ InquiryEntity.createNew() with null phone
✓ createNew() emits InquiryCreatedEvent
✓ markAsRead() sets flag to true
✓ markAsRead() emits InquiryReadEvent
✓ InquiryEntity.createNew() phone
✓ InquiryEntity.createNew() với phone null
✓ createNew() phát InquiryCreatedEvent
✓ markAsRead() đặt cờ thành true
✓ markAsRead() phát InquiryReadEvent
```
#### CreateInquiryHandler Tests - 4 tests
#### Test CreateInquiryHandler - 4 test
```
Creates inquiry successfully
Throws NotFoundException for missing listing
✓ Publishes domain events after saving
Tạo inquiry thành công
Ném NotFoundException khi listing không tồn tại
✓ Phát domain events sau khi lưu
```
#### MarkInquiryReadHandler Tests - 5 tests
#### Test MarkInquiryReadHandler - 5 test
```
Marks inquiry as read successfully
Throws NotFoundException when inquiry not found
Throws NotFoundException when listing not found
Throws ForbiddenException when user not agent
Throws ForbiddenException when agent not found
Đánh dấu inquiry đã đọc thành công
Ném NotFoundException khi không tìm thấy inquiry
Ném NotFoundException khi không tìm thấy listing
Ném ForbiddenException khi người dùng không phải agent
Ném ForbiddenException khi không tìm thấy agent
```
#### GetInquiriesByListingHandler Tests - 2 tests
#### Test GetInquiriesByListingHandler - 2 test
```
Returns paginated results
Returns empty data when no inquiries
Trả về kết quả phân trang
Trả về dữ liệu rỗng khi không có inquiry
```
#### GetInquiriesByAgentHandler Tests - 2 tests
#### Test GetInquiriesByAgentHandler - 2 test
```
Returns paginated results
Throws NotFoundException for non-agent user
Trả về kết quả phân trang
Ném NotFoundException cho người dùng không phải agent
```
#### InquiriesController Tests - 6 tests
#### Test InquiriesController - 6 test
```
✓ POST creates inquiry with command dispatch
✓ POST passes null phone when not provided
✓ GET /listing dispatches query with defaults
✓ GET /listing passes custom pagination
✓ GET /agent/me dispatches agent query
✓ PATCH marks inquiry and returns success
✓ POST tạo inquiry với command dispatch
✓ POST truyền phone null khi không cung cấp
✓ GET /listing phân phối query với giá trị mặc định
✓ GET /listing truyền phân trang tùy chỉnh
✓ GET /agent/me phân phối agent query
✓ PATCH đánh dấu inquiry và trả về thành công
```
---
## 🔍 SUMMARY STATISTICS
## 🔍 THỐNG KÊ TÓM TẮT
| Metric | Count |
| Chỉ Số | Số Lượng |
|--------|-------|
| **Total Files** | 25 |
| **Source Files (.ts, excluding tests)** | 19 |
| **Test Files** | 6 |
| **Total Lines of Code** | 1,212 |
| **Tổng Số File** | 25 |
| **File Nguồn (.ts, trừ test)** | 19 |
| **File Test** | 6 |
| **Tổng Số Dòng Code** | 1.212 |
| **Commands** | 2 |
| **Queries** | 2 |
| **Command Handlers** | 2 |
| **Query Handlers** | 2 |
| **Domain Events** | 2 |
| **HTTP Endpoints** | 4 |
| **HTTP Endpoint** | 4 |
| **DTOs** | 2 |
| **Interfaces** | 3 (IInquiryRepository, PaginatedResult, InquiryReadDto) |
| **Test Suites** | 6 |
| **Test Cases** | 24 |
| **Test Suite** | 6 |
| **Test Case** | 24 |
---
## 🛠️ KEY DEPENDENCIES
## 🛠️ CÁC DEPENDENCY CHÍNH
**External Packages:**
**Gói Bên Ngoài:**
- `@nestjs/common` - Framework
- `@nestjs/cqrs` - CQRS bus
- `@paralleldrive/cuid2` - ID generation
- `@paralleldrive/cuid2` - Tạo ID
- `@prisma/client` - ORM
- `class-validator` - DTO validation
- `@nestjs/swagger` - API documentation
- `class-validator` - Xác thực DTO
- `@nestjs/swagger` - Tài liệu API
**Internal Modules:**
**Module Nội Bộ:**
- `@modules/shared` - AggregateRoot, DomainEvent, exceptions
- `@modules/auth` - JwtPayload, JwtAuthGuard, RolesGuard
---
## 🔐 Security & Authorization
## 🔐 Bảo Mật & Phân Quyền
**Authentication:**
- All endpoints require `@UseGuards(JwtAuthGuard)`
- JWT token extracted via `@CurrentUser()` decorator
**Xác Thực Danh Tính:**
- Tất cả endpoint yêu cầu `@UseGuards(JwtAuthGuard)`
- JWT token được trích xuất qua decorator `@CurrentUser()`
**Authorization:**
- `GET /agent/me``@Roles('AGENT')` enforced
- `PATCH /:id/read``@Roles('AGENT')` enforced
- **MarkInquiryReadHandler** verifies:
- Inquiry exists
- Listing exists
- User is registered as agent
- Agent owns the listing
**Phân Quyền:**
- `GET /agent/me` áp dụng `@Roles('AGENT')`
- `PATCH /:id/read` áp dụng `@Roles('AGENT')`
- **MarkInquiryReadHandler** xác minh:
- Inquiry tồn tại
- Listing tồn tại
- Người dùng đã đăng ký là agent
- Agent sở hữu listing
---
## 📝 API CONTRACTS
## 📝 HỢP ĐỒNG API
### POST /inquiries
```
@@ -654,49 +654,49 @@ Status: 200 OK | 401 Unauthorized | 403 Forbidden
### PATCH /inquiries/:id/read
```
Request: (no body)
Request: (không có body)
Response: { success: boolean }
Status: 200 OK | 401 Unauthorized | 403 Forbidden | 404 Not Found
```
---
## 🎓 ARCHITECTURAL INSIGHTS
## 🎓 NHẬN XÉT KIẾN TRÚC
### **Strengths**
### **Điểm Mạnh**
1. **Clean Architecture** - Clear separation of concerns across layers
2. **CQRS Pattern** - Separate read/write paths enable scalability
3. **Domain-Driven Design** - Business logic in entities, not anemic models
4. **Event-Driven** - Domain events enable audit trails and event sourcing
5. **Testability** - Each layer independently testable with mocks
6. **Type Safety** - Full TypeScript with interfaces and strict types
7. **DI Framework** - NestJS provides dependency injection out of box
1. **Kiến Trúc Sạch** - Phân tách mối quan tâm rõ ràng giữa các tầng
2. **Mẫu CQRS** - Đường đọc/ghi riêng biệt giúp tăng khả năng mở rộng
3. **Thiết Kế Hướng Domain** - Logic nghiệp vụ nằm trong entity, không phải mô hình thiếu máu
4. **Hướng Sự Kiện** - Domain events cho phép audit trail event sourcing
5. **Khả Năng Kiểm Thử** - Mỗi tầng có thể kiểm thử độc lập với mock
6. **An Toàn Kiểu** - TypeScript đầy đủ với interface và kiểu chặt chẽ
7. **Framework DI** - NestJS cung cấp dependency injection sẵn có
### **Design Decisions**
### **Quyết Định Thiết Kế**
1. **InquiryEntity as Aggregate Root**
- Encapsulates inquiry business rules
- Controls state transitions via methods (createNew, markAsRead)
- Collects domain events
1. **InquiryEntity Aggregate Root**
- Đóng gói quy tắc nghiệp vụ inquiry
- Kiểm soát chuyển đổi trạng thái qua phương thức (createNew, markAsRead)
- Thu thập domain events
2. **Repository Pattern**
- Interface in domain, implementation in infrastructure
- Allows swapping data sources without affecting business logic
2. **Mẫu Repository**
- Interface trong domain, triển khai trong infrastructure
- Cho phép hoán đổi nguồn dữ liệu mà không ảnh hưởng đến logic nghiệp vụ
3. **Separate Read/Write DTOs**
- `CreateInquiryDto` (input) vs `InquiryReadDto` (output)
- Enables flexible API evolution
3. **DTO Đọc/Ghi Riêng Biệt**
- `CreateInquiryDto` (đầu vào) so với `InquiryReadDto` (đầu ra)
- Cho phép tiến hóa API linh hoạt
4. **CQRS Handlers**
- Commands handle mutations with authorization
- Queries handle reads with filtering
- Both independent, can be optimized separately
- Commands xử lý thay đổi dữ liệu với phân quyền
- Queries xử lý đọc với lọc
- Cả hai độc lập, có thể tối ưu riêng biệt
5. **Pagination Interface**
- Consistent pagination across all list endpoints
- Page + limit model with calculated totalPages
5. **Interface Phân Trang**
- Phân trang nhất quán trên tất cả endpoint danh sách
- Mô hình page + limit với totalPages được tính toán
---
**End of Exploration Report**
**Kết Thúc Báo Cáo Khám Phá**