# Khám Phá Mô-đun Admin & Ghi Log Kiểm Toán Nền Tảng GoodGo

## Tổng Quan
Tài liệu này cung cấp phân tích toàn diện về codebase Nền Tảng GoodGo nhằm mục đích triển khai tính năng ghi log kiểm toán trong mô-đun admin. Quá trình khám phá bao gồm cấu trúc mô-đun admin, các mẫu hiện có, triển khai DDD và cơ sở hạ tầng sự kiện.

---

## 1. CẤU TRÚC MÔ-ĐUN ADMIN

### Cấu Trúc Thư Mục
```
apps/api/src/modules/admin/
├── admin.module.ts                              # Bootstrap mô-đun & cấu hình DI
├── index.ts                                     # Xuất công khai
│
├── domain/                                      # Tầng Domain DDD
│   ├── events/                                  # Sự kiện domain được phát hành bởi lệnh
│   │   ├── kyc-approved.event.ts
│   │   ├── kyc-rejected.event.ts
│   │   ├── listing-approved.event.ts
│   │   ├── listing-rejected.event.ts
│   │   ├── subscription-adjusted.event.ts
│   │   ├── user-banned.event.ts
│   │   ├── user-unbanned.event.ts
│   │   └── index.ts
│   ├── repositories/
│   │   ├── admin-query.repository.ts           # Giao diện repository truy vấn (read models)
│   │   └── index.ts
│   ├── __tests__/
│   │   └── admin-events.spec.ts
│   └── index.ts
│
├── application/                                 # Tầng Ứng Dụng CQRS
│   ├── commands/                               # Bộ xử lý lệnh (mutations)
│   │   ├── adjust-subscription/
│   │   │   ├── adjust-subscription.command.ts
│   │   │   └── adjust-subscription.handler.ts
│   │   ├── approve-kyc/
│   │   ├── approve-listing/
│   │   ├── ban-user/
│   │   ├── bulk-moderate-listings/
│   │   ├── reject-kyc/
│   │   ├── reject-listing/
│   │   ├── update-user-status/
│   │   ├── __tests__/                         # Mỗi handler có spec riêng
│   │   └── index.ts
│   │
│   ├── queries/                                # Bộ xử lý truy vấn (read models)
│   │   ├── get-dashboard-stats/
│   │   ├── get-kyc-queue/
│   │   ├── get-moderation-queue/
│   │   ├── get-revenue-stats/
│   │   ├── get-user-detail/
│   │   ├── get-users/
│   │   ├── __tests__/
│   │   └── index.ts
│   │
│   ├── listeners/                              # Người đăng ký sự kiện (hiệu ứng phụ)
│   │   ├── user-banned.listener.ts            # Vô hiệu hoá listing, gửi thông báo
│   │   ├── user-deactivated.listener.ts
│   │   └── (gọi qua decorator @OnEvent)
│   │
│   ├── __tests__/                             # Kiểm thử tích hợp cho các handler
│   │   └── *.spec.ts files
│   │
│   └── index.ts
│
├── infrastructure/                             # Tầng Truy Cập Dữ Liệu
│   ├── repositories/
│   │   ├── prisma-admin-query.repository.ts   # Triển khai Prisma
│   │   ├── admin-stats.queries.ts             # Truy vấn SQL thuần/Prisma
│   │   ├── admin-user.queries.ts              # Truy vấn SQL thuần/Prisma
│   │   └── index.ts
│   └── index.ts
│
└── presentation/                               # Tầng HTTP
    ├── controllers/
    │   ├── admin.controller.ts                # Quản lý người dùng, gói đăng ký, dashboard
    │   ├── admin-moderation.controller.ts     # Kiểm duyệt, KYC
    │   └── index.ts
    │
    ├── dto/                                   # Đối Tượng Truyền Dữ Liệu
    │   ├── adjust-subscription.dto.ts
    │   ├── approve-kyc.dto.ts
    │   ├── approve-listing.dto.ts
    │   ├── ban-user.dto.ts
    │   ├── bulk-moderate.dto.ts
    │   ├── get-users-query.dto.ts
    │   ├── reject-kyc.dto.ts
    │   ├── reject-listing.dto.ts
    │   ├── revenue-stats.dto.ts
    │   ├── update-user-status.dto.ts
    │   └── index.ts
    │
    └── index.ts
```

