Files
goodgo-platform/docs/audits/ADMIN_AUDIT_QUICK_FILES.md
Ho Ngoc Hai 59272e9321 chore(docs): consolidate 22 audit files from root into docs/audits/
Root directory had accumulated audit/exploration markdown files cluttering
the project root. Moved all audit-related files to docs/audits/ with a
README.md index, and updated cross-references in K6_LOAD_TESTING_GUIDE.md
and README_FRONTEND_DOCS.md.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-10 23:16:00 +07:00

9.0 KiB

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:

// 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)

// 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

// 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

@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<void> {
    // 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.)