Skip to main content

รายการบทความและหมวดหมู่สาธารณะ

ภาพรวม

สอง endpoint ที่ให้หน้าเว็บดึงรายการบทความที่เผยแพร่แล้วของ OA หนึ่ง พร้อมตัวกรอง (หมวดหมู่ หมวดหมู่ย่อยแบบไล่ลงไปถึงลูกทั้งหมด คำค้น และช่วงวันที่เผยแพร่) รวมถึงดึงรายการหมวดหมู่และหมวดหมู่ย่อยสำหรับ filter bar

ทั้งสอง endpoint เป็น endpoint สาธารณะที่ไม่ต้องใช้ LIFF token จึงมี validation ที่เข้มงวดและมี rate limit 10 ครั้งต่อ 60 วินาที ต่อหนึ่ง IP

Business Flow

รายการบทความ — GET /api/public-content/contents

การตรวจสอบ query parameter

Parameterเงื่อนไข
lineOaIdบังคับ เป็นจำนวนเต็มตั้งแต่ 1 ขึ้นไป
categoryId / subcategoryIdไม่บังคับ เป็นจำนวนเต็มตั้งแต่ 1 ขึ้นไป
searchไม่บังคับ ถูก sanitize ก่อน validate โดย trim แล้วลบอักขระพิเศษทิ้ง จากนั้นต้องผ่าน pattern ^[a-zA-Z0-9฀-๿\s\-_]*$ และยาวไม่เกิน 100 ตัวอักษร
publishedDateFrom / publishedDateToไม่บังคับ ต้องอยู่ในรูปแบบ ISO 8601
pageไม่บังคับ ค่าระหว่าง 1–100
limitไม่บังคับ ค่าระหว่าง 1–50

Business rule

  1. validateLineOa — แถวใน line_oa ต้อง active และไม่ถูก soft-delete มิฉะนั้นตอบ 404 Invalid or inactive channel
  2. validateDateRange — เมื่อระบุมาทั้งสองด้าน ช่วงที่เกิน 365 วันตอบ 400 Date range cannot exceed 365 days และวันเริ่มต้นที่อยู่หลังวันสิ้นสุดตอบ 400 publishedDateFrom must be before publishedDateTo หากด้านใดด้านหนึ่ง parse ไม่ได้จะข้ามการตรวจไป
  3. คำค้นที่สั้นกว่า 2 ตัวอักษรตอบ 400 Search term must be at least 2 characters
  4. ค่าเริ่มต้นคือ page=1 และ limit=10 โดย skip = (page-1)*limit และ หาก skip เกิน 500 จะตอบ 400 Page number exceeds maximum allowed เพื่อป้องกัน deep pagination
  5. เมื่อระบุ subcategoryId ระบบจะขยายเป็น หมวดหมู่ย่อยลูกหลานทั้งหมดที่ active ด้วย recursive CTE ผ่าน FindActiveDescendantIDs แล้วกรองด้วยชุด id นั้น ผู้ใช้จึงเลือกหมวดย่อยระดับบนแล้วเห็นบทความของหมวดลูกด้วย
  6. ดึงข้อมูลหนึ่งหน้าแล้ว hydrate เพิ่ม — โหลด content_page_translation ทุกภาษาใน batch เดียว ผ่าน FindTranslationsByPageIDs และโหลดข้อมูลอ้างอิงหมวดหมู่/หมวดหมู่ย่อย (id, name, slug) โดย cache ต่อหนึ่งหน้า เพื่อไม่ให้เกิดปัญหา N+1 ข้อมูลอ้างอิงนี้อ่านแบบไม่กรอง active หรือ soft-delete เพื่อให้ผลลัพธ์ตรงกับพฤติกรรมเดิม และหมวดหมู่ที่หายไปจะไม่ปรากฏ key ในผลลัพธ์
  7. ตอบกลับ {data, total, page, limit, totalPages} โดย totalPages คำนวณจาก ceil(total/limit)

รายการหมวดหมู่ — GET /api/public-content/contents/categories

  • lineOaId ถูกอ่านแบบ raw โดยไม่ผ่าน DTO ค่าที่ขาดหายหรือไม่ใช่ตัวเลขจึงกลายเป็น 0 และ validateLineOa จะตอบ 404
  • คืนหมวดหมู่ที่ active ของ OA นั้นพร้อมหมวดหมู่ย่อยที่ active จัดกลุ่มไว้ใต้ category_id โดย repository เรียงลำดับตาม sort_order มาให้แล้ว
  • หมวดหมู่ที่ไม่มีหมวดหมู่ย่อยจะคืนค่าเป็น array ว่าง ไม่ใช่ null

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

RouteRate limitHandler
GET /api/public-content/contents10/60sinternal/publiccontent/handler.go(*Handler).ListPublicContents
GET /api/public-content/contents/categories10/60s(*Handler).ListPublicCategories
  • internal/publiccontent/register.goRegister(r, deps) ทำหน้าที่ mount ทั้ง 4 route ของโดเมนนี้
  • internal/publiccontent/service.goListPublicContents, ListPublicCategories, validateLineOa, validateDateRange, buildListItems, mapToPublicListItem, parseJSDate
  • internal/publiccontent/handler.gocontentsDTO, byLinkDTO, numberTransform, searchTransform, isDateStringCheck
  • Repository — internal/contentpage/repository.go (ListPublished, FindTranslationsByPageIDs), internal/contentcategory/repository.go และ internal/contentsubcategory/repository.go (FindActiveDescendantIDs)

จุดเชื่อมต่อกับ Service อื่น

  • ฐานข้อมูล — ตาราง content_page, content_page_translation, content_category, content_subcategory และ line_oa
  • Middlewaremiddleware.RateLimit(10) ทำงานต่อ route และต่อ IP
  • ฟีเจอร์ที่เกี่ยวข้อง — ใช้ repository ร่วมกับ content link viewer และ content page viewer เนื่องจากทั้งสาม endpoint อยู่ใน package publiccontent เดียวกัน
  • client-web — ตรงกับฟีเจอร์ content-link-listing (หน้ารายการพร้อมค้นหาและ infinite scroll) และ content-page-viewer ที่นำ metadata ของรายการไปใช้