260222:1053 20260222 refactor specs/ #1 03-Data-and-Storage
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
All checks were successful
Build and Deploy / deploy (push) Successful in 1m0s
This commit is contained in:
505
specs/02-architecture/01-02-architecture.md
Normal file
505
specs/02-architecture/01-02-architecture.md
Normal file
@@ -0,0 +1,505 @@
|
||||
# 🛠️ Section 2: System Architecture (สถาปัตยกรรมและเทคโนโลยี)
|
||||
|
||||
---
|
||||
|
||||
title: 'System Architecture'
|
||||
version: 1.8.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2026-01-26
|
||||
related: -
|
||||
specs/01-objectives.md
|
||||
|
||||
---
|
||||
|
||||
ชื่อกำหนด สถาปัตยกรรมแบบ Headless/API-First ที่ทันสมัย ทำงานทั้งหมดบน QNAP Server ผ่าน Container Station เพื่อความสะดวกในการจัดการและบำรุงรักษา
|
||||
|
||||
## **2.1 Infrastructure & Environment:**
|
||||
|
||||
- Domain: `np-dms.work`, `www.np-dms.work`
|
||||
- IP: 159.192.126.103
|
||||
- Server: QNAP TS-473A, RAM: 32GB, CPU: AMD Ryzen V1500B, HDD: 4TBx4nos. RAID 5, SSD: 1TB ใช้เป็น caching, มี port 2.5Gbps 2 port
|
||||
- Server: AS5304T, RAM: 16GB, CPU: Intel Celeron CPU @ 2.00GH, HDD: 6TBx3nos. RAID 5, SSD: 1TB ใช้เป็น caching, มี port 2.5Gbps 2 port
|
||||
- Rotuter: TP-LINK ER7206, WAN/LAN port 1 SFP, WAN port 2, WAN/LAN 10/100/1000 port 3-6
|
||||
- Core Switch: TP-LINK TL-SG2428P, LAN port 1-24 10/100/1000, SFP port 25-28 1Gbps
|
||||
- Server Switch: AMPCOM, LAN port 1-8 10/100/1000/2500, SFP+ port 9 10Gbps
|
||||
- Admin Switch: TP-LINK ES205G, LAN port 1-5 10/100/1000
|
||||
- CCTV Switch: TP-LINK TL-SL1226P port 1-24 PoE+ 100Mbps, SFP port 24-25 1Gbps
|
||||
- IP Phone Switch: TP-LINK TL-SG1210P port 1-8 PoE+ 100Mbps , Uplink1 10/100/1000, Uplink2 SFP 1Gbps
|
||||
- Controller: TP-LINK OC200
|
||||
- Wireless Access point: TP-LINK EAP610 16 ตัว
|
||||
- CCTV: HikVision (DS-7732NXI-K4) + กล้อง 6 ตัว
|
||||
- IP Phone: YeaLink 8 ตัว
|
||||
- Admin Desktop: Windows 11, LAN port 10/100/1000/2500
|
||||
- Printer: Kyocera CS 3554ci, LAN port 10/100/1000
|
||||
- Containerization: Container Station (Docker & Docker Compose) ใช้ UI ของ Container Station เป็นหลัก ในการ configuration และการรัน docker command
|
||||
- Development Environment: VS Code/Cursor on Windows 11
|
||||
- Data Storage: /share/dms-data บน QNAP
|
||||
- ข้อจำกัด: ไม่สามารถใช้ .env ในการกำหนดตัวแปรภายนอกได้ ต้องกำหนดใน docker-compose.yml เท่านั้น
|
||||
|
||||
## **2.2 Netwrok Configuration**
|
||||
|
||||
**VLAN Networks**
|
||||
| VLAN ID | Name | Purpose | Gateway/Subnet | DHCP | IP Range | DNS | Lease Time | ARP Detection | IGMP Snooping | MLD Snooping | Notes |
|
||||
| ------- | ------ | --------- | --------------- | ---- | ------------------ | ------- | ---------- | ------------- | ------------- | ------------ | --------------- |
|
||||
| 10 | SERVER | Interface | 192.168.10.1/24 | No | - | Custom | - | - | - | - | Static servers |
|
||||
| 20 | MGMT | Interface | 192.168.20.1/24 | No | - | Custom | - | Enable | Enable | - | Management only |
|
||||
| 30 | USER | Interface | 192.168.30.1/24 | Yes | 192.168.30.10-254 | Auto | 7 Days | - | Enable | - | User devices |
|
||||
| 40 | CCTV | Interface | 192.168.40.1/24 | Yes | 192.168.40.100-150 | Auto | 7 Days | - | Enable | - | CCTV & NVR |
|
||||
| 50 | VOICE | Interface | 192.168.50.1/24 | Yes | 192.168.50.201-250 | Auto | 7 Days | - | - | - | IP Phones |
|
||||
| 60 | DMZ | Interface | 192.168.60.1/24 | No | - | 1.1.1.1 | - | - | - | - | Public services |
|
||||
| 70 | GUEST | Interface | 192.168.70.1/24 | Yes | 192.168.70.200-250 | Auto | 1 Day | - | - | - | Guest |
|
||||
|
||||
|
||||
**Switch Profiles**
|
||||
| Profile Name | Native Network | Tagged Networks | Untagged Networks | Voice Network | Loopback Control | Usage |
|
||||
| ---------------- | -------------- | --------------------- | ----------------- | ------------- | ---------------- | ----------------------- |
|
||||
| 01_CORE_TRUNK | MGMT (20) | 10,30,40,50,60,70 | MGMT (20) | - | Spanning Tree | Router & switch uplinks |
|
||||
| 02_MGMT_ONLY | MGMT (20) | MGMT (20) | - | - | Spanning Tree | Management only |
|
||||
| 03_SERVER_ACCESS | SERVER (10) | MGMT (20) | SERVER (10) | - | Spanning Tree | QNAP / ASUSTOR |
|
||||
| 04_CCTV_ACCESS | CCTV (40) | - | CCTV (40) | - | Spanning Tree | CCTV cameras |
|
||||
| 05_USER_ACCESS | USER (30) | - | USER (30) | - | Spanning Tree | PC / Printer |
|
||||
| 06_AP_TRUNK | MGMT (20) | USER (30), GUEST (70) | MGMT (20) | - | Spanning Tree | EAP610 Access Points |
|
||||
| 07_VOICE_ACCESS | USER (30) | VOICE (50) | USER (30) | VOICE (50) | Spanning Tree | IP Phones |
|
||||
|
||||
|
||||
**ER7206 Port Mapping**
|
||||
| Port | Connected Device | Port | Description |
|
||||
| ---- | ---------------- | ------------- | ----------- |
|
||||
| 1 | - | - | - |
|
||||
| 2 | WAN | - | Internet |
|
||||
| 3 | SG2428P | PVID MGMT(20) | Core Switch |
|
||||
| 4 | - | - | - |
|
||||
| 5 | - | - | - |
|
||||
| 6 | - | - | - |
|
||||
|
||||
**AMPCOM Port Aggregate Setting**
|
||||
| Aggregate Group ID | Type | Member port | Aggregated Port |
|
||||
| ------------------ | ---- | ----------- | --------------- |
|
||||
| Trunk1 | LACP | 3,4 | 3,4 |
|
||||
| Trunk2 | LACP | 5,6 | 5,6 |
|
||||
|
||||
|
||||
**AMPCOM Port VLAN Mapping**
|
||||
| Port | Connected Device | Port vlan type | Access VLAN | Native VLAN | Trunk vlan |
|
||||
| ------ | ---------------- | -------------- | ----------- | ----------- | -------------------- |
|
||||
| 1 | SG2428P | Trunk | - | 20 | 10,20,30,40,50,60,70 |
|
||||
| 2 | - | Trunk | - | 20 | 10,20,30,40,50,60,70 |
|
||||
| 7 | - | Access | 20 | - | - |
|
||||
| 8 | Admin Desktop | Access | 20 | - | - |
|
||||
| Trunk1 | QNAP | Trunk | - | 10 | 10,20,30,40,50,60,70 |
|
||||
| Trunk2 | ASUSTOR | Trunk | - | 10 | 10,20,30,40,50,60,70 |
|
||||
|
||||
|
||||
**NAS NIC Bonding Configuration**
|
||||
| Device | Bonding Mode | Member Ports | VLAN Mode | Tagged VLAN | IP Address | Gateway | Notes |
|
||||
| ------- | ------------------- | ------------ | --------- | ----------- | --------------- | ------------ | ---------------------- |
|
||||
| QNAP | IEEE 802.3ad (LACP) | Adapter 1, 2 | Untagged | 10 (SERVER) | 192.168.10.8/24 | 192.168.10.1 | Primary NAS for DMS |
|
||||
| ASUSTOR | IEEE 802.3ad (LACP) | Port 1, 2 | Untagged | 10 (SERVER) | 192.168.10.9/24 | 192.168.10.1 | Backup / Secondary NAS |
|
||||
|
||||
> **หมายเหตุ**: NAS ทั้งสองตัวใช้ LACP bonding เพื่อเพิ่ม bandwidth และ redundancy โดยต้อง config ให้ตรงกับ AMPCOM Switch (Trunk1)
|
||||
|
||||
|
||||
**SG2428P Port Mapping**
|
||||
| Port | Connected Device | Switch Profile | Description |
|
||||
| ---- | ------------------------- | -------------------- | ------------- |
|
||||
| 1 | ER7206 | 01_CORE_TRUNK | Internet |
|
||||
| 2 | OC200 | 01_CORE_TRUNK | Controller |
|
||||
| 3 | Ampcom 2.5G Switch Port 1 | LAG1 (01_CORE_TRUNK) | Uplink |
|
||||
| 4 | - | LAG1 (01_CORE_TRUNK) | Reserved |
|
||||
| 5 | EAP610-01 | 06_AP_TRUNK | Access Point |
|
||||
| 6 | EAP610-02 | 06_AP_TRUNK | Access Point |
|
||||
| 7 | EAP610-03 | 06_AP_TRUNK | Access Point |
|
||||
| 8 | EAP610-04 | 06_AP_TRUNK | Access Point |
|
||||
| 9 | EAP610-05 | 06_AP_TRUNK | Access Point |
|
||||
| 10 | EAP610-06 | 06_AP_TRUNK | Access Point |
|
||||
| 11 | EAP610-07 | 06_AP_TRUNK | Access Point |
|
||||
| 12 | EAP610-08 | 06_AP_TRUNK | Access Point |
|
||||
| 13 | EAP610-09 | 06_AP_TRUNK | Access Point |
|
||||
| 14 | EAP610-10 | 06_AP_TRUNK | Access Point |
|
||||
| 15 | EAP610-11 | 06_AP_TRUNK | Access Point |
|
||||
| 16 | EAP610-12 | 06_AP_TRUNK | Access Point |
|
||||
| 17 | EAP610-13 | 06_AP_TRUNK | Access Point |
|
||||
| 18 | EAP610-14 | 06_AP_TRUNK | Access Point |
|
||||
| 19 | EAP610-15 | 06_AP_TRUNK | Access Point |
|
||||
| 20 | EAP610-16 | 06_AP_TRUNK | Access Point |
|
||||
| 21 | Reserved | 01_CORE_TRUNK | |
|
||||
| 22 | Reserved | 01_CORE_TRUNK | |
|
||||
| 23 | Printer | 05_USER_ACCESS | Printer |
|
||||
| 24 | ES205G | 01_CORE_TRUNK | Management PC |
|
||||
| 25 | TL-SL1226P | 01_CORE_TRUNK | Uplink |
|
||||
| 26 | SG1210P | 01_CORE_TRUNK | Uplink |
|
||||
| 27 | Reserved | 01_CORE_TRUNK | |
|
||||
| 28 | Reserved | 01_CORE_TRUNK | |
|
||||
|
||||
|
||||
**ES205G Port Mapping (Admin Switch)**
|
||||
| Port | Connected Device | VLAN | Description |
|
||||
| ---- | ---------------- | ----------- | ----------- |
|
||||
| 1 | SG2428P Port 24 | Trunk (All) | Uplink |
|
||||
| 2 | Admin Desktop | MGMT (20) | Admin PC |
|
||||
| 3 | Reserved | MGMT (20) | |
|
||||
| 4 | Reserved | MGMT (20) | |
|
||||
| 5 | Reserved | MGMT (20) | |
|
||||
|
||||
> **หมายเหตุ**: ES205G เป็น Unmanaged Switch ไม่รองรับ VLAN tagging ดังนั้นทุก port จะอยู่ใน Native VLAN (20) ของ uplink
|
||||
|
||||
|
||||
**TL-SL1226P Port Mapping (CCTV Switch)**
|
||||
| Port | Connected Device | PoE | VLAN | Description |
|
||||
| ---- | ---------------- | ---- | --------- | ----------- |
|
||||
| 1 | Camera-01 | PoE+ | CCTV (40) | CCTV Camera |
|
||||
| 2 | Camera-02 | PoE+ | CCTV (40) | CCTV Camera |
|
||||
| 3 | Camera-03 | PoE+ | CCTV (40) | CCTV Camera |
|
||||
| 4 | Camera-04 | PoE+ | CCTV (40) | CCTV Camera |
|
||||
| 5 | Camera-05 | PoE+ | CCTV (40) | CCTV Camera |
|
||||
| 6 | Camera-06 | PoE+ | CCTV (40) | CCTV Camera |
|
||||
| 7-23 | Reserved | PoE+ | CCTV (40) | |
|
||||
| 24 | HikVision NVR | - | CCTV (40) | NVR |
|
||||
| 25 | SG2428P Port 25 | - | Trunk | SFP Uplink |
|
||||
| 26 | Reserved | - | Trunk | SFP |
|
||||
|
||||
|
||||
**SG1210P Port Mapping (IP Phone Switch)**
|
||||
| Port | Connected Device | PoE | Data VLAN | Voice VLAN | Description |
|
||||
| ------- | ---------------- | ---- | --------- | ---------- | ----------- |
|
||||
| 1 | IP Phone-01 | PoE+ | USER (30) | VOICE (50) | IP Phone |
|
||||
| 2 | IP Phone-02 | PoE+ | USER (30) | VOICE (50) | IP Phone |
|
||||
| 3 | IP Phone-03 | PoE+ | USER (30) | VOICE (50) | IP Phone |
|
||||
| 4 | IP Phone-04 | PoE+ | USER (30) | VOICE (50) | IP Phone |
|
||||
| 5 | IP Phone-05 | PoE+ | USER (30) | VOICE (50) | IP Phone |
|
||||
| 6 | IP Phone-06 | PoE+ | USER (30) | VOICE (50) | IP Phone |
|
||||
| 7 | IP Phone-07 | PoE+ | USER (30) | VOICE (50) | IP Phone |
|
||||
| 8 | IP Phone-08 | PoE+ | USER (30) | VOICE (50) | IP Phone |
|
||||
| Uplink1 | Reserved | - | Trunk | - | RJ45 Uplink |
|
||||
| Uplink2 | SG2428P Port 26 | - | Trunk | - | SFP Uplink |
|
||||
|
||||
> **หมายเหตุ**: SG1210P รองรับ Voice VLAN ทำให้ IP Phone ใช้ VLAN 50 สำหรับ voice traffic และ passthrough VLAN 30 สำหรับ PC ที่ต่อผ่าน phone
|
||||
|
||||
|
||||
**Static IP Allocation**
|
||||
| VLAN | Device | IP Address | MAC Address | Notes |
|
||||
| ---------- | --------------- | ------------------ | ----------- | ---------------- |
|
||||
| SERVER(10) | QNAP | 192.168.10.8 | - | Primary NAS |
|
||||
| SERVER(10) | ASUSTOR | 192.168.10.9 | - | Backup NAS |
|
||||
| SERVER(10) | Docker Host | 192.168.10.10 | - | Containers |
|
||||
| MGMT(20) | ER7206 | 192.168.20.1 | - | Gateway/Router |
|
||||
| MGMT(20) | SG2428P | 192.168.20.2 | - | Core Switch |
|
||||
| MGMT(20) | AMPCOM | 192.168.20.3 | - | Server Switch |
|
||||
| MGMT(20) | TL-SL1226P | 192.168.20.4 | - | CCTV Switch |
|
||||
| MGMT(20) | SG1210P | 192.168.20.5 | - | Phone Switch |
|
||||
| MGMT(20) | OC200 | 192.168.20.250 | - | Omada Controller |
|
||||
| MGMT(20) | Admin Desktop | 192.168.20.100 | - | Admin PC |
|
||||
| USER(30) | Printer | 192.168.30.222 | - | Kyocera CS3554ci |
|
||||
| CCTV(40) | NVR | 192.168.40.100 | - | HikVision NVR |
|
||||
| CCTV(40) | Camera-01 to 06 | 192.168.40.101-106 | - | CCTV Cameras |
|
||||
| USER(30) | Admin Desktop | 192.168.30.100 | - | Admin PC (USER) |
|
||||
|
||||
**2.8 DHCP Reservation (MAC Mapping)**
|
||||
|
||||
**CCTV MAC Address Mapping (VLAN 40)**
|
||||
| Device Name | IP Address | MAC Address | Port (Switch) | Notes |
|
||||
| ------------- | -------------- | ----------- | ------------- | ---------- |
|
||||
| HikVision NVR | 192.168.40.100 | | Port 24 | Master NVR |
|
||||
| Camera-01 | 192.168.40.101 | | Port 1 | |
|
||||
| Camera-02 | 192.168.40.102 | | Port 2 | |
|
||||
| Camera-03 | 192.168.40.103 | | Port 3 | |
|
||||
| Camera-04 | 192.168.40.104 | | Port 4 | |
|
||||
| Camera-05 | 192.168.40.105 | | Port 5 | |
|
||||
| Camera-06 | 192.168.40.106 | | Port 6 | |
|
||||
|
||||
**IP Phone MAC Address Mapping (VLAN 50)**
|
||||
| Device Name | IP Address | MAC Address | Port (Switch) | Notes |
|
||||
| ----------- | -------------- | ----------- | ------------- | ------- |
|
||||
| IP Phone-01 | 192.168.50.201 | | Port 1 | Yealink |
|
||||
| IP Phone-02 | 192.168.50.202 | | Port 2 | Yealink |
|
||||
| IP Phone-03 | 192.168.50.203 | | Port 3 | Yealink |
|
||||
| IP Phone-04 | 192.168.50.204 | | Port 4 | Yealink |
|
||||
| IP Phone-05 | 192.168.50.205 | | Port 5 | Yealink |
|
||||
| IP Phone-06 | 192.168.50.206 | | Port 6 | Yealink |
|
||||
| IP Phone-07 | 192.168.50.207 | | Port 7 | Yealink |
|
||||
| IP Phone-08 | 192.168.50.208 | | Port 8 | Yealink |
|
||||
|
||||
|
||||
**Wireless SSID Mapping (OC200 Controller)**
|
||||
| SSID Name | Band | VLAN | Security | Portal Auth | Notes |
|
||||
| --------- | ------- | ---------- | --------- | ----------- | ----------------------- |
|
||||
| PSLCBP3 | 2.4G/5G | USER (30) | WPA2/WPA3 | No | Staff WiFi |
|
||||
| GUEST | 2.4G/5G | GUEST (70) | WPA2 | Yes | Guest WiFi with Captive |
|
||||
|
||||
> **หมายเหตุ**: ทุก SSID broadcast ผ่าน EAP610 ทั้ง 16 ตัว โดยใช้ 06_AP_TRUNK profile ที่ tag VLAN 30 และ 70
|
||||
|
||||
|
||||
**Gateway ACL (ER7206 Firewall Rules)**
|
||||
|
||||
*Inter-VLAN Routing Policy*
|
||||
| # | Name | Source | Destination | Service | Action | Log | Notes |
|
||||
| --- | ----------------- | --------------- | ---------------- | -------------- | ------ | --- | --------------------------- |
|
||||
| 1 | MGMT-to-ALL | VLAN20 (MGMT) | Any | Any | Allow | No | Admin full access |
|
||||
| 2 | SERVER-to-ALL | VLAN10 (SERVER) | Any | Any | Allow | No | Servers outbound access |
|
||||
| 3 | USER-to-SERVER | VLAN30 (USER) | VLAN10 (SERVER) | HTTP/HTTPS/SSH | Allow | No | Users access web apps |
|
||||
| 4 | USER-to-DMZ | VLAN30 (USER) | VLAN60 (DMZ) | HTTP/HTTPS | Allow | No | Users access DMZ services |
|
||||
| 5 | USER-to-MGMT | VLAN30 (USER) | VLAN20 (MGMT) | Any | Deny | Yes | Block users from management |
|
||||
| 6 | USER-to-CCTV | VLAN30 (USER) | VLAN40 (CCTV) | Any | Deny | Yes | Isolate CCTV |
|
||||
| 7 | USER-to-VOICE | VLAN30 (USER) | VLAN50 (VOICE) | Any | Deny | No | Isolate Voice |
|
||||
| 8 | USER-to-GUEST | VLAN30 (USER) | VLAN70 (GUEST) | Any | Deny | No | Isolate Guest |
|
||||
| 9 | CCTV-to-INTERNET | VLAN40 (CCTV) | WAN | HTTPS (443) | Allow | No | NVR cloud backup (optional) |
|
||||
| 10 | CCTV-to-ALL | VLAN40 (CCTV) | Any (except WAN) | Any | Deny | Yes | CCTV isolated |
|
||||
| 11 | VOICE-to-SIP | VLAN50 (VOICE) | SIP Server IP | SIP/RTP | Allow | No | Voice to SIP trunk |
|
||||
| 12 | VOICE-to-ALL | VLAN50 (VOICE) | Any | Any | Deny | No | Voice isolated |
|
||||
| 13 | DMZ-to-ALL | VLAN60 (DMZ) | Any (internal) | Any | Deny | Yes | DMZ cannot reach internal |
|
||||
| 14 | GUEST-to-INTERNET | VLAN70 (GUEST) | WAN | HTTP/HTTPS/DNS | Allow | No | Guest internet only |
|
||||
| 15 | GUEST-to-ALL | VLAN70 (GUEST) | Any (internal) | Any | Deny | Yes | Guest isolated |
|
||||
| 99 | DEFAULT-DENY | Any | Any | Any | Deny | Yes | Catch-all deny |
|
||||
|
||||
*WAN Inbound Rules (Port Forwarding)*
|
||||
| # | Name | WAN Port | Internal IP | Internal Port | Protocol | Notes |
|
||||
| --- | --------- | -------- | ------------ | ------------- | -------- | ------------------- |
|
||||
| 1 | HTTPS-NPM | 443 | 192.168.10.8 | 443 | TCP | Nginx Proxy Manager |
|
||||
| 2 | HTTP-NPM | 80 | 192.168.10.8 | 80 | TCP | HTTP redirect |
|
||||
|
||||
> **หมายเหตุ**: ER7206 ใช้หลักการ Default Deny - Rules ประมวลผลจากบนลงล่าง
|
||||
|
||||
|
||||
**Switch ACL (SG2428P Layer 2 Rules)**
|
||||
|
||||
*Port-Based Access Control*
|
||||
| # | Name | Source Port | Source MAC/VLAN | Destination | Action | Notes |
|
||||
| --- | --------------- | --------------- | --------------- | ------------------- | ------ | ------------------------ |
|
||||
| 1 | CCTV-Isolation | Port 25 (CCTV) | VLAN 40 | VLAN 10,20,30 | Deny | CCTV cannot reach others |
|
||||
| 2 | Guest-Isolation | Port 5-20 (APs) | VLAN 70 | VLAN 10,20,30,40,50 | Deny | Guest isolation |
|
||||
| 3 | Voice-QoS | Port 26 (Phone) | VLAN 50 | Any | Allow | QoS priority DSCP EF |
|
||||
|
||||
*Storm Control (per port)*
|
||||
| Port Range | Broadcast | Multicast | Unknown Unicast | Notes |
|
||||
| ---------- | --------- | --------- | --------------- | ----------------------- |
|
||||
| 1-28 | 10% | 10% | 10% | Prevent broadcast storm |
|
||||
|
||||
*Spanning Tree Configuration*
|
||||
| Setting | Value | Notes |
|
||||
| -------------------- | --------- | ------------------------------ |
|
||||
| STP Mode | RSTP | Rapid Spanning Tree |
|
||||
| Root Bridge Priority | 4096 | SG2428P as root |
|
||||
| Port Fast | Port 5-24 | Edge ports (APs, endpoints) |
|
||||
| BPDU Guard | Port 5-24 | Protect against rogue switches |
|
||||
|
||||
> **หมายเหตุ**: SG2428P เป็น L2+ switch, ACL ทำได้จำกัด ให้ใช้ ER7206 เป็น primary firewall
|
||||
|
||||
|
||||
**EAP ACL (Omada Controller - Wireless Rules)**
|
||||
|
||||
*SSID: PSLCBP3 (Staff WiFi)*
|
||||
| # | Name | Source | Destination | Service | Action | Schedule | Notes |
|
||||
| --- | ------------------- | ---------- | ---------------- | -------- | ------ | -------- | ----------------- |
|
||||
| 1 | Allow-DNS | Any Client | 8.8.8.8, 1.1.1.1 | DNS (53) | Allow | Always | DNS resolution |
|
||||
| 2 | Allow-Server | Any Client | 192.168.10.0/24 | Any | Allow | Always | Access to servers |
|
||||
| 3 | Allow-Printer | Any Client | 192.168.30.222 | 9100,631 | Allow | Always | Print services |
|
||||
| 4 | Allow-Internet | Any Client | WAN | Any | Allow | Always | Internet access |
|
||||
| 5 | Block-MGMT | Any Client | 192.168.20.0/24 | Any | Deny | Always | No management |
|
||||
| 6 | Block-CCTV | Any Client | 192.168.40.0/24 | Any | Deny | Always | No CCTV access |
|
||||
| 7 | Block-Voice | Any Client | 192.168.50.0/24 | Any | Deny | Always | No Voice access |
|
||||
| 8 | Block-Client2Client | Any Client | Any Client | Any | Deny | Always | Client isolation |
|
||||
|
||||
*SSID: GUEST (Guest WiFi)*
|
||||
| # | Name | Source | Destination | Service | Action | Schedule | Notes |
|
||||
| --- | ------------------- | ---------- | ---------------- | ---------- | ------ | -------- | ------------------ |
|
||||
| 1 | Allow-DNS | Any Client | 8.8.8.8, 1.1.1.1 | DNS (53) | Allow | Always | DNS resolution |
|
||||
| 2 | Allow-HTTP | Any Client | WAN | HTTP/HTTPS | Allow | Always | Web browsing |
|
||||
| 3 | Block-RFC1918 | Any Client | 10.0.0.0/8 | Any | Deny | Always | No private IPs |
|
||||
| 4 | Block-RFC1918-2 | Any Client | 172.16.0.0/12 | Any | Deny | Always | No private IPs |
|
||||
| 5 | Block-RFC1918-3 | Any Client | 192.168.0.0/16 | Any | Deny | Always | No internal access |
|
||||
| 6 | Block-Client2Client | Any Client | Any Client | Any | Deny | Always | Client isolation |
|
||||
|
||||
*Rate Limiting*
|
||||
| SSID | Download Limit | Upload Limit | Notes |
|
||||
| ------- | -------------- | ------------ | ----------------------- |
|
||||
| PSLCBP3 | Unlimited | Unlimited | Staff full speed |
|
||||
| GUEST | 10 Mbps | 5 Mbps | Guest bandwidth control |
|
||||
|
||||
*Captive Portal (GUEST SSID)*
|
||||
| Setting | Value | Notes |
|
||||
| ---------------- | --------------- | ---------------------- |
|
||||
| Portal Type | Simple Password | Single shared password |
|
||||
| Session Timeout | 8 Hours | Re-auth after 8 hours |
|
||||
| Idle Timeout | 30 Minutes | Disconnect if idle |
|
||||
| Terms of Service | Enabled | User must accept ToS |
|
||||
|
||||
> **หมายเหตุ**: EAP ACL ทำงานที่ Layer 3 บน Omada Controller ช่วยลด load บน ER7206
|
||||
|
||||
|
||||
**Network Topology Diagram**
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph Internet
|
||||
WAN[("🌐 Internet<br/>WAN")]
|
||||
end
|
||||
|
||||
subgraph Router["ER7206 Router"]
|
||||
R[("🔲 ER7206<br/>192.168.20.1")]
|
||||
end
|
||||
|
||||
subgraph CoreSwitch["SG2428P Core Switch"]
|
||||
CS[("🔲 SG2428P<br/>192.168.20.2")]
|
||||
end
|
||||
|
||||
subgraph ServerSwitch["AMPCOM 2.5G Switch"]
|
||||
SS[("🔲 AMPCOM<br/>192.168.20.3")]
|
||||
end
|
||||
|
||||
subgraph Servers["VLAN 10 - Servers"]
|
||||
QNAP[("💾 QNAP<br/>192.168.10.10")]
|
||||
ASUSTOR[("💾 ASUSTOR<br/>192.168.10.11")]
|
||||
end
|
||||
|
||||
subgraph AccessPoints["EAP610 x16"]
|
||||
AP[("📶 WiFi APs")]
|
||||
end
|
||||
|
||||
subgraph OtherSwitches["Distribution"]
|
||||
CCTV_SW[("🔲 TL-SL1226P<br/>CCTV")]
|
||||
PHONE_SW[("🔲 SG1210P<br/>IP Phone")]
|
||||
ADMIN_SW[("🔲 ES205G<br/>Admin")]
|
||||
end
|
||||
|
||||
WAN --> R
|
||||
R -->|Port 3| CS
|
||||
CS -->|LAG Port 3-4| SS
|
||||
SS -->|Port 3-4 LACP| QNAP
|
||||
SS -->|Port 5-6 LACP| ASUSTOR
|
||||
SS -->|Port 7| ADMIN_SW
|
||||
CS -->|Port 5-20| AP
|
||||
CS -->|SFP 25| CCTV_SW
|
||||
CS -->|SFP 26| PHONE_SW
|
||||
CS -->|Port 24| ADMIN_SW
|
||||
```
|
||||
|
||||
|
||||
**OC200 Omada Controller Configuration**
|
||||
| Setting | Value | Notes |
|
||||
| --------------- | -------------------------- | ------------------------------ |
|
||||
| Controller IP | 192.168.20.10 | Static IP in MGMT VLAN |
|
||||
| Controller Port | 8043 (HTTPS) | Management Web UI |
|
||||
| Adoption URL | https://192.168.20.10:8043 | URL for AP adoption |
|
||||
| Site Name | LCBP3 | Single site configuration |
|
||||
| Managed Devices | 16x EAP610 | All APs managed centrally |
|
||||
| Firmware Update | Manual | Test before production rollout |
|
||||
| Backup Schedule | Weekly (Sunday 2AM) | Auto backup to QNAP |
|
||||
|
||||
|
||||
## **2.3 การจัดการ Configuration (ปรับปรุง):**
|
||||
|
||||
- ใช้ docker-compose.yml สำหรับ environment variables ตามข้อจำกัดของ QNAP
|
||||
- Secrets Management:
|
||||
- ห้ามระบุ Sensitive Secrets (Password, Keys) ใน docker-compose.yml หลัก
|
||||
- ต้องใช้ไฟล์ docker-compose.override.yml (ที่ถูก gitignore) สำหรับ Inject Environment Variables ที่เป็นความลับในแต่ละ Environment (Dev/Prod)
|
||||
- ไฟล์ docker-compose.yml หลักให้ใส่ค่า Dummy หรือว่างไว้
|
||||
- แต่ต้องมี mechanism สำหรับจัดการ sensitive secrets อย่างปลอดภัย โดยใช้:
|
||||
- Docker secrets (ถ้ารองรับ)
|
||||
- External secret management (Hashicorp Vault) หรือ
|
||||
- Encrypted environment variables
|
||||
- Development environment ยังใช้ .env ได้ แต่ต้องไม่ commit เข้า version control
|
||||
- ต้องมี configuration validation during application startup
|
||||
- ต้องแยก configuration ตาม environment (development, staging, production)
|
||||
- Docker Network: ทุก Service จะเชื่อมต่อผ่านเครือข่ายกลางชื่อ lcbp3 เพื่อให้สามารถสื่อสารกันได้
|
||||
|
||||
## **2.4 Core Services:**
|
||||
|
||||
- Code Hosting: Gitea (Self-hosted on QNAP)
|
||||
|
||||
- Application name: git
|
||||
- Service name: gitea
|
||||
- Domain: git.np-dms.work
|
||||
- หน้าที่: เป็นศูนย์กลางในการเก็บและจัดการเวอร์ชันของโค้ด (Source Code) สำหรับทุกส่วน
|
||||
|
||||
- Backend / Data Platform: NestJS
|
||||
|
||||
- Application name: lcbp3-backend
|
||||
- Service name: backend
|
||||
- Domain: backend.np-dms.work
|
||||
- Framework: NestJS (Node.js, TypeScript, ESM)
|
||||
- หน้าที่: จัดการโครงสร้างข้อมูล (Data Models), สร้าง API, จัดการสิทธิ์ผู้ใช้ (Roles & Permissions), และสร้าง Workflow ทั้งหมดของระบบ
|
||||
|
||||
- Database: MariaDB 11.8
|
||||
|
||||
- Application name: lcbp3-db
|
||||
- Service name: mariadb
|
||||
- Domain: db.np-dms.work
|
||||
- หน้าที่: ฐานข้อมูลหลักสำหรับเก็บข้อมูลทั้งหมด
|
||||
- Tooling: DBeaver (Community Edition), phpmyadmin สำหรับการออกแบบและจัดการฐานข้อมูล
|
||||
|
||||
- Database Management: phpMyAdmin
|
||||
|
||||
- Application name: lcbp3-db
|
||||
- Service: phpmyadmin:5-apache
|
||||
- Service name: pma
|
||||
- Domain: pma.np-dms.work
|
||||
- หน้าที่: จัดการฐานข้อมูล mariadb ผ่าน Web UI
|
||||
|
||||
- Frontend: Next.js
|
||||
|
||||
- Application name: lcbp3-frontend
|
||||
- Service name: frontend
|
||||
- Domain: lcbp3.np-dms.work
|
||||
- Framework: Next.js (App Router, React, TypeScript, ESM)
|
||||
- Styling: Tailwind CSS + PostCSS
|
||||
- Component Library: shadcn/ui
|
||||
- หน้าที่: สร้างหน้าตาเว็บแอปพลิเคชันสำหรับให้ผู้ใช้งานเข้ามาดู Dashboard, จัดการเอกสาร, และติดตามงาน โดยจะสื่อสารกับ Backend ผ่าน API
|
||||
|
||||
- Workflow Automation: n8n
|
||||
|
||||
- Application name: lcbp3-n8n
|
||||
- Service: n8nio/n8n:latest
|
||||
- Service name: n8n
|
||||
- Domain: n8n.np-dms.work
|
||||
- หน้าที่: จัดการ workflow ระหว่าง Backend และ Line
|
||||
|
||||
- Reverse Proxy: Nginx Proxy Manager
|
||||
|
||||
- Application name: lcbp3-npm
|
||||
- Service: Nginx Proxy Manager (nginx-proxy-manage: latest)
|
||||
- Service name: npm
|
||||
- Domain: npm.np-dms.work
|
||||
- หน้าที่: เป็นด่านหน้าในการรับ-ส่งข้อมูล จัดการโดเมนทั้งหมด, ทำหน้าที่เป็น Proxy ชี้ไปยัง Service ที่ถูกต้อง, และจัดการ SSL Certificate (HTTPS) ให้อัตโนมัติ
|
||||
|
||||
- Search Engine: Elasticsearch
|
||||
- Cache: Redis
|
||||
|
||||
## **2.5 Business Logic & Consistency (ปรับปรุง):**
|
||||
|
||||
- 2.5.1 Unified Workflow Engine (หลัก):
|
||||
|
||||
- ระบบการเดินเอกสารทั้งหมด (Correspondence, RFA, Circulation) ต้อง ใช้ Engine กลางเดียวกัน โดยกำหนด Logic ผ่าน Workflow DSL (JSON Configuration) แทนการเขียน Hard-coded ลงในตาราง
|
||||
- Workflow Versioning (เพิ่ม): ระบบต้องรองรับการกำหนด Version ของ Workflow Definition โดยเอกสารที่เริ่มกระบวนการไปแล้ว (In-progress instances) จะต้องใช้ Workflow Version เดิม จนกว่าจะสิ้นสุดกระบวนการ หรือได้รับคำสั่ง Migrate จาก Admin เพื่อป้องกันความขัดแย้งของ State
|
||||
|
||||
- 2.5.2 Separation of Concerns:
|
||||
|
||||
- Module ต่างๆ (Correspondence, RFA, Circulation) จะเก็บเฉพาะข้อมูลของเอกสาร (Data) ส่วนสถานะและการเปลี่ยนสถานะ (State Transition) จะถูกจัดการโดย Workflow Engine
|
||||
|
||||
- 2.5.3 Idempotency & Locking:
|
||||
|
||||
- ใช้กลไกเดิมในการป้องกันการทำรายการซ้ำ
|
||||
|
||||
- 2.5.4 Optimistic Locking:
|
||||
|
||||
- ใช้ Version Column ใน Database ควบคู่กับ Redis Lock สำหรับการสร้างเลขที่เอกสาร เพื่อเป็น Safety Net ชั้นสุดท้าย
|
||||
|
||||
- 2.5.5 จะไม่มีการใช้ SQL Triggers
|
||||
- เพื่อป้องกันตรรกะซ่อนเร้น (Hidden Logic) และความซับซ้อนในการดีบัก
|
||||
|
||||
## **2.6 Data Migration และ Schema Versioning:**
|
||||
|
||||
- ต้องมี database migration scripts สำหรับทุก schema change โดยใช้ TypeORM migrations
|
||||
- ต้องรองรับ rollback ของ migration ได้
|
||||
- ต้องมี data seeding strategy สำหรับ environment ต่างๆ (development, staging, production)
|
||||
- ต้องมี version compatibility between schema versions
|
||||
- Migration scripts ต้องผ่านการทดสอบใน staging environment ก่อน production
|
||||
- ต้องมี database backup ก่อนทำ migration ใน production
|
||||
|
||||
## **2.7 กลยุทธ์ความทนทานและการจัดการข้อผิดพลาด (Resilience & Error Handling Strategy)**
|
||||
|
||||
- 2.7.1 Circuit Breaker Pattern: ใช้สำหรับ external service calls (Email, LINE, Elasticsearch)
|
||||
- 2.7.2 Retry Mechanism: ด้วย exponential backoff สำหรับ transient failures
|
||||
- 2.7.3 Fallback Strategies: Graceful degradation เมื่อบริการภายนอกล้มเหลว
|
||||
- 2.7.4 Error Handling: Error messages ต้องไม่เปิดเผยข้อมูล sensitive
|
||||
- 2.6.5 Monitoring: Centralized error monitoring และ alerting system
|
||||
|
||||
@@ -1,652 +0,0 @@
|
||||
# Data Model Architecture
|
||||
|
||||
---
|
||||
|
||||
title: 'Data Model Architecture'
|
||||
version: 1.5.0
|
||||
status: first-draft
|
||||
owner: Nattanin Peancharoen
|
||||
last_updated: 2025-11-30
|
||||
related:
|
||||
|
||||
- specs/01-requirements/02-architecture.md
|
||||
- specs/01-requirements/03-functional-requirements.md
|
||||
- docs/4_Data_Dictionary_V1_4_5.md
|
||||
- docs/8_lcbp3_v1_4_5.sql
|
||||
|
||||
---
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
เอกสารนี้อธิบายสถาปัตยกรรมของ Data Model สำหรับระบบ LCBP3-DMS โดยครอบคลุมโครงสร้างฐานข้อมูล, ความสัมพันธ์ระหว่างตาราง, และหลักการออกแบบที่สำคัญ
|
||||
|
||||
## 🎯 Design Principles
|
||||
|
||||
### 1. Separation of Concerns
|
||||
|
||||
- **Master-Revision Pattern**: แยกข้อมูลที่ไม่เปลี่ยนแปลง (Master) จากข้อมูลที่มีการแก้ไข (Revisions)
|
||||
- `correspondences` (Master) ↔ `correspondence_revisions` (Revisions)
|
||||
- `rfas` (Master) ↔ `rfa_revisions` (Revisions)
|
||||
- `shop_drawings` (Master) ↔ `shop_drawing_revisions` (Revisions)
|
||||
|
||||
### 2. Data Integrity
|
||||
|
||||
- **Foreign Key Constraints**: ใช้ FK ทุกความสัมพันธ์เพื่อรักษาความสมบูรณ์ของข้อมูล
|
||||
- **Soft Delete**: ใช้ `deleted_at` แทนการลบข้อมูลจริง เพื่อรักษาประวัติ
|
||||
- **Optimistic Locking**: ใช้ `version` column ใน `document_number_counters` ป้องกัน Race Condition
|
||||
|
||||
### 3. Flexibility & Extensibility
|
||||
|
||||
- **JSON Details Field**: เก็บข้อมูลเฉพาะประเภทใน `correspondence_revisions.details`
|
||||
- **Virtual Columns**: สร้าง Index จาก JSON fields สำหรับ Performance
|
||||
- **Master Data Tables**: แยกข้อมูล Master (Types, Status, Codes) เพื่อความยืดหยุ่น
|
||||
|
||||
### 4. Security & Audit
|
||||
|
||||
- **RBAC (Role-Based Access Control)**: ระบบสิทธิ์แบบ Hierarchical Scope
|
||||
- **Audit Trail**: บันทึกผู้สร้าง/แก้ไข และเวลาในทุกตาราง
|
||||
- **Two-Phase File Upload**: ป้องกันไฟล์ขยะด้วย Temporary Storage
|
||||
|
||||
## 🗂️ Database Schema Overview
|
||||
|
||||
### Entity Relationship Diagram
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
%% Core Entities
|
||||
organizations ||--o{ users : "employs"
|
||||
projects ||--o{ contracts : "contains"
|
||||
projects ||--o{ correspondences : "manages"
|
||||
|
||||
%% RBAC
|
||||
users ||--o{ user_assignments : "has"
|
||||
roles ||--o{ user_assignments : "assigned_to"
|
||||
roles ||--o{ role_permissions : "has"
|
||||
permissions ||--o{ role_permissions : "granted_by"
|
||||
|
||||
%% Correspondences
|
||||
correspondences ||--o{ correspondence_revisions : "has_revisions"
|
||||
correspondence_types ||--o{ correspondences : "categorizes"
|
||||
correspondence_status ||--o{ correspondence_revisions : "defines_state"
|
||||
disciplines ||--o{ correspondences : "classifies"
|
||||
|
||||
%% RFAs
|
||||
rfas ||--o{ rfa_revisions : "has_revisions"
|
||||
rfa_types ||--o{ rfas : "categorizes"
|
||||
rfa_status_codes ||--o{ rfa_revisions : "defines_state"
|
||||
rfa_approve_codes ||--o{ rfa_revisions : "defines_result"
|
||||
disciplines ||--o{ rfas : "classifies"
|
||||
|
||||
%% Drawings
|
||||
shop_drawings ||--o{ shop_drawing_revisions : "has_revisions"
|
||||
shop_drawing_main_categories ||--o{ shop_drawings : "categorizes"
|
||||
shop_drawing_sub_categories ||--o{ shop_drawings : "sub_categorizes"
|
||||
|
||||
%% Attachments
|
||||
attachments ||--o{ correspondence_attachments : "attached_to"
|
||||
correspondences ||--o{ correspondence_attachments : "has"
|
||||
```
|
||||
|
||||
## 📊 Data Model Categories
|
||||
|
||||
### 1. 🏢 Core & Master Data
|
||||
|
||||
#### 1.1 Organizations & Projects
|
||||
|
||||
**Tables:**
|
||||
|
||||
- `organization_roles` - บทบาทขององค์กร (OWNER, DESIGNER, CONSULTANT, CONTRACTOR)
|
||||
- `organizations` - องค์กรทั้งหมดในระบบ
|
||||
- `projects` - โครงการ
|
||||
- `contracts` - สัญญาภายใต้โครงการ
|
||||
- `project_organizations` - M:N ระหว่าง Projects และ Organizations
|
||||
- `contract_organizations` - M:N ระหว่าง Contracts และ Organizations พร้อม Role
|
||||
|
||||
**Key Relationships:**
|
||||
|
||||
```
|
||||
projects (1) ──→ (N) contracts
|
||||
projects (N) ←→ (N) organizations [via project_organizations]
|
||||
contracts (N) ←→ (N) organizations [via contract_organizations]
|
||||
```
|
||||
|
||||
**Business Rules:**
|
||||
|
||||
- Organization code ต้องไม่ซ้ำกันในระบบ
|
||||
- Contract ต้องผูกกับ Project เสมอ (ON DELETE CASCADE)
|
||||
- Soft delete ใช้ `is_active` flag
|
||||
|
||||
---
|
||||
|
||||
### 2. 👥 Users & RBAC
|
||||
|
||||
#### 2.1 User Management
|
||||
|
||||
**Tables:**
|
||||
|
||||
- `users` - ผู้ใช้งานระบบ
|
||||
- `roles` - บทบาทพร้อม Scope (Global, Organization, Project, Contract)
|
||||
- `permissions` - สิทธิ์การใช้งาน (49 permissions)
|
||||
- `role_permissions` - M:N mapping
|
||||
- `user_assignments` - การมอบหมายบทบาทพร้อม Scope Context
|
||||
|
||||
**Scope Hierarchy:**
|
||||
|
||||
```
|
||||
Global (ทั้งระบบ)
|
||||
↓
|
||||
Organization (ระดับองค์กร)
|
||||
↓
|
||||
Project (ระดับโครงการ)
|
||||
↓
|
||||
Contract (ระดับสัญญา)
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
|
||||
- **Hierarchical Scope**: User สามารถมีหลาย Role ในหลาย Scope
|
||||
- **Scope Inheritance**: สิทธิ์ระดับบนครอบคลุมระดับล่าง
|
||||
- **Account Security**: Failed login tracking, Account locking, Password hashing (bcrypt)
|
||||
|
||||
**Example User Assignment:**
|
||||
|
||||
```sql
|
||||
-- User A เป็น Editor ในองค์กร TEAM
|
||||
INSERT INTO user_assignments (user_id, role_id, organization_id)
|
||||
VALUES (1, 4, 3);
|
||||
|
||||
-- User B เป็น Project Manager ในโครงการ LCBP3
|
||||
INSERT INTO user_assignments (user_id, role_id, project_id)
|
||||
VALUES (2, 6, 1);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. ✉️ Correspondences (เอกสารโต้ตอบ)
|
||||
|
||||
#### 3.1 Master-Revision Pattern
|
||||
|
||||
**Master Table: `correspondences`**
|
||||
|
||||
เก็บข้อมูลที่ไม่เปลี่ยนแปลง:
|
||||
|
||||
- `correspondence_number` - เลขที่เอกสาร (Unique per Project)
|
||||
- `correspondence_type_id` - ประเภทเอกสาร (RFA, RFI, TRANSMITTAL, etc.)
|
||||
- `discipline_id` - สาขางาน (GEN, STR, ARC, etc.) [NEW v1.4.5]
|
||||
- `project_id`, `originator_id` - โครงการและองค์กรผู้ส่ง
|
||||
|
||||
**Revision Table: `correspondence_revisions`**
|
||||
|
||||
เก็บข้อมูลที่เปลี่ยนแปลงได้:
|
||||
|
||||
- `revision_number` - หมายเลข Revision (0, 1, 2...)
|
||||
- `is_current` - Flag สำหรับ Revision ปัจจุบัน (UNIQUE constraint)
|
||||
- `title`, `description` - เนื้อหาเอกสาร
|
||||
- `correspondence_status_id` - สถานะ (DRAFT, SUBOWN, REPCSC, etc.)
|
||||
- `details` - JSON field สำหรับข้อมูลเฉพาะประเภท
|
||||
- Virtual Columns: `v_ref_project_id`, `v_ref_type`, `v_doc_subtype` (Indexed)
|
||||
|
||||
**Supporting Tables:**
|
||||
|
||||
- `correspondence_types` - Master ประเภทเอกสาร (10 types)
|
||||
- `correspondence_status` - Master สถานะ (23 status codes)
|
||||
- `correspondence_sub_types` - ประเภทย่อยสำหรับ Document Numbering [NEW v1.4.5]
|
||||
- `disciplines` - สาขางาน (GEN, STR, ARC, etc.) [NEW v1.4.5]
|
||||
- `correspondence_recipients` - M:N ผู้รับ (TO/CC)
|
||||
- `correspondence_tags` - M:N Tags
|
||||
- `correspondence_references` - M:N Cross-references
|
||||
|
||||
**Example Query - Get Current Revision:**
|
||||
|
||||
```sql
|
||||
SELECT c.correspondence_number, cr.title, cr.revision_label, cs.status_name
|
||||
FROM correspondences c
|
||||
JOIN correspondence_revisions cr ON c.id = cr.correspondence_id
|
||||
JOIN correspondence_status cs ON cr.correspondence_status_id = cs.id
|
||||
WHERE cr.is_current = TRUE
|
||||
AND c.deleted_at IS NULL;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 📐 RFAs (Request for Approval)
|
||||
|
||||
#### 4.1 RFA Structure
|
||||
|
||||
**Master Table: `rfas`**
|
||||
|
||||
- `rfa_type_id` - ประเภท RFA (DWG, DOC, MAT, SPC, etc.)
|
||||
- `discipline_id` - สาขางาน [NEW v1.4.5]
|
||||
|
||||
**Revision Table: `rfa_revisions`**
|
||||
|
||||
- `correspondence_id` - Link กับ Correspondence (RFA เป็น Correspondence ประเภทหนึ่ง)
|
||||
- `rfa_status_code_id` - สถานะ (DFT, FAP, FRE, FCO, ASB, OBS, CC)
|
||||
- `rfa_approve_code_id` - ผลการอนุมัติ (1A, 1C, 1N, 1R, 3C, 3R, 4X, 5N)
|
||||
- `approved_date` - วันที่อนุมัติ
|
||||
|
||||
**Supporting Tables:**
|
||||
|
||||
- `rfa_types` - 11 ประเภท (Shop Drawing, Document, Material, etc.)
|
||||
- `rfa_status_codes` - 7 สถานะ
|
||||
- `rfa_approve_codes` - 8 รหัสผลการอนุมัติ
|
||||
- `rfa_items` - M:N เชื่อม RFA (ประเภท DWG) กับ Shop Drawing Revisions
|
||||
|
||||
**RFA Workflow States:**
|
||||
|
||||
```
|
||||
DFT (Draft)
|
||||
↓
|
||||
FAP (For Approve) / FRE (For Review)
|
||||
↓
|
||||
[Approval Process]
|
||||
↓
|
||||
FCO (For Construction) / ASB (As-Built) / 3R (Revise) / 4X (Reject)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 📐 Drawings (แบบก่อสร้าง)
|
||||
|
||||
#### 5.1 Contract Drawings (แบบคู่สัญญา)
|
||||
|
||||
**Tables:**
|
||||
|
||||
- `contract_drawing_volumes` - เล่มแบบ
|
||||
- `contract_drawing_cats` - หมวดหมู่หลัก
|
||||
- `contract_drawing_sub_cats` - หมวดหมู่ย่อย
|
||||
- `contract_drawing_subcat_cat_maps` - M:N Mapping
|
||||
- `contract_drawings` - แบบคู่สัญญา
|
||||
|
||||
**Hierarchy:**
|
||||
|
||||
```
|
||||
Volume (เล่ม) - has volume_page
|
||||
└─ Category (หมวดหมู่หลัก)
|
||||
└─ Sub-Category (หมวดหมู่ย่อย) -- Linked via Map Table
|
||||
└─ Drawing (แบบ)
|
||||
```
|
||||
|
||||
#### 5.2 Shop Drawings (แบบก่อสร้าง)
|
||||
|
||||
**Tables:**
|
||||
|
||||
- `shop_drawing_main_categories` - หมวดหมู่หลัก (Project Specific)
|
||||
- `shop_drawing_sub_categories` - หมวดหมู่ย่อย (Project Specific)
|
||||
- `shop_drawings` - Master แบบก่อสร้าง (No title, number only)
|
||||
- `shop_drawing_revisions` - Revisions (Holds Title & Legacy Number)
|
||||
- `shop_drawing_revision_contract_refs` - M:N อ้างอิงแบบคู่สัญญา
|
||||
|
||||
**Revision Tracking:**
|
||||
|
||||
```sql
|
||||
-- Get latest revision of a shop drawing
|
||||
SELECT sd.shop_drawing_number, sdr.revision_label, sdr.revision_date
|
||||
FROM shop_drawings sd
|
||||
JOIN shop_drawing_revisions sdr ON sd.id = sdr.shop_drawing_id
|
||||
WHERE sd.shop_drawing_number = 'SD-STR-001'
|
||||
ORDER BY sdr.revision_number DESC
|
||||
LIMIT 1;
|
||||
```
|
||||
|
||||
#### 5.3 As Built Drawings (แบบสร้างจริง) [NEW v1.7.0]
|
||||
|
||||
**Tables:**
|
||||
|
||||
- `asbuilt_drawings` - Master แบบสร้างจริง
|
||||
- `asbuilt_drawing_revisions` - Revisions history
|
||||
- `asbuilt_revision_shop_revisions_refs` - เชื่อมโยงกับ Shop Drawing Revision source
|
||||
- `asbuilt_drawing_revision_attachments` - ไฟล์แนบ (PDF/DWG)
|
||||
|
||||
**Business Rules:**
|
||||
|
||||
- As Built 1 ใบ อาจมาจาก Shop Drawing หลายใบ (Many-to-Many via refs table)
|
||||
- แยก Counter distinct จาก Shop Drawing และ Contract Drawing
|
||||
- รองรับไฟล์แนบหลายประเภท (PDF, DWG, SOURCE)
|
||||
|
||||
---
|
||||
|
||||
### 6. 🔄 Circulations & Transmittals
|
||||
|
||||
#### 6.1 Circulations (ใบเวียนภายใน)
|
||||
|
||||
**Tables:**
|
||||
|
||||
- `circulation_status_codes` - สถานะ (OPEN, IN_REVIEW, COMPLETED, CANCELLED)
|
||||
- `circulations` - ใบเวียน (1:1 กับ Correspondence)
|
||||
|
||||
**Workflow:**
|
||||
|
||||
```
|
||||
OPEN → IN_REVIEW → COMPLETED
|
||||
↓
|
||||
CANCELLED
|
||||
```
|
||||
|
||||
#### 6.2 Transmittals (เอกสารนำส่ง)
|
||||
|
||||
**Tables:**
|
||||
|
||||
- `transmittals` - ข้อมูล Transmittal (1:1 กับ Correspondence)
|
||||
- `transmittal_items` - M:N รายการเอกสารที่นำส่ง
|
||||
|
||||
**Purpose Types:**
|
||||
|
||||
- FOR_APPROVAL
|
||||
- FOR_INFORMATION
|
||||
- FOR_REVIEW
|
||||
- OTHER
|
||||
|
||||
---
|
||||
|
||||
### 7. 📎 File Management
|
||||
|
||||
#### 7.1 Two-Phase Storage Pattern
|
||||
|
||||
**Table: `attachments`**
|
||||
|
||||
**Phase 1: Temporary Upload**
|
||||
|
||||
```sql
|
||||
INSERT INTO attachments (
|
||||
original_filename, stored_filename, file_path,
|
||||
mime_type, file_size, is_temporary, temp_id,
|
||||
uploaded_by_user_id, expires_at, checksum
|
||||
)
|
||||
VALUES (
|
||||
'document.pdf', 'uuid-document.pdf', '/temp/uuid-document.pdf',
|
||||
'application/pdf', 1024000, TRUE, 'temp-uuid-123',
|
||||
1, NOW() + INTERVAL 1 HOUR, 'sha256-hash'
|
||||
);
|
||||
```
|
||||
|
||||
**Phase 2: Commit to Permanent**
|
||||
|
||||
```sql
|
||||
-- Update attachment to permanent
|
||||
UPDATE attachments
|
||||
SET is_temporary = FALSE, expires_at = NULL
|
||||
WHERE temp_id = 'temp-uuid-123';
|
||||
|
||||
-- Link to correspondence
|
||||
INSERT INTO correspondence_attachments (correspondence_id, attachment_id, is_main_document)
|
||||
VALUES (1, 123, TRUE);
|
||||
```
|
||||
|
||||
**Junction Tables:**
|
||||
|
||||
- `correspondence_attachments` - M:N
|
||||
- `circulation_attachments` - M:N
|
||||
- `shop_drawing_revision_attachments` - M:N (with file_type)
|
||||
- `contract_drawing_attachments` - M:N (with file_type)
|
||||
|
||||
**Security Features:**
|
||||
|
||||
- Checksum validation (SHA-256)
|
||||
- Automatic cleanup of expired temporary files
|
||||
- File type validation via `mime_type`
|
||||
|
||||
---
|
||||
|
||||
### 8. 🔢 Document Numbering
|
||||
|
||||
#### 8.1 Format & Counter System
|
||||
|
||||
**Tables:**
|
||||
|
||||
- `document_number_formats` - Template รูปแบบเลขที่เอกสาร
|
||||
- `document_number_counters` - Running Number Counter with Optimistic Locking
|
||||
|
||||
**Format Template Example:**
|
||||
|
||||
```
|
||||
{ORG_CODE}-{TYPE_CODE}-{DISCIPLINE_CODE}-{YEAR}-{SEQ:4}
|
||||
→ TEAM-RFA-STR-2025-0001
|
||||
```
|
||||
|
||||
**Counter Table Structure:**
|
||||
|
||||
```sql
|
||||
CREATE TABLE document_number_counters (
|
||||
project_id INT,
|
||||
originator_organization_id INT,
|
||||
correspondence_type_id INT,
|
||||
discipline_id INT DEFAULT 0, -- NEW v1.4.5
|
||||
current_year INT,
|
||||
version INT DEFAULT 0, -- Optimistic Lock
|
||||
last_number INT DEFAULT 0,
|
||||
PRIMARY KEY (
|
||||
project_id,
|
||||
originator_organization_id,
|
||||
recipient_organization_id,
|
||||
correspondence_type_id,
|
||||
sub_type_id,
|
||||
rfa_type_id,
|
||||
discipline_id,
|
||||
reset_scope
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
**Optimistic Locking Pattern:**
|
||||
|
||||
```sql
|
||||
-- Get next number with version check
|
||||
UPDATE document_number_counters
|
||||
SET last_number = last_number + 1,
|
||||
version = version + 1
|
||||
WHERE project_id = 1
|
||||
AND originator_organization_id = 3
|
||||
AND correspondence_type_id = 1
|
||||
AND discipline_id = 2
|
||||
AND current_year = 2025
|
||||
AND version = @current_version; -- Optimistic lock check
|
||||
|
||||
-- If affected rows = 0, retry (conflict detected)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security & Audit
|
||||
|
||||
### 1. Audit Logging
|
||||
|
||||
**Table: `audit_logs`**
|
||||
|
||||
บันทึกการเปลี่ยนแปลงสำคัญ:
|
||||
|
||||
- User actions (CREATE, UPDATE, DELETE)
|
||||
- Entity type และ Entity ID
|
||||
- Old/New values (JSON)
|
||||
- IP Address, User Agent
|
||||
|
||||
### 2. User Preferences
|
||||
|
||||
**Table: `user_preferences`**
|
||||
|
||||
เก็บการตั้งค่าส่วนตัว:
|
||||
|
||||
- Language preference
|
||||
- Notification settings
|
||||
- UI preferences (JSON)
|
||||
|
||||
### 3. JSON Schema Validation
|
||||
|
||||
**Table: `json_schemas`**
|
||||
|
||||
เก็บ Schema สำหรับ Validate JSON fields:
|
||||
|
||||
- `correspondence_revisions.details`
|
||||
- `user_preferences.preferences`
|
||||
|
||||
---
|
||||
|
||||
## 📈 Performance Optimization
|
||||
|
||||
### 1. Indexing Strategy
|
||||
|
||||
**Primary Indexes:**
|
||||
|
||||
- Primary Keys (AUTO_INCREMENT)
|
||||
- Foreign Keys (automatic in InnoDB)
|
||||
- Unique Constraints (business keys)
|
||||
|
||||
**Secondary Indexes:**
|
||||
|
||||
```sql
|
||||
-- Correspondence search
|
||||
CREATE INDEX idx_corr_type_status ON correspondence_revisions(correspondence_type_id, correspondence_status_id);
|
||||
CREATE INDEX idx_corr_date ON correspondence_revisions(document_date);
|
||||
|
||||
-- Virtual columns for JSON
|
||||
CREATE INDEX idx_v_ref_project ON correspondence_revisions(v_ref_project_id);
|
||||
CREATE INDEX idx_v_doc_subtype ON correspondence_revisions(v_doc_subtype);
|
||||
|
||||
-- User lookup
|
||||
CREATE INDEX idx_user_email ON users(email);
|
||||
CREATE INDEX idx_user_org ON users(primary_organization_id, is_active);
|
||||
```
|
||||
|
||||
### 2. Virtual Columns
|
||||
|
||||
ใช้ Virtual Columns สำหรับ Index JSON fields:
|
||||
|
||||
```sql
|
||||
ALTER TABLE correspondence_revisions
|
||||
ADD COLUMN v_ref_project_id INT GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(details, '$.ref_project_id'))) VIRTUAL,
|
||||
ADD INDEX idx_v_ref_project(v_ref_project_id);
|
||||
```
|
||||
|
||||
### 3. Partitioning (Future)
|
||||
|
||||
พิจารณา Partition ตาราง `audit_logs` ตามปี:
|
||||
|
||||
```sql
|
||||
ALTER TABLE audit_logs
|
||||
PARTITION BY RANGE (YEAR(created_at)) (
|
||||
PARTITION p2024 VALUES LESS THAN (2025),
|
||||
PARTITION p2025 VALUES LESS THAN (2026),
|
||||
PARTITION p_future VALUES LESS THAN MAXVALUE
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Migration Strategy
|
||||
|
||||
### 1. TypeORM Migrations
|
||||
|
||||
ใช้ TypeORM Migration สำหรับ Schema Changes:
|
||||
|
||||
```typescript
|
||||
// File: backend/src/migrations/1234567890-AddDisciplineToCorrespondences.ts
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddDisciplineToCorrespondences1234567890
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE correspondences
|
||||
ADD COLUMN discipline_id INT NULL COMMENT 'สาขางาน (ถ้ามี)'
|
||||
AFTER correspondence_type_id
|
||||
`);
|
||||
|
||||
await queryRunner.query(`
|
||||
ALTER TABLE correspondences
|
||||
ADD CONSTRAINT fk_corr_discipline
|
||||
FOREIGN KEY (discipline_id) REFERENCES disciplines(id)
|
||||
ON DELETE SET NULL
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE correspondences DROP FOREIGN KEY fk_corr_discipline`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE correspondences DROP COLUMN discipline_id`
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Data Seeding
|
||||
|
||||
ใช้ Seed Scripts สำหรับ Master Data:
|
||||
|
||||
```typescript
|
||||
// File: backend/src/seeds/1-organizations.seed.ts
|
||||
export class OrganizationSeeder implements Seeder {
|
||||
public async run(dataSource: DataSource): Promise<void> {
|
||||
const repository = dataSource.getRepository(Organization);
|
||||
await repository.save([
|
||||
{
|
||||
organization_code: 'กทท.',
|
||||
organization_name: 'Port Authority of Thailand',
|
||||
},
|
||||
{
|
||||
organization_code: 'TEAM',
|
||||
organization_name: 'TEAM Consulting Engineering',
|
||||
},
|
||||
// ...
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Best Practices
|
||||
|
||||
### 1. Naming Conventions
|
||||
|
||||
- **Tables**: `snake_case`, plural (e.g., `correspondences`, `users`)
|
||||
- **Columns**: `snake_case` (e.g., `correspondence_number`, `created_at`)
|
||||
- **Foreign Keys**: `{referenced_table_singular}_id` (e.g., `project_id`, `user_id`)
|
||||
- **Junction Tables**: `{table1}_{table2}` (e.g., `correspondence_tags`)
|
||||
|
||||
### 2. Timestamp Columns
|
||||
|
||||
ทุกตารางควรมี:
|
||||
|
||||
- `created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP`
|
||||
- `updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`
|
||||
|
||||
### 3. Soft Delete
|
||||
|
||||
ใช้ `deleted_at DATETIME NULL` แทนการลบจริง:
|
||||
|
||||
```sql
|
||||
-- Soft delete
|
||||
UPDATE correspondences SET deleted_at = NOW() WHERE id = 1;
|
||||
|
||||
-- Query active records
|
||||
SELECT * FROM correspondences WHERE deleted_at IS NULL;
|
||||
```
|
||||
|
||||
### 4. JSON Field Guidelines
|
||||
|
||||
- ใช้สำหรับข้อมูลที่ไม่ต้อง Query บ่อย
|
||||
- สร้าง Virtual Columns สำหรับ fields ที่ต้อง Index
|
||||
- Validate ด้วย JSON Schema
|
||||
- Document structure ใน Data Dictionary
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Related Documentation
|
||||
|
||||
- [System Architecture](../01-requirements/01-02-architecture.md) - สถาปัตยกรรมระบบโดยรวม
|
||||
- [API Design](02-02-api-design.md) - การออกแบบ API
|
||||
- [Data Dictionary v1.4.5](../../docs/4_Data_Dictionary_V1_4_5.md) - รายละเอียดตารางทั้งหมด
|
||||
- [SQL Schema v1.4.5](../../docs/8_lcbp3_v1_4_5.sql) - SQL Script สำหรับสร้างฐานข้อมูล
|
||||
- [Functional Requirements](../01-requirements/01-03-functional-requirements.md) - ความต้องการด้านฟังก์ชัน
|
||||
|
||||
---
|
||||
|
||||
## 📝 Version History
|
||||
|
||||
| Version | Date | Author | Changes |
|
||||
| ------- | ---------- | -------------------- | ---------------------------------------------- |
|
||||
| 1.5.0 | 2025-11-30 | Nattanin Peancharoen | Initial data model documentation |
|
||||
| 1.4.5 | 2025-11-29 | System | Added disciplines and correspondence_sub_types |
|
||||
149
specs/02-architecture/03_Securities.md
Normal file
149
specs/02-architecture/03_Securities.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# การตั้งค่า Network Segmentation และ Firewall Rules
|
||||
|
||||
สำหรับอุปกรณ์ Omada (ER7206 + OC200) กลยุทธ์หลักคือการใช้ **VLANs (Virtual LANs)** เพื่อแบ่งกลุ่มอุปกรณ์ และใช้ **Firewall ACLs (Access Control Lists)** เพื่อควบคุมการจราจรระหว่างกลุ่มเหล่านั้น
|
||||
|
||||
นี่คือคำแนะนำตามแนวทาง "Zero Trust" ที่ปรับให้เข้ากับสถาปัตยกรรมของคุณครับ
|
||||
|
||||
---
|
||||
|
||||
## 1. 🌐 การแบ่งส่วนเครือข่าย (VLAN Segmentation)
|
||||
|
||||
ใน Omada Controller (OC200) ให้คุณไปที่ `Settings > Wired Networks > LAN` และสร้างเครือข่ายย่อย (VLANs) ดังนี้:
|
||||
|
||||
* **VLAN 10: SERVER**
|
||||
* **IP Range:** 192.168.10.x
|
||||
* **วัตถุประสงค์:** ใช้สำหรับอุปกรณ์ Server (QNAP และ ASUSTOR)
|
||||
|
||||
* **VLAN 20 MGMT(Default): Management**
|
||||
* **IP Range:** 192.168.20.x
|
||||
* **วัตถุประสงค์:** ใช้สำหรับอุปกรณ์ Network (ER7206, OC200, Switches) และ PC ของผู้ดูแลระบบ (Admin) เท่านั้น
|
||||
|
||||
* **VLAN 30: USER**
|
||||
* **IP Range:** 192.168.30.x
|
||||
* **วัตถุประสงค์:** สำหรับ PC, Notebook, และ Wi-Fi ของพนักงานทั่วไปที่ต้องเข้าใช้งานระบบ (เช่น `lcbp3.np-dms.work`)
|
||||
|
||||
* **VLAN 40: CCTV**
|
||||
* **IP Range:** 192.168.40.x
|
||||
* **วัตถุประสงค์:** ใช้สำหรับอุปกรณ์ CCTV เท่านั้น
|
||||
|
||||
* **VLAN 50 VOICEt**
|
||||
* **IP Range:** 192.168.50.x
|
||||
* **วัตถุประสงค์:** ใช้สำหรับอุปกรณ์ IP Phone เท่านั้น
|
||||
|
||||
* **VLAN 60 DMZ**
|
||||
* **IP Range:** 192.168.60.x
|
||||
* **วัตถุประสงค์:** ใช้สำหรับ Network DMZ เท่านั้น
|
||||
|
||||
* **VLAN 70: GUEST / Untrusted** (สำหรับ Wi-Fi แขก)
|
||||
* **IP Range:** 192.168.70.x
|
||||
* **วัตถุประสงค์:** สำหรับ Wi-Fi แขก (Guest) ห้ามเข้าถึงเครือข่ายภายในโดยเด็ดขาด
|
||||
|
||||
**การตั้งค่า Port Switch:**
|
||||
หลังจากสร้าง VLANs แล้ว ให้ไปที่ `Devices` > เลือก Switch ของคุณ > `Ports` > กำหนด Port Profile:
|
||||
|
||||
* Port ที่เสียบ QNAP NAS: ตั้งค่า Profile เป็น **VLAN 10**
|
||||
* Port ที่เสียบ PC พนักงาน: ตั้งค่า Profile เป็น **VLAN 20**
|
||||
|
||||
---
|
||||
|
||||
## 2. 🔥 Firewall Rules (ACLs)
|
||||
|
||||
นี่คือหัวใจสำคัญครับ ไปที่ `Settings > Network Security > ACL (Access Control)`
|
||||
|
||||
กฎของ Firewall จะทำงานจากบนลงล่าง (ข้อ 1 ทำก่อนข้อ 2)
|
||||
|
||||
---
|
||||
|
||||
### 2.1 IP Groups & Port Groups
|
||||
|
||||
**IP Groups** (สร้างใน `Settings > Network Security > Groups`):
|
||||
|
||||
| Group Name | Members |
|
||||
| :----------------- | :------------------------------------------------ |
|
||||
| `Server` | 192.168.10.8, 192.168.10.9, 192.168.10.111 |
|
||||
| `Omada-Controller` | 192.168.20.250 (OC200 IP) |
|
||||
| `DHCP-Gateways` | 192.168.30.1, 192.168.70.1 |
|
||||
| `QNAP_Services` | 192.168.10.8 |
|
||||
| `Internal` | 192.168.10.0/24, 192.168.20.0/24, 192.168.30.0/24 |
|
||||
| `Blacklist` | (Add malicious IPs as needed) |
|
||||
|
||||
**Port Groups**:
|
||||
|
||||
| Group Name | Ports |
|
||||
| :----------- | :-------------------------------------- |
|
||||
| `Web` | TCP 443, 8443, 80, 81, 2222 |
|
||||
| `Omada-Auth` | TCP 443, 8043, 8088, 8843, 29810-29814 |
|
||||
| `VoIP` | UDP 5060, 5061, 10000-20000 (SIP + RTP) |
|
||||
| `DHCP` | UDP 67, 68 |
|
||||
|
||||
---
|
||||
|
||||
### 2.2 Switch ACL (สำหรับ Omada OC200)
|
||||
|
||||
| ลำดับ | Name | Policy | Source | Destination | Ports |
|
||||
| :--- | :------------------------ | :----- | :---------------- | :---------------------------- | :---------------------------------------- |
|
||||
| 1 | 01 Allow-User-DHCP | Allow | Network → VLAN 30 | IP → 192.168.30.1 | Port Group → DHCP |
|
||||
| 2 | 02 Allow-Guest-DHCP | Allow | Network → VLAN 70 | IP → 192.168.70.1 | Port Group → DHCP |
|
||||
| 3 | 03 Allow-WiFi-Auth | Allow | Network → VLAN 30 | IP Group → Omada-Controller | Port Group → Omada-Auth |
|
||||
| 4 | 04 Allow-Guest-WiFi-Auth | Allow | Network → VLAN 70 | IP Group → Omada-Controller | Port Group → Omada-Auth |
|
||||
| 5 | 05 Isolate-Guests | Deny | Network → VLAN 70 | Network → VLAN 10, 20, 30, 60 | All |
|
||||
| 6 | 06 Isolate-Servers | Deny | Network → VLAN 10 | Network → VLAN 30 (USER) | All |
|
||||
| 7 | 07 Block-User-to-Mgmt | Deny | Network → VLAN 30 | Network → VLAN 20 (MGMT) | All |
|
||||
| 8 | 08 Allow-User-to-Services | Allow | Network → VLAN 30 | IP → QNAP (192.168.10.8) | Port Group → Web (443,8443, 80, 81, 2222) |
|
||||
| 9 | 09 Allow-Voice-to-User | Allow | Network → VLAN 50 | Network → VLAN 30,50 | All |
|
||||
| 10 | 10 Allow-MGMT-to-All | Allow | Network → VLAN 20 | Any | All |
|
||||
| 11 | 11 Allow-Server-Internal | Allow | IP Group : Server | IP Group : Server | All |
|
||||
| 12 | 12 Allow-Server → CCTV | Allow | IP Group : Server | Network → VLAN 40 (CCTV) | All |
|
||||
| 13 | 100 (Default) | Deny | Any | Any | All |
|
||||
|
||||
> ⚠️ **หมายเหตุสำคัญ - ลำดับ ACL:**
|
||||
> 1. **Allow rules ก่อน** - DHCP (#1-2) และ WiFi-Auth (#3-4) ต้องอยู่ **บนสุด**
|
||||
> 2. **Isolate/Deny rules ถัดมา** - (#5-7) block traffic ที่ไม่ต้องการ
|
||||
> 3. **Allow specific rules** - (#8-12) อนุญาต traffic ที่เหลือ
|
||||
> 4. **Default Deny ล่าสุด** - (#13) block ทุกอย่างที่ไม่ match
|
||||
|
||||
---
|
||||
|
||||
### 2.3 Gateway ACL (สำหรับ Omada ER7206)
|
||||
|
||||
| ลำดับ | Name | Policy | Direction | PROTOCOLS | Source | Destination |
|
||||
| :--- | :---------------------- | :----- | :-------- | :-------- | :------------------- | :--------------------------- |
|
||||
| 1 | 01 Blacklist | Deny | [WAN2] IN | All | IP Group:Blacklist | IP Group:Internal |
|
||||
| 2 | 02 Geo | Permit | [WAN2] IN | All | Location Group:Allow | IP Group:Internal |
|
||||
| 3 | 03 Allow-Voice-Internet | Permit | LAN->WAN | UDP | Network → VLAN 50 | Any |
|
||||
| 4 | 04 Internal → Internet | Permit | LAN->WAN | All | IP Group:Internal | Domain Group:DomainGroup_Any |
|
||||
|
||||
> 💡 **หมายเหตุ:** Rule #3 `Allow-Voice-Internet` อนุญาต IP Phone (VLAN 50) เชื่อมต่อ Cloud PBX ภายนอก ผ่าน Port Group → VoIP (UDP 5060, 5061, 10000-20000)
|
||||
|
||||
---
|
||||
|
||||
## 3. 🚪 Port Forwarding (การเปิด Service สู่สาธารณะ)
|
||||
|
||||
ส่วนนี้ไม่ใช่ Firewall ACL แต่จำเป็นเพื่อให้คนนอกเข้าใช้งานได้ครับ
|
||||
ไปที่ `Settings > Transmission > Port Forwarding`
|
||||
|
||||
สร้างกฎเพื่อส่งต่อการจราจรจาก WAN (อินเทอร์เน็ต) ไปยัง Nginx Proxy Manager (NPM) ที่อยู่บน QNAP (VLAN 10)
|
||||
|
||||
* **Name:** Allow-NPM-HTTPS
|
||||
* **External Port:** 443
|
||||
* **Internal Port:** 443
|
||||
* **Internal IP:** `192.168.10.8` (IP ของ QNAP)
|
||||
* **Protocol:** TCP
|
||||
|
||||
* **Name:** Allow-NPM-HTTP (สำหรับ Let's Encrypt)
|
||||
* **External Port:** 80
|
||||
* **Internal Port:** 80
|
||||
* **Internal IP:** `192.168.10.8` (IP ของ QNAP)
|
||||
* **Protocol:** TCP
|
||||
|
||||
### สรุปผังการเชื่อมต่อ
|
||||
|
||||
1. **ผู้ใช้ภายนอก** -> `https://lcbp3.np-dms.work`
|
||||
2. **ER7206** รับที่ Port 443
|
||||
3. **Port Forwarding** ส่งต่อไปยัง `192.168.10.8:443` (QNAP NPM)
|
||||
4. **NPM** (บน QNAP) ส่งต่อไปยัง `backend:3000` หรือ `frontend:3000` ภายใน Docker
|
||||
5. **ผู้ใช้ภายใน (Office)** -> `https://lcbp3.np-dms.work`
|
||||
6. **Firewall ACL** (กฎข้อ 4) อนุญาตให้ VLAN 30 คุยกับ `192.168.10.8:443`
|
||||
7. (ขั้นตอนที่ 3-4 ทำงานเหมือนเดิม)
|
||||
|
||||
การตั้งค่าตามนี้จะช่วยแยกส่วน Server ของคุณออกจากเครือข่ายพนักงานอย่างชัดเจน ซึ่งปลอดภัยกว่าการวางทุกอย่างไว้ในวง LAN เดียวกันมากครับ
|
||||
352
specs/02-architecture/ADR-007-api-design-error-handling.md
Normal file
352
specs/02-architecture/ADR-007-api-design-error-handling.md
Normal file
@@ -0,0 +1,352 @@
|
||||
# ADR-007: API Design & Error Handling Strategy
|
||||
|
||||
**Status:** ✅ Accepted
|
||||
**Date:** 2025-12-01
|
||||
**Decision Makers:** Backend Team, System Architect
|
||||
**Related Documents:** [Backend Guidelines](../03-implementation/03-02-backend-guidelines.md), [ADR-005: Technology Stack](./ADR-005-technology-stack.md)
|
||||
|
||||
---
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
ระบบ LCBP3-DMS ต้องการมาตรฐานการออกแบบ API ที่ชัดเจนและสม่ำเสมอทั้งระบบ รวมถึงกลยุทธ์การจัดการ Error และ Validation ที่เหมาะสม
|
||||
|
||||
### ปัญหาที่ต้องแก้:
|
||||
|
||||
1. **API Consistency:** ทำอย่างไรให้ API response format สม่ำเสมอทั้งระบบ
|
||||
2. **Error Handling:** จัดการ error อย่างไรให้ client เข้าใจและแก้ไขได้
|
||||
3. **Validation:** Validate request อย่างไรให้ครอบคลุมและให้ feedback ที่ดี
|
||||
4. **Status Codes:** ใช้ HTTP status codes อย่างไรให้ถูกต้องและสม่ำเสมอ
|
||||
|
||||
---
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
- 🎯 **Developer Experience:** Frontend developers ต้องใช้ API ได้ง่าย
|
||||
- 🔒 **Security:** ป้องกัน Information Leakage จาก Error messages
|
||||
- 📊 **Debuggability:** ต้องหา Root cause ของ Error ได้ง่าย
|
||||
- 🌍 **Internationalization:** รองรับภาษาไทยและอังกฤษ
|
||||
- 📝 **Standards Compliance:** ใช้มาตรฐานที่เป็นที่ยอมรับ (REST, JSON:API)
|
||||
|
||||
---
|
||||
|
||||
## Considered Options
|
||||
|
||||
### Option 1: Standard REST with Custom Error Format
|
||||
|
||||
**รูปแบบ:**
|
||||
|
||||
```typescript
|
||||
// Success
|
||||
{
|
||||
"data": { ... },
|
||||
"meta": { "timestamp": "..." }
|
||||
}
|
||||
|
||||
// Error
|
||||
{
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Validation failed",
|
||||
"details": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
|
||||
- ✅ Simple และเข้าใจง่าย
|
||||
- ✅ Flexible สำหรับ Custom needs
|
||||
- ✅ ไม่ต้อง Follow spec ที่ซับซ้อน
|
||||
|
||||
**Cons:**
|
||||
|
||||
- ❌ ไม่มี Standard specification
|
||||
- ❌ ต้องสื่อสารภายในทีมให้ชัดเจน
|
||||
- ❌ อาจไม่สม่ำเสมอหากไม่ระวัง
|
||||
|
||||
### Option 2: JSON:API Specification
|
||||
|
||||
**รูปแบบ:**
|
||||
|
||||
```typescript
|
||||
{
|
||||
"data": {
|
||||
"type": "correspondences",
|
||||
"id": "1",
|
||||
"attributes": { ... },
|
||||
"relationships": { ... }
|
||||
},
|
||||
"included": [...]
|
||||
}
|
||||
```
|
||||
|
||||
**Pros:**
|
||||
|
||||
- ✅ มาตรฐานที่เป็นที่ยอมรับ
|
||||
- ✅ มี Libraries ช่วย
|
||||
- ✅ รองรับ Relationships ได้ดี
|
||||
|
||||
**Cons:**
|
||||
|
||||
- ❌ ซับซ้อนเกินความจำเป็น
|
||||
- ❌ Verbose (ข้อมูลซ้ำซ้อน)
|
||||
- ❌ Learning curve สูง
|
||||
|
||||
### Option 3: GraphQL
|
||||
|
||||
**Pros:**
|
||||
|
||||
- ✅ Client เลือกข้อมูลที่ต้องการได้
|
||||
- ✅ ลด Over-fetching/Under-fetching
|
||||
- ✅ Strong typing
|
||||
|
||||
**Cons:**
|
||||
|
||||
- ❌ Complexity สูง
|
||||
- ❌ Caching ยาก
|
||||
- ❌ ไม่เหมาะกับ Document-heavy system
|
||||
- ❌ Team ยังไม่มีประสบการณ์
|
||||
|
||||
---
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
**Chosen Option:** **Option 1 - Standard REST with Custom Error Format + NestJS Exception Filters**
|
||||
|
||||
### Rationale
|
||||
|
||||
1. **Simplicity:** ทีมคุ้นเคยกับ REST API และ NestJS มี Built-in support ที่ดี
|
||||
2. **Flexibility:** สามารถปรับแต่งตาม Business needs ได้ง่าย
|
||||
3. **Performance:** Lightweight กว่า JSON:API และ GraphQL
|
||||
4. **Team Capability:** ทีมมีประสบการณ์ REST มากกว่า GraphQL
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Success Response Format
|
||||
|
||||
```typescript
|
||||
// Single resource
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"document_number": "CORR-2024-0001",
|
||||
"subject": "...",
|
||||
...
|
||||
},
|
||||
"meta": {
|
||||
"timestamp": "2024-01-01T00:00:00Z",
|
||||
"version": "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
// Collection with pagination
|
||||
{
|
||||
"data": [
|
||||
{ "id": 1, ... },
|
||||
{ "id": 2, ... }
|
||||
],
|
||||
"meta": {
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"total": 100,
|
||||
"totalPages": 5
|
||||
},
|
||||
"timestamp": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Error Response Format
|
||||
|
||||
```typescript
|
||||
{
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Validation failed on input data",
|
||||
"statusCode": 400,
|
||||
"timestamp": "2024-01-01T00:00:00Z",
|
||||
"path": "/api/correspondences",
|
||||
"details": [
|
||||
{
|
||||
"field": "subject",
|
||||
"message": "Subject is required",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. HTTP Status Codes
|
||||
|
||||
| Status | Use Case |
|
||||
| ------------------------- | ------------------------------------------- |
|
||||
| 200 OK | Successful GET, PUT, PATCH |
|
||||
| 201 Created | Successful POST |
|
||||
| 204 No Content | Successful DELETE |
|
||||
| 400 Bad Request | Validation error, Invalid input |
|
||||
| 401 Unauthorized | Missing or invalid JWT token |
|
||||
| 403 Forbidden | Insufficient permissions (RBAC) |
|
||||
| 404 Not Found | Resource not found |
|
||||
| 409 Conflict | Duplicate resource, Business rule violation |
|
||||
| 422 Unprocessable Entity | Business logic error |
|
||||
| 429 Too Many Requests | Rate limit exceeded |
|
||||
| 500 Internal Server Error | Unexpected server error |
|
||||
|
||||
### 4. Global Exception Filter
|
||||
|
||||
```typescript
|
||||
// File: backend/src/common/filters/global-exception.filter.ts
|
||||
import {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
ArgumentsHost,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
|
||||
@Catch()
|
||||
export class GlobalExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse();
|
||||
const request = ctx.getRequest();
|
||||
|
||||
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
let code = 'INTERNAL_SERVER_ERROR';
|
||||
let message = 'An unexpected error occurred';
|
||||
let details = null;
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
status = exception.getStatus();
|
||||
const exceptionResponse = exception.getResponse();
|
||||
|
||||
if (typeof exceptionResponse === 'object') {
|
||||
code = (exceptionResponse as any).error || exception.name;
|
||||
message = (exceptionResponse as any).message || exception.message;
|
||||
details = (exceptionResponse as any).details;
|
||||
} else {
|
||||
message = exceptionResponse;
|
||||
}
|
||||
}
|
||||
|
||||
// Log error (but don't expose internal details to client)
|
||||
console.error('Exception:', exception);
|
||||
|
||||
response.status(status).json({
|
||||
error: {
|
||||
code,
|
||||
message,
|
||||
statusCode: status,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.url,
|
||||
...(details && { details }),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Custom Business Exception
|
||||
|
||||
```typescript
|
||||
// File: backend/src/common/exceptions/business.exception.ts
|
||||
export class BusinessException extends HttpException {
|
||||
constructor(message: string, code: string = 'BUSINESS_ERROR') {
|
||||
super(
|
||||
{
|
||||
error: code,
|
||||
message,
|
||||
},
|
||||
HttpStatus.UNPROCESSABLE_ENTITY
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
throw new BusinessException(
|
||||
'Cannot approve correspondence in current status',
|
||||
'INVALID_WORKFLOW_TRANSITION'
|
||||
);
|
||||
```
|
||||
|
||||
### 6. Validation Pipe Configuration
|
||||
|
||||
```typescript
|
||||
// File: backend/src/main.ts
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true, // Strip properties not in DTO
|
||||
forbidNonWhitelisted: true, // Throw error if unknown properties
|
||||
transform: true, // Auto-transform payloads to DTO instances
|
||||
transformOptions: {
|
||||
enableImplicitConversion: true,
|
||||
},
|
||||
exceptionFactory: (errors) => {
|
||||
const details = errors.map((error) => ({
|
||||
field: error.property,
|
||||
message: Object.values(error.constraints || {}).join(', '),
|
||||
value: error.value,
|
||||
}));
|
||||
|
||||
return new HttpException(
|
||||
{
|
||||
error: 'VALIDATION_ERROR',
|
||||
message: 'Validation failed',
|
||||
details,
|
||||
},
|
||||
HttpStatus.BAD_REQUEST
|
||||
);
|
||||
},
|
||||
})
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive Consequences
|
||||
|
||||
1. ✅ **Consistency:** API responses มีรูปแบบสม่ำเสมอทั้งระบบ
|
||||
2. ✅ **Developer Friendly:** Frontend developers ใช้งาน API ได้ง่าย
|
||||
3. ✅ **Debuggability:** Error messages ให้ข้อมูลเพียงพอสำหรับ Debug
|
||||
4. ✅ **Security:** ไม่เปิดเผย Internal error details ให้ Client
|
||||
5. ✅ **Maintainability:** ใช้ NestJS built-in features ทำให้ Maintain ง่าย
|
||||
|
||||
### Negative Consequences
|
||||
|
||||
1. ❌ **No Standard Spec:** ไม่ใช่ Standard เช่น JSON:API จึงต้องเขียน Documentation ชัดเจน
|
||||
2. ❌ **Manual Documentation:** ต้อง Document API response format เอง
|
||||
3. ❌ **Learning Curve:** Team members ใหม่ต้องเรียนรู้ Error code conventions
|
||||
|
||||
### Mitigation Strategies
|
||||
|
||||
- **Documentation:** ใช้ Swagger/OpenAPI เพื่อ Auto-generate API docs
|
||||
- **Code Generation:** Generate TypeScript interfaces สำหรับ Frontend จาก DTOs
|
||||
- **Error Code Registry:** มี Centralized list ของ Error codes พร้อมคำอธิบาย
|
||||
- **Testing:** เขียน Integration tests เพื่อ Validate response formats
|
||||
|
||||
---
|
||||
|
||||
## Related ADRs
|
||||
|
||||
- [ADR-005: Technology Stack](./ADR-005-technology-stack.md) - เลือกใช้ NestJS
|
||||
- [ADR-004: RBAC Implementation](./ADR-004-rbac-implementation.md) - Error 403 Forbidden
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [NestJS Exception Filters](https://docs.nestjs.com/exception-filters)
|
||||
- [HTTP Status Codes](https://httpstatuses.com/)
|
||||
- [REST API Best Practices](https://restfulapi.net/)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-12-01
|
||||
**Next Review:** 2025-06-01
|
||||
Reference in New Issue
Block a user