จัดการสมาชิก 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 ของผู้เรียก
- middleware ยืนยันตัวตน client แล้วเก็บ
apiClientIdและapiKeyไว้ใน context - resolve scope ด้วย
SELECT ... FROM api_key WHERE api_client_id=$1 AND key=$2 AND status='active' - หากไม่พบ row จะตอบ
200พร้อมค่าnull(เพื่อคง parity กับ NestJS ที่ returnundefined) - หากพบ จะดึงรายการด้วย
SELECT id, title, slug FROM audience WHERE api_client_id=$1 AND api_key_id=$2
2. PUT /api/audience/:id — เพิ่มหรือลบสมาชิกแบบ asynchronous
- แปลง
:idเป็นint64หากแปลงไม่ได้จะตอบ400 updatememberbyaudienceiddto::id::invalid - ตรวจสอบ DTO ตามกฎ class-validator เดิม
lineUserIds— ต้องเป็น array ที่ไม่ว่าง สมาชิกทุกตัวเป็น string และตรงกับรูปแบบ^U[0-9a-f]{32}$(ไม่สนตัวพิมพ์เล็กใหญ่) จำนวน 1 ถึง 10,000 รายการactionType— แปลงเป็นตัวพิมพ์เล็กก่อน แล้วต้องเป็นaddหรือremoveisComplete— เป็น boolean และบังคับส่งlineChannelId— บังคับส่ง
- resolve scope จากตาราง
api_keyหากไม่พบจะตอบ500ไม่ใช่404เนื่องจากต้นฉบับ dereference ค่าโดยตรง - publish bare JSON ลงคิว
mookept_audienceด้วยรูปแบบ{lineUserIds, actionType, isComplete, lineChannelId, audienceId, organizationId, lineOaId} - รอจนกว่าการ publish จะสำเร็จ แล้วตอบกลับเป็น echo ของคำขอ
{fn:"updateMemberByAudienceId", apiClientId, apiKey, id, body}
งานจริง คือการเขียนรายชื่อสมาชิกลงไฟล์ CSV และตารางที่เกี่ยวข้อง จะดำเนินการโดย worker-go
3. DELETE /api/audience/:id — ล้างสมาชิกทั้งกลุ่มแบบ synchronous
- แปลง
:idเป็นตัวเลข แล้วอ่าน body แบบหลวมเพื่อดึงค่าisComplete(ต้นฉบับไม่มี DTO หาก body ว่างจะได้ค่าfalse) - Step 1 — หากไม่พบ row ในตาราง
api_keyจะตอบ400 "Step 1: Checking line oa by api key" - 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 มีอิโมจิกำกับตามต้นฉบับ)
- ไม่พบ จะตอบ
- ทำการ 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ในกรณีอื่น - ตอบกลับ
{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
ไฟล์และฟังก์ชันหลัก
| Method | Route | Auth | Handler |
|---|---|---|---|
| GET | /api/audience | api-key | Handler.getListByApiKey |
| PUT | /api/audience/:id | api-key | Handler.updateMemberByAudienceId |
| DELETE | /api/audience/:id | api-key | Handler.deleteAllMembersByAudienceId |
ซอร์สโค้ดทั้งหมดอยู่ภายใต้ internal/audience/
| ไฟล์ | ของสำคัญ |
|---|---|
handler.go | Register (ครอบด้วย deps.APIKeyAuth()), caller, parseID และ handler ทั้ง 3 ตัว |
service.go | Service.GetListByApiKey, .UpdateMemberByAudienceId, .DeleteAllMembersByAudienceId, .CreateAudienceTemplate, .resolveScope |
repository.go | FindActiveBySlug, FindActiveByTitle, FindByIDScoped, ListByClientAndKey, Insert, UpdateMembersReset |
entity.go | struct Audience, ListItem, AudienceInfo (jsonb Scanner/Valuer), ค่าคงที่ของ status และ systemUser = 0 |
dto.go | UpdateMemberByAudienceIdDto พร้อมเมธอด Rules() ซึ่งเป็น port ของ class-validator |
จุดเชื่อมต่อกับ Service อื่น
- Postgres — ตาราง
audience(อ่านและเขียน),api_key(ใช้กำหนด scope) และapi_client(ใช้ยืนยันตัวตน) เข้าถึงผ่าน sqlx บน pgx โดยใช้ simple query protocol ดูรายละเอียดที่ แกนกลาง HTTP และการเชื่อมฐานข้อมูล - RabbitMQ — คิว
mookept_audience(envRABBITMQ_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
:::