Files
goodgo-platform/docs/audits/INQUIRIES_COMPLETE_FILE_INDEX.md
Ho Ngoc Hai b8512ebff4 docs: consolidate audit and analysis reports into docs/audits/
Move 36 root-level audit/analysis documents and 7 web app audit documents
into docs/audits/ directory to declutter the project root. Remove stale
EXPLORATION_SUMMARY.txt.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-11 01:37:50 +07:00

23 KiB

Inquiries Module - Complete File Index

Generated: April 11, 2026
Module: apps/api/src/modules/inquiries/
Total Files: 25


📋 COMPLETE FILE LISTING BY PATH

1. APPLICATION LAYER

Commands (4 files)

📄 apps/api/src/modules/inquiries/application/commands/create-inquiry/create-inquiry.command.ts
   Type: Command (CQRS)
   Purpose: Input DTO for create inquiry use case
   Exports: CreateInquiryCommand class
   Properties: userId, listingId, message, phone
   Lines: ~8
📄 apps/api/src/modules/inquiries/application/commands/create-inquiry/create-inquiry.handler.ts
   Type: Command Handler (CQRS)
   Purpose: Orchestrates inquiry creation
   Implements: ICommandHandler<CreateInquiryCommand>
   Returns: CreateInquiryResult { id, listingId, createdAt }
   Key Logic:
     • Validate listing exists (Prisma.listing.findUnique)
     • Create InquiryEntity via factory method
     • Save to repository
     • Publish InquiryCreatedEvent via EventBus
   Lines: ~60
   Dependencies: 
     • INQUIRY_REPOSITORY (injected)
     • EventBus (injected)
     • PrismaService (injected)
     • LoggerService (injected)
📄 apps/api/src/modules/inquiries/application/commands/mark-inquiry-read/mark-inquiry-read.command.ts
   Type: Command (CQRS)
   Purpose: Input DTO for mark inquiry as read
   Exports: MarkInquiryReadCommand class
   Properties: inquiryId, agentUserId
   Lines: ~6
📄 apps/api/src/modules/inquiries/application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts
   Type: Command Handler (CQRS)
   Purpose: Orchestrates mark as read operation
   Implements: ICommandHandler<MarkInquiryReadCommand>
   Returns: void (no return value)
   Key Logic:
     • Load inquiry entity from repository
     • Load listing and verify agent ownership
     • Call inquiry.markAsRead() to update state
     • Persist via repository.markAsRead()
     • Publish InquiryReadEvent via EventBus
   Authorization Checks:
     • Inquiry exists
     • Listing exists
     • User is registered as agent
     • Agent ID matches listing.agentId
   Lines: ~50
   Dependencies:
     • INQUIRY_REPOSITORY (injected)
     • EventBus (injected)
     • PrismaService (injected)
     • LoggerService (injected)

Queries (4 files)

📄 apps/api/src/modules/inquiries/application/queries/get-inquiries-by-agent/get-inquiries-by-agent.query.ts
   Type: Query (CQRS)
   Purpose: Input DTO for listing agent's inquiries
   Exports: GetInquiriesByAgentQuery class
   Properties: agentUserId, page, limit
   Lines: ~6
📄 apps/api/src/modules/inquiries/application/queries/get-inquiries-by-agent/get-inquiries-by-agent.handler.ts
   Type: Query Handler (CQRS)
   Purpose: Resolves paginated inquiries for an agent
   Implements: IQueryHandler<GetInquiriesByAgentQuery>
   Returns: PaginatedResult<InquiryReadDto>
   Key Logic:
     • Resolve agent ID from userId via Prisma
     • Throw NotFoundException if user not an agent
     • Delegate to repository.findByAgent()
   Lines: ~30
   Dependencies:
     • INQUIRY_REPOSITORY (injected)
     • PrismaService (injected)
📄 apps/api/src/modules/inquiries/application/queries/get-inquiries-by-listing/get-inquiries-by-listing.query.ts
   Type: Query (CQRS)
   Purpose: Input DTO for listing inquiries
   Exports: GetInquiriesByListingQuery class
   Properties: listingId, page, limit
   Lines: ~6
