Skip to content

11.4 · vtrc-api REST surface

บทนี้รวบรวม REST endpoints ทั้ง 30 ตัวของ vtrc-api ที่อยู่ใน routes.js (~1300 บรรทัด, 68 KB) พร้อมระบุ auth, pattern และ use case


ภาพรวม

ตัวเลขค่า
ไฟล์routes.js (~1300 บรรทัด)
รวม endpoints30
GET21
POST6
PUT1
DELETE / PATCH0
Static (USE)2
Webhook (ALL)2

ทุก endpoint อยู่ในไฟล์ routes.js ไฟล์เดียว — ไม่มี Express Router sub-mounting


รายการ endpoints ทั้งหมด

กลุ่ม PDF — Payslip, Tax, Salary cert

#MethodPathบรรทัดAuthความหมาย
1GET/readfile/prs/{year}/{month}/{pid}/{empcode}/{device}/{timestamp}/{hash}51signed hash + 1h timeoutPayslip PDF (qpdf-encrypted)
2GET/tax/{year}/{month}/{pid}/{empcode}/{device}/{timestamp}/{hash}101signed hashTax PDF
3GET/tax6month/...205signed hash6-month tax PDF
4GET/taxyears/...255signed hashAnnual tax PDF
5GET/salarycert/{pid}/{empcode}/{device}/{profileKey}/{documentType}/{timestamp}305signed hashSalary certificate PDF
6GET/failpayment/{wdExportId}148Failed-payment PDF

Pattern: signed URL — hash เป็น HMAC ที่ server sign ไว้ มี timestamp กัน replay (timeout 1 ชั่วโมง)

กลุ่ม File — อัปโหลด/ดาวน์โหลด

#MethodPathบรรทัดAuthความหมาย
7GET/file/{id}389apiKey (prod keys only)File render inline
8GET/file/{id}/download464File download (attachment)
9POST/upload501apiKeyGeneric upload (multer single)
10POST/hospitalslip/upload525apiKey + JWTHospital slip (multer 100)
11POST/hospitaldocument/upload580apiKey + JWTHospital documents
12POST/leave/attachment/draft633Leave attach draft (local)
13POST/leave/attachment677apiKey + JWTLeave attach → cu-central-api
14PUT/leave/attachment729apiKey + JWTLeave attach update

กลุ่ม Export — Excel, ZIP

#MethodPathบรรทัดAuthความหมาย
15GET/download/excel/{table}497Excel download
16GET/wdHospital/export772Export ZIP (KBank + SmartCredit + FMIS)
17POST/wdHospital/export/wdpaid835Paid welfare report ZIP
18GET/wdHospital/report/sumorg876Summary report JSON
19GET/fund/ratechange/summary1065Fund rate-change PDF
20GET/fund/benefitperson/summary1142Fund beneficiary PDF
21GET/fund/application/excel1219Fund application Excel ZIP (Tisco)
22POST/fund/ratechange/excel1233Fund rate-change Excel ZIP
23GET/fund/benefitperson/form1280Beneficiary form PDF

กลุ่ม Conicle (LMS integration)

#MethodPathบรรทัดAuthความหมาย
24GET/conicle/downloadFile/{id}902Conicle file download
25GET/conicle/getFileFromUrl920Proxy fetch from Conicle session

กลุ่ม Misc

#MethodPathบรรทัดAuthความหมาย
26ALL/webdeployment975Trigger vtrc-update-uat-loadtest shell script
27USE/resources986Static storage/prs
28USE/988Static storage
29ALL/ais-smsgw990AIS SMS gateway webhook
30GET/summary/{empCode}/summaryWelfareReport995Welfare approver PDF

Middleware

ฝั่ง server (global)

js
// index.js:92-93
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }))

Multipart (multer)

js
// routes.js:43-45
const upload = multer({
  storage: multer.memoryStorage()
})
const cpUpload = upload.fields([{ name: 'attachFiles', maxCount: 100 }])

multer.memoryStorage() เก็บไฟล์ใน RAM — ระวัง memory เมื่ออัปโหลดไฟล์ใหญ่พร้อมกัน

GraphQL multipart

js
// 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)

js
// 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)

js
// 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)

js
// routes.js:148 (failpayment)
app.get('/failpayment/:wdExportId', async (req, res) => {
  // ไม่ตรวจ auth
  // ดึง PDF และส่งกลับ
})

getPayLoad(token) helper

js
// 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:

EndpointResponse
PDFContent-Type: application/pdf + binary
File renderContent-Type: <mime> + Content-Disposition: inline
File downloadContent-Type: <mime> + Content-Disposition: attachment
Export ZIPContent-Type: application/zip + binary
Export ExcelContent-Type: application/vnd.ms-excel + binary
JSON (summary report){ result: [...] }
Upload success{ message: 'success' }
ErrorsendStatus(404) / sendStatus(400) / sendStatus(500)

HTTP status codes

Statusเมื่อไร
200สำเร็จ (default)
400validation/render error
404auth fail / file missing / generic catch (ใช้บ่อยเป็น "auth failed")
500archiver/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

js
// 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

js
// routes.js:990 (concept)
app.all('/ais-smsgw', (req, res) => {
  // AIS ส่ง SMS delivery report มาที่นี่
  // ไม่ต้อง auth (AIS อาจไม่ส่ง header)
})

Shell trigger — /webdeployment

js
// 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

ด้านGraphQLREST
จำนวน265 ops30 endpoints
Auth@auth directiveinline check
Responsestructured (data, errors)raw (binary, JSON)
ValidationGraphQL schemamanual
Error envelope{ errors: [{ extensions: { code } }] }sendStatus(404)
Use casequery/mutation ทั่วไปbinary (file, PDF, export)

เคล็ดลับตอน debug REST

ถ้า PDF ไม่แสดง

  1. ตรวจ URL — มี timestamp + hash ที่ถูกต้องไหม
  2. ตรวจว่า timestamp ภายใน 1 ชั่วโมง
  3. ถ้า hash ผิด → server ส่ง 404

ถ้า upload ล้มเหลว

  1. ตรวจ apiKey + Authorization header
  2. ตรวจ Content-Type: multipart/form-data
  3. ตรวจขนาดไฟล์ (multer memoryStorage — ถ้าใหญ่เกิน RAM จะ crash)

ถ้าได้ 404 ทั้ง ๆ ที่ path ถูก

  • มักเป็น auth failed (ไม่ใช่ file missing)
  • ตรวจ apiKey + JWT

ขั้นตอนถัดไป

ไป บท 10.5 cu-central-api GraphQL + REST