โหลดโครงฟอร์ม (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
- ค้นหา
form_builderที่ active ด้วยform_hashหากไม่พบจะตอบ 400 พร้อม messageBad Request(source เขียนเป็นthrow new BadRequestException()โดยไม่ระบุข้อความ NestJS จึงเติม reason phrase ให้เอง) - transformCover — หาก
theme.coverเป็น string ที่ไม่ว่าง ให้แทนค่าด้วย public URL ของไฟล์นั้น โดยตรวจก่อนว่า object มีอยู่จริงผ่านIsFileExistหากไม่มีไฟล์หรือไม่มี storage จะเขียนเป็นnullทั้งนี้การเข้ารหัสจะรักษาลำดับ key ของ objectthemeเดิมไว้และแก้เฉพาะค่าcover - transformPattern — โหลด common rule ที่ active แบบ ไม่ผ่าน cache ด้วย
FindAllActiveแล้วสำหรับคำถามที่มีcommonRuleIdตรงกับ rule ใด จะ append keycommonRuleที่มีค่าเป็นrule.propertiesต่อท้าย object ของคำถามนั้น (เลียนแบบ JS spread แบบ{...question, commonRule}) การจับคู่เลียนแบบNumber(rule.id) === Number(question.commonRuleId)กล่าวคือ string ที่เป็นตัวเลขจับคู่ได้ ค่าว่างให้ผลเป็น 0 และค่าที่ coerce แล้วเป็น NaN จะไม่จับคู่กับอะไรเลย - คืน entity
FormBuilderทั้งก้อนเป็น JSON
POST /api/form-builder/:hash/is-submitted
- โหลดฟอร์มด้วย flow ด้านบน (ตอบ 400 ถ้าไม่มี) เพื่อให้ได้
line_oa_id - ไม่เชื่อ body — ค่า
lineUserIdที่ client ส่งมาจะถูกละทิ้งทั้งหมด ตัวตนผู้ใช้มาจากการ verifyx-liff-tokenเท่านั้น เหตุผลคือหากเชื่อ body ใครก็สามารถ probe ได้ว่า LINE user คนใดส่งฟอร์มนี้ไปแล้ว ซึ่งจะกลายเป็น per-user oracle - หาก verify ไม่ผ่านหรือไม่มี token จะคืนค่า
falseโดยไม่ถือเป็น error เนื่องจากการกันส่งซ้ำจริงยังทำอยู่ที่ขั้นตอน submit - เมื่อผ่านแล้วจะ query ตาราง
form_submissionว่ามีแถวที่is_submitted = trueของคู่(form_hash, line_user_id)หรือไม่ - 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 เท่านั้น
ไฟล์และฟังก์ชันหลัก
| Route | Handler |
|---|---|
GET /api/form-builder/:hash | internal/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.go→Register(r, deps)ประกอบ repository,formbuilderrule.Serviceแบบ uncached,storagexและliff.Serviceinternal/formbuilder/service.go→GetByHash,IsSubmitted,transformCover,transformPattern,getImageURL,matchRule,jsNumber,extractLineUserIDinternal/formbuilder/repository.go→FindActiveByHash,IsSubmittedinternal/formbuilder/entity.go→FormBuilder,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สำหรับ endpointis-submitted internal/storagexผ่านIsFileExistและGetPublicURLสำหรับจัดการtheme.cover- ถูกใช้ต่อโดย ส่งคำตอบฟอร์ม และ ยืนยันตัวตนด้วย OTP ซึ่งเรียก
GetByHashเป็นขั้นแรกของ pipeline เสมอ - ฝั่ง client-web ที่เกี่ยวข้องคือ feature
form-fill