Skip to main content

Content Management — Content Pages

ภาพรวม​

Content Pages คือเมนูสร้างและแก้ไข "หน้าเนื้อหา" ของ LINE OA เช่นบทความ หน้าโปรโมชัน หรือหน้าข้อมูลบริการ ที่เปิดให้ผู้ใช้เข้าชมผ่านลิงก์หรือ QR code โดยไม่ต้องพัฒนาเว็บเพิ่ม เนื้อหาเก็บเป็น HTML แก้ผ่าน Rich Text Editor (Tiptap) และเก็บสองภาษา (ไทย/อังกฤษ) ไว้ในรายการเดียวกัน

จุดสำคัญที่ต้องรู้ก่อนเริ่มคือ หนึ่งหน้าเนื้อหามี slug ได้เพียงค่าเดียว แม้ฟอร์มจะมีช่อง "URL Slug" แยกไว้ให้ทั้งแท็บไทยและอังกฤษ ตอนบันทึกโค้ดหน้าเว็บจะเลือกค่าเดียวไปเก็บ (content.th.slug || content.en.slug) — slug ของภาษาอังกฤษจึงถูกใช้เฉพาะเมื่อฝั่งไทยไม่มี slug และหน้าที่แก้ไขอยู่จะดึง slug เดียวกันนั้นกลับมาแสดงในทั้งสองแท็บ

บทบาทที่ใช้งาน — ทีมการตลาดหรือทีมคอนเทนต์ที่ต้องเผยแพร่หน้าข้อมูลให้ลูกค้าเปิดดูได้เอง (โมดูลนี้ ไม่มี ModuleGate ที่ backend — ดูหัวข้อ สิทธิ์การเข้าถึง)

เส้นทางผู้ใช้งาน​

1. ดูรายการหน้าเนื้อหา​

เปิดเมนู Content Management > Content Pages ตารางแสดงคอลัมน์ ลำดับ, เนื้อหา, หมวดหมู่, การมองเห็น, สถานะ, จำนวนเข้าชม, อัปเดตล่าสุด และคอลัมน์การดำเนินการที่ตรึงไว้ทางขวา

  • คอลัมน์ "เนื้อหา" แสดงชื่อเรื่องพร้อมป้ายภาษาที่มีจริง (TH สีน้ำเงิน / EN สีเขียว) ป้าย FT สีมะนาวถ้าผูก Friends Track ไว้ และ slug แบบ monospace ใต้ชื่อ (ตัดที่ 30 ตัวอักษร)
  • คอลัมน์ "หมวดหมู่" แสดงเป็นป้ายคู่ หมวดหมู่ › หมวดหมู่ย่อย
  • คอลัมน์ "การมองเห็น" แสดงป้าย สาธารณะ เมื่อไม่จำกัดกลุ่มเป้าหมาย หรือป้าย "N กลุ่มเป้าหมาย" สีม่วงพร้อม tooltip ระบุชื่อกลุ่มทั้งหมด
  • ตัวกรองมี 2 ช่อง — ค้นหาเนื้อหา และ สถานะ (ทั้งหมด / เผยแพร่แล้ว / ฉบับร่าง / เก็บถาวร) ทั้งสองช่องรีเซ็ตกลับหน้า 1 ทุกครั้งที่เปลี่ยนค่า มุมขวาของแถบเครื่องมือแสดงจำนวนหน้าทั้งหมด

2. สร้างหน้าเนื้อหาใหม่​

