Commit 8bc9c30b authored by ThinhNC's avatar ThinhNC

docs(swagger): add complete openapi 3.0 path specs and component schemas for...

docs(swagger): add complete openapi 3.0 path specs and component schemas for all remaining endpoints
parent f89bdfdb
...@@ -25,33 +25,34 @@ All findings across authentication security, dynamic permission-based access con ...@@ -25,33 +25,34 @@ All findings across authentication security, dynamic permission-based access con
## Findings Backlog & Resolution Summary ## Findings Backlog & Resolution Summary
| ID | Severity | Module | Summary of Issue | Verification | Resolution Status | | ID | Severity | Module | Summary of Issue | Verification | Resolution Status |
| :--- | :--- | :--- | :--- | :--- | :--- | | :----------- | :------- | :------------------- | :-------------------------------------------------------------------------------------------- | :----------- | :-------------------------------- |
| **BUG-01** | 🔴 P0 | App / Security | CORS origin reflection allowed wildcard with credentials | CONFIRMED | **FIXED & TESTED** | | **BUG-01** | 🔴 P0 | App / Security | CORS origin reflection allowed wildcard with credentials | CONFIRMED | **FIXED & TESTED** |
| **BUG-02** | 🟠 P1 | Webhooks / Templates | Missing authorization guards on webhook and extraction template mutations | CONFIRMED | **FIXED & RBAC-PROTECTED** | | **BUG-02** | 🟠 P1 | Webhooks / Templates | Missing authorization guards on webhook and extraction template mutations | CONFIRMED | **FIXED & RBAC-PROTECTED** |
| **BUG-03** | 🟠 P1 | Auth / DB | Non-atomic default role assignment during user registration | CONFIRMED | **FIXED (Atomic Transaction)** | | **BUG-03** | 🟠 P1 | Auth / DB | Non-atomic default role assignment during user registration | CONFIRMED | **FIXED (Atomic Transaction)** |
| **BUG-04** | 🟠 P1 | Error Handling | Unhandled Prisma Known Request Errors (P2002, P2023, P2025, P2003) | CONFIRMED | **FIXED & STANDARDIZED** | | **BUG-04** | 🟠 P1 | Error Handling | Unhandled Prisma Known Request Errors (P2002, P2023, P2025, P2003) | CONFIRMED | **FIXED & STANDARDIZED** |
| **BUG-05** | 🟠 P1 | Users / Auth | Soft-delete and self-deactivation failed to cascade deactivate schedules, keys, and webhooks | CONFIRMED | **FIXED (Cascade Deactivation)** | | **BUG-05** | 🟠 P1 | Users / Auth | Soft-delete and self-deactivation failed to cascade deactivate schedules, keys, and webhooks | CONFIRMED | **FIXED (Cascade Deactivation)** |
| **BUG-10** | 🟠 P1 | App / Security | Helmet Content Security Policy (CSP) disabled globally | CONFIRMED | **FIXED (Scaped via Branching)** | | **BUG-10** | 🟠 P1 | App / Security | Helmet Content Security Policy (CSP) disabled globally | CONFIRMED | **FIXED (Scaped via Branching)** |
| **BUG-06** | 🟠 P1 | Roles / Users | Role assignment performed N+1 database queries in a loop | CONFIRMED | **FIXED (findByIds Batch Query)** | | **BUG-06** | 🟠 P1 | Roles / Users | Role assignment performed N+1 database queries in a loop | CONFIRMED | **FIXED (findByIds Batch Query)** |
| **BUG-07** | 🟡 P2 | Health / Layering | Layer violation: `HealthService` directly executed `prisma.$queryRaw` | CONFIRMED | **FIXED (HealthRepository)** | | **BUG-07** | 🟡 P2 | Health / Layering | Layer violation: `HealthService` directly executed `prisma.$queryRaw` | CONFIRMED | **FIXED (HealthRepository)** |
| **BUG-08** | 🟠 P1 | Dashboard | 11 sequential `count()` queries overloaded database CPU | CONFIRMED | **FIXED (groupBy Aggregations)** | | **BUG-08** | 🟠 P1 | Dashboard | 11 sequential `count()` queries overloaded database CPU | CONFIRMED | **FIXED (groupBy Aggregations)** |
| **BUG-09** | 🟡 P2 | Database / Prisma | Missing `onDelete: Cascade` on CrawlAsset foreign key | CONFIRMED | **FIXED (Prisma Migration)** | | **BUG-09** | 🟡 P2 | Database / Prisma | Missing `onDelete: Cascade` on CrawlAsset foreign key | CONFIRMED | **FIXED (Prisma Migration)** |
| **BUG-15** | 🟡 P2 | Database / Prisma | Missing composite index `@@index([userId, createdAt])` on CrawlJob | CONFIRMED | **FIXED (Prisma Migration)** | | **BUG-15** | 🟡 P2 | Database / Prisma | Missing composite index `@@index([userId, createdAt])` on CrawlJob | CONFIRMED | **FIXED (Prisma Migration)** |
| **BUG-11** | 🟡 P2 | CrawlExports | Inconsistent pagination envelope `{ success: true, data: items, pagination }` | CONFIRMED | **FIXED & STANDARDIZED** | | **BUG-11** | 🟡 P2 | CrawlExports | Inconsistent pagination envelope `{ success: true, data: items, pagination }` | CONFIRMED | **FIXED & STANDARDIZED** |
| **BUG-12** | 🟡 P2 | Validation | Missing edge parameter & query validation (Avatar Path Traversal, Job/Export queries) | CONFIRMED | **FIXED (Zod Schemas)** | | **BUG-12** | 🟡 P2 | Validation | Missing edge parameter & query validation (Avatar Path Traversal, Job/Export queries) | CONFIRMED | **FIXED (Zod Schemas)** |
| **BUG-13** | 🟢 P3 | Cross-Cutting | Zero-hardcode principle violations with raw string literals | CONFIRMED | **FIXED (Domain Constants)** | | **BUG-13** | 🟢 P3 | Cross-Cutting | Zero-hardcode principle violations with raw string literals | CONFIRMED | **FIXED (Domain Constants)** |
| **BUG-14** | 🟢 P3 | ChangeDetection | Inline `@prisma/client` enum import in service | CONFIRMED | **FIXED (Domain Constants)** | | **BUG-14** | 🟢 P3 | ChangeDetection | Inline `@prisma/client` enum import in service | CONFIRMED | **FIXED (Domain Constants)** |
| **BUG-16** | 🟢 P3 | Upload | Discrepancy between MIME type whitelist and validation error message | CONFIRMED | **FIXED (Added image/gif)** | | **BUG-16** | 🟢 P3 | Upload | Discrepancy between MIME type whitelist and validation error message | CONFIRMED | **FIXED (Added image/gif)** |
| **BUG-17** | 🟢 P3 | Exports | Object destructuring rest-omission in large loops allocated redundant GC garbage | CONFIRMED | **FIXED (Explicit Projection)** | | **BUG-17** | 🟢 P3 | Exports | Object destructuring rest-omission in large loops allocated redundant GC garbage | CONFIRMED | **FIXED (Explicit Projection)** |
| **AUDIT-01** | 🟠 P1 | CrawlSchedules | Response envelope in `CrawlScheduleController` lacked `{ success: true, data }` wrapping | CONFIRMED | **FIXED & STANDARDIZED** | | **AUDIT-01** | 🟠 P1 | CrawlSchedules | Response envelope in `CrawlScheduleController` lacked `{ success: true, data }` wrapping | CONFIRMED | **FIXED & STANDARDIZED** |
| **AUDIT-02** | 🟡 P2 | Routing / Edge | Missing `validateParams` on `:id`, `:roleId`, and `:permissionId` across all resource routers | CONFIRMED | **FIXED & BOUNDED** | | **AUDIT-02** | 🟡 P2 | Routing / Edge | Missing `validateParams` on `:id`, `:roleId`, and `:permissionId` across all resource routers | CONFIRMED | **FIXED & BOUNDED** |
--- ---
## Fixed Issues Detail ## Fixed Issues Detail
### [BUG-01] CORS Origin Reflection With Credentials ### [BUG-01] CORS Origin Reflection With Credentials
- **Severity**: 🔴 P0 - **Severity**: 🔴 P0
- **Module**: `app` - **Module**: `app`
- **Root Cause**: Wildcard origins combined with `credentials: true` caused the server to reflect the incoming `Origin` header dynamically, permitting malicious third-party origins to perform authenticated cross-origin reads. - **Root Cause**: Wildcard origins combined with `credentials: true` caused the server to reflect the incoming `Origin` header dynamically, permitting malicious third-party origins to perform authenticated cross-origin reads.
...@@ -63,6 +64,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -63,6 +64,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [BUG-02] Missing RBAC / Permissions on Webhooks and Extraction Templates ### [BUG-02] Missing RBAC / Permissions on Webhooks and Extraction Templates
- **Severity**: 🟠 P1 - **Severity**: 🟠 P1
- **Module**: `webhooks`, `extraction-templates` - **Module**: `webhooks`, `extraction-templates`
- **Root Cause**: Router definitions applied `authMiddleware` but lacked permission checks, allowing unprivileged accounts (`VIEWER`) to create webhooks (SSRF / Data exfiltration risk) or alter extraction templates. - **Root Cause**: Router definitions applied `authMiddleware` but lacked permission checks, allowing unprivileged accounts (`VIEWER`) to create webhooks (SSRF / Data exfiltration risk) or alter extraction templates.
...@@ -75,6 +77,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -75,6 +77,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [BUG-03] Atomic Default Role Assignment During Registration ### [BUG-03] Atomic Default Role Assignment During Registration
- **Severity**: 🟠 P1 - **Severity**: 🟠 P1
- **Module**: `auth` - **Module**: `auth`
- **Root Cause**: User creation and initial role assignment to `user_roles` were executed across separate, non-atomic steps, creating dangling unassigned users if interrupted. - **Root Cause**: User creation and initial role assignment to `user_roles` were executed across separate, non-atomic steps, creating dangling unassigned users if interrupted.
...@@ -86,6 +89,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -86,6 +89,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [BUG-04] Prisma Known Request Error Normalization ### [BUG-04] Prisma Known Request Error Normalization
- **Severity**: 🟠 P1 - **Severity**: 🟠 P1
- **Module**: `error-middleware` - **Module**: `error-middleware`
- **Root Cause**: Uncaught Prisma errors (`P2002`, `P2023`, `P2025`, `P2003`) fell into the generic 500 handler, leaking database table names and column identifiers to client logs. - **Root Cause**: Uncaught Prisma errors (`P2002`, `P2023`, `P2025`, `P2003`) fell into the generic 500 handler, leaking database table names and column identifiers to client logs.
...@@ -97,6 +101,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -97,6 +101,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [BUG-05] Cascading Resource Deactivation on User Soft-Delete & Self-Deactivation ### [BUG-05] Cascading Resource Deactivation on User Soft-Delete & Self-Deactivation
- **Severity**: 🟠 P1 - **Severity**: 🟠 P1
- **Module**: `users`, `auth` - **Module**: `users`, `auth`
- **Root Cause**: Deleting a user or confirming account deactivation left `crawl_schedules`, `api_keys`, and `webhook_configs` active, causing background BullMQ workers to continue crawling and dispatching webhooks. - **Root Cause**: Deleting a user or confirming account deactivation left `crawl_schedules`, `api_keys`, and `webhook_configs` active, causing background BullMQ workers to continue crawling and dispatching webhooks.
...@@ -109,6 +114,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -109,6 +114,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [BUG-10] Global Content Security Policy (CSP) Scoping ### [BUG-10] Global Content Security Policy (CSP) Scoping
- **Severity**: 🟠 P1 - **Severity**: 🟠 P1
- **Module**: `app` - **Module**: `app`
- **Root Cause**: Global Helmet CSP was previously turned off to allow Swagger UI inline assets, removing client-side injection protection for all API endpoints. - **Root Cause**: Global Helmet CSP was previously turned off to allow Swagger UI inline assets, removing client-side injection protection for all API endpoints.
...@@ -120,6 +126,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -120,6 +126,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [BUG-06] N+1 Query in User Role Assignment ### [BUG-06] N+1 Query in User Role Assignment
- **Severity**: 🟠 P1 - **Severity**: 🟠 P1
- **Module**: `roles`, `users` - **Module**: `roles`, `users`
- **Root Cause**: `assignUserRoles` iterated sequentially over `roleIds` with individual `findById` queries. - **Root Cause**: `assignUserRoles` iterated sequentially over `roleIds` with individual `findById` queries.
...@@ -132,6 +139,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -132,6 +139,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [BUG-07] Strict Layer Architecture Isolation in Health Check ### [BUG-07] Strict Layer Architecture Isolation in Health Check
- **Severity**: 🟡 P2 - **Severity**: 🟡 P2
- **Module**: `health` - **Module**: `health`
- **Root Cause**: `HealthService` directly imported and called `prisma.$queryRaw`, violating the exclusive Prisma access rule in `AGENTS.md`. - **Root Cause**: `HealthService` directly imported and called `prisma.$queryRaw`, violating the exclusive Prisma access rule in `AGENTS.md`.
...@@ -144,6 +152,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -144,6 +152,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [BUG-08] Dashboard Query Aggregation Optimization ### [BUG-08] Dashboard Query Aggregation Optimization
- **Severity**: 🟠 P1 - **Severity**: 🟠 P1
- **Module**: `dashboard` - **Module**: `dashboard`
- **Root Cause**: 11 sequential `count()` queries executed per dashboard stats request, overloading PostgreSQL. - **Root Cause**: 11 sequential `count()` queries executed per dashboard stats request, overloading PostgreSQL.
...@@ -155,6 +164,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -155,6 +164,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [BUG-09] & [BUG-15] Schema Cascade & Composite Index Optimization ### [BUG-09] & [BUG-15] Schema Cascade & Composite Index Optimization
- **Severity**: 🟡 P2 - **Severity**: 🟡 P2
- **Module**: `database` - **Module**: `database`
- **Root Cause**: `CrawlAsset.crawlJob` lacked `onDelete: Cascade` (causing P2003 errors on job deletion), and `CrawlJob` lacked composite indexing for user timeline queries. - **Root Cause**: `CrawlAsset.crawlJob` lacked `onDelete: Cascade` (causing P2003 errors on job deletion), and `CrawlJob` lacked composite indexing for user timeline queries.
...@@ -166,6 +176,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -166,6 +176,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [AUDIT-01] CrawlScheduleController Envelope Standardization ### [AUDIT-01] CrawlScheduleController Envelope Standardization
- **Severity**: 🟠 P1 - **Severity**: 🟠 P1
- **Module**: `crawl-schedules` - **Module**: `crawl-schedules`
- **Root Cause**: Endpoints in `CrawlScheduleController` returned raw data or `{ message, data }` without `{ success: true, data }`, breaking frontend API consumer expectations. - **Root Cause**: Endpoints in `CrawlScheduleController` returned raw data or `{ message, data }` without `{ success: true, data }`, breaking frontend API consumer expectations.
...@@ -177,6 +188,7 @@ All findings across authentication security, dynamic permission-based access con ...@@ -177,6 +188,7 @@ All findings across authentication security, dynamic permission-based access con
--- ---
### [AUDIT-02] Edge Route Parameter Validation Across All Routers ### [AUDIT-02] Edge Route Parameter Validation Across All Routers
- **Severity**: 🟡 P2 - **Severity**: 🟡 P2
- **Module**: `cross-cutting / routing` - **Module**: `cross-cutting / routing`
- **Root Cause**: Route identifiers (`:id`, `:roleId`, `:permissionId`) were passed directly to services without edge validation, risking malformed identifiers reaching Prisma. - **Root Cause**: Route identifiers (`:id`, `:roleId`, `:permissionId`) were passed directly to services without edge validation, risking malformed identifiers reaching Prisma.
......
...@@ -166,6 +166,117 @@ export const swaggerPaths: Record<string, any> = { ...@@ -166,6 +166,117 @@ export const swaggerPaths: Record<string, any> = {
401: { description: "Chưa xác thực" }, 401: { description: "Chưa xác thực" },
}, },
}, },
patch: {
tags: ["Auth"],
summary: "Cập nhật một phần thông tin cá nhân",
description: "Cập nhật họ tên của người dùng hiện tại.",
requestBody: {
required: true,
content: {
"application/json": {
schema: { $ref: "#/components/schemas/UpdateMeRequest" },
},
},
},
responses: {
200: {
description: "Cập nhật thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: { $ref: "#/components/schemas/User" },
},
},
},
},
},
400: { description: "Dữ liệu yêu cầu không hợp lệ" },
401: { description: "Chưa xác thực" },
},
},
},
"/auth/avatar": {
post: {
tags: ["Auth"],
summary: "Tải lên ảnh đại diện (Avatar)",
description:
"Tải lên tệp ảnh đại diện cho người dùng hiện tại (JPG, PNG, WEBP, GIF, tối đa 2MB).",
requestBody: {
required: true,
content: {
"multipart/form-data": {
schema: {
type: "object",
required: ["avatar"],
properties: {
avatar: {
type: "string",
format: "binary",
description: "Tệp ảnh avatar tải lên",
},
},
},
},
},
},
responses: {
200: {
description: "Tải lên ảnh đại diện thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: {
type: "object",
properties: {
avatarUrl: {
type: "string",
example: "/api/v1/auth/avatar/avatar_123.jpg",
},
},
},
},
},
},
},
},
400: { description: "Tệp không hợp lệ hoặc vượt kích thước" },
401: { description: "Chưa xác thực" },
},
},
},
"/auth/avatar/{fileName}": {
get: {
tags: ["Auth"],
summary: "Tải hoặc hiển thị ảnh đại diện",
description: "Xem và tải tệp ảnh đại diện của người dùng.",
parameters: [
{
name: "fileName",
in: "path",
required: true,
schema: { type: "string" },
description: "Tên tệp ảnh đại diện",
},
],
responses: {
200: {
description: "Tệp ảnh dạng binary",
content: {
"image/jpeg": { schema: { type: "string", format: "binary" } },
"image/png": { schema: { type: "string", format: "binary" } },
"image/webp": { schema: { type: "string", format: "binary" } },
"image/gif": { schema: { type: "string", format: "binary" } },
},
},
404: { description: "Không tìm thấy tệp ảnh đại diện" },
},
},
}, },
"/auth/me/usage": { "/auth/me/usage": {
get: { get: {
...@@ -892,6 +1003,44 @@ export const swaggerPaths: Record<string, any> = { ...@@ -892,6 +1003,44 @@ export const swaggerPaths: Record<string, any> = {
}, },
}, },
}, },
delete: {
tags: ["Crawl Jobs"],
summary: "Xóa crawl job",
description:
"Xóa hoàn toàn crawl job cùng toàn bộ dữ liệu trang, assets và export liên quan.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID của crawl job",
},
],
responses: {
200: {
description: "Xóa crawl job thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
message: {
type: "string",
example: "Crawl job deleted successfully",
},
},
},
},
},
},
400: { description: "Không thể xóa job đang chạy" },
401: { description: "Chưa xác thực" },
403: { description: "Không có quyền CRAWL_JOBS_DELETE" },
404: { description: "Không tìm thấy crawl job" },
},
},
}, },
"/crawl-jobs/{id}/cancel": { "/crawl-jobs/{id}/cancel": {
post: { post: {
...@@ -927,6 +1076,135 @@ export const swaggerPaths: Record<string, any> = { ...@@ -927,6 +1076,135 @@ export const swaggerPaths: Record<string, any> = {
}, },
}, },
}, },
"/crawl-jobs/{id}/rerun": {
post: {
tags: ["Crawl Jobs"],
summary: "Chạy lại crawl job với cấu hình ban đầu",
description:
"Khởi tạo một job mới kế thừa toàn bộ startUrl, mode, maxPages và maxDepth từ job trước đó.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID của crawl job cần chạy lại",
},
],
responses: {
201: {
description: "Khởi tạo job chạy lại thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: { $ref: "#/components/schemas/CrawlJob" },
},
},
},
},
},
401: { description: "Chưa xác thực" },
403: { description: "Không có quyền CRAWL_JOBS_RETRY" },
404: { description: "Không tìm thấy crawl job" },
},
},
},
"/crawl-jobs/{id}/logs": {
get: {
tags: ["Crawl Jobs"],
summary: "Xem nhật ký (logs) chi tiết của crawl job",
description:
"Lấy danh sách các bản ghi log tiến trình thực thi từ worker theo từng bước.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID của crawl job",
},
{
name: "page",
in: "query",
schema: { type: "integer", default: 1 },
description: "Số trang",
},
{
name: "limit",
in: "query",
schema: { type: "integer", default: 50 },
description: "Số bản ghi mỗi trang (tối đa 100)",
},
],
responses: {
200: {
description: "Lấy nhật ký thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: {
type: "object",
properties: {
items: {
type: "array",
items: { $ref: "#/components/schemas/CrawlJobLog" },
},
meta: {
type: "object",
properties: {
total: { type: "integer" },
page: { type: "integer" },
limit: { type: "integer" },
totalPages: { type: "integer" },
},
},
},
},
},
},
},
},
},
401: { description: "Chưa xác thực" },
404: { description: "Không tìm thấy crawl job" },
},
},
},
"/crawl-jobs/{id}/events": {
get: {
tags: ["Crawl Jobs"],
summary: "Server-Sent Events (SSE) theo dõi tiến độ Job thời gian thực",
description:
"Mở luồng SSE nhận dữ liệu tiến độ crawl định kỳ (mỗi 3 giây) cho đến khi job hoàn tất.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID của crawl job",
},
],
responses: {
200: {
description: "Luồng sự kiện SSE (text/event-stream)",
content: {
"text/event-stream": {
schema: { type: "string" },
},
},
},
401: { description: "Chưa xác thực" },
404: { description: "Không tìm thấy crawl job" },
},
},
},
"/crawl-jobs/{id}/pages": { "/crawl-jobs/{id}/pages": {
get: { get: {
tags: ["Crawl Jobs"], tags: ["Crawl Jobs"],
...@@ -1923,34 +2201,118 @@ export const swaggerPaths: Record<string, any> = { ...@@ -1923,34 +2201,118 @@ export const swaggerPaths: Record<string, any> = {
404: { description: "Không tìm thấy cấu hình Webhook" }, 404: { description: "Không tìm thấy cấu hình Webhook" },
}, },
}, },
}, patch: {
"/webhooks/deliveries": {
get: {
tags: ["Webhooks"], tags: ["Webhooks"],
summary: "Xem lịch sử gửi Webhook", summary: "Cập nhật cấu hình Webhook",
description: description:
"Xem toàn bộ lịch sử gửi webhook (delivery logs) bao gồm các nỗ lực gửi, trạng thái, mã phản hồi và lỗi nếu có.", "Cập nhật endpoint URL, signing secret, danh sách sự kiện đăng ký hoặc bật/tắt Webhook.",
parameters: [ parameters: [
{ {
name: "jobId", name: "id",
in: "query", in: "path",
schema: { type: "string" }, required: true,
description: "Lọc theo ID của crawl job", schema: { type: "string", format: "uuid" },
}, description: "ID cấu hình Webhook",
{
name: "status",
in: "query",
schema: { type: "string" },
description:
"Lọc theo trạng thái giao nhận (PENDING, SUCCESS, FAILED)",
}, },
], ],
responses: { requestBody: {
200: { required: true,
description: "Lấy lịch sử thành công", content: {
content: { "application/json": {
"application/json": { schema: { $ref: "#/components/schemas/UpdateWebhookConfigRequest" },
schema: { },
},
},
responses: {
200: {
description: "Cập nhật cấu hình thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: { $ref: "#/components/schemas/WebhookConfig" },
},
},
},
},
},
400: { description: "Dữ liệu yêu cầu không hợp lệ" },
401: { description: "Chưa xác thực" },
404: { description: "Không tìm thấy cấu hình Webhook" },
},
},
},
"/webhooks/configs/{id}/test": {
post: {
tags: ["Webhooks"],
summary: "Kiểm tra kết nối Webhook (Ping Test)",
description:
"Gửi một payload mẫu có kèm HMAC signature tới Webhook URL để kiểm tra khả năng tiếp nhận.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID cấu hình Webhook",
},
],
responses: {
200: {
description: "Kiểm tra Webhook hoàn tất",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: {
type: "object",
properties: {
statusCode: { type: "integer", example: 200 },
responseBody: { type: "string", example: "ok" },
success: { type: "boolean", example: true },
},
},
},
},
},
},
},
401: { description: "Chưa xác thực" },
404: { description: "Không tìm thấy cấu hình Webhook" },
},
},
},
"/webhooks/deliveries": {
get: {
tags: ["Webhooks"],
summary: "Xem lịch sử gửi Webhook",
description:
"Xem toàn bộ lịch sử gửi webhook (delivery logs) bao gồm các nỗ lực gửi, trạng thái, mã phản hồi và lỗi nếu có.",
parameters: [
{
name: "jobId",
in: "query",
schema: { type: "string" },
description: "Lọc theo ID của crawl job",
},
{
name: "status",
in: "query",
schema: { type: "string" },
description:
"Lọc theo trạng thái giao nhận (PENDING, SUCCESS, FAILED)",
},
],
responses: {
200: {
description: "Lấy lịch sử thành công",
content: {
"application/json": {
schema: {
type: "object", type: "object",
properties: { properties: {
success: { type: "boolean", example: true }, success: { type: "boolean", example: true },
...@@ -1967,6 +2329,45 @@ export const swaggerPaths: Record<string, any> = { ...@@ -1967,6 +2329,45 @@ export const swaggerPaths: Record<string, any> = {
}, },
}, },
}, },
"/webhooks/deliveries/{id}/redeliver": {
post: {
tags: ["Webhooks"],
summary: "Gửi lại (Redeliver) Webhook thất bại",
description:
"Đưa thông báo webhook vào hàng đợi BullMQ để tiến hành gửi lại tới server đích.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID của bản ghi webhook delivery",
},
],
responses: {
200: {
description: "Đã đưa vào hàng đợi gửi lại",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
message: {
type: "string",
example: "Webhook redelivery enqueued successfully",
},
data: { $ref: "#/components/schemas/WebhookDelivery" },
},
},
},
},
},
401: { description: "Chưa xác thực" },
404: { description: "Không tìm thấy bản ghi webhook delivery" },
},
},
},
"/crawl-jobs/{id}/diff": { "/crawl-jobs/{id}/diff": {
get: { get: {
tags: ["Crawl Jobs"], tags: ["Crawl Jobs"],
...@@ -2648,4 +3049,398 @@ export const swaggerPaths: Record<string, any> = { ...@@ -2648,4 +3049,398 @@ export const swaggerPaths: Record<string, any> = {
}, },
}, },
}, },
"/exports": {
get: {
tags: ["Exports"],
summary: "Danh sách tất cả các bản xuất dữ liệu",
description:
"Lấy danh sách các tệp xuất dữ liệu crawl của người dùng có phân trang.",
parameters: [
{
name: "page",
in: "query",
schema: { type: "integer", default: 1 },
description: "Số trang",
},
{
name: "limit",
in: "query",
schema: { type: "integer", default: 20 },
description: "Số bản ghi mỗi trang (tối đa 100)",
},
],
responses: {
200: {
description: "Lấy danh sách bản xuất thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: {
type: "object",
properties: {
items: {
type: "array",
items: { $ref: "#/components/schemas/CrawlExport" },
},
meta: {
type: "object",
properties: {
total: { type: "integer" },
page: { type: "integer" },
limit: { type: "integer" },
totalPages: { type: "integer" },
},
},
},
},
},
},
},
},
},
401: { description: "Chưa xác thực" },
},
},
},
"/exports/{exportId}": {
delete: {
tags: ["Exports"],
summary: "Xóa bản xuất dữ liệu",
description:
"Xóa bản ghi xuất dữ liệu và tệp lưu trữ vật lý tương ứng trên ổ cứng hoặc S3.",
parameters: [
{
name: "exportId",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID của bản xuất dữ liệu",
},
],
responses: {
200: {
description: "Xóa bản xuất dữ liệu thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
message: {
type: "string",
example: "Export deleted successfully",
},
},
},
},
},
},
401: { description: "Chưa xác thực" },
403: { description: "Không có quyền EXPORTS_DELETE" },
404: { description: "Không tìm thấy bản xuất dữ liệu" },
},
},
},
"/extraction-templates": {
post: {
tags: ["Extraction Templates"],
summary: "Tạo template trích xuất dữ liệu có cấu trúc",
description:
"Định nghĩa bộ selector CSS và thuộc tính trích xuất nội dung cho một tên miền web cụ thể.",
requestBody: {
required: true,
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/CreateExtractionTemplateRequest",
},
},
},
},
responses: {
201: {
description: "Tạo template thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: { $ref: "#/components/schemas/ExtractionTemplate" },
},
},
},
},
},
400: {
description:
"Dữ liệu yêu cầu không hợp lệ hoặc đã tồn tại template cho domain này",
},
401: { description: "Chưa xác thực" },
},
},
get: {
tags: ["Extraction Templates"],
summary: "Danh sách template trích xuất dữ liệu",
description:
"Lấy toàn bộ danh sách các template trích xuất do người dùng hiện tại tạo.",
responses: {
200: {
description: "Lấy danh sách template thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: {
type: "array",
items: { $ref: "#/components/schemas/ExtractionTemplate" },
},
},
},
},
},
},
401: { description: "Chưa xác thực" },
},
},
},
"/extraction-templates/{id}": {
get: {
tags: ["Extraction Templates"],
summary: "Chi tiết template trích xuất",
description:
"Xem chi tiết thông tin và danh sách selectors của template.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID của template",
},
],
responses: {
200: {
description: "Lấy chi tiết template thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: { $ref: "#/components/schemas/ExtractionTemplate" },
},
},
},
},
},
401: { description: "Chưa xác thực" },
404: { description: "Không tìm thấy template" },
},
},
patch: {
tags: ["Extraction Templates"],
summary: "Cập nhật template trích xuất",
description:
"Cập nhật tên hoặc danh sách trường trích xuất của template.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID của template",
},
],
requestBody: {
required: true,
content: {
"application/json": {
schema: {
$ref: "#/components/schemas/UpdateExtractionTemplateRequest",
},
},
},
},
responses: {
200: {
description: "Cập nhật template thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: { $ref: "#/components/schemas/ExtractionTemplate" },
},
},
},
},
},
400: { description: "Dữ liệu yêu cầu không hợp lệ" },
401: { description: "Chưa xác thực" },
404: { description: "Không tìm thấy template" },
},
},
delete: {
tags: ["Extraction Templates"],
summary: "Xóa template trích xuất",
description: "Xóa cấu hình template trích xuất khỏi hệ thống.",
parameters: [
{
name: "id",
in: "path",
required: true,
schema: { type: "string", format: "uuid" },
description: "ID của template",
},
],
responses: {
200: {
description: "Xóa template thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: { type: "null" },
},
},
},
},
},
401: { description: "Chưa xác thực" },
404: { description: "Không tìm thấy template" },
},
},
},
"/health/liveness": {
get: {
tags: ["Health"],
summary: "Kiểm tra liveness của service",
description:
"Endpoint kiểm tra xem ứng dụng còn phản hồi hay không (dành cho Kubernetes / Docker health check).",
responses: {
200: {
description: "Ứng dụng hoạt động bình thường",
content: {
"application/json": {
schema: {
type: "object",
properties: {
status: { type: "string", example: "ok" },
uptimeSeconds: { type: "integer", example: 3600 },
timestamp: { type: "string", format: "date-time" },
nodeVersion: { type: "string", example: "v22.14.0" },
},
},
},
},
},
},
},
},
"/health/readiness": {
get: {
tags: ["Health"],
summary: "Kiểm tra readiness của service (PostgreSQL & Redis)",
description:
"Endpoint kiểm tra kết nối tới cơ sở dữ liệu PostgreSQL và hàng đợi Redis.",
responses: {
200: {
description: "Hệ thống sẵn sàng tiếp nhận request",
content: {
"application/json": {
schema: {
type: "object",
properties: {
status: { type: "string", example: "ready" },
checks: {
type: "object",
properties: {
database: {
type: "object",
properties: {
status: { type: "string", example: "up" },
latencyMs: { type: "integer", example: 5 },
},
},
redis: {
type: "object",
properties: {
status: { type: "string", example: "up" },
latencyMs: { type: "integer", example: 2 },
},
},
},
},
timestamp: { type: "string", format: "date-time" },
},
},
},
},
},
503: {
description: "Hệ thống chưa sẵn sàng, dịch vụ phụ trợ gặp lỗi",
},
},
},
},
"/health/metrics": {
get: {
tags: ["Health"],
summary: "Xem thông số metrics hệ thống và hàng đợi",
description:
"Trả về thông tin chi tiết về bộ nhớ RAM tiến trình, thời gian uptime và trạng thái các hàng đợi BullMQ.",
responses: {
200: {
description: "Lấy metrics thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
memory: { type: "object" },
uptime: { type: "number" },
queues: { type: "object" },
},
},
},
},
},
},
},
},
"/dashboard/stats": {
get: {
tags: ["Dashboard"],
summary: "Thống kê tổng quan hệ thống Crawler",
description:
"Thống kê tổng hợp số lượng crawl jobs theo trạng thái, số trang đã crawl, số lịch crawl đang chạy và tổng số tệp export.",
responses: {
200: {
description: "Lấy thống kê thành công",
content: {
"application/json": {
schema: {
type: "object",
properties: {
success: { type: "boolean", example: true },
data: { $ref: "#/components/schemas/DashboardStats" },
},
},
},
},
},
401: { description: "Chưa xác thực" },
403: { description: "Không có quyền DASHBOARD_READ" },
},
},
},
}; };
...@@ -14,32 +14,132 @@ ...@@ -14,32 +14,132 @@
"paths": { "paths": {
"/health/liveness": { "/health/liveness": {
"get": { "get": {
"description": "", "description": "Endpoint kiểm tra xem ứng dụng còn phản hồi hay không (dành cho Kubernetes / Docker health check).",
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Ứng dụng hoạt động bình thường",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "ok"
},
"uptimeSeconds": {
"type": "integer",
"example": 3600
},
"timestamp": {
"type": "string",
"format": "date-time"
},
"nodeVersion": {
"type": "string",
"example": "v22.14.0"
}
}
}
}
}
} }
} },
"tags": ["Health"],
"summary": "Kiểm tra liveness của service"
} }
}, },
"/health/readiness": { "/health/readiness": {
"get": { "get": {
"description": "", "description": "Endpoint kiểm tra kết nối tới cơ sở dữ liệu PostgreSQL và hàng đợi Redis.",
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Hệ thống sẵn sàng tiếp nhận request",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "ready"
},
"checks": {
"type": "object",
"properties": {
"database": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "up"
},
"latencyMs": {
"type": "integer",
"example": 5
}
}
},
"redis": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "up"
},
"latencyMs": {
"type": "integer",
"example": 2
}
}
}
}
},
"timestamp": {
"type": "string",
"format": "date-time"
}
}
}
}
}
},
"503": {
"description": "Hệ thống chưa sẵn sàng, dịch vụ phụ trợ gặp lỗi"
} }
} },
"tags": ["Health"],
"summary": "Kiểm tra readiness của service (PostgreSQL & Redis)"
} }
}, },
"/health/metrics": { "/health/metrics": {
"get": { "get": {
"description": "", "description": "Trả về thông tin chi tiết về bộ nhớ RAM tiến trình, thời gian uptime và trạng thái các hàng đợi BullMQ.",
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Lấy metrics thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"memory": {
"type": "object"
},
"uptime": {
"type": "number"
},
"queues": {
"type": "object"
}
}
}
}
}
} }
} },
"tags": ["Health"],
"summary": "Xem thông số metrics hệ thống và hàng đợi"
} }
}, },
"/auth/login": { "/auth/login": {
...@@ -83,9 +183,7 @@ ...@@ -83,9 +183,7 @@
"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": [ "tags": ["Auth"],
"Auth"
],
"summary": "Đăng nhập người dùng", "summary": "Đăng nhập người dùng",
"requestBody": { "requestBody": {
"required": true, "required": true,
...@@ -141,9 +239,7 @@ ...@@ -141,9 +239,7 @@
} }
} }
}, },
"tags": [ "tags": ["Auth"],
"Auth"
],
"summary": "Làm mới Access Token" "summary": "Làm mới Access Token"
} }
}, },
...@@ -182,9 +278,7 @@ ...@@ -182,9 +278,7 @@
} }
} }
}, },
"tags": [ "tags": ["Auth"],
"Auth"
],
"summary": "Đăng xuất" "summary": "Đăng xuất"
} }
}, },
...@@ -215,9 +309,7 @@ ...@@ -215,9 +309,7 @@
"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": [ "tags": ["Auth"],
"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": {
...@@ -249,9 +341,7 @@ ...@@ -249,9 +341,7 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["Auth"],
"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,
...@@ -265,10 +355,44 @@ ...@@ -265,10 +355,44 @@
} }
}, },
"patch": { "patch": {
"description": "", "description": "Cập nhật họ tên của người dùng hiện tại.",
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Cập nhật thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
},
"400": {
"description": "Dữ liệu yêu cầu không hợp lệ"
},
"401": {
"description": "Chưa xác thực"
}
},
"tags": ["Auth"],
"summary": "Cập nhật một phần thông tin cá nhân",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateMeRequest"
}
}
} }
} }
} }
...@@ -348,25 +472,71 @@ ...@@ -348,25 +472,71 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["Auth"],
"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"
} }
}, },
"/auth/avatar": { "/auth/avatar": {
"post": { "post": {
"description": "", "description": "Tải lên tệp ảnh đại diện cho người dùng hiện tại (JPG, PNG, WEBP, GIF, tối đa 2MB).",
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Tải lên ảnh đại diện thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"properties": {
"avatarUrl": {
"type": "string",
"example": "/api/v1/auth/avatar/avatar_123.jpg"
}
}
}
}
}
}
}
},
"400": {
"description": "Tệp không hợp lệ hoặc vượt kích thước"
},
"401": {
"description": "Chưa xác thực"
}
},
"tags": ["Auth"],
"summary": "Tải lên ảnh đại diện (Avatar)",
"requestBody": {
"required": true,
"content": {
"multipart/form-data": {
"schema": {
"type": "object",
"required": ["avatar"],
"properties": {
"avatar": {
"type": "string",
"format": "binary",
"description": "Tệp ảnh avatar tải lên"
}
}
}
}
} }
} }
} }
}, },
"/auth/avatar/{fileName}": { "/auth/avatar/{fileName}": {
"get": { "get": {
"description": "", "description": "Xem và tải tệp ảnh đại diện của người dùng.",
"parameters": [ "parameters": [
{ {
"name": "fileName", "name": "fileName",
...@@ -374,14 +544,46 @@ ...@@ -374,14 +544,46 @@
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string"
} },
"description": "Tên tệp ảnh đại diện"
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Tệp ảnh dạng binary",
"content": {
"image/jpeg": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/png": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/webp": {
"schema": {
"type": "string",
"format": "binary"
}
},
"image/gif": {
"schema": {
"type": "string",
"format": "binary"
}
}
}
},
"404": {
"description": "Không tìm thấy tệp ảnh đại diện"
} }
} },
"tags": ["Auth"],
"summary": "Tải hoặc hiển thị ảnh đại diện"
} }
}, },
"/auth/change-password": { "/auth/change-password": {
...@@ -415,9 +617,7 @@ ...@@ -415,9 +617,7 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["Auth"],
"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,
...@@ -458,9 +658,7 @@ ...@@ -458,9 +658,7 @@
"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": [ "tags": ["Auth"],
"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,
...@@ -502,9 +700,7 @@ ...@@ -502,9 +700,7 @@
"description": "Email không hợp lệ" "description": "Email không hợp lệ"
} }
}, },
"tags": [ "tags": ["Auth"],
"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,
...@@ -512,9 +708,7 @@ ...@@ -512,9 +708,7 @@
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
"required": [ "required": ["email"],
"email"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
...@@ -556,9 +750,7 @@ ...@@ -556,9 +750,7 @@
"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": [ "tags": ["Auth"],
"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,
...@@ -566,10 +758,7 @@ ...@@ -566,10 +758,7 @@
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
"required": [ "required": ["token", "password"],
"token",
"password"
],
"properties": { "properties": {
"token": { "token": {
"type": "string", "type": "string",
...@@ -614,9 +803,7 @@ ...@@ -614,9 +803,7 @@
"description": "Địa chỉ email không hợp lệ" "description": "Địa chỉ email không hợp lệ"
} }
}, },
"tags": [ "tags": ["Auth"],
"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,
...@@ -658,9 +845,7 @@ ...@@ -658,9 +845,7 @@
"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": [ "tags": ["Auth"],
"Auth"
],
"summary": "Xác thực địa chỉ email", "summary": "Xác thực địa chỉ email",
"requestBody": { "requestBody": {
"required": true, "required": true,
...@@ -668,9 +853,7 @@ ...@@ -668,9 +853,7 @@
"application/json": { "application/json": {
"schema": { "schema": {
"type": "object", "type": "object",
"required": [ "required": ["token"],
"token"
],
"properties": { "properties": {
"token": { "token": {
"type": "string", "type": "string",
...@@ -714,9 +897,7 @@ ...@@ -714,9 +897,7 @@
"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": [ "tags": ["Auth"],
"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,
...@@ -758,9 +939,7 @@ ...@@ -758,9 +939,7 @@
"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": [ "tags": ["Auth"],
"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,
...@@ -857,9 +1036,7 @@ ...@@ -857,9 +1036,7 @@
"description": "Không có quyền truy cập" "description": "Không có quyền truy cập"
} }
}, },
"tags": [ "tags": ["Users"],
"Users"
],
"summary": "Lấy danh sách người dùng" "summary": "Lấy danh sách người dùng"
}, },
"post": { "post": {
...@@ -904,9 +1081,7 @@ ...@@ -904,9 +1081,7 @@
} }
} }
}, },
"tags": [ "tags": ["Users"],
"Users"
],
"summary": "Tạo người dùng mới" "summary": "Tạo người dùng mới"
} }
}, },
...@@ -954,9 +1129,7 @@ ...@@ -954,9 +1129,7 @@
"description": "Không tìm thấy người dùng" "description": "Không tìm thấy người dùng"
} }
}, },
"tags": [ "tags": ["Users"],
"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": {
...@@ -1009,9 +1182,7 @@ ...@@ -1009,9 +1182,7 @@
} }
} }
}, },
"tags": [ "tags": ["Users"],
"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": {
...@@ -1052,9 +1223,7 @@ ...@@ -1052,9 +1223,7 @@
"description": "Không tìm thấy người dùng" "description": "Không tìm thấy người dùng"
} }
}, },
"tags": [ "tags": ["Users"],
"Users"
],
"summary": "Xóa người dùng" "summary": "Xóa người dùng"
} }
}, },
...@@ -1083,9 +1252,7 @@ ...@@ -1083,9 +1252,7 @@
"description": "Không có quyền users.roles.read" "description": "Không có quyền users.roles.read"
} }
}, },
"tags": [ "tags": ["Users"],
"Users"
],
"summary": "Xem Roles của User" "summary": "Xem Roles của User"
}, },
"put": { "put": {
...@@ -1118,9 +1285,7 @@ ...@@ -1118,9 +1285,7 @@
"description": "Không tìm thấy User hoặc Role" "description": "Không tìm thấy User hoặc Role"
} }
}, },
"tags": [ "tags": ["Users"],
"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,
...@@ -1171,9 +1336,7 @@ ...@@ -1171,9 +1336,7 @@
"description": "Không tìm thấy User hoặc Role" "description": "Không tìm thấy User hoặc Role"
} }
}, },
"tags": [ "tags": ["Users"],
"Users"
],
"summary": "Gán thêm một Role cho User" "summary": "Gán thêm một Role cho User"
}, },
"delete": { "delete": {
...@@ -1215,9 +1378,7 @@ ...@@ -1215,9 +1378,7 @@
"description": "Không tìm thấy User hoặc Role" "description": "Không tìm thấy User hoặc Role"
} }
}, },
"tags": [ "tags": ["Users"],
"Users"
],
"summary": "Gỡ một Role khỏi User" "summary": "Gỡ một Role khỏi User"
} }
}, },
...@@ -1274,9 +1435,7 @@ ...@@ -1274,9 +1435,7 @@
"description": "Không có quyền roles.read" "description": "Không có quyền roles.read"
} }
}, },
"tags": [ "tags": ["Roles"],
"Roles"
],
"summary": "Danh sách Roles" "summary": "Danh sách Roles"
}, },
"post": { "post": {
...@@ -1298,9 +1457,7 @@ ...@@ -1298,9 +1457,7 @@
"description": "Role slug đã tồn tại" "description": "Role slug đã tồn tại"
} }
}, },
"tags": [ "tags": ["Roles"],
"Roles"
],
"summary": "Tạo Role tùy chỉnh", "summary": "Tạo Role tùy chỉnh",
"requestBody": { "requestBody": {
"required": true, "required": true,
...@@ -1342,9 +1499,7 @@ ...@@ -1342,9 +1499,7 @@
"description": "Không tìm thấy Role" "description": "Không tìm thấy Role"
} }
}, },
"tags": [ "tags": ["Roles"],
"Roles"
],
"summary": "Chi tiết Role" "summary": "Chi tiết Role"
}, },
"patch": { "patch": {
...@@ -1377,9 +1532,7 @@ ...@@ -1377,9 +1532,7 @@
"description": "Không tìm thấy Role" "description": "Không tìm thấy Role"
} }
}, },
"tags": [ "tags": ["Roles"],
"Roles"
],
"summary": "Cập nhật Role", "summary": "Cập nhật Role",
"requestBody": { "requestBody": {
"required": true, "required": true,
...@@ -1422,9 +1575,7 @@ ...@@ -1422,9 +1575,7 @@
"description": "Không tìm thấy Role" "description": "Không tìm thấy Role"
} }
}, },
"tags": [ "tags": ["Roles"],
"Roles"
],
"summary": "Xóa Role" "summary": "Xóa Role"
} }
}, },
...@@ -1456,9 +1607,7 @@ ...@@ -1456,9 +1607,7 @@
"description": "Không tìm thấy Role" "description": "Không tìm thấy Role"
} }
}, },
"tags": [ "tags": ["Roles"],
"Roles"
],
"summary": "Xem danh sách Permissions của Role" "summary": "Xem danh sách Permissions của Role"
}, },
"put": { "put": {
...@@ -1488,9 +1637,7 @@ ...@@ -1488,9 +1637,7 @@
"description": "Không tìm thấy Role" "description": "Không tìm thấy Role"
} }
}, },
"tags": [ "tags": ["Roles"],
"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,
...@@ -1529,9 +1676,7 @@ ...@@ -1529,9 +1676,7 @@
"description": "Không có quyền roles.read" "description": "Không có quyền roles.read"
} }
}, },
"tags": [ "tags": ["Roles"],
"Roles"
],
"summary": "Danh sách Users thuộc Role" "summary": "Danh sách Users thuộc Role"
} }
}, },
...@@ -1565,9 +1710,7 @@ ...@@ -1565,9 +1710,7 @@
"description": "Không có quyền permissions.read" "description": "Không có quyền permissions.read"
} }
}, },
"tags": [ "tags": ["Permissions"],
"Permissions"
],
"summary": "Danh mục Permissions hệ thống" "summary": "Danh mục Permissions hệ thống"
} }
}, },
...@@ -1599,20 +1742,42 @@ ...@@ -1599,20 +1742,42 @@
"description": "Không tìm thấy Permission" "description": "Không tìm thấy Permission"
} }
}, },
"tags": [ "tags": ["Permissions"],
"Permissions"
],
"summary": "Chi tiết Permission" "summary": "Chi tiết Permission"
} }
}, },
"/dashboard/stats": { "/dashboard/stats": {
"get": { "get": {
"description": "", "description": "Thống kê tổng hợp số lượng crawl jobs theo trạng thái, số trang đã crawl, số lịch crawl đang chạy và tổng số tệp export.",
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Lấy thống kê thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"$ref": "#/components/schemas/DashboardStats"
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
},
"403": {
"description": "Không có quyền DASHBOARD_READ"
} }
} },
"tags": ["Dashboard"],
"summary": "Thống kê tổng quan hệ thống Crawler"
} }
}, },
"/crawl-jobs": { "/crawl-jobs": {
...@@ -1654,9 +1819,7 @@ ...@@ -1654,9 +1819,7 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["Crawl Jobs"],
"Crawl Jobs"
],
"summary": "Tạo crawl job mới", "summary": "Tạo crawl job mới",
"requestBody": { "requestBody": {
"required": true, "required": true,
...@@ -1745,9 +1908,7 @@ ...@@ -1745,9 +1908,7 @@
} }
} }
}, },
"tags": [ "tags": ["Crawl Jobs"],
"Crawl Jobs"
],
"summary": "Lấy danh sách các crawl jobs" "summary": "Lấy danh sách các crawl jobs"
} }
}, },
...@@ -1789,116 +1950,234 @@ ...@@ -1789,116 +1950,234 @@
"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": [ "tags": ["Crawl Jobs"],
"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": {
"description": "", "description": "Xóa hoàn toàn crawl job cùng toàn bộ dữ liệu trang, assets và export liên quan.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
}, },
{ "description": "ID của crawl job"
"name": "x-api-key",
"in": "header",
"schema": {
"type": "string"
}
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Xóa crawl job thành công",
} "content": {
} "application/json": {
} "schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"message": {
"type": "string",
"example": "Crawl job deleted successfully"
}
}
}
}
}
},
"400": {
"description": "Không thể xóa job đang chạy"
},
"401": {
"description": "Chưa xác thực"
},
"403": {
"description": "Không có quyền CRAWL_JOBS_DELETE"
},
"404": {
"description": "Không tìm thấy crawl job"
}
},
"tags": ["Crawl Jobs"],
"summary": "Xóa crawl job"
}
}, },
"/crawl-jobs/{id}/rerun": { "/crawl-jobs/{id}/rerun": {
"post": { "post": {
"description": "", "description": "Khởi tạo một job mới kế thừa toàn bộ startUrl, mode, maxPages và maxDepth từ job trước đó.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
}, },
{ "description": "ID của crawl job cần chạy lại"
"name": "x-api-key",
"in": "header",
"schema": {
"type": "string"
}
} }
], ],
"responses": { "responses": {
"default": { "201": {
"description": "" "description": "Khởi tạo job chạy lại thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"$ref": "#/components/schemas/CrawlJob"
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
},
"403": {
"description": "Không có quyền CRAWL_JOBS_RETRY"
},
"404": {
"description": "Không tìm thấy crawl job"
} }
} },
"tags": ["Crawl Jobs"],
"summary": "Chạy lại crawl job với cấu hình ban đầu"
} }
}, },
"/crawl-jobs/{id}/logs": { "/crawl-jobs/{id}/logs": {
"get": { "get": {
"description": "", "description": "Lấy danh sách các bản ghi log tiến trình thực thi từ worker theo từng bước.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
},
"description": "ID của crawl job"
}, },
{ {
"name": "x-api-key", "name": "page",
"in": "header", "in": "query",
"schema": { "schema": {
"type": "string" "type": "integer",
} "default": 1
},
"description": "Số trang"
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"default": 50
},
"description": "Số bản ghi mỗi trang (tối đa 100)"
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Lấy nhật ký thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CrawlJobLog"
}
},
"meta": {
"type": "object",
"properties": {
"total": {
"type": "integer"
},
"page": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"totalPages": {
"type": "integer"
}
}
}
}
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
},
"404": {
"description": "Không tìm thấy crawl job"
} }
} },
"tags": ["Crawl Jobs"],
"summary": "Xem nhật ký (logs) chi tiết của crawl job"
} }
}, },
"/crawl-jobs/{id}/events": { "/crawl-jobs/{id}/events": {
"get": { "get": {
"description": "", "description": "Mở luồng SSE nhận dữ liệu tiến độ crawl định kỳ (mỗi 3 giây) cho đến khi job hoàn tất.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
}, },
{ "description": "ID của crawl job"
"name": "x-api-key",
"in": "header",
"schema": {
"type": "string"
}
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Luồng sự kiện SSE (text/event-stream)",
"content": {
"text/event-stream": {
"schema": {
"type": "string"
}
}
}
},
"401": {
"description": "Chưa xác thực"
},
"404": {
"description": "Không tìm thấy crawl job"
} }
} },
"tags": ["Crawl Jobs"],
"summary": "Server-Sent Events (SSE) theo dõi tiến độ Job thời gian thực"
} }
}, },
"/crawl-jobs/{id}/cancel": { "/crawl-jobs/{id}/cancel": {
...@@ -1942,9 +2221,7 @@ ...@@ -1942,9 +2221,7 @@
"description": "Không tìm thấy crawl job" "description": "Không tìm thấy crawl job"
} }
}, },
"tags": [ "tags": ["Crawl Jobs"],
"Crawl Jobs"
],
"summary": "Hủy một crawl job đang chạy" "summary": "Hủy một crawl job đang chạy"
} }
}, },
...@@ -2148,10 +2425,7 @@ ...@@ -2148,10 +2425,7 @@
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string", "type": "string",
"enum": [ "enum": ["asc", "desc"],
"asc",
"desc"
],
"default": "asc" "default": "asc"
}, },
"description": "Thứ tự sắp xếp" "description": "Thứ tự sắp xếp"
...@@ -2212,9 +2486,7 @@ ...@@ -2212,9 +2486,7 @@
} }
} }
}, },
"tags": [ "tags": ["Crawl Jobs"],
"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"
} }
}, },
...@@ -2418,10 +2690,7 @@ ...@@ -2418,10 +2690,7 @@
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string", "type": "string",
"enum": [ "enum": ["asc", "desc"],
"asc",
"desc"
],
"default": "asc" "default": "asc"
}, },
"description": "Thứ tự sắp xếp" "description": "Thứ tự sắp xếp"
...@@ -2556,9 +2825,7 @@ ...@@ -2556,9 +2825,7 @@
"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": [ "tags": ["Crawl Jobs"],
"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"
} }
}, },
...@@ -2600,9 +2867,7 @@ ...@@ -2600,9 +2867,7 @@
} }
} }
}, },
"tags": [ "tags": ["Crawl Jobs"],
"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": {
...@@ -2639,9 +2904,7 @@ ...@@ -2639,9 +2904,7 @@
} }
} }
}, },
"tags": [ "tags": ["Crawl Jobs"],
"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,
...@@ -2680,9 +2943,7 @@ ...@@ -2680,9 +2943,7 @@
} }
} }
}, },
"tags": [ "tags": ["Crawl Jobs"],
"Crawl Jobs"
],
"summary": "Tải xuống file export mới nhất" "summary": "Tải xuống file export mới nhất"
} }
}, },
...@@ -2723,14 +2984,7 @@ ...@@ -2723,14 +2984,7 @@
"required": false, "required": false,
"schema": { "schema": {
"type": "string", "type": "string",
"enum": [ "enum": ["IMAGE", "LINK", "PDF", "FILE", "VIDEO", "OTHER"]
"IMAGE",
"LINK",
"PDF",
"FILE",
"VIDEO",
"OTHER"
]
}, },
"description": "Lọc theo loại asset" "description": "Lọc theo loại asset"
} }
...@@ -2822,9 +3076,7 @@ ...@@ -2822,9 +3076,7 @@
"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": [ "tags": ["Crawl Jobs"],
"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)"
} }
}, },
...@@ -2877,9 +3129,7 @@ ...@@ -2877,9 +3129,7 @@
"description": "Không tìm thấy crawl job" "description": "Không tìm thấy crawl job"
} }
}, },
"tags": [ "tags": ["Crawl Jobs"],
"Crawl Jobs"
],
"summary": "Xem báo cáo thay đổi (Diff Report)" "summary": "Xem báo cáo thay đổi (Diff Report)"
} }
}, },
...@@ -2924,9 +3174,7 @@ ...@@ -2924,9 +3174,7 @@
"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": [ "tags": ["Crawl Jobs"],
"Crawl Jobs"
],
"summary": "Tải file diff_report.json" "summary": "Tải file diff_report.json"
} }
}, },
...@@ -2968,9 +3216,7 @@ ...@@ -2968,9 +3216,7 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["Crawl Schedules"],
"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,
...@@ -2999,12 +3245,7 @@ ...@@ -2999,12 +3245,7 @@
"in": "query", "in": "query",
"schema": { "schema": {
"type": "string", "type": "string",
"enum": [ "enum": ["DAILY", "WEEKLY", "MONTHLY", "CUSTOM"]
"DAILY",
"WEEKLY",
"MONTHLY",
"CUSTOM"
]
}, },
"description": "Lọc theo tần suất" "description": "Lọc theo tần suất"
}, },
...@@ -3073,9 +3314,7 @@ ...@@ -3073,9 +3314,7 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["Crawl Schedules"],
"Crawl Schedules"
],
"summary": "Lấy danh sách lịch crawl định kỳ" "summary": "Lấy danh sách lịch crawl định kỳ"
} }
}, },
...@@ -3111,9 +3350,7 @@ ...@@ -3111,9 +3350,7 @@
"description": "Không tìm thấy lịch crawl" "description": "Không tìm thấy lịch crawl"
} }
}, },
"tags": [ "tags": ["Crawl Schedules"],
"Crawl Schedules"
],
"summary": "Xem chi tiết lịch crawl" "summary": "Xem chi tiết lịch crawl"
}, },
"patch": { "patch": {
...@@ -3158,9 +3395,7 @@ ...@@ -3158,9 +3395,7 @@
"description": "Không tìm thấy lịch crawl" "description": "Không tìm thấy lịch crawl"
} }
}, },
"tags": [ "tags": ["Crawl Schedules"],
"Crawl Schedules"
],
"summary": "Cập nhật lịch crawl", "summary": "Cập nhật lịch crawl",
"requestBody": { "requestBody": {
"required": true, "required": true,
...@@ -3210,9 +3445,7 @@ ...@@ -3210,9 +3445,7 @@
"description": "Không tìm thấy lịch crawl" "description": "Không tìm thấy lịch crawl"
} }
}, },
"tags": [ "tags": ["Crawl Schedules"],
"Crawl Schedules"
],
"summary": "Xóa lịch crawl" "summary": "Xóa lịch crawl"
} }
}, },
...@@ -3257,9 +3490,7 @@ ...@@ -3257,9 +3490,7 @@
"description": "Không tìm thấy lịch crawl" "description": "Không tìm thấy lịch crawl"
} }
}, },
"tags": [ "tags": ["Crawl Schedules"],
"Crawl Schedules"
],
"summary": "Kích hoạt chạy ngay lịch crawl" "summary": "Kích hoạt chạy ngay lịch crawl"
} }
}, },
...@@ -3336,79 +3567,13 @@ ...@@ -3336,79 +3567,13 @@
"description": "Không tìm thấy lịch crawl" "description": "Không tìm thấy lịch crawl"
} }
}, },
"tags": [ "tags": ["Crawl Schedules"],
"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"
} }
}, },
"/exports": { "/exports": {
"get": { "get": {
"description": "", "description": "Lấy danh sách các tệp xuất dữ liệu crawl của người dùng có phân trang.",
"responses": {
"default": {
"description": ""
}
}
}
},
"/exports/{exportId}/download": {
"get": {
"description": "Tải xuống tệp dữ liệu đã xuất cụ thể theo ID của tệp.",
"parameters": [
{
"name": "exportId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID của file export"
}
],
"responses": {
"200": {
"description": "Trả về file binary để tải xuống",
"headers": {
"Content-Disposition": {
"type": "string",
"description": "attachment; filename=export.json"
}
}
},
"404": {
"description": "Không tìm thấy file export"
}
},
"tags": [
"Crawl Exports"
],
"summary": "Tải xuống tệp export theo ID"
}
},
"/exports/{exportId}": {
"delete": {
"description": "",
"parameters": [
{
"name": "exportId",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"default": {
"description": ""
}
}
}
},
"/audit-logs": {
"get": {
"description": "Lấy danh sách phân trang các hành động được ghi nhật ký trong hệ thống. Chỉ có ADMIN mới có quyền truy cập.",
"parameters": [ "parameters": [
{ {
"name": "page", "name": "page",
...@@ -3417,14 +3582,172 @@ ...@@ -3417,14 +3582,172 @@
"type": "integer", "type": "integer",
"default": 1 "default": 1
}, },
"description": "Số trang cần lấy" "description": "Số trang"
}, },
{ {
"name": "limit", "name": "limit",
"in": "query", "in": "query",
"schema": { "schema": {
"type": "integer", "type": "integer",
"default": 10 "default": 20
},
"description": "Số bản ghi mỗi trang (tối đa 100)"
}
],
"responses": {
"200": {
"description": "Lấy danh sách bản xuất thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CrawlExport"
}
},
"meta": {
"type": "object",
"properties": {
"total": {
"type": "integer"
},
"page": {
"type": "integer"
},
"limit": {
"type": "integer"
},
"totalPages": {
"type": "integer"
}
}
}
}
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
}
},
"tags": ["Exports"],
"summary": "Danh sách tất cả các bản xuất dữ liệu"
}
},
"/exports/{exportId}/download": {
"get": {
"description": "Tải xuống tệp dữ liệu đã xuất cụ thể theo ID của tệp.",
"parameters": [
{
"name": "exportId",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "ID của file export"
}
],
"responses": {
"200": {
"description": "Trả về file binary để tải xuống",
"headers": {
"Content-Disposition": {
"type": "string",
"description": "attachment; filename=export.json"
}
}
},
"404": {
"description": "Không tìm thấy file export"
}
},
"tags": ["Crawl Exports"],
"summary": "Tải xuống tệp export theo ID"
}
},
"/exports/{exportId}": {
"delete": {
"description": "Xóa bản ghi xuất dữ liệu và tệp lưu trữ vật lý tương ứng trên ổ cứng hoặc S3.",
"parameters": [
{
"name": "exportId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
},
"description": "ID của bản xuất dữ liệu"
}
],
"responses": {
"200": {
"description": "Xóa bản xuất dữ liệu thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"message": {
"type": "string",
"example": "Export deleted successfully"
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
},
"403": {
"description": "Không có quyền EXPORTS_DELETE"
},
"404": {
"description": "Không tìm thấy bản xuất dữ liệu"
}
},
"tags": ["Exports"],
"summary": "Xóa bản xuất dữ liệu"
}
},
"/audit-logs": {
"get": {
"description": "Lấy danh sách phân trang các hành động được ghi nhật ký trong hệ thống. Chỉ có ADMIN mới có quyền truy cập.",
"parameters": [
{
"name": "page",
"in": "query",
"schema": {
"type": "integer",
"default": 1
},
"description": "Số trang cần lấy"
},
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"default": 10
}, },
"description": "Số lượng bản ghi mỗi trang" "description": "Số lượng bản ghi mỗi trang"
}, },
...@@ -3520,9 +3843,7 @@ ...@@ -3520,9 +3843,7 @@
"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": [ "tags": ["Audit Logs"],
"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"
} }
}, },
...@@ -3556,9 +3877,7 @@ ...@@ -3556,9 +3877,7 @@
"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": [ "tags": ["API Keys"],
"API Keys"
],
"security": [ "security": [
{ {
"BearerAuth": [] "BearerAuth": []
...@@ -3605,9 +3924,7 @@ ...@@ -3605,9 +3924,7 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["API Keys"],
"API Keys"
],
"security": [ "security": [
{ {
"BearerAuth": [] "BearerAuth": []
...@@ -3661,9 +3978,7 @@ ...@@ -3661,9 +3978,7 @@
"description": "Trạng thái không hợp lệ" "description": "Trạng thái không hợp lệ"
} }
}, },
"tags": [ "tags": ["API Keys"],
"API Keys"
],
"security": [ "security": [
{ {
"BearerAuth": [] "BearerAuth": []
...@@ -3722,9 +4037,7 @@ ...@@ -3722,9 +4037,7 @@
"description": "Không tìm thấy API Key" "description": "Không tìm thấy API Key"
} }
}, },
"tags": [ "tags": ["API Keys"],
"API Keys"
],
"security": [ "security": [
{ {
"BearerAuth": [] "BearerAuth": []
...@@ -3763,9 +4076,7 @@ ...@@ -3763,9 +4076,7 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["Webhooks"],
"Webhooks"
],
"summary": "Tạo cấu hình Webhook", "summary": "Tạo cấu hình Webhook",
"requestBody": { "requestBody": {
"required": true, "required": true,
...@@ -3807,28 +4118,65 @@ ...@@ -3807,28 +4118,65 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["Webhooks"],
"Webhooks"
],
"summary": "Xem danh sách Webhook configs" "summary": "Xem danh sách Webhook configs"
} }
}, },
"/webhooks/configs/{id}": { "/webhooks/configs/{id}": {
"patch": { "patch": {
"description": "", "description": "Cập nhật endpoint URL, signing secret, danh sách sự kiện đăng ký hoặc bật/tắt Webhook.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
},
"description": "ID cấu hình Webhook"
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Cập nhật cấu hình thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"$ref": "#/components/schemas/WebhookConfig"
}
}
}
}
}
},
"400": {
"description": "Dữ liệu yêu cầu không hợp lệ"
},
"401": {
"description": "Chưa xác thực"
},
"404": {
"description": "Không tìm thấy cấu hình Webhook"
}
},
"tags": ["Webhooks"],
"summary": "Cập nhật cấu hình Webhook",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateWebhookConfigRequest"
}
}
} }
} }
}, },
...@@ -3872,30 +4220,68 @@ ...@@ -3872,30 +4220,68 @@
"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": [ "tags": ["Webhooks"],
"Webhooks"
],
"summary": "Xóa cấu hình Webhook" "summary": "Xóa cấu hình Webhook"
} }
}, },
"/webhooks/configs/{id}/test": { "/webhooks/configs/{id}/test": {
"post": { "post": {
"description": "", "description": "Gửi một payload mẫu có kèm HMAC signature tới Webhook URL để kiểm tra khả năng tiếp nhận.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
},
"description": "ID cấu hình Webhook"
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Kiểm tra Webhook hoàn tất",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "object",
"properties": {
"statusCode": {
"type": "integer",
"example": 200
},
"responseBody": {
"type": "string",
"example": "ok"
},
"success": {
"type": "boolean",
"example": true
}
}
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
},
"404": {
"description": "Không tìm thấy cấu hình Webhook"
} }
} },
"tags": ["Webhooks"],
"summary": "Kiểm tra kết nối Webhook (Ping Test)"
} }
}, },
"/webhooks/deliveries": { "/webhooks/deliveries": {
...@@ -3946,104 +4332,281 @@ ...@@ -3946,104 +4332,281 @@
"description": "Chưa xác thực" "description": "Chưa xác thực"
} }
}, },
"tags": [ "tags": ["Webhooks"],
"Webhooks"
],
"summary": "Xem lịch sử gửi Webhook" "summary": "Xem lịch sử gửi Webhook"
} }
}, },
"/webhooks/deliveries/{id}/redeliver": { "/webhooks/deliveries/{id}/redeliver": {
"post": { "post": {
"description": "", "description": "Đưa thông báo webhook vào hàng đợi BullMQ để tiến hành gửi lại tới server đích.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
},
"description": "ID của bản ghi webhook delivery"
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Đã đưa vào hàng đợi gửi lại",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"message": {
"type": "string",
"example": "Webhook redelivery enqueued successfully"
},
"data": {
"$ref": "#/components/schemas/WebhookDelivery"
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
},
"404": {
"description": "Không tìm thấy bản ghi webhook delivery"
} }
} },
"tags": ["Webhooks"],
"summary": "Gửi lại (Redeliver) Webhook thất bại"
} }
}, },
"/extraction-templates": { "/extraction-templates": {
"post": { "post": {
"description": "", "description": "Định nghĩa bộ selector CSS và thuộc tính trích xuất nội dung cho một tên miền web cụ thể.",
"responses": { "responses": {
"default": { "201": {
"description": "" "description": "Tạo template thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"$ref": "#/components/schemas/ExtractionTemplate"
}
}
}
}
}
},
"400": {
"description": "Dữ liệu yêu cầu không hợp lệ hoặc đã tồn tại template cho domain này"
},
"401": {
"description": "Chưa xác thực"
}
},
"tags": ["Extraction Templates"],
"summary": "Tạo template trích xuất dữ liệu có cấu trúc",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateExtractionTemplateRequest"
}
}
} }
} }
}, },
"get": { "get": {
"description": "", "description": "Lấy toàn bộ danh sách các template trích xuất do người dùng hiện tại tạo.",
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Lấy danh sách template thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ExtractionTemplate"
}
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
} }
} },
"tags": ["Extraction Templates"],
"summary": "Danh sách template trích xuất dữ liệu"
} }
}, },
"/extraction-templates/{id}": { "/extraction-templates/{id}": {
"get": { "get": {
"description": "", "description": "Xem chi tiết thông tin và danh sách selectors của template.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
},
"description": "ID của template"
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Lấy chi tiết template thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"$ref": "#/components/schemas/ExtractionTemplate"
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
},
"404": {
"description": "Không tìm thấy template"
} }
} },
"tags": ["Extraction Templates"],
"summary": "Chi tiết template trích xuất"
}, },
"patch": { "patch": {
"description": "", "description": "Cập nhật tên hoặc danh sách trường trích xuất của template.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
},
"description": "ID của template"
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Cập nhật template thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"$ref": "#/components/schemas/ExtractionTemplate"
}
}
}
}
}
},
"400": {
"description": "Dữ liệu yêu cầu không hợp lệ"
},
"401": {
"description": "Chưa xác thực"
},
"404": {
"description": "Không tìm thấy template"
}
},
"tags": ["Extraction Templates"],
"summary": "Cập nhật template trích xuất",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateExtractionTemplateRequest"
}
}
} }
} }
}, },
"delete": { "delete": {
"description": "", "description": "Xóa cấu hình template trích xuất khỏi hệ thống.",
"parameters": [ "parameters": [
{ {
"name": "id", "name": "id",
"in": "path", "in": "path",
"required": true, "required": true,
"schema": { "schema": {
"type": "string" "type": "string",
} "format": "uuid"
},
"description": "ID của template"
} }
], ],
"responses": { "responses": {
"default": { "200": {
"description": "" "description": "Xóa template thành công",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"success": {
"type": "boolean",
"example": true
},
"data": {
"type": "null"
}
}
}
}
}
},
"401": {
"description": "Chưa xác thực"
},
"404": {
"description": "Không tìm thấy template"
} }
} },
"tags": ["Extraction Templates"],
"summary": "Xóa template trích xuất"
} }
} }
}, },
...@@ -4079,11 +4642,7 @@ ...@@ -4079,11 +4642,7 @@
}, },
"role": { "role": {
"type": "string", "type": "string",
"enum": [ "enum": ["ADMIN", "CRAWLER_USER", "VIEWER"]
"ADMIN",
"CRAWLER_USER",
"VIEWER"
]
}, },
"isActive": { "isActive": {
"type": "boolean" "type": "boolean"
...@@ -4112,12 +4671,7 @@ ...@@ -4112,12 +4671,7 @@
}, },
"mode": { "mode": {
"type": "string", "type": "string",
"enum": [ "enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"]
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
]
}, },
"status": { "status": {
"type": "string", "type": "string",
...@@ -4328,10 +4882,7 @@ ...@@ -4328,10 +4882,7 @@
}, },
"type": { "type": {
"type": "string", "type": "string",
"enum": [ "enum": ["internal", "external"]
"internal",
"external"
]
} }
} }
} }
...@@ -4417,21 +4968,11 @@ ...@@ -4417,21 +4968,11 @@
}, },
"exportType": { "exportType": {
"type": "string", "type": "string",
"enum": [ "enum": ["JSON", "CSV", "XLSX", "MARKDOWN", "ZIP"]
"JSON",
"CSV",
"XLSX",
"MARKDOWN",
"ZIP"
]
}, },
"status": { "status": {
"type": "string", "type": "string",
"enum": [ "enum": ["PENDING", "COMPLETED", "FAILED"]
"PENDING",
"COMPLETED",
"FAILED"
]
}, },
"fileName": { "fileName": {
"type": "string" "type": "string"
...@@ -4454,10 +4995,7 @@ ...@@ -4454,10 +4995,7 @@
}, },
"LoginRequest": { "LoginRequest": {
"type": "object", "type": "object",
"required": [ "required": ["email", "password"],
"email",
"password"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
...@@ -4472,10 +5010,7 @@ ...@@ -4472,10 +5010,7 @@
}, },
"RegisterRequest": { "RegisterRequest": {
"type": "object", "type": "object",
"required": [ "required": ["email", "password"],
"email",
"password"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
...@@ -4494,9 +5029,7 @@ ...@@ -4494,9 +5029,7 @@
}, },
"ResendVerificationRequest": { "ResendVerificationRequest": {
"type": "object", "type": "object",
"required": [ "required": ["email"],
"email"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
...@@ -4507,9 +5040,7 @@ ...@@ -4507,9 +5040,7 @@
}, },
"RequestDeactivationRequest": { "RequestDeactivationRequest": {
"type": "object", "type": "object",
"required": [ "required": ["password"],
"password"
],
"properties": { "properties": {
"password": { "password": {
"type": "string", "type": "string",
...@@ -4520,9 +5051,7 @@ ...@@ -4520,9 +5051,7 @@
}, },
"ConfirmDeactivationRequest": { "ConfirmDeactivationRequest": {
"type": "object", "type": "object",
"required": [ "required": ["token"],
"token"
],
"properties": { "properties": {
"token": { "token": {
"type": "string", "type": "string",
...@@ -4533,9 +5062,7 @@ ...@@ -4533,9 +5062,7 @@
}, },
"RefreshRequest": { "RefreshRequest": {
"type": "object", "type": "object",
"required": [ "required": ["refreshToken"],
"refreshToken"
],
"properties": { "properties": {
"refreshToken": { "refreshToken": {
"type": "string", "type": "string",
...@@ -4545,9 +5072,7 @@ ...@@ -4545,9 +5072,7 @@
}, },
"LogoutRequest": { "LogoutRequest": {
"type": "object", "type": "object",
"required": [ "required": ["refreshToken"],
"refreshToken"
],
"properties": { "properties": {
"refreshToken": { "refreshToken": {
"type": "string", "type": "string",
...@@ -4566,11 +5091,7 @@ ...@@ -4566,11 +5091,7 @@
}, },
"ChangePasswordRequest": { "ChangePasswordRequest": {
"type": "object", "type": "object",
"required": [ "required": ["currentPassword", "newPassword", "confirmPassword"],
"currentPassword",
"newPassword",
"confirmPassword"
],
"properties": { "properties": {
"currentPassword": { "currentPassword": {
"type": "string", "type": "string",
...@@ -4588,10 +5109,7 @@ ...@@ -4588,10 +5109,7 @@
}, },
"CreateUserRequest": { "CreateUserRequest": {
"type": "object", "type": "object",
"required": [ "required": ["email", "password"],
"email",
"password"
],
"properties": { "properties": {
"email": { "email": {
"type": "string", "type": "string",
...@@ -4608,11 +5126,7 @@ ...@@ -4608,11 +5126,7 @@
}, },
"role": { "role": {
"type": "string", "type": "string",
"enum": [ "enum": ["ADMIN", "CRAWLER_USER", "VIEWER"],
"ADMIN",
"CRAWLER_USER",
"VIEWER"
],
"example": "CRAWLER_USER" "example": "CRAWLER_USER"
}, },
"maxPagesLimit": { "maxPagesLimit": {
...@@ -4638,11 +5152,7 @@ ...@@ -4638,11 +5152,7 @@
}, },
"role": { "role": {
"type": "string", "type": "string",
"enum": [ "enum": ["ADMIN", "CRAWLER_USER", "VIEWER"],
"ADMIN",
"CRAWLER_USER",
"VIEWER"
],
"example": "VIEWER" "example": "VIEWER"
}, },
"isActive": { "isActive": {
...@@ -4673,12 +5183,7 @@ ...@@ -4673,12 +5183,7 @@
}, },
"mode": { "mode": {
"type": "string", "type": "string",
"enum": [ "enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"],
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
],
"example": "CRAWL" "example": "CRAWL"
}, },
"maxPages": { "maxPages": {
...@@ -4699,38 +5204,25 @@ ...@@ -4699,38 +5204,25 @@
"type": "string", "type": "string",
"format": "uri" "format": "uri"
}, },
"example": [ "example": ["https://example.com/1", "https://example.com/2"],
"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": [ "required": ["exportType"],
"exportType"
],
"properties": { "properties": {
"exportType": { "exportType": {
"type": "string", "type": "string",
"enum": [ "enum": ["JSON", "CSV", "XLSX", "MARKDOWN", "ZIP"],
"JSON",
"CSV",
"XLSX",
"MARKDOWN",
"ZIP"
],
"example": "JSON" "example": "JSON"
} }
} }
}, },
"CreateApiKeyRequest": { "CreateApiKeyRequest": {
"type": "object", "type": "object",
"required": [ "required": ["name"],
"name"
],
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
...@@ -4749,9 +5241,7 @@ ...@@ -4749,9 +5241,7 @@
}, },
"UpdateApiKeyStatusRequest": { "UpdateApiKeyStatusRequest": {
"type": "object", "type": "object",
"required": [ "required": ["isActive"],
"isActive"
],
"additionalProperties": false, "additionalProperties": false,
"properties": { "properties": {
"isActive": { "isActive": {
...@@ -4848,11 +5338,7 @@ ...@@ -4848,11 +5338,7 @@
}, },
"CreateWebhookConfigRequest": { "CreateWebhookConfigRequest": {
"type": "object", "type": "object",
"required": [ "required": ["url", "secret", "events"],
"url",
"secret",
"events"
],
"properties": { "properties": {
"url": { "url": {
"type": "string", "type": "string",
...@@ -4867,15 +5353,9 @@ ...@@ -4867,15 +5353,9 @@
"type": "array", "type": "array",
"items": { "items": {
"type": "string", "type": "string",
"enum": [ "enum": ["job.completed", "job.failed"]
"job.completed",
"job.failed"
]
}, },
"example": [ "example": ["job.completed", "job.failed"]
"job.completed",
"job.failed"
]
} }
} }
}, },
...@@ -4936,11 +5416,7 @@ ...@@ -4936,11 +5416,7 @@
}, },
"status": { "status": {
"type": "string", "type": "string",
"enum": [ "enum": ["PENDING", "SUCCESS", "FAILED"]
"PENDING",
"SUCCESS",
"FAILED"
]
}, },
"statusCode": { "statusCode": {
"type": "integer", "type": "integer",
...@@ -5004,21 +5480,11 @@ ...@@ -5004,21 +5480,11 @@
}, },
"mode": { "mode": {
"type": "string", "type": "string",
"enum": [ "enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"]
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
]
}, },
"frequency": { "frequency": {
"type": "string", "type": "string",
"enum": [ "enum": ["DAILY", "WEEKLY", "MONTHLY", "CUSTOM"]
"DAILY",
"WEEKLY",
"MONTHLY",
"CUSTOM"
]
}, },
"cronExpression": { "cronExpression": {
"type": "string", "type": "string",
...@@ -5081,10 +5547,7 @@ ...@@ -5081,10 +5547,7 @@
}, },
"CreateCrawlScheduleRequest": { "CreateCrawlScheduleRequest": {
"type": "object", "type": "object",
"required": [ "required": ["name", "startUrl"],
"name",
"startUrl"
],
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
...@@ -5097,22 +5560,12 @@ ...@@ -5097,22 +5560,12 @@
}, },
"mode": { "mode": {
"type": "string", "type": "string",
"enum": [ "enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"],
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
],
"example": "CRAWL" "example": "CRAWL"
}, },
"frequency": { "frequency": {
"type": "string", "type": "string",
"enum": [ "enum": ["DAILY", "WEEKLY", "MONTHLY", "CUSTOM"],
"DAILY",
"WEEKLY",
"MONTHLY",
"CUSTOM"
],
"example": "DAILY" "example": "DAILY"
}, },
"cronExpression": { "cronExpression": {
...@@ -5175,21 +5628,11 @@ ...@@ -5175,21 +5628,11 @@
}, },
"mode": { "mode": {
"type": "string", "type": "string",
"enum": [ "enum": ["SCRAPE", "CRAWL", "SITEMAP", "URL_LIST"]
"SCRAPE",
"CRAWL",
"SITEMAP",
"URL_LIST"
]
}, },
"frequency": { "frequency": {
"type": "string", "type": "string",
"enum": [ "enum": ["DAILY", "WEEKLY", "MONTHLY", "CUSTOM"]
"DAILY",
"WEEKLY",
"MONTHLY",
"CUSTOM"
]
}, },
"cronExpression": { "cronExpression": {
"type": "string" "type": "string"
...@@ -5389,10 +5832,7 @@ ...@@ -5389,10 +5832,7 @@
}, },
"CreateRoleRequest": { "CreateRoleRequest": {
"type": "object", "type": "object",
"required": [ "required": ["name", "slug"],
"name",
"slug"
],
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
...@@ -5431,9 +5871,7 @@ ...@@ -5431,9 +5871,7 @@
}, },
"AssignRolePermissionsRequest": { "AssignRolePermissionsRequest": {
"type": "object", "type": "object",
"required": [ "required": ["permissionIds"],
"permissionIds"
],
"properties": { "properties": {
"permissionIds": { "permissionIds": {
"type": "array", "type": "array",
...@@ -5446,9 +5884,7 @@ ...@@ -5446,9 +5884,7 @@
}, },
"AssignUserRolesRequest": { "AssignUserRolesRequest": {
"type": "object", "type": "object",
"required": [ "required": ["roleIds"],
"roleIds"
],
"properties": { "properties": {
"roleIds": { "roleIds": {
"type": "array", "type": "array",
...@@ -5458,6 +5894,224 @@ ...@@ -5458,6 +5894,224 @@
} }
} }
} }
},
"UpdateWebhookConfigRequest": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "uri",
"example": "https://example.com/webhook"
},
"secret": {
"type": "string",
"minLength": 16,
"maxLength": 128,
"example": "new_webhook_secret_key_123456"
},
"events": {
"type": "array",
"items": {
"type": "string",
"enum": ["job.completed", "job.failed"]
},
"example": ["job.completed"]
},
"isActive": {
"type": "boolean",
"example": true
}
}
},
"ExtractionTemplateField": {
"type": "object",
"required": ["name", "selector", "attr", "required"],
"properties": {
"name": {
"type": "string",
"example": "title"
},
"selector": {
"type": "string",
"example": "h1.product-title"
},
"attr": {
"type": "string",
"example": "innerText"
},
"required": {
"type": "boolean",
"example": true
}
}
},
"ExtractionTemplate": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"userId": {
"type": "string",
"format": "uuid"
},
"name": {
"type": "string",
"example": "E-Commerce Product Extractor"
},
"domain": {
"type": "string",
"example": "example.com"
},
"fields": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ExtractionTemplateField"
}
},
"createdAt": {
"type": "string",
"format": "date-time"
},
"updatedAt": {
"type": "string",
"format": "date-time"
}
}
},
"CreateExtractionTemplateRequest": {
"type": "object",
"required": ["name", "domain", "fields"],
"properties": {
"name": {
"type": "string",
"example": "E-Commerce Product Extractor"
},
"domain": {
"type": "string",
"example": "example.com"
},
"fields": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ExtractionTemplateField"
}
}
}
},
"UpdateExtractionTemplateRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"example": "Updated Template Name"
},
"fields": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ExtractionTemplateField"
}
}
}
},
"CrawlJobLog": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"jobId": {
"type": "string",
"format": "uuid"
},
"level": {
"type": "string",
"enum": ["INFO", "WARN", "ERROR"]
},
"step": {
"type": "string",
"example": "FETCH_PAGE"
},
"message": {
"type": "string",
"example": "Successfully fetched page 1"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"DashboardStats": {
"type": "object",
"properties": {
"jobs": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"example": 42
},
"completed": {
"type": "integer",
"example": 35
},
"failed": {
"type": "integer",
"example": 3
},
"running": {
"type": "integer",
"example": 2
},
"pending": {
"type": "integer",
"example": 2
}
}
},
"pages": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"example": 1250
},
"successful": {
"type": "integer",
"example": 1200
},
"failed": {
"type": "integer",
"example": 50
}
}
},
"schedules": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"example": 5
},
"active": {
"type": "integer",
"example": 4
}
}
},
"exports": {
"type": "object",
"properties": {
"total": {
"type": "integer",
"example": 18
}
}
}
}
} }
} }
}, },
...@@ -5469,4 +6123,4 @@ ...@@ -5469,4 +6123,4 @@
"ApiKeyAuth": [] "ApiKeyAuth": []
} }
] ]
} }
\ No newline at end of file
...@@ -695,6 +695,122 @@ const rawSchemas = { ...@@ -695,6 +695,122 @@ const rawSchemas = {
}, },
}, },
}, },
UpdateWebhookConfigRequest: {
type: "object",
properties: {
url: {
type: "string",
format: "uri",
example: "https://example.com/webhook",
},
secret: {
type: "string",
minLength: 16,
maxLength: 128,
example: "new_webhook_secret_key_123456",
},
events: {
type: "array",
items: { type: "string", enum: ["job.completed", "job.failed"] },
example: ["job.completed"],
},
isActive: { type: "boolean", example: true },
},
},
ExtractionTemplateField: {
type: "object",
required: ["name", "selector", "attr", "required"],
properties: {
name: { type: "string", example: "title" },
selector: { type: "string", example: "h1.product-title" },
attr: { type: "string", example: "innerText" },
required: { type: "boolean", example: true },
},
},
ExtractionTemplate: {
type: "object",
properties: {
id: { type: "string", format: "uuid" },
userId: { type: "string", format: "uuid" },
name: { type: "string", example: "E-Commerce Product Extractor" },
domain: { type: "string", example: "example.com" },
fields: {
type: "array",
items: { $ref: "#/components/schemas/ExtractionTemplateField" },
},
createdAt: { type: "string", format: "date-time" },
updatedAt: { type: "string", format: "date-time" },
},
},
CreateExtractionTemplateRequest: {
type: "object",
required: ["name", "domain", "fields"],
properties: {
name: { type: "string", example: "E-Commerce Product Extractor" },
domain: { type: "string", example: "example.com" },
fields: {
type: "array",
items: { $ref: "#/components/schemas/ExtractionTemplateField" },
},
},
},
UpdateExtractionTemplateRequest: {
type: "object",
properties: {
name: { type: "string", example: "Updated Template Name" },
fields: {
type: "array",
items: { $ref: "#/components/schemas/ExtractionTemplateField" },
},
},
},
CrawlJobLog: {
type: "object",
properties: {
id: { type: "string", format: "uuid" },
jobId: { type: "string", format: "uuid" },
level: { type: "string", enum: ["INFO", "WARN", "ERROR"] },
step: { type: "string", example: "FETCH_PAGE" },
message: { type: "string", example: "Successfully fetched page 1" },
createdAt: { type: "string", format: "date-time" },
},
},
DashboardStats: {
type: "object",
properties: {
jobs: {
type: "object",
properties: {
total: { type: "integer", example: 42 },
completed: { type: "integer", example: 35 },
failed: { type: "integer", example: 3 },
running: { type: "integer", example: 2 },
pending: { type: "integer", example: 2 },
},
},
pages: {
type: "object",
properties: {
total: { type: "integer", example: 1250 },
successful: { type: "integer", example: 1200 },
failed: { type: "integer", example: 50 },
},
},
schedules: {
type: "object",
properties: {
total: { type: "integer", example: 5 },
active: { type: "integer", example: 4 },
},
},
exports: {
type: "object",
properties: {
total: { type: "integer", example: 18 },
},
},
},
},
}; };
const outputFile = "./src/docs/swagger.json"; const outputFile = "./src/docs/swagger.json";
......
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