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>
9.3 KiB
9.3 KiB
Inquiries Module - Quick Reference
📊 Module at a Glance
Path: apps/api/src/modules/inquiries/
Pattern: CQRS + DDD
Total Files: 25
Total LOC: 1,212
Test Coverage: 6 suites, 24 tests
📁 FILES BY LAYER
PRESENTATION (5 files)
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]
APPLICATION (8 files)
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]
DOMAIN (6 files)
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]
INFRASTRUCTURE (1 file)
infrastructure/
└── repositories/
└── prisma-inquiry.repository.ts [6 methods]
MODULE (2 files)
inquiries.module.ts [NestJS Module]
index.ts [Barrel export]
🔄 REQUEST FLOWS
CREATE INQUIRY
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 }
MARK AS READ
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 }
LIST BY LISTING
GET /inquiries/listing/:listingId?page=1&limit=20
↓
InquiriesController.getByListing()
↓
QueryBus.execute(GetInquiriesByListingQuery)
↓
GetInquiriesByListingHandler
↓
PrismaInquiryRepository.findByListing()
↓
Response: PaginatedResult<InquiryReadDto>
LIST BY AGENT
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>
🔑 KEY CLASSES
| Class | Location | Purpose |
|---|---|---|
| InquiryEntity | domain/entities/ | Aggregate root with business logic |
| CreateInquiryHandler | application/commands/create-inquiry/ | Executes create command |
| MarkInquiryReadHandler | application/commands/mark-inquiry-read/ | Executes mark read command |
| GetInquiriesByListingHandler | application/queries/get-inquiries-by-listing/ | Resolves listing inquiries |
| GetInquiriesByAgentHandler | application/queries/get-inquiries-by-agent/ | Resolves agent inquiries |
| PrismaInquiryRepository | infrastructure/repositories/ | Implements persistence |
| InquiriesController | presentation/controllers/ | HTTP endpoints |
📝 KEY INTERFACES
// 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
}
🧪 TEST FILES AT A GLANCE
| Test File | Tests | Focus |
|---|---|---|
domain/__tests__/inquiry-domain.spec.ts |
5 | Entity creation, events |
application/__tests__/create-inquiry.handler.spec.ts |
4 | Handler success, validation |
application/__tests__/mark-inquiry-read.handler.spec.ts |
5 | Handler success, auth checks |
application/__tests__/get-inquiries-by-listing.handler.spec.ts |
2 | Query results, empty state |
application/__tests__/get-inquiries-by-agent.handler.spec.ts |
2 | Query results, agent lookup |
presentation/__tests__/inquiries.controller.spec.ts |
6 | All endpoints, defaults |
| TOTAL | 24 | Comprehensive coverage |
🔐 Authorization Matrix
| Endpoint | Auth | Role | Query |
|---|---|---|---|
POST /inquiries |
JWT | Any | - |
GET /listing/:id |
JWT | Any | page, limit |
GET /agent/me |
JWT | AGENT | page, limit |
PATCH /:id/read |
JWT | AGENT | - |
Permission Checks:
- MarkInquiryReadHandler: Verifies user is agent, agent owns listing
🎯 DDD PRINCIPLES
Domain Entity Encapsulation
// 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
Repository Pattern
- Interface in Domain →
IInquiryRepository - Implementation in Infrastructure →
PrismaInquiryRepository - Dependency Injection →
INQUIRY_REPOSITORYsymbol
Separate Read/Write Models
- Write Model:
InquiryEntity(aggregate) - Read Model:
InquiryReadDto(query DTO)
🔄 Domain Events
| Event | When | Data |
|---|---|---|
| InquiryCreatedEvent | Inquiry created | aggregateId, listingId, userId |
| InquiryReadEvent | Marked as read | aggregateId, listingId, userId |
💾 Database Operations
Prisma Models Used:
inquiry- Main entitylisting- For foreign key & agent lookupproperty- For listing titleuser- For buyer name & phone
Key Queries:
inquiry.create()- New inquiryinquiry.update()- Mark readinquiry.findMany()- Paginationinquiry.count()- Total count
🚀 Entry Points
// Module export
export { InquiriesModule }
// Exported interfaces
export { INQUIRY_REPOSITORY, type IInquiryRepository }
export { InquiryEntity }
// Usage in other modules
import { InquiriesModule } from '@modules/inquiries'
🎓 Architecture Summary
CLEAN ARCHITECTURE with CQRS + DDD
Presentation Layer (Controllers + DTOs)
↓
Application Layer (CQRS Handlers)
↓
Domain Layer (Entities + Events + Interfaces)
↓
Infrastructure Layer (Prisma Repository)
↓
Database
Key Characteristics:
✅ Dependency Inversion - Domain defines contracts
✅ Separation of Concerns - Each layer has clear responsibility
✅ Testability - Mock implementations at each layer
✅ Event-Driven - Domain events for audit & integration
✅ CQRS - Separate commands & queries for scalability
✅ Type Safety - Full TypeScript with strict interfaces
📌 Common Patterns
Command Pattern:
// Send command
commandBus.execute(new CreateInquiryCommand(...))
// Handler processes
@CommandHandler(CreateInquiryCommand)
class CreateInquiryHandler { ... }
Query Pattern:
// Send query
queryBus.execute(new GetInquiriesByListingQuery(...))
// Handler processes
@QueryHandler(GetInquiriesByListingQuery)
class GetInquiriesByListingHandler { ... }
Dependency Injection Pattern:
@Injectable()
export class Handler {
constructor(
@Inject(INQUIRY_REPOSITORY) private repo: IInquiryRepository,
private prisma: PrismaService,
) {}
}
🔍 Where to Look For...
| Need | File |
|---|---|
| Add new endpoint | presentation/controllers/inquiries.controller.ts |
| Add command | application/commands/[name]/[name].command.ts + [name].handler.ts |
| Add query | application/queries/[name]/[name].query.ts + [name].handler.ts |
| Business logic | domain/entities/inquiry.entity.ts |
| New domain event | domain/events/[name].event.ts |
| Database queries | infrastructure/repositories/prisma-inquiry.repository.ts |
| Input validation | presentation/dto/*.ts |
| Write tests | [layer]/__tests__/* |
Last Updated: April 11, 2026