Skip to main content

จัดการสมาชิก Audience ผ่าน Public API

ภาพรวม

กลุ่ม endpoint /api/audience เป็น API สำหรับให้ระบบภายนอกเรียกใช้งานโดยตรง ไม่ใช่เส้นทางที่ LINE หรือ CMS ใช้ ลูกค้าและพาร์ตเนอร์ที่ถือ api-key สามารถทำสิ่งต่อไปนี้ได้

  • ดูรายการ audience ที่ตนเองเป็นเจ้าของ
  • เพิ่มหรือลบ LINE user ในกลุ่ม audience ครั้งละไม่เกิน 10,000 รายการ
  • ล้างสมาชิกทั้งกลุ่ม

ผู้ใช้งานหลักที่ปรากฏในโค้ดคือระบบชื่อ mookept (คิว mookept_audience และ audience template slug mookept-1 ถึง mookept-4) ซึ่งเป็น integration แบบ B2B ที่ผลักรายชื่อลูกค้าเข้ามาให้เรา ใช้ยิงแคมเปญ

ทุก endpoint ในกลุ่มนี้ต้องผ่านการยืนยันตัวตนด้วย api-key แบบ 3 header (ดู การยืนยันตัวตนด้วย API Key) และถูกจำกัดขอบเขตด้วย row ของตาราง api_key เสมอ (lineOaId, organizationId, apiKeyId) ผู้เรียกจึงมองเห็นและแก้ไขได้เฉพาะ ข้อมูลของตนเองเท่านั้น

Business Flow

1. GET /api/audience — ดูรายการ audience ของผู้เรียก

  1. middleware ยืนยันตัวตน client แล้วเก็บ apiClientId และ apiKey ไว้ใน context
  2. resolve scope ด้วย SELECT ... FROM api_key WHERE api_client_id=$1 AND key=$2 AND status='active'
  3. หากไม่พบ row จะตอบ 200 พร้อมค่า null (เพื่อคง parity กับ NestJS ที่ return undefined)
  4. หากพบ จะดึงรายการด้วย SELECT id, title, slug FROM audience WHERE api_client_id=$1 AND api_key_id=$2

2. PUT /api/audience/:id — เพิ่มหรือลบสมาชิกแบบ asynchronous

  1. แปลง :id เป็น int64 หากแปลงไม่ได้จะตอบ 400 updatememberbyaudienceiddto::id::invalid
  2. ตรวจสอบ DTO ตามกฎ class-validator เดิม
    • lineUserIds — ต้องเป็น array ที่ไม่ว่าง สมาชิกทุกตัวเป็น string และตรงกับรูปแบบ ^U[0-9a-f]{32}$ (ไม่สนตัวพิมพ์เล็กใหญ่) จำนวน 1 ถึง 10,000 รายการ
    • actionType — แปลงเป็นตัวพิมพ์เล็กก่อน แล้วต้องเป็น add หรือ remove
    • isComplete — เป็น boolean และบังคับส่ง
    • lineChannelId — บังคับส่ง
  3. resolve scope จากตาราง api_key หากไม่พบจะตอบ 500 ไม่ใช่ 404 เนื่องจากต้นฉบับ dereference ค่าโดยตรง
  4. publish bare JSON ลงคิว mookept_audience ด้วยรูปแบบ {lineUserIds, actionType, isComplete, lineChannelId, audienceId, organizationId, lineOaId}
  5. รอจนกว่าการ publish จะสำเร็จ แล้วตอบกลับเป็น echo ของคำขอ {fn:"updateMemberByAudienceId", apiClientId, apiKey, id, body}

งานจริง คือการเขียนรายชื่อสมาชิกลงไฟล์ CSV และตารางที่เกี่ยวข้อง จะดำเนินการโดย worker-go

