Skip to main content

เมนูสาธารณะ (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 เป็นค่าไม่บังคับ

  1. ค้นหาแถว menu_builder ด้วย public_token — ไม่พบตอบ 404 Menu not found
  2. หากมี x-liff-token จะเรียก VerifyAndGetLineUser(token, menu.lineOaId) เพื่อดึง line_user.audience_ids โดย ความล้มเหลวทุกกรณีถูกกลืนไว้ ผู้ใช้จะเห็นเฉพาะ item ที่เป็นสาธารณะ ระบบไม่ตอบ 401 เพื่อให้ graceful degrade
  3. Normalize ค่า audience_ids ให้เป็น slice ของจำนวนเต็ม โดยรับได้ทั้งรูปแบบ array ของตัวเลขและ array ของ object ที่มี key id ส่วนค่า JSON null หรือ object ที่ไม่มี key id จะ ถูกทิ้งไป ไม่ coerce เป็น 0
  4. 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
  5. ประกอบ 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 ไม่ได้ หรือ key lineLiffId ไม่มีหรือมีค่าเป็น JSON null

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

รายการค่า
RouteGET /api/menu-builder/:token
Registerinternal/menubuilder/register.go → Register(r, deps) ซึ่งประกอบ liff.Service ขึ้นเองในไฟล์นี้ เพราะเป็นผู้ใช้เพียงรายเดียว และ server.Deps ไม่ได้ถือ instance ร่วม
Handlerinternal/menubuilder/handler.go → (*Handler).FindOne
Serviceinternal/menubuilder/service.go → (*Service).FindOne, filterItemsByAudience, extractAudienceIDs
Repositoryinternal/menubuilder/repository.go → FindByPublicToken
Entityinternal/menubuilder/entity.go → MenuBuilder, MenuItemConfig, ItemTypeNode
ResponseFilteredMenuResponse, responseConfig

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

  • ฐานข้อมูล — ตาราง menu_builder (jsonb config, 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