📄 apps/api/src/modules/inquiries/application/queries/get-inquiries-by-listing/get-inquiries-by-listing.handler.ts
   Type: Query Handler (CQRS)
   Purpose: Resolves paginated inquiries for a listing
   Implements: IQueryHandler<GetInquiriesByListingQuery>
   Returns: PaginatedResult<InquiryReadDto>
   Key Logic:
     • Direct delegation to repository.findByListing()
   Lines: ~20
   Dependencies:
     • INQUIRY_REPOSITORY (injected)

Application Tests (4 files)

📄 apps/api/src/modules/inquiries/application/__tests__/create-inquiry.handler.spec.ts
   Type: Unit Tests (Jest/Vitest)
   Test Framework: Vitest with Mock functions (vi.fn)
   Test Count: 4
   Tests:
     ✓ creates an inquiry successfully
     ✓ throws NotFoundException when listing not found
     ✓ publishes domain events after saving
   Mocks:
     • mockInquiryRepo (IInquiryRepository implementation)
     • mockEventBus
     • mockPrisma.listing.findUnique
   Lines: ~98
📄 apps/api/src/modules/inquiries/application/__tests__/mark-inquiry-read.handler.spec.ts
   Type: Unit Tests (Jest/Vitest)
   Test Count: 5
   Tests:
     ✓ marks an inquiry as read successfully
     ✓ throws NotFoundException when inquiry not found
     ✓ throws NotFoundException when listing not found
     ✓ throws ForbiddenException when user is not the listing agent
     ✓ throws ForbiddenException when agent not found for user
   Mocks:
     • mockInquiryRepo
     • mockEventBus
     • mockPrisma.listing.findUnique
     • mockPrisma.agent.findUnique
   Lines: ~130
📄 apps/api/src/modules/inquiries/application/__tests__/get-inquiries-by-listing.handler.spec.ts
   Type: Unit Tests (Jest/Vitest)
   Test Count: 2
   Tests:
     ✓ returns paginated results
     ✓ returns empty data when no inquiries found
   Mocks:
     • mockInquiryRepo.findByListing
   Lines: ~69
📄 apps/api/src/modules/inquiries/application/__tests__/get-inquiries-by-agent.handler.spec.ts
   Type: Unit Tests (Jest/Vitest)
   Test Count: 2
   Tests:
     ✓ returns paginated results
     ✓ throws NotFoundException when agent not found for user
   Mocks:
     • mockInquiryRepo.findByAgent
     • mockPrisma.agent.findUnique
   Lines: ~77

2. DOMAIN LAYER

Entities (1 file)

📄 apps/api/src/modules/inquiries/domain/entities/inquiry.entity.ts
   Type: Aggregate Root (DDD)
   Extends: AggregateRoot<string>
   Purpose: Core business logic for inquiries
   
   Properties (Private):
     • _listingId: string
     • _userId: string
     • _message: string
     • _phone: string | null
     • _isRead: boolean
   
   Getters (Read-only):
     • listingId: string
     • userId: string
     • message: string
     • phone: string | null
     • isRead: boolean
   
   Factory Method:
     • static createNew(id, listingId, userId, message, phone): InquiryEntity
       - Creates new inquiry with isRead=false
       - Emits InquiryCreatedEvent
       - Returns entity instance
   
   Domain Methods:
     • markAsRead(): void
       - Sets _isRead to true
       - Emits InquiryReadEvent
   
   Lines: ~63
   Dependencies:
     • AggregateRoot from @modules/shared
     • InquiryCreatedEvent
     • InquiryReadEvent

Events (2 files)

📄 apps/api/src/modules/inquiries/domain/events/inquiry-created.event.ts
   Type: Domain Event (DDD)
   Implements: DomainEvent interface
   Purpose: Signals that an inquiry was created
   
   Properties:
     • eventName: string = 'inquiry.created'
     • occurredAt: Date = new Date()
     • aggregateId: string
     • listingId: string
     • userId: string
   
   Lines: ~13
