Skip to content

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 field to (user id) — ไม่ใช่ reply token ของ webhook (line.service.ts:20-22)
  • ข้อความถูกห่อเป็น text message เดียวเสมอ
  • Token จาก config.get('lineToken') ← env LINE_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

typescript
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


สิ่งที่อยู่นอกขอบเขตบทนี้