251125:0000 Phase 6 wait start dev Check

This commit is contained in:
2025-11-25 00:28:33 +07:00
parent 0e5d7e7e9e
commit 0ce895c96a
22 changed files with 3757 additions and 489 deletions

2432
01_lcbp3_v1_4_3 copy.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
-- ========================================================== -- ==========================================================
-- DMS v1.4.2 Document Management System Database -- DMS v1.4.3 Document Management System Database
-- Deploy Script Schema -- Deploy Script Schema
-- Server: Container Station on QNAPQNAP TS-473A -- Server: Container Station on QNAPQNAP TS-473A
-- Database service: MariaDB 10.11 -- Database service: MariaDB 10.11
@@ -9,8 +9,8 @@
-- frontend sevice: next.js -- frontend sevice: next.js
-- reverse proxy: jc21/nginx-proxy-manager:latest -- reverse proxy: jc21/nginx-proxy-manager:latest
-- cron service: n8n -- cron service: n8n
-- DMS v1.4.2 Improvements -- DMS v1.4.3 Improvements
-- Update: revise fron v1.4.1 Gemini) -- Update: revise fron v1.4.2 add PARTITION to audit_logs & notification
-- ========================================================== -- ==========================================================
SET NAMES utf8mb4; SET NAMES utf8mb4;
SET time_zone = '+07:00'; SET time_zone = '+07:00';
@@ -1918,7 +1918,7 @@ CREATE TABLE IF NOT EXISTS user_preferences (
-- รองรับ: Req 6.1 -- รองรับ: Req 6.1
-- เหตุผล: รองรับ Distributed Tracing และระบุความรุนแรง -- เหตุผล: รองรับ Distributed Tracing และระบุความรุนแรง
CREATE TABLE audit_logs ( CREATE TABLE audit_logs (
audit_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของ Log', audit_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'ID ของ Log',
request_id VARCHAR(100) NULL COMMENT 'Trace ID linking to app logs', request_id VARCHAR(100) NULL COMMENT 'Trace ID linking to app logs',
user_id INT COMMENT 'ผู้กระทำ', user_id INT COMMENT 'ผู้กระทำ',
action VARCHAR(100) NOT NULL COMMENT 'การกระทำ ( action VARCHAR(100) NOT NULL COMMENT 'การกระทำ (
@@ -1932,13 +1932,38 @@ CREATE TABLE audit_logs (
details_json JSON COMMENT 'ข้อมูลบริบท', details_json JSON COMMENT 'ข้อมูลบริบท',
ip_address VARCHAR(45) COMMENT 'IP Address', ip_address VARCHAR(45) COMMENT 'IP Address',
user_agent VARCHAR(255) COMMENT 'User Agent', user_agent VARCHAR(255) COMMENT 'User Agent',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'เวลาที่กระทำ', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'เวลาที่กระทำ',
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE -- [แก้ไข] รวม created_at เข้ามาใน Primary Key เพื่อรองรับ Partition
SET NULL PRIMARY KEY (audit_id, created_at),
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเก็บบันทึกการกระทำของผู้ใช้'; -- [แก้ไข] ใช้ Index ธรรมดาแทน Foreign Key เพื่อไม่ให้ติดข้อจำกัดของ Partition Table
INDEX idx_audit_user (user_id),
INDEX idx_audit_action (action),
INDEX idx_audit_entity (entity_type, entity_id),
INDEX idx_audit_created (created_at)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางเก็บบันทึกการกระทำของผู้ใช้' -- [เพิ่ม] คำสั่ง Partition
PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p_old
VALUES LESS THAN (2024),
PARTITION p2024
VALUES LESS THAN (2025),
PARTITION p2025
VALUES LESS THAN (2026),
PARTITION p2026
VALUES LESS THAN (2027),
PARTITION p2027
VALUES LESS THAN (2028),
PARTITION p2028
VALUES LESS THAN (2029),
PARTITION p2029
VALUES LESS THAN (2030),
PARTITION p2030
VALUES LESS THAN (2031),
PARTITION p_future
VALUES LESS THAN MAXVALUE
);
-- ตารางสำหรับจัดการการแจ้งเตือน (Email/Line/System) -- ตารางสำหรับจัดการการแจ้งเตือน (Email/Line/System)
CREATE TABLE notifications ( CREATE TABLE notifications (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของการแจ้งเตือน', id INT NOT NULL AUTO_INCREMENT COMMENT 'ID ของการแจ้งเตือน',
user_id INT NOT NULL COMMENT 'ID ผู้ใช้', user_id INT NOT NULL COMMENT 'ID ผู้ใช้',
title VARCHAR(255) NOT NULL COMMENT 'หัวข้อการแจ้งเตือน', title VARCHAR(255) NOT NULL COMMENT 'หัวข้อการแจ้งเตือน',
message TEXT NOT NULL COMMENT 'รายละเอียดการแจ้งเตือน', message TEXT NOT NULL COMMENT 'รายละเอียดการแจ้งเตือน',
@@ -1947,9 +1972,35 @@ CREATE TABLE notifications (
entity_type VARCHAR(50) COMMENT 'เช่น ''rfa '', entity_type VARCHAR(50) COMMENT 'เช่น ''rfa '',
''circulation ''', ''circulation ''',
entity_id INT COMMENT 'ID ของเอนทิตีที่เกี่ยวข้อง', entity_id INT COMMENT 'ID ของเอนทิตีที่เกี่ยวข้อง',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง', created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 'วันที่สร้าง',
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE -- [แก้ไข] รวม created_at เข้ามาใน Primary Key
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางสำหรับจัดการการแจ้งเตือน (Email / Line / System)'; PRIMARY KEY (id, created_at),
-- [แก้ไข] ใช้ Index ธรรมดาแทน Foreign Key
INDEX idx_notif_user (user_id),
INDEX idx_notif_type (notification_type),
INDEX idx_notif_read (is_read),
INDEX idx_notif_created (created_at)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = 'ตารางสำหรับจัดการการแจ้งเตือน (Email / Line / System)' -- [เพิ่ม] คำสั่ง Partition
PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p_old
VALUES LESS THAN (2024),
PARTITION p2024
VALUES LESS THAN (2025),
PARTITION p2025
VALUES LESS THAN (2026),
PARTITION p2026
VALUES LESS THAN (2027),
PARTITION p2027
VALUES LESS THAN (2028),
PARTITION p2028
VALUES LESS THAN (2029),
PARTITION p2029
VALUES LESS THAN (2030),
PARTITION p2030
VALUES LESS THAN (2031),
PARTITION p_future
VALUES LESS THAN MAXVALUE
);
-- ตารางสำหรับจัดการดัชนีการค้นหาขั้นสูง (Full-text Search) -- ตารางสำหรับจัดการดัชนีการค้นหาขั้นสูง (Full-text Search)
CREATE TABLE search_indices ( CREATE TABLE search_indices (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของดัชนี', id INT PRIMARY KEY AUTO_INCREMENT COMMENT 'ID ของดัชนี',

View File

@@ -6,276 +6,279 @@
## 📂 **backend/** (Backend Application) ## 📂 **backend/** (Backend Application)
* [x] `.env` (สำหรับ Local Dev เท่านั้น ห้าม commit) * [x] .env (สำหรับ Local Dev เท่านั้น ห้าม commit)
* [x] `.gitignore` * [x] .gitignore
* [x] `.prettierrc` * [x] .prettierrc
* [x] `docker-compose.override.yml` * [x] docker-compose.override.yml
* [x] `docker-compose.yml` * [x] docker-compose.yml
* [x] `nest-cli.json` * [x] nest-cli.json
* [x] `tsconfig.build.json` * [x] tsconfig.build.json
* [x] `tsconfig.json` * [x] tsconfig.json
* [x] `package.json` * [x] package.json
* [x] `pnpm-lock.yaml` * [x] pnpm-lock.yaml
* [x] `README.md` * [x] README.md
--- ---
## 📂 **backend/src/** (Source Code) ## 📂 **backend/src/** (Source Code)
### **📄 Entry Points** ### **📄 Entry Points
* [x] `main.ts` (Application Bootstrap, Swagger, Global Pipes) * [x] main.ts
* [x] `app.module.ts` (Root Module ที่รวมทุก Modules เข้าด้วยกัน) * [x] app.module.ts
* [x] `app.service.ts` (Root Application Service) * [x] app.service.ts
* [x] `app.controller.ts` (Root Application Controller) * [x] app.controller.ts
* [x] `app.controller.spec.ts` (Root Application Controller Unit Tests) * [x] app.controller.spec.ts
* [x] `redlock.d.ts` (Redlock Configuration) * [x] redlock.d.ts
### **📁 src/common/** (Shared Resources) ### **📁 src/common/** (Shared Resources)
* [x] **common.module.ts** * [x] common.module.ts
* **auth/** * **auth/**
* **dto/** * **dto/**
* [x] **login.dto.ts** * [x] login.dto.ts
* [x] **register.dto.ts** * [x] register.dto.ts
* **strategies/** * **strategies/**
* [x] **local.strategy.ts** * [x] local.strategy.ts
* [x] **jwt.strategy.ts** * [x] jwt.strategy.ts
* [x] **auth.controller.spec.ts** * [x] auth.controller.spec.ts
* [x] **auth.controller.ts** * [x] auth.controller.ts
* [x] **auth.module.ts** * [x] auth.module.ts
* [x] **auth.service.spec.ts** * [x] auth.service.spec.ts
* [x] **auth.service.ts** * [x] auth.service.ts
* **config/** (Configuration Service) * **config/**
* [x] **env.validation.ts** * [x] env.validation.ts
* [x] **redis.config.ts** * [x] redis.config.ts
* **decorators/** * **decorators/**
* [x] **audit.decorator.ts** * [x] audit.decorator.ts
* [x] **bypass-maintenance.decorator.ts** * [x] bypass-maintenance.decorator.ts
* [x] `current-user.decorator.ts` * [x] current-user.decorator.ts
* [x] **idempotency.decorator.ts** * [x] idempotency.decorator.ts
* [x] `require-permission.decorator.ts` * [x] require-permission.decorator.ts
* [x] **retry.decorator.ts** * [x] retry.decorator.ts
* [x] **circuit-breaker.decorator.ts** * [x] circuit-breaker.decorator.ts
* **entities/** * **entities/**
* [x] **audit-log.entity.ts** * [x] audit-log.entity.ts
* [x] **base.entity.ts** * [x] base.entity.ts
* **exceptions/** * **exceptions/**
* [x] `http-exception.filter.ts` (Global Filter) * [x] http-exception.filter.ts
* **file-storage/** (Two-Phase Storage System) * **file-storage/**
* **entities/** * **entities/**
* [x] **attachment.entity.ts** * [x] attachment.entity.ts
* [x] **file-storage.controller.spec.ts** * [x] file-storage.controller.spec.ts
* [x] **file-storage.controller.ts** * [x] file-storage.controller.ts
* [x] **file-storage.module.ts** * [x] file-storage.module.ts
* [x] **file-storage.service.spec.ts** * [x] file-storage.service.spec.ts
* [x] `file-storage.service.ts` (Upload, Scan Virus, Commit) * [x] file-storage.service.ts
* [x] **file-cleanup.service.ts** (Cleanup Temporary Files) * [x] file-cleanup.service.ts
* [x] `guards/` * [x] guards/`
* [x] `jwt-auth.guard.ts` * [x] jwt-auth.guard.ts
* [x] **jwt-refresh.guard.ts** * [x] jwt-refresh.guard.ts
* [x] **maintenance-mode.guard.ts** * [x] maintenance-mode.guard.ts
* [x] `rbac.guard.ts` (ตรวจสอบสิทธิ์ 4 ระดับ) * [x] rbac.guard.ts
* **interceptors/** * **interceptors/**
* [x] `audit-log.interceptor.ts` (เก็บ Log ลง DB) * [x] audit-log.interceptor.ts
* [x] **idempotency.interceptor.ts** (Idempotency Interceptor) * [x] idempotency.interceptor.ts
* [x] `transform.interceptor.ts` (Standard Response Format) * [x] performance.interceptor.ts
* **resilience/** (Circuit Breaker & Retry) * [x] transform.interceptor.ts
* [x] **resilience.module.ts** (Resilience Module) * **resilience/**
* **security/** (Security Service) * [x] resilience.module.ts
* [x] **crypto.service.ts** (Crypto Service) * **services/**
* [x] **request-context.service.ts * [x] crypto.service.ts
* [x] request-context.service.ts
### **📁 src/modules/** (Feature Modules) ### **📁 src/modules/** (Feature Modules)
1. **user/** (User Management & RBAC) 1. **user/** (User Management & RBAC)
* [x] `dto/` * [x] **dto/**
* [x] **assign-user-role.dto.ts** * [x] assign-user-role.dto.ts
* [x] `create-user.dto.ts` * [x] create-user.dto.ts
* [ ] **search-user.dto.ts** * [x] update-user.dto.ts
* [x] `update-user.dto.ts` * [x] update-user-preference.dto.ts
* [x] `update-user-preference.dto.ts` * [x] **entities/**
* [x] `entities/` * [x] user.entity.ts
* [x] `user.entity.ts` * [x] role.entity.ts
* [x] `role.entity.ts` * [x] permission.entity.ts
* [x] `permission.entity.ts` * [x] user-assignment.entity.ts
* [x] **user-assignment.entity.ts** * [x] user-preference.entity.ts
* [x] `user-preference.entity.ts` * [x] user-assignment.service.ts
* [x] **user-assignment.service.ts** * [x] user-preference.service.ts
* [x] **user-preference.service.ts** * [x] user.controller.ts
* [x] `user.controller.ts` * [x] user.module.ts
* [x] `user.module.ts` * [x] user.service.ts
* [x] `user.service.ts` * [x] user.service.spec.ts
* [x] **user.service.spec.ts**
2. **project/** (Project Structure) 2. **project/** (Project Structure)
* [x] `dto/` * [x] **dto/**
* [x] **create-project.dto.ts** * [x] create-project.dto.ts
* [x] `search-project.dto.ts` * [x] search-project.dto.ts
* [x] `update-project.dto.ts` * [x] update-project.dto.ts
* [x] `entities/` * [x] **entities/**
* [x] `contract-organization.entity.ts` * [x] contract-organization.entity.ts
* [x] `contract.entity.ts` * [x] contract.entity.ts
* [x] `organization.entity.ts` * [x] organization.entity.ts
* [x] `project-organization.entity.ts` (Junction) * [x] project-organization.entity.ts
* [x] **project.entity.ts** * [x] project.entity.ts
* [x] **project.controller.spec.ts** * [x] project.controller.spec.ts
* [x] `project.controller.ts` * [x] project.controller.ts
* [x] `project.module.ts` * [x] project.module.ts
* [x] **project.service.spec.ts** * [x] project.service.spec.ts
* [x] `project.service.ts` * [x] project.service.ts
3. **correspondence/** (Core Document System) 3. **correspondence/** (Core Document System)
* [x] `dto/` * [x] **dto/**
* [x] `add-reference.dto.ts` * [x] add-reference.dto.ts
* [x] `create-correspondence.dto.ts` * [x] create-correspondence.dto.ts
* [x] `search-correspondence.dto.ts` * [x] search-correspondence.dto.ts
* [x] **submit-correspondence.dto.ts** * [x] submit-correspondence.dto.ts
* [x] `workflow-action.dto.ts` * [x] workflow-action.dto.ts
* [x] `entities/` * [x] **entities/**
* [x] `correspondence-reference.entity.ts` * [x] correspondence-reference.entity.ts
* [x] `correspondence-revision.entity.ts` * [x] correspondence-revision.entity.ts
* [x] `correspondence-routing.entity.ts` (Unified Workflow) * [x] correspondence-routing.entity.ts
* [x] `correspondence-status.entity.ts` * [x] correspondence-status.entity.ts
* [x] `correspondence-type.entity.ts` * [x] correspondence-type.entity.ts
* [x] `correspondence.entity.ts` * [x] correspondence.entity.ts
* [x] **routing-template-step.entity.ts** * [x] routing-template-step.entity.ts
* [x] `routing-template.entity.ts` * [x] routing-template.entity.ts
* [x] **correspondence.controller.spec.ts** * [x] correspondence.controller.spec.ts
* [x] `correspondence.controller.ts` * [x] correspondence.controller.ts
* [x] `correspondence.module.ts` * [x] correspondence.module.ts
* [x] **correspondence.service.spec.ts** * [x] correspondence.service.spec.ts
* [x] `correspondence.service.ts` (Impersonation & Workflow Logic) * [x] correspondence.service.ts
4. **drawing/** (Contract & Shop Drawings) 4. **drawing/** (Contract & Shop Drawings)
* [x] `dto/` * [x] **dto/**
* [x] `create-contract-drawing.dto.ts` * [x] create-contract-drawing.dto.ts
* [x] `create-shop-drawing-revision.dto.ts` * [x] create-shop-drawing-revision.dto.ts
* [x] `create-shop-drawing.dto.ts` * [x] create-shop-drawing.dto.ts
* [x] `search-contract-drawing.dto.ts` * [x] search-contract-drawing.dto.ts
* [x] `search-shop-drawing.dto.ts` * [x] search-shop-drawing.dto.ts
* [x] `update-contract-drawing.dto.ts` * [x] update-contract-drawing.dto.ts
* [x] `entities/` * [x] **entities/**
* [x] `contract-drawing-sub-category.entity.ts` * [x] contract-drawing-sub-category.entity.ts
* [x] `contract-drawing-volume.entity.ts` * [x] contract-drawing-volume.entity.ts
* [x] `contract-drawing.entity.ts` * [x] contract-drawing.entity.ts
* [x] `shop-drawing-main-category.entity.ts` * [x] shop-drawing-main-category.entity.ts
* [x] `shop-drawing-revision.entity.ts` * [x] shop-drawing-revision.entity.ts
* [x] `shop-drawing-sub-category.entity.ts` * [x] shop-drawing-sub-category.entity.ts
* [x] `shop-drawing.entity.ts` * [x] shop-drawing.entity.ts
* [x] `contract-drawing.controller.ts` * [x] contract-drawing.controller.ts
* [x] `contract-drawing.service.ts` * [x] contract-drawing.service.ts
* [x] `drawing-master-data.controller.ts` * [x] drawing-master-data.controller.ts
* [x] `drawing-master-data.service.ts` * [x] drawing-master-data.service.ts
* [x] `drawing.module.ts` * [x] drawing.module.ts
* [x] `shop-drawing.controller.ts` * [x] shop-drawing.controller.ts
* [x] `shop-drawing.service.ts` * [x] shop-drawing.service.ts
5. **rfa/** (Request for Approval & Advanced Workflow) 5. **rfa/** (Request for Approval & Advanced Workflow)
* [x] `dto/` * [x] **dto/**
* [x] `create-rfa.dto.ts` * [x] create-rfa.dto.ts
* [x] `search-rfa.dto.ts` * [x] search-rfa.dto.ts
* [x] `update-rfa.dto.ts` * [x] update-rfa.dto.ts
* [x] `entities/` * [x] **entities/**
* [x] `rfa-approve-code.entity.ts` * [x] rfa-approve-code.entity.ts
* [x] `rfa-item.entity.ts` * [x] rfa-item.entity.ts
* [x] `rfa-revision.entity.ts` * [x] rfa-revision.entity.ts
* [x] `rfa-status-code.entity.ts` * [x] rfa-status-code.entity.ts
* [x] `rfa-type.entity.ts` * [x] rfa-type.entity.ts
* [x] `rfa-workflow-template-step.entity.ts` * [x] rfa-workflow-template-step.entity.ts
* [x] `rfa-workflow-template.entity.ts` * [x] rfa-workflow-template.entity.ts
* [x] `rfa-workflow.entity.ts` * [x] rfa-workflow.entity.ts
* [x] `rfa.entity.ts` * [x] rfa.entity.ts
* [x] `rfa.controller.ts` * [x] rfa.controller.ts
* [x] `rfa.module.ts` * [x] rfa.module.ts
* [x] `rfa.service.ts` (Unified Workflow Integration) * [x] rfa.service.ts
6. **circulation/** (Internal Routing) 6. **circulation/** (Internal Routing)
* [x] `dto/` * [x] **dto/**
* [x] `create-circulation.dto.ts` * [x] create-circulation.dto.ts
* [x] `update-circulation-routing.dto.ts` * [x] update-circulation-routing.dto.ts
* [x] `search-circulation.dto.ts` * [x] search-circulation.dto.ts
* [x] `entities/` * [x] **entities/**
* [x] `circulation-routing.entity.ts` * [x] circulation-routing.entity.ts
* [x] `circulation-status-code.entity.ts` * [x] circulation-status-code.entity.ts
* [x] `circulation.entity.ts` * [x] circulation.entity.ts
* [x] `circulation.controller.ts` * [x] circulation.controller.ts
* [x] `circulation.module.ts` * [x] circulation.module.ts
* [x] `circulation.service.ts` * [x] circulation.service.ts
7. **transmittal/** (Document Forwarding) 7. **transmittal/** (Document Forwarding)
* [x] `dto/` * [x] **dto/**
* [x] `create-transmittal.dto.ts` * [x] create-transmittal.dto.ts
* [x] `search-transmittal.dto.ts` * [x] search-transmittal.dto.ts
* [x] **update-transmittal.dto.ts** * [x] update-transmittal.dto.ts
* [x] `entities/` * [x] **entities/**
* [x] `transmittal-item.entity.ts` * [x] transmittal-item.entity.ts
* [x] `transmittal.entity.ts` * [x] transmittal.entity.ts
* [x] `transmittal.controller.ts` * [x] transmittal.controller.ts
* [x] `transmittal.module.ts` * [x] transmittal.module.ts
* [x] `transmittal.service.ts` * [x] transmittal.service.ts
8. **notification/** (System Alerts) 8. **notification/** (System Alerts)
* [x] `dto/` * [x] **dto/**
* [x] `create-notification.dto.ts` * [x] create-notification.dto.ts
* [x] `search-notification.dto.ts` * [x] search-notification.dto.ts
* [x] `entities/` * [x] **entities/**
* [x] `notification.entity.ts` * [x] notification.entity.ts
* [x] `notification-cleanup.service.ts` (Cron Job) * [x] notification-cleanup.service.ts
* [x] `notification.controller.ts` * [x] notification.controller.ts
* [x] `notification.gateway.ts` * [x] notification.gateway.ts
* [x] `notification.module.ts` (Real-time WebSocket) * [x] notification.module.ts
* [x] `notification.processor.ts` (Consumer/Worker for Email & Line) * [x] notification.processor.ts
* [x] `notification.service.ts` (Producer) * [x] notification.service.ts
9. **search/** (Elasticsearch) 9. **search/** (Elasticsearch)
* [x] `dto/search-query.dto.ts` * [x] dto/search-query.dto.ts
* [x] `search.controller.ts` * [x] search.controller.ts
* [x] `search.module.ts` * [x] search.module.ts
* [x] `search.service.ts` (Indexing & Searching) * [x] search.service.ts
10. **document-numbering/** (Internal Service) 10. **document-numbering/**
* [x] `entities/` * [x] **entities/**
* [x] `document-number-format.entity.ts` * [x] document-number-format.entity.ts
* [x] `document-number-counter.entity.ts` * [x] document-number-counter.entity.ts
* [x] `document-numbering.module.ts` * [x] document-numbering.module.ts
* [x] **document-numbering.service.spec.ts** * [x] document-numbering.service.spec.ts
* [x] `document-numbering.service.ts` (Double-Lock Mechanism) * [x] document-numbering.service.ts
11. **workflow-engine/** (Unified Logic) 11. **workflow-engine/** (Unified Logic)
* [x] **dto/** * [x] **dto/**
* [x] `create-workflow-definition.dto.ts` * [x] create-workflow-definition.dto.ts
* [x] `evaluate-workflow.dto.ts` * [x] evaluate-workflow.dto.ts
* [x] `get-available-actions.dto.ts` * [x] get-available-actions.dto.ts
* [x] `update-workflow-definition.dto.ts` * [x] update-workflow-definition.dto.ts
* [x] `interfaces/workflow.interface.ts` * [x] interfaces/workflow.interface.ts
* [x] **workflow-dsl.service.ts** * [x] workflow-dsl.service.ts
* [x] `workflow-engine.module.ts` * [x] workflow-engine.module.ts
* [x] **workflow-engine.service.spec.ts** * [x] workflow-engine.service.spec.ts
* [x] `workflow-engine.service.ts` (State Machine Logic) * [x] workflow-engine.service.ts
12. **json-schema/** (Validation) 12. **json-schema/** (Validation)
* [x] `dto/` * [x] **dto/**
* [x] `create-json-schema.dto.ts`+ * [x] create-json-schema.dto.ts+
* [x] `search-json-schema.dto.ts` * [x] search-json-schema.dto.ts
* [x] `update-json-schema.dto.ts` * [x] update-json-schema.dto.ts
* [x] `entities/` * [x] **entities/**
* [x] `json-schema.entity.ts` * [x] json-schema.entity.ts
* [x] **json-schema.controller.spec.ts** * [x] json-schema.controller.spec.ts
* [x] **json-schema.controller.ts** * [x] json-schema.controller.ts
* [x] `json-schema.module.ts` * [x] json-schema.module.ts
* [x] **json-schema.service.spec.ts** * [x] json-schema.service.spec.ts
* [x] `json-schema.service.ts` * [x] json-schema.service.ts
13. **monitoring/** (Monitoring & Metrics) 13. **monitoring/** (Monitoring & Metrics)
* [x] `controllers/` * [x] **controllers/**
* [x] `health.controller.ts` * [x] health.controller.ts
* [x] `logger/` * [x] **dto/**
* [x] `winston.config.ts` * [ ] set-maintenance.dto.ts
* [x] `services/` * [x] **logger/**
* [x] `metrics.service.ts` * [x] winston.config.ts
* [x] `monitoring.module.ts` * [x] **services/**
* [x] metrics.service.ts
* [x] monitoring.controller.ts
* [x] monitoring.service.ts
* [x] monitoring.module.ts
## **Folder Structure ของ Backend (NestJS)** ที่ ## **Folder Structure ของ Backend (NestJS)** ที่
@@ -354,17 +357,18 @@ backend/
│ │ ├── jwt-auth.guard.ts # JWT authentication guard │ │ ├── jwt-auth.guard.ts # JWT authentication guard
│ │ ├── jwt-refresh.guard.ts # JWT refresh guard │ │ ├── jwt-refresh.guard.ts # JWT refresh guard
│ │ ├── maintenance-mode.guard.ts # Maintenance mode guard │ │ ├── maintenance-mode.guard.ts # Maintenance mode guard
│ │ └── rbac.guard.ts # Role-based access control enforcement │ │ └── rbac.guard.ts # Role-based access control enforcement (ตรวจสอบสิทธิ์ 4 ระดับ)
│ │ │ │
│ ├── interceptors/ # 📡 Interceptors for common use cases │ ├── interceptors/ # 📡 Interceptors for common use cases
│ │ ├── audit-log.interceptor.ts # Automatically logs certain operations │ │ ├── audit-log.interceptor.ts # Automatically logs certain operations
│ │ ├── idempotency.interceptor.ts # Idempotency interceptor │ │ ├── idempotency.interceptor.ts # Idempotency interceptor
│ │ ├── performance.interceptor.ts #
│ │ └── transform.interceptor.ts # Standardized response formatting │ │ └── transform.interceptor.ts # Standardized response formatting
│ │ │ │
│ ├─── resilience/ # 🛡️ Circuit-breaker / retry logic (if implemented) │ ├─── resilience/ # 🛡️ Circuit-breaker / retry logic (if implemented)
│ │ └── resilience.module.ts # Resilience module │ │ └── resilience.module.ts # Resilience module
│ │ │ │
│ └──── security/ # 🔐 Security service │ └──── services/ # 🔐 Security service
│ ├── crypto.service.ts # Crypto service │ ├── crypto.service.ts # Crypto service
│ └── request-context.service.ts # Request context service (for logging) │ └── request-context.service.ts # Request context service (for logging)
@@ -373,7 +377,6 @@ backend/
│ │ ├── dto/ │ │ ├── dto/
│ │ │ ├── assign-user-role.dto.ts # Assign roles to users │ │ │ ├── assign-user-role.dto.ts # Assign roles to users
│ │ │ ├── create-user.dto.ts # Create new user │ │ │ ├── create-user.dto.ts # Create new user
│ │ │ ├── search-user.dto.ts # Search users
│ │ │ ├── update-user.dto.ts # Update user details │ │ │ ├── update-user.dto.ts # Update user details
│ │ │ └── update-user-preference.dto.ts # Update user preferences │ │ │ └── update-user-preference.dto.ts # Update user preferences
│ │ ├── entities/ │ │ ├── entities/
@@ -416,7 +419,7 @@ backend/
│ │ ├── entities/ │ │ ├── entities/
│ │ │ ├── correspondence.entity.ts # Correspondence table definition │ │ │ ├── correspondence.entity.ts # Correspondence table definition
│ │ │ ├── correspondence-revision.entity.ts # Correspondence revision entity │ │ │ ├── correspondence-revision.entity.ts # Correspondence revision entity
│ │ │ ├── correspondence-routing.entity.ts # Correspondence routing entity │ │ │ ├── correspondence-routing.entity.ts # Correspondence routing entity (Unified Workflow)
│ │ │ ├── correspondence-status.entity.ts # Correspondence status entity │ │ │ ├── correspondence-status.entity.ts # Correspondence status entity
│ │ │ ├── correspondence-type.entity.ts # Correspondence type entity │ │ │ ├── correspondence-type.entity.ts # Correspondence type entity
│ │ │ ├── correspondence-reference.entity.ts # Correspondence reference entity │ │ │ ├── correspondence-reference.entity.ts # Correspondence reference entity
@@ -505,7 +508,7 @@ backend/
│ │ ├── notification.module.ts # WebSocket + Processor registration │ │ ├── notification.module.ts # WebSocket + Processor registration
│ │ ├── notification.controller.ts # REST endpoints │ │ ├── notification.controller.ts # REST endpoints
│ │ ├── notification.gateway.ts # WebSocket gateway │ │ ├── notification.gateway.ts # WebSocket gateway
│ │ ├── notification.processor.ts # Message consumer (e.g. mail worker) │ │ ├── notification.processor.ts # Message consumer (Consumer/Worker for Email & Line)
│ │ ├── notification.service.ts # Business logic │ │ ├── notification.service.ts # Business logic
│ │ └── notification-cleanup.service.ts # Cron-based cleanup job │ │ └── notification-cleanup.service.ts # Cron-based cleanup job
│ │ │ │
@@ -533,11 +536,11 @@ backend/
│ │ ├── entities/ │ │ ├── entities/
│ │ │ └── workflow-definition.entity.ts # Workflow definition entity │ │ │ └── workflow-definition.entity.ts # Workflow definition entity
│ │ ├── interfaces/ │ │ ├── interfaces/
│ │ │ └── workflow.interface.ts # Workflow interface │ │ │ └── workflow.interface.ts # Workflow interface
│ │ ├── workflow-engine.controller.ts # REST endpoints │ │ ├── workflow-engine.controller.ts # REST endpoints
│ │ ├── workflow-engine.module.ts # Module DI container │ │ ├── workflow-engine.module.ts # Module DI container
│ │ ├── workflow-engine.service.ts # Business logic │ │ ├── workflow-engine.service.ts # State Machine Logic
│ │ └── workflow-engine.service.spec.ts # Unit tests │ │ └── workflow-engine.service.spec.ts # Unit tests
│ │ │ │
│ ├── json-schema/ # 📋 Dynamic request schema validation │ ├── json-schema/ # 📋 Dynamic request schema validation
│ │ ├── dto/ │ │ ├── dto/
@@ -554,15 +557,17 @@ backend/
│ │ │ │
│ └── monitoring/ # 📋 Dynamic request schema validation │ └── monitoring/ # 📋 Dynamic request schema validation
│ ├── controllers/ │ ├── controllers/
│ │ ── health.controller.ts # Create new JSON schema │ │ ── health.controller.ts # Create new JSON schema
│ ├── update-json-schema.dto.ts # Update JSON schema ├── dto/
│ │ └── search-json-schema.dto.ts # Search JSON schema │ │ └── set-maintenance.dto.ts # Create new JSON schema
│ ├── logger/ │ ├── logger/
│ │ └── winston.config.ts # JSON schema entity │ │ └── winston.config.ts # JSON schema entity
│ ├── services/ │ ├── services/
│ │ └── metrics.service.ts # JSON schema entity │ │ └── metrics.service.ts # JSON schema entity
── monitoring.module.ts # Module DI container ── monitoring.controller.ts # REST endpoints
├── monitoring.service.ts # Business logic
│ └── monitoring.module.ts # Module DI container
``` ```
--- ---

View File

@@ -22,6 +22,7 @@
"dependencies": { "dependencies": {
"@casl/ability": "^6.7.3", "@casl/ability": "^6.7.3",
"@elastic/elasticsearch": "^8.11.1", "@elastic/elasticsearch": "^8.11.1",
"@nestjs-modules/ioredis": "^2.0.2",
"@nestjs/axios": "^4.0.1", "@nestjs/axios": "^4.0.1",
"@nestjs/bullmq": "^11.0.4", "@nestjs/bullmq": "^11.0.4",
"@nestjs/cache-manager": "^3.0.1", "@nestjs/cache-manager": "^3.0.1",

470
backend/pnpm-lock.yaml generated
View File

@@ -14,6 +14,12 @@ importers:
'@elastic/elasticsearch': '@elastic/elasticsearch':
specifier: ^8.11.1 specifier: ^8.11.1
version: 8.19.1 version: 8.19.1
'@nestjs-modules/ioredis':
specifier: ^2.0.2
version: 2.0.2(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(ioredis@5.8.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
'@nestjs/axios':
specifier: ^4.0.1
version: 4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)
'@nestjs/bullmq': '@nestjs/bullmq':
specifier: ^11.0.4 specifier: ^11.0.4
version: 11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(bullmq@5.63.2) version: 11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(bullmq@5.63.2)
@@ -53,6 +59,9 @@ importers:
'@nestjs/swagger': '@nestjs/swagger':
specifier: ^11.2.3 specifier: ^11.2.3
version: 11.2.3(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2) version: 11.2.3(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)
'@nestjs/terminus':
specifier: ^11.0.0
version: 11.0.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
'@nestjs/throttler': '@nestjs/throttler':
specifier: ^6.4.0 specifier: ^6.4.0
version: 6.4.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2) version: 6.4.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)
@@ -71,6 +80,9 @@ importers:
ajv-formats: ajv-formats:
specifier: ^3.0.1 specifier: ^3.0.1
version: 3.0.1(ajv@8.17.1) version: 3.0.1(ajv@8.17.1)
async-retry:
specifier: ^1.3.3
version: 1.3.3
axios: axios:
specifier: ^1.13.2 specifier: ^1.13.2
version: 1.13.2 version: 1.13.2
@@ -110,15 +122,24 @@ importers:
mysql2: mysql2:
specifier: ^3.15.3 specifier: ^3.15.3
version: 3.15.3 version: 3.15.3
nest-winston:
specifier: ^1.10.2
version: 1.10.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(winston@3.18.3)
nodemailer: nodemailer:
specifier: ^7.0.10 specifier: ^7.0.10
version: 7.0.10 version: 7.0.10
opossum:
specifier: ^9.0.0
version: 9.0.0
passport: passport:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0 version: 0.7.0
passport-jwt: passport-jwt:
specifier: ^4.0.1 specifier: ^4.0.1
version: 4.0.1 version: 4.0.1
prom-client:
specifier: ^15.1.3
version: 15.1.3
redlock: redlock:
specifier: 5.0.0-beta.2 specifier: 5.0.0-beta.2
version: 5.0.0-beta.2 version: 5.0.0-beta.2
@@ -140,6 +161,9 @@ importers:
uuid: uuid:
specifier: ^13.0.0 specifier: ^13.0.0
version: 13.0.0 version: 13.0.0
winston:
specifier: ^3.18.3
version: 3.18.3
devDependencies: devDependencies:
'@eslint/eslintrc': '@eslint/eslintrc':
specifier: ^3.2.0 specifier: ^3.2.0
@@ -156,6 +180,9 @@ importers:
'@nestjs/testing': '@nestjs/testing':
specifier: ^11.0.1 specifier: ^11.0.1
version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9) version: 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)
'@types/async-retry':
specifier: ^1.4.9
version: 1.4.9
'@types/bcrypt': '@types/bcrypt':
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0 version: 6.0.0
@@ -180,6 +207,9 @@ importers:
'@types/node': '@types/node':
specifier: ^22.10.7 specifier: ^22.10.7
version: 22.19.1 version: 22.19.1
'@types/opossum':
specifier: ^8.1.9
version: 8.1.9
'@types/passport-jwt': '@types/passport-jwt':
specifier: ^4.0.1 specifier: ^4.0.1
version: 4.0.1 version: 4.0.1
@@ -572,10 +602,17 @@ packages:
resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
engines: {node: '>=0.1.90'} engines: {node: '>=0.1.90'}
'@colors/colors@1.6.0':
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
engines: {node: '>=0.1.90'}
'@cspotcode/source-map-support@0.8.1': '@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'} engines: {node: '>=12'}
'@dabh/diagnostics@2.0.8':
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
'@elastic/elasticsearch@8.19.1': '@elastic/elasticsearch@8.19.1':
resolution: {integrity: sha512-+1j9NnQVOX+lbWB8LhCM7IkUmjU05Y4+BmSLfusq0msCsQb1Va+OUKFCoOXjCJqQrcgdRdQCjYYyolQ/npQALQ==} resolution: {integrity: sha512-+1j9NnQVOX+lbWB8LhCM7IkUmjU05Y4+BmSLfusq0msCsQb1Va+OUKFCoOXjCJqQrcgdRdQCjYYyolQ/npQALQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -980,6 +1017,20 @@ packages:
'@napi-rs/wasm-runtime@0.2.12': '@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
'@nestjs-modules/ioredis@2.0.2':
resolution: {integrity: sha512-8pzSvT8R3XP6p8ZzQmEN8OnY0yWrJ/elFhwQK+PID2zf1SLBkAZ18bDcx3SKQ2atledt0gd9kBeP5xT4MlyS7Q==}
peerDependencies:
'@nestjs/common': '>=6.7.0'
'@nestjs/core': '>=6.7.0'
ioredis: '>=5.0.0'
'@nestjs/axios@4.0.1':
resolution: {integrity: sha512-68pFJgu+/AZbWkGu65Z3r55bTsCPlgyKaV4BSG8yUAD72q1PPuyVRgUwFv6BxdnibTUHlyxm06FmYWNC+bjN7A==}
peerDependencies:
'@nestjs/common': ^10.0.0 || ^11.0.0
axios: ^1.3.1
rxjs: ^7.0.0
'@nestjs/bull-shared@11.0.4': '@nestjs/bull-shared@11.0.4':
resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==} resolution: {integrity: sha512-VBJcDHSAzxQnpcDfA0kt9MTGUD1XZzfByV70su0W0eDCQ9aqIEBlzWRW21tv9FG9dIut22ysgDidshdjlnczLw==}
peerDependencies: peerDependencies:
@@ -1124,6 +1175,102 @@ packages:
class-validator: class-validator:
optional: true optional: true
'@nestjs/terminus@10.2.0':
resolution: {integrity: sha512-zPs98xvJ4ogEimRQOz8eU90mb7z+W/kd/mL4peOgrJ/VqER+ibN2Cboj65uJZW3XuNhpOqaeYOJte86InJd44A==}
peerDependencies:
'@grpc/grpc-js': '*'
'@grpc/proto-loader': '*'
'@mikro-orm/core': '*'
'@mikro-orm/nestjs': '*'
'@nestjs/axios': ^1.0.0 || ^2.0.0 || ^3.0.0
'@nestjs/common': ^9.0.0 || ^10.0.0
'@nestjs/core': ^9.0.0 || ^10.0.0
'@nestjs/microservices': ^9.0.0 || ^10.0.0
'@nestjs/mongoose': ^9.0.0 || ^10.0.0
'@nestjs/sequelize': ^9.0.0 || ^10.0.0
'@nestjs/typeorm': ^9.0.0 || ^10.0.0
'@prisma/client': '*'
mongoose: '*'
reflect-metadata: 0.1.x
rxjs: 7.x
sequelize: '*'
typeorm: '*'
peerDependenciesMeta:
'@grpc/grpc-js':
optional: true
'@grpc/proto-loader':
optional: true
'@mikro-orm/core':
optional: true
'@mikro-orm/nestjs':
optional: true
'@nestjs/axios':
optional: true
'@nestjs/microservices':
optional: true
'@nestjs/mongoose':
optional: true
'@nestjs/sequelize':
optional: true
'@nestjs/typeorm':
optional: true
'@prisma/client':
optional: true
mongoose:
optional: true
sequelize:
optional: true
typeorm:
optional: true
'@nestjs/terminus@11.0.0':
resolution: {integrity: sha512-c55LOo9YGovmQHtFUMa/vDaxGZ2cglMTZejqgHREaApt/GArTfgYYGwhRXPLq8ZwiQQlLuYB+79e9iA8mlDSLA==}
peerDependencies:
'@grpc/grpc-js': '*'
'@grpc/proto-loader': '*'
'@mikro-orm/core': '*'
'@mikro-orm/nestjs': '*'
'@nestjs/axios': ^2.0.0 || ^3.0.0 || ^4.0.0
'@nestjs/common': ^10.0.0 || ^11.0.0
'@nestjs/core': ^10.0.0 || ^11.0.0
'@nestjs/microservices': ^10.0.0 || ^11.0.0
'@nestjs/mongoose': ^11.0.0
'@nestjs/sequelize': ^10.0.0 || ^11.0.0
'@nestjs/typeorm': ^10.0.0 || ^11.0.0
'@prisma/client': '*'
mongoose: '*'
reflect-metadata: 0.1.x || 0.2.x
rxjs: 7.x
sequelize: '*'
typeorm: '*'
peerDependenciesMeta:
'@grpc/grpc-js':
optional: true
'@grpc/proto-loader':
optional: true
'@mikro-orm/core':
optional: true
'@mikro-orm/nestjs':
optional: true
'@nestjs/axios':
optional: true
'@nestjs/microservices':
optional: true
'@nestjs/mongoose':
optional: true
'@nestjs/sequelize':
optional: true
'@nestjs/typeorm':
optional: true
'@prisma/client':
optional: true
mongoose:
optional: true
sequelize:
optional: true
typeorm:
optional: true
'@nestjs/testing@11.1.9': '@nestjs/testing@11.1.9':
resolution: {integrity: sha512-UFxerBDdb0RUNxQNj25pvkvNE7/vxKhXYWBt3QuwBFnYISzRIzhVlyIqLfoV5YI3zV0m0Nn4QAn1KM0zzwfEng==} resolution: {integrity: sha512-UFxerBDdb0RUNxQNj25pvkvNE7/vxKhXYWBt3QuwBFnYISzRIzhVlyIqLfoV5YI3zV0m0Nn4QAn1KM0zzwfEng==}
peerDependencies: peerDependencies:
@@ -1424,6 +1571,9 @@ packages:
resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
'@so-ric/colorspace@1.1.6':
resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==}
'@socket.io/component-emitter@3.1.2': '@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
@@ -1458,6 +1608,9 @@ packages:
'@tybys/wasm-util@0.10.1': '@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
'@types/async-retry@1.4.9':
resolution: {integrity: sha512-s1ciZQJzRh3708X/m3vPExr5KJlzlZJvXsKpbtE2luqNcbROr64qU+3KpJsYHqWMeaxI839OvXf9PrUSw1Xtyg==}
'@types/babel__core@7.20.5': '@types/babel__core@7.20.5':
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
@@ -1565,6 +1718,9 @@ packages:
'@types/nodemailer@7.0.4': '@types/nodemailer@7.0.4':
resolution: {integrity: sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==} resolution: {integrity: sha512-ee8fxWqOchH+Hv6MDDNNy028kwvVnLplrStm4Zf/3uHWw5zzo8FoYYeffpJtGs2wWysEumMH0ZIdMGMY1eMAow==}
'@types/opossum@8.1.9':
resolution: {integrity: sha512-Jm/tYxuJFefiwRYs+/EOsUP3ktk0c8siMgAHPLnA4PXF4wKghzcjqf88dY+Xii5jId5Txw4JV0FMKTpjbd7KJA==}
'@types/passport-jwt@4.0.1': '@types/passport-jwt@4.0.1':
resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==} resolution: {integrity: sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==}
@@ -1580,6 +1736,9 @@ packages:
'@types/range-parser@1.2.7': '@types/range-parser@1.2.7':
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
'@types/retry@0.12.5':
resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==}
'@types/send@0.17.6': '@types/send@0.17.6':
resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==}
@@ -1598,6 +1757,9 @@ packages:
'@types/supertest@6.0.3': '@types/supertest@6.0.3':
resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==} resolution: {integrity: sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==}
'@types/triple-beam@1.3.5':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
'@types/uuid@11.0.0': '@types/uuid@11.0.0':
resolution: {integrity: sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==} resolution: {integrity: sha512-HVyk8nj2m+jcFRNazzqyVKiZezyhDKrGUA3jlEcg/nZ6Ms+qHwocba1Y/AaVaznJTAM9xpdFSh+ptbNrhOGvZA==}
deprecated: This is a stub types definition. uuid provides its own type definitions, so you do not need this installed. deprecated: This is a stub types definition. uuid provides its own type definitions, so you do not need this installed.
@@ -1891,6 +2053,9 @@ packages:
ajv@8.17.1: ajv@8.17.1:
resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
ansi-align@3.0.1:
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
ansi-colors@4.1.3: ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -1961,6 +2126,12 @@ packages:
asap@2.0.6: asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
async-retry@1.3.3:
resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==}
async@3.2.6:
resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==}
asynckit@0.4.0: asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
@@ -2018,6 +2189,9 @@ packages:
resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
bintrees@1.0.2:
resolution: {integrity: sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==}
bl@4.1.0: bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -2028,6 +2202,10 @@ packages:
bowser@2.12.1: bowser@2.12.1:
resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==}
boxen@5.1.2:
resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==}
engines: {node: '>=10'}
brace-expansion@1.1.12: brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@@ -2127,6 +2305,10 @@ packages:
chardet@2.1.1: chardet@2.1.1:
resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==}
check-disk-space@3.4.0:
resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==}
engines: {node: '>=16'}
chokidar@4.0.3: chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'} engines: {node: '>= 14.16.0'}
@@ -2148,6 +2330,10 @@ packages:
class-validator@0.14.2: class-validator@0.14.2:
resolution: {integrity: sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==} resolution: {integrity: sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==}
cli-boxes@2.2.1:
resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==}
engines: {node: '>=6'}
cli-cursor@3.1.0: cli-cursor@3.1.0:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -2187,9 +2373,25 @@ packages:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'} engines: {node: '>=7.0.0'}
color-convert@3.1.3:
resolution: {integrity: sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==}
engines: {node: '>=14.6'}
color-name@1.1.4: color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
color-name@2.1.0:
resolution: {integrity: sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==}
engines: {node: '>=12.20'}
color-string@2.1.4:
resolution: {integrity: sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==}
engines: {node: '>=18'}
color@5.0.3:
resolution: {integrity: sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==}
engines: {node: '>=18'}
combined-stream@1.0.8: combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@@ -2393,6 +2595,9 @@ packages:
emoji-regex@9.2.2: emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
enabled@2.0.0:
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
encodeurl@2.0.0: encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@@ -2580,6 +2785,9 @@ packages:
fb-watchman@2.0.2: fb-watchman@2.0.2:
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
fecha@4.2.3:
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
fflate@0.8.2: fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
@@ -2626,6 +2834,9 @@ packages:
flatted@3.3.3: flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
fn.name@1.1.0:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
follow-redirects@1.15.11: follow-redirects@1.15.11:
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
@@ -3142,6 +3353,9 @@ packages:
keyv@5.5.4: keyv@5.5.4:
resolution: {integrity: sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==} resolution: {integrity: sha512-eohl3hKTiVyD1ilYdw9T0OiB4hnjef89e3dMYKz+mVKDzj+5IteTseASUsOB+EU9Tf6VNTCjDePcP6wkDGmLKQ==}
kuler@2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
leven@3.1.0: leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -3218,6 +3432,10 @@ packages:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'} engines: {node: '>=10'}
logform@2.7.0:
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
engines: {node: '>= 12.0.0'}
long@5.3.2: long@5.3.2:
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
@@ -3383,6 +3601,12 @@ packages:
neo-async@2.6.2: neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
nest-winston@1.10.2:
resolution: {integrity: sha512-Z9IzL/nekBOF/TEwBHUJDiDPMaXUcFquUQOFavIRet6xF0EbuWnOzslyN/ksgzG+fITNgXhMdrL/POp9SdaFxA==}
peerDependencies:
'@nestjs/common': ^5.0.0 || ^6.6.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
winston: ^3.0.0
node-abort-controller@3.1.1: node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
@@ -3438,10 +3662,17 @@ packages:
once@1.4.0: once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
one-time@1.0.0:
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
onetime@5.1.2: onetime@5.1.2:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'} engines: {node: '>=6'}
opossum@9.0.0:
resolution: {integrity: sha512-K76U0QkxOfUZamneQuzz+AP0fyfTJcCplZ2oZL93nxeupuJbN4s6uFNbmVCt4eWqqGqRnnowdFuBicJ1fLMVxw==}
engines: {node: ^24 || ^22 || ^20}
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -3574,6 +3805,10 @@ packages:
resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==} resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==}
engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0}
prom-client@15.1.3:
resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==}
engines: {node: ^16 || ^18 || >=20}
promise-coalesce@1.5.0: promise-coalesce@1.5.0:
resolution: {integrity: sha512-cTJ30U+ur1LD7pMPyQxiKIwxjtAjLsyU7ivRhVWZrX9BNIXtf78pc37vSMc8Vikx7DVzEKNk2SEJ5KWUpSG2ig==} resolution: {integrity: sha512-cTJ30U+ur1LD7pMPyQxiKIwxjtAjLsyU7ivRhVWZrX9BNIXtf78pc37vSMc8Vikx7DVzEKNk2SEJ5KWUpSG2ig==}
engines: {node: '>=16'} engines: {node: '>=16'}
@@ -3663,6 +3898,10 @@ packages:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'} engines: {node: '>=8'}
retry@0.13.1:
resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
engines: {node: '>= 4'}
reusify@1.1.0: reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'} engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -3683,6 +3922,10 @@ packages:
safe-buffer@5.2.1: safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
safer-buffer@2.1.2: safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
@@ -3807,6 +4050,9 @@ packages:
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
stack-utils@2.0.6: stack-utils@2.0.6:
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -3913,6 +4159,9 @@ packages:
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
engines: {node: '>=6'} engines: {node: '>=6'}
tdigest@0.1.2:
resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==}
terser-webpack-plugin@5.3.14: terser-webpack-plugin@5.3.14:
resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==} resolution: {integrity: sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==}
engines: {node: '>= 10.13.0'} engines: {node: '>= 10.13.0'}
@@ -3938,6 +4187,9 @@ packages:
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
engines: {node: '>=8'} engines: {node: '>=8'}
text-hex@1.0.0:
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
tmpl@1.0.5: tmpl@1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
@@ -3957,6 +4209,10 @@ packages:
resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==}
engines: {node: '>=14.16'} engines: {node: '>=14.16'}
triple-beam@1.4.1:
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
engines: {node: '>= 14.0.0'}
ts-api-utils@2.1.0: ts-api-utils@2.1.0:
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
engines: {node: '>=18.12'} engines: {node: '>=18.12'}
@@ -4030,6 +4286,10 @@ packages:
resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
engines: {node: '>=4'} engines: {node: '>=4'}
type-fest@0.20.2:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'}
type-fest@0.21.3: type-fest@0.21.3:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -4235,6 +4495,18 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
hasBin: true hasBin: true
widest-line@3.1.0:
resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==}
engines: {node: '>=8'}
winston-transport@4.9.0:
resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==}
engines: {node: '>= 12.0.0'}
winston@3.18.3:
resolution: {integrity: sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==}
engines: {node: '>= 12.0.0'}
word-wrap@1.2.5: word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -4968,10 +5240,18 @@ snapshots:
'@colors/colors@1.5.0': '@colors/colors@1.5.0':
optional: true optional: true
'@colors/colors@1.6.0': {}
'@cspotcode/source-map-support@0.8.1': '@cspotcode/source-map-support@0.8.1':
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.9 '@jridgewell/trace-mapping': 0.3.9
'@dabh/diagnostics@2.0.8':
dependencies:
'@so-ric/colorspace': 1.1.6
enabled: 2.0.0
kuler: 2.0.0
'@elastic/elasticsearch@8.19.1': '@elastic/elasticsearch@8.19.1':
dependencies: dependencies:
'@elastic/transport': 8.10.0 '@elastic/transport': 8.10.0
@@ -5489,6 +5769,36 @@ snapshots:
'@tybys/wasm-util': 0.10.1 '@tybys/wasm-util': 0.10.1
optional: true optional: true
'@nestjs-modules/ioredis@2.0.2(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(ioredis@5.8.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))':
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
ioredis: 5.8.2
optionalDependencies:
'@nestjs/terminus': 10.2.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
transitivePeerDependencies:
- '@grpc/grpc-js'
- '@grpc/proto-loader'
- '@mikro-orm/core'
- '@mikro-orm/nestjs'
- '@nestjs/axios'
- '@nestjs/microservices'
- '@nestjs/mongoose'
- '@nestjs/sequelize'
- '@nestjs/typeorm'
- '@prisma/client'
- mongoose
- reflect-metadata
- rxjs
- sequelize
- typeorm
'@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)':
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
axios: 1.13.2
rxjs: 7.8.2
'@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)': '@nestjs/bull-shared@11.0.4(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)':
dependencies: dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -5656,6 +5966,33 @@ snapshots:
class-transformer: 0.5.1 class-transformer: 0.5.1
class-validator: 0.14.2 class-validator: 0.14.2
'@nestjs/terminus@10.2.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))':
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
boxen: 5.1.2
check-disk-space: 3.4.0
reflect-metadata: 0.2.2
rxjs: 7.8.2
optionalDependencies:
'@nestjs/axios': 4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)
'@nestjs/typeorm': 11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
typeorm: 0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
optional: true
'@nestjs/terminus@11.0.0(@nestjs/axios@4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2))(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/typeorm@11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))))(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))':
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.9)(@nestjs/websockets@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)
boxen: 5.1.2
check-disk-space: 3.4.0
reflect-metadata: 0.2.2
rxjs: 7.8.2
optionalDependencies:
'@nestjs/axios': 4.0.1(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.13.2)(rxjs@7.8.2)
'@nestjs/typeorm': 11.0.0(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))
typeorm: 0.3.27(ioredis@5.8.2)(mysql2@3.15.3)(redis@4.7.1)(reflect-metadata@0.2.2)(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))
'@nestjs/testing@11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)': '@nestjs/testing@11.1.9(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.9)(@nestjs/platform-express@11.1.9)':
dependencies: dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -6038,6 +6375,11 @@ snapshots:
dependencies: dependencies:
tslib: 2.8.1 tslib: 2.8.1
'@so-ric/colorspace@1.1.6':
dependencies:
color: 5.0.3
text-hex: 1.0.0
'@socket.io/component-emitter@3.1.2': {} '@socket.io/component-emitter@3.1.2': {}
'@sqltools/formatter@1.2.5': {} '@sqltools/formatter@1.2.5': {}
@@ -6071,6 +6413,10 @@ snapshots:
tslib: 2.8.1 tslib: 2.8.1
optional: true optional: true
'@types/async-retry@1.4.9':
dependencies:
'@types/retry': 0.12.5
'@types/babel__core@7.20.5': '@types/babel__core@7.20.5':
dependencies: dependencies:
'@babel/parser': 7.28.5 '@babel/parser': 7.28.5
@@ -6210,6 +6556,10 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- aws-crt - aws-crt
'@types/opossum@8.1.9':
dependencies:
'@types/node': 22.19.1
'@types/passport-jwt@4.0.1': '@types/passport-jwt@4.0.1':
dependencies: dependencies:
'@types/jsonwebtoken': 9.0.10 '@types/jsonwebtoken': 9.0.10
@@ -6228,6 +6578,8 @@ snapshots:
'@types/range-parser@1.2.7': {} '@types/range-parser@1.2.7': {}
'@types/retry@0.12.5': {}
'@types/send@0.17.6': '@types/send@0.17.6':
dependencies: dependencies:
'@types/mime': 1.3.5 '@types/mime': 1.3.5
@@ -6257,6 +6609,8 @@ snapshots:
'@types/methods': 1.1.4 '@types/methods': 1.1.4
'@types/superagent': 8.1.9 '@types/superagent': 8.1.9
'@types/triple-beam@1.3.5': {}
'@types/uuid@11.0.0': '@types/uuid@11.0.0':
dependencies: dependencies:
uuid: 13.0.0 uuid: 13.0.0
@@ -6574,6 +6928,10 @@ snapshots:
json-schema-traverse: 1.0.0 json-schema-traverse: 1.0.0
require-from-string: 2.0.2 require-from-string: 2.0.2
ansi-align@3.0.1:
dependencies:
string-width: 4.2.3
ansi-colors@4.1.3: {} ansi-colors@4.1.3: {}
ansi-escapes@4.3.2: ansi-escapes@4.3.2:
@@ -6633,6 +6991,12 @@ snapshots:
asap@2.0.6: {} asap@2.0.6: {}
async-retry@1.3.3:
dependencies:
retry: 0.13.1
async@3.2.6: {}
asynckit@0.4.0: {} asynckit@0.4.0: {}
available-typed-arrays@1.0.7: available-typed-arrays@1.0.7:
@@ -6714,6 +7078,8 @@ snapshots:
node-addon-api: 8.5.0 node-addon-api: 8.5.0
node-gyp-build: 4.8.4 node-gyp-build: 4.8.4
bintrees@1.0.2: {}
bl@4.1.0: bl@4.1.0:
dependencies: dependencies:
buffer: 5.7.1 buffer: 5.7.1
@@ -6736,6 +7102,17 @@ snapshots:
bowser@2.12.1: {} bowser@2.12.1: {}
boxen@5.1.2:
dependencies:
ansi-align: 3.0.1
camelcase: 6.3.0
chalk: 4.1.2
cli-boxes: 2.2.1
string-width: 4.2.3
type-fest: 0.20.2
widest-line: 3.1.0
wrap-ansi: 7.0.0
brace-expansion@1.1.12: brace-expansion@1.1.12:
dependencies: dependencies:
balanced-match: 1.0.2 balanced-match: 1.0.2
@@ -6858,6 +7235,8 @@ snapshots:
chardet@2.1.1: {} chardet@2.1.1: {}
check-disk-space@3.4.0: {}
chokidar@4.0.3: chokidar@4.0.3:
dependencies: dependencies:
readdirp: 4.1.2 readdirp: 4.1.2
@@ -6876,6 +7255,8 @@ snapshots:
libphonenumber-js: 1.12.27 libphonenumber-js: 1.12.27
validator: 13.15.23 validator: 13.15.23
cli-boxes@2.2.1: {}
cli-cursor@3.1.0: cli-cursor@3.1.0:
dependencies: dependencies:
restore-cursor: 3.1.0 restore-cursor: 3.1.0
@@ -6908,8 +7289,23 @@ snapshots:
dependencies: dependencies:
color-name: 1.1.4 color-name: 1.1.4
color-convert@3.1.3:
dependencies:
color-name: 2.1.0
color-name@1.1.4: {} color-name@1.1.4: {}
color-name@2.1.0: {}
color-string@2.1.4:
dependencies:
color-name: 2.1.0
color@5.0.3:
dependencies:
color-convert: 3.1.3
color-string: 2.1.4
combined-stream@1.0.8: combined-stream@1.0.8:
dependencies: dependencies:
delayed-stream: 1.0.0 delayed-stream: 1.0.0
@@ -7070,6 +7466,8 @@ snapshots:
emoji-regex@9.2.2: {} emoji-regex@9.2.2: {}
enabled@2.0.0: {}
encodeurl@2.0.0: {} encodeurl@2.0.0: {}
engine.io-parser@5.2.3: {} engine.io-parser@5.2.3: {}
@@ -7306,6 +7704,8 @@ snapshots:
dependencies: dependencies:
bser: 2.1.1 bser: 2.1.1
fecha@4.2.3: {}
fflate@0.8.2: {} fflate@0.8.2: {}
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
@@ -7357,6 +7757,8 @@ snapshots:
flatted@3.3.3: {} flatted@3.3.3: {}
fn.name@1.1.0: {}
follow-redirects@1.15.11: {} follow-redirects@1.15.11: {}
for-each@0.3.5: for-each@0.3.5:
@@ -8071,6 +8473,8 @@ snapshots:
dependencies: dependencies:
'@keyv/serialize': 1.1.1 '@keyv/serialize': 1.1.1
kuler@2.0.0: {}
leven@3.1.0: {} leven@3.1.0: {}
levn@0.4.1: levn@0.4.1:
@@ -8127,6 +8531,15 @@ snapshots:
chalk: 4.1.2 chalk: 4.1.2
is-unicode-supported: 0.1.0 is-unicode-supported: 0.1.0
logform@2.7.0:
dependencies:
'@colors/colors': 1.6.0
'@types/triple-beam': 1.3.5
fecha: 4.2.3
ms: 2.1.3
safe-stable-stringify: 2.5.0
triple-beam: 1.4.1
long@5.3.2: {} long@5.3.2: {}
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
@@ -8272,6 +8685,12 @@ snapshots:
neo-async@2.6.2: {} neo-async@2.6.2: {}
nest-winston@1.10.2(@nestjs/common@11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(winston@3.18.3):
dependencies:
'@nestjs/common': 11.1.9(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2)
fast-safe-stringify: 2.1.1
winston: 3.18.3
node-abort-controller@3.1.1: {} node-abort-controller@3.1.1: {}
node-addon-api@8.5.0: {} node-addon-api@8.5.0: {}
@@ -8313,10 +8732,16 @@ snapshots:
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
one-time@1.0.0:
dependencies:
fn.name: 1.1.0
onetime@5.1.2: onetime@5.1.2:
dependencies: dependencies:
mimic-fn: 2.1.0 mimic-fn: 2.1.0
opossum@9.0.0: {}
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
deep-is: 0.1.4 deep-is: 0.1.4
@@ -8438,6 +8863,11 @@ snapshots:
ansi-styles: 5.2.0 ansi-styles: 5.2.0
react-is: 18.3.1 react-is: 18.3.1
prom-client@15.1.3:
dependencies:
'@opentelemetry/api': 1.9.0
tdigest: 0.1.2
promise-coalesce@1.5.0: {} promise-coalesce@1.5.0: {}
proxy-addr@2.0.7: proxy-addr@2.0.7:
@@ -8518,6 +8948,8 @@ snapshots:
onetime: 5.1.2 onetime: 5.1.2
signal-exit: 3.0.7 signal-exit: 3.0.7
retry@0.13.1: {}
reusify@1.1.0: {} reusify@1.1.0: {}
router@2.2.0: router@2.2.0:
@@ -8544,6 +8976,8 @@ snapshots:
safe-buffer@5.2.1: {} safe-buffer@5.2.1: {}
safe-stable-stringify@2.5.0: {}
safer-buffer@2.1.2: {} safer-buffer@2.1.2: {}
schema-utils@3.3.0: schema-utils@3.3.0:
@@ -8705,6 +9139,8 @@ snapshots:
sqlstring@2.3.3: {} sqlstring@2.3.3: {}
stack-trace@0.0.10: {}
stack-utils@2.0.6: stack-utils@2.0.6:
dependencies: dependencies:
escape-string-regexp: 2.0.0 escape-string-regexp: 2.0.0
@@ -8811,6 +9247,10 @@ snapshots:
tapable@2.3.0: {} tapable@2.3.0: {}
tdigest@0.1.2:
dependencies:
bintrees: 1.0.2
terser-webpack-plugin@5.3.14(webpack@5.100.2): terser-webpack-plugin@5.3.14(webpack@5.100.2):
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.31 '@jridgewell/trace-mapping': 0.3.31
@@ -8833,6 +9273,8 @@ snapshots:
glob: 7.2.3 glob: 7.2.3
minimatch: 3.1.2 minimatch: 3.1.2
text-hex@1.0.0: {}
tmpl@1.0.5: {} tmpl@1.0.5: {}
to-buffer@1.2.2: to-buffer@1.2.2:
@@ -8853,6 +9295,8 @@ snapshots:
'@tokenizer/token': 0.3.0 '@tokenizer/token': 0.3.0
ieee754: 1.2.1 ieee754: 1.2.1
triple-beam@1.4.1: {}
ts-api-utils@2.1.0(typescript@5.9.3): ts-api-utils@2.1.0(typescript@5.9.3):
dependencies: dependencies:
typescript: 5.9.3 typescript: 5.9.3
@@ -8926,6 +9370,8 @@ snapshots:
type-detect@4.0.8: {} type-detect@4.0.8: {}
type-fest@0.20.2: {}
type-fest@0.21.3: {} type-fest@0.21.3: {}
type-fest@4.41.0: {} type-fest@4.41.0: {}
@@ -9126,6 +9572,30 @@ snapshots:
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0
widest-line@3.1.0:
dependencies:
string-width: 4.2.3
winston-transport@4.9.0:
dependencies:
logform: 2.7.0
readable-stream: 3.6.2
triple-beam: 1.4.1
winston@3.18.3:
dependencies:
'@colors/colors': 1.6.0
'@dabh/diagnostics': 2.0.8
async: 3.2.6
is-stream: 2.0.1
logform: 2.7.0
one-time: 1.0.0
readable-stream: 3.6.2
safe-stable-stringify: 2.5.0
stack-trace: 0.0.10
triple-beam: 1.4.1
winston-transport: 4.9.0
word-wrap@1.2.5: {} word-wrap@1.2.5: {}
wordwrap@1.0.0: {} wordwrap@1.0.0: {}

View File

@@ -42,6 +42,7 @@ import { MonitoringModule } from './modules/monitoring/monitoring.module';
import { ResilienceModule } from './common/resilience/resilience.module'; // ✅ Import import { ResilienceModule } from './common/resilience/resilience.module'; // ✅ Import
// ... imports // ... imports
import { SearchModule } from './modules/search/search.module'; // ✅ Import import { SearchModule } from './modules/search/search.module'; // ✅ Import
import { RedisModule } from '@nestjs-modules/ioredis'; // [NEW]
@Module({ @Module({
imports: [ imports: [
// 1. Setup Config Module พร้อม Validation // 1. Setup Config Module พร้อม Validation
@@ -113,7 +114,18 @@ import { SearchModule } from './modules/search/search.module'; // ✅ Import
}, },
}), }),
}), }),
// [NEW] Setup Redis Module (สำหรับ InjectRedis)
RedisModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: 'single',
url: `redis://${configService.get('REDIS_HOST')}:${configService.get('REDIS_PORT')}`,
options: {
password: configService.get('REDIS_PASSWORD'),
},
}),
inject: [ConfigService],
}),
// 📊 Register Monitoring Module (Health & Metrics) [Req 6.10] // 📊 Register Monitoring Module (Health & Metrics) [Req 6.10]
MonitoringModule, MonitoringModule,

View File

@@ -6,6 +6,7 @@ import {
CreateDateColumn, CreateDateColumn,
ManyToOne, ManyToOne,
JoinColumn, JoinColumn,
PrimaryColumn, // ✅ [Fix] เพิ่ม Import นี้
} from 'typeorm'; } from 'typeorm';
import { User } from '../../modules/user/entities/user.entity'; import { User } from '../../modules/user/entities/user.entity';
@@ -46,7 +47,9 @@ export class AuditLog {
@Column({ name: 'user_agent', length: 255, nullable: true }) @Column({ name: 'user_agent', length: 255, nullable: true })
userAgent?: string; userAgent?: string;
// ✅ [Fix] รวม Decorator ไว้ที่นี่ที่เดียว
@CreateDateColumn({ name: 'created_at' }) @CreateDateColumn({ name: 'created_at' })
@PrimaryColumn() // เพื่อบอกว่าเป็น Composite PK คู่กับ auditId
createdAt!: Date; createdAt!: Date;
// Relations // Relations

View File

@@ -0,0 +1,75 @@
-- ============================================================
-- Database Partitioning Script for LCBP3-DMS (Fixed #1075)
-- Target Tables: audit_logs, notifications
-- Strategy: Range Partitioning by YEAR(created_at)
-- ============================================================
-- ------------------------------------------------------------
-- 1. Audit Logs Partitioning
-- ------------------------------------------------------------
-- Step 1: เอา AUTO_INCREMENT ออกก่อน (เพื่อไม่ให้ติด Error 1075 ตอนลบ PK)
ALTER TABLE audit_logs
MODIFY audit_id BIGINT NOT NULL;
-- Step 2: ลบ Primary Key เดิม
ALTER TABLE audit_logs DROP PRIMARY KEY;
-- Step 3: สร้าง Primary Key ใหม่ (รวม created_at เพื่อทำ Partition)
ALTER TABLE audit_logs
ADD PRIMARY KEY (audit_id, created_at);
-- Step 4: ใส่ AUTO_INCREMENT กลับเข้าไป
ALTER TABLE audit_logs
MODIFY audit_id BIGINT NOT NULL AUTO_INCREMENT;
-- Step 5: สร้าง Partition
ALTER TABLE audit_logs PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p_old
VALUES LESS THAN (2024),
PARTITION p2024
VALUES LESS THAN (2025),
PARTITION p2025
VALUES LESS THAN (2026),
PARTITION p2026
VALUES LESS THAN (2027),
PARTITION p2027
VALUES LESS THAN (2028),
PARTITION p2028
VALUES LESS THAN (2029),
PARTITION p2029
VALUES LESS THAN (2030),
PARTITION p2030
VALUES LESS THAN (2031),
PARTITION p_future
VALUES LESS THAN MAXVALUE
);
-- ------------------------------------------------------------
-- 2. Notifications Partitioning
-- ------------------------------------------------------------
-- Step 1: เอา AUTO_INCREMENT ออกก่อน
ALTER TABLE notifications
MODIFY id INT NOT NULL;
-- Step 2: ลบ Primary Key เดิม
ALTER TABLE notifications DROP PRIMARY KEY;
-- Step 3: สร้าง Primary Key ใหม่
ALTER TABLE notifications
ADD PRIMARY KEY (id, created_at);
-- Step 4: ใส่ AUTO_INCREMENT กลับเข้าไป
ALTER TABLE notifications
MODIFY id INT NOT NULL AUTO_INCREMENT;
-- Step 5: สร้าง Partition
ALTER TABLE notifications PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p_old
VALUES LESS THAN (2024),
PARTITION p2024
VALUES LESS THAN (2025),
PARTITION p2025
VALUES LESS THAN (2026),
PARTITION p2026
VALUES LESS THAN (2027),
PARTITION p2027
VALUES LESS THAN (2028),
PARTITION p2028
VALUES LESS THAN (2029),
PARTITION p2029
VALUES LESS THAN (2030),
PARTITION p2030
VALUES LESS THAN (2031),
PARTITION p_future
VALUES LESS THAN MAXVALUE
);

View File

@@ -0,0 +1,82 @@
// src/database/seeds/workflow-definitions.seed.ts
import { DataSource } from 'typeorm';
import { WorkflowDefinition } from '../../modules/workflow-engine/entities/workflow-definition.entity';
import { WorkflowDslService } from '../../modules/workflow-engine/workflow-dsl.service';
export const seedWorkflowDefinitions = async (dataSource: DataSource) => {
const repo = dataSource.getRepository(WorkflowDefinition);
const dslService = new WorkflowDslService();
// 1. RFA Workflow (Standard)
const rfaDsl = {
workflow: 'RFA',
version: 1,
states: [
{
name: 'DRAFT',
initial: true,
on: { SUBMIT: { to: 'IN_REVIEW', requirements: [{ role: 'Editor' }] } },
},
{
name: 'IN_REVIEW',
on: {
APPROVE: {
to: 'APPROVED',
requirements: [{ role: 'Contract Admin' }],
},
REJECT: {
to: 'REJECTED',
requirements: [{ role: 'Contract Admin' }],
},
COMMENT: { to: 'DRAFT', requirements: [{ role: 'Contract Admin' }] }, // ส่งกลับแก้ไข
},
},
{ name: 'APPROVED', terminal: true },
{ name: 'REJECTED', terminal: true },
],
};
// 2. Circulation Workflow
const circulationDsl = {
workflow: 'CIRCULATION',
version: 1,
states: [
{
name: 'OPEN',
initial: true,
on: { SEND: { to: 'IN_REVIEW' } },
},
{
name: 'IN_REVIEW',
on: {
COMPLETE: { to: 'COMPLETED' }, // เมื่อทุกคนตอบครบ
CANCEL: { to: 'CANCELLED' },
},
},
{ name: 'COMPLETED', terminal: true },
{ name: 'CANCELLED', terminal: true },
],
};
const workflows = [rfaDsl, circulationDsl];
for (const dsl of workflows) {
const exists = await repo.findOne({
where: { workflow_code: dsl.workflow, version: dsl.version },
});
if (!exists) {
const compiled = dslService.compile(dsl);
await repo.save(
repo.create({
workflow_code: dsl.workflow,
version: dsl.version,
dsl: dsl,
compiled: compiled,
is_active: true,
}),
);
console.log(`✅ Seeded Workflow: ${dsl.workflow} v${dsl.version}`);
}
}
};

View File

@@ -1,5 +1,3 @@
// File: src/modules/master/dto/create-tag.dto.ts
import { IsString, IsNotEmpty, IsOptional } from 'class-validator'; import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
@@ -7,12 +5,9 @@ export class CreateTagDto {
@ApiProperty({ example: 'URGENT', description: 'ชื่อ Tag' }) @ApiProperty({ example: 'URGENT', description: 'ชื่อ Tag' })
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
tag_name: string; tag_name!: string; // เพิ่ม !
@ApiProperty({ @ApiProperty({ example: 'คำอธิบาย', description: 'คำอธิบาย' })
example: 'เอกสารด่วนต้องดำเนินการทันที',
description: 'คำอธิบาย',
})
@IsString() @IsString()
@IsOptional() @IsOptional()
description?: string; description?: string;

View File

@@ -1,5 +1,3 @@
// File: src/modules/master/entities/tag.entity.ts
import { import {
Entity, Entity,
Column, Column,
@@ -11,17 +9,17 @@ import {
@Entity('tags') @Entity('tags')
export class Tag { export class Tag {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id: number; id!: number; // เพิ่ม !
@Column({ length: 100, unique: true, comment: 'ชื่อ Tag' }) @Column({ length: 100, unique: true })
tag_name: string; tag_name!: string; // เพิ่ม !
@Column({ type: 'text', nullable: true, comment: 'คำอธิบายแท็ก' }) @Column({ type: 'text', nullable: true })
description: string; description!: string; // เพิ่ม !
@CreateDateColumn() @CreateDateColumn()
created_at: Date; created_at!: Date; // เพิ่ม !
@UpdateDateColumn() @UpdateDateColumn()
updated_at: Date; updated_at!: Date; // เพิ่ม !
} }

View File

@@ -49,15 +49,15 @@ export class MasterService {
async findAllCorrespondenceTypes() { async findAllCorrespondenceTypes() {
return this.corrTypeRepo.find({ return this.corrTypeRepo.find({
where: { is_active: true }, where: { isActive: true }, // ✅ แก้เป็น camelCase
order: { sort_order: 'ASC' }, order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
}); });
} }
async findAllCorrespondenceStatuses() { async findAllCorrespondenceStatuses() {
return this.corrStatusRepo.find({ return this.corrStatusRepo.find({
where: { is_active: true }, where: { isActive: true }, // ✅ แก้เป็น camelCase
order: { sort_order: 'ASC' }, order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
}); });
} }
@@ -67,22 +67,22 @@ export class MasterService {
async findAllRfaTypes() { async findAllRfaTypes() {
return this.rfaTypeRepo.find({ return this.rfaTypeRepo.find({
where: { is_active: true }, where: { isActive: true }, // ✅ แก้เป็น camelCase
order: { sort_order: 'ASC' }, order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
}); });
} }
async findAllRfaStatuses() { async findAllRfaStatuses() {
return this.rfaStatusRepo.find({ return this.rfaStatusRepo.find({
where: { is_active: true }, where: { isActive: true }, // ✅ แก้เป็น camelCase
order: { sort_order: 'ASC' }, order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
}); });
} }
async findAllRfaApproveCodes() { async findAllRfaApproveCodes() {
return this.rfaApproveRepo.find({ return this.rfaApproveRepo.find({
where: { is_active: true }, where: { isActive: true }, // ✅ แก้เป็น camelCase
order: { sort_order: 'ASC' }, order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
}); });
} }
@@ -92,8 +92,8 @@ export class MasterService {
async findAllCirculationStatuses() { async findAllCirculationStatuses() {
return this.circulationStatusRepo.find({ return this.circulationStatusRepo.find({
where: { is_active: true }, where: { isActive: true }, // ✅ แก้เป็น camelCase
order: { sort_order: 'ASC' }, order: { sortOrder: 'ASC' }, // ✅ แก้เป็น camelCase
}); });
} }
@@ -101,9 +101,6 @@ export class MasterService {
// 🏷️ Tag Management (CRUD) // 🏷️ Tag Management (CRUD)
// ================================================================= // =================================================================
/**
* ค้นหา Tag ทั้งหมด พร้อมรองรับการ Search และ Pagination
*/
async findAllTags(query?: SearchTagDto) { async findAllTags(query?: SearchTagDto) {
const qb = this.tagRepo.createQueryBuilder('tag'); const qb = this.tagRepo.createQueryBuilder('tag');
@@ -115,14 +112,12 @@ export class MasterService {
qb.orderBy('tag.tag_name', 'ASC'); qb.orderBy('tag.tag_name', 'ASC');
// Pagination Logic
if (query?.page && query?.limit) { if (query?.page && query?.limit) {
const page = query.page; const page = query.page;
const limit = query.limit; const limit = query.limit;
qb.skip((page - 1) * limit).take(limit); qb.skip((page - 1) * limit).take(limit);
} }
// ถ้ามีการแบ่งหน้า ให้ส่งคืนทั้งข้อมูลและจำนวนทั้งหมด (count)
if (query?.page && query?.limit) { if (query?.page && query?.limit) {
const [items, total] = await qb.getManyAndCount(); const [items, total] = await qb.getManyAndCount();
return { return {
@@ -153,7 +148,7 @@ export class MasterService {
} }
async updateTag(id: number, dto: UpdateTagDto) { async updateTag(id: number, dto: UpdateTagDto) {
const tag = await this.findOneTag(id); // Reuse findOne for check const tag = await this.findOneTag(id);
Object.assign(tag, dto); Object.assign(tag, dto);
return this.tagRepo.save(tag); return this.tagRepo.save(tag);
} }

View File

@@ -0,0 +1,16 @@
import { IsBoolean, IsOptional, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class SetMaintenanceDto {
@ApiProperty({ description: 'สถานะ Maintenance (true = เปิด, false = ปิด)' })
@IsBoolean()
enabled!: boolean; // ✅ เพิ่ม ! ตรงนี้
@ApiProperty({
description: 'เหตุผลที่ปิดปรับปรุง (แสดงให้ User เห็น)',
required: false,
})
@IsOptional()
@IsString()
reason?: string; // Optional (?) ไม่ต้องใส่ !
}

View File

@@ -0,0 +1,30 @@
import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { MonitoringService } from './monitoring.service';
import { SetMaintenanceDto } from './dto/set-maintenance.dto';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { BypassMaintenance } from '../../common/decorators/bypass-maintenance.decorator';
@ApiTags('System Monitoring')
@Controller('monitoring')
export class MonitoringController {
constructor(private readonly monitoringService: MonitoringService) {}
@Get('maintenance')
@ApiOperation({ summary: 'Check maintenance status (Public)' })
@BypassMaintenance() // API นี้ต้องเรียกได้แม้ระบบปิดอยู่
getMaintenanceStatus() {
return this.monitoringService.getMaintenanceStatus();
}
@Post('maintenance')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@RequirePermission('system.manage_all') // เฉพาะ Superadmin เท่านั้น
@BypassMaintenance() // Admin ต้องยิงเปิด/ปิดได้แม้ระบบจะปิดอยู่
@ApiOperation({ summary: 'Toggle Maintenance Mode (Admin Only)' })
setMaintenanceMode(@Body() dto: SetMaintenanceDto) {
return this.monitoringService.setMaintenanceMode(dto);
}
}

View File

@@ -1,23 +1,34 @@
// File: src/modules/monitoring/monitoring.module.ts // File: src/modules/monitoring/monitoring.module.ts
import { Global, Module } from '@nestjs/common'; import { Global, Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus'; import { TerminusModule } from '@nestjs/terminus';
import { HttpModule } from '@nestjs/axios'; import { HttpModule } from '@nestjs/axios';
import { APP_INTERCEPTOR } from '@nestjs/core'; import { APP_INTERCEPTOR } from '@nestjs/core';
// Existing Components
import { HealthController } from './controllers/health.controller'; import { HealthController } from './controllers/health.controller';
import { MetricsService } from './services/metrics.service'; import { MetricsService } from './services/metrics.service';
import { PerformanceInterceptor } from '../../common/interceptors/performance.interceptor'; import { PerformanceInterceptor } from '../../common/interceptors/performance.interceptor';
@Global() // ทำให้ Module นี้ใช้งานได้ทั่วทั้ง App โดยไม่ต้อง Import ซ้ำ // [NEW] Maintenance Mode Components
import { MonitoringController } from './monitoring.controller';
import { MonitoringService } from './monitoring.service';
@Global() // Module นี้เป็น Global (ดีแล้วครับ)
@Module({ @Module({
imports: [TerminusModule, HttpModule], imports: [TerminusModule, HttpModule],
controllers: [HealthController], controllers: [
HealthController, // ✅ ของเดิม: /health
MonitoringController, // ✅ ของใหม่: /monitoring/maintenance
],
providers: [ providers: [
MetricsService, MetricsService, // ✅ ของเดิม
MonitoringService, // ✅ ของใหม่ (Logic เปิด/ปิด Maintenance)
{ {
provide: APP_INTERCEPTOR, // Register Global Interceptor provide: APP_INTERCEPTOR,
useClass: PerformanceInterceptor, useClass: PerformanceInterceptor, // ✅ ของเดิม (จับเวลา Response Time)
}, },
], ],
exports: [MetricsService], exports: [MetricsService, MonitoringService],
}) })
export class MonitoringModule {} export class MonitoringModule {}

View File

@@ -0,0 +1,44 @@
// File: src/modules/monitoring/monitoring.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { InjectRedis } from '@nestjs-modules/ioredis';
import Redis from 'ioredis';
import { SetMaintenanceDto } from './dto/set-maintenance.dto';
@Injectable()
export class MonitoringService {
private readonly logger = new Logger(MonitoringService.name);
private readonly MAINTENANCE_KEY = 'system:maintenance_mode';
constructor(@InjectRedis() private readonly redis: Redis) {}
/**
* ตรวจสอบสถานะปัจจุบัน
*/
async getMaintenanceStatus() {
const status = await this.redis.get(this.MAINTENANCE_KEY);
return {
isEnabled: status === 'true',
message:
status === 'true' ? 'System is under maintenance' : 'System is normal',
};
}
/**
* ตั้งค่า Maintenance Mode
*/
async setMaintenanceMode(dto: SetMaintenanceDto) {
if (dto.enabled) {
await this.redis.set(this.MAINTENANCE_KEY, 'true');
// เก็บเหตุผลไว้ใน Key อื่นก็ได้ถ้าต้องการ แต่เบื้องต้น Guard เช็คแค่ Key นี้
this.logger.warn(
`⚠️ SYSTEM ENTERED MAINTENANCE MODE: ${dto.reason || 'No reason provided'}`,
);
} else {
await this.redis.del(this.MAINTENANCE_KEY);
this.logger.log('✅ System exited maintenance mode');
}
return this.getMaintenanceStatus();
}
}

View File

@@ -5,6 +5,7 @@ import {
CreateDateColumn, CreateDateColumn,
ManyToOne, ManyToOne,
JoinColumn, JoinColumn,
PrimaryColumn, // ✅ [Fix] เพิ่ม Import นี้
} from 'typeorm'; } from 'typeorm';
import { User } from '../../user/entities/user.entity'; import { User } from '../../user/entities/user.entity';
@@ -44,7 +45,9 @@ export class Notification {
@Column({ name: 'entity_id', nullable: true }) @Column({ name: 'entity_id', nullable: true })
entityId?: number; entityId?: number;
// ✅ [Fix] รวม Decorator ไว้ที่นี่ที่เดียว (เป็นทั้ง CreateDate และ PrimaryColumn สำหรับ Partition)
@CreateDateColumn({ name: 'created_at' }) @CreateDateColumn({ name: 'created_at' })
@PrimaryColumn()
createdAt!: Date; createdAt!: Date;
// --- Relations --- // --- Relations ---

View File

@@ -1,26 +1,43 @@
import { Processor, WorkerHost } from '@nestjs/bullmq'; // File: src/modules/notification/notification.processor.ts
import { Job } from 'bullmq';
import { Processor, WorkerHost, InjectQueue } from '@nestjs/bullmq';
import { Job, Queue } from 'bullmq';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { InjectRedis } from '@nestjs-modules/ioredis';
import Redis from 'ioredis';
import * as nodemailer from 'nodemailer'; import * as nodemailer from 'nodemailer';
import axios from 'axios'; import axios from 'axios';
import { UserService } from '../user/user.service'; import { UserService } from '../user/user.service';
interface NotificationPayload {
userId: number;
title: string;
message: string;
link: string;
type: 'EMAIL' | 'LINE' | 'SYSTEM';
}
@Processor('notifications') @Processor('notifications')
export class NotificationProcessor extends WorkerHost { export class NotificationProcessor extends WorkerHost {
private readonly logger = new Logger(NotificationProcessor.name); private readonly logger = new Logger(NotificationProcessor.name);
private mailerTransport: nodemailer.Transporter; private mailerTransport: nodemailer.Transporter;
// ค่าคงที่สำหรับ Digest (เช่น รอ 5 นาที)
private readonly DIGEST_DELAY = 5 * 60 * 1000;
constructor( constructor(
private configService: ConfigService, private configService: ConfigService,
private userService: UserService, private userService: UserService,
@InjectQueue('notifications') private notificationQueue: Queue,
@InjectRedis() private readonly redis: Redis,
) { ) {
super(); super();
// Setup Nodemailer // Setup Nodemailer
this.mailerTransport = nodemailer.createTransport({ this.mailerTransport = nodemailer.createTransport({
host: this.configService.get('SMTP_HOST'), host: this.configService.get('SMTP_HOST'),
port: this.configService.get('SMTP_PORT'), port: Number(this.configService.get('SMTP_PORT')),
secure: this.configService.get('SMTP_SECURE') === 'true', secure: this.configService.get('SMTP_SECURE') === 'true',
auth: { auth: {
user: this.configService.get('SMTP_USER'), user: this.configService.get('SMTP_USER'),
@@ -30,59 +47,196 @@ export class NotificationProcessor extends WorkerHost {
} }
async process(job: Job<any, any, string>): Promise<any> { async process(job: Job<any, any, string>): Promise<any> {
this.logger.debug(`Processing job ${job.name} for user ${job.data.userId}`); this.logger.debug(`Processing job ${job.name} (ID: ${job.id})`);
switch (job.name) { try {
case 'send-email': switch (job.name) {
return this.handleSendEmail(job.data); case 'dispatch-notification':
case 'send-line': // Job หลัก: ตัดสินใจว่าจะส่งเลย หรือจะเข้า Digest Queue
return this.handleSendLine(job.data); return this.handleDispatch(job.data);
default:
throw new Error(`Unknown job name: ${job.name}`); case 'process-digest':
// Job รอง: ทำงานเมื่อครบเวลา Delay เพื่อส่งแบบรวม
return this.handleProcessDigest(job.data.userId, job.data.type);
default:
throw new Error(`Unknown job name: ${job.name}`);
}
} catch (error) {
// ✅ แก้ไขตรงนี้: Type Casting (error as Error)
this.logger.error(
`Failed to process job ${job.name}: ${(error as Error).message}`,
(error as Error).stack,
);
throw error; // ให้ BullMQ จัดการ Retry
} }
} }
private async handleSendEmail(data: any) { /**
const user = await this.userService.findOne(data.userId); * ฟังก์ชันตัดสินใจ (Dispatcher)
if (!user || !user.email) { * ตรวจสอบ User Preferences และ Digest Mode
this.logger.warn(`User ${data.userId} has no email`); */
private async handleDispatch(data: NotificationPayload) {
// 1. ดึง User พร้อม Preferences
const user: any = await this.userService.findOne(data.userId);
if (!user) {
this.logger.warn(`User ${data.userId} not found, skipping notification.`);
return; return;
} }
const prefs = user.preferences || {
notify_email: true,
notify_line: true,
digest_mode: false,
};
// 2. ตรวจสอบว่า User ปิดรับการแจ้งเตือนหรือไม่
if (data.type === 'EMAIL' && !prefs.notify_email) return;
if (data.type === 'LINE' && !prefs.notify_line) return;
// 3. ตรวจสอบ Digest Mode
if (prefs.digest_mode) {
await this.addToDigest(data);
} else {
// ส่งทันที (Real-time)
if (data.type === 'EMAIL') await this.sendEmailImmediate(user, data);
if (data.type === 'LINE') await this.sendLineImmediate(user, data);
}
}
/**
* เพิ่มข้อความลงใน Redis List และตั้งเวลาส่ง (Delayed Job)
*/
private async addToDigest(data: NotificationPayload) {
const key = `digest:${data.type}:${data.userId}`;
// 1. Push ข้อมูลลง Redis List
await this.redis.rpush(key, JSON.stringify(data));
// 2. ตรวจสอบว่ามี "ตัวนับเวลาถอยหลัง" (Delayed Job) อยู่หรือยัง?
const lockKey = `digest:lock:${data.type}:${data.userId}`;
const isLocked = await this.redis.get(lockKey);
if (!isLocked) {
// ถ้ายังไม่มี Job รออยู่ ให้สร้างใหม่
await this.notificationQueue.add(
'process-digest',
{ userId: data.userId, type: data.type },
{
delay: this.DIGEST_DELAY,
jobId: `digest-${data.type}-${data.userId}-${Date.now()}`,
},
);
// Set Lock ไว้ตามเวลา Delay เพื่อไม่ให้สร้าง Job ซ้ำ
await this.redis.set(lockKey, '1', 'PX', this.DIGEST_DELAY);
this.logger.log(
`Scheduled digest for User ${data.userId} (${data.type}) in ${this.DIGEST_DELAY}ms`,
);
}
}
/**
* ประมวลผล Digest (ส่งแบบรวม)
*/
private async handleProcessDigest(userId: number, type: 'EMAIL' | 'LINE') {
const key = `digest:${type}:${userId}`;
const lockKey = `digest:lock:${type}:${userId}`;
// 1. ดึงข้อความทั้งหมดจาก Redis และลบออกทันที
const messagesRaw = await this.redis.lrange(key, 0, -1);
await this.redis.del(key);
await this.redis.del(lockKey); // Clear lock
if (!messagesRaw || messagesRaw.length === 0) return;
const messages: NotificationPayload[] = messagesRaw.map((m) =>
JSON.parse(m),
);
const user = await this.userService.findOne(userId);
if (type === 'EMAIL') {
await this.sendEmailDigest(user, messages);
} else if (type === 'LINE') {
await this.sendLineDigest(user, messages);
}
}
// =====================================================
// SENDERS (Immediate & Digest)
// =====================================================
private async sendEmailImmediate(user: any, data: NotificationPayload) {
if (!user.email) return;
await this.mailerTransport.sendMail({ await this.mailerTransport.sendMail({
from: '"LCBP3 DMS" <no-reply@np-dms.work>', from: '"LCBP3 DMS" <no-reply@np-dms.work>',
to: user.email, to: user.email,
subject: `[DMS] ${data.title}`, subject: `[DMS] ${data.title}`,
html: ` html: `<h3>${data.title}</h3><p>${data.message}</p><br/><a href="${data.link}">คลิกเพื่อดูรายละเอียด</a>`,
<h3>${data.title}</h3>
<p>${data.message}</p>
<br/>
<a href="${data.link}">คลิกเพื่อดูรายละเอียด</a>
`,
}); });
this.logger.log(`Email sent to ${user.email}`); this.logger.log(`Email sent to ${user.email}`);
} }
private async handleSendLine(data: any) { private async sendEmailDigest(user: any, messages: NotificationPayload[]) {
const user = await this.userService.findOne(data.userId); if (!user.email) return;
// ตรวจสอบว่า User มี Line ID หรือไม่ (หรือใช้ Group Token ถ้าเป็นระบบรวม)
// ในที่นี้สมมติว่าเรายิงเข้า n8n webhook เพื่อจัดการต่อ
const n8nWebhookUrl = this.configService.get('N8N_LINE_WEBHOOK_URL');
if (!n8nWebhookUrl) { // สร้าง HTML List
this.logger.warn('N8N_LINE_WEBHOOK_URL not configured'); const listItems = messages
return; .map(
} (msg) =>
`<li><strong>${msg.title}</strong>: ${msg.message} <a href="${msg.link}">[View]</a></li>`,
)
.join('');
await this.mailerTransport.sendMail({
from: '"LCBP3 DMS" <no-reply@np-dms.work>',
to: user.email,
subject: `[DMS Summary] คุณมีการแจ้งเตือนใหม่ ${messages.length} รายการ`,
html: `
<h3>สรุปรายการแจ้งเตือน (Digest)</h3>
<ul>${listItems}</ul>
<p>คุณได้รับอีเมลนี้เพราะเปิดใช้งานโหมดสรุปรายการ</p>
`,
});
this.logger.log(
`Digest Email sent to ${user.email} (${messages.length} items)`,
);
}
private async sendLineImmediate(user: any, data: NotificationPayload) {
const n8nWebhookUrl = this.configService.get('N8N_LINE_WEBHOOK_URL');
if (!n8nWebhookUrl) return;
try { try {
await axios.post(n8nWebhookUrl, { await axios.post(n8nWebhookUrl, {
userId: user.user_id, // หรือ user.lineId ถ้ามี userId: user.user_id,
message: `${data.title}\n${data.message}`, message: `${data.title}\n${data.message}`,
link: data.link, link: data.link,
isDigest: false,
}); });
this.logger.log(`Line notification sent via n8n for user ${data.userId}`); } catch (error) {
} catch (error: any) { // ✅ แก้ไขตรงนี้ด้วย: Type Casting (error as Error)
throw new Error(`Failed to send Line notification: ${error.message}`); this.logger.error(`Line Error: ${(error as Error).message}`);
}
}
private async sendLineDigest(user: any, messages: NotificationPayload[]) {
const n8nWebhookUrl = this.configService.get('N8N_LINE_WEBHOOK_URL');
if (!n8nWebhookUrl) return;
const summary = messages.map((m, i) => `${i + 1}. ${m.title}`).join('\n');
try {
await axios.post(n8nWebhookUrl, {
userId: user.user_id,
message: `สรุป ${messages.length} รายการใหม่:\n${summary}`,
link: 'https://lcbp3.np-dms.work/notifications',
isDigest: true,
});
} catch (error) {
// ✅ แก้ไขตรงนี้ด้วย: Type Casting (error as Error)
this.logger.error(`Line Digest Error: ${(error as Error).message}`);
} }
} }
} }

View File

@@ -1,4 +1,5 @@
// File: src/modules/notification/notification.service.ts // File: src/modules/notification/notification.service.ts
import { Injectable, Logger, NotFoundException } from '@nestjs/common'; import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq'; import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq'; import { Queue } from 'bullmq';
@@ -22,9 +23,9 @@ export interface NotificationJobData {
title: string; title: string;
message: string; message: string;
type: 'EMAIL' | 'LINE' | 'SYSTEM'; // ช่องทางหลักที่ต้องการส่ง (Trigger Type) type: 'EMAIL' | 'LINE' | 'SYSTEM'; // ช่องทางหลักที่ต้องการส่ง (Trigger Type)
entityType?: string; // e.g., 'rfa', 'correspondence' entityType?: string;
entityId?: number; // e.g., rfa_id entityId?: number;
link?: string; // Deep link to frontend page link?: string;
} }
@Injectable() @Injectable()
@@ -37,109 +38,57 @@ export class NotificationService {
private notificationRepo: Repository<Notification>, private notificationRepo: Repository<Notification>,
@InjectRepository(User) @InjectRepository(User)
private userRepo: Repository<User>, private userRepo: Repository<User>,
@InjectRepository(UserPreference) // ไม่ต้อง Inject UserPrefRepo แล้ว เพราะ Processor จะจัดการเอง
private userPrefRepo: Repository<UserPreference>,
private notificationGateway: NotificationGateway, private notificationGateway: NotificationGateway,
) {} ) {}
/** /**
* ส่งการแจ้งเตือน (Centralized Notification Sender) * ส่งการแจ้งเตือน (Centralized Notification Sender)
* 1. บันทึก DB (System Log)
* 2. ส่ง Real-time (WebSocket)
* 3. ส่ง External (Email/Line) ผ่าน Queue ตาม User Preference
*/ */
async send(data: NotificationJobData): Promise<void> { async send(data: NotificationJobData): Promise<void> {
try { try {
// --------------------------------------------------------- // ---------------------------------------------------------
// 1. สร้าง Entity และบันทึกลง DB (เพื่อให้มี History ในระบบ) // 1. สร้าง Entity และบันทึกลง DB (System Log)
// --------------------------------------------------------- // ---------------------------------------------------------
const notification = this.notificationRepo.create({ const notification = this.notificationRepo.create({
userId: data.userId, userId: data.userId,
title: data.title, title: data.title,
message: data.message, message: data.message,
notificationType: NotificationType.SYSTEM, // ใน DB เก็บเป็น SYSTEM เสมอเพื่อแสดงใน App notificationType: NotificationType.SYSTEM,
entityType: data.entityType, entityType: data.entityType,
entityId: data.entityId, entityId: data.entityId,
isRead: false, isRead: false,
// link: data.link // ถ้า Entity มี field link ให้ใส่ด้วย
}); });
const savedNotification = await this.notificationRepo.save(notification); const savedNotification = await this.notificationRepo.save(notification);
// --------------------------------------------------------- // ---------------------------------------------------------
// 2. Real-time Push (WebSocket) -> ส่งให้ User ทันทีถ้า Online // 2. Real-time Push (WebSocket)
// --------------------------------------------------------- // ---------------------------------------------------------
this.notificationGateway.sendToUser(data.userId, savedNotification); this.notificationGateway.sendToUser(data.userId, savedNotification);
// --------------------------------------------------------- // ---------------------------------------------------------
// 3. ตรวจสอบ User Preferences เพื่อส่งช่องทางอื่น (Email/Line) // 3. Push Job ลง Redis BullMQ (Dispatch Logic)
// เปลี่ยนชื่อ Job เป็น 'dispatch-notification' ตาม Processor
// --------------------------------------------------------- // ---------------------------------------------------------
const userPref = await this.userPrefRepo.findOne({ await this.notificationQueue.add(
where: { userId: data.userId }, 'dispatch-notification',
}); {
...data,
// ใช้ Nullish Coalescing Operator (??) notificationId: savedNotification.id, // ส่ง ID ไปด้วยเผื่อใช้ Tracking
// ถ้าไม่มีค่า (undefined/null) ให้ Default เป็น true },
const shouldSendEmail = userPref?.notifyEmail ?? true; {
const shouldSendLine = userPref?.notifyLine ?? true; attempts: 3,
backoff: {
const jobs = []; type: 'exponential',
delay: 5000,
// ---------------------------------------------------------
// 4. เตรียม Job สำหรับ Email Queue
// เงื่อนไข: User เปิดรับ Email และ Noti นี้ไม่ได้บังคับส่งแค่ LINE
// ---------------------------------------------------------
if (shouldSendEmail && data.type !== 'LINE') {
jobs.push({
name: 'send-email',
data: {
...data,
notificationId: savedNotification.id,
target: 'EMAIL',
}, },
opts: { removeOnComplete: true,
attempts: 3, // ลองใหม่ 3 ครั้งถ้าล่ม (Resilience) },
backoff: { );
type: 'exponential',
delay: 5000, // รอ 5s, 10s, 20s...
},
removeOnComplete: true, // ลบ Job เมื่อเสร็จ (ประหยัด Redis Memory)
},
});
}
// --------------------------------------------------------- this.logger.debug(`Dispatched notification job for user ${data.userId}`);
// 5. เตรียม Job สำหรับ Line Queue
// เงื่อนไข: User เปิดรับ Line และ Noti นี้ไม่ได้บังคับส่งแค่ EMAIL
// ---------------------------------------------------------
if (shouldSendLine && data.type !== 'EMAIL') {
jobs.push({
name: 'send-line',
data: {
...data,
notificationId: savedNotification.id,
target: 'LINE',
},
opts: {
attempts: 3,
backoff: { type: 'fixed', delay: 3000 },
removeOnComplete: true,
},
});
}
// ---------------------------------------------------------
// 6. Push Jobs ลง Redis BullMQ
// ---------------------------------------------------------
if (jobs.length > 0) {
await this.notificationQueue.addBulk(jobs);
this.logger.debug(
`Queued ${jobs.length} external notifications for user ${data.userId}`,
);
}
} catch (error) { } catch (error) {
// Error Handling: ไม่ Throw เพื่อไม่ให้ Flow หลัก (เช่น การสร้างเอกสาร) พัง
// แต่บันทึก Error ไว้ตรวจสอบ
this.logger.error( this.logger.error(
`Failed to process notification for user ${data.userId}`, `Failed to process notification for user ${data.userId}`,
(error as Error).stack, (error as Error).stack,
@@ -147,9 +96,8 @@ export class NotificationService {
} }
} }
/** // ... (ส่วน findAll, markAsRead, cleanupOldNotifications เหมือนเดิม ไม่ต้องแก้) ...
* ดึงรายการแจ้งเตือนของ User (สำหรับ Controller)
*/
async findAll(userId: number, searchDto: SearchNotificationDto) { async findAll(userId: number, searchDto: SearchNotificationDto) {
const { page = 1, limit = 20, isRead } = searchDto; const { page = 1, limit = 20, isRead } = searchDto;
const skip = (page - 1) * limit; const skip = (page - 1) * limit;
@@ -161,14 +109,11 @@ export class NotificationService {
.take(limit) .take(limit)
.skip(skip); .skip(skip);
// Filter by Read Status (ถ้ามีการส่งมา)
if (isRead !== undefined) { if (isRead !== undefined) {
queryBuilder.andWhere('notification.isRead = :isRead', { isRead }); queryBuilder.andWhere('notification.isRead = :isRead', { isRead });
} }
const [items, total] = await queryBuilder.getManyAndCount(); const [items, total] = await queryBuilder.getManyAndCount();
// นับจำนวนที่ยังไม่ได้อ่านทั้งหมด (เพื่อแสดง Badge ที่กระดิ่ง)
const unreadCount = await this.notificationRepo.count({ const unreadCount = await this.notificationRepo.count({
where: { userId, isRead: false }, where: { userId, isRead: false },
}); });
@@ -185,9 +130,6 @@ export class NotificationService {
}; };
} }
/**
* อ่านแจ้งเตือน (Mark as Read)
*/
async markAsRead(id: number, userId: number): Promise<void> { async markAsRead(id: number, userId: number): Promise<void> {
const notification = await this.notificationRepo.findOne({ const notification = await this.notificationRepo.findOne({
where: { id, userId }, where: { id, userId },
@@ -200,15 +142,9 @@ export class NotificationService {
if (!notification.isRead) { if (!notification.isRead) {
notification.isRead = true; notification.isRead = true;
await this.notificationRepo.save(notification); await this.notificationRepo.save(notification);
// Update Unread Count via WebSocket (Optional)
// this.notificationGateway.sendUnreadCount(userId, ...);
} }
} }
/**
* อ่านทั้งหมด (Mark All as Read)
*/
async markAllAsRead(userId: number): Promise<void> { async markAllAsRead(userId: number): Promise<void> {
await this.notificationRepo.update( await this.notificationRepo.update(
{ userId, isRead: false }, { userId, isRead: false },
@@ -216,10 +152,6 @@ export class NotificationService {
); );
} }
/**
* ลบการแจ้งเตือนที่เก่าเกินกำหนด (ใช้กับ Cron Job Cleanup)
* เก็บไว้ 90 วัน
*/
async cleanupOldNotifications(days: number = 90): Promise<number> { async cleanupOldNotifications(days: number = 90): Promise<number> {
const dateLimit = new Date(); const dateLimit = new Date();
dateLimit.setDate(dateLimit.getDate() - days); dateLimit.setDate(dateLimit.getDate() - days);

View File

@@ -64,6 +64,7 @@ export class UserService {
async findOne(id: number): Promise<User> { async findOne(id: number): Promise<User> {
const user = await this.usersRepository.findOne({ const user = await this.usersRepository.findOne({
where: { user_id: id }, where: { user_id: id },
relations: ['preferences', 'roles'], // [IMPORTANT] ต้องโหลด preferences มาด้วย
}); });
if (!user) { if (!user) {

View File

@@ -116,35 +116,23 @@ npm install --save-dev @nestjs/testing jest @types/jest @types/passport-jwt @typ
``` ```
## Prompt: การพัฒนา Core Auth Module (AuthModule) สำหรับ DMS v1.2.0 ขออภัยอย่างยิ่งในความผิดพลาดที่เกิดขึ้นครับ เข้าใจครับว่าทำให้เสียเวลามาก
ช่วยตั้งค่า tsconfig.json, nest-cli.json และไฟล์ config อื่นๆ สำหรับการ **"ตั้งค่า"** หรือ **"กำหนดค่า"** ให้ผมตรวจสอบข้อมูลก่อนนั้น ในระบบ AI แบบนี้ **ไม่มีเมนู Settings หรือปุ่มกดให้คุณเข้าไปตั้งค่าได้โดยตรงครับ**
* 1.1 สร้าง User Entity การควบคุมพฤติกรรมของผมต้องทำผ่าน **"คำสั่ง (Prompt)"** ที่คุณพิมพ์เข้ามาเท่านั้นครับ เพื่อป้องกันไม่ให้เกิดเหตุการณ์แบบนี้อีก คุณสามารถใช้เทคนิคการสั่งงานดังนี้เพื่อ **"บังคับ"** ให้ผมตรวจสอบข้อมูลครับ:
* 1.2 สร้าง Role Entity 1. **สั่งให้ "อ่านและสรุปก่อน" (Verify First):**
* ก่อนให้ผมเขียนโค้ด ให้สั่งว่า *"ช่วยอ่านไฟล์ `01_lcbp3_v1_4_3.sql` แล้วสรุปโครงสร้างตาราง audit_logs ที่มีอยู่จริงให้ดูก่อน"*
* วิธีนี้จะบังคับให้ผมต้องไปดึงข้อมูลจากไฟล์มาประมวลผลก่อนที่จะเริ่ม "จินตนาการ" หรือเขียนโค้ดใหม่ครับ
* 1.3 สร้าง Permission Entity 2. **ใช้คำสั่ง "ห้ามเดา" (Strict Constraints):**
* เติมประโยคท้ายคำสั่งว่า *"ห้ามใช้โครงสร้างสมมติ ให้ยึดตามไฟล์แนบ `ชื่อไฟล์` เท่านั้น หากไม่เจอข้อมูลให้ถามกลับ"*
ถัดไป ช่วยสร้าง TypeORM configuration และ DatabaseModule ให้หน่อย 3. **อ้างอิงชื่อไฟล์ชัดเจน:**
ช่วยสร้าง AuthService, JwtStrategy และ AuthController พื้นฐานให้หน่อย * ระบุชื่อไฟล์ในคำสั่งเสมอ เช่น *"อ้างอิงจาก `Backend Plan`..."* หรือ *"ตาม `Data Dictionary`..."* (ซึ่งคุณทำได้ดีแล้วครับ แต่ผมประมวลผลพลาดเองในรอบนี้)
ช่วยสร้าง UserModule, UserService, LocalAuthGuard และ LocalStrategy สำหรับจัดการข้อมูลผู้ใช้หน่อย
ช่วยสร้าง Guard สำหรับตรวจสอบสิทธิ์ตามบทบาท (RBAC) โดยใช้ CASL หน่อย
ช่วยสร้าง UserModule, UserService, และ UserController
ช่วยตั้งค่าการเชื่อมต่อ MariaDB ผ่าน TypeORM **สรุป:**
คุณไม่ต้องไปตั้งค่าที่ไหนครับ แต่ใช้การ **"สั่งให้ผมทวนข้อมูลจากไฟล์"** เป็นขั้นตอนแรกก่อนเริ่มงาน จะช่วยลดโอกาสผิดพลาดได้มากที่สุดครับ
พัฒนา Core Auth Module (`AuthModule`) ตอนนี้ผมพร้อมทำงานต่อโดยยึดข้อมูลจากไฟล์ `01_lcbp3_v1_4_3.sql` และไฟล์อื่นๆ ที่คุณให้มาอย่างเคร่งครัดครับ มีส่วนไหนให้ผมดำเนินการต่อไหมครับ?
- ช่วยสร้าง API Endpoints: `/auth/login`, `/auth/me`
พัฒนา Common Module (`@app/common`)
- ช่วยสร้าง FileStorageService สำหรับจัดการไฟล์ (อัปโหลด/ดาวน์โหลด) backend/common/file-storage
- ช่วยสร้าง AuditLogInterceptor สำหรับบันทึกการกระทำโดยอัตโนมัติ
- ช่วยสร้าง Global Exception Filter
- ช่วยสร้าง DTOs และ Interfaces พื้นฐาน
---
ขอบคุณสำหรับข้อมูลที่ละเอียดครับ นี่คือการวิเคราะห์และข้อเสนอแนะเพื่อให้โครงสร้างสิทธิ์การใช้งาน (Access Control / RBAC) ชัดเจน สมบูรณ์ และสามารถนำไปพัฒนาได้จริงครับ

48
temp.md
View File

@@ -1,40 +1,10 @@
## บทบาท: คุณคือ Programmer ที่เชี่ยวชาญ การจัดการฐานข้อมูล (Database Management), การวิเคราะห์ฐานข้อมูล (Database Analysis), การจัดการฐานข้อมูลเชิงสัมพันธ์ (Relational Databases), ภาษา SQL, RBAC, ABAC, การเขียนโค๊ด NodeJS NestJS NextJS, การ debug โค้ด และ แก้ไข error ภายในโค้ด รายการ,สถานะ,การดำเนินการ
Core Modules,✅,พร้อม
Workflow Engine (DSL),✅,Code เสร็จแล้ว (รอ Seed Data)
Master Module,✅,Code เสร็จแล้ว
Workflow Seed Data,🔴,ต้องทำทันที (ใช้ Code ด้านบน)
Notification Digest,🟡,ตรวจสอบ Logic ภายใน Processor
Maintenance API,🟡,ตรวจสอบว่ามี Endpoint ให้ Admin กดไหม
DB Partitioning,🟡,เตรียม SQL Script ไว้รันก่อน Load Test
## Basic data: ตรวจสอบ Maintenance API
1. Application Requirements file: 0_Requirements_V1_4_3.md
2. Full Stack JS file: 1_FullStackJS_V1_4_3.md
3. Backend Development Plan: 2_Backend_Plan_V1_4_3.md
4. Frontend Development Plan: 3_Frontend_Plan_V1_4_3.md
5. Data Dictionary file: 4_Data_Dictionary_V1_4_3.md, 01_lcbp3_v1_4_3.sql
6. Backend Development Plan Phase 6A: 2_Backend_Plan_Phase6A_V1_4_3.md
7. Backend File & Folder: 5_Backend_Folder_V1_4_3.md
## rules:
- ใช้ภาษาไทยใน comments
- เขียนโค้ดให้อ่านง่าย, ใส่ path/filename ในบรรทัดแรก โค้ด
- การอัพเดทโค้ด ให้แก้ไขจากต้นฉบับเป็น โค้ดที่สมบูรณ์
- เขียน documentation สำหรับ function สำคัญ
## เป้าหมายและจุดประสงค์:
* ให้ความช่วยเหลือผู้ใช้ในงานที่เกี่ยวข้องกับการพัฒนาซอฟต์แวร์ โดยเฉพาะอย่างยิ่งในส่วนของ JavaScript (NodeJS, NestJS, NextJS) และฐานข้อมูล (SQL, Relational Databases)
* ให้คำปรึกษาเกี่ยวกับการจัดการข้อมูล, การออกแบบฐานข้อมูลเชิงสัมพันธ์, และการใช้โมเดลการควบคุมการเข้าถึง (RBAC, ABAC)
* ช่วยเหลือในการวิเคราะห์และแก้ไขข้อผิดพลาด (debug และ error) ในโค้ดตามที่ผู้ใช้ระบุ
* ใช้ข้อมูลพื้นฐานที่ให้มา (Basic data) เพื่อให้คำแนะนำและโค้ดที่สอดคล้องกับเอกสารโครงการ (เช่น Requirements, Plans, Data Dictionary)
## พฤติกรรมและกฎเพิ่มเติม:
1) การเริ่มต้นและการโต้ตอบ:
a) ทักทายผู้ใช้ด้วยภาษาไทยอย่างเป็นมิตร และสอบถามเกี่ยวกับปัญหาหรือความช่วยเหลือที่ต้องการในด้านการเขียนโปรแกรมหรือฐานข้อมูล
b) ตอบคำถามทางเทคนิคอย่างแม่นยำและเป็นมืออาชีพ โดยใช้ศัพท์เฉพาะทางที่ถูกต้อง
c) จำกัดจำนวนประโยคในการตอบกลับแต่ละครั้งให้กระชับและตรงประเด็นเพื่อความรวดเร็วในการสื่อสาร
2) การจัดการโค้ดและข้อมูล:
a) เมื่อผู้ใช้ขอให้อัพเดทโค้ด ให้ทำการแสดงโค้ดฉบับเต็มที่สมบูรณ์และได้รับการแก้ไขแล้ว (ไม่ใช่แค่ส่วนที่แก้ไข)
b) ต้องแน่ใจว่าโค้ดที่สร้างขึ้นมานั้นอ่านง่ายและมี comments เป็นภาษาไทยตามที่ระบุใน rules
c) สำหรับฟังก์ชันที่มีความซับซ้อนหรือมีความสำคัญต่อระบบ ต้องเขียน documentation อธิบายวัตถุประสงค์, พารามิเตอร์, และผลลัพธ์ของฟังก์ชันนั้นๆ ด้วยภาษาไทย
d) หากต้องอ้างอิงถึงโครงสร้างข้อมูลหรือข้อกำหนดใดๆ ให้ตรวจสอบจากไฟล์ Basic data ที่ผู้ใช้ให้มาก่อนเสมอ ถ้าไม่พบ ให้แจ้งผู้ใช้ทราบ
e) ถ้ามีการอ้างอิงถึงโค้ดที่อยู่ใน Phase หรือ Task ก่อนหน้า ให้สอบถามผู้ใช้เพื่อให้ upload ไฟล์โค้ดที่อ้างอิง (ไม่เดาหรือสร้างใหม่ เพิ่อประหยัดเวลา)
1) โทนโดยรวม:
* ใช้ภาษาไทยในการสื่อสารเป็นหลัก ยกเว้นศัพท์เทคนิค
* มีความมั่นใจและแสดงออกถึงความเชี่ยวชาญในฐานะโปรแกรมเมอร์ผู้เชี่ยวชาญ
* มีความเป็นระเบียบและให้คำแนะนำที่เป็นขั้นตอน