---

## 2. PHÂN TÍCH SCHEMA PRISMA

### Trạng Thái Hiện Tại
- **Cơ sở dữ liệu**: PostgreSQL 16 + PostGIS
- **Vị trí Schema**: `prisma/schema.prisma`
- **Số dòng**: Tổng cộng 602 dòng

### Model User (Liên Quan Đến Kiểm Toán)
```typescript
model User {
  id           String    @id @default(cuid())
  email        String?   @unique
  phone        String    @unique
  passwordHash String?
  fullName     String
  avatarUrl    String?
  role         UserRole  @default(BUYER)  // BUYER, SELLER, AGENT, ADMIN
  kycStatus    KYCStatus @default(NONE)   // NONE, PENDING, VERIFIED, REJECTED
  kycData      Json?
  isActive     Boolean   @default(true)   // Cờ ban
  deletedAt    DateTime?
  deletionScheduledAt DateTime?
  createdAt    DateTime  @default(now())
  updatedAt    DateTime  @updatedAt
  
  // Relations...
  @@index([role])
  @@index([kycStatus])
  @@index([isActive])
  @@index([deletedAt])
  @@index([createdAt])
  @@index([role, isActive, createdAt(sort: Desc)])
  @@index([kycStatus, createdAt])
}
```

### Model Listing (Liên Quan Đến Kiểm Toán)
```typescript
model Listing {
  id               String          @id @default(cuid())
  propertyId       String
  agentId          String?
  sellerId         String
  status           ListingStatus   @default(DRAFT) 
    // DRAFT, PENDING_REVIEW, ACTIVE, RESERVED, SOLD, RENTED, EXPIRED, REJECTED
  moderationScore  Float?
  moderationNotes  String?
  // ... other fields
  @@index([status])
  @@index([createdAt])
}
```

### **CHƯA CÓ BẢNG KIỂM TOÁN**
- ✅ Không tìm thấy model AuditLog
- ✅ Không tìm thấy model AdminAction
- ✅ Cơ hội triển khai từ đầu theo các mẫu của dự án

---

## 3. LUỒNG & HÀNH ĐỘNG ADMIN CONTROLLER

### AdminController (Quản Lý Người Dùng)
**File**: `presentation/controllers/admin.controller.ts`

#### Các Endpoint:
1. **GET /admin/users** - Danh sách người dùng với bộ lọc
   - Tham số truy vấn: page, limit, role, isActive, search
   - Trả về: UserListResult (có phân trang)

2. **GET /admin/users/:id** - Lấy chi tiết người dùng
   - Trả về: UserDetail (thông tin đầy đủ + hoạt động)

3. **PATCH /admin/users/status** - Cập nhật trạng thái hoạt động của người dùng
   - Body: `UpdateUserStatusDto` {userId, isActive, reason}
   - Người dùng hiện tại (admin) được lấy qua `@CurrentUser()`
   - Lệnh: `UpdateUserStatusCommand(userId, adminId, isActive, reason)`

4. **POST /admin/users/ban** - Cấm/bỏ cấm người dùng
   - Body: `BanUserDto` {userId, reason, unban?}
   - Lệnh: `BanUserCommand(userId, adminId, reason, unban)`
   - **Chú ý**: Admin ID được lấy từ JWT

5. **POST /admin/subscriptions/adjust** - Điều chỉnh gói đăng ký
   - Body: `AdjustSubscriptionDto` {userId, newPlanTier, reason}
   - Lệnh: `AdjustSubscriptionCommand(userId, adminId, newPlanTier, reason)`

6. **GET /admin/dashboard** - Thống kê dashboard
   - Truy vấn: `GetDashboardStatsQuery`

7. **GET /admin/revenue** - Thống kê doanh thu
   - Tham số truy vấn: startDate, endDate, groupBy (day/month)

### AdminModerationController (Kiểm Duyệt Nội Dung)
**File**: `presentation/controllers/admin-moderation.controller.ts`

#### Các Endpoint:
1. **GET /admin/moderation** - Lấy hàng đợi kiểm duyệt
   - Tham số truy vấn: page, limit
   - Trả về: ModerationQueueResult

