ลิงก์รวมเนื้อหา
ภาพรวม
ลิงก์รวมเนื้อหา (Content Link) คือลิงก์ที่มี token ประจำตัว ซึ่งไม่ได้ชี้ไปยังบทความใดบทความหนึ่ง แต่เก็บ เงื่อนไขการคัดเลือกเนื้อหา เอาไว้ เมื่อผู้รับเปิดลิงก์ ระบบจะไปดึงเนื้อหาที่ตรงเงื่อนไข ณ ขณะนั้นมาแสดงเป็นรายการ
ประโยชน์คือทีมการตลาดสร้างลิงก์ครั้งเดียวแล้วนำไปติดในริชเมนู แคมเปญ หรือข้อความ LINE ได้เลย เนื้อหาที่ผู้ใช้เห็นจะอัปเดตตามบทความใหม่ที่ตรงเงื่อนไขโดยไม่ต้องแก้ลิงก์อีก
เงื่อนไขที่ตั้งได้ต่อหนึ่งลิงก์ประกอบด้วย หมวดหมู่, หมวดหมู่ย่อย, ช่วงวันที่เผยแพร่, กลุ่มผู้ชม (audience) ที่จำกัดสิทธิ์การดู และสถานะเปิด/ปิดใช้งาน แต่ละลิงก์มีตัวนับจำนวนคลิก และสามารถออก token ใหม่ได้ทันทีหากลิงก์รั่วไหล
หน้าจอทั้งหมดอยู่ในหน้าเดียว โดยฟอร์มสร้างและแก้ไขเปิดเป็นหน้าต่างซ้อน ไม่มีการเปลี่ยนหน้า
Business Flow
- เปิดหน้ารายการลิงก์รวมเนื้อหา ระบบโหลดรายการลิงก์พร้อมตัวเลือกหมวดหมู่สำหรับฟิลเตอร์ และดึงข้อมูล LINE OA ปัจจุบันเพื่อนำ LIFF ID มาใช้ประกอบลิงก์
- ตารางแสดงชื่อลิงก์, สรุปเงื่อนไขที่ตั้งไว้ในรูปแบบป้ายกำกับ (หมวดหมู่ / หมวดย่อย / ช่วงวันที่ / จำนวนกลุ่มผู้ชม หรือระบุว่ายังไม่มีเงื่อนไข), จำนวนคลิก, วันที่สร้าง และปุ่มคำสั่ง
- แถบเครื่องมือด้านบนมีตัวกรอง 3 ตัว ได้แก่ ค้นหาจากชื่อ, สถานะ และหมวดหมู่
- การสร้างลิงก์ใหม่ เปิดหน้าต่างฟอร์มซึ่งโหลดตัวเลือกหมวดหมู่และกลุ่มผู้ชมพร้อมกัน เมื่อผู้ใช้เลือกหมวดหมู่ ระบบจะล้างค่าหมวดย่อยเดิมแล้วโหลดตัวเลือกหมวดย่อยของหมวดนั้นให้ใหม่ (ช่องหมวดย่อยจะถูกปิดไว้จนกว่าจะเลือกหมวดหมู่)
- ฟิลด์ในฟอร์มประกอบด้วย ชื่อ (บังคับ), คำอธิบาย, กล่องเงื่อนไขการคัดเลือก (หมวดหมู่, หมวดย่อย, ช่วงวันที่, กลุ่มผู้ชมแบบเลือกได้หลายกลุ่ม) และสวิตช์เปิด/ปิดใช้งาน
- เมื่อบันทึก ระบบจะแปลงค่าฟอร์มเป็น payload โดยแปลงสวิตช์เป็นสถานะ active หรือ inactive, จัดรูปแบบช่วงวันที่เป็น
YYYY-MM-DDและส่งค่าว่างเป็น null อย่างชัดเจน เพื่อให้การแก้ไขสามารถล้างเงื่อนไขเดิมออกได้จริง - การคัดลอกลิงก์ ให้ผลต่างกันตามการตั้งค่า ถ้าลิงก์จำกัดกลุ่มผู้ชมและ OA มี LIFF ID ระบบจะสร้างลิงก์แบบ LIFF เพื่อให้เปิดในแอป LINE และระบุตัวตนผู้ใช้ได้ แต่ถ้าเป็นลิงก์สาธารณะจะได้ URL แบบเว็บปกติที่ประกอบจาก hash ของ OA และ token
- การออก token ใหม่ จะมีกล่องยืนยันเตือนว่าลิงก์เดิมจะใช้งานไม่ได้ทันที และการลบลิงก์ก็ต้องยืนยันเช่นกัน
หน้าจอและองค์ประกอบหลัก
หน้ารายการ (src/app/content-links/page.tsx) — รวมตาราง, ตัวกรอง, การคัดลอกลิงก์ทั้งแบบเว็บและแบบ LIFF, การออก token ใหม่ผ่าน POST /content-links/{id}/regenerate-token และการลบ
หน้าต่างฟอร์ม (src/app/content-links/components/ContentLinkFormModal.tsx) — ใช้ร่วมกันทั้งโหมดสร้างและแก้ไข รับผิดชอบการโหลดตัวเลือกทั้งหมด การผูกหมวดย่อยกับหมวดหมู่ที่เลือก และการประกอบ payload ก่อนส่ง
เซอร์วิสกลาง (src/services/content-link.service.ts) — ครอบคลุมการอ่านรายการ, อ่านรายตัว, สร้าง, แก้ไข, ลบ, ออก token ใหม่ และดึงเนื้อหาตัวอย่างของลิงก์ (preview) โดยฟังก์ชัน preview ไม่ได้ถูกเรียกจากหน้านี้ แต่ถูกใช้โดยตัวสร้างเมนูเพื่อ preview เมนูแบบแสดงเนื้อหาในตัว
โครงสร้างข้อมูลสำคัญ — ระเบียนลิงก์เก็บ token, จำนวนคลิก, ข้อมูล OA ที่สังกัด (ใช้ hash ประกอบ URL) และข้อมูลหมวดหมู่/หมวดย่อยที่อ้างอิง ส่วนผลลัพธ์ของ preview จะคืนรายการเนื้อหาพร้อม token สาธารณะ, slug, หัวเรื่อง, เกริ่นนำ, ภาพปก และวันที่เผยแพร่ ควบคู่กับข้อมูลการแบ่งหน้า
จุดเชื่อมต่อกับฟีเจอร์อื่น
- สิทธิ์การเข้าถึง — ใช้ subject
content-linksเป็นคีย์ของเมนูย่อยใต้ Content Management ปลดล็อกจากโมดูลline-oaฝั่งหลังบ้าน - จัดการเนื้อหา (Content Management) — เป็นแหล่งของเนื้อหาที่ถูกคัดมาแสดง
- หมวดหมู่และหมวดหมู่ย่อย — เป็นเงื่อนไขคัดเลือกหลัก โดยตัวเลือกหมวดย่อยดึงจาก endpoint แบบ hierarchical dropdown
- กลุ่มผู้ชม (Audience) — เมื่อกำหนดกลุ่มผู้ชม ลิงก์จะเปลี่ยนรูปแบบเป็น LIFF URL เพื่อให้ระบบระบุตัวตนผู้เปิดได้
- ตัวสร้างเมนู (Menu Builder) — รายการเมนูชนิด
content_linkอ้างอิง token ของลิงก์ และใช้ endpoint preview เพื่อแสดงเนื้อหาในตัวอย่างเมนู - การตั้งค่าระบบ — URL สาธารณะประกอบจากตัวแปรสภาพแวดล้อม
NEXT_PUBLIC_BASE_APP_URLและ API เรียกผ่านNEXT_PUBLIC_BASE_CONSOLE_API_URL
รายละเอียดฝั่ง Backend (CMS API)
โมดูลนี้อยู่ที่ internal/modules/contentlink/ ลงทะเบียนไว้ใต้กลุ่ม /api/content-links ซึ่งอยู่ในโซนที่ต้องล็อกอิน ส่วนตัวลิงก์ที่ผู้รับเปิดจริงจะไปตกที่โมดูล public แยกกัน
สิทธิ์และการจำกัดขอบเขตข้อมูล
- ทุก endpoint ของหน้านี้ต้องผ่าน
JwtAuthระดับ global - handler ประกาศ policy metadata ของโมดูล
line-oaไว้ (readAll/read/create/update/delete) แต่ ยังไม่บังคับใช้จริง และไม่มีModuleGateคุม — การควบคุมที่ได้ผลจริงคือการซ่อนเมนูฝั่งหน้าจอ - service อ่าน
userId,lineOaId,organizationIdจาก context ของ request มาประกอบเป็นบริบทผู้ใช้ ทำให้ทั้งการลิสต์และการสร้างถูกผูกกับ OA ที่กำลังทำงานอยู่โดยอัตโนมัติ ไม่ได้รับค่าเหล่านี้จาก payload
token ของลิงก์
- ตอน
POST /api/content-linksbackend เป็นฝ่าย generate token เอง โดยสุ่มจากแหล่งสุ่มเชิงเข้ารหัส (crypto/rand) แล้วแปลงเป็น hex — หน้าจอไม่ได้กำหนด token และไม่ควรพยายามส่งเข้ามา POST /api/content-links/:id/regenerate-tokenเขียน token ใหม่ทับของเดิม ลิงก์เดิมจึงใช้ไม่ได้ทันทีที่เรียกสำเร็จ ไม่มีช่วงผ่อนผันและไม่มีทางย้อนกลับไปใช้ token เก่า จึงเป็นเหตุผลที่หน้าจอต้องมีกล่องยืนยัน- เพราะ token คือสิ่งเดียวที่ป้องกันการเข้าถึงลิงก์สาธารณะ ใครที่ได้ token ไปก็เปิดดูรายการคอนเทนต์ได้ การจำกัดกลุ่มผู้ชม (audience) จึงเป็นชั้นที่ต้องพึ่งการระบุตัวตนผ่าน LIFF ไม่ใช่พึ่งความลับของ token
preview ก่อนส่งลิงก์จริง
GET /api/content-links/:id/preview-contents รับ page กับ limit และคืนรายการคอนเทนต์ที่ลิงก์นี้จะแสดงจริงโดยประเมินเงื่อนไข ณ เวลาที่เรียก ประโยชน์คือใช้ตรวจก่อนส่งว่าเงื่อนไขที่ตั้งไว้ให้ผลตามที่คิด (เช่น ช่วงวันที่แคบเกินไปจนไม่เหลือคอนเทนต์เลย) endpoint นี้ยังถูก Menu Builder เรียกใช้เพื่อ preview เมนูชนิดแสดงคอนเทนต์ในตัวด้วย
สิ่งที่เกิดขึ้นตอนผู้รับเปิดลิงก์
- คำขอจากผู้รับไม่ได้เข้ามาที่
/api/content-linksแต่ไปที่ endpoint สาธารณะGET /api/public/contentsซึ่งไม่ต้องล็อกอิน - endpoint สาธารณะนั้นมี rate limit 10 ครั้งต่อ 60 วินาที ซึ่งเป็นเพดานที่ควรคำนึงถึงเมื่อส่งลิงก์เดียวให้คนจำนวนมากพร้อมกัน หรือเมื่อหน้าปลายทางเรียกซ้ำหลายรอบ (รายละเอียดอยู่ในเอกสารหน้าสาธารณะ)
- ตัวนับจำนวนคลิกที่แสดงบนตารางถูกเพิ่มจากฝั่งการเปิดลิงก์สาธารณะ ไม่ได้เพิ่มจากการกด preview ในหลังบ้าน
ข้อสังเกตอื่น
DELETE /api/content-links/:idตอบกลับด้วยสถานะ 204 (ไม่มีเนื้อหาใน body) ตามสัญญาเดิมของระบบ — โค้ดฝั่งเรียกจึงไม่ควรพยายามอ่าน JSON จาก response นี้- การแก้ไขผ่าน
PATCH /api/content-links/:idรับ null ได้เพื่อล้างเงื่อนไขเดิมออก ตรงกับที่หน้าจอส่งค่าว่างเป็น null อย่างชัดเจน - ตารางที่เกี่ยวข้อง:
content_linkเป็นตารางหลัก อ่านประกอบกับcontent_page,content_category,content_subcategoryและline_oa