2way-line-service — Core Pipeline
บทนี้อธิบาย lifecycle ของ HTTP request ใน 2way-line-service (branch: master) โดยเน้น LINE rich menu flow (nextPage / previousPage) และ push/multicast
ดูแผนที่ที่ 01-repository-map และ contract กับ 2way-api ที่ 03-persistence-integrations
Request lifecycle ภาพรวม
Caller (เช่น 2way-api / client อื่น)
│
▼
NestJS (main.ts)
│ bodyParser 50mb
│ CORS origin=*
▼
Global prefix: `${BASE_PATH}api/v1`
│ BASE_PATH ว่างใน .env.example → api/v1/...
▼
JwtAuthGuard (global)
│ Line handlers ทั้งหมดมี @Public() → ผ่านทันที
▼
ValidationPipe (whitelist + forbidNonWhitelisted)
▼
LineController
▼
LineService (axios → api.line.me)
▼
boolean true/false หรือ BadRequestExceptionหลักฐาน bootstrap: main.ts:24-28
หลักฐาน @Public(): line.controller.ts:13,19,25,31
Trace A — Push message เดี่ยว
POST /api/v1/Line
Body: LinePushMsgDto { replyToken, messages }
│
▼
LineService.pushMassage
│ JSON: { to: replyToken, messages: [{ type:'text', text: messages }] }
│ POST https://api.line.me/v2/bot/message/push
│ Authorization: Bearer ${lineToken}
▼
status == 200 → true / else falseแหล่ง: line.service.ts:18-45, controller line.controller.ts:13-16
จุดสำคัญ:
- ฟิลด์ชื่อ
replyTokenแต่ถูกส่งเป็น LINE Messaging API fieldto(user id) — ไม่ใช่ reply token ของ webhook (line.service.ts:20-22) - ข้อความถูกห่อเป็น text message เดียวเสมอ
- Token จาก
config.get('lineToken')← envLINE_AUTH_TOKEN(configuration.ts:11)
Trace B — Multicast
POST /api/v1/Line/multiCast
Body: LinePushMsgMultiDto { to: string[], messages }
│
▼
POST https://api.line.me/v2/bot/message/multicast
│ { to, messages: [{ type:'text', text: messages }] }
▼
status == 200 → true / else falseแหล่ง: line.service.ts:48-75, controller line.controller.ts:19-22
LINE multicast มีขีดจำกัดจำนวน user ต่อครั้งตามเอกสาร LINE — โค้ดนี้ไม่ enforce ขีดจำกัดใน service
Trace C — Rich menu nextPage (ธีมหลักของ service)
POST /api/v1/Line/nextPage
Body: NextPageDto { userId, isChula? }
│
▼
เลือก richMenuId คงที่:
│ default (isChula truthy / ไม่เข้า if):
│ richmenu-4a2198498bb2a45c9f8e83f34e1d8b27
│ if (!isChula):
│ richmenu-4b7b5788ffc3663761757bf80ffead54
│ (บรรทัดที่ comment ไว้เป็น id อื่น)
▼
POST https://api.line.me/v2/bot/user/{userId}/richmenu/{richMenuId}
│ Authorization: Bearer ${lineToken}
▼
status === 200 → true / else falseแหล่ง: line.service.ts:78-108, DTO dto/line.dto.ts:24-32, controller line.controller.ts:25-28
let richMenu = 'richmenu-4a2198498bb2a45c9f8e83f34e1d8b27';
if (!isChula) {
// richMenu = 'richmenu-4eb43ff2ee5b7cee86378e9e9bf1d0ae';
richMenu = 'richmenu-4b7b5788ffc3663761757bf80ffead54';
}แหล่ง: line.service.ts:82-86
ความหมายเชิงธุรกิจจากโค้ด (ไม่ invent นอกหลักฐาน):
- มีอย่างน้อย สอง rich menu id ที่ hardcode ในซอร์ส
- การเลือกขึ้นกับ boolean
isChula - ไม่มี env สำหรับ override rich menu id
- id เก่าถูก comment เหลือเป็นหลักฐานว่าเคยเปลี่ยนเมนู
พฤติกรรมของ isChula เมื่อไม่ส่งใน body: เป็น undefined → !isChula เป็น true → เข้ากิ่ง non-Chula และใช้ richmenu-4b7b5788... (line.service.ts:83-85)
Trace D — Rich menu previousPage
DELETE /api/v1/Line/previousPage
Body: PreviousPageDto { userId }
│
▼
DELETE https://api.line.me/v2/bot/user/{userId}/richmenu
│ (unlink rich menu ของ user — ไม่ระบุ richMenuId)
▼
status === 200 → true / else falseแหล่ง: line.service.ts:111-134, controller line.controller.ts:31-34
ตาม LINE Rich Menu API — DELETE ที่ path นี้เป็นการ unlink default rich menu ของ user (พฤติกรรมของ LINE platform; โค้ดแค่เรียก URL นี้)
ไม่ได้อ่าน/อัปเดตตาราง userLine.richMenuID ใน service นี้ — ไม่มี DB
Error handling
ทุก method ใน LineService ห่อด้วย try/catch:
- log ผ่าน
I3GatewayLogger - throw
BadRequestException(error)(line.service.ts:42-44,72-74,105-107,131-133)
ไม่ได้ map LINE error status เป็น HTTP status ของ Nest โดยละเอียด — axios error ทั้งก้อนถูกโยนเข้า BadRequest
Global HttpExceptionFilter อยู่ที่ main.ts:26 — รูปแบบ response ของ filter ไม่ขยายในบทนี้ (อ่านไฟล์ libs/exception/http-exception.filter.ts เมื่อต้องการ)
Auth surface ของ pipeline นี้
แม้มี global JWT:
| Endpoint | ต้องมี Bearer จริง? |
|---|---|
POST /Line | ไม่ (@Public) |
POST /Line/multiCast | ไม่ |
POST /Line/nextPage | ไม่ |
DELETE /Line/previousPage | ไม่ |
ผลกระทบ: ใครก็ตามที่เข้าถึง host:port ของ service ได้สามารถ push ข้อความหรือสลับ rich menu ของ userId ใดก็ได้ที่รู้ — SEC-2WAYLINE-01
ลำดับ rich menu จากมุมผู้ใช้ (ที่พิสูจน์ได้จากโค้ด)
(สถานะเริ่มต้นบน LINE OA — UNVERIFIED จากโค้ดนี้)
│
│ caller ส่ง nextPage { userId, isChula }
▼
ผูก rich menu id ตามกิ่ง Chula / non-Chula
│
│ caller ส่ง previousPage { userId }
▼
unlink rich menu ของ userไม่มี state machine ใน DB ของ service นี้ — สถานะเมนูอยู่ที่ LINE platform
สิ่งที่อยู่นอกขอบเขตบทนี้
- Entity ค้าง / env keys → 01-repository-map
- Contract กับ
2way-api(path case, DTO field names) → 03-persistence-integrations - Debt IDs → 04-scorecard