Skip to main content

อ่านบทความเดี่ยว (Content Page)

ภาพรวม

Endpoint ที่ส่งเนื้อหาบทความหนึ่งหน้า รองรับ 3 โหมดที่รูปแบบ response ต่างกัน ได้แก่ โหมด metadata สำหรับสร้าง OG tag ตอน server-render โหมดขอรหัสผ่าน และโหมดเนื้อหาเต็ม พร้อมทั้งจัดการการจำกัดผู้อ่านด้วย audience การนับ view และบล็อก CTA เพิ่มเพื่อน

มีสองเรื่องที่ควรทราบก่อนใช้งาน

  1. พารามิเตอร์ :token รับได้ทั้งค่า publicToken และ slug
  2. กรณีรหัสผ่านผิด ระบบ ตอบ 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

  1. ค้นหา content_page ที่เผยแพร่แล้วด้วย public_token หากไม่พบให้ลองค้นด้วย slug และหากยังไม่พบตอบ 404 Content not found

  2. โหลด translations ของทุกภาษาสำหรับหน้านั้น

  3. โหมด metadataOnly=true — ข้ามการตรวจ audience และรหัสผ่าน และ ไม่นับ view ตอบกลับ {uuid, slug, translations, requirePassword} โดยแต่ละภาษามีเฉพาะ meta/OG field

    โหมดนี้มีการ สร้าง excerpt อัตโนมัติ เมื่อ excerpt, ogDescription และ metaDescription ว่างทั้งหมดแต่มี content อยู่ โดยลบ HTML tag ออก แทนที่   ด้วยช่องว่าง ยุบช่องว่างซ้ำ trim แล้วตัด 200 ตัวอักษรแรก (นับเป็น rune) พร้อมเติมจุดไข่ปลาต่อท้ายหากยาวกว่านั้น

  4. การจำกัด audience — เมื่อ audience_ids ไม่ว่าง

    • หากไม่มี x-liff-token ตอบ 403 Authentication required for this content
    • หากมี จะเรียก VerifyContentAccess ซึ่งใช้ primary channel เท่านั้น ไม่มี fallback ไปยัง channel ของฟอร์ม และ error ทุกกรณีตอบ 403 หาก audience ไม่ทับกันเลยจะตอบ 403 You do not have access to this content
  5. การตรวจรหัสผ่าน — เมื่อ 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 เดียวกันจึงตรวจผ่านได้
  6. ประกอบ translations ฉบับเต็มซึ่งรวม content แล้วเรียก IncrementViewCount

  7. บล็อก friendTrack — หากหน้ามี friend_track_campaign_id ระบบจะ resolve token ของแคมเปญแล้วส่ง {campaignToken, buttonText, lineOaHash, lineLiffId} โดย buttonText มีค่าเริ่มต้นเป็น "เพิ่มเพื่อน" และ lineLiffId มาจาก line_login_info.lineLiffId หรือ .liffId หากไม่มีจะเป็นสตริงว่าง

  8. ตอบกลับ {uuid, slug, translations, requireAuth, friendTrack} ด้วยสถานะ 200

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

รายการค่า
RouteGET /api/public-content/content/:token
Handlerinternal/publiccontent/handler.go(*Handler).GetContentByToken
Serviceinternal/publiccontent/service.go(*Service).GetContentByToken ซึ่งคืนค่าเป็น any เพราะรูปแบบ response ต่างกันตาม branch
HelperautoExcerpt, comparePassword, liffIDFromLoginInfo, isEmptyPtr, jsonArrayLen, extractAudienceIds
Repositoryinternal/contentpage/repository.goFindPublishedByPublicToken, FindPublishedBySlug, FindTranslationsByPageID, IncrementViewCount, FindLineOaRelationByID, FindFriendTrackCampaignTokenByID
Response shapedetailTrans, 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
  • Librarygolang.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