อ่านบทความเดี่ยว (Content Page)
ภาพรวม
Endpoint ที่ส่งเนื้อหาบทความหนึ่งหน้า รองรับ 3 โหมดที่รูปแบบ response ต่างกัน ได้แก่ โหมด metadata สำหรับสร้าง OG tag ตอน server-render โหมดขอรหัสผ่าน และโหมดเนื้อหาเต็ม พร้อมทั้งจัดการการจำกัดผู้อ่านด้วย audience การนับ view และบล็อก CTA เพิ่มเพื่อน
มีสองเรื่องที่ควรทราบก่อนใช้งาน
- พารามิเตอร์
:tokenรับได้ทั้งค่าpublicTokenและslug - กรณีรหัสผ่านผิด ระบบ ตอบ HTTP 200 พร้อม body ที่อธิบายสาเหตุ ไม่ใช่ 401 หรือ 403 ซึ่งเป็น quirk ของ controller เดิมที่ถูก port มาตรง ๆ เพื่อรักษา parity
Business Flow
GET /api/public-content/content/:token
Endpoint นี้ไม่มี rate limit ประจำ route แต่ใช้ตัว global ส่วน query parameter ที่รับคือ password และ metadataOnly
-
ค้นหา
content_pageที่เผยแพร่แล้วด้วยpublic_tokenหากไม่พบให้ลองค้นด้วยslugและหากยังไม่พบตอบ 404Content not found -
โหลด translations ของทุกภาษาสำหรับหน้านั้น
-
โหมด
metadataOnly=true— ข้ามการตรวจ audience และรหัสผ่าน และ ไม่นับ view ตอบกลับ{uuid, slug, translations, requirePassword}โดยแต่ละภาษามีเฉพาะ meta/OG fieldโหมดนี้มีการ สร้าง excerpt อัตโนมัติ เมื่อ
excerpt,ogDescriptionและmetaDescriptionว่างทั้งหมดแต่มีcontentอยู่ โดยลบ HTML tag ออก แทนที่ ด้วยช่องว่าง ยุบช่องว่างซ้ำ trim แล้วตัด 200 ตัวอักษรแรก (นับเป็น rune) พร้อมเติมจุดไข่ปลาต่อท้ายหากยาวกว่านั้น -
การจำกัด audience — เมื่อ
audience_idsไม่ว่าง- หากไม่มี
x-liff-tokenตอบ 403Authentication required for this content - หากมี จะเรียก
VerifyContentAccessซึ่งใช้ primary channel เท่านั้น ไม่มี fallback ไปยัง channel ของฟอร์ม และ error ทุกกรณีตอบ 403 หาก audience ไม่ทับกันเลยจะตอบ 403You do not have access to this content
- หากไม่มี
-
การตรวจรหัสผ่าน — เมื่อ
password_protectedเป็นจริง- ไม่ได้ส่ง password มา ตอบ 200 พร้อม
{requirePassword:true, message:"This content is password protected"} - รหัสผ่านผิด ตอบ 200 พร้อม
{requirePassword:true, message:"Invalid password", error:"INVALID_PASSWORD"} - เทียบด้วย bcrypt โดย hash ที่พังรูปหรือว่างจะไม่ match แต่ไม่ throw และ hash ที่ฝั่ง Node เขียนด้วย
bcryptjsใช้ format เดียวกันจึงตรวจผ่านได้
- ไม่ได้ส่ง password มา ตอบ 200 พร้อม
-
ประกอบ translations ฉบับเต็มซึ่งรวม
contentแล้วเรียกIncrementViewCount -
บล็อก friendTrack — หากหน้ามี
friend_track_campaign_idระบบจะ resolve token ของแคมเปญแล้วส่ง{campaignToken, buttonText, lineOaHash, lineLiffId}โดยbuttonTextมีค่าเริ่มต้นเป็น "เพิ่มเพื่อน" และlineLiffIdมาจากline_login_info.lineLiffIdหรือ.liffIdหากไม่มีจะเป็นสตริงว่าง -
ตอบกลับ
{uuid, slug, translations, requireAuth, friendTrack}ด้วยสถานะ 200
ไฟล์และฟังก์ชันหลัก
| รายการ | ค่า |
|---|---|
| Route | GET /api/public-content/content/:token |
| Handler | internal/publiccontent/handler.go → (*Handler).GetContentByToken |
| Service | internal/publiccontent/service.go → (*Service).GetContentByToken ซึ่งคืนค่าเป็น any เพราะรูปแบบ response ต่างกันตาม branch |
| Helper | autoExcerpt, comparePassword, liffIDFromLoginInfo, isEmptyPtr, jsonArrayLen, extractAudienceIds |
| Repository | internal/contentpage/repository.go → FindPublishedByPublicToken, FindPublishedBySlug, FindTranslationsByPageID, IncrementViewCount, FindLineOaRelationByID, FindFriendTrackCampaignTokenByID |
| Response shape | detailTrans, metadataTrans, friendTrackBlock |
จุดเชื่อมต่อกับ Service อื่น
- ฐานข้อมูล — ตาราง
content_page(public_token,slug,password_protected,password_hash,audience_ids,require_auth,friend_track_campaign_id,friend_track_button_text,view_count),content_page_translation,friend_track_campaignและline_oa - Library —
golang.org/x/crypto/bcryptสำหรับเทียบรหัสผ่าน - LIFF authentication — เรียก
VerifyContentAccess - Friend track — บล็อก friendTrack เชื่อมต่อกับฟีเจอร์ friend track โดยเว็บนำ
campaignTokenไปเรียกPOST /friend-track/:token/visit - ฟีเจอร์ที่เกี่ยวข้อง — อยู่ใน package เดียวกับ public content listing และ content link viewer
- client-web — ตรงกับฟีเจอร์
content-page-viewer