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.goRegister(r, deps) ซึ่งประกอบ liff.Service ขึ้นเองในไฟล์นี้ เพราะเป็นผู้ใช้เพียงรายเดียว และ server.Deps ไม่ได้ถือ instance ร่วม
Handlerinternal/menubuilder/handler.go(*Handler).FindOne
Serviceinternal/menubuilder/service.go(*Service).FindOne, filterItemsByAudience, extractAudienceIDs
Repositoryinternal/menubuilder/repository.goFindByPublicToken
Entityinternal/menubuilder/entity.goMenuBuilder, 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