Files
goodgo-platform/docs/audits/ADMIN_AUDIT_EXPLORATION.md
Ho Ngoc Hai 11f2bf26e6
Some checks failed
CI / Lint → Typecheck → Test → Build (22) (push) Failing after 29s
CI / E2E Tests (push) Has been skipped
CodeQL Analysis / CodeQL (javascript-typescript) (push) Failing after 2m42s
Deploy / Build Web Image (push) Failing after 27s
Deploy / Build AI Services Image (push) Failing after 29s
E2E Tests / Playwright E2E (push) Failing after 43s
Deploy / Build API Image (push) Failing after 1m31s
Security Scanning / Dependency Audit (pnpm) (push) Failing after 6s
Security Scanning / Trivy Scan — API Image (push) Failing after 5m35s
Security Scanning / Trivy Scan — AI Services Image (push) Failing after 3m45s
Deploy / Deploy to Staging (push) Has been skipped
Deploy / Smoke Test Staging (push) Has been skipped
Deploy / Deploy to Production (push) Has been skipped
Deploy / Smoke Test Production (push) Has been skipped
Deploy / Rollback Staging (push) Has been skipped
Deploy / Rollback Production (push) Has been skipped
Security Scanning / Trivy Scan — Web Image (push) Failing after 13m51s
Security Scanning / Trivy Filesystem Scan (push) Failing after 14m46s
Security Scanning / Security Gate (push) Has been cancelled
chore: update project documentation, audit reports, and initialize IDE configuration files
2026-04-19 03:12:54 +07:00

26 KiB

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)

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)

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

@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

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

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

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

@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):

@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

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

@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

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

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:

@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

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

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

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

@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

@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