1. Bussiness | Logic
Traffic Tool
  • Traffic Tool Docs
    • Tổng Quan
    • project
      • admin
        • Hướng dẫn sử dung
          • Đăng Nhập & Xác Thực Hai Bước
          • Thống Kê Hệ Thống
          • Thống Kê Hàng Ngày
          • Tìm Kiếm Tự Động
          • Trung Tâm Báo Cáo
          • Quản Lý Tài Khoản
          • Quản Lý Server
          • Quản Lý Proxy
          • Quản Lý Gói Proxy
          • Quản Lý Nhà Cung Cấp Proxy
          • Quản Lý Backup Proxy
          • Quản Lý Tài Khoản Google
      • api + tool
        • Hướng dẫn sử dụng
          • User Guide
          • Setup Guide
        • Bussiness | Logic
          • 1. Thống kê hàng ngày (Daily Statistics)
          • 2. Thống kê hệ thống (System Statistics)
          • 3. Tìm kiếm tự động (Automated Discovery)
          • 4. Trung tâm Báo cáo (Report Center)
          • 5. Quản lý tài khoản (Account Management)
          • 6. Quản lý Hệ thống (System Management)
          • 7. Quản lý tài khoản Google (Google Account Management)
        • Architecture
          • Database Schema
          • System Architecture
          • Code Structure
          • Logs and Monitoring
          • Environment & Configuration
        • Deployment
          • Local
          • Staging
          • Product
  • Traffic Backend API
    • 🔑 Identity & Session
      • Đăng nhập hệ thống (Login)
      • Đăng ký tài khoản mới (Public)
      • Lấy danh sách người dùng (Phân trang)
      • Admin tạo người dùng mới
      • Lấy thông tin cá nhân hiện tại
      • Khởi tạo bảo mật 2FA
      • Xác thực mã OTP
      • Chi tiết người dùng theo ID
      • Cập nhật thông tin người dùng
      • Xóa tài khoản người dùng
      • Đổi mật khẩu
      • Kiểm tra Cấu hình Thông báo Telegram Toàn hệ thống
      • Bật/Tắt Thông báo Telegram Toàn cục
    • 📁 Campaign Management
      • Danh sách Chiến dịch Toàn cầu
      • Khởi tạo Chiến dịch Mới
      • Chi tiết Chiến dịch
      • Cập nhật Chiến dịch
      • Xóa Chiến dịch
      • Tắt/Mở Chiến dịch (Hàng loạt)
      • Dữ liệu Hình mẫu SEO (Negative SEO)
    • ⚙️ Project Mechanics
      • Danh sách Dự án (Projects List)
      • Khởi tạo Kịch bản Mô phỏng
      • Lấy chi tiết cấu hình Dự án
      • Cập nhật Kịch bản chạy
      • Xóa Dự án
      • Chỉnh sửa Hàng loạt (Bulk)
      • Bật/Tắt Dự án
    • 👤 Profile Management
      • Danh sách Vân tay số (Profiles Data)
      • Tạo Hồ sơ Đơn lẻ (Tạo Vân tay mới)
      • Nhập kho Tài khoản Email (Bulk Import)
      • Chi tiết Session/Cookies
      • Chỉnh sửa Hồ sơ/Ghi chú
      • Xóa Vân tay số và Dữ liệu Local
      • Mở khóa Captcha/Trạng thái Blocked
      • Tra cứu Kho Profile Khả dụng
      • Báo cáo Sức khỏe Kho Tài Khoản
    • 🦾 Worker Interface
      • Đăng ký Khởi tạo Node (Handshake)
      • Nhịp Tim Khảo Sát Tình Trạng (Heartbeat)
      • Nhận Cấu hình Bypass & Hệ thống
      • Kéo (PULL) Nhiệm vụ Traffic SEO
      • Báo cáo Sự cố Node (Crash Report)
    • 📊 Report: Execution
      • Kéo Công việc Báo cáo (Worker Pull)
      • Quản lý Giám sát Nhiệm vụ (Task Dashboard)
      • Bắn Lại Báo Cáo Thất Bại (Manual Retry)
    • 📊 Report: Discovery
      • Danh sách Tên miền Chờ Xử Lý (Discovery Pool)
      • Nhập Mục Tiêu Thủ Công (Manual Insert)
      • Kích hoạt Heuristic Scanner (Cào tự động)
      • Chi tiết Bằng Chứng (Evidence Data)
      • Dán nhãn Vi Phạm / Cập nhật Screenshot
      • Loại Bỏ Mục Tiêu
      • Duyệt Yêu Cầu (Approve to Execution)
    • 📊 Report: Platforms Configuration
      • Truy vấn Danh sách Nền tảng Đối tác (Vendor)
      • Tạo Nền tảng Vendor Báo cáo Mới
      • Bật/Tắt Trang báo cáo theo Loại
      • Cập nhật Metadata Trang
      • Xóa Trang Báo Cáo
      • Lấy Cài đặt LLM cho Vendor cụ thể
      • Cập nhật Cài đặt Tạo mẫu LLM
      • Lấy Cấu hình Schema Biểu mẫu cho Vendor
      • Cập nhật Yêu cầu Trường Biểu mẫu
    • 📊 Report: Email Automation
      • Danh sách SMTP/Mailer Server
      • Thêm kết nối SMTP Mới
      • Nhật ký Nhiệm vụ Gửi Mail (Email Log)
      • Phân tích Tỉ lệ Chuyển đổi (Email Delivery Analytics)
    • 🌐 Global Proxies
      • Danh sách Kho Proxy Phân trang (Proxy Pool)
      • Thêm mới Tuyến IP (Bulk Import)
      • Cập nhật Thông tin máy chủ Proxy
      • Xóa Proxy (Thu hồi tài nguyên)
      • Kích hoạt Xoay vòng (Rotate IP) Cưỡng bức
    • 📈 System Intelligence
      • Báo cáo Luồng Bảo mật Hợp nhất (Colossal Report)
      • Bản Đồ Lưu Lượng GeoIP (Heatmap)
    • 🪝 Webhook Integrations
      • Callback Hoàn tất Gói Lưu lượng SEO (Traffic Node)
      • Callback Thông báo Hoàn tất Bắn Report AI
    • Schemas
      • AppError
      • LoginRequest
      • UserResponseDto
      • UserModel
      • CreateUserRequest
      • UpdateUserRequest
      • PagingInfo
      • ProfileModel
      • CreateProfileRequest
      • BulkImportProfileReq
      • UpdateProfileRequest
      • CampaignModel
      • CampaignConfigs
      • CreateCampaignRequest
      • UpdateCampaignRequest
      • BulkUpdateCampaignStatusReq
      • NegativeSeoData
      • CreateProjectRequest
      • UpdateProjectRequest
      • ProjectModel
      • BulkUpdateProjectRequest
      • ProjectAttribute
      • WorkerHandshakeRequest
      • WorkerHandshakeResponse
      • WorkerHeartbeatPayload
      • WorkerGlobalSettings
      • TaskPullRequest
      • WorkerFatalLog
      • TaskModel
      • TaskTrafficType
      • TaskUpdateDto
      • ReportTaskModel
      • ReportTaskResult
      • PCReportResponse
      • SummaryStats
      • DailyStats
      • BrandStats
      • ProfileStats
      • ReportTaskItem
      • ReportSiteItem
      • CreateReportSiteRequest
      • GPTConfig
      • ResponseConfig
      • FieldConfig
      • ReportDomainsResponse
      • ReportDomainItem
      • AddReportDomainRequest
      • UpdateReportDomainRequest
      • BulkUpdateDashboardRequest
      • ProxyModel
      • CreateProxyRequest
      • UpdateProxyRequest
      • SMTPServerConfig
      • EmailTaskLog
      • GeoLocation
  • Traffic Tools V2 API
    • Tasks
      • Lọc và Truy vấn Nhật ký Phiên Duyệt lẻ
      • Get all tasks
      • Nạp Kết Quả Chạy của Puppeteer (Update Callback)
      • Xóa toàn bộ Tasks lẻ
      • Get all tasks
      • Xóa một Task (ID)
    • Group task
      • Đẩy nhóm công việc Traffic theo lô (Batch Pipeline Task Creation)
      • Lấy sơ đồ trạng thái hàng đợi Group Tasks
      • Endpoint /api/task-traffics/
      • Clear/Flush Hàng đợi Group Task (Kill Queue)
      • Endpoint /api/task-traffics/
      • Xóa 1 nhóm nhiệm vụ (Theo ObjectID Mongoose)
      • Parse chuỗi Proxy
    • Test
      • Kiểm tra Proxy hoạt động ngầm
      • Endpoint /api/test
      • Kho Fingerprint Thử Nghiệm
      • Endpoint /api/test/account-stats
      • Endpoint /api/test/session-recommendation
      • Endpoint /api/test/check-browser
      • Endpoint /api/test/clear-browser-sessions
    • Proxies
      • Lấy kho Proxy server
      • Khai báo Pool Tuyến Proxy mới
      • Get all proxies
      • Get all proxies
      • Xóa toàn bộ Proxy
      • Get all proxies
      • Xóa Proxy theo ID
      • Reset lại số đếm tiến trình (Cron Proxy Thread)
    • Report Platforms
      • Submit report to multiple platforms
      • Submit report to specific platform
      • Get available platforms
      • Get platform statistics
      • Health check
    • Reports
      • Create a new report
      • Create a new report
      • Get report by ID
      • Get report by ID
      • Get report statistics
      • Get report groups
      • Get report groups
      • Get report group
      • Add report to group
      • Bulk add reports to group
      • Get report group statistics
      • Get analytics data
      • Get platform success rates
      • Get reports by platform
      • Get reports by group
      • Create test reports in bulk
    • Test Report
      • Test Microsoft report submission
      • Test AdGuard report submission
      • Test Google Ads report submission
      • Test Spamhaus report submission
      • Test ESET report submission
      • Test multi-platform report submission
      • Get available platforms
    • ESET Test
      • Simple test
      • Minimal POST test
      • Generate ESET report content
      • Get sample test data
      • Test form filling
      • Test full form submission
    • Spamhaus Test
      • Simple test
      • Minimal POST test
      • Generate Spamhaus report content
      • Get sample test data
      • Test Spamhaus form filling
      • Test full Spamhaus form submission
    • Report Scheduler
      • Get report scheduler statistics
      • Process ad detection result
      • Submit a scheduled report
      • Start report scheduler
      • Stop report scheduler
      • Get scheduler status
      • Process scheduled reports
      • Test browser session (non-headless)
      • Test report submission (non-headless)
    • Comprehensive Analytics
      • Get comprehensive dashboard analytics
      • Get overview statistics
      • Get brand-wise statistics
      • Get daily statistics
      • Get platform success rates
      • Get account performance
      • Get summary report
    • Search
      • Ra lệnh Máy Lọc Dữ Liệu TOP SEO Hàng Loại (Scraper Engine)
      • Đếm tổng số dòng tìm kiếm (Length)
    • Report Tasks
      • Tạo chiến dịch Report (Auto Spawn 14 tasks)
      • Tạo chiến dịch Report Form hàng loạt (Bulk Abuse Report)
      • Create Direct Report (Từ API hệ thống thay vì Frontend)
      • Tạo Nhiệm Vụ Sinh Email Khiếu nại (Automated Bulk Mail Spam)
      • Worker Pull - Lấy task Báo cáo đang Pending
      • Worker Trả kết quả (Report Status)
      • Danh mục Platfoms & Providers
      • Dashboard Statistic Báo Cáo
      • Force Run Cronjob
    • Monitor
      • Xuất dữ liệu đo đếm Hardware (Telemetry OS Watchdog)
    • Schemas
      • CreateTask
      • UpdateTask
      • CreateTaskGroupRequest
      • CreateProxy
      • CreateSearch
      • ReportTaskCreation
      • CreateEmail
      • CreateMultipleSearchRequest
      • CreateMultipleReportRequest
      • UpdateReportStatusRequest
      • TestProxyRequest
  1. Bussiness | Logic