📄 apps/api/src/modules/inquiries/domain/events/inquiry-read.event.ts
   Type: Domain Event (DDD)
   Implements: DomainEvent interface
   Purpose: Signals that an inquiry was marked as read
   
   Properties:
     • eventName: string = 'inquiry.read'
     • occurredAt: Date = new Date()
     • aggregateId: string
     • listingId: string
     • userId: string
   
   Lines: ~13

Repositories (2 files)

📄 apps/api/src/modules/inquiries/domain/repositories/inquiry.repository.ts
   Type: Repository Interface + Symbol (DDD)
   Purpose: Defines persistence contract
   
   Exports:
     • INQUIRY_REPOSITORY: Symbol (DI token)
     • IInquiryRepository: Interface
     • PaginatedResult<T>: Interface
   
   IInquiryRepository Methods:
     • 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>
   
   PaginatedResult<T> Interface:
     • data: T[]
     • total: number
     • page: number
     • limit: number
     • totalPages: number
   
   Lines: ~22
📄 apps/api/src/modules/inquiries/domain/repositories/inquiry-read.dto.ts
   Type: Data Transfer Object (Read Model)
   Purpose: DTO for query results
   Exports: InquiryReadDto interface
   
   InquiryReadDto Properties:
     • id: string
     • listingId: string
     • listingTitle: string
     • userId: string
     • userName: string
     • userPhone: string
     • message: string
     • phone: string | null
     • isRead: boolean
     • createdAt: string
   
   Lines: ~13

Domain Tests (1 file)

📄 apps/api/src/modules/inquiries/domain/__tests__/inquiry-domain.spec.ts
   Type: Unit Tests (Jest/Vitest)
   Test Count: 5
   Tests:
     ✓ createNew() creates an inquiry with correct properties
     ✓ createNew() creates an inquiry with null phone
     ✓ createNew() emits InquiryCreatedEvent
     ✓ markAsRead() sets isRead to true
     ✓ markAsRead() emits InquiryReadEvent
   Focus: Entity behavior and domain event emission
   Lines: ~96

3. INFRASTRUCTURE LAYER

Repository Implementation (1 file)

📄 apps/api/src/modules/inquiries/infrastructure/repositories/prisma-inquiry.repository.ts
   Type: Repository Implementation (Service)
   Implements: IInquiryRepository
   Decorator: @Injectable()
   Purpose: Prisma-based persistence
   
   Methods:
     1. findById(id: string): Promise<InquiryEntity | null>
        - Query: prisma.inquiry.findUnique({ where: { id } })
        - Returns: InquiryEntity | null
     
     2. save(entity: InquiryEntity): Promise<void>
        - Query: prisma.inquiry.create({ data: {...} })
        - Maps entity properties to Prisma model
     
     3. markAsRead(id: string): Promise<void>
        - Query: prisma.inquiry.update({ where: { id }, data: { isRead: true } })
     
     4. findByListing(listingId, page, limit): Promise<PaginatedResult<InquiryReadDto>>
        - Query: prisma.inquiry.findMany() with joins
        - Includes: listing.property.title, user.fullName, user.phone
        - Pagination: skip/take pattern
        - Sorting: orderBy: { createdAt: 'desc' }
        - Returns: Mapped InquiryReadDto array with pagination info
     
     5. findByAgent(agentId, page, limit): Promise<PaginatedResult<InquiryReadDto>>
        - Query: prisma.inquiry.findMany() filtered by listing.agentId
        - Same includes and pagination as findByListing
        - Filter: { listing: { agentId } }
     
     6. countUnreadByAgent(agentId): Promise<number>
        - Query: prisma.inquiry.count()
        - Filter: { isRead: false, listing: { agentId } }
     
     Helper Method:
     • toDomain(raw: PrismaInquiry): InquiryEntity
       - Maps database record to domain entity
   
   Lines: ~146
   Dependencies:
     • PrismaService (injected)
     • InquiryEntity
     • IInquiryRepository (implements)

4. PRESENTATION LAYER

Controller (1 file)

