Skip to main content

Managing Audience Members via Public API

Overview

The /api/audience endpoints form a public API intended for external systems to call directly — not for LINE and not for the CMS. Customers and partners holding an api-key can:

  • List the audiences they own
  • Add or remove LINE users from an audience, up to 10,000 IDs per call
  • Clear all members from an audience

The primary consumer visible in the code is a system called mookept (the mookept_audience queue and the audience template slugs mookept-1 through mookept-4) — a B2B integration that pushes customer lists into our platform for campaign targeting.

Every endpoint in this group requires three-header api-key authentication (see API Key Authentication) and is always scoped by the caller's api_key row (lineOaId, organizationId, apiKeyId), so a caller can only see and modify its own data.

Business Flow

1. GET /api/audience — list the caller's audiences

  1. The middleware authenticates the client and stores apiClientId and apiKey in the context.
  2. Resolve the scope with SELECT ... FROM api_key WHERE api_client_id=$1 AND key=$2 AND status='active'.
  3. If no row matches, respond 200 with null — parity with the NestJS original, which returned undefined.
  4. Otherwise, fetch the list with SELECT id, title, slug FROM audience WHERE api_client_id=$1 AND api_key_id=$2.

2. PUT /api/audience/:id — add or remove members asynchronously

  1. Parse :id as an int64; on failure respond 400 updatememberbyaudienceiddto::id::invalid.
  2. Validate the DTO against the original class-validator rules:
    • lineUserIds — a non-empty array of strings, each matching ^U[0-9a-f]{32}$ (case-insensitive), with a length between 1 and 10,000.
    • actionType — lowercased first, then required to be either add or remove.
    • isComplete — a required boolean.
    • lineChannelId — required.
  3. Resolve the scope from api_key. If no row is found the response is 500, not 404, because the original code dereferenced the value directly.
  4. Publish bare JSON to the mookept_audience queue in the shape {lineUserIds, actionType, isComplete, lineChannelId, audienceId, organizationId, lineOaId}.
  5. Wait for the publish to succeed, then echo the request back as {fn:"updateMemberByAudienceId", apiClientId, apiKey, id, body}.

The real work — writing the member list into the audience CSV and its related tables — is carried out by worker-go.

3. DELETE /api/audience/:id — clear all members synchronously

  1. Parse :id, then loosely read the body to extract isComplete. The original had no DTO here, so an empty body yields false.
  2. Step 1 — if no api_key row is found, respond 400 "Step 1: Checking line oa by api key".
  3. Step 2 — look up the audience with SELECT ... WHERE id=$1 AND line_oa_id=$2 AND organization_id=$3 AND api_client_id=$4
    • Not found responds 400 "... Audience id (N) not found".
    • A status of processing responds 400 "... Audience id (N) still processing". (The error strings include the emoji carried over from the original.)
  4. Reset the record — UPDATE audience SET info=(default), status=$2, line_oa_id, organization_id, api_client_id, updated_by=0, updated_date=now WHERE id=$8 — where status becomes completed when isComplete is true and processing otherwise.
  5. Respond with {code:"RES_SUCCESS_003", message:"Delete successful"}.

4. Audience templates (invoked from another module)

CreateAudienceTemplate is exported so that LINE OA Verification can call it. It seeds four starter audiences for a new customer — slugs mookept-1 through mookept-4, namely New member, First purchase, Repeat buyer, and Most loyalty customer. Creation is idempotent per slug; when a title collides, -{epochMs} is appended before inserting with data_source='filter', status='completed', details='', and created_by=0.

Key Files & Functions

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

All source lives under internal/audience/.

FileHighlights
handler.goRegister (wrapped in deps.APIKeyAuth()), caller, parseID, and the three handlers
service.goService.GetListByApiKey, .UpdateMemberByAudienceId, .DeleteAllMembersByAudienceId, .CreateAudienceTemplate, .resolveScope
repository.goFindActiveBySlug, FindActiveByTitle, FindByIDScoped, ListByClientAndKey, Insert, UpdateMembersReset
entity.gostruct Audience, ListItem, AudienceInfo (jsonb Scanner/Valuer), status constants, systemUser = 0
dto.goUpdateMemberByAudienceIdDto and its Rules() method — a port of the class-validator schema

Connections to Other Services

  • Postgres — the audience table (read and write), plus api_key for scoping and api_client for authentication, accessed through sqlx over pgx using the simple query protocol. See Core HTTP & Database Access.
  • RabbitMQ — the mookept_audience queue (env RABBITMQ_QUEUE_MOOKEPT_AUDIENCE_QUEUE); see RabbitMQ Publisher & Topology.
  • worker-go — consumes mookept_audience, performs the actual member add/remove against the audience CSV file, and updates audience.info.stats.
  • cms-api — the audience-management and audience-filter modules manage the same audiences from the CMS side, while api-client and api-key issue the caller's credentials.
  • Related database domains: audience and api-client-key.

:::caution JSONB contract The audience.info column must preserve its field names exactly ({originalFile, lineUserIdsPathFilename, stats:{totalOriginal,totalSuccess,totalFail}}), because the document is shared between webhook-go, cms-api, and worker-go. :::