5. Quản lý tài khoản (Account Management)

Quản Lý Tài Khoản — Technical Documentation#

Module: user-management
Frontend: admin/src/views/user-management/
Backend: api/internal/user/ và api/internal/activity_log/
Database: MySQL (users, activity_logs tables)
Route prefix API: /api/users/

I. Tổng quan module#

Module Quản Lý Tài Khoản (User Management) phụ trách:
1.
Quản lý tài khoản người dùng — CRUD user, phân quyền, xác thực 2 lớp (TOTP).
2.
Quản lý hoạt động — Audit log ghi lại toàn bộ hành động của từng user trong hệ thống.
Module bao gồm 2 tab chính được điều hướng qua RouterLink (route-based tab):
TabRoute nameView component
Quản lý tài khoảnuser_management.userusers/UserView.vue
Quản lý hoạt độnguser_management.activeactivities/ActivityView.vue
Tab switching dùng Vue Router RouterLink với active-class="active", không dùng v-show/v-if — mỗi tab là một route con riêng biệt. Transition hiệu ứng fade-transform được áp dụng qua <Transition>.

II. Kiến trúc component#

IndexView.vue                  ← Tab navigation (2 RouterLinks)
├── users/
│   ├── UserView.vue           ← Trang Quản lý tài khoản
│   ├── components/
│   │   ├── DataTable.vue      ← Bảng danh sách users
│   │   ├── CreateModal.vue    ← Modal tạo user mới
│   │   ├── UpdateModal.vue    ← Modal sửa thông tin user
│   │   └── ChangePasswordModal.vue ← Modal đổi mật khẩu
│   ├── composables/
│   │   ├── useDataTable.ts    ← Cấu hình columns, actions, delete handler
│   │   └── useFormData.ts     ← Form data (create + edit + change password)
│   └── stores/
│       └── useUserStore.ts    ← Pinia store: fetchData, pagination, modal states
│
└── activities/
    ├── ActivityView.vue       ← Trang Quản lý hoạt động
    ├── components/
    │   └── DataTable.vue      ← Bảng audit log (grouped by user)
    ├── composables/
    │   └── useDataTable.ts    ← Columns + groupedData computed
    └── stores/
        └── useActivityStore.ts ← Pinia store: fetchData, pagination

III. Tab Quản lý tài khoản#

UserView.vue — Entry point#

Lifecycle:
onBeforeMount → userStore.resetUrlParams() (sync URL params) → userStore.fetchData()
onUnmounted → userStore.$reset() (reset toàn bộ state về default)
Filter: NInput bind với userStore.urlParams.keywords, debounce 500ms trước khi gọi onChangePage(1).
Lưu ý: Filter theo is_active (trạng thái) đã được comment out — field is_active không được sử dụng hiện tại dù vẫn còn trong UrlSearchParams.

useUserStore (Pinia)#

fetchData():
1.
Set isFetching = true.
2.
Gọi apiUsers.getList(urlParams).
3.
Nếu page > 1 và data.length === 0 → tự động lùi về page - 1 (xử lý trường hợp xóa hết item trong trang cuối).
4.
Lưu vào listData.
5.
Set isFetching = false.
resetUrlParams(): Dùng zod để parse và validate page/limit từ URL:
Điều này đảm bảo URL params không bao giờ có giá trị không hợp lệ (string, âm, NaN...).
Modal states:
editId (number | null) — Lưu ID của user đang được sửa/đổi mật khẩu. Được set trước khi mở modal tương ứng.

DataTable columns (useDataTable.ts)#

ColumnKeyMô tả
STT—renderIndex(page, limit, index) — số thứ tự theo trang
Tên người dùngnameSort client-side bằng localeCompare
EmailemailHiển thị obfuscated — chỉ lộ 2 ký tự đầu + 2 ký tự cuối
Ngày tạocreated_atFormat bằng $d(date, 'dateTime') (i18n)
Tuỳ chọn—3 action buttons
Email obfuscation logic:
3 Action buttons trong cột Tuỳ chọn:
IconTooltipAction
icon-passworddd (màu #23B7E5)"Đổi mật khẩu"Set editId, mở isShowChangePasswordModal
icon-edit—Set editId, mở isShowUpdateModal
icon-trash—Dialog xác nhận → apiUsers.delete(row.id)
Điều kiện delete: Nút xóa chỉ render nếu authStore.profile?.id !== row.id — user không thể tự xóa tài khoản của mình.

IV. Các Modal#

CreateModal.vue#

Fields:
name — Tên người dùng (required)
email — Email (required, unique)
password — Mật khẩu (required)
Flow:
1.
Validate form qua formRef.value?.validate().
2.
Gọi apiUsers.create(formData).
3.
Thành công: Reset form → emit success → đóng modal.
4.
Thất bại: showMessageError(error) — hiển thị error từ response.
Reset logic: Form được reset cả khi submit thành công AND khi modal đóng (watch isShow).

UpdateModal.vue#

Chỉ cho phép sửa name — field email đã bị comment out trong template:
Load data khi mở modal:
1.
onBeforeMount → gọi fetchDataDetail() nếu có id.
2.
Watch isShow → nếu mở (true) và có id → gọi fetchDataDetail().
3.
Watch props.id → nếu id thay đổi khi modal đang mở → reload.
fetchDataDetail():
Gọi apiUsers.getDetail(props.id).
Gán formData.name = data.name, formData.email = data.email.
Set isFetching = true/false → form bị disabled trong lúc load.
Submit: apiUsers.update(props.id, formData) — PUT request với {name}.

ChangePasswordModal.vue#

1 field duy nhất: password (new password, required).
Validation rule:
Cũng load chi tiết user khi mở (fetchDataDetail) — nhưng kết quả chỉ đọc vào dataDetail (không dùng hiển thị gì thêm trong form), có thể dùng để xác nhận user còn tồn tại trước khi đổi mật khẩu.
Submit: apiUsers.changePassword(props.id, formData) → PUT /api/users/change-password/:id.

V. Backend User Module#

Data model — MySQL users table#

Table name: users
Lưu ý quan trọng:
Password được lưu dưới dạng bcrypt hash — không bao giờ lưu plain text.
SecretKey là secret key base32 dùng cho TOTP (Google Authenticator compatible).
DeviceToken và IsLoggedIn dùng để quản lý session thiết bị.
LastActiveAt tự động cập nhật qua GORM autoUpdateTime.

RoleType#

ValueNameQuyền
0adminToàn quyền
1userQuyền hạn chế
API response trả về role_name (string) thay vì số nguyên: User.RoleType(role).String().

UserResponse struct (API response)#

Password, SecretKey, DeviceToken, IsLoggedIn, LastActiveAt không bao giờ xuất hiện trong response API.

VI. API Endpoints — User#

Base route: /api/users/ (Fiber Router, có middleware xác thực JWT)
MethodPathHandlerMô tả
GET/GetListWithPaginationDanh sách users phân trang
GET/get-allGetListToàn bộ users (không phân trang)
GET/profileProfileThông tin user đang đăng nhập
GET/generate-totpGenerateTOTPTạo TOTP secret + URI
GET/:idFindByIdChi tiết một user
POST/CreateTạo user mới
POST/verify-totpVerifyTOTPXác thực mã OTP
DELETE/:idDeleteXóa user
PUT/:idUpdateCập nhật thông tin user
PUTchange-password/:idChangePasswordĐổi mật khẩu

GetListWithPagination#

Query params:
page       (int, default: 1)
limit      (int, default: 20)
keywords   (string, optional) — tìm kiếm theo tên
email      (string, optional)
username   (string, optional)
is_active  (string, optional: "Y"/"N")
Response chuẩn:
{
  "message": "Lấy dữ liệu thành công",
  "data": {
    "data": [...UserResponse],
    "paginate": {
      "page": 1,
      "limit": 20,
      "total": 29,
      "total_pages": 2
    }
  }
}
Xử lý phân trang:
1.
Parse page và limit từ query → common.Paging struct.
2.
pagingData.Fulfill() — set default nếu thiếu (page=1, limit=20).
3.
Service GetListWithPagination(ctx, params, &pagingData).
4.
Map []*user.User sang []*user.UserResponse (ẩn sensitive fields).

Create#

Body (JSON):
{
  "name": "Tên người dùng",
  "email": "user@example.com",
  "password": "password123",
  "first_name": "",
  "last_name": ""
}
Validation: dto.CreateUser được validate bởi validate.Validate(body).
Email duplicate check: Nếu MySQL trả về duplicate key error → IsDuplicateEmailError(err) → 400 Bad Request: "Email đã tồn tại".
Activity log: Sau khi tạo thành công → activityLogService.LogActivity(c, "Tạo người dùng", "user", user.ID, nil, nil, body).

ChangePassword#

URL param: :id (int)
Body:
{ "password": "newSecurePassword123" }
Validation: dto.ChangePasswordRequest với validate tag.
Service ChangePassword(ctx, parseUserID, &params):
Hash password mới bằng bcrypt.
Update users.password trong MySQL.
Activity log: activityLogService.LogActivity(ctx, "Thay đổi mật khẩu", "user", 0, nil, nil, params) — modelID = 0 (không truyền ID cụ thể).

VII. Xác thực 2 lớp (TOTP)#

GenerateTOTP#

Endpoint: GET /api/users/generate-totp
Flow:
1.
Lấy userID từ JWT token.
2.
Gọi userService.FindById(userID).
3.
userService.GenerateTOTPWithSecret(user):
Tạo TOTP secret key ngẫu nhiên (base32).
Lưu secret_key vào users table.
Tạo TOTP URI format: otpauth://totp/{issuer}:{email}?secret={secret}&issuer={issuer}
4.
Trả về URI → Frontend hiển thị QR code.
Response:
{
  "message": "Tạo mã OTP thành công",
  "data": "otpauth://totp/TrafficTools:user@example.com?secret=BASE32SECRET&issuer=TrafficTools"
}
Activity log: "Tạo mã OTP", model "user".

VerifyTOTP#

Endpoint: POST /api/users/verify-totp
Body: { "totp": "123456" }
Flow:
1.
Lấy userID từ JWT.
2.
userService.VerifyTOTP(userID, body.Totp):
Lấy secret_key từ DB.
Validate mã TOTP dùng thư viện TOTP (thường là pquerna/otp).
Nếu hợp lệ: Update two_factor_authentication = true.
3.
Trả về thành công hoặc 400 "Mã OTP không hợp lệ".

VIII. Tab Quản lý hoạt động — ActivityView#

ActivityView.vue#

onBeforeMount gọi song song:
Filter UI đang bị comment out toàn bộ — không có filter active. Data hiển thị toàn bộ không filter.
Các filter đã định nghĩa trong UrlSearchParams nhưng chưa hoạt động:
description — Lọc theo tên công việc
user_id — Lọc theo người thao tác
from / to — Khoảng thời gian (yyyy-MM-dd)

useActivityStore (Pinia)#

fetchData() gọi apiActivities.getList(urlParams) → GET /api/activity-logs/.
fetchOptionUsers() và fetchOptiopJobs() đều không thực hiện gì (body bị comment out):
Điều này cho thấy chức năng filter theo user và job chưa được implement mặc dù UI đã có sẵn.

IX. ActivityLog Data Model — MySQL activity_logs#

Table name: activity_logs
Column quan trọng:
ColumnVí dụ giá trị
action"Tạo người dùng", "Thay đổi mật khẩu", "Tạo mã OTP"
model_name"user", "project", "campaign"
parameters{"name":"abc","email":"x@x.com","password":"..."} (JSON)
old_value{"name":"old name"}
new_value{"name":"new name"}
ip_address"192.168.1.100"
user_agent"Mozilla/5.0 (Windows NT 10.0; Win64; x64)..."

X. LogActivity — Ghi Audit Log#

Hàm: activityLogService.LogActivity(ctx, action, modelName, modelID, oldValue, newValue, params)
Lấy thông tin từ request:
userID → extract từ JWT token bằng jwt.GetUserFromToken(ctx). Nếu lỗi → userID = 0, lưu user_id = NULL.
ipAddress → ctx.IP()
userAgent → ctx.Get("User-Agent")
Serialization: Cả oldValue, newValue, params đều được serialize thành JSON string qua formatJsonValue():
Lưu ý: Nếu activityLog.LogActivity gặp lỗi khi ghi DB → chỉ log error, không làm fail request chính. Audit logging là best-effort, không blocking.

Tất cả các action được ghi log trong user module#

HandlerActionModelNameModelIDParams
Create"Tạo người dùng""user"user.IDdto.CreateUser (có password!)
ChangePassword"Thay đổi mật khẩu""user"0dto.ChangePasswordRequest
GenerateTOTP"Tạo mã OTP""user"user.IDnil
Security concern: Create handler log cả params bao gồm password text (trước khi hash). Đây là security risk vì password sẽ được lưu (dù là chuỗi đã nhập) trong activity_logs.parameters.

XI. Cơ chế phân trang trong GetListWithPagination (Activity Log)#

Backend repository dùng phân trang theo user thay vì theo dòng:
Điều này có nghĩa:
1 "page" = toàn bộ activities của N users (N = limit), không giới hạn số activity.
Tổng pages dựa trên số user unique, không phải số activity.
Mỗi user có thể có hàng trăm activities, tất cả xuất hiện trong một page.
NULL user_id handling:

XII. Hiển thị Activity Log phía Frontend#

groupedData — Client-side grouping#

useDataTable.ts trong activities không dùng raw data từ store mà group lại theo user bằng computed property:
Lưu ý: Vì backend đã group theo user trước khi trả về, client grouping chủ yếu để gom activities của cùng một user vào cùng một row trong bảng.

Columns bảng Quản lý hoạt động#

ColumnMô tả
STTSố thứ tự
Người thao tácTên user (bold), fallback "Không xác định"
Tổng số hoạt độngNTag màu info, hiển thị totalCount
Chi tiết hoạt độngNCollapse — click để expand danh sách activities

NCollapse trong Chi tiết hoạt động#

Mỗi row mở ra một collapse panel hiển thị list activities:
Date format logic (custom, không dùng $d):

NModal xem JSON properties (hiện disabled)#

DataTable.vue có một NModal kèm NCode để hiển thị JSON của một activity:
Nhưng handleOpenPropertyModal đã bị comment out trong columns, nên modal này không còn được trigger từ UI. Vẫn giữ lại trong code như là tính năng dự phòng.

XIII. API Endpoints — ActivityLog#

Base route: /api/activity-logs/
MethodPathHandlerMô tả
GET/GetListWithPaginationDanh sách activity logs phân trang theo user
Query params:
page    (int, default: 1)
limit   (int, default: 20)
Response:
{
  "message": "...",
  "data": {
    "data": [
      {
        "id": 1,
        "user_id": 3,
        "action": "Tạo người dùng",
        "parameter": {"name": "abc", "email": "x@x.com"},
        "model_name": "user",
        "model_id": 15,
        "old_value": null,
        "new_value": null,
        "ip_address": "192.168.1.1",
        "user_agent": "Mozilla/5.0...",
        "created_at": "2026-03-23T07:00:00Z",
        "user": {
          "id": 3,
          "name": "admin",
          "email": "ad****in"
        }
      }
    ],
    "paginate": {
      "page": 1,
      "limit": 20,
      "total": 8,         // Số user unique, không phải số activity
      "total_pages": 1
    }
  }
}

XIV. Pagination component (CPagination)#

Cả hai tab đều dùng component CPagination (custom):
CPagination là component dùng chung (@/components), wraps NDataTable's native pagination với thêm các xử lý custom.

XV. Cấu trúc thư mục Backend đầy đủ#

api/internal/user/
├── user.go                         ← User struct, UserResponse, RoleType
├── dto/
│   ├── create_user.go              ← CreateUser DTO
│   ├── update_user.go              ← UpdateUser DTO
│   ├── change_password_request.go  ← ChangePasswordRequest DTO
│   ├── verify_totp_request.go      ← VerifyTOTPRequest DTO
│   └── get_list_with_pagination_request.go
├── handler/
│   ├── handler.go                  ← DI, Router setup
│   ├── get_list.go                 ← GET / (no pagination)
│   ├── get_list_with_pagination.go ← GET / (with pagination)
│   ├── find_by_id.go               ← GET /:id
│   ├── create.go                   ← POST /
│   ├── update.go                   ← PUT /:id
│   ├── delete.go                   ← DELETE /:id
│   ├── change_password.go          ← PUT /change-password/:id
│   ├── login.go                    ← POST /login (auth endpoint)
│   ├── profile.go                  ← GET /profile
│   └── two_factor_authentication.go ← GET /generate-totp, POST /verify-totp
├── repository/
│   └── ...
└── service/
    └── ...

api/internal/activity_log/
├── activity_log.go                 ← ActivityLog struct, ActivityLogResponse
├── dto/
│   └── get_list_pagination_request.go
├── handler/
│   ├── handler.go
│   └── get_list_with_pagination.go
├── repository/
│   ├── repository.go
│   └── get_list_with_pagination.go ← Pagination by unique user logic
└── service/
    ├── service.go
    ├── create_log.go               ← Ghi một record vào DB
    ├── log_activity.go             ← LogActivity() — public API
    └── get_list_with_pagination.go

XVI. Điểm cần lưu ý#

1. Email obfuscation chỉ phía Frontend:
Email trong response từ API vẫn là full email. Obfuscation chỉ xảy ra trong render() function của DataTable column. API /api/users/ trả về email đầy đủ.
2. Filter is_active chưa hoạt động:
urlParams.is_active tồn tại nhưng chưa được truyền vào query và backend cũng chưa xử lý filter này.
3. delete button tự bảo vệ (self-delete prevention):
Nút xóa không render nếu authStore.profile?.id === row.id. Đây là UI-level guard, không phải backend-level guard. Nếu có backend-level check, cần kiểm tra service Delete.
4. Activity log cho Create lưu password:
Create handler truyền nguyên body (dto.CreateUser) vào LogActivity → password được lưu vào activity_logs.parameters. Dù password sau này được hash, giá trị originally-typed password vẫn tồn tại trong audit log.
5. Phân trang Activity Log theo User Unit:
total_pages trong response là số trang user, không phải trang activity. Một page có thể chứa hàng trăm activity records nếu một user có nhiều hành động.
6. Lazy render trong NCollapse:
defaultExpandedNames: [] → tất cả collapse mặc định đóng. Chỉ khi user click "Xem N hoạt động" mới render nội dung bên trong. Tốt về performance khi nhiều user.
7. fetchOptionUsers và fetchOptiopJobs là no-op:
Hai function này được gọi trong onBeforeMount nhưng không làm gì. Nếu filter cần được implement, cần thêm API call vào đây.

XVII. Login Flow chi tiết#

Endpoint: POST /api/auth/login
Handler file: api/internal/user/handler/login.go

Bước thực hiện#

1. Parse body: { email, password }
2. FindByEmail(body.Email)  lấy user từ MySQL
3. Login(user, body, c):
   a. ValidatePassword(user.Password, body.Password)
       bcrypt.CompareHashAndPassword(hash, plaintext)
   b. BEGIN TRANSACTION
   c. generateUniqueDeviceToken(tx, c):
      SHA-256(UUID-v4 + User-Agent + IP) | Max 5 attempts
   d. middleware.HandleLogin(tx, redis, userID, deviceToken, IP)
   e. COMMIT TRANSACTION
   f. jwt.GenerateJWT(user, ENV)  JWT
4. 2FA check:
   - ENV.Disable2FA=true AND AppMode=development  is_2fa=false
   - HasServerPerformanceAccess(name, email)  is_2fa=false
   - Else: is_2fa = user.TwoFactorAuthentication
5. LogActivity("Đăng nhập", "user", user.ID, nil, nil, body)
6. Return: { token, device_token, is_2fa }

Device Token Generation#

Lưu cả trong MySQL (users.device_token) và Redis session. HasServerPerformanceAccess là whitelist bypass 2FA cho service accounts/monitoring users.

XVIII. TOTP chi tiết (gotp library)#

Library: github.com/xlzd/gotp

GenerateTOTPWithSecret#

VerifyTOTP#

Trạng thái 2FA:
two_factorsecret_keyÝ nghĩa
false""Chưa setup 2FA
false"ABC..."Đã gen secret, chưa verify
true"ABC..."2FA hoàn toàn active
TOTP: SHA-1, 6 digits, 30s period. Compatible với Google Authenticator, Authy, Microsoft Authenticator.

XIX. Service layer User methods#

MethodFileMô tả
Create(ctx, user)create.gobcrypt hash password INSERT
FindByEmail(email)find_by_email.goSELECT WHERE email=?
FindById(id)find_by_id.goSELECT WHERE id=?
GetList(ctx)get_list.goSELECT tất cả
GetListWithPagination(ctx, params, paging)get_list_with_pagination.goFilter + phân trang
Update(ctx, id, user)update.goUPDATE name WHERE id=?
Delete(ctx, id)delete.goDELETE WHERE id=?
ChangePassword(ctx, id, params)change_password.gobcrypt hash mới UPDATE
Login(user, body, c)login.gobcrypt verify + device token + JWT
GenerateTOTPWithSecret(user)two_factor_authentication.goGen secret + URI
VerifyTOTP(userId, totp)two_factor_authentication.goVerify + activate 2FA

XX. useFormData 3 form objects#

File: users/composables/useFormData.ts

XXI. Tổng hợp API calls phía Frontend#

MethodEndpointNơi dùng
getList(params)GET /api/users/useUserStore.fetchData()
create(data)POST /api/users/CreateModal.onSubmit()
getDetail(id)GET /api/users/:idUpdateModal, ChangePasswordModal
update(id, data)PUT /api/users/:idUpdateModal.onSubmit()
delete(id)DELETE /api/users/:iduseDataTable.onDeleteRow()
changePassword(id, data)PUT /api/users/change-password/:idChangePasswordModal.onSubmit()
apiActivities.getList(params)GET /api/activity-logs/useActivityStore.fetchData()

XXII. Luồng hoàn chỉnh tạo tài khoản mới#

Admin click "Thêm tài khoản"
   isShowCreateModal = true  CreateModal hiển thị
  
User nhập: name, email, password
   formRef.validate()  POST /api/users/
  
Backend:
  1. dto.CreateUser validate
  2. bcrypt.GenerateFromPassword(password, cost=12)
  3. INSERT INTO users (name, email, password, role=1)
  4. IsDuplicateEmailError  400 "Email đã tồn tại"
  5. LogActivity("Tạo người dùng", body)  INSERT activity_logs
  
Response OK:
   message.success
   reset form  emit success  fetchData() reload
   isShow = false

XXIII. Delete Flow tự bảo vệ#

Click icon trash (chỉ render nếu row.id !== authStore.profile.id)
   window.$dialog.warning("Xác nhận xóa...")
  
User click "Xóa"
   dialog.loading = true
   DELETE /api/users/:id
   Backend: DELETE FROM users WHERE id = ?
  
OK: message.success  fetchData()
Err: showMessageError  dialog.loading = false
Activity logs của user đã xóa vẫn còn trong DB (user_id records còn tồn tại với NULL reference sau khi user bị xóa vì GORM không có CASCADE DELETE mặc định).

XXIV. Tab styling và route navigation#

Container background: #09484a0d (5% opacity teal) với rounded-[48px] pill shape.
<RouterView> dùng <Transition name="fade-transform" mode="out-in"> chuyển tab có hiệu ứng fade.
Route:
/user-management/users         UserView.vue
/user-management/activities    ActivityView.vue

XXV. Tồn đọng và TODO trong codebase#

Filter Activity bị comment out:
fetchOptionUsers/fetchOptiopJobs là no-ops:
Update email bị comment out:
is_active filter chưa hoạt động:
urlParams.is_active tồn tại nhưng không được expose trong UI và backend chưa xử lý.
Security note password trong Activity Log:
Create handler truyền nguyên dto.CreateUser body (bao gồm plain-text password) vào LogActivity. Password này được serialize và lưu vào activity_logs.parameters.
Modified at 2026-03-23 05:18:39
Previous
4. Trung tâm Báo cáo (Report Center)
Next
6. Quản lý Hệ thống (System Management)
Built with