Admin / CMS

admin module
Tính năng: User moderation · Report review · Hashtag trending · Statistics  ·  Auth: UserRoleType = ADMIN
5
Routes
users · ban · report · hashtag · stats
2
Implemented
GET users · PATCH ban
RBAC
Auth system
JWT roles[] + checkRole
2
Guard middleware
adminHandler + checkRole

Proposed: Report Review Flow


User báo cáo bài viết INSERT Report (PENDING) 🔮 AI Content Moderation confidence > 0.9 → auto-hide bài GET /admin/reports?status=PENDING Admin xem nội dung bài vi phạm Quyết định? dismiss Bỏ qua → status=DISMISSED hide Ẩn bài + cảnh cáo user delete Xóa bài + ban user (tùy)

User báo cáo → (tùy chọn) AI auto-filter → vào hàng đợi admin → admin quyết định: bỏ qua / ẩn bài / xóa bài + ban.

Proposed: User Moderation Flow


PATCH /admin/users/:userId action? lock status = LOCKED User không thể đăng nhập temp_ban status = BANNED, bannedUntil = +7 ngày Cron job tự unlock khi hết hạn unlock status = ACTIVE Mở khóa tài khoản

Admin có 3 hành động: khóa vĩnh viễn (lock), ban tạm thời (7 ngày), hoặc mở khóa (unlock).

Implementation Checklist


#TaskChi tiếtStatus
01
Admin guard middleware
adminHandler kiểm tra req.user.roles.includes(ADMIN) từ JWT payload. Đặt trước tất cả admin routes. ⚠️ Lưu ý: đang bị duplicate với checkRole per-route.done
02
PATCH /admin/users/:userId/ban
Ban user
Cập nhật UserStatus thành BANNED qua userManagementService.banUser().done
03
GET /admin/users
Danh sách users
Trả về: id, name, email, username, status, verifiedAt, createdAt, roles[], isVerified. Chưa có filter/search/pagination.done
04
RBAC: JWT roles[]
Tích hợp role vào JWT
Register → auto assign USER. Login/Refresh → load roles vào JWT payload {userId, status, roles[]}.done
05
RBAC: checkRole + checkPermission
checkRole kiểm tra JWT roles. checkPermission query DB (⚠️ cache chưa hoạt động đúng). Cần fix: UnauthorizedExceptionForbiddenException.done
06
Bảng Report
Prisma migration
Model Report { id, reporterId, targetType (POST/USER), targetId, reason, status, adminNote, createdAt }.todo
07
PATCH /admin/reports/:id
Xử lý báo cáo
Route + controller có. Service/repo stub.stub
08
GET /admin/hashtags/trending
Route + controller có. Service/repo stub.stub
09
GET /admin/stats
Route + controller có. Service/repo stub.stub
10
Cron: auto-remove-ban
Job chạy mỗi giờ: tìm users có bannedUntil < now() → update status ACTIVE.todo

Potential Issues


Session
Ban user nhưng họ đang online → refresh token vẫn valid. Cần: blacklist refresh token khi ban, hoặc check UserStatus trong auth middleware mỗi request.
Cascade
Xóa bài vi phạm → cần xử lý: likes, replies, quotes pointing to deleted post. Soft delete safe hơn hard delete.
Audit log
Mọi hành động admin cần log: ai làm, lúc nào, trên target nào. Tạo bảng AdminAuditLog để truy vết.
Super admin
Cần phân biệt: ADMIN (quản lý content) vs SUPER_ADMIN (quản lý cả admins). Enum UserRoleType đã có sẵn.

🔍 Code Review — Bugs & Issues Found


#SeverityIssueChi tiếtStatus
B1blocking
Duplicate admin role check
routes/index.ts + admin.routes.ts
adminHandler ở router level VÀ checkRole(ADMIN) ở mỗi route → check 2 lần. Chọn 1: bỏ adminHandler hoặc bỏ checkRole per-route.todo
B2blocking
Dead import: userRepository
middlewares/admin.ts:3
Import userRepository không còn sử dụng sau khi refactor sang JWT roles. Đã xóa file.done
B3blocking
Dead import: NotFound (AWS SDK)
access-control/middleware/index.ts:6
Import NotFound từ @aws-sdk/client-s3 không liên quan access control. Đã xóa import.done
B4medium
checkPermission cache không đọc
access-control/middleware/index.ts:9-41
Đã fix: đọc cache bằng `redisService.get`, đổi sang dùng `req.user.sub` làm key.done
B5medium
checkRole throw sai error type
access-control/middleware/index.ts:51
Thiếu role → 403 Forbidden. Đã fix dùng ForbiddenException thay UnauthorizedException.done
B6medium
sendInvitation empty if block
circle/service/circle.service.ts:55-64
Đã sửa thành guard clause: if (!role) throw.done
B7medium
sendInvitation không tạo invitation
circle/service/circle.service.ts:40-64
Đã bổ sung gọi circleInvitationRepository.create(...) bằng transaction.done
B8medium
CircleInvitation cursor: Number(after) có thể NaN
circle/repository/circle-invation.repository.ts:45
Cursor dùng int id nhưng after là string. Không validate → Number("abc") = NaN. Nên thêm validation hoặc dùng publicId.todo
B9nit
acceptInvitation dùng req.user.id
circle/controller/circle.controller.ts:63
Các method khác đã chuyển sang req.user.sub. Method này vẫn dùng req.user.id cũ.todo
B10nit
prisma import trực tiếp trong service
circle/service/circle.service.ts:1,86
Đã chuyển sang dùng transactionService.doInTransaction để giữ abstraction layer.done