📄 apps/api/src/modules/inquiries/presentation/controllers/inquiries.controller.ts
   Type: NestJS Controller
   Decorator: @Controller('inquiries')
   Decorator: @ApiTags('inquiries')
   Purpose: HTTP API endpoints
   
   Endpoints:
   
   1. POST /inquiries
      Decorator: @Post()
      Auth: @UseGuards(JwtAuthGuard)
      Method: createInquiry(dto: CreateInquiryDto, user: JwtPayload)
      Body: CreateInquiryDto { listingId, message, phone? }
      Returns: CreateInquiryResult { id, listingId, createdAt }
      Status Codes: 201 Created, 400 Bad Request, 401 Unauthorized, 404 Not Found
      Logic: 
        • Dispatch CreateInquiryCommand via CommandBus
        • Pass user.sub as userId
   
   2. GET /inquiries/listing/:listingId
      Decorator: @Get('listing/:listingId')
      Auth: @UseGuards(JwtAuthGuard)
      Method: getByListing(listingId: string, dto: ListInquiriesDto)
      Query: page?, limit?
      Returns: PaginatedResult<InquiryReadDto>
      Status Codes: 200 OK, 401 Unauthorized
      Logic:
        • Dispatch GetInquiriesByListingQuery via QueryBus
        • Default page=1, limit=20
   
   3. GET /inquiries/agent/me
      Decorator: @Get('agent/me')
      Auth: @UseGuards(JwtAuthGuard, RolesGuard)
      Decorator: @Roles('AGENT')
      Method: getMyInquiries(user: JwtPayload, dto: ListInquiriesDto)
      Query: page?, limit?
      Returns: PaginatedResult<InquiryReadDto>
      Status Codes: 200 OK, 401 Unauthorized, 403 Forbidden
      Logic:
        • Dispatch GetInquiriesByAgentQuery via QueryBus
        • Pass user.sub as agentUserId
        • Default page=1, limit=20
   
   4. PATCH /inquiries/:id/read
      Decorator: @Patch(':id/read')
      Auth: @UseGuards(JwtAuthGuard, RolesGuard)
      Decorator: @Roles('AGENT')
      Method: markAsRead(id: string, user: JwtPayload)
      Returns: { success: boolean }
      Status Codes: 200 OK, 401 Unauthorized, 403 Forbidden, 404 Not Found
      Logic:
        • Dispatch MarkInquiryReadCommand via CommandBus
        • Pass user.sub as agentUserId
        • Return { success: true }
   
   Lines: ~121
   Dependencies:
     • CommandBus (injected)
     • QueryBus (injected)
     • @nestjs/swagger decorators
     • @modules/auth guards and decorators

DTOs (2 files)

📄 apps/api/src/modules/inquiries/presentation/dto/create-inquiry.dto.ts
   Type: Data Transfer Object (Validation)
   Purpose: Validate POST /inquiries request body
   Decorators: @ApiProperty, @ApiPropertyOptional
   
   Properties:
     • listingId: string (required)
       Validators: @IsString(), @IsNotEmpty()
       API Doc: "ID of the listing"
     
     • message: string (required)
       Validators: @IsString(), @IsNotEmpty(), @MaxLength(2000)
       API Doc: "Tin nhắn yêu cầu tư vấn" (Consultation request message)
       Max: 2000 characters
     
     • phone?: string (optional)
       Validators: @IsOptional(), @IsString()
       API Doc: "Số điện thoại liên hệ" (Contact phone number)
   
   Lines: ~21
   Dependencies: class-validator, @nestjs/swagger
📄 apps/api/src/modules/inquiries/presentation/dto/list-inquiries.dto.ts
   Type: Data Transfer Object (Validation)
   Purpose: Validate query parameters for list endpoints
   Decorators: @ApiPropertyOptional, @Type
   
   Properties:
     • page?: number (optional)
       Validators: @IsOptional(), @IsInt(), @Min(1), @Type(() => Number)
       API Doc: "Page number", default: 1
       Example: 1
     
     • limit?: number (optional)
       Validators: @IsOptional(), @IsInt(), @Min(1), @Max(100), @Type(() => Number)
       API Doc: "Items per page", default: 20
       Example: 20
       Max: 100
   
   Lines: ~21
   Dependencies: class-validator, @nestjs/swagger, class-transformer

Presentation Tests (1 file)

