# Quick File Reference for Admin Module Audit Logging ## MUST READ FIRST (15 min total) ### 1. Main Controllers (Define what actions need audit) - ✅ `apps/api/src/modules/admin/presentation/controllers/admin.controller.ts` (155 lines) - User management: ban, update status, adjust subscription - All endpoints have @CurrentUser() decorator to capture admin ID - ✅ `apps/api/src/modules/admin/presentation/controllers/admin-moderation.controller.ts` (157 lines) - Listing approval/rejection - KYC approval/rejection - Bulk moderation ### 2. Command Handlers (Where to hook audit logging) Each command publishes a domain event. Audit logging listener should listen to these events. **Ban User Flow:** - Input: `apps/api/src/modules/admin/presentation/dto/ban-user.dto.ts` - Command: `apps/api/src/modules/admin/application/commands/ban-user/ban-user.command.ts` - Handler: `apps/api/src/modules/admin/application/commands/ban-user/ban-user.handler.ts` (70 lines) - Line 62: `this.eventBus.publish(new UserBannedEvent(...))` **Approve Listing Flow:** - Input: `apps/api/src/modules/admin/presentation/dto/approve-listing.dto.ts` - Command: `apps/api/src/modules/admin/application/commands/approve-listing/approve-listing.command.ts` - Handler: `apps/api/src/modules/admin/application/commands/approve-listing/approve-listing.handler.ts` (52 lines) - Line 42-44: `this.eventBus.publish(new ListingApprovedEvent(...))` ### 3. Domain Events (What information is published) ``` apps/api/src/modules/admin/domain/events/ ├── user-banned.event.ts ├── user-unbanned.event.ts ├── listing-approved.event.ts ├── listing-rejected.event.ts ├── subscription-adjusted.event.ts ├── kyc-approved.event.ts └── kyc-rejected.event.ts ``` Each event has: - `eventName` (e.g., 'user.banned') - `occurredAt` (timestamp) - `aggregateId` (userId or listingId) - `adminId` (the admin who performed the action) - Additional context (reason, notes, etc.) ### 4. Existing Event Listener Pattern (Template) - ✅ `apps/api/src/modules/admin/application/listeners/user-banned.listener.ts` (52 lines) - Shows @OnEvent() decorator - Shows how to access event data - Shows side effects (deactivate listings, send notification) - **THIS IS YOUR TEMPLATE FOR AUDIT LOGGING LISTENER** ### 5. Logger Service (Where to log) - ✅ `apps/api/src/modules/shared/infrastructure/logger.service.ts` (65 lines) - Pino-based structured logging - Auto PII redaction - Methods: log(), error(), warn(), debug(), verbose() ### 6. Exception Filter (For error logging) - ✅ `apps/api/src/modules/shared/infrastructure/filters/global-exception.filter.ts` (145 lines) - Catches all exceptions - Logs with correlationId - Could capture failed admin actions --- ## ARCHITECTURE REFERENCES ### Repository Pattern - Domain Interface: `apps/api/src/modules/admin/domain/repositories/admin-query.repository.ts` - Prisma Implementation: `apps/api/src/modules/admin/infrastructure/repositories/prisma-admin-query.repository.ts` - **FOLLOW THIS PATTERN** for AuditLog repository ### Module Bootstrap - `apps/api/src/modules/admin/admin.module.ts` (64 lines) - Shows how to register repositories via DI - Shows how to register listeners - Shows how to import CQRS module ### Global App Setup - `apps/api/src/app.module.ts` (100+ lines) - Shows APP_FILTER, APP_GUARD, APP_INTERCEPTOR registration - Shows CqrsModule.forRoot() setup - Shows middleware configuration --- ## PRISMA SCHEMA ### Current Models (What we're auditing) - `prisma/schema.prisma` (602 lines total) **User Model** (lines 34-71): - Fields to audit: isActive, kycStatus, role **Listing Model** (lines 227-276): - Fields to audit: status, moderationScore, moderationNotes **NO AUDIT MODEL YET** - Opportunity to create from scratch --- ## EXACT ENDPOINTS TO AUDIT (From Controllers) ### AdminController Actions: 1. `PATCH /admin/users/status` - Update user active status 2. `POST /admin/users/ban` - Ban/unban user 3. `POST /admin/subscriptions/adjust` - Adjust subscription ### AdminModerationController Actions: 1. `POST /admin/moderation/approve` - Approve listing 2. `POST /admin/moderation/reject` - Reject listing 3. `POST /admin/moderation/bulk` - Bulk moderate listings 4. `POST /admin/kyc/approve` - Approve KYC 5. `POST /admin/kyc/reject` - Reject KYC Each action: - Already captures admin ID from JWT - Already publishes a domain event - Already has a command handler - Needs: Audit logging listener to capture to database --- ## DEPENDENCIES ALREADY IMPORTED ### In AdminModule: ```typescript // Already available: - CqrsModule (from @nestjs/cqrs) - AuthModule (auth guards/decorators) - ListingsModule (for listing operations) - SubscriptionsModule (for subscription operations) // In providers: - CommandHandlers (8 total) - QueryHandlers (6 total) - Event Listeners (2 existing + need to add AuditLoggingListener) ``` ### From SharedModule: - `PrismaService` (database) - `LoggerService` (logging) - Exception types (NotFoundException, ValidationException, etc.) --- ## IMPLEMENTATION CHECKLIST ### Phase 1: Database & Repository - [ ] Create AuditLog Prisma model in schema.prisma - [ ] Create IAuditLogRepository interface - [ ] Create PrismaAuditLogRepository implementation - [ ] Add to AdminModule providers ### Phase 2: Events & Listeners - [ ] Create AuditEvent domain event (if needed as wrapper) - [ ] Create AuditLoggingListener to @OnEvent() for all admin events - [ ] Inject AuditLogRepository into listener - [ ] Persist audit records on event ### Phase 3: Query & API - [ ] Create GetAuditLogsQuery - [ ] Create GetAuditLogsHandler - [ ] Create IAuditLogQueryRepository method - [ ] Add to QueryHandlers in module ### Phase 4: Controller Endpoint - [ ] Add GET /admin/audit-logs endpoint - [ ] Add filtering DTOs (dateRange, adminId, actionType, resourceId) - [ ] Add pagination support ### Phase 5: Testing - [ ] Unit tests for AuditLoggingListener - [ ] Integration tests for audit persistence - [ ] E2E tests for audit log retrieval --- ## CRITICAL PATTERNS TO FOLLOW ### 1. Command Pattern (Already Used) ```typescript // DTOs validate input // Commands encapsulate business intent // Handlers execute + publish events // Events trigger side effects via listeners ``` ### 2. DDD Layer Structure ``` Presentation (DTO validation) ↓ Application (Command/Query execution + Event publishing) ↓ Domain (Event definitions, Repository interfaces) ↓ Infrastructure (Database implementation) ``` ### 3. Dependency Injection ```typescript // Always use Symbol for tokens: export const AUDIT_LOG_REPOSITORY = Symbol('AUDIT_LOG_REPOSITORY'); // Register in module: { provide: AUDIT_LOG_REPOSITORY, useClass: PrismaAuditLogRepository } // Inject in service: constructor( @Inject(AUDIT_LOG_REPOSITORY) private readonly auditRepo: IAuditLogRepository, ) {} ``` ### 4. Event Listener Pattern ```typescript @Injectable() export class AuditLoggingListener { constructor( @Inject(AUDIT_LOG_REPOSITORY) private readonly auditRepo: IAuditLogRepository, private readonly logger: LoggerService, ) {} @OnEvent('user.banned', { async: true }) async handleUserBanned(event: UserBannedEvent): Promise { // Extract data from event // Persist to database // Log if successful/failed } } ``` --- ## WHERE TO ADD CODE ``` apps/api/src/modules/admin/ ├── domain/ │ ├── events/ │ │ └── audit-logged.event.ts (NEW - optional wrapper) │ └── repositories/ │ ├── audit-log.repository.ts (NEW - interface) │ └── index.ts (update exports) │ ├── application/ │ ├── queries/ │ │ ├── get-audit-logs/ (NEW) │ │ │ ├── get-audit-logs.query.ts │ │ │ └── get-audit-logs.handler.ts │ │ └── index.ts (update exports) │ │ │ └── listeners/ │ ├── audit-logging.listener.ts (NEW) │ └── index.ts (update if needed) │ ├── infrastructure/ │ └── repositories/ │ ├── prisma-audit-log.repository.ts (NEW) │ └── index.ts (update exports) │ └── presentation/ ├── controllers/ │ ├── admin.controller.ts (ADD ENDPOINT) │ └── admin-moderation.controller.ts (UPDATE if needed) │ └── dto/ ├── get-audit-logs-query.dto.ts (NEW) └── index.ts (update exports) prisma/ └── schema.prisma (ADD AuditLog MODEL) ``` --- ## EVENTS TO LISTEN TO 1. 'user.banned' - from UserBannedEvent 2. 'user.unbanned' - from UserUnbannedEvent 3. 'listing.approved' - from ListingApprovedEvent 4. 'listing.rejected' - from ListingRejectedEvent 5. 'kyc.approved' - from KycApprovedEvent 6. 'kyc.rejected' - from KycRejectedEvent 7. 'subscription.adjusted' - from SubscriptionAdjustedEvent 8. 'user.deactivated' - (if exists in auth module) Each gets logged with: - Admin ID (from event) - Resource ID (aggregateId from event) - Resource Type (derived from eventName) - Timestamp (from event.occurredAt) - Additional context (reason, notes, etc.)