507 lines
20 KiB
Markdown
Executable File
507 lines
20 KiB
Markdown
Executable File
# DMS Architecture Deep Dive (Backend + Frontend)
|
||
|
||
**Project:** Document Management System (DMS) — LCBP3
|
||
**Platform:** QNAP TS‑473A (Container Station)
|
||
**Last updated:** 2025‑10‑07 (UTC+7)
|
||
|
||
---
|
||
|
||
## 0) TL;DR (Executive Summary)
|
||
|
||
* Reverse proxy (Nginx/NPM) เผยแพร่ Frontend (Next.js) และ Backend (Node.js/Express) ผ่าน HTTPS (HSTS)
|
||
* Backend เชื่อม MariaDB 10.11 (ข้อมูลหลัก DMS) และแยก n8n + Postgres 16 สำหรับ workflow
|
||
* RBAC/ABAC ถูกบังคับใช้งานใน middleware + มีชุด SQL (tables → triggers → procedures → views → seed)
|
||
* ไฟล์จริง (PDF/DWG) เก็บนอก webroot ที่ **/share/dms‑data** พร้อมมาตรฐานการตั้งชื่อ+โฟลเดอร์
|
||
* Dev/Prod แยกชัดเจนผ่าน Docker multi‑stage + docker‑compose + โฟลเดอร์ persist logs/config/certs
|
||
|
||
---
|
||
|
||
## 1) Runtime Topology & Trust Boundaries
|
||
|
||
```
|
||
Internet Clients (Browser)
|
||
│ HTTPS 443 (HSTS) [QNAP mgmt = 8443]
|
||
▼
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Reverse Proxy Layer │
|
||
│ ├─ Nginx (Alpine) or Nginx Proxy Manager (NPM) │
|
||
│ ├─ TLS (LE cert; SAN multi‑subdomain) │
|
||
│ └─ Routes: │
|
||
│ • /, /_next/* → Frontend (Next.js :3000) │
|
||
│ • /api/* → Backend (Express :3001) │
|
||
│ • /pma/* → phpMyAdmin │
|
||
│ • /n8n/* → n8n (Workflows) │
|
||
└─────────────────────────────────────────────────────┘
|
||
│ │
|
||
│ └──────────┐
|
||
▼ │
|
||
Frontend (Next.js) │
|
||
│ Cookie-based Auth (HttpOnly) │
|
||
▼ ▼
|
||
Backend (Node/Express ESM) ─────────► MariaDB 10.11
|
||
│ │
|
||
└────────────────────────────────────┘
|
||
Project data (.pdf/.dwg) @ /share/dms-data
|
||
|
||
n8n (workflows) ──► Postgres 16 (separate DB for automations)
|
||
```
|
||
|
||
**Trust Boundaries**
|
||
|
||
* Public zone: Internet ↔ Reverse proxy
|
||
* App zone: Reverse proxy ↔ FE/BE containers (internal Docker network)
|
||
* Data zone: Backend ↔ Databases (MariaDB, Postgres) + `/share/dms-data`
|
||
|
||
---
|
||
|
||
## 2) Frontend Architecture (Next.js / React)
|
||
|
||
### 2.1 Stack & Key libs
|
||
|
||
* **Next.js (App Router)**, **React**, ESM
|
||
* **Tailwind CSS**, **PostCSS**, **shadcn/ui** (components.json)
|
||
* Fetch API (credentials include) → Cookie Auth (HttpOnly)
|
||
|
||
### 2.2 Directory Layout
|
||
|
||
```
|
||
/frontend/
|
||
├─ app/
|
||
│ ├─ login/
|
||
│ ├─ dashboard/
|
||
│ ├─ users/
|
||
│ ├─ correspondences/
|
||
│ ├─ health/
|
||
│ └─ layout.tsx / page.tsx (ตาม App Router)
|
||
├─ public/
|
||
├─ Dockerfile (multi-stage: dev/prod)
|
||
├─ package.json
|
||
├─ next.config.js
|
||
└─ ...
|
||
```
|
||
|
||
### 2.3 Routing & Layouts
|
||
|
||
* **Public**: `/login`, `/health`
|
||
* **Protected**: `/dashboard`, `/users`, `/correspondences`, ... (client-side guard)
|
||
* เก็บ **middleware.ts (ของเดิม)** เพื่อหลีกเลี่ยง regression; ใช้ client‑guard + server action อย่างระมัดระวัง
|
||
|
||
### 2.4 Auth Flow (Cookie-based)
|
||
|
||
1. ผู้ใช้ submit form `/login` → `POST /api/auth/login` (Backend)
|
||
2. Backend set **HttpOnly** cookie (JWT) + `SameSite=Lax/Strict`, `Secure`
|
||
3. หน้า protected เรียก `GET /api/auth/me` เพื่อตรวจสอบสถานะ
|
||
4. หาก 401 → redirect → `/login`
|
||
|
||
> **CORS/Fetch**: เปิด `credentials: 'include'` ทุกครั้ง, ตั้ง `NEXT_PUBLIC_API_BASE` เป็น origin ของ backend ผ่าน proxy (เช่น `https://lcbp3.np-dms.work`)
|
||
|
||
### 2.5 UI/UX
|
||
|
||
* Sea‑blue palette, sidebar พับได้, card‑based KPI
|
||
* ตารางข้อมูลเตรียมรองรับ **server‑side DataTables**
|
||
* shadcn/ui: Button, Card, Badge, Tabs, Dropdown, Tooltip, Switch, etc.
|
||
|
||
### 2.6 Config & ENV
|
||
|
||
* `NEXT_PUBLIC_API_BASE` (ex: `https://lcbp3.np-dms.work`)
|
||
* Build output แยก dev/prod; ระวัง EACCES บน QNAP → ใช้ user `node` + ปรับสิทธิ์โวลุ่ม `.next/*`
|
||
|
||
### 2.7 Error Handling & Observability (FE)
|
||
|
||
* Global error boundary (app router) + toast/alert patterns
|
||
* Network layer: แยก handler สำหรับ 401/403/500 + retry/backoff ที่จำเป็น
|
||
* Metrics (optional): web‑vitals, UX timing (เก็บฝั่ง n8n หรือ simple logging)
|
||
|
||
---
|
||
|
||
## 3) Backend Architecture (Node.js ESM / Express)
|
||
|
||
### 3.1 Stack & Structure
|
||
|
||
* Node 20.x, **ESM** modules, **Express**
|
||
* `mysql2/promise`, `jsonwebtoken`, `cookie-parser`, `cors`, `helmet`, `winston/morgan`
|
||
|
||
```tree
|
||
/backend/
|
||
├─ src/
|
||
│ ├─ index.js # bootstrap server, CORS, cookies, health
|
||
│ ├─ routes/
|
||
│ │ ├─ auth.js # /api/auth/* (login, me, logout)
|
||
│ │ ├─ users.js # /api/users/*
|
||
│ │ ├─ correspondences.js # /api/correspondences/*
|
||
│ │ ├─ drawings.js # /api/drawings/*
|
||
│ │ ├─ rfas.js # /api/rfas/*
|
||
│ │ └─ transmittals.js # /api/transmittals/*
|
||
│ ├─ middleware/
|
||
│ │ ├─ authGuard.js # verify JWT from cookie
|
||
│ │ ├─ requirePermission.js# RBAC/ABAC enforcement
|
||
│ │ ├─ errorHandler.js
|
||
│ │ └─ requestLogger.js
|
||
│ ├─ db/
|
||
│ │ ├─ pool.js # createPool, sane defaults
|
||
│ │ └─ models/ # query builders (User, Drawing, ...)
|
||
│ ├─ utils/
|
||
│ │ ├─ hash.js (bcrypt/argon2)
|
||
│ │ ├─ jwt.js
|
||
│ │ ├─ pagination.js
|
||
│ │ └─ responses.js
|
||
│ └─ config/
|
||
│ └─ index.js # env, constants
|
||
├─ Dockerfile
|
||
└─ package.json
|
||
```
|
||
|
||
### 3.2 Request Lifecycle
|
||
|
||
1. `helmet` + `cors` (allow specific origin; credentials true)
|
||
2. `cookie-parser`, `json limit` (e.g., 2MB)
|
||
3. `requestLogger` → trace + response time
|
||
4. Route handler → `authGuard` (protected) → `requirePermission` (per‑route) → Controller
|
||
5. Error bubbles → `errorHandler` (JSON shape, status map)
|
||
|
||
### 3.3 Auth & RBAC/ABAC
|
||
|
||
* **JWT** ใน HttpOnly cookie; Claims: `sub` (user_id), `roles`, `exp`
|
||
* **authGuard**: ตรวจ token → แนบ `req.user`
|
||
* **requirePermission**: เช็ค permission ตามเส้นทาง/วิธี; แผนขยาย ABAC (เช่น project scope, owner, doc state)
|
||
* Roles/Permissions ถูก seed ใน SQL; มี **view เมทริกซ์** เพื่อ debug (เช่น `v_role_permission_matrix`)
|
||
|
||
**ตัวอย่าง pseudo** `requirePermission(permission)`
|
||
|
||
```js
|
||
export const requirePermission = (perm) => async (req, res, next) => {
|
||
if (!req.user) return res.status(401).json({ error: 'Unauthenticated' });
|
||
const ok = await checkPermission(req.user.user_id, perm, req.context);
|
||
if (!ok) return res.status(403).json({ error: 'Forbidden' });
|
||
return next();
|
||
};
|
||
```
|
||
|
||
### 3.4 Database Access & Pooling
|
||
|
||
* `createPool({ connectionLimit: 10~25, queueLimit: 0, waitForConnections: true })`
|
||
* ใช้ parameterized queries เสมอ; ปรับ `sql_mode` ที่จำเป็นใน `my.cnf`
|
||
|
||
### 3.5 File Storage & Secure Download
|
||
|
||
* Root: **/share/dms‑data**
|
||
* โครงโฟลเดอร์: `{module}/{yyyy}/{mm}/{entityId}/` + ชื่อไฟล์ตามมาตรฐาน (เช่น `DRW-<code>-REV-<rev>.pdf`)
|
||
* Endpoint download: ตรวจสิทธิ์ (RBAC/ABAC) → `res.sendFile()`/stream; ป้องกัน path traversal
|
||
* MIME allowlist + size limit + virus scan (optional; ภายหลัง)
|
||
|
||
### 3.6 Health & Readiness
|
||
|
||
* `GET /api/health` → `{ ok: true }`
|
||
* (optional) `/api/ready` ตรวจ DB ping + disk space (dms‑data)
|
||
|
||
### 3.7 Config & ENV (BE)
|
||
|
||
* `DB_HOST, DB_PORT, DB_USER, DB_PASS, DB_NAME`
|
||
* `JWT_SECRET, COOKIE_NAME, COOKIE_SAMESITE, COOKIE_SECURE`
|
||
* `CORS_ORIGIN, LOG_LEVEL, APP_BASE_URL`
|
||
* `FILE_ROOT=/share/dms-data`
|
||
|
||
### 3.8 Logging
|
||
|
||
* Access log (morgan) + App log (winston) → `/share/Container/dms/logs/backend/`
|
||
* รูปแบบ JSON (timestamp, level, msg, reqId) + daily rotation (logrotate/container‑side)
|
||
|
||
---
|
||
|
||
## 4) Database (MariaDB 10.11)
|
||
|
||
### 4.1 Schema Overview (ย่อ)
|
||
|
||
* **RBAC core**: `users`, `roles`, `permissions`, `user_roles`, `role_permissions`
|
||
* **Domain**: `drawings`, `contracts`, `correspondences`, `rfas`, `transmittals`, `organizations`, `projects`, ...
|
||
* **Audit**: `audit_logs` (แผนขยาย), `deleted_at` (soft delete, แผนงาน)
|
||
|
||
```
|
||
[users]──<user_roles>──[roles]──<role_permissions>──[permissions]
|
||
│
|
||
└── activities/audit_logs (future expansion)
|
||
|
||
[drawings]──<mapping>──[contracts]
|
||
[rfas]──<links>──[drawings]
|
||
[correspondences] (internal/external flag)
|
||
```
|
||
|
||
### 4.2 Init SQL Pipeline
|
||
|
||
1. `01_*_deploy_table_rbac.sql` — สร้างตารางหลักทั้งหมด + RBAC
|
||
2. `02_*_triggers.sql` — บังคับ data rules, auto‑audit fields
|
||
3. `03_*_procedures_handlers.sql` — upsert/bulk handlers (เช่น `sp_bulk_import_contract_dwg`)
|
||
4. `04_*_views.sql` — รายงาน/เมทริกซ์สิทธิ์ (`v_role_permission_matrix`, etc.)
|
||
5. `05_*_seed_data.sql` — ค่าพื้นฐาน domain (project, categories, statuses)
|
||
6. `06_*_seed_users.sql` — บัญชีเริ่มต้น (superadmin, editors, viewers)
|
||
7. `07_*_seed_contract_dwg.sql` — ข้อมูลตัวอย่างแบบสัญญา
|
||
|
||
### 4.3 Indexing & Performance
|
||
|
||
* Composite indexes ตามคอลัมน์ filter/sort (เช่น `(project_id, updated_at DESC)`)
|
||
* Full‑text index (optional) สำหรับ advanced search
|
||
* Query plan review (EXPLAIN) + เพิ่ม covering index ตามรายงาน
|
||
|
||
### 4.4 MySQL/MariaDB Config (my.cnf — แนวทาง)
|
||
|
||
```
|
||
[mysqld]
|
||
innodb_buffer_pool_size = 4G # ปรับตาม RAM/QNAP
|
||
innodb_log_file_size = 512M
|
||
innodb_flush_log_at_trx_commit = 1
|
||
max_connections = 200
|
||
sql_mode = STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
|
||
character-set-server = utf8mb4
|
||
collation-server = utf8mb4_unicode_ci
|
||
```
|
||
|
||
> ปรับค่าให้เหมาะกับ workload จริง + เฝ้าดู IO/CPU ของ QNAP
|
||
|
||
### 4.5 Backup/Restore
|
||
|
||
* Logical backup: `mysqldump --routines --triggers --single-transaction`
|
||
* Physical (snapshot QNAP) + schedule ผ่าน n8n/cron
|
||
* เก็บสำเนา off‑NAS (encrypted)
|
||
|
||
---
|
||
|
||
## 5) Reverse Proxy & TLS
|
||
|
||
### 5.1 Nginx (Alpine) — ตัวอย่าง server block
|
||
|
||
> **สำคัญ:** บนสภาพแวดล้อมนี้ ให้ใช้คนละบรรทัด:
|
||
> `listen 443 ssl;`
|
||
> `http2 on;`
|
||
> หลีกเลี่ยง `listen 443 ssl http2;`
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name lcbp3.np-dms.work;
|
||
return 301 https://$host$request_uri;
|
||
}
|
||
|
||
server {
|
||
listen 443 ssl;
|
||
http2 on;
|
||
server_name lcbp3.np-dms.work;
|
||
|
||
ssl_certificate /etc/nginx/certs/fullchain.pem;
|
||
ssl_certificate_key /etc/nginx/certs/privkey.pem;
|
||
add_header Strict-Transport-Security "max-age=63072000; preload" always;
|
||
|
||
# Frontend
|
||
location / {
|
||
proxy_pass http://frontend:3000;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
|
||
# Next.js static
|
||
location /_next/ {
|
||
proxy_pass http://frontend:3000;
|
||
}
|
||
|
||
# Backend API
|
||
location /api/ {
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Connection "";
|
||
proxy_pass http://backend:3001;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_set_header X-Forwarded-Proto $scheme;
|
||
}
|
||
|
||
# phpMyAdmin (sub-path)
|
||
location /pma/ {
|
||
proxy_pass http://phpmyadmin:80/;
|
||
}
|
||
|
||
# n8n
|
||
location /n8n/ {
|
||
proxy_pass http://n8n:5678/;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 Nginx Proxy Manager (NPM) — Tips
|
||
|
||
* ระวังอย่าใส่ `proxy_http_version` ซ้ำซ้อน (duplicate directive) ใน Advanced
|
||
* ถ้าต้องแก้ไฟล์ด้านใน NPM → ระวังไฟล์ใน `/data/nginx/proxy_host/*.conf`
|
||
* จัดการ certificate / SAN หลาย sub‑domain ใน UI แต่ mainten ดีเรื่อง symlink/renew
|
||
|
||
### 5.3 TLS & Certificates
|
||
|
||
* Let’s Encrypt (HTTP‑01 webroot/standalone) + HSTS
|
||
* QNAP mgmt เปลี่ยนเป็น 8443 → พอร์ต 443 public ว่างสำหรับ Nginx/NPM
|
||
|
||
---
|
||
|
||
## 6) Docker Compose Topology
|
||
|
||
### 6.1 Services (สรุป)
|
||
|
||
* `frontend` (Next.js) :3000
|
||
* `backend` (Express) :3001
|
||
* `mariadb` (10.11) :3306 (internal)
|
||
* `phpmyadmin` :80 (internal)
|
||
* `nginx` or `npm` :80/443 (published)
|
||
* `n8n` :5678 (internal)
|
||
* `postgres_n8n` (16-alpine)
|
||
* `pgadmin4`
|
||
|
||
### 6.2 Volumes & Paths
|
||
|
||
```
|
||
/share/Container/dms/
|
||
├─ mariadb/data
|
||
├─ mariadb/init/*.sql
|
||
├─ backend/ (code)
|
||
├─ frontend/ (code)
|
||
├─ phpmyadmin/{sessions,tmp,config.user.inc.php}
|
||
├─ nginx/{nginx.conf,dms.conf,certs/}
|
||
├─ n8n, n8n-postgres, n8n-cache
|
||
└─ logs/{backend,frontend,nginx,pgadmin,phpmyadmin,postgres_n8n}
|
||
/share/dms-data (pdf/dwg storage)
|
||
```
|
||
|
||
### 6.3 Healthchecks (suggested)
|
||
|
||
* **backend**: curl `http://localhost:3001/api/health`
|
||
* **frontend**: curl `/health` (simple JSON)
|
||
* **mariadb**: `mysqladmin ping` with credentials
|
||
* **nginx**: `nginx -t` at startup
|
||
|
||
### 6.4 Security Hardening
|
||
|
||
* รัน container ด้วย user non‑root (`user: node` สำหรับ FE/BE)
|
||
* จำกัด capabilities; read‑only FS (ยกเว้นโวลุ่มจำเป็น)
|
||
* เฉพาะ backend เมานต์ `/share/dms-data`
|
||
|
||
---
|
||
|
||
## 7) Observability, Ops, and Troubleshooting
|
||
|
||
### 7.1 Logs
|
||
|
||
* Frontend → `/logs/frontend/*`
|
||
* Backend → `/logs/backend/*` (app/access/error)
|
||
* Nginx/NPM → `/logs/nginx/*`
|
||
* MariaDB → default datadir log + slow query (เปิดใน my.cnf หากต้องการ)
|
||
|
||
### 7.2 Common Issues & Playbooks
|
||
|
||
* **401 Unauthenticated**: ตรวจ `authGuard` → JWT cookie มี/หมดอายุ → เวลา server/FE sync → CORS `credentials: true`
|
||
* **EACCES Next.js**: สิทธิ์ `.next/*` + run as `node`, โวลุ่ม map ถูก user:group
|
||
* **NPM duplicate directive**: ลบซ้ำ `proxy_http_version` ใน Advanced / ตรวจ `proxy_host/*.conf`
|
||
* **LE cert path/symlink**: ตรวจ `/etc/letsencrypt/live/npm-*` symlink ชี้ถูก
|
||
* **DB field not found**: ตรวจ schema vs code (migration/init SQL) → sync ให้ตรง
|
||
|
||
### 7.3 Performance Guides
|
||
|
||
* **Backend**: keep‑alive, gzip/deflate at proxy, pool 10–25, paginate, avoid N+1
|
||
* **Frontend**: prefetch critical routes, cache static, image optimization
|
||
* **DB**: เพิ่ม index จุด filter, analyze query (EXPLAIN), ปรับ buffer pool
|
||
|
||
---
|
||
|
||
## 8) Security & Compliance
|
||
|
||
* **HTTPS only** + HSTS (preload)
|
||
* **CORS**: allow list เฉพาะ FE origin; `Access-Control-Allow-Credentials: true`
|
||
* **Cookie**: HttpOnly, Secure, SameSite=Lax/Strict
|
||
* **Input Validation**: celebrate/zod (optional) + sanitize
|
||
* **Rate limiting**: per IP/route (optional)
|
||
* **AuditLog**: วางแผนเพิ่ม ครอบคลุม CRUD + mapping (actor, action, entity, before/after)
|
||
* **Backups**: DB + `/share/dms-data` + config (encrypted off‑NAS)
|
||
|
||
---
|
||
|
||
## 9) Backlog → Architecture Mapping
|
||
|
||
1. **RBAC Enforcement ครบ** → เติม `requirePermission` ทุก route + test matrix ผ่าน view
|
||
2. **AuditLog ครบ CRUD/Mapping** → trigger + table `audit_logs` + BE hook
|
||
3. **Upload/Download จริงของ Drawing Revisions** → BE endpoints + virus scan (optional)
|
||
4. **Dashboard KPI** → BE summary endpoints + FE cards/charts
|
||
5. **Server‑side DataTables** → paging/sort/filter + indexesรองรับ
|
||
6. **รายงาน Export CSV/Excel/PDF** → BE export endpoints + FE buttons
|
||
7. **Soft delete** (`deleted_at`) → BE filter default scope + restore endpoint
|
||
8. **Validation เข้ม** → celebrate/zod schema + consistent error shape
|
||
9. **Indexing/Perf** → slow query log + EXPLAIN review
|
||
10. **Job/Cron Deadline Alerts** → n8n schedule + SMTP
|
||
|
||
---
|
||
|
||
## 10) Port & ENV Matrix (Quick Ref)
|
||
|
||
| Component | Ports | Key ENV |
|
||
| --------- | --------------- | ------------------------------------------------ |
|
||
| Nginx/NPM | 80/443 (public) | SSL paths, HSTS |
|
||
| Frontend | 3000 (internal) | `NEXT_PUBLIC_API_BASE` |
|
||
| Backend | 3001 (internal) | `DB_*`, `JWT_SECRET`, `CORS_ORIGIN`, `FILE_ROOT` |
|
||
| MariaDB | 3306 (internal) | `MY_CNF`, credentials |
|
||
| n8n | 5678 (internal) | `N8N_*`, webhook URL under `/n8n/` |
|
||
| Postgres | 5432 (internal) | n8n DB |
|
||
|
||
**QNAP mgmt**: 8443 (already moved)
|
||
|
||
---
|
||
|
||
## 11) Sample Snippets
|
||
|
||
### 11.1 Backend CORS (credentials)
|
||
|
||
```js
|
||
app.use(cors({
|
||
origin: ['https://lcbp3.np-dms.work'],
|
||
credentials: true,
|
||
}));
|
||
```
|
||
|
||
### 11.2 Secure Download (guarded)
|
||
|
||
```js
|
||
router.get('/files/:module/:id/:filename', authGuard, requirePermission('file.read'), async (req, res) => {
|
||
const { module, id, filename } = req.params;
|
||
// 1) ABAC: verify user can access this module/entity
|
||
const ok = await canReadFile(req.user.user_id, module, id);
|
||
if (!ok) return res.status(403).json({ error: 'Forbidden' });
|
||
|
||
const abs = path.join(FILE_ROOT, module, id, filename);
|
||
if (!abs.startsWith(FILE_ROOT)) return res.status(400).json({ error: 'Bad path' });
|
||
return res.sendFile(abs);
|
||
});
|
||
```
|
||
|
||
### 11.3 Healthcheck
|
||
|
||
```js
|
||
router.get('/health', (req, res) => res.json({ ok: true }));
|
||
```
|
||
|
||
---
|
||
|
||
## 12) Deployment Workflow (Suggested)
|
||
|
||
1. Git (Gitea) branch strategy `feature/*` → PR → main
|
||
2. Build images (dev/prod) via Dockerfile multi‑stage; pin Node/MariaDB versions
|
||
3. `docker compose up -d --build` จาก `/share/Container/dms`
|
||
4. Validate: `/health`, `/api/health`, login roundtrip
|
||
5. Monitor logs + baseline perf; run SQL smoke tests (views/triggers/procs)
|
||
|
||
---
|
||
|
||
## 13) Appendix
|
||
|
||
* **Naming conventions**: snake_case DB, camelCase JS
|
||
* **Timezones**: store UTC in DB; display in app TZ (+07:00)
|
||
* **Character set**: UTF‑8 (`utf8mb4_unicode_ci`)
|
||
* **Large file policy**: size limit (e.g., 50–200MB), allowlist extensions
|
||
* **Retention**: archive strategy for old revisions (optional)
|
||
|
||
---
|
||
|
||
> หากต้องการ เวอร์ชัน **README.md พร้อมโค้ดตัวอย่าง compose/nginx** จัดรูปแบบให้นำไปวางใน repo ได้ทันที แจ้งได้เลยว่าจะให้แตกไฟล์เป็น `/docs/Architecture.md` + `/nginx/dms.conf` + `/docker-compose.yml` template หรือรูปแบบอื่นที่สะดวกต่อการใช้งานของทีม
|