Files
goodgo-platform/docs/audits/INQUIRIES_MODULE_QUICK_REFERENCE.md
Ho Ngoc Hai 11f2bf26e6
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
chore: update project documentation, audit reports, and initialize IDE configuration files
2026-04-19 03:12:54 +07:00

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ềnIInquiryRepository
  • Triển khai trong Hạ tầngPrismaInquiryRepository
  • 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ính
  • listing - Cho khóa ngoại & tra cứu môi giới
  • property - Cho tiêu đề tin đăng
  • user - 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ới
  • inquiry.update() - Đánh dấu đã đọc
  • inquiry.findMany() - Phân trang
  • inquiry.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