Skip to main content

โหลดโครงฟอร์ม (Form Builder)

ภาพรวม

endpoint ที่ส่ง "พิมพ์เขียว" ของฟอร์มให้หน้ากรอกฟอร์มฝั่ง client-web นำไป render ประกอบด้วยรายการคำถามทั้งหมดพร้อมชนิด field, กฎ validation, conditional logic, ธีม และค่าตั้งค่าระดับฟอร์มอย่าง requireLineLogin, oneTimeSubmission, convertToMember, profileMapping และ thankYou

นอกจากนี้ยังมี endpoint ผู้ช่วยอีก 2 ตัว คือ endpoint ที่ตรวจว่าผู้ใช้คนนี้ส่งฟอร์มไปแล้วหรือยัง (สำหรับฟอร์มที่อนุญาตให้ส่งได้ครั้งเดียว) และ stub ที่ยังไม่ทำอะไรซึ่ง port ไว้เพื่อ parity

Business Flow

GET /api/form-builder/:hash

  1. ค้นหา form_builder ที่ active ด้วย form_hash หากไม่พบจะตอบ 400 พร้อม message Bad Request (source เขียนเป็น throw new BadRequestException() โดยไม่ระบุข้อความ NestJS จึงเติม reason phrase ให้เอง)
  2. transformCover — หาก theme.cover เป็น string ที่ไม่ว่าง ให้แทนค่าด้วย public URL ของไฟล์นั้น โดยตรวจก่อนว่า object มีอยู่จริงผ่าน IsFileExist หากไม่มีไฟล์หรือไม่มี storage จะเขียนเป็น null ทั้งนี้การเข้ารหัสจะรักษาลำดับ key ของ object theme เดิมไว้และแก้เฉพาะค่า cover
  3. transformPattern — โหลด common rule ที่ active แบบ ไม่ผ่าน cache ด้วย FindAllActive แล้วสำหรับคำถามที่มี commonRuleId ตรงกับ rule ใด จะ append key commonRule ที่มีค่าเป็น rule.properties ต่อท้าย object ของคำถามนั้น (เลียนแบบ JS spread แบบ {...question, commonRule}) การจับคู่เลียนแบบ Number(rule.id) === Number(question.commonRuleId) กล่าวคือ string ที่เป็นตัวเลขจับคู่ได้ ค่าว่างให้ผลเป็น 0 และค่าที่ coerce แล้วเป็น NaN จะไม่จับคู่กับอะไรเลย
  4. คืน entity FormBuilder ทั้งก้อนเป็น JSON

POST /api/form-builder/:hash/is-submitted

  1. โหลดฟอร์มด้วย flow ด้านบน (ตอบ 400 ถ้าไม่มี) เพื่อให้ได้ line_oa_id
  2. ไม่เชื่อ body — ค่า lineUserId ที่ client ส่งมาจะถูกละทิ้งทั้งหมด ตัวตนผู้ใช้มาจากการ verify x-liff-token เท่านั้น เหตุผลคือหากเชื่อ body ใครก็สามารถ probe ได้ว่า LINE user คนใดส่งฟอร์มนี้ไปแล้ว ซึ่งจะกลายเป็น per-user oracle
  3. หาก verify ไม่ผ่านหรือไม่มี token จะคืนค่า false โดยไม่ถือเป็น error เนื่องจากการกันส่งซ้ำจริงยังทำอยู่ที่ขั้นตอน submit
  4. เมื่อผ่านแล้วจะ query ตาราง form_submission ว่ามีแถวที่ is_submitted = true ของคู่ (form_hash, line_user_id) หรือไม่
  5. response เป็น primitive boolean ในรูปแบบ text คือ "true" หรือ "false" และใช้ status 201 ตาม default ของ NestJS สำหรับ @Post

POST /api/form-builder/:hash/form-submission

เป็น debug stub ที่มีอยู่ใน source โดยโค้ดจริงถูก comment ไว้ ตัว endpoint จะคืนค่า :hash กลับไปเป็น body ดิบ ๆ ด้วย status 201 ซึ่ง port ไว้เพื่อ parity เท่านั้น

ไฟล์และฟังก์ชันหลัก

RouteHandler
GET /api/form-builder/:hashinternal/formbuilder/handler.go(*Handler).GetByHash
POST /api/form-builder/:hash/is-submitted(*Handler).IsSubmitted
POST /api/form-builder/:hash/form-submission(*Handler).CheckFormSubmission (stub)
  • internal/formbuilder/register.goRegister(r, deps) ประกอบ repository, formbuilderrule.Service แบบ uncached, storagex และ liff.Service
  • internal/formbuilder/service.goGetByHash, IsSubmitted, transformCover, transformPattern, getImageURL, matchRule, jsNumber, extractLineUserID
  • internal/formbuilder/repository.goFindActiveByHash, IsSubmitted
  • internal/formbuilder/entity.goFormBuilder, RawJSON, decodeOrdered ซึ่งเป็น ordered JSON object ที่ใช้รักษาลำดับ key

จุดเชื่อมต่อกับ Service อื่น

  • ตาราง form_builder (คอลัมน์ jsonb ได้แก่ questions, theme, profile_mapping, thank_you, field_attribute_mappings) และตาราง form_submission
  • กฎ Validation กลาง เป็นแหล่งที่มาของ commonRule
  • การตรวจสิทธิ์ LIFF Token ใช้ verify x-liff-token สำหรับ endpoint is-submitted
  • internal/storagex ผ่าน IsFileExist และ GetPublicURL สำหรับจัดการ theme.cover
  • ถูกใช้ต่อโดย ส่งคำตอบฟอร์ม และ ยืนยันตัวตนด้วย OTP ซึ่งเรียก GetByHash เป็นขั้นแรกของ pipeline เสมอ
  • ฝั่ง client-web ที่เกี่ยวข้องคือ feature form-fill