กดปุ่ม สร้างเนื้อหา เข้าสู่หน้าฟอร์มแบ่งสองคอลัมน์ — ซ้ายคือแท็บแก้ไข 4 แท็บ ขวาคือตัวอย่างสดที่สลับกรอบจำลองได้ 3 ขนาด (เดสก์ท็อป = เต็มความกว้าง, แท็บเล็ต = 768px, มือถือ = 375px) พร้อมการ์ด ตัวอย่างผลการค้นหา ด้านล่างที่จำลองหน้าตาผลลัพธ์ Google

  1. แท็บ เนื้อหา — เลือก หมวดหมู่ (ไม่บังคับ) จากเมนู Categories ก่อน ช่อง หมวดหมู่ย่อย จะถูกปิดไว้จนกว่าจะเลือกหมวดหมู่ (placeholder เปลี่ยนเป็น "กรุณาเลือกหมวดหมู่ก่อน") เมื่อเลือกแล้วระบบเรียก dropdown แบบลำดับชั้นจากเมนู Subcategories มาเติมพร้อมคำใบ้ "หมวดหมู่ย่อยรองรับลำดับชั้นได้สูงสุด 4 ระดับ" — และการเปลี่ยนหมวดหมู่จะล้างหมวดหมู่ย่อยที่เลือกไว้ทิ้งทุกครั้ง
  2. ถัดลงมาเป็นแท็บภาษา ภาษาไทย (TH) / English (EN) แต่ละภาษามีช่อง หัวข้อ (Title), URL Slug และ เนื้อหา (Content) เมื่อกรอกหัวข้อ ระบบสร้าง slug ให้อัตโนมัติ (ทับค่าที่แก้ไว้ทุกครั้งที่พิมพ์หัวข้อ) ปุ่ม แปลอัตโนมัติ มุมขวาของแท็บยังเป็น placeholder — กดแล้วขึ้น alert ว่า "ฟีเจอร์แปลภาษาจะเปิดให้ใช้งานเร็วๆ นี้!"
  3. แผงพับ SEO Settings ในแต่ละภาษามี Meta Title, Meta Description (นับตัวอักษร สูงสุด 160), Excerpt (สูงสุด 200), OG Title (สูงสุด 160), OG Description (สูงสุด 200) และ OG Image URL
  4. แท็บ การควบคุมการเข้าถึง — เลือก จำกัดกลุ่มเป้าหมาย ได้หลายกลุ่มจากเมนู Segment audience และ/หรือเปิดสวิตช์ ป้องกันด้วยรหัสผ่าน ทั้งสองช่องนี้ใช้ร่วมกันได้ แต่ช่องกลุ่มเป้าหมายจะถูก ปิด ทันทีที่ผูก Friends Track ไว้ และกลับกันด้วย
  5. แท็บ Friends Track — เลือกแคมเปญเพิ่มเพื่อนสถานะ active จากเมนู Friend track เมื่อเลือกแล้วระบบโชว์การ์ดสีเขียวระบุชื่อและ token ของแคมเปญ ช่อง ข้อความปุ่ม CTA (สูงสุด 50 ตัวอักษร ค่าเริ่มต้น "เพิ่มเพื่อน") และตัวอย่างปุ่มสีเขียว LINE (#06C755) ที่จะติดอยู่ท้ายหน้าเนื้อหาจริง
  6. แท็บ ตั้งค่าแคมเปญ — พารามิเตอร์ UTM (utmSource/utmMedium เป็น select แบบ tags มีตัวเลือกสำเร็จรูป, utmCampaign/utmTerm/utmContent เป็นช่องข้อความ) และการวิเคราะห์ (Google Analytics Events แบบ JSON, Facebook Pixel Events, LINE Tag Events, สคริปต์ Head ที่กำหนดเอง)
  7. กด บันทึกเป็นฉบับร่าง หรือ เผยแพร่ — ปุ่มทั้งสองถูก disable ไว้จนกว่าจะมีหัวข้อหรือเนื้อหาอย่างน้อยหนึ่งภาษา และก่อนยิง API ยังตรวจซ้ำอีกสองชั้นว่า "กรุณากรอกเนื้อหาอย่างน้อยหนึ่งภาษา" และ "กรุณาระบุ URL slug อย่างน้อยหนึ่งภาษา"

3. แก้ไขและสลับสถานะ​

กดไอคอนดินสอในตารางเพื่อเข้าหน้าแก้ไข (/content-management/edit?id=…) หน้าตาเหมือนฟอร์มสร้างแต่มีป้ายสถานะกำกับหัวข้อ และแถบปุ่มเปลี่ยนตามสถานะปัจจุบัน:

  • ฉบับร่าง → มีปุ่ม บันทึกการเปลี่ยนแปลง และ เผยแพร่
  • เผยแพร่แล้ว → มีปุ่ม บันทึกการเปลี่ยนแปลง และ ยกเลิกการเผยแพร่ (ส่ง status = draft กลับไป)
  • ช่องรหัสผ่านในหน้าแก้ไขเปลี่ยน placeholder เป็น "เว้นว่างเพื่อคงรหัสผ่านเดิม"

4. เผยแพร่ต่อไปยังช่องทางอื่น​

จากหน้ารายการ กดไอคอนลิงก์เพื่อคัดลอกลิงก์ (ปุ่มถูก disable ถ้าหน้ายังไม่มี publicToken หรือระบบยังไม่รู้ค่า LINE OA hash) หรือไอคอน QR เพื่อเปิดกล่อง QR code ขนาด 256px ที่ ดาวน์โหลด PNG, คัดลอกลิงก์ หรือปิดได้ รูปแบบลิงก์ที่ได้ต่างกันตามการตั้งค่า:

  • มีการจำกัดกลุ่มเป้าหมาย + OA มี LIFF ID → https://liff.line.me/{liffId}/{lineOaHash}/content/… (ข้อความยืนยัน "คัดลอกลิงก์ LIFF แล้ว! (เปิดในแอป LINE)")
  • ไม่จำกัดกลุ่ม → {BASE_APP_URL}/{lineOaHash}/content/…

นำลิงก์ไปฝังต่อในเมนู Menu Builder, ริชเมนูที่เมนู Rich menu หรือแคมเปญบรอดแคสต์ที่เมนู Campaign

รายละเอียดสำคัญที่ตรวจสอบจากซอร์สโค้ดจริง​

ยืนยันจาก internal/modules/contentpage/{controller,service,dto,helpers}.go และ internal/modules/publicmod/controller.go ของ line-management-cms-api-go:

  • slug ถูกบังคับรูปแบบเข้มงวดที่ backend แต่ฝั่งหน้าเว็บไม่ตรวจเลย — backend ใช้ regex ^[a-z0-9-]+$ และตอบกลับข้อความ "Slug must contain only lowercase letters, numbers, and hyphens" (ยาวได้ไม่เกิน 255 ตัวอักษร) ส่วนฟอร์มปล่อยให้พิมพ์อะไรก็ได้ในช่อง URL Slug — ถ้าแก้ slug เองแล้วเผลอใส่ตัวพิมพ์ใหญ่ ขีดล่าง หรืออักษรไทย จะโดนปฏิเสธตอนกดบันทึก ไม่ใช่ตอนพิมพ์ ตัวสร้าง slug อัตโนมัติของหน้าเว็บมี ตารางถอดอักษรไทยเป็นละตินในตัว (ก→k, ข/ฃ/ค/ฅ/ฆ→kh, เ→e, ไ/ใ→ai ฯลฯ) แล้วจึงตัดอักขระที่เหลือทิ้ง — วรรณยุกต์และไม้ทัณฑฆาตไม่มีในตาราง จึงถูกตัดหายไปเงียบ ๆ
  • slug ต้องไม่ซ้ำ "ต่อหนึ่ง LINE OA" ไม่ใช่ทั้งระบบ — ตรวจด้วยเงื่อนไข line_oa_id + slug + deleted_date IS NULL ถ้าซ้ำจะได้ 409 "A content page with this slug already exists" ส่วนตอนแก้ไขจะตรวจเฉพาะเมื่อ slug เปลี่ยนจากค่าเดิม — บันทึกซ้ำโดยไม่แตะ slug จึงไม่ถูกปฏิเสธ
  • กลุ่มเป้าหมายกับ Friends Track ห้ามอยู่ด้วยกัน และ backend บังคับจริง — ทั้ง create และ update ตรวจว่าถ้ามี audienceIds อย่างน้อยหนึ่งค่าและมี friendTrackCampaignId พร้อมกัน จะถูกปฏิเสธด้วย 400 พร้อมเหตุผลเต็ม ๆ ว่า "Audience Restriction requires authentication which blocks the publicly accessible Friend Track CTA." ที่สำคัญคือตอน update ระบบไม่ได้ตรวจแค่ค่าที่ส่งมา แต่ รวมค่าเดิมในฐานข้อมูลกับค่าที่ส่งมาก่อน แล้วจึงตรวจ — ส่ง audienceIds มาเพียงตัวเดียวเพื่อจะแก้เนื้อหาหน้าที่ผูก Friends Track อยู่แล้ว ก็ยังถูกปฏิเสธ
  • public token ออกครั้งเดียวและไม่เปลี่ยนอีกเลย — เมื่อบันทึกด้วยสถานะ published ครั้งแรก backend สุ่ม crypto.randomBytes(32) เป็น hex 64 ตัวอักษรเก็บไว้เป็น public_token พร้อมประทับ publishedAt/publishedBy แต่ตอนแก้ไข เงื่อนไขออก token ใหม่คือ "ยังไม่มี token" เท่านั้น ดังนั้นวนยกเลิกเผยแพร่แล้วเผยแพร่ใหม่กี่ครั้ง ลิงก์เดิมยังเป็นลิงก์เดียวกันตลอด — และเมนูนี้ ไม่มีปุ่มออก token ใหม่ (ต่างจากเมนู Content Links และ Menu Builder ที่มี)
  • จำนวนเข้าชมนับทุกครั้งที่โหลด และคอลัมน์ "ไม่ซ้ำ" ไม่เคยขยับ — GET /api/public/content/:token เพิ่ม view_count ทีละ 1 ทุกครั้งที่อ่านสำเร็จ (รวมทั้งกดรีเฟรชซ้ำและกด preview จากหน้ารายการ) ส่วน unique_view_count ต้องเพิ่มจากฟังก์ชัน trackView ที่บันทึก session — แต่ handler ของ endpoint ติดตามผลนั้น แค่ตรวจว่าเนื้อหามีอยู่จริงแล้วตอบข้อความ "View tracked successfully" กลับไปโดยไม่เขียนอะไรลงฐานข้อมูลเลย (ความเห็นในโค้ดระบุว่ายกมาจากต้นฉบับที่เขียนว่า "we'll skip the actual tracking implementation") ทำให้สถิติ analytics ทั้งชุด (viewsByDate, topReferrers, deviceTypes) ไม่มีข้อมูลป้อนเข้า
  • มีสองเส้นทางสาธารณะสำหรับอ่านหน้าเดียวกัน และตอบต่างกันเมื่อรหัสผ่านผิด — GET /api/public/content-pages/:token ตอบ 403 "Password required" / "Invalid password" ส่วน GET /api/public/content/:token (เส้นทางที่หน้าเว็บฝั่งผู้ใช้เรียกจริง) ตอบ 200 พร้อม body { requirePassword: true, message: "…" } เพื่อให้หน้าเว็บเรนเดอร์ฟอร์มกรอกรหัสผ่านได้ — เส้นทางหลังยังตรวจกลุ่มเป้าหมายด้วย x-liff-token header (ไม่มี header → 403 "Authentication required for this content") ในขณะที่เส้นทางแรกไม่ตรวจกลุ่มเป้าหมายเลย
  • ปุ่มลบเป็น soft delete แบบเจาะจงคอลัมน์ — DELETE /api/content-pages/:id ตอบ 204 ว่างเปล่า และเขียนแค่ 3 คอลัมน์ (deleted_date, deleted_by, updated_date) ทิ้ง updated_by ไว้เท่าเดิมโดยเจตนา ตามที่ความเห็นในโค้ดอธิบายว่าเป็นการทำซ้ำพฤติกรรม partial-select ของ TypeORM เดิม
  • การอัปโหลดรูปในตัวแก้ไขจำกัดชนิดไฟล์ที่ backend เท่านั้น — Rich Text Editor รับ accept="image/*" และอัปโหลดได้ทั้งจากปุ่ม ลากวาง และวางจากคลิปบอร์ด แต่ backend อนุญาตแค่ image/jpeg, image/jpg, image/png, image/gif (สูงสุด 10MB) — WebP, AVIF หรือ SVG จึงผ่านหน้าเว็บไปแล้วโดนปฏิเสธด้วย "Invalid file type. Only JPEG, PNG, and GIF are allowed" ไฟล์ที่อัปโหลดสำเร็จตั้งชื่อเป็น content-image-{timestamp} แยกโฟลเดอร์ตาม LINE OA hash

เมนูที่เกี่ยวข้องกันในเส้นทางนี้​

  • Categories — เป็นแหล่งของตัวเลือก "หมวดหมู่" ในแท็บเนื้อหา (เรียก endpoint dropdown เฉพาะ)
  • Subcategories — โหลดต่อจากหมวดหมู่ที่เลือก โดยใช้ dropdown แบบลำดับชั้นที่เยื้องระดับให้เห็นโครงต้นไม้
  • Content Links — เมนูพี่น้องที่กรองเนื้อหาจากหมวดหมู่/หมวดหมู่ย่อยชุดเดียวกันนี้ แล้วรวมหน้าเนื้อหาที่ตรงเงื่อนไขเป็นลิงก์เดียว
  • Segment audience — เป็นแหล่งของกลุ่มเป้าหมายในแท็บการควบคุมการเข้าถึง และเป็นตัวชี้ว่าลิงก์ที่คัดลอกจะออกมาเป็นแบบ LIFF หรือเว็บปกติ
  • Friend track — เป็นแหล่งของแคมเปญเพิ่มเพื่อนในแท็บ Friends Track (ดึงเฉพาะสถานะ active) และหน้าเนื้อหาจะกลายเป็นหน้า landing ของแคมเปญนั้น
  • Menu Builder — รายการเมนูชนิด "หน้าเนื้อหา" อ้างอิงหน้าที่เผยแพร่แล้วจากเมนูนี้โดยตรง
  • Rich menu / Campaign — ปลายทางที่นำลิงก์หรือ QR code ที่คัดลอกได้ไปฝัง

จุดที่ควรระวัง​

  • ลิงก์ที่คัดลอกได้ใช้ slug ไม่ใช่ token — โค้ดหน้ารายการสร้างเส้นทางจาก record.slug || record.publicToken (slug มาก่อนเสมอ และ slug บังคับกรอกอยู่แล้ว) แต่หน้าอ่านสาธารณะและ API GET /api/public/content/:token ค้นหาด้วย public_token เท่านั้น ถ้าเปิดลิงก์ที่คัดลอกมาแล้วได้ "Content not found" ให้ตรวจค่า publicToken ของหน้านั้นก่อน
  • ปุ่มคัดลอกลิงก์/QR ถูก disable เมื่อระบบยังไม่รู้ค่า LINE OA hash — ค่านี้มาจากการเรียก GET ข้อมูล LINE OA แยกอีกครั้งหลังหน้าโหลด ถ้าเรียกล้มเหลวจะขึ้นเพียง error ใน console แล้วปุ่มยังคง disable อยู่เงียบ ๆ
  • สถานะ เก็บถาวร มีในตัวกรองและ backend มี endpoint POST /content-pages/:id/archive รองรับ แต่ ไม่มีปุ่มใดในหน้าเว็บที่ตั้งสถานะนี้ ตัวเลือกในตัวกรองจึงคืนผลว่างอยู่ตลอดในทางปฏิบัติ
  • ตัวอย่าง "URL ที่สร้างขึ้น" ในแท็บตั้งค่าแคมเปญแสดงรูปแบบ {origin}/th/{slug} และ {origin}/en/{slug} ซึ่ง ไม่ใช่รูปแบบลิงก์จริง ที่ระบบคัดลอกให้ ({base}/{lineOaHash}/content/…) ใช้เป็นภาพประกอบเรื่อง UTM เท่านั้น
  • ช่อง slug ถูกทับด้วยค่าที่สร้างอัตโนมัติทุกครั้งที่แก้หัวข้อ — ถ้าตั้งใจใช้ slug ที่กำหนดเอง ให้แก้หัวข้อให้เสร็จก่อนแล้วจึงมาแก้ slug
  • ผู้ใช้คนอื่นในองค์กรเดียวกันแก้/ลบหน้าเนื้อหาของเราได้ — การตรวจสิทธิ์เจ้าของที่ backend ยอมรับเมื่อ ผู้ใช้เดียวกัน หรือ องค์กรเดียวกัน ก็พอ ตราบใดที่อยู่ LINE OA เดียวกัน

สิทธิ์การเข้าถึง (ตรวจสอบจากซอร์สโค้ดจริง)​

  • ฝั่งหน้าเว็บ — หน้ารายการและฟอร์มของเมนูนี้ ไม่ได้ครอบด้วย PermissionGuard (ต่างจากหน้าอย่าง Campaign ที่ครอบไว้จริง) สิ่งเดียวที่ตรวจสิทธิ์คือ การแสดงเมนูในแถบด้านข้าง ซึ่งเช็ค ability.can(VIEW, 'content-management') ก่อนจะแสดงกลุ่มเมนู Content Management กับลูกทั้ง 4 รายการ — ถ้าผู้ใช้ไม่มีสิทธิ์นี้ เมนูจะหาย แต่พิมพ์ URL /content-management ตรง ๆ ยังเข้าหน้าได้
  • ฝั่งเซิร์ฟเวอร์ — ทุกเส้นทางประกาศ policy ระดับการกระทำไว้ (READALL/READ/CREATE/UPDATE/DELETE บน subject line-oa) แต่ auth.CheckPolicies เป็น middleware ที่ปล่อยผ่านทุกคำขอ ตามที่ความเห็นในโค้ดระบุตรง ๆ ว่า "Enforcement disabled — allow all authenticated requests (TS parity)" การบังคับสิทธิ์จริงจึงเหลือแค่ JWT + การตรวจสิทธิ์เจ้าของ (validateOwnership: Super Admin ผ่านหมด, คนอื่นต้องอยู่ LINE OA เดียวกัน และต้องเป็นเจ้าของหรืออยู่องค์กรเดียวกัน)
  • ไม่มี ModuleGate ในโมดูลนี้ — ตรวจการลงทะเบียนเส้นทางใน contentpage/controller.go แล้วไม่พบการเรียก modulegate.ModuleGate(d, "...") เลย (ต่างจากโมดูล campaign ที่มี) และชุดโมดูลที่มี gate จริงตามรายการใน modulegate/coverage_test.go ก็ไม่มี content-page อยู่ด้วย ที่น่าสังเกตคือโมดูลนี้ ไม่ได้อยู่ในรายการ exemptModules ที่บันทึกโมดูลซึ่ง "ตั้งใจไม่ให้ gate" ไว้เช่นกัน — ตามคำอธิบายในเทสต์นั้น โมดูลที่ไม่มี gate หมายความว่าผู้ดูแลระดับแพลตฟอร์ม ปิดโมดูลนี้ให้องค์กรใดไม่ได้เลย เพราะไม่มีจุดที่บังคับ
  • เส้นทางสาธารณะ 2 เส้นทาง (/api/public/content-pages/:token และ /api/public/content/:token) ไม่ต้องมี JWT โดยเจตนา — การป้องกันมาจาก token ที่เดายาก + รหัสผ่าน (bcrypt cost 10) + การตรวจกลุ่มเป้าหมายผ่าน x-liff-token
  • ขอบเขตข้อมูลถูกจำกัดด้วย LINE OA เสมอ — GET /api/content-pages กรองด้วย line_oa_id ของผู้เรียกเป็นเงื่อนไขแรกทุกครั้ง

ไฟล์อ้างอิงจากซอร์สโค้ด​

Frontend (cms-web-2026)

  • src/app/content-management/page.tsx — หน้ารายการ, ตัวกรอง, ปุ่มคัดลอกลิงก์/QR, การประกอบ URL แบบ LIFF vs เว็บปกติ
  • src/app/content-management/create-multilang/page.tsx, src/app/content-management/edit/page.tsx — ฟอร์ม 4 แท็บ, ตัวอย่างสด, ตรรกะปุ่มบันทึก/เผยแพร่/ยกเลิกการเผยแพร่
  • src/components/content-management/MultiLanguageForm.tsx — แท็บภาษา, การสร้าง slug อัตโนมัติ, แผงพับ SEO
  • src/components/content-management/TiptapEditorSimple.tsx — Rich Text Editor และการอัปโหลดรูป (ปุ่ม/ลากวาง/วาง)
  • src/utils/slugify.ts — ตารางถอดอักษรไทยเป็นละติน
  • src/services/content-page.service.ts — endpoint ที่หน้าเว็บเรียกใช้
  • src/components/layout/sidemenu/sidemenu.container.tsx — การตรวจสิทธิ์ระดับเมนู content-management

Backend (line-management-cms-api-go)

  • internal/modules/contentpage/controller.go — 12 เส้นทาง (รวม 2 เส้นทางสาธารณะ), การอัปโหลดรูป, การไม่มี ModuleGate
  • internal/modules/contentpage/service.go — ตรรกะสร้าง/แก้ไข/ลบ/ทำสำเนา, การออก public token, validateOwnership, การนับ view
  • internal/modules/contentpage/dto.go — regex ของ slug และกฎตรวจสอบข้อมูลขาเข้าทั้งชุด
  • internal/modules/publicmod/controller.go — GET /public/content/:token, liff-config, การตรวจกลุ่มเป้าหมายและรหัสผ่านของฝั่งผู้ใช้
  • internal/modules/auth/policy.go — CheckPolicies ที่เป็น no-op
  • internal/modules/modulegate/coverage_test.go — รายการโมดูลที่มี ModuleGate จริงและโมดูลที่ยกเว้น

ดูรายละเอียดเชิงเทคนิคเพิ่มเติม (เวอร์ชันก่อนหน้าของทีมเอกสาร) ที่ จัดการหน้าเนื้อหา (Content Page)