เมนูสาธารณะ (Menu Builder) พร้อมกรองตาม Audience
ภาพรวม
เมนูหลายชั้นที่แอดมินออกแบบใน CMS แล้วเผยแพร่ผ่าน publicToken เมื่อผู้ใช้เปิดลิงก์ endpoint นี้จะคืนโครงเมนูทั้งก้อน ประกอบด้วย template, theme, version และรายการ item ในรูปแบบต้นไม้ โดย กรอง item ตาม audience ของผู้เปิดดูเรียบร้อยแล้วตั้งแต่ฝั่ง server
จุดสำคัญคือ การกรองนี้เป็นด่านจริง ไม่ใช่แค่การซ่อน UI — item ที่ผู้ใช้ไม่มีสิทธิ์เห็นจะ ไม่ถูกส่งออกไปเลย ทั้งชื่อและ URL ปลายทาง
Business Flow
GET /api/menu-builder/:token
Header x-liff-token เป็นค่าไม่บังคับ
- ค้นหาแถว
menu_builderด้วยpublic_token— ไม่พบตอบ 404Menu not found - หากมี
x-liff-tokenจะเรียกVerifyAndGetLineUser(token, menu.lineOaId)เพื่อดึงline_user.audience_idsโดย ความล้มเหลวทุกกรณีถูกกลืนไว้ ผู้ใช้จะเห็นเฉพาะ item ที่เป็นสาธารณะ ระบบไม่ตอบ 401 เพื่อให้ graceful degrade - Normalize ค่า
audience_idsให้เป็น slice ของจำนวนเต็ม โดยรับได้ทั้งรูปแบบ array ของตัวเลขและ array ของ object ที่มี keyidส่วนค่า JSON null หรือ object ที่ไม่มี keyidจะ ถูกทิ้งไป ไม่ coerce เป็น 0 filterItemsByAudienceทำงานแบบ recursive- กรอง children ก่อนโดยลงลึกไปทั้งต้น พร้อมแยกแยะกรณี "ไม่มี property children" ซึ่งจะทำให้ key หายไปจาก JSON ออกจากกรณี "มี children เป็น array ว่าง" ซึ่งจะส่ง array ว่างออกไป เพื่อรักษาพฤติกรรมเดิม
- Item ที่ไม่มี
audienceIdsทุกคนเห็นได้ ส่วน item ที่มีค่านี้จะเห็นได้เมื่อมี id ทับกันอย่างน้อยหนึ่งตัว - Item ชนิด
nodeซึ่งทำหน้าที่เป็นโฟลเดอร์ หาก children ถูกกรองจนหมดจะถูกตัดออกไปด้วย โดย children ที่ไม่มี property ก็นับว่าว่างเช่นกัน - Item ที่ผ่านการกรองจะถูกประกอบใหม่ด้วย key ครบทั้ง 18 ตัว ได้แก่
id,name,type,url,contentPageToken,contentLinkToken,contentLinkBehavior,contentLinkDisplayStyle,audienceIds,children,icon,level,parentId,order,customStyle,image,showTextและtextPosition
- ประกอบ response
{id, name, publicToken, lineOaHash, lineLiffId, config}โดยconfigประกอบด้วยitems,template,themeและversion- ค่า
templateและthemeถูกส่งผ่านเป็น raw JSON เพื่อรักษารูปแบบเดิมไว้ทุกประการ - ค่า
lineLiffIdใช้ semantic แบบ nullish coalescing ไม่ใช่ logical OR ดังนั้นสตริงว่างที่บันทึกไว้ต้อง คงเป็นสตริงว่าง ไม่กลายเป็น null ค่าจะเป็น null เฉพาะเมื่อline_login_infoไม่มี, parse ไม่ได้ หรือ keylineLiffIdไม่มีหรือมีค่าเป็น JSON null
- ค่า
ไฟล์และฟังก์ชันหลัก
| รายการ | ค่า |
|---|---|
| Route | GET /api/menu-builder/:token |
| Register | internal/menubuilder/register.go → Register(r, deps) ซึ่งประกอบ liff.Service ขึ้นเองในไฟล์นี้ เพราะเป็นผู้ใช้เพียงรายเดียว และ server.Deps ไม่ได้ถือ instance ร่วม |
| Handler | internal/menubuilder/handler.go → (*Handler).FindOne |
| Service | internal/menubuilder/service.go → (*Service).FindOne, filterItemsByAudience, extractAudienceIDs |
| Repository | internal/menubuilder/repository.go → FindByPublicToken |
| Entity | internal/menubuilder/entity.go → MenuBuilder, MenuItemConfig, ItemTypeNode |
| Response | FilteredMenuResponse, responseConfig |
จุดเชื่อมต่อกับ Service อื่น
- ฐานข้อมูล — ตาราง
menu_builder(jsonbconfig,public_token,line_oa_id) join กับline_oaเพื่อเอาline_oa_hashและline_login_info - LIFF authentication — เรียก
VerifyAndGetLineUserแบบไม่บังคับ - ฟีเจอร์ที่เกี่ยวข้อง — ปลายทางของ item แต่ละชนิดจะเชื่อมไปยัง content page viewer ผ่าน
contentPageTokenและ content link viewer ผ่านcontentLinkToken - client-web — ตรงกับฟีเจอร์
menu-viewer