# 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 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 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 Returns: PaginatedResult 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 Returns: PaginatedResult 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 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: Interface IInquiryRepository Methods: β€’ findById(id: string): Promise β€’ save(inquiry: InquiryEntity): Promise β€’ markAsRead(id: string): Promise β€’ findByListing(listingId, page, limit): Promise> β€’ findByAgent(agentId, page, limit): Promise> β€’ countUnreadByAgent(agentId): Promise PaginatedResult 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 - Query: prisma.inquiry.findUnique({ where: { id } }) - Returns: InquiryEntity | null 2. save(entity: InquiryEntity): Promise - Query: prisma.inquiry.create({ data: {...} }) - Maps entity properties to Prisma model 3. markAsRead(id: string): Promise - Query: prisma.inquiry.update({ where: { id }, data: { isRead: true } }) 4. findByListing(listingId, page, limit): Promise> - 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> - Query: prisma.inquiry.findMany() filtered by listing.agentId - Same includes and pagination as findByListing - Filter: { listing: { agentId } } 6. countUnreadByAgent(agentId): Promise - 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 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 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.ts` β†’ `domain/__tests__/inquiry-domain.spec.ts` - `application/commands/create-inquiry/create-inquiry.handler.ts` β†’ `application/__tests__/create-inquiry.handler.spec.ts` - `application/commands/mark-inquiry-read/mark-inquiry-read.handler.ts` β†’ `application/__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.ts` β†’ `presentation/__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