📄 apps/api/src/modules/inquiries/presentation/__tests__/inquiries.controller.spec.ts
   Type: Unit Tests (Jest/Vitest)
   Test Count: 6
   
   Tests:
     ✓ POST /inquiries dispatches CreateInquiryCommand with correct parameters
     ✓ POST /inquiries passes null phone when not provided
     ✓ GET /listing/:id dispatches GetInquiriesByListingQuery with defaults
     ✓ GET /listing/:id passes custom pagination
     ✓ GET /agent/me dispatches GetInquiriesByAgentQuery with current user
     ✓ PATCH /:id/read dispatches MarkInquiryReadCommand and returns success
   
   Mocks:
     • mockCommandBus with execute() mock function
     • mockQueryBus with execute() mock function
     • mockBuyer user object (sub='buyer-1', role='BUYER')
     • mockAgent user object (sub='agent-1', role='AGENT')
   
   Lines: ~105

5. MODULE LAYER

📄 apps/api/src/modules/inquiries/inquiries.module.ts
   Type: NestJS Module
   Decorator: @Module()
   Purpose: Configure module, declare dependencies, exports
   
   Imports: [CqrsModule]
   Controllers: [InquiriesController]
   
   Providers:
     • { provide: INQUIRY_REPOSITORY, useClass: PrismaInquiryRepository }
       - Dependency injection token mapping
     • CommandHandlers array: [CreateInquiryHandler, MarkInquiryReadHandler]
     • QueryHandlers array: [GetInquiriesByListingHandler, GetInquiriesByAgentHandler]
   
   Exports: [INQUIRY_REPOSITORY]
     - Makes repository available to other modules
   
   Lines: ~29
   Dependencies:
     • @nestjs/common
     • @nestjs/cqrs
     • All handlers, repository, controller
📄 apps/api/src/modules/inquiries/index.ts
   Type: Barrel Export (Public API)
   Purpose: Define public module interface
   
   Exports:
     • InquiriesModule (default export for app imports)
     • INQUIRY_REPOSITORY (DI token)
     • IInquiryRepository (interface type)
     • InquiryEntity (for external access)
   
   Lines: ~4

📊 FILE STATISTICS

By Layer

Layer Files Type Purpose
Presentation 5 Controller + DTOs + Tests HTTP endpoints and input validation
Application 8 Commands/Queries + Handlers + Tests Use case orchestration
Domain 6 Entities + Events + Repository Interface + Tests Business logic and contracts
Infrastructure 1 Repository Implementation Database persistence
Module 2 Module + Exports Configuration and public API
TOTAL 25 - -

By Type

Type Count
Source Files (.ts, no tests) 19
Test Files (.spec.ts) 6
Total 25

By Purpose

Purpose Count Files
Controllers 1 inquiries.controller.ts
DTOs 2 create-inquiry.dto.ts, list-inquiries.dto.ts
Commands 2 create-inquiry.command.ts, mark-inquiry-read.command.ts
Queries 2 get-inquiries-by-*.query.ts (2 files)
Handlers 4 2 command handlers + 2 query handlers
Entities 1 inquiry.entity.ts
Events 2 inquiry-created.event.ts, inquiry-read.event.ts
Repositories 3 interface + 2 DTOs + 1 implementation
Tests 6 5 test suites across layers
Module 2 inquiries.module.ts, index.ts

🔍 FILE DISCOVERY GUIDE

Looking for Business Logic?

domain/entities/inquiry.entity.ts - Core business rules → domain/events/*.event.ts - Business events

Looking for Use Cases?

application/commands/*/ - Write operations → application/queries/*/ - Read operations → application/[type]/__tests__/ - Test cases

Looking for HTTP Endpoints?

presentation/controllers/inquiries.controller.ts - All 4 endpoints

Looking for Input Validation?

presentation/dto/ - All DTOs with validators

Looking for Database Logic?

infrastructure/repositories/prisma-inquiry.repository.ts - All Prisma queries

Looking for Tests?

domain/__tests__/ - Domain behavior → application/__tests__/ - Handler tests → presentation/__tests__/ - Controller tests


🔗 DEPENDENCY GRAPH

presentation/controllers/inquiries.controller.ts
  ├── application/commands/create-inquiry/create-inquiry.handler.ts
  ├── application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts
  ├── application/queries/get-inquiries-by-*/[name].handler.ts
  └── CommandBus/QueryBus (NestJS CQRS)

