Commit 18858339 authored by ThinhNC's avatar ThinhNC

Merge branch 'refactor/audit-fixes-and-dynamic-rbac-permissions' into 'develop'

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

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