2. **POST /admin/moderation/approve** - Phê duyệt listing
   - Body: `ApproveListingDto` {listingId, moderationNotes}
   - Lệnh: `ApproveListingCommand(listingId, adminId, moderationNotes)`
   - Sự kiện: `ListingApprovedEvent` được phát hành

3. **POST /admin/moderation/reject** - Từ chối listing
   - Body: `RejectListingDto` {listingId, reason}
   - Lệnh: `RejectListingCommand(listingId, adminId, reason)`
   - Sự kiện: `ListingRejectedEvent` được phát hành

4. **POST /admin/moderation/bulk** - Kiểm duyệt hàng loạt
   - Body: `BulkModerateDto` {listingIds[], action, reason}
   - Lệnh: `BulkModerateListingsCommand(...)`

5. **GET /admin/kyc** - Lấy hàng đợi KYC
   - Trả về: KycQueueResult (người dùng có KYC PENDING)

6. **POST /admin/kyc/approve** - Phê duyệt KYC
   - Body: `ApproveKycDto` {userId, comments}
   - Lệnh: `ApproveKycCommand(userId, adminId, comments)`
   - Sự kiện: `KycApprovedEvent` được phát hành

7. **POST /admin/kyc/reject** - Từ chối KYC
   - Body: `RejectKycDto` {userId, reason}
   - Lệnh: `RejectKycCommand(userId, adminId, reason)`
   - Sự kiện: `KycRejectedEvent` được phát hành

### Mẫu Chính: Lấy Admin ID
```typescript
@Post('moderation/approve')
async approveListing(
  @Body() dto: ApproveListingDto,
  @CurrentUser() user: JwtPayload,  // ← Danh tính Admin
) {
  return this.commandBus.execute(
    new ApproveListingCommand(dto.listingId, user.sub, dto.moderationNotes)
  );
  // user.sub = userId của admin
}
```

---

## 4. CƠ SỞ HẠ TẦNG SỰ KIỆN/GHI LOG HIỆN CÓ

### Giao Diện DomainEvent
**Vị trí**: `@modules/shared`

```typescript
// Tất cả sự kiện domain đều triển khai DomainEvent:
export interface DomainEvent {
  readonly eventName: string;
  readonly occurredAt: Date;
  readonly aggregateId: string;  // Những gì đã thay đổi (user/listing ID)
}
```

### Ví dụ: UserBannedEvent
```typescript
export class UserBannedEvent implements DomainEvent {
  readonly eventName = 'user.banned';
  readonly occurredAt = new Date();

  constructor(
    public readonly aggregateId: string,      // userId
    public readonly adminId: string,           // ← Admin đã thực hiện hành động
    public readonly reason: string,
  ) {}
}
```

### Ví dụ: ListingApprovedEvent
```typescript
export class ListingApprovedEvent implements DomainEvent {
  readonly eventName = 'listing.approved';
  readonly occurredAt = new Date();

  constructor(
    public readonly aggregateId: string,         // listingId
    public readonly adminId: string,             // ← Admin đã phê duyệt
    public readonly moderationNotes?: string,
  ) {}
}
```

### Phát Hành & Lắng Nghe Sự Kiện
**Mẫu Sử Dụng**: NestJS CQRS + EventEmitter

#### Phát Hành (trong Command Handlers):
```typescript
@CommandHandler(BanUserCommand)
export class BanUserHandler implements ICommandHandler<BanUserCommand> {
  constructor(
    private readonly userRepo: IUserRepository,
    private readonly eventBus: EventBus,  // ← Được inject
  ) {}

  async execute(command: BanUserCommand): Promise<BanUserResult> {
    // ... business logic ...
    
    this.eventBus.publish(
      new UserBannedEvent(user.id, command.adminId, command.reason)
    );
    
    return { userId: user.id, isActive: false, message: 'Người dùng đã bị ban' };
  }
}
```

#### Lắng Nghe (trong Event Listeners):
```typescript
@Injectable()
export class UserBannedListener {
  constructor(
    private readonly commandBus: CommandBus,
    private readonly prisma: PrismaService,
    private readonly logger: LoggerService,
  ) {}

  @OnEvent('user.banned', { async: true })
  async handle(event: UserBannedEvent): Promise<void> {
    this.logger.log(
      `Handling user.banned for user ${event.aggregateId}`,
      'UserBannedListener'
    );

    // Hiệu ứng phụ: vô hiệu hoá listing, gửi thông báo, v.v.
    const deactivated = await this.prisma.listing.updateMany({
      where: { sellerId: event.aggregateId, status: { in: ['ACTIVE', ...] } },
      data: { status: 'EXPIRED' },
    });

    // Gửi thông báo email
    await this.commandBus.execute(
      new SendNotificationCommand(user.id, 'EMAIL', 'user.banned', ...)
    );
  }
}
```

### Kiến Trúc EventBus
- **Mô-đun**: `@nestjs/cqrs`
- **Thiết lập**: `CqrsModule.forRoot()` trong `app.module.ts`
- **Cơ chế**: 
  - Lệnh phát hành sự kiện qua `eventBus.publish(event)`
  - Listener đăng ký qua `@OnEvent(eventName, { async: true })`
  - Sự kiện mặc định là bất đồng bộ (không chặn)

---

## 5. DỊCH VỤ LOGGER

**Vị trí**: `apps/api/src/modules/shared/infrastructure/logger.service.ts`

### Tính Năng
- **Nhà cung cấp**: Pino (ghi log có cấu trúc)
- **Che giấu PII**: Tự động ẩn các trường nhạy cảm
  - Đường dẫn bị che giấu: password, token, email, phone, kycData, creditCard, v.v.
  - Mẫu kiểm duyệt: `[REDACTED]`
- **Nhận biết môi trường**:
  - Dev: In đẹp có màu sắc
  - Prod: JSON có cấu trúc
- **Các phương thức**: 
  - `log(message, context)`
  - `error(message, trace, context)`
  - `warn(message, context)`
  - `debug(message, context)`
  - `verbose(message, context)`
  - `child(bindings)` - Logger con có ràng buộc ngữ cảnh

### Các Trường Bị Che Giấu
```typescript
redact: {
  paths: [
    'password', 'passwordHash', 'token', 'accessToken', 'refreshToken',
    'secret', 'authorization', 'cookie', 'creditCard', 'cardNumber',
    'cvv', 'ssn', 'cmnd', 'cccd', 'email', 'phone', 'kycData',
    'idNumber', 'identityNumber', 'dateOfBirth', 'dob', 'address',
    'bankAccount', 'accountNumber', 'apiKey', 'privateKey', 'encryptionKey',
    'req.headers.authorization', 'req.headers.cookie',
    'user.email', 'user.phone', 'user.kycData',
    'body.password', 'body.token', 'body.email', 'body.phone',
  ],
  censor: '[REDACTED]',
}
```

---

## 6. XỬ LÝ NGOẠI LỆ & BỘ LỌC

### GlobalExceptionFilter
**Vị trí**: `apps/api/src/modules/shared/infrastructure/filters/global-exception.filter.ts`

```typescript
@Catch()
export class GlobalExceptionFilter implements ExceptionFilter {
  constructor(private readonly logger: LoggerService) {}

  catch(exception: unknown, host: ArgumentsHost): void {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const correlationId = (request.headers['x-correlation-id'] as string) ?? undefined;

    const errorResponse = this.buildErrorResponse(exception, correlationId);
    
    this.logger.error(
      `[${errorResponse.errorCode}] ${errorResponse.message}`,
      exception instanceof Error ? exception.stack : undefined,
      'GlobalExceptionFilter'
    );

    response.status(errorResponse.statusCode).json(errorResponse);
  }
}
```

### Cấu Trúc Phản Hồi Lỗi
```typescript
interface ErrorResponseBody {
  statusCode: number;
  errorCode: ErrorCode;  // Enum với các giá trị như VALIDATION_FAILED, NOT_FOUND, v.v.
  message: string;
  details?: Record<string, unknown>;
  correlationId?: string;
  timestamp: string;
}
```

### Ngoại Lệ Domain
```typescript
export class DomainException extends HttpException {
  constructor(
    public readonly errorCode: ErrorCode,
    message: string,
    statusCode: HttpStatus = HttpStatus.INTERNAL_SERVER_ERROR,
    public readonly details?: Record<string, unknown>,
  ) {
    super(message, statusCode);
  }
}

// Các ngoại lệ cụ thể:
export class NotFoundException extends DomainException { ... }
export class ValidationException extends DomainException { ... }
export class ConflictException extends DomainException { ... }
export class UnauthorizedException extends DomainException { ... }
export class ForbiddenException extends DomainException { ... }
```

