Skip to content

Advertising Domain

Manages ad campaigns and advertising tickets that appear inline in the customer-facing Rail Menu. There are two layers: a simple Campaign model scoped to an org/workspace (CMS side), and an AdvertisingTicket model (ticketing side) that renders branded cards alongside ticket groups in a rail.

Handles:

  • Campaign CRUD β€” org/workspace-scoped
  • Advertising ticket CRUD β€” multilingual (BM/EN/CN), multiple ad types
  • Rail Menu assignment β€” link / unlink advertising tickets from rail menus
  • Campaign β†’ advertising ticket linkage (planned)
  • Impression / click tracking (planned)

Architecture

graph LR
    CMS["CMS Frontend"]
    CoreAPI["CoreAPI"]
    TicketingDB["Ticketing DB\n(advertising_ticket,\nrail_menu_advertising_ticket)"]
    CMSDB["CMS DB\n(campaigns)"]

    CMS -->|/api/v1/campaigns| CoreAPI
    CMS -->|/api/advertisingTickets| CoreAPI
    CoreAPI --> CMSDB
    CoreAPI --> TicketingDB

Data Model

erDiagram
    Campaign {
        string id
        string organizationId
        string workspaceId
        string name
        string targetGroupId
        string status
        datetime startDate
        datetime endDate
    }

    AdvertisingTicket {
        uint advertisingTicketId
        string advertisementType
        string advertiser
        string color
        int placement
        string titleBm
        string titleEn
        string titleCn
        string headerBm
        string headerEn
        string headerCn
        string descBm
        string descEn
        string descCn
        string actionButton
        string redirectUrl
        string imageUrl
        string activeStartDate
        string activeEndDate
        bool isActive
    }

    RailMenu ||--o{ RailMenuAdvertisingTicket : "has"
    AdvertisingTicket ||--o{ RailMenuAdvertisingTicket : "placed in"

    RailMenuAdvertisingTicket {
        uint railMenuId
        uint advertisingTicketId
    }

Implementation Status

Campaigns (CMS, /api/v1)

  • GET /campaigns β€” list by org, optional workspace filter
  • POST /campaigns β€” create
  • PUT /campaigns/:campaignId β€” update
  • DELETE /campaigns/:campaignId β€” delete

Advertising Tickets (Ticketing, /api/advertisingTickets)

  • GET /advertisingTickets β€” list all active tickets
  • POST /advertisingTickets β€” create with full multilingual content
  • PUT /advertisingTickets β€” update
  • DELETE /advertisingTickets β€” delete
  • POST /advertisingTickets/railMenus β€” assign ticket to a rail menu
  • DELETE /advertisingTickets/railMenus β€” remove ticket from a rail menu

Ad Types

Type Description
brandImage Full-bleed image with optional CTA button
coupon Discount coupon card with validity window
editorial Article-style card with header and body copy
typographic Text-only card, no image

Endpoints

Campaigns (/api/v1 β€” CMS)

Headers required: X-Organization-ID, optionally X-Workspace-ID.

GET /api/v1/campaigns

List all campaigns for the authenticated org. Optionally filtered to a workspace.

Response 200

[
  {
    "id": "<uuid>",
    "organizationId": "<uuid>",
    "workspaceId": "<uuid>",
    "name": "Summer Sale 2026",
    "targetGroupId": "<uuid>",
    "targetGroupName": "Zoo Visitors",
    "status": "active",
    "startDate": "2026-06-01T00:00:00Z",
    "endDate": "2026-08-31T23:59:59Z",
    "createdAt": "2026-05-01T10:00:00Z",
    "updatedAt": "2026-05-01T10:00:00Z"
  }
]


POST /api/v1/campaigns

Request

{
  "name": "Summer Sale 2026",
  "targetGroupId": "<uuid>",
  "status": "draft",
  "startDate": "2026-06-01T00:00:00Z",
  "endDate": "2026-08-31T23:59:59Z"
}

Response 201

{ "id": "<uuid>" }


PUT /api/v1/campaigns/:campaignId

Request β€” same shape as POST body.

Response 200

{ "message": "Updated" }

Status Meaning
404 Campaign not found or permission denied

DELETE /api/v1/campaigns/:campaignId

Response 200

{ "message": "Deleted" }


Advertising Tickets (/api/advertisingTickets β€” Ticketing)

All routes require Authorization: Bearer <token> with role ADMIN, SYSADMIN, or MEMBER.

GET /api/advertisingTickets

List all advertising tickets with their rail menu assignments.

Response 200 β€” array of AdvertisingTicket objects.


POST /api/advertisingTickets

Request

{
  "advertisementType": "brandImage",
  "advertiser": "Acme Corp",
  "color": "#FF5733",
  "placement": 1,
  "titleBm": "Tajuk BM",
  "titleEn": "English Title",
  "titleCn": "δΈ­ζ–‡ζ ‡ι’˜",
  "headerBm": "Header BM",
  "headerEn": "English Header",
  "headerCn": "δΈ­ζ–‡ζ ‡ι’˜",
  "descBm": "Penerangan dalam BM",
  "descEn": "Description in English",
  "descCn": "中文描述",
  "actionButton": "Buy Now",
  "redirectUrl": "https://example.com/offer",
  "imageUrl": "https://cdn.example.com/ad.jpg",
  "activeStartDate": "2026-06-01",
  "activeEndDate": "2026-08-31",
  "isActive": true
}

Response 201 β€” created ticket object.


PUT /api/advertisingTickets

Update by passing the full ticket object including advertisingTicketId.

Response 200 β€” updated ticket object.


DELETE /api/advertisingTickets

Request body

{ "advertisingTicketId": 42 }

Response 200 β€” success message.


POST /api/advertisingTickets/railMenus

Assign an advertising ticket to a rail menu.

Request

{
  "railMenuId": 1,
  "advertisingTicketId": 42
}


DELETE /api/advertisingTickets/railMenus

Request body β€” same as assign.