Loading a Form's Structure
Overview
This endpoint delivers the form "blueprint" that the client-web form page renders: the full list of questions with their field types, validation rules, conditional logic, theme, and form-level settings such as requireLineLogin, oneTimeSubmission, convertToMember, profileMapping, and thankYou.
Two helper endpoints sit alongside it: one that checks whether the current user has already submitted the form (for single-submission forms), and a stub that does nothing but was ported for parity.
Business Flow
GET /api/form-builder/:hash
- Look up the active
form_builderbyform_hash. If none is found, return 400 with the messageBad Request— the source throwsnew BadRequestException()with no message, so NestJS supplies the reason phrase. - transformCover — if
theme.coveris a non-empty string, replace it with the file's public URL, but only after confirming the object actually exists viaIsFileExist. A missing file or missing storage writesnullinstead. The encoder preserves the original key order of thethemeobject and changes only thecovervalue. - transformPattern — load the active common rules without the cache using
FindAllActive. For each question whosecommonRuleIdmatches a rule, append acommonRulekey holdingrule.propertiesto that question object (mirroring the JS spread{...question, commonRule}). Matching imitatesNumber(rule.id) === Number(question.commonRuleId): numeric strings match, an empty string yields 0, and anything that coerces to NaN matches nothing. - Return the entire
FormBuilderentity as JSON.
POST /api/form-builder/:hash/is-submitted
- Load the form through the flow above (400 if absent) to obtain
line_oa_id. - The body is not trusted — any
lineUserIdthe client sends is discarded entirely; identity comes only from verifyingx-liff-token. Trusting the body would let anyone probe which LINE user has already submitted the form, turning it into a per-user oracle. - If verification fails or no token is present, return
falserather than an error. Real duplicate prevention still happens at submit time. - Once verified, query
form_submissionfor a row withis_submitted = truefor the(form_hash, line_user_id)pair. - The response is a primitive boolean rendered as text —
"true"or"false"— with status 201, the NestJS default for@Post.
POST /api/form-builder/:hash/form-submission
A debug stub in the source with its real body commented out. It echoes :hash back as a raw response body with status 201, ported purely for parity.
Key Files & Functions
| 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)wires up the repository, an uncachedformbuilderrule.Service,storagex, andliff.Serviceinternal/formbuilder/service.go→GetByHash,IsSubmitted,transformCover,transformPattern,getImageURL,matchRule,jsNumber,extractLineUserIDinternal/formbuilder/repository.go→FindActiveByHash,IsSubmittedinternal/formbuilder/entity.go→FormBuilder,RawJSON, anddecodeOrdered, an ordered JSON object used to preserve key order
Connections to Other Services
- The
form_buildertable (jsonb columnsquestions,theme,profile_mapping,thank_you,field_attribute_mappings) and theform_submissiontable. - Central Validation Rules is the source of
commonRule. - LIFF Token Verification verifies
x-liff-tokenfor theis-submittedendpoint. internal/storagexprovidesIsFileExistandGetPublicURLfor handlingtheme.cover.- Consumed by Form Answer Submission and OTP Verification, both of which call
GetByHashas the first step of their pipeline. - The related client-web feature is
form-fill.