# 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

```typescript
// 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
```typescript
// 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_REPOSITORY` symbol

### 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 entity
- `listing` - For foreign key & agent lookup
- `property` - For listing title
- `user` - For buyer name & phone

**Key Queries:**
- `inquiry.create()` - New inquiry
- `inquiry.update()` - Mark read
- `inquiry.findMany()` - Pagination
- `inquiry.count()` - Total count

---

## 🚀 Entry Points

```typescript
// 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:**
```typescript
// Send command
commandBus.execute(new CreateInquiryCommand(...))
// Handler processes
@CommandHandler(CreateInquiryCommand)
class CreateInquiryHandler { ... }
```

**Query Pattern:**
```typescript
// Send query
queryBus.execute(new GetInquiriesByListingQuery(...))
// Handler processes
@QueryHandler(GetInquiriesByListingQuery)
class GetInquiriesByListingHandler { ... }
```

**Dependency Injection Pattern:**
```typescript
@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
