11.4 · vtrc-api REST surface
บทนี้รวบรวม REST endpoints ทั้ง 30 ตัวของ vtrc-api ที่อยู่ใน routes.js (~1300 บรรทัด, 68 KB) พร้อมระบุ auth, pattern และ use case
ภาพรวม
| ตัวเลข | ค่า |
|---|---|
| ไฟล์ | routes.js (~1300 บรรทัด) |
| รวม endpoints | 30 |
| GET | 21 |
| POST | 6 |
| PUT | 1 |
| DELETE / PATCH | 0 |
| Static (USE) | 2 |
| Webhook (ALL) | 2 |
ทุก endpoint อยู่ในไฟล์ routes.js ไฟล์เดียว — ไม่มี Express Router sub-mounting
รายการ endpoints ทั้งหมด
กลุ่ม PDF — Payslip, Tax, Salary cert
| # | Method | Path | บรรทัด | Auth | ความหมาย |
|---|---|---|---|---|---|
| 1 | GET | /readfile/prs/{year}/{month}/{pid}/{empcode}/{device}/{timestamp}/{hash} | 51 | signed hash + 1h timeout | Payslip PDF (qpdf-encrypted) |
| 2 | GET | /tax/{year}/{month}/{pid}/{empcode}/{device}/{timestamp}/{hash} | 101 | signed hash | Tax PDF |
| 3 | GET | /tax6month/... | 205 | signed hash | 6-month tax PDF |
| 4 | GET | /taxyears/... | 255 | signed hash | Annual tax PDF |
| 5 | GET | /salarycert/{pid}/{empcode}/{device}/{profileKey}/{documentType}/{timestamp} | 305 | signed hash | Salary certificate PDF |
| 6 | GET | /failpayment/{wdExportId} | 148 | — | Failed-payment PDF |
Pattern: signed URL — hash เป็น HMAC ที่ server sign ไว้ มี timestamp กัน replay (timeout 1 ชั่วโมง)
กลุ่ม File — อัปโหลด/ดาวน์โหลด
| # | Method | Path | บรรทัด | Auth | ความหมาย |
|---|---|---|---|---|---|
| 7 | GET | /file/{id} | 389 | apiKey (prod keys only) | File render inline |
| 8 | GET | /file/{id}/download | 464 | — | File download (attachment) |
| 9 | POST | /upload | 501 | apiKey | Generic upload (multer single) |
| 10 | POST | /hospitalslip/upload | 525 | apiKey + JWT | Hospital slip (multer 100) |
| 11 | POST | /hospitaldocument/upload | 580 | apiKey + JWT | Hospital documents |
| 12 | POST | /leave/attachment/draft | 633 | — | Leave attach draft (local) |
| 13 | POST | /leave/attachment | 677 | apiKey + JWT | Leave attach → cu-central-api |
| 14 | PUT | /leave/attachment | 729 | apiKey + JWT | Leave attach update |
กลุ่ม Export — Excel, ZIP
| # | Method | Path | บรรทัด | Auth | ความหมาย |
|---|---|---|---|---|---|
| 15 | GET | /download/excel/{table} | 497 | — | Excel download |
| 16 | GET | /wdHospital/export | 772 | — | Export ZIP (KBank + SmartCredit + FMIS) |
| 17 | POST | /wdHospital/export/wdpaid | 835 | — | Paid welfare report ZIP |
| 18 | GET | /wdHospital/report/sumorg | 876 | — | Summary report JSON |
| 19 | GET | /fund/ratechange/summary | 1065 | — | Fund rate-change PDF |
| 20 | GET | /fund/benefitperson/summary | 1142 | — | Fund beneficiary PDF |
| 21 | GET | /fund/application/excel | 1219 | — | Fund application Excel ZIP (Tisco) |
| 22 | POST | /fund/ratechange/excel | 1233 | — | Fund rate-change Excel ZIP |
| 23 | GET | /fund/benefitperson/form | 1280 | — | Beneficiary form PDF |
กลุ่ม Conicle (LMS integration)
| # | Method | Path | บรรทัด | Auth | ความหมาย |
|---|---|---|---|---|---|
| 24 | GET | /conicle/downloadFile/{id} | 902 | — | Conicle file download |
| 25 | GET | /conicle/getFileFromUrl | 920 | — | Proxy fetch from Conicle session |
กลุ่ม Misc
| # | Method | Path | บรรทัด | Auth | ความหมาย |
|---|---|---|---|---|---|
| 26 | ALL | /webdeployment | 975 | — | Trigger vtrc-update-uat-loadtest shell script |
| 27 | USE | /resources | 986 | — | Static storage/prs |
| 28 | USE | / | 988 | — | Static storage |
| 29 | ALL | /ais-smsgw | 990 | — | AIS SMS gateway webhook |
| 30 | GET | /summary/{empCode}/summaryWelfareReport | 995 | — | Welfare approver PDF |
Middleware
ฝั่ง server (global)
// index.js:92-93
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }))Multipart (multer)
// routes.js:43-45
const upload = multer({
storage: multer.memoryStorage()
})
const cpUpload = upload.fields([{ name: 'attachFiles', maxCount: 100 }])multer.memoryStorage() เก็บไฟล์ใน RAM — ระวัง memory เมื่ออัปโหลดไฟล์ใหญ่พร้อมกัน
GraphQL multipart
// index.js:25
app.use(API_SERVICE_PATH, graphqlUploadExpress({ maxFileSize: 20_000_000, maxFiles: 100 }), server)สำหรับ GraphQL Upload scalar (ดู บท 10.8)
Auth บน REST
ไม่มี middleware กลาง — แต่ละ route ตรวจ auth เอง inline:
แบบที่ 1 — signed hash (PDF)
// routes.js:51-100 (ตัวอย่าง concept)
app.get('/readfile/prs/:year/:month/:pid/:empcode/:device/:timestamp/:hash', (req, res) => {
const { timestamp, hash } = req.params
// ตรวจ timeout 1 ชั่วโมง
const age = Date.now() - parseInt(timestamp)
if (age > 3600 * 1000) return res.sendStatus(404)
// ตรวจ HMAC
const expectedHash = hmac(timestamp + empcode + pid, SECRET)
if (hash !== expectedHash) return res.sendStatus(404)
// สร้าง PDF + ส่งกลับ
})แบบที่ 2 — apiKey + JWT (multer routes)
// routes.js:525-580 (ตัวอย่าง concept)
app.post('/hospitalslip/upload', cpUpload, async (req, res) => {
const apiKey = req.headers.apikey
const token = (req.headers.authorization || '').split(' ')[1]
// ตรวจ apiKey
const mapKey = allowedKeys.filter(value => value.key === apiKey)
if (mapKey.length === 0) return res.sendStatus(404)
// ตรวจ JWT
const payload = getPayLoad(token)
if (!payload) return res.sendStatus(404)
// ประมวลผลไฟล์
})แบบที่ 3 — public (ไม่ต้อง auth)
// routes.js:148 (failpayment)
app.get('/failpayment/:wdExportId', async (req, res) => {
// ไม่ตรวจ auth
// ดึง PDF และส่งกลับ
})getPayLoad(token) helper
// lib/controllers/auth/auth.js:23-41
export const getPayLoad = (token) => {
try {
const decoded = jwt.verify(token, JWT_SECRET)
return decoded
} catch (err) {
return null
}
}Response format — ไม่มี envelope
REST ไม่มี wrapper { data, status, message } — response ขึ้นอยู่กับ endpoint:
| Endpoint | Response |
|---|---|
Content-Type: application/pdf + binary | |
| File render | Content-Type: <mime> + Content-Disposition: inline |
| File download | Content-Type: <mime> + Content-Disposition: attachment |
| Export ZIP | Content-Type: application/zip + binary |
| Export Excel | Content-Type: application/vnd.ms-excel + binary |
| JSON (summary report) | { result: [...] } |
| Upload success | { message: 'success' } |
| Error | sendStatus(404) / sendStatus(400) / sendStatus(500) |
HTTP status codes
| Status | เมื่อไร |
|---|---|
| 200 | สำเร็จ (default) |
| 400 | validation/render error |
| 404 | auth fail / file missing / generic catch (ใช้บ่อยเป็น "auth failed") |
| 500 | archiver/conicle error |
ความผิดปกติ: ใช้ 404 เป็น "auth failed" แทน 401 — ทำให้ debug สับสน (คิดว่าไฟล์ไม่มี แต่จริง ๆ token หมดอายุ)
Path convention
ทุก path อยู่ใต้ API_PREFIX_PATH env:
- Production:
''(ว่าง) → path คือ/file/:id,/upload, ... - UAT:
''(ว่างเช่นกัน) → path คือ/file/:id, ...
ดังนั้น full URL คือ https://vtrcapi.redcross.or.th/file/abc123 (prod)
Static file serving
// routes.js:986-988
app.use('/resources', express.static(PWD + '/storage/prs'))
app.use('/', express.static(PWD + '/storage'))หมายความว่าไฟล์ใน storage/ สามารถเข้าถึงได้โดยตรงผ่าน HTTP — เช่น https://vtrcapi.redcross.or.th/abc123/file.pdf
ข้อระวัง: ถ้ามีไฟล์ sensitive ใน storage/ อาจรั่ว
Webhook — /ais-smsgw
// routes.js:990 (concept)
app.all('/ais-smsgw', (req, res) => {
// AIS ส่ง SMS delivery report มาที่นี่
// ไม่ต้อง auth (AIS อาจไม่ส่ง header)
})Shell trigger — /webdeployment
// routes.js:975-984
app.all('/webdeployment', (req, res) => {
exec('vtrc-update-uat-loadtest', (err, stdout, stderr) => {
// trigger deploy script
})
})ข้อระวัง: เป็น RCE (Remote Code Execution) endpoint — ถ้าไม่ auth และเปิดสู่ internet ผู้โจมตีสามารถ trigger deploy ได้
ข้อระวังเมื่อแก้ REST routes
1. ไฟล์ routes.js ใหญ่มาก
- 1300 บรรทัดในไฟล์เดียว — grep หา path แทน scroll
- แยกไฟล์ไม่ได้เพราะเป็น default export function
2. Auth inline ไม่สม่ำเสมอ
- บาง route ตรวจ apiKey + JWT
- บาง route ตรวจแค่ apiKey
- บาง route ไม่ตรวจเลย
→ ตอนเพิ่ม endpoint ใหม่ ตัดสินใจให้ชัดเจนว่า auth อย่างไร และทำให้สอดคล้องกับ pattern ของกลุ่มนั้น
3. ไม่มี rate limiting
- ไม่มี
express-rate-limitหรือ custom throttle - ระวัง endpoint ที่ Generate PDF หนัก — อาจถูก DDoS
4. ไม่มี helmet / compression
- ไม่มี security headers
- ไม่มี gzip/brotli
เปรียบเทียบกับ GraphQL
| ด้าน | GraphQL | REST |
|---|---|---|
| จำนวน | 265 ops | 30 endpoints |
| Auth | @auth directive | inline check |
| Response | structured (data, errors) | raw (binary, JSON) |
| Validation | GraphQL schema | manual |
| Error envelope | { errors: [{ extensions: { code } }] } | sendStatus(404) |
| Use case | query/mutation ทั่วไป | binary (file, PDF, export) |
เคล็ดลับตอน debug REST
ถ้า PDF ไม่แสดง
- ตรวจ URL — มี timestamp + hash ที่ถูกต้องไหม
- ตรวจว่า timestamp ภายใน 1 ชั่วโมง
- ถ้า hash ผิด → server ส่ง 404
ถ้า upload ล้มเหลว
- ตรวจ
apiKey+Authorizationheader - ตรวจ
Content-Type: multipart/form-data - ตรวจขนาดไฟล์ (multer memoryStorage — ถ้าใหญ่เกิน RAM จะ crash)
ถ้าได้ 404 ทั้ง ๆ ที่ path ถูก
- มักเป็น auth failed (ไม่ใช่ file missing)
- ตรวจ apiKey + JWT