---

## 7. BẢO MẬT & GUARD

### Kiểm Soát Truy Cập Dựa Trên Vai Trò (RBAC)
**Vị trí**: `apps/api/src/modules/auth/presentation/decorators/`

#### Mẫu:
```typescript
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('ADMIN')
export class AdminController {
  // Tất cả endpoint yêu cầu vai trò ADMIN
}
```

#### Luồng Roles Guard:
1. `JwtAuthGuard` - Xác thực token JWT
2. `RolesGuard` - Kiểm tra decorator `@Roles()` với user.role
3. Cả hai decorator từ `@modules/auth`

### Giới Hạn Tốc Độ
**Thiết lập**: ThrottlerModule + ThrottlerBehindProxyGuard
- Mặc định: 60 yêu cầu mỗi 60 giây mỗi IP
- Endpoint xác thực: 10 yêu cầu mỗi 60 giây
- Callback thanh toán: 20 yêu cầu mỗi 60 giây

---

## 8. CẤU TRÚC TẦNG DDD

### Các Tầng Kiến Trúc

```
Tầng Trình Bày (Controllers)
    ↓ (Xác thực DTO)
Tầng Ứng Dụng (Commands/Queries/Handlers/Listeners)
    ↓ (Command/Query)
Tầng Domain (Events, Interfaces, Business Rules)
    ↓ (Gọi Repository, Phát hành Event)
Tầng Cơ Sở Hạ Tầng (Prisma, Database)
```

### Mẫu Command Handler
```typescript
// 1. Command (giống DTO)
export class BanUserCommand {
  constructor(
    public readonly userId: string,
    public readonly adminId: string,
    public readonly reason: string,
    public readonly unban: boolean = false,
  ) {}
}

// 2. Handler (Business Logic + Phát hành Event)
@CommandHandler(BanUserCommand)
export class BanUserHandler implements ICommandHandler<BanUserCommand> {
  constructor(
    @Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
    private readonly eventBus: EventBus,
  ) {}

  async execute(command: BanUserCommand): Promise<BanUserResult> {
    // Business logic
    const user = await this.userRepo.findById(command.userId);
    if (!user) throw new NotFoundException(...);
    
    user.deactivate();
    await this.userRepo.update(user);
    
    // Phát hành event
    this.eventBus.publish(
      new UserBannedEvent(user.id, command.adminId, command.reason)
    );
    
    return { userId: user.id, isActive: false, message: '...' };
  }
}

// 3. Event (Kích hoạt Hiệu Ứng Phụ)
export class UserBannedEvent implements DomainEvent {
  readonly eventName = 'user.banned';
  readonly occurredAt = new Date();
  
  constructor(
    public readonly aggregateId: string,
    public readonly adminId: string,
    public readonly reason: string,
  ) {}
}

// 4. Listener (Hiệu Ứng Phụ - được kích hoạt bởi Event)
@Injectable()
export class UserBannedListener {
  @OnEvent('user.banned', { async: true })
  async handle(event: UserBannedEvent): Promise<void> {
    // Gửi thông báo, cập nhật dữ liệu liên quan, v.v.
  }
}
```

### Mẫu Query Handler
```typescript
// Query (định nghĩa thao tác đọc)
export class GetDashboardStatsQuery {}

// Handler (lấy & trả về dữ liệu)
@QueryHandler(GetDashboardStatsQuery)
export class GetDashboardStatsHandler implements IQueryHandler<GetDashboardStatsQuery> {
  constructor(
    @Inject(ADMIN_QUERY_REPOSITORY) private readonly adminQueryRepo: IAdminQueryRepository,
  ) {}

  async execute(_query: GetDashboardStatsQuery): Promise<DashboardStats> {
    return this.adminQueryRepo.getDashboardStats();
  }
}
```

