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.