chore: update project documentation, audit reports, and initialize IDE configuration files
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
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
This commit is contained in:
@@ -1,20 +1,20 @@
|
||||
# GoodGo Platform Admin Module & Audit Logging Exploration
|
||||
# Khám Phá Mô-đun Admin & Ghi Log Kiểm Toán Nền Tảng GoodGo
|
||||
|
||||
## Overview
|
||||
This document provides a comprehensive analysis of the GoodGo Platform codebase for implementing audit logging in the admin module. The exploration covers the admin module structure, existing patterns, DDD implementation, and event infrastructure.
|
||||
## 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. ADMIN MODULE STRUCTURE
|
||||
## 1. CẤU TRÚC MÔ-ĐUN ADMIN
|
||||
|
||||
### Directory Layout
|
||||
### Cấu Trúc Thư Mục
|
||||
```
|
||||
apps/api/src/modules/admin/
|
||||
├── admin.module.ts # Module bootstrap & DI configuration
|
||||
├── index.ts # Public exports
|
||||
├── admin.module.ts # Bootstrap mô-đun & cấu hình DI
|
||||
├── index.ts # Xuất công khai
|
||||
│
|
||||
├── domain/ # DDD Domain Layer
|
||||
│ ├── events/ # Domain events published by commands
|
||||
├── 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
|
||||
@@ -24,14 +24,14 @@ apps/api/src/modules/admin/
|
||||
│ │ ├── user-unbanned.event.ts
|
||||
│ │ └── index.ts
|
||||
│ ├── repositories/
|
||||
│ │ ├── admin-query.repository.ts # Query repository interface (read models)
|
||||
│ │ ├── admin-query.repository.ts # Giao diện repository truy vấn (read models)
|
||||
│ │ └── index.ts
|
||||
│ ├── __tests__/
|
||||
│ │ └── admin-events.spec.ts
|
||||
│ └── index.ts
|
||||
│
|
||||
├── application/ # CQRS Application Layer
|
||||
│ ├── commands/ # Command handlers (mutations)
|
||||
├── application/ # Tầng Ứng Dụng CQRS
|
||||
│ ├── commands/ # Bộ xử lý lệnh (mutations)
|
||||
│ │ ├── adjust-subscription/
|
||||
│ │ │ ├── adjust-subscription.command.ts
|
||||
│ │ │ └── adjust-subscription.handler.ts
|
||||
@@ -42,10 +42,10 @@ apps/api/src/modules/admin/
|
||||
│ │ ├── reject-kyc/
|
||||
│ │ ├── reject-listing/
|
||||
│ │ ├── update-user-status/
|
||||
│ │ ├── __tests__/ # Each handler has spec
|
||||
│ │ ├── __tests__/ # Mỗi handler có spec riêng
|
||||
│ │ └── index.ts
|
||||
│ │
|
||||
│ ├── queries/ # Query handlers (read models)
|
||||
│ ├── queries/ # Bộ xử lý truy vấn (read models)
|
||||
│ │ ├── get-dashboard-stats/
|
||||
│ │ ├── get-kyc-queue/
|
||||
│ │ ├── get-moderation-queue/
|
||||
@@ -55,31 +55,31 @@ apps/api/src/modules/admin/
|
||||
│ │ ├── __tests__/
|
||||
│ │ └── index.ts
|
||||
│ │
|
||||
│ ├── listeners/ # Event subscribers (side effects)
|
||||
│ │ ├── user-banned.listener.ts # Deactivates listings, sends notification
|
||||
│ ├── 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
|
||||
│ │ └── (called via @OnEvent decorator)
|
||||
│ │ └── (gọi qua decorator @OnEvent)
|
||||
│ │
|
||||
│ ├── __tests__/ # Integration tests for handlers
|
||||
│ ├── __tests__/ # Kiểm thử tích hợp cho các handler
|
||||
│ │ └── *.spec.ts files
|
||||
│ │
|
||||
│ └── index.ts
|
||||
│
|
||||
├── infrastructure/ # Data Access Layer
|
||||
├── infrastructure/ # Tầng Truy Cập Dữ Liệu
|
||||
│ ├── repositories/
|
||||
│ │ ├── prisma-admin-query.repository.ts # Prisma implementation
|
||||
│ │ ├── admin-stats.queries.ts # Raw SQL/Prisma queries
|
||||
│ │ ├── admin-user.queries.ts # Raw SQL/Prisma queries
|
||||
│ │ ├── 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/ # HTTP Layer
|
||||
└── presentation/ # Tầng HTTP
|
||||
├── controllers/
|
||||
│ ├── admin.controller.ts # User management, subscriptions, dashboard
|
||||
│ ├── admin-moderation.controller.ts # Moderation, KYC
|
||||
│ ├── 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/ # Data Transfer Objects
|
||||
├── dto/ # Đối Tượng Truyền Dữ Liệu
|
||||
│ ├── adjust-subscription.dto.ts
|
||||
│ ├── approve-kyc.dto.ts
|
||||
│ ├── approve-listing.dto.ts
|
||||
@@ -97,14 +97,14 @@ apps/api/src/modules/admin/
|
||||
|
||||
---
|
||||
|
||||
## 2. PRISMA SCHEMA ANALYSIS
|
||||
## 2. PHÂN TÍCH SCHEMA PRISMA
|
||||
|
||||
### Current State
|
||||
- **Database**: PostgreSQL 16 + PostGIS
|
||||
- **Schema Location**: `prisma/schema.prisma`
|
||||
- **Lines**: 602 lines total
|
||||
### 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
|
||||
|
||||
### User Model (Relevant to Audit)
|
||||
### Model User (Liên Quan Đến Kiểm Toán)
|
||||
```typescript
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
@@ -116,7 +116,7 @@ model User {
|
||||
role UserRole @default(BUYER) // BUYER, SELLER, AGENT, ADMIN
|
||||
kycStatus KYCStatus @default(NONE) // NONE, PENDING, VERIFIED, REJECTED
|
||||
kycData Json?
|
||||
isActive Boolean @default(true) // Ban flag
|
||||
isActive Boolean @default(true) // Cờ ban
|
||||
deletedAt DateTime?
|
||||
deletionScheduledAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
@@ -133,7 +133,7 @@ model User {
|
||||
}
|
||||
```
|
||||
|
||||
### Listing Model (Relevant to Audit)
|
||||
### Model Listing (Liên Quan Đến Kiểm Toán)
|
||||
```typescript
|
||||
model Listing {
|
||||
id String @id @default(cuid())
|
||||
@@ -150,112 +150,112 @@ model Listing {
|
||||
}
|
||||
```
|
||||
|
||||
### **NO EXISTING AUDIT TABLE**
|
||||
- ✅ No AuditLog model found
|
||||
- ✅ No AdminAction model found
|
||||
- ✅ Opportunity to implement from scratch following project patterns
|
||||
### **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. ADMIN CONTROLLER ACTIONS & FLOW
|
||||
## 3. LUỒNG & HÀNH ĐỘNG ADMIN CONTROLLER
|
||||
|
||||
### AdminController (User Management)
|
||||
### AdminController (Quản Lý Người Dùng)
|
||||
**File**: `presentation/controllers/admin.controller.ts`
|
||||
|
||||
#### Endpoints:
|
||||
1. **GET /admin/users** - List users with filters
|
||||
- Query params: page, limit, role, isActive, search
|
||||
- Returns: UserListResult (paginated)
|
||||
#### 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** - Get user details
|
||||
- Returns: UserDetail (full profile + activity)
|
||||
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** - Update user active status
|
||||
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}
|
||||
- Current user (admin) captured via `@CurrentUser()`
|
||||
- Command: `UpdateUserStatusCommand(userId, adminId, 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** - Ban/unban user
|
||||
4. **POST /admin/users/ban** - Cấm/bỏ cấm người dùng
|
||||
- Body: `BanUserDto` {userId, reason, unban?}
|
||||
- Command: `BanUserCommand(userId, adminId, reason, unban)`
|
||||
- **Key**: Admin ID is captured from JWT
|
||||
- Lệnh: `BanUserCommand(userId, adminId, reason, unban)`
|
||||
- **Chú ý**: Admin ID được lấy từ JWT
|
||||
|
||||
5. **POST /admin/subscriptions/adjust** - Adjust subscription
|
||||
5. **POST /admin/subscriptions/adjust** - Điều chỉnh gói đăng ký
|
||||
- Body: `AdjustSubscriptionDto` {userId, newPlanTier, reason}
|
||||
- Command: `AdjustSubscriptionCommand(userId, adminId, newPlanTier, reason)`
|
||||
- Lệnh: `AdjustSubscriptionCommand(userId, adminId, newPlanTier, reason)`
|
||||
|
||||
6. **GET /admin/dashboard** - Dashboard stats
|
||||
- Query: `GetDashboardStatsQuery`
|
||||
6. **GET /admin/dashboard** - Thống kê dashboard
|
||||
- Truy vấn: `GetDashboardStatsQuery`
|
||||
|
||||
7. **GET /admin/revenue** - Revenue statistics
|
||||
- Query params: startDate, endDate, groupBy (day/month)
|
||||
7. **GET /admin/revenue** - Thống kê doanh thu
|
||||
- Tham số truy vấn: startDate, endDate, groupBy (day/month)
|
||||
|
||||
### AdminModerationController (Content Moderation)
|
||||
### AdminModerationController (Kiểm Duyệt Nội Dung)
|
||||
**File**: `presentation/controllers/admin-moderation.controller.ts`
|
||||
|
||||
#### Endpoints:
|
||||
1. **GET /admin/moderation** - Get moderation queue
|
||||
- Query params: page, limit
|
||||
- Returns: ModerationQueueResult
|
||||
#### 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** - Approve listing
|
||||
2. **POST /admin/moderation/approve** - Phê duyệt listing
|
||||
- Body: `ApproveListingDto` {listingId, moderationNotes}
|
||||
- Command: `ApproveListingCommand(listingId, adminId, moderationNotes)`
|
||||
- Event: `ListingApprovedEvent` published
|
||||
- Lệnh: `ApproveListingCommand(listingId, adminId, moderationNotes)`
|
||||
- Sự kiện: `ListingApprovedEvent` được phát hành
|
||||
|
||||
3. **POST /admin/moderation/reject** - Reject listing
|
||||
3. **POST /admin/moderation/reject** - Từ chối listing
|
||||
- Body: `RejectListingDto` {listingId, reason}
|
||||
- Command: `RejectListingCommand(listingId, adminId, reason)`
|
||||
- Event: `ListingRejectedEvent` published
|
||||
- Lệnh: `RejectListingCommand(listingId, adminId, reason)`
|
||||
- Sự kiện: `ListingRejectedEvent` được phát hành
|
||||
|
||||
4. **POST /admin/moderation/bulk** - Bulk moderate
|
||||
4. **POST /admin/moderation/bulk** - Kiểm duyệt hàng loạt
|
||||
- Body: `BulkModerateDto` {listingIds[], action, reason}
|
||||
- Command: `BulkModerateListingsCommand(...)`
|
||||
- Lệnh: `BulkModerateListingsCommand(...)`
|
||||
|
||||
5. **GET /admin/kyc** - Get KYC queue
|
||||
- Returns: KycQueueResult (users with PENDING KYC)
|
||||
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** - Approve KYC
|
||||
6. **POST /admin/kyc/approve** - Phê duyệt KYC
|
||||
- Body: `ApproveKycDto` {userId, comments}
|
||||
- Command: `ApproveKycCommand(userId, adminId, comments)`
|
||||
- Event: `KycApprovedEvent` published
|
||||
- Lệnh: `ApproveKycCommand(userId, adminId, comments)`
|
||||
- Sự kiện: `KycApprovedEvent` được phát hành
|
||||
|
||||
7. **POST /admin/kyc/reject** - Reject KYC
|
||||
7. **POST /admin/kyc/reject** - Từ chối KYC
|
||||
- Body: `RejectKycDto` {userId, reason}
|
||||
- Command: `RejectKycCommand(userId, adminId, reason)`
|
||||
- Event: `KycRejectedEvent` published
|
||||
- Lệnh: `RejectKycCommand(userId, adminId, reason)`
|
||||
- Sự kiện: `KycRejectedEvent` được phát hành
|
||||
|
||||
### Key Pattern: Admin ID Capture
|
||||
### Mẫu Chính: Lấy Admin ID
|
||||
```typescript
|
||||
@Post('moderation/approve')
|
||||
async approveListing(
|
||||
@Body() dto: ApproveListingDto,
|
||||
@CurrentUser() user: JwtPayload, // ← Admin's identity
|
||||
@CurrentUser() user: JwtPayload, // ← Danh tính Admin
|
||||
) {
|
||||
return this.commandBus.execute(
|
||||
new ApproveListingCommand(dto.listingId, user.sub, dto.moderationNotes)
|
||||
);
|
||||
// user.sub = admin's userId
|
||||
// user.sub = userId của admin
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. EXISTING EVENT/LOGGING INFRASTRUCTURE
|
||||
## 4. CƠ SỞ HẠ TẦNG SỰ KIỆN/GHI LOG HIỆN CÓ
|
||||
|
||||
### DomainEvent Interface
|
||||
**Location**: `@modules/shared`
|
||||
### Giao Diện DomainEvent
|
||||
**Vị trí**: `@modules/shared`
|
||||
|
||||
```typescript
|
||||
// All domain events implement DomainEvent:
|
||||
// Tất cả sự kiện domain đều triển khai DomainEvent:
|
||||
export interface DomainEvent {
|
||||
readonly eventName: string;
|
||||
readonly occurredAt: Date;
|
||||
readonly aggregateId: string; // What changed (user/listing ID)
|
||||
readonly aggregateId: string; // Những gì đã thay đổi (user/listing ID)
|
||||
}
|
||||
```
|
||||
|
||||
### Example: UserBannedEvent
|
||||
### Ví dụ: UserBannedEvent
|
||||
```typescript
|
||||
export class UserBannedEvent implements DomainEvent {
|
||||
readonly eventName = 'user.banned';
|
||||
@@ -263,13 +263,13 @@ export class UserBannedEvent implements DomainEvent {
|
||||
|
||||
constructor(
|
||||
public readonly aggregateId: string, // userId
|
||||
public readonly adminId: string, // ← Admin who performed action
|
||||
public readonly adminId: string, // ← Admin đã thực hiện hành động
|
||||
public readonly reason: string,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
### Example: ListingApprovedEvent
|
||||
### Ví dụ: ListingApprovedEvent
|
||||
```typescript
|
||||
export class ListingApprovedEvent implements DomainEvent {
|
||||
readonly eventName = 'listing.approved';
|
||||
@@ -277,22 +277,22 @@ export class ListingApprovedEvent implements DomainEvent {
|
||||
|
||||
constructor(
|
||||
public readonly aggregateId: string, // listingId
|
||||
public readonly adminId: string, // ← Admin who approved
|
||||
public readonly adminId: string, // ← Admin đã phê duyệt
|
||||
public readonly moderationNotes?: string,
|
||||
) {}
|
||||
}
|
||||
```
|
||||
|
||||
### Event Publishing & Listening
|
||||
**Pattern Used**: NestJS CQRS + EventEmitter
|
||||
### Phát Hành & Lắng Nghe Sự Kiện
|
||||
**Mẫu Sử Dụng**: NestJS CQRS + EventEmitter
|
||||
|
||||
#### Publishing (in Command Handlers):
|
||||
#### 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, // ← Injected
|
||||
private readonly eventBus: EventBus, // ← Được inject
|
||||
) {}
|
||||
|
||||
async execute(command: BanUserCommand): Promise<BanUserResult> {
|
||||
@@ -307,7 +307,7 @@ export class BanUserHandler implements ICommandHandler<BanUserCommand> {
|
||||
}
|
||||
```
|
||||
|
||||
#### Listening (in Event Listeners):
|
||||
#### Lắng Nghe (trong Event Listeners):
|
||||
```typescript
|
||||
@Injectable()
|
||||
export class UserBannedListener {
|
||||
@@ -324,13 +324,13 @@ export class UserBannedListener {
|
||||
'UserBannedListener'
|
||||
);
|
||||
|
||||
// Side effects: deactivate listings, send notification, etc.
|
||||
// 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' },
|
||||
});
|
||||
|
||||
// Send email notification
|
||||
// Gửi thông báo email
|
||||
await this.commandBus.execute(
|
||||
new SendNotificationCommand(user.id, 'EMAIL', 'user.banned', ...)
|
||||
);
|
||||
@@ -338,37 +338,37 @@ export class UserBannedListener {
|
||||
}
|
||||
```
|
||||
|
||||
### EventBus Architecture
|
||||
- **Module**: `@nestjs/cqrs`
|
||||
- **Setup**: `CqrsModule.forRoot()` in `app.module.ts`
|
||||
- **Mechanism**:
|
||||
- Commands publish events via `eventBus.publish(event)`
|
||||
- Listeners subscribe via `@OnEvent(eventName, { async: true })`
|
||||
- Events are async by default (non-blocking)
|
||||
### 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. LOGGER SERVICE
|
||||
## 5. DỊCH VỤ LOGGER
|
||||
|
||||
**Location**: `apps/api/src/modules/shared/infrastructure/logger.service.ts`
|
||||
**Vị trí**: `apps/api/src/modules/shared/infrastructure/logger.service.ts`
|
||||
|
||||
### Features
|
||||
- **Provider**: Pino (structured logging)
|
||||
- **PII Redaction**: Automatic masking of sensitive fields
|
||||
- Redacted paths: password, token, email, phone, kycData, creditCard, etc.
|
||||
- Censor pattern: `[REDACTED]`
|
||||
- **Environment-aware**:
|
||||
- Dev: Pretty-printed with colors
|
||||
- Prod: Structured JSON
|
||||
- **Methods**:
|
||||
### 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)` - Child logger with context binding
|
||||
- `child(bindings)` - Logger con có ràng buộc ngữ cảnh
|
||||
|
||||
### Redacted Fields
|
||||
### Các Trường Bị Che Giấu
|
||||
```typescript
|
||||
redact: {
|
||||
paths: [
|
||||
@@ -387,10 +387,10 @@ redact: {
|
||||
|
||||
---
|
||||
|
||||
## 6. EXCEPTION HANDLING & FILTERS
|
||||
## 6. XỬ LÝ NGOẠI LỆ & BỘ LỌC
|
||||
|
||||
### GlobalExceptionFilter
|
||||
**Location**: `apps/api/src/modules/shared/infrastructure/filters/global-exception.filter.ts`
|
||||
**Vị trí**: `apps/api/src/modules/shared/infrastructure/filters/global-exception.filter.ts`
|
||||
|
||||
```typescript
|
||||
@Catch()
|
||||
@@ -416,11 +416,11 @@ export class GlobalExceptionFilter implements ExceptionFilter {
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response Structure
|
||||
### Cấu Trúc Phản Hồi Lỗi
|
||||
```typescript
|
||||
interface ErrorResponseBody {
|
||||
statusCode: number;
|
||||
errorCode: ErrorCode; // Enum with values like VALIDATION_FAILED, NOT_FOUND, etc.
|
||||
errorCode: ErrorCode; // Enum với các giá trị như VALIDATION_FAILED, NOT_FOUND, v.v.
|
||||
message: string;
|
||||
details?: Record<string, unknown>;
|
||||
correlationId?: string;
|
||||
@@ -428,7 +428,7 @@ interface ErrorResponseBody {
|
||||
}
|
||||
```
|
||||
|
||||
### Domain Exceptions
|
||||
### Ngoại Lệ Domain
|
||||
```typescript
|
||||
export class DomainException extends HttpException {
|
||||
constructor(
|
||||
@@ -441,7 +441,7 @@ export class DomainException extends HttpException {
|
||||
}
|
||||
}
|
||||
|
||||
// Specific exceptions:
|
||||
// Các ngoại lệ cụ thể:
|
||||
export class NotFoundException extends DomainException { ... }
|
||||
export class ValidationException extends DomainException { ... }
|
||||
export class ConflictException extends DomainException { ... }
|
||||
@@ -451,51 +451,51 @@ export class ForbiddenException extends DomainException { ... }
|
||||
|
||||
---
|
||||
|
||||
## 7. SECURITY & GUARDS
|
||||
## 7. BẢO MẬT & GUARD
|
||||
|
||||
### Role-Based Access Control (RBAC)
|
||||
**Location**: `apps/api/src/modules/auth/presentation/decorators/`
|
||||
### Kiểm Soát Truy Cập Dựa Trên Vai Trò (RBAC)
|
||||
**Vị trí**: `apps/api/src/modules/auth/presentation/decorators/`
|
||||
|
||||
#### Pattern:
|
||||
#### Mẫu:
|
||||
```typescript
|
||||
@Controller('admin')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
@Roles('ADMIN')
|
||||
export class AdminController {
|
||||
// All endpoints require ADMIN role
|
||||
// Tất cả endpoint yêu cầu vai trò ADMIN
|
||||
}
|
||||
```
|
||||
|
||||
#### Roles Guard Flow:
|
||||
1. `JwtAuthGuard` - Validates JWT token
|
||||
2. `RolesGuard` - Checks `@Roles()` decorator against user.role
|
||||
3. Both decorators from `@modules/auth`
|
||||
#### 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`
|
||||
|
||||
### Rate Limiting
|
||||
**Setup**: ThrottlerModule + ThrottlerBehindProxyGuard
|
||||
- Default: 60 requests per 60 seconds per IP
|
||||
- Auth endpoints: 10 requests per 60 seconds
|
||||
- Payment callbacks: 20 requests per 60 seconds
|
||||
### 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. DDD LAYER STRUCTURE
|
||||
## 8. CẤU TRÚC TẦNG DDD
|
||||
|
||||
### Architectural Layers
|
||||
### Các Tầng Kiến Trúc
|
||||
|
||||
```
|
||||
Presentation Layer (Controllers)
|
||||
↓ (DTO validation)
|
||||
Application Layer (Commands/Queries/Handlers/Listeners)
|
||||
Tầng Trình Bày (Controllers)
|
||||
↓ (Xác thực DTO)
|
||||
Tầng Ứng Dụng (Commands/Queries/Handlers/Listeners)
|
||||
↓ (Command/Query)
|
||||
Domain Layer (Events, Interfaces, Business Rules)
|
||||
↓ (Repository calls, Event publishing)
|
||||
Infrastructure Layer (Prisma, Database)
|
||||
Tầng Domain (Events, Interfaces, Business Rules)
|
||||
↓ (Gọi Repository, Phát hành Event)
|
||||
Tầng Cơ Sở Hạ Tầng (Prisma, Database)
|
||||
```
|
||||
|
||||
### Command Handler Pattern
|
||||
### Mẫu Command Handler
|
||||
```typescript
|
||||
// 1. Command (DTO-like)
|
||||
// 1. Command (giống DTO)
|
||||
export class BanUserCommand {
|
||||
constructor(
|
||||
public readonly userId: string,
|
||||
@@ -505,7 +505,7 @@ export class BanUserCommand {
|
||||
) {}
|
||||
}
|
||||
|
||||
// 2. Handler (Business Logic + Event Publishing)
|
||||
// 2. Handler (Business Logic + Phát hành Event)
|
||||
@CommandHandler(BanUserCommand)
|
||||
export class BanUserHandler implements ICommandHandler<BanUserCommand> {
|
||||
constructor(
|
||||
@@ -521,7 +521,7 @@ export class BanUserHandler implements ICommandHandler<BanUserCommand> {
|
||||
user.deactivate();
|
||||
await this.userRepo.update(user);
|
||||
|
||||
// Publish event
|
||||
// Phát hành event
|
||||
this.eventBus.publish(
|
||||
new UserBannedEvent(user.id, command.adminId, command.reason)
|
||||
);
|
||||
@@ -530,7 +530,7 @@ export class BanUserHandler implements ICommandHandler<BanUserCommand> {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Event (Side-effect Trigger)
|
||||
// 3. Event (Kích hoạt Hiệu Ứng Phụ)
|
||||
export class UserBannedEvent implements DomainEvent {
|
||||
readonly eventName = 'user.banned';
|
||||
readonly occurredAt = new Date();
|
||||
@@ -542,22 +542,22 @@ export class UserBannedEvent implements DomainEvent {
|
||||
) {}
|
||||
}
|
||||
|
||||
// 4. Listener (Side Effects - triggered by Event)
|
||||
// 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> {
|
||||
// Send notification, update related data, etc.
|
||||
// Gửi thông báo, cập nhật dữ liệu liên quan, v.v.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Query Handler Pattern
|
||||
### Mẫu Query Handler
|
||||
```typescript
|
||||
// Query (read operation definition)
|
||||
// Query (định nghĩa thao tác đọc)
|
||||
export class GetDashboardStatsQuery {}
|
||||
|
||||
// Handler (fetch & return data)
|
||||
// Handler (lấy & trả về dữ liệu)
|
||||
@QueryHandler(GetDashboardStatsQuery)
|
||||
export class GetDashboardStatsHandler implements IQueryHandler<GetDashboardStatsQuery> {
|
||||
constructor(
|
||||
@@ -570,29 +570,29 @@ export class GetDashboardStatsHandler implements IQueryHandler<GetDashboardStats
|
||||
}
|
||||
```
|
||||
|
||||
### Repository Pattern
|
||||
### Mẫu Repository
|
||||
```typescript
|
||||
// Domain interface (no implementation details)
|
||||
// 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>;
|
||||
// ... more methods
|
||||
// ... thêm phương thức
|
||||
}
|
||||
|
||||
// Infrastructure implementation (Prisma-specific)
|
||||
// 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> {
|
||||
// Prisma queries here
|
||||
// Truy vấn Prisma ở đây
|
||||
}
|
||||
}
|
||||
|
||||
// DI Token
|
||||
// Token DI
|
||||
export const ADMIN_QUERY_REPOSITORY = Symbol('ADMIN_QUERY_REPOSITORY');
|
||||
|
||||
// Module registration
|
||||
// Đăng ký mô-đun
|
||||
@Module({
|
||||
providers: [
|
||||
{ provide: ADMIN_QUERY_REPOSITORY, useClass: PrismaAdminQueryRepository },
|
||||
@@ -603,9 +603,9 @@ export class AdminModule {}
|
||||
|
||||
---
|
||||
|
||||
## 9. MODULE BOOTSTRAP
|
||||
## 9. BOOTSTRAP MÔ-ĐUN
|
||||
|
||||
### AdminModule Setup
|
||||
### Thiết Lập AdminModule
|
||||
**File**: `apps/api/src/modules/admin/admin.module.ts`
|
||||
|
||||
```typescript
|
||||
@@ -617,8 +617,8 @@ export class AdminModule {}
|
||||
{ provide: ADMIN_QUERY_REPOSITORY, useClass: PrismaAdminQueryRepository },
|
||||
|
||||
// CQRS Handlers
|
||||
...CommandHandlers, // 8 command handlers
|
||||
...QueryHandlers, // 6 query handlers
|
||||
...CommandHandlers, // 8 command handler
|
||||
...QueryHandlers, // 6 query handler
|
||||
|
||||
// Event Listeners
|
||||
UserBannedListener,
|
||||
@@ -628,17 +628,17 @@ export class AdminModule {}
|
||||
export class AdminModule {}
|
||||
```
|
||||
|
||||
### Global Setup
|
||||
### Thiết Lập Toàn Cục
|
||||
**File**: `apps/api/src/app.module.ts`
|
||||
|
||||
```typescript
|
||||
@Module({
|
||||
imports: [
|
||||
SentryModule.forRoot(),
|
||||
CqrsModule.forRoot(), // ← CQRS with Event Bus
|
||||
CqrsModule.forRoot(), // ← CQRS với Event Bus
|
||||
ScheduleModule.forRoot(),
|
||||
ThrottlerModule.forRoot(...), // ← Rate limiting
|
||||
// ... other modules including AdminModule
|
||||
ThrottlerModule.forRoot(...), // ← Giới hạn tốc độ
|
||||
// ... các mô-đun khác bao gồm AdminModule
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@@ -665,77 +665,77 @@ export class AppModule implements NestModule {
|
||||
|
||||
---
|
||||
|
||||
## 10. EXISTING INTERCEPTOR PATTERNS
|
||||
## 10. CÁC MẪU INTERCEPTOR HIỆN CÓ
|
||||
|
||||
### HttpMetricsInterceptor
|
||||
**Location**: `@modules/metrics`
|
||||
**Vị trí**: `@modules/metrics`
|
||||
|
||||
- Injected globally via `APP_INTERCEPTOR`
|
||||
- Tracks HTTP request/response metrics
|
||||
- Could serve as a template for audit logging interceptor
|
||||
- Đượ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
|
||||
**Location**: `@modules/shared/infrastructure/middleware/csrf.middleware`
|
||||
**Vị trí**: `@modules/shared/infrastructure/middleware/csrf.middleware`
|
||||
|
||||
- Double-submit cookie pattern
|
||||
- Validates on state-changing methods
|
||||
- Provides model for request context enhancement
|
||||
- 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. SUMMARY FOR AUDIT LOGGING IMPLEMENTATION
|
||||
## 11. TÓM TẮT CHO TRIỂN KHAI GHI LOG KIỂM TOÁN
|
||||
|
||||
### What's Already in Place ✅
|
||||
1. **Event-driven architecture** - Commands publish events, listeners handle side effects
|
||||
2. **Admin identity capture** - All admin actions have `adminId` in commands
|
||||
3. **Logger service** - Pino-based with PII redaction
|
||||
4. **Exception handling** - Global filter + DomainException hierarchy
|
||||
5. **RBAC** - @Roles('ADMIN') guard in place
|
||||
6. **Module bootstrap** - Clear DI pattern ready for audit repository injection
|
||||
7. **DTO validation** - All inputs validated via class-validator
|
||||
### 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
|
||||
|
||||
### What Needs to Be Built 🚀
|
||||
1. **AuditLog Prisma Model** - Store in database
|
||||
2. **AuditLoggingInterceptor** - Capture HTTP context (IP, timestamp, endpoint)
|
||||
3. **AuditEvent Domain Event** - Extend domain events for audit purposes
|
||||
4. **AuditLoggingListener** - Event listener that persists to AuditLog
|
||||
5. **AuditLog Repository** - CRUD operations for AuditLog
|
||||
6. **Query Handler** - Retrieve audit logs with filters (date range, admin, action type)
|
||||
7. **Controller Endpoint** - GET /admin/audit-logs for viewing audit trail
|
||||
### 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
|
||||
|
||||
### Key Integration Points
|
||||
1. Commands already pass `adminId` → Use in AuditLoggingListener
|
||||
2. Domain events already published → Hook audit listener to relevant events
|
||||
3. HTTPContext (IP, user-agent, etc.) → Capture in interceptor
|
||||
4. Logger service available → Use for structured logging
|
||||
5. Repository pattern established → Follow for AuditLog repository
|
||||
### 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
|
||||
|
||||
### Recommended Audit Fields
|
||||
- `id` (primary key)
|
||||
- `adminId` (who performed action)
|
||||
- `adminName` (for denormalization)
|
||||
- `action` (e.g., 'user.banned', 'listing.approved')
|
||||
- `resourceType` (e.g., 'user', 'listing')
|
||||
- `resourceId` (what was affected)
|
||||
- `changes` (JSON of what changed - before/after)
|
||||
- `reason` (from DTO if provided)
|
||||
- `ipAddress` (from request)
|
||||
- `userAgent` (from request)
|
||||
- `status` (success/failure)
|
||||
### 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` (if failed)
|
||||
- `duration` (milliseconds)
|
||||
- `createdAt` (timestamp)
|
||||
- `errorMessage` (nếu thất bại)
|
||||
- `duration` (mili giây)
|
||||
- `createdAt` (dấu thời gian)
|
||||
|
||||
---
|
||||
|
||||
## Key Files Reference
|
||||
## Tham Chiếu Các File Chính
|
||||
|
||||
### Controllers
|
||||
- `presentation/controllers/admin.controller.ts` - Main admin operations
|
||||
- `presentation/controllers/admin-moderation.controller.ts` - Moderation & KYC
|
||||
- `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 (Action Points)
|
||||
### 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`
|
||||
@@ -745,35 +745,34 @@ export class AppModule implements NestModule {
|
||||
- `application/commands/update-user-status/update-user-status.handler.ts`
|
||||
- `application/commands/bulk-moderate-listings/bulk-moderate-listings.handler.ts`
|
||||
|
||||
### Event Listeners (Where to Hook Audit)
|
||||
### Event Listeners (Nơi Móc Kiểm Toán)
|
||||
- `application/listeners/user-banned.listener.ts`
|
||||
- `application/listeners/user-deactivated.listener.ts`
|
||||
|
||||
### Infrastructure
|
||||
### Cơ Sở Hạ Tầng
|
||||
- `infrastructure/repositories/prisma-admin-query.repository.ts`
|
||||
- `infrastructure/repositories/admin-stats.queries.ts`
|
||||
- `infrastructure/repositories/admin-user.queries.ts`
|
||||
|
||||
### Shared Resources
|
||||
### 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 lines, no audit model yet)
|
||||
- Client type safety guaranteed
|
||||
- Schema: `prisma/schema.prisma` (602 dòng, chưa có model kiểm toán)
|
||||
- Đảm bảo type safety của Prisma Client
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. ✅ **Schema Design** - Create AuditLog model in Prisma
|
||||
2. ✅ **Repository Pattern** - Create AuditLogRepository with interface
|
||||
3. ✅ **Audit Events** - Create domain events for audit purposes
|
||||
4. ✅ **Event Listener** - Create AuditLoggingListener to persist events
|
||||
5. ✅ **Interceptor** - Capture HTTP context (optional enhancement)
|
||||
6. ✅ **Query Handler** - Create query/handler for retrieving audit logs
|
||||
7. ✅ **Controller Endpoint** - Add GET /admin/audit-logs endpoint
|
||||
8. ✅ **Tests** - Unit and integration tests
|
||||
9. ✅ **Documentation** - API docs in Swagger
|
||||
## 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
|
||||
|
||||
Reference in New Issue
Block a user