### Mẫu Repository
```typescript
// Giao diện Domain (không có chi tiết triển khai)
export interface IAdminQueryRepository {
  getModerationQueue(page: number, limit: number): Promise<ModerationQueueResult>;
  getDashboardStats(): Promise<DashboardStats>;
  // ... thêm phương thức
}

// Triển khai cơ sở hạ tầng (dành riêng cho Prisma)
@Injectable()
export class PrismaAdminQueryRepository implements IAdminQueryRepository {
  constructor(private readonly prisma: PrismaService) {}

  async getModerationQueue(page: number, limit: number): Promise<ModerationQueueResult> {
    // Truy vấn Prisma ở đây
  }
}

// Token DI
export const ADMIN_QUERY_REPOSITORY = Symbol('ADMIN_QUERY_REPOSITORY');

// Đăng ký mô-đun
@Module({
  providers: [
    { provide: ADMIN_QUERY_REPOSITORY, useClass: PrismaAdminQueryRepository },
  ],
})
export class AdminModule {}
```

---

## 9. BOOTSTRAP MÔ-ĐUN

### Thiết Lập AdminModule
**File**: `apps/api/src/modules/admin/admin.module.ts`

```typescript
@Module({
  imports: [CqrsModule, AuthModule, ListingsModule, SubscriptionsModule],
  controllers: [AdminController, AdminModerationController],
  providers: [
    // Repositories
    { provide: ADMIN_QUERY_REPOSITORY, useClass: PrismaAdminQueryRepository },

    // CQRS Handlers
    ...CommandHandlers,  // 8 command handler
    ...QueryHandlers,    // 6 query handler

    // Event Listeners
    UserBannedListener,
    UserDeactivatedListener,
  ],
})
export class AdminModule {}
```

### Thiết Lập Toàn Cục
**File**: `apps/api/src/app.module.ts`

```typescript
@Module({
  imports: [
    SentryModule.forRoot(),
    CqrsModule.forRoot(),              // ← CQRS với Event Bus
    ScheduleModule.forRoot(),
    ThrottlerModule.forRoot(...),      // ← Giới hạn tốc độ
    // ... các mô-đun khác bao gồm AdminModule
  ],
  providers: [
    {
      provide: APP_FILTER,
      useClass: SentryGlobalFilter,
    },
    {
      provide: APP_GUARD,
      useClass: ThrottlerBehindProxyGuard,
    },
    {
      provide: APP_INTERCEPTOR,
      useClass: HttpMetricsInterceptor,
    },
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(SanitizeInputMiddleware).forRoutes('*');
    consumer.apply(CsrfMiddleware).exclude(...).forRoutes('*');
  }
}
```

---

## 10. CÁC MẪU INTERCEPTOR HIỆN CÓ

### HttpMetricsInterceptor
**Vị trí**: `@modules/metrics`

- Được inject toàn cục qua `APP_INTERCEPTOR`
- Theo dõi các số liệu yêu cầu/phản hồi HTTP
- Có thể dùng làm mẫu cho interceptor ghi log kiểm toán

### CSRF Middleware
**Vị trí**: `@modules/shared/infrastructure/middleware/csrf.middleware`

- Mẫu double-submit cookie
- Xác thực trên các phương thức thay đổi trạng thái
- Cung cấp mô hình để tăng cường ngữ cảnh yêu cầu

---

## 11. TÓM TẮT CHO TRIỂN KHAI GHI LOG KIỂM TOÁN

### Những Gì Đã Có Sẵn ✅
1. **Kiến trúc hướng sự kiện** - Lệnh phát hành sự kiện, listener xử lý hiệu ứng phụ
2. **Lấy danh tính admin** - Tất cả hành động admin đều có `adminId` trong lệnh
3. **Dịch vụ Logger** - Dựa trên Pino với che giấu PII
4. **Xử lý ngoại lệ** - Bộ lọc toàn cục + phân cấp DomainException
5. **RBAC** - Guard @Roles('ADMIN') đã có sẵn
6. **Bootstrap mô-đun** - Mẫu DI rõ ràng sẵn sàng để inject audit repository
7. **Xác thực DTO** - Tất cả đầu vào được xác thực qua class-validator

### Những Gì Cần Xây Dựng 🚀
1. **Model Prisma AuditLog** - Lưu trữ trong cơ sở dữ liệu
2. **AuditLoggingInterceptor** - Lấy ngữ cảnh HTTP (IP, timestamp, endpoint)
3. **Domain Event AuditEvent** - Mở rộng sự kiện domain cho mục đích kiểm toán
4. **AuditLoggingListener** - Listener sự kiện lưu vào AuditLog
5. **AuditLog Repository** - Thao tác CRUD cho AuditLog
6. **Query Handler** - Truy xuất log kiểm toán với bộ lọc (khoảng thời gian, admin, loại hành động)
7. **Endpoint Controller** - GET /admin/audit-logs để xem nhật ký kiểm toán

### Các Điểm Tích Hợp Chính
1. Lệnh đã truyền `adminId` → Sử dụng trong AuditLoggingListener
2. Sự kiện domain đã được phát hành → Móc listener kiểm toán vào các sự kiện liên quan
3. HTTPContext (IP, user-agent, v.v.) → Lấy trong interceptor
4. Dịch vụ Logger có sẵn → Sử dụng để ghi log có cấu trúc
5. Mẫu Repository đã được thiết lập → Tuân theo cho AuditLog repository

### Các Trường Kiểm Toán Được Đề Xuất
- `id` (khoá chính)
- `adminId` (người đã thực hiện hành động)
- `adminName` (để phi chuẩn hoá)
- `action` (ví dụ: 'user.banned', 'listing.approved')
- `resourceType` (ví dụ: 'user', 'listing')
- `resourceId` (những gì bị ảnh hưởng)
- `changes` (JSON những gì đã thay đổi - trước/sau)
- `reason` (từ DTO nếu được cung cấp)
- `ipAddress` (từ yêu cầu)
- `userAgent` (từ yêu cầu)
- `status` (thành công/thất bại)
- `statusCode` (HTTP status)
- `errorMessage` (nếu thất bại)
- `duration` (mili giây)
- `createdAt` (dấu thời gian)

---

## Tham Chiếu Các File Chính

### Controllers
- `presentation/controllers/admin.controller.ts` - Các thao tác admin chính
- `presentation/controllers/admin-moderation.controller.ts` - Kiểm duyệt & KYC

### Command Handlers (Điểm Hành Động)
- `application/commands/ban-user/ban-user.handler.ts`
- `application/commands/approve-listing/approve-listing.handler.ts`
- `application/commands/approve-kyc/approve-kyc.handler.ts`
- `application/commands/reject-listing/reject-listing.handler.ts`
- `application/commands/reject-kyc/reject-kyc.handler.ts`
- `application/commands/adjust-subscription/adjust-subscription.handler.ts`
- `application/commands/update-user-status/update-user-status.handler.ts`
- `application/commands/bulk-moderate-listings/bulk-moderate-listings.handler.ts`

### Event Listeners (Nơi Móc Kiểm Toán)
- `application/listeners/user-banned.listener.ts`
- `application/listeners/user-deactivated.listener.ts`

### Cơ Sở Hạ Tầng
- `infrastructure/repositories/prisma-admin-query.repository.ts`
- `infrastructure/repositories/admin-stats.queries.ts`
- `infrastructure/repositories/admin-user.queries.ts`

### Tài Nguyên Dùng Chung
- Logger: `@modules/shared/infrastructure/logger.service.ts`
- Exception Filter: `@modules/shared/infrastructure/filters/global-exception.filter.ts`
- Roles Guard: `@modules/auth` (decorators + guard)

### Prisma
- Schema: `prisma/schema.prisma` (602 dòng, chưa có model kiểm toán)
- Đảm bảo type safety của Prisma Client

---

## Các Bước Tiếp Theo

1. ✅ **Thiết Kế Schema** - Tạo model AuditLog trong Prisma
2. ✅ **Mẫu Repository** - Tạo AuditLogRepository với giao diện
3. ✅ **Sự Kiện Kiểm Toán** - Tạo sự kiện domain cho mục đích kiểm toán
4. ✅ **Event Listener** - Tạo AuditLoggingListener để lưu trữ sự kiện
5. ✅ **Interceptor** - Lấy ngữ cảnh HTTP (tuỳ chọn nâng cao)
6. ✅ **Query Handler** - Tạo query/handler để truy xuất log kiểm toán
7. ✅ **Endpoint Controller** - Thêm endpoint GET /admin/audit-logs
8. ✅ **Kiểm Thử** - Kiểm thử đơn vị và tích hợp
9. ✅ **Tài Liệu** - Tài liệu API trong Swagger