3. DELETE /api/audience/:id — ล้างสมาชิกทั้งกลุ่มแบบ synchronous

  1. แปลง :id เป็นตัวเลข แล้วอ่าน body แบบหลวมเพื่อดึงค่า isComplete (ต้นฉบับไม่มี DTO หาก body ว่างจะได้ค่า false)
  2. Step 1 — หากไม่พบ row ในตาราง api_key จะตอบ 400 "Step 1: Checking line oa by api key"
  3. Step 2 — ค้นหา audience ด้วย SELECT ... WHERE id=$1 AND line_oa_id=$2 AND organization_id=$3 AND api_client_id=$4
    • ไม่พบ จะตอบ 400 "... Audience id (N) not found"
    • หาก status เป็น processing จะตอบ 400 "... Audience id (N) still processing" (ข้อความ error มีอิโมจิกำกับตามต้นฉบับ)
  4. ทำการ reset ด้วยคำสั่ง UPDATE audience SET info=(ค่าเริ่มต้น), status=$2, line_oa_id, organization_id, api_client_id, updated_by=0, updated_date=now WHERE id=$8 โดย status จะเป็น completed เมื่อ isComplete เป็น true และเป็น processing ในกรณีอื่น
  5. ตอบกลับ {code:"RES_SUCCESS_003", message:"Delete successful"}

4. Audience Template (ถูกเรียกจากโมดูลอื่น)

ฟังก์ชัน CreateAudienceTemplate ถูก export ให้ ยืนยันสิทธิ์ LINE OA เรียกใช้ เพื่อสร้าง audience ตั้งต้น 4 กลุ่มให้ลูกค้าใหม่ (slug mookept-1 ถึง mookept-4 ได้แก่ New member, First purchase, Repeat buyer และ Most loyalty customer) การสร้างเป็นแบบ idempotent ต่อ slug หากชื่อซ้ำจะเติม -{epochMs} ต่อท้าย แล้ว insert ด้วยค่า data_source='filter', status='completed', details='' และ created_by=0

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

MethodRouteAuthHandler
GET/api/audienceapi-keyHandler.getListByApiKey
PUT/api/audience/:idapi-keyHandler.updateMemberByAudienceId
DELETE/api/audience/:idapi-keyHandler.deleteAllMembersByAudienceId

ซอร์สโค้ดทั้งหมดอยู่ภายใต้ internal/audience/

ไฟล์ของสำคัญ
handler.goRegister (ครอบด้วย deps.APIKeyAuth()), caller, parseID และ handler ทั้ง 3 ตัว
service.goService.GetListByApiKey, .UpdateMemberByAudienceId, .DeleteAllMembersByAudienceId, .CreateAudienceTemplate, .resolveScope
repository.goFindActiveBySlug, FindActiveByTitle, FindByIDScoped, ListByClientAndKey, Insert, UpdateMembersReset
entity.gostruct Audience, ListItem, AudienceInfo (jsonb Scanner/Valuer), ค่าคงที่ของ status และ systemUser = 0
dto.goUpdateMemberByAudienceIdDto พร้อมเมธอด Rules() ซึ่งเป็น port ของ class-validator

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

  • Postgres — ตาราง audience (อ่านและเขียน), api_key (ใช้กำหนด scope) และ api_client (ใช้ยืนยันตัวตน) เข้าถึงผ่าน sqlx บน pgx โดยใช้ simple query protocol ดูรายละเอียดที่ แกนกลาง HTTP และการเชื่อมฐานข้อมูล
  • RabbitMQ — คิว mookept_audience (env RABBITMQ_QUEUE_MOOKEPT_AUDIENCE_QUEUE) ดูรายละเอียดที่ RabbitMQ Publisher และ Topology
  • worker-go — เป็น consumer ของคิว mookept_audience ทำหน้าที่เพิ่มหรือลบสมาชิกจริงในไฟล์ CSV ของ audience แล้วอัปเดตค่าใน audience.info.stats
  • cms-api — โมดูล audience-management และ audience-filter จัดการ audience ชุดเดียวกันนี้ จากฝั่ง CMS ส่วนโมดูล api-client และ api-key เป็นผู้ออก credential ให้ผู้เรียก
  • โดเมนฐานข้อมูลที่เกี่ยวข้อง: audience และ api-client-key

:::caution ข้อตกลงเรื่อง jsonb คอลัมน์ audience.info ต้องรักษาชื่อฟิลด์ไว้เหมือนเดิมทุกตัวอักษร ({originalFile, lineUserIdsPathFilename, stats:{totalOriginal,totalSuccess,totalFail}}) เพราะเป็น document ที่ใช้ร่วมกันระหว่าง webhook-go, cms-api และ worker-go :::