application/commands/*/[name].handler.ts
  ├── domain/entities/inquiry.entity.ts
  ├── domain/repositories/inquiry.repository.ts (interface)
  ├── domain/events/*.event.ts
  └── @modules/shared (AggregateRoot, exceptions)

application/queries/*/[name].handler.ts
  ├── domain/repositories/inquiry.repository.ts
  └── domain/repositories/inquiry-read.dto.ts

domain/entities/inquiry.entity.ts
  ├── @modules/shared (AggregateRoot)
  └── domain/events/*.event.ts

infrastructure/repositories/prisma-inquiry.repository.ts
  ├── domain/entities/inquiry.entity.ts
  ├── domain/repositories/inquiry.repository.ts (implements)
  ├── domain/repositories/inquiry-read.dto.ts
  └── @prisma/client (Prisma)

inquiries.module.ts
  ├── All handlers
  ├── All repositories
  ├── InquiriesController
  └── CqrsModule (NestJS)

📝 FILE CROSS-REFERENCES

Files Using INQUIRY_REPOSITORY Symbol

  • inquiries.module.ts - Provides implementation
  • application/commands/create-inquiry/create-inquiry.handler.ts - Injects
  • application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts - Injects
  • application/queries/get-inquiries-by-agent/get-inquiries-by-agent.handler.ts - Injects
  • application/queries/get-inquiries-by-listing/get-inquiries-by-listing.handler.ts - Injects

Files Creating/Modifying InquiryEntity

  • domain/entities/inquiry.entity.ts - Defines
  • application/commands/create-inquiry/create-inquiry.handler.ts - Creates
  • application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts - Modifies
  • infrastructure/repositories/prisma-inquiry.repository.ts - Maps to/from

Files Publishing Events

  • domain/entities/inquiry.entity.ts - Emits (via addDomainEvent)
  • application/commands/create-inquiry/create-inquiry.handler.ts - Publishes
  • application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts - Publishes

Files with Tests

  • domain/entities/inquiry.entity.tsdomain/__tests__/inquiry-domain.spec.ts
  • application/commands/create-inquiry/create-inquiry.handler.tsapplication/__tests__/create-inquiry.handler.spec.ts
  • application/commands/mark-inquiry-read/mark-inquiry-read.handler.tsapplication/__tests__/mark-inquiry-read.handler.spec.ts
  • application/queries/get-inquiries-by-listing/application/__tests__/get-inquiries-by-listing.handler.spec.ts
  • application/queries/get-inquiries-by-agent/application/__tests__/get-inquiries-by-agent.handler.spec.ts
  • presentation/controllers/inquiries.controller.tspresentation/__tests__/inquiries.controller.spec.ts

🎯 ENTRY POINTS FOR MODIFICATIONS

To Add a New Endpoint

  1. Add method to presentation/controllers/inquiries.controller.ts
  2. Create command/query in application/[type]/[name]/
  3. Create handler: [name].handler.ts
  4. Add tests in application/__tests__/
  5. Optional: Update domain if new business logic needed

To Add a New Command

  1. Create application/commands/[name]/[name].command.ts (DTO)
  2. Create application/commands/[name]/[name].handler.ts (@CommandHandler)
  3. Register in inquiries.module.ts CommandHandlers array
  4. Add tests in application/__tests__/[name].handler.spec.ts
  5. Optional: Define new domain events in domain/events/

To Add Domain Logic

  1. Modify domain/entities/inquiry.entity.ts
  2. Add new public methods or properties
  3. Emit new events if needed: domain/events/[name].event.ts
  4. Update tests in domain/__tests__/inquiry-domain.spec.ts
  5. Update handlers that use the entity

To Add Database Operations

  1. Add method to infrastructure/repositories/prisma-inquiry.repository.ts
  2. Update interface in domain/repositories/inquiry.repository.ts
  3. Call from relevant handlers

Total Lines of Code: 1,212
Last Updated: April 11, 2026