Commit b5e2e132 authored by ThinhNC's avatar ThinhNC

feat: implement authentication module with registration, login, verification,...

feat: implement authentication module with registration, login, verification, and password recovery workflows
parent 998ad180
NODE_ENV=development
PORT=3000
PORT=9898
TRUST_PROXY=false
# Database Configuration (PostgreSQL / Supabase)
......
export const envConfig = {
nodeEnv: process.env.NODE_ENV || "development",
port: parseInt(process.env.PORT || "3000", 10),
port: parseInt(process.env.PORT || "9898", 10),
trustProxy: process.env.TRUST_PROXY || "false",
database: {
host: process.env.DB_HOST || "localhost",
......
......@@ -45,7 +45,9 @@
}
}
},
"tags": ["Health"],
"tags": [
"Health"
],
"summary": "Kiểm tra liveness của service"
}
},
......@@ -108,7 +110,9 @@
"description": "Hệ thống chưa sẵn sàng, dịch vụ phụ trợ gặp lỗi"
}
},
"tags": ["Health"],
"tags": [
"Health"
],
"summary": "Kiểm tra readiness của service (PostgreSQL & Redis)"
}
},
......@@ -138,7 +142,9 @@
}
}
},
"tags": ["Health"],
"tags": [
"Health"
],
"summary": "Xem thông số metrics hệ thống và hàng đợi"
}
},
......@@ -183,7 +189,9 @@
"description": "Email hoặc mật khẩu không chính xác"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Đăng nhập người dùng",
"requestBody": {
"required": true,
......@@ -239,7 +247,9 @@
}
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Làm mới Access Token"
}
},
......@@ -278,7 +288,9 @@
}
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Đăng xuất"
}
},
......@@ -309,7 +321,9 @@
"description": "Chưa xác thực hoặc token không hợp lệ"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Lấy thông tin người dùng hiện tại"
},
"put": {
......@@ -341,7 +355,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Cập nhật thông tin cá nhân",
"requestBody": {
"required": true,
......@@ -383,7 +399,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Cập nhật một phần thông tin cá nhân",
"requestBody": {
"required": true,
......@@ -472,7 +490,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Xem hạn mức và mức độ sử dụng Quota hiện tại"
}
},
......@@ -512,7 +532,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Tải lên ảnh đại diện (Avatar)",
"requestBody": {
"required": true,
......@@ -520,7 +542,9 @@
"multipart/form-data": {
"schema": {
"type": "object",
"required": ["avatar"],
"required": [
"avatar"
],
"properties": {
"avatar": {
"type": "string",
......@@ -582,7 +606,9 @@
"description": "Không tìm thấy tệp ảnh đại diện"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Tải hoặc hiển thị ảnh đại diện"
}
},
......@@ -617,7 +643,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Đổi mật khẩu tài khoản",
"requestBody": {
"required": true,
......@@ -658,7 +686,9 @@
"description": "Email đã được sử dụng hoặc dữ liệu không hợp lệ"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Đăng ký tài khoản mới",
"requestBody": {
"required": true,
......@@ -700,7 +730,9 @@
"description": "Email không hợp lệ"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Yêu cầu đặt lại mật khẩu",
"requestBody": {
"required": true,
......@@ -708,7 +740,9 @@
"application/json": {
"schema": {
"type": "object",
"required": ["email"],
"required": [
"email"
],
"properties": {
"email": {
"type": "string",
......@@ -750,7 +784,9 @@
"description": "Token không hợp lệ, đã hết hạn hoặc mật khẩu không đúng định dạng"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Đặt lại mật khẩu mới",
"requestBody": {
"required": true,
......@@ -758,7 +794,10 @@
"application/json": {
"schema": {
"type": "object",
"required": ["token", "password"],
"required": [
"token",
"password"
],
"properties": {
"token": {
"type": "string",
......@@ -803,7 +842,9 @@
"description": "Địa chỉ email không hợp lệ"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Gửi lại email xác thực",
"requestBody": {
"required": true,
......@@ -845,7 +886,9 @@
"description": "Token không hợp lệ hoặc đã hết hạn"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Xác thực địa chỉ email",
"requestBody": {
"required": true,
......@@ -853,7 +896,9 @@
"application/json": {
"schema": {
"type": "object",
"required": ["token"],
"required": [
"token"
],
"properties": {
"token": {
"type": "string",
......@@ -897,7 +942,9 @@
"description": "Chưa xác thực hoặc mật khẩu không chính xác"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Yêu cầu vô hiệu hóa tài khoản",
"requestBody": {
"required": true,
......@@ -939,7 +986,9 @@
"description": "Mã xác nhận không hợp lệ, đã hết hạn hoặc tài khoản đã bị vô hiệu hóa"
}
},
"tags": ["Auth"],
"tags": [
"Auth"
],
"summary": "Xác nhận vô hiệu hóa tài khoản",
"requestBody": {
"required": true,
......@@ -1036,7 +1085,9 @@
"description": "Không có quyền truy cập"
}
},
"tags": ["Users"],
"tags": [
"Users"
],
"summary": "Lấy danh sách người dùng"
},
"post": {
......@@ -1081,7 +1132,9 @@
}
}
},
"tags": ["Users"],
"tags": [
"Users"
],
"summary": "Tạo người dùng mới"
}
},
......@@ -1129,7 +1182,9 @@
"description": "Không tìm thấy người dùng"
}
},
"tags": ["Users"],
"tags": [
"Users"
],
"summary": "Lấy thông tin người dùng theo ID"
},
"put": {
......@@ -1182,7 +1237,9 @@
}
}
},
"tags": ["Users"],
"tags": [
"Users"
],
"summary": "Cập nhật thông tin người dùng"
},
"delete": {
......@@ -1223,7 +1280,9 @@
"description": "Không tìm thấy người dùng"
}
},
"tags": ["Users"],
"tags": [
"Users"
],
"summary": "Xóa người dùng"
}
},
......@@ -1252,7 +1311,9 @@
"description": "Không có quyền users.roles.read"
}
},
"tags": ["Users"],
"tags": [
"Users"
],
"summary": "Xem Roles của User"
},
"put": {
......@@ -1285,7 +1346,9 @@
"description": "Không tìm thấy User hoặc Role"
}
},
"tags": ["Users"],
"tags": [
"Users"
],
"summary": "Cập nhật toàn bộ Roles của User",
"requestBody": {
"required": true,
......@@ -1336,7 +1399,9 @@
"description": "Không tìm thấy User hoặc Role"
}
},
"tags": ["Users"],
"tags": [
"Users"
],
"summary": "Gán thêm một Role cho User"
},
"delete": {
......@@ -1378,7 +1443,9 @@
"description": "Không tìm thấy User hoặc Role"
}
},
"tags": ["Users"],
"tags": [
"Users"
],
"summary": "Gỡ một Role khỏi User"
}
},
......@@ -1435,7 +1502,9 @@
"description": "Không có quyền roles.read"
}
},
"tags": ["Roles"],
"tags": [
"Roles"
],
"summary": "Danh sách Roles"
},
"post": {
......@@ -1457,7 +1526,9 @@
"description": "Role slug đã tồn tại"
}
},
"tags": ["Roles"],
"tags": [
"Roles"
],
"summary": "Tạo Role tùy chỉnh",
"requestBody": {
"required": true,
......@@ -1499,7 +1570,9 @@
"description": "Không tìm thấy Role"
}
},
"tags": ["Roles"],
"tags": [
"Roles"
],
"summary": "Chi tiết Role"
},
"patch": {
......@@ -1532,7 +1605,9 @@
"description": "Không tìm thấy Role"
}
},
"tags": ["Roles"],
"tags": [
"Roles"
],
"summary": "Cập nhật Role",
"requestBody": {
"required": true,
......@@ -1575,7 +1650,9 @@
"description": "Không tìm thấy Role"
}
},
"tags": ["Roles"],
"tags": [
"Roles"
],
"summary": "Xóa Role"
}
},
......@@ -1607,7 +1684,9 @@
"description": "Không tìm thấy Role"
}
},
"tags": ["Roles"],
"tags": [
"Roles"
],
"summary": "Xem danh sách Permissions của Role"
},
"put": {
......@@ -1637,7 +1716,9 @@
"description": "Không tìm thấy Role"
}
},
"tags": ["Roles"],
"tags": [
"Roles"
],
"summary": "Gán danh sách Permissions cho Role",
"requestBody": {
"required": true,
......@@ -1676,7 +1757,9 @@
"description": "Không có quyền roles.read"
}
},
"tags": ["Roles"],
"tags": [
"Roles"
],
"summary": "Danh sách Users thuộc Role"
}
},
......@@ -1710,7 +1793,9 @@
"description": "Không có quyền permissions.read"
}
},
"tags": ["Permissions"],
"tags": [
"Permissions"
],
"summary": "Danh mục Permissions hệ thống"
}
},
......@@ -1742,7 +1827,9 @@
"description": "Không tìm thấy Permission"
}
},
"tags": ["Permissions"],
"tags": [
"Permissions"
],
"summary": "Chi tiết Permission"
}
},
......@@ -1776,7 +1863,9 @@
"description": "Không có quyền DASHBOARD_READ"
}
},
"tags": ["Dashboard"],
"tags": [
"Dashboard"
],
"summary": "Thống kê tổng quan hệ thống Crawler"
}
},
......@@ -1819,7 +1908,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Tạo crawl job mới",
"requestBody": {
"required": true,
......@@ -1908,7 +1999,9 @@
}
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Lấy danh sách các crawl jobs"
}
},
......@@ -1950,7 +2043,9 @@
"description": "Không tìm thấy crawl job hoặc không có quyền truy cập"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Lấy thông tin chi tiết một crawl job"
},
"delete": {
......@@ -2001,7 +2096,9 @@
"description": "Không tìm thấy crawl job"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Xóa crawl job"
}
},
......@@ -2050,7 +2147,9 @@
"description": "Không tìm thấy crawl job"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Chạy lại crawl job với cấu hình ban đầu"
}
},
......@@ -2139,7 +2238,9 @@
"description": "Không tìm thấy crawl job"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Xem nhật ký (logs) chi tiết của crawl job"
}
},
......@@ -2176,7 +2277,9 @@
"description": "Không tìm thấy crawl job"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Server-Sent Events (SSE) theo dõi tiến độ Job thời gian thực"
}
},
......@@ -2221,7 +2324,9 @@
"description": "Không tìm thấy crawl job"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Hủy một crawl job đang chạy"
}
},
......@@ -2425,7 +2530,10 @@
"in": "query",
"schema": {
"type": "string",
"enum": ["asc", "desc"],
"enum": [
"asc",
"desc"
],
"default": "asc"
},
"description": "Thứ tự sắp xếp"
......@@ -2486,7 +2594,9 @@
}
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Lấy danh sách các trang đã crawl của job"
}
},
......@@ -2690,7 +2800,10 @@
"in": "query",
"schema": {
"type": "string",
"enum": ["asc", "desc"],
"enum": [
"asc",
"desc"
],
"default": "asc"
},
"description": "Thứ tự sắp xếp"
......@@ -2825,7 +2938,9 @@
"description": "Không tìm thấy crawl job hoặc không có quyền truy cập"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Xem preview dữ liệu clean/raw của các trang đã crawl"
}
},
......@@ -2867,7 +2982,9 @@
}
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Lấy danh sách các bản export của job"
},
"post": {
......@@ -2904,7 +3021,9 @@
}
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Yêu cầu xuất dữ liệu cho job",
"requestBody": {
"required": true,
......@@ -2943,7 +3062,9 @@
}
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Tải xuống file export mới nhất"
}
},
......@@ -2984,7 +3105,14 @@
"required": false,
"schema": {
"type": "string",
"enum": ["IMAGE", "LINK", "PDF", "FILE", "VIDEO", "OTHER"]
"enum": [
"IMAGE",
"LINK",
"PDF",
"FILE",
"VIDEO",
"OTHER"
]
},
"description": "Lọc theo loại asset"
}
......@@ -3076,7 +3204,9 @@
"description": "Không tìm thấy crawl job hoặc không có quyền truy cập"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Lấy danh sách assets của job (có phân trang)"
}
},
......@@ -3129,7 +3259,9 @@
"description": "Không tìm thấy crawl job"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Xem báo cáo thay đổi (Diff Report)"
}
},
......@@ -3174,7 +3306,9 @@
"description": "Không tìm thấy crawl job hoặc diff report"
}
},
"tags": ["Crawl Jobs"],
"tags": [
"Crawl Jobs"
],
"summary": "Tải file diff_report.json"
}
},
......@@ -3216,7 +3350,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Crawl Schedules"],
"tags": [
"Crawl Schedules"
],
"summary": "Tạo lịch crawl định kỳ mới",
"requestBody": {
"required": true,
......@@ -3245,7 +3381,12 @@
"in": "query",
"schema": {
"type": "string",
"enum": ["DAILY", "WEEKLY", "MONTHLY", "CUSTOM"]
"enum": [
"DAILY",
"WEEKLY",
"MONTHLY",
"CUSTOM"
]
},
"description": "Lọc theo tần suất"
},
......@@ -3314,7 +3455,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Crawl Schedules"],
"tags": [
"Crawl Schedules"
],
"summary": "Lấy danh sách lịch crawl định kỳ"
}
},
......@@ -3350,7 +3493,9 @@
"description": "Không tìm thấy lịch crawl"
}
},
"tags": ["Crawl Schedules"],
"tags": [
"Crawl Schedules"
],
"summary": "Xem chi tiết lịch crawl"
},
"patch": {
......@@ -3395,7 +3540,9 @@
"description": "Không tìm thấy lịch crawl"
}
},
"tags": ["Crawl Schedules"],
"tags": [
"Crawl Schedules"
],
"summary": "Cập nhật lịch crawl",
"requestBody": {
"required": true,
......@@ -3445,7 +3592,9 @@
"description": "Không tìm thấy lịch crawl"
}
},
"tags": ["Crawl Schedules"],
"tags": [
"Crawl Schedules"
],
"summary": "Xóa lịch crawl"
}
},
......@@ -3490,7 +3639,9 @@
"description": "Không tìm thấy lịch crawl"
}
},
"tags": ["Crawl Schedules"],
"tags": [
"Crawl Schedules"
],
"summary": "Kích hoạt chạy ngay lịch crawl"
}
},
......@@ -3567,7 +3718,9 @@
"description": "Không tìm thấy lịch crawl"
}
},
"tags": ["Crawl Schedules"],
"tags": [
"Crawl Schedules"
],
"summary": "Xem lịch sử các lần chạy của lịch crawl"
}
},
......@@ -3643,7 +3796,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Exports"],
"tags": [
"Exports"
],
"summary": "Danh sách tất cả các bản xuất dữ liệu"
}
},
......@@ -3675,7 +3830,9 @@
"description": "Không tìm thấy file export"
}
},
"tags": ["Crawl Exports"],
"tags": [
"Crawl Exports"
],
"summary": "Tải xuống tệp export theo ID"
}
},
......@@ -3725,7 +3882,9 @@
"description": "Không tìm thấy bản xuất dữ liệu"
}
},
"tags": ["Exports"],
"tags": [
"Exports"
],
"summary": "Xóa bản xuất dữ liệu"
}
},
......@@ -3843,7 +4002,9 @@
"description": "Không có quyền truy cập (không phải ADMIN)"
}
},
"tags": ["Audit Logs"],
"tags": [
"Audit Logs"
],
"summary": "Lấy danh sách nhật ký hệ thống"
}
},
......@@ -3877,7 +4038,9 @@
"description": "Dữ liệu không hợp lệ hoặc thời điểm hết hạn không ở trong tương lai"
}
},
"tags": ["API Keys"],
"tags": [
"API Keys"
],
"security": [
{
"BearerAuth": []
......@@ -3924,7 +4087,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["API Keys"],
"tags": [
"API Keys"
],
"security": [
{
"BearerAuth": []
......@@ -3978,7 +4143,9 @@
"description": "Trạng thái không hợp lệ"
}
},
"tags": ["API Keys"],
"tags": [
"API Keys"
],
"security": [
{
"BearerAuth": []
......@@ -4037,7 +4204,9 @@
"description": "Không tìm thấy API Key"
}
},
"tags": ["API Keys"],
"tags": [
"API Keys"
],
"security": [
{
"BearerAuth": []
......@@ -4076,7 +4245,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Webhooks"],
"tags": [
"Webhooks"
],
"summary": "Tạo cấu hình Webhook",
"requestBody": {
"required": true,
......@@ -4118,7 +4289,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Webhooks"],
"tags": [
"Webhooks"
],
"summary": "Xem danh sách Webhook configs"
}
},
......@@ -4167,7 +4340,9 @@
"description": "Không tìm thấy cấu hình Webhook"
}
},
"tags": ["Webhooks"],
"tags": [
"Webhooks"
],
"summary": "Cập nhật cấu hình Webhook",
"requestBody": {
"required": true,
......@@ -4220,7 +4395,9 @@
"description": "Không tìm thấy cấu hình Webhook"
}
},
"tags": ["Webhooks"],
"tags": [
"Webhooks"
],
"summary": "Xóa cấu hình Webhook"
}
},
......@@ -4280,7 +4457,9 @@
"description": "Không tìm thấy cấu hình Webhook"
}
},
"tags": ["Webhooks"],
"tags": [
"Webhooks"
],
"summary": "Kiểm tra kết nối Webhook (Ping Test)"
}
},
......@@ -4332,7 +4511,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Webhooks"],
"tags": [
"Webhooks"
],
"summary": "Xem lịch sử gửi Webhook"
}
},
......@@ -4382,7 +4563,9 @@
"description": "Không tìm thấy bản ghi webhook delivery"
}
},
"tags": ["Webhooks"],
"tags": [
"Webhooks"
],
"summary": "Gửi lại (Redeliver) Webhook thất bại"
}
},
......@@ -4416,7 +4599,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Extraction Templates"],
"tags": [
"Extraction Templates"
],
"summary": "Tạo template trích xuất dữ liệu có cấu trúc",
"requestBody": {
"required": true,
......@@ -4458,7 +4643,9 @@
"description": "Chưa xác thực"
}
},
"tags": ["Extraction Templates"],
"tags": [
"Extraction Templates"
],
"summary": "Danh sách template trích xuất dữ liệu"
}
},
......@@ -4504,7 +4691,9 @@
"description": "Không tìm thấy template"
}
},
"tags": ["Extraction Templates"],
"tags": [
"Extraction Templates"
],
"summary": "Chi tiết template trích xuất"
},
"patch": {
......@@ -4551,7 +4740,9 @@
"description": "Không tìm thấy template"
}
},
"tags": ["Extraction Templates"],
"tags": [
"Extraction Templates"
],
"summary": "Cập nhật template trích xuất",
"requestBody": {
"required": true,
......@@ -4605,7 +4796,9 @@
"description": "Không tìm thấy template"
}
},
"tags": ["Extraction Templates"],
"tags": [
"Extraction Templates"
],
"summary": "Xóa template trích xuất"
}
}
......@@ -4642,7 +4835,11 @@
},
"role": {
"type": "string",
"enum": ["ADMIN", "CRAWLER_USER", "VIEWER"]
"enum": [
"ADMIN",
"CRAWLER_USER",
"VIEWER"
]
},
"isActive": {
"type": "boolean"
......@@ -4671,7 +4868,12 @@
},
"mode": {
"type": "string",
"enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"]
"enum": [
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
]
},
"status": {
"type": "string",
......@@ -4882,7 +5084,10 @@
},
"type": {
"type": "string",
"enum": ["internal", "external"]
"enum": [
"internal",
"external"
]
}
}
}
......@@ -4968,11 +5173,21 @@
},
"exportType": {
"type": "string",
"enum": ["JSON", "CSV", "XLSX", "MARKDOWN", "ZIP"]
"enum": [
"JSON",
"CSV",
"XLSX",
"MARKDOWN",
"ZIP"
]
},
"status": {
"type": "string",
"enum": ["PENDING", "COMPLETED", "FAILED"]
"enum": [
"PENDING",
"COMPLETED",
"FAILED"
]
},
"fileName": {
"type": "string"
......@@ -4995,7 +5210,10 @@
},
"LoginRequest": {
"type": "object",
"required": ["email", "password"],
"required": [
"email",
"password"
],
"properties": {
"email": {
"type": "string",
......@@ -5010,7 +5228,10 @@
},
"RegisterRequest": {
"type": "object",
"required": ["email", "password"],
"required": [
"email",
"password"
],
"properties": {
"email": {
"type": "string",
......@@ -5029,7 +5250,9 @@
},
"ResendVerificationRequest": {
"type": "object",
"required": ["email"],
"required": [
"email"
],
"properties": {
"email": {
"type": "string",
......@@ -5040,7 +5263,9 @@
},
"RequestDeactivationRequest": {
"type": "object",
"required": ["password"],
"required": [
"password"
],
"properties": {
"password": {
"type": "string",
......@@ -5051,7 +5276,9 @@
},
"ConfirmDeactivationRequest": {
"type": "object",
"required": ["token"],
"required": [
"token"
],
"properties": {
"token": {
"type": "string",
......@@ -5062,7 +5289,9 @@
},
"RefreshRequest": {
"type": "object",
"required": ["refreshToken"],
"required": [
"refreshToken"
],
"properties": {
"refreshToken": {
"type": "string",
......@@ -5072,7 +5301,9 @@
},
"LogoutRequest": {
"type": "object",
"required": ["refreshToken"],
"required": [
"refreshToken"
],
"properties": {
"refreshToken": {
"type": "string",
......@@ -5091,7 +5322,11 @@
},
"ChangePasswordRequest": {
"type": "object",
"required": ["currentPassword", "newPassword", "confirmPassword"],
"required": [
"currentPassword",
"newPassword",
"confirmPassword"
],
"properties": {
"currentPassword": {
"type": "string",
......@@ -5109,7 +5344,10 @@
},
"CreateUserRequest": {
"type": "object",
"required": ["email", "password"],
"required": [
"email",
"password"
],
"properties": {
"email": {
"type": "string",
......@@ -5126,7 +5364,11 @@
},
"role": {
"type": "string",
"enum": ["ADMIN", "CRAWLER_USER", "VIEWER"],
"enum": [
"ADMIN",
"CRAWLER_USER",
"VIEWER"
],
"example": "CRAWLER_USER"
},
"maxPagesLimit": {
......@@ -5152,7 +5394,11 @@
},
"role": {
"type": "string",
"enum": ["ADMIN", "CRAWLER_USER", "VIEWER"],
"enum": [
"ADMIN",
"CRAWLER_USER",
"VIEWER"
],
"example": "VIEWER"
},
"isActive": {
......@@ -5183,7 +5429,12 @@
},
"mode": {
"type": "string",
"enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"],
"enum": [
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
],
"example": "CRAWL"
},
"maxPages": {
......@@ -5204,25 +5455,38 @@
"type": "string",
"format": "uri"
},
"example": ["https://example.com/1", "https://example.com/2"],
"example": [
"https://example.com/1",
"https://example.com/2"
],
"description": "Bắt buộc khi mode là URL_LIST"
}
}
},
"CreateExportRequest": {
"type": "object",
"required": ["exportType"],
"required": [
"exportType"
],
"properties": {
"exportType": {
"type": "string",
"enum": ["JSON", "CSV", "XLSX", "MARKDOWN", "ZIP"],
"enum": [
"JSON",
"CSV",
"XLSX",
"MARKDOWN",
"ZIP"
],
"example": "JSON"
}
}
},
"CreateApiKeyRequest": {
"type": "object",
"required": ["name"],
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
......@@ -5241,7 +5505,9 @@
},
"UpdateApiKeyStatusRequest": {
"type": "object",
"required": ["isActive"],
"required": [
"isActive"
],
"additionalProperties": false,
"properties": {
"isActive": {
......@@ -5338,7 +5604,11 @@
},
"CreateWebhookConfigRequest": {
"type": "object",
"required": ["url", "secret", "events"],
"required": [
"url",
"secret",
"events"
],
"properties": {
"url": {
"type": "string",
......@@ -5353,9 +5623,15 @@
"type": "array",
"items": {
"type": "string",
"enum": ["job.completed", "job.failed"]
"enum": [
"job.completed",
"job.failed"
]
},
"example": ["job.completed", "job.failed"]
"example": [
"job.completed",
"job.failed"
]
}
}
},
......@@ -5416,7 +5692,11 @@
},
"status": {
"type": "string",
"enum": ["PENDING", "SUCCESS", "FAILED"]
"enum": [
"PENDING",
"SUCCESS",
"FAILED"
]
},
"statusCode": {
"type": "integer",
......@@ -5480,11 +5760,21 @@
},
"mode": {
"type": "string",
"enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"]
"enum": [
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
]
},
"frequency": {
"type": "string",
"enum": ["DAILY", "WEEKLY", "MONTHLY", "CUSTOM"]
"enum": [
"DAILY",
"WEEKLY",
"MONTHLY",
"CUSTOM"
]
},
"cronExpression": {
"type": "string",
......@@ -5547,7 +5837,10 @@
},
"CreateCrawlScheduleRequest": {
"type": "object",
"required": ["name", "startUrl"],
"required": [
"name",
"startUrl"
],
"properties": {
"name": {
"type": "string",
......@@ -5560,12 +5853,22 @@
},
"mode": {
"type": "string",
"enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"],
"enum": [
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
],
"example": "CRAWL"
},
"frequency": {
"type": "string",
"enum": ["DAILY", "WEEKLY", "MONTHLY", "CUSTOM"],
"enum": [
"DAILY",
"WEEKLY",
"MONTHLY",
"CUSTOM"
],
"example": "DAILY"
},
"cronExpression": {
......@@ -5628,11 +5931,21 @@
},
"mode": {
"type": "string",
"enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"]
"enum": [
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
]
},
"frequency": {
"type": "string",
"enum": ["DAILY", "WEEKLY", "MONTHLY", "CUSTOM"]
"enum": [
"DAILY",
"WEEKLY",
"MONTHLY",
"CUSTOM"
]
},
"cronExpression": {
"type": "string"
......@@ -5832,7 +6145,10 @@
},
"CreateRoleRequest": {
"type": "object",
"required": ["name", "slug"],
"required": [
"name",
"slug"
],
"properties": {
"name": {
"type": "string",
......@@ -5871,7 +6187,9 @@
},
"AssignRolePermissionsRequest": {
"type": "object",
"required": ["permissionIds"],
"required": [
"permissionIds"
],
"properties": {
"permissionIds": {
"type": "array",
......@@ -5884,7 +6202,9 @@
},
"AssignUserRolesRequest": {
"type": "object",
"required": ["roleIds"],
"required": [
"roleIds"
],
"properties": {
"roleIds": {
"type": "array",
......@@ -5913,9 +6233,14 @@
"type": "array",
"items": {
"type": "string",
"enum": ["job.completed", "job.failed"]
"enum": [
"job.completed",
"job.failed"
]
},
"example": ["job.completed"]
"example": [
"job.completed"
]
},
"isActive": {
"type": "boolean",
......@@ -5925,7 +6250,12 @@
},
"ExtractionTemplateField": {
"type": "object",
"required": ["name", "selector", "attr", "required"],
"required": [
"name",
"selector",
"attr",
"required"
],
"properties": {
"name": {
"type": "string",
......@@ -5982,7 +6312,11 @@
},
"CreateExtractionTemplateRequest": {
"type": "object",
"required": ["name", "domain", "fields"],
"required": [
"name",
"domain",
"fields"
],
"properties": {
"name": {
"type": "string",
......@@ -6028,7 +6362,11 @@
},
"level": {
"type": "string",
"enum": ["INFO", "WARN", "ERROR"]
"enum": [
"INFO",
"WARN",
"ERROR"
]
},
"step": {
"type": "string",
......@@ -6123,4 +6461,4 @@
"ApiKeyAuth": []
}
]
}
}
\ No newline at end of file
......@@ -209,7 +209,7 @@ describe("AuthService registration mail failures", () => {
);
});
it("returns { success: true } and does not call mail service if user is not found", async () => {
it("throws 404 NOT_FOUND and does not call mail service if user is not found", async () => {
const service = new AuthService();
const repository = {
findByEmail: jest.fn().mockResolvedValue(null),
......@@ -224,15 +224,15 @@ describe("AuthService registration mail failures", () => {
mutableService.repository = repository;
mutableService.mailService = mailService;
const result = await service.forgotPassword({
email: "nonexistent@example.com",
});
expect(result).toEqual({ success: true });
await expect(
service.forgotPassword({
email: "nonexistent@example.com",
})
).rejects.toThrow("Email không tồn tại trong hệ thống.");
expect(mailService.sendPasswordResetEmail).not.toHaveBeenCalled();
});
it("returns { success: true } and does not call mail service if user is inactive", async () => {
it("throws 403 USER_INACTIVE and does not call mail service if user is inactive", async () => {
const service = new AuthService();
const repository = {
findByEmail: jest
......@@ -249,9 +249,9 @@ describe("AuthService registration mail failures", () => {
mutableService.repository = repository;
mutableService.mailService = mailService;
const result = await service.forgotPassword({ email: activeUser.email });
expect(result).toEqual({ success: true });
await expect(
service.forgotPassword({ email: activeUser.email })
).rejects.toThrow("Tài khoản chưa được kích hoạt hoặc đã bị khóa.");
expect(mailService.sendPasswordResetEmail).not.toHaveBeenCalled();
});
});
......
......@@ -330,8 +330,7 @@ export class AuthController {
res.json({
success: true,
message:
"If the email exists in our system, a password reset link has been sent.",
message: "Đường dẫn đặt lại mật khẩu đã được gửi đến email của bạn.",
});
} catch (error) {
next(error);
......
......@@ -607,8 +607,20 @@ export class AuthService {
const { email } = data;
const user = await this.repository.findByEmail(email);
if (!user || !user.isActive) {
return { success: true };
if (!user) {
throw new AppError(
"Email không tồn tại trong hệ thống.",
404,
ERROR_CODE.NOT_FOUND,
);
}
if (!user.isActive) {
throw new AppError(
"Tài khoản chưa được kích hoạt hoặc đã bị khóa.",
403,
ERROR_CODE.USER_INACTIVE,
);
}
const secret = `${jwtConfig.accessSecret}-${user.passwordHash}`;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment