vtrc-ocr-api — Core Pipeline
บทนี้ (branch: main) แยก flow ที่ README โฆษณา กับ flow ที่โค้ดทำจริง — รวม in-memory lifecycle และจุดที่ config ไม่มีผล
Flow ที่ README ตั้งใจ vs flow จริง
README บรรยายว่าส่ง base64 image แล้วได้ข้อความจาก OCR (README.md:78-93)
Flow จริง:
Client
│ POST /api/ocr/process
│ body: { image: string, options?: object }
▼
routes/ocr.routes.js
│ validateOCRRequest → ocrController.processOCR
│ `ocr.routes.js:38`
▼
middleware/validation.middleware.js
│ image: notEmpty + isString
│ options: optional + isObject
│ ไม่ตรวจ base64 validity / MIME / ขนาดไฟล์
│ `validation.middleware.js:3-23`
▼
controllers/ocr.controller.js#processOCR
│ ดึง { image, options } → ocrService.processImage
│ `ocr.controller.js:3-17`
▼
services/ocr.service.js#processImage (ocr.service.js:6-41)
│ 1. ถ้า !image → throw { status: 400, message: 'Image is required' }
│ (ซ้ำกับ validator — กันกรณีเรียก service โดยตรง)
│ 2. generateProcessingId() → `ocr_{Date.now()}_{random36}`
│ `ocr.service.js:69-71`
│ 3. save record { id, status:'processing', createdAt, options }
│ 4. setTimeout(1000ms):
│ update → {
│ status: 'completed',
│ text: 'Sample OCR result text', ← คงที่
│ confidence: 0.95, ← คงที่
│ completedAt: ISO
│ }
│ 5. return ทันที { processingId, status:'processing', message }
▼
HTTP 200 { success: true, data: { processingId, status, message } }ไม่มี decode base64, ไม่มีเรียก OCR library, ไม่มี HTTP ออกไปยัง Vision API — ค่า text/confidence hard-code ที่ ocr.service.js:26-32 เท่านั้น
ค่า image และ options ถูกเก็บใน record ตอนสร้าง (options เท่านั้นใน object ที่ save — image ไม่ถูกเก็บ ใน model) (ocr.service.js:14-19)
Poll status
GET /api/ocr/status/:id
▼
getProcessingStatus(id) (ocr.service.js:43-51)
│ ocrModel.getProcessingRecord(id)
│ ไม่พบ → throw { status: 404, message: 'Processing record not found' }
▼
200 { success: true, data: record }สถานะขึ้นกับว่า setTimeout ยิงแล้วหรือยัง:
| เวลาหลัง POST | status ใน Map | fields พิเศษ |
|---|---|---|
| < ~1s | processing | createdAt, options |
| ≥ ~1s | completed | + text, confidence, completedAt (merge ทับ) |
หลัง process restart → Map ว่าง → ทุก id ได้ 404
History + pagination
GET /api/ocr/history?page=1&limit=10
▼
getProcessingHistory(page, limit) (ocr.service.js:53-67)
│ getAllProcessingRecords() → reverse chronological
│ slice((page-1)*limit, page*limit)
│ pagination: { page, limit, total, totalPages } หลัง parseIntController ส่ง query string ตรง ๆ (ocr.controller.js:39) — ไม่มี validation ของ page/limit
| Input ผิดปกติ | พฤติกรรมที่สังเกตได้จากโค้ด |
|---|---|
limit ไม่ใช่ตัวเลข | parseInt → NaN → .slice(NaN, NaN) → [] |
page=0 | startIndex = -limit → slice จากท้าย array (พฤติกรรมของ Array#slice) |
| ไม่ส่ง page/limit | default 1 / 10 จาก destructuring (ocr.controller.js:39) |
Config ที่ไม่มีผลต่อ pipeline
| Config field | Default | ถูกใช้ใน OCR path? | Citation |
|---|---|---|---|
ocr.maxImageSize | '10MB' | ไม่ | config.js:22 |
ocr.supportedFormats | jpg/jpeg/png/pdf | ไม่ | config.js:23 |
ocr.timeout | 30000 | ไม่ — ใช้ setTimeout(1000) คงที่ | config.js:24 vs ocr.service.js:25 |
cors.origin | '*' | ไม่ — app.use(cors()) ไม่รับ options | config.js:15-18 vs app.js:16 |
Error handling ที่ใช้จริง vs ที่ประกาศไว้
| Path | วิธีจับ error | Citation |
|---|---|---|
| OCR controllers | try/catch ในแต่ละ handler + error.status | ocr.controller.js:12-16,29-33,46-50 |
| Health controllers | try/catch แยก | health.controller.js:7-11,19-23 |
Global app.js | (err, req, res, next) inline | app.js:58-64 |
middleware/error.middleware.js | errorHandler + asyncHandler | ไม่มี import ใน app.js — dead |
Controllers ไม่ใช้ asyncHandler ดังนั้น rejected promise ที่ไม่ถูก catch ใน try จะไม่เข้า Express error middleware โดยอัตโนมัติ — แต่ทุก handler ปัจจุบันมี try/catch ครอบ await อยู่แล้ว
Response shape ไม่สม่ำเสมอ:
| Source | Success shape | Error shape |
|---|---|---|
| OCR controller | { success, data } | { success: false, error } |
| Validation MW | — | { success: false, errors: [...] } |
| Health service via controller | object ตรงจาก service (ไม่มี success) | { error, message } |
GET /health ใน app.js | { status, message, timestamp } | — |
utils/response.js (unused) | { success, message, data } | { success, message, errors? } |
Data flow กับระบบอื่นในแพลตฟอร์ม
ตรวจทั้ง repo: ไม่มี axios / fetch / http.request ไป host ภายนอก
(ไม่มี caller ใน workspace ที่ยืนยันได้)
│
▼
vtrc-ocr-api (standalone stub)
│
└── in-memory Map เท่านั้น
ไม่มี DB, ไม่มี queue, ไม่มี callback ไป service อื่นถือเป็น standalone mock — ไม่ถูกผูกเข้า legacy GraphQL หรือ NestJS microservices ในโค้ดที่ตรวจได้
Swagger coverage
docs/swagger.js กำหนด OpenAPI 3.0, server http://localhost:{port}, schemas:
| Schema | Fields | Citation |
|---|---|---|
OCRRequest | image (required string), options (object) | swagger.js:21-34 |
OCRStatusResponse | id, status enum, text, confidence, createdAt, completedAt | swagger.js:35-45 |
JSDoc ใน routes/*.js document: /api, /api/health, /api/health/detailed, /api/ocr/process, /api/ocr/status/{id}, /api/ocr/history
ไม่ document GET /health (นอก /api) และ GET / ใน OpenAPI — แต่มีใน Express