# Advertisements Controller Documentation **File:** `controllers/admin/advertisements.controller.js` **Base URL:** `/api/admin/advertisements` **Guards:** `authenticate → requireAdmin() → adminLimiter` --- ## Table of Contents - [Get All Advertisements](#get-all-advertisements) - [Get Single Advertisement](#get-single-advertisement) - [Create Advertisement](#create-advertisement) - [Update Advertisement](#update-advertisement) - [Archive Advertisement](#archive-advertisement) - [Bulk Archive Advertisements](#bulk-archive-advertisements) - [Restore Advertisement](#restore-advertisement) - [Bulk Restore Advertisements](#bulk-restore-advertisements) - [Get Archived Advertisements](#get-archived-advertisements) - [Get Field Values](#get-field-values) --- ## Status Derivation Status is **never** trusted as stored — it is recomputed on every read and write: | Condition | Derived Status | |-----------|----------------| | `deletedAt` is set | `archived` | | `is_active = false` | `draft` | | `end_date` < now | `expired` | | `start_date` > now | `scheduled` | | Otherwise | `active` | `archived` is the only status that bypasses derivation (set explicitly by archive/restore). --- ## CTAs Each advertisement supports up to **2 CTAs**: ```json [ { "label": "Learn More", "link": "/courses", "variant": "default" }, { "label": "Sign Up", "link": "/register", "variant": "outline" } ] ``` - First CTA defaults to `"default"` variant; second defaults to `"outline"`. - An explicit valid variant from the client (`"default"` or `"outline"`) always wins. - Items beyond 2 are silently discarded. --- ## Get All Advertisements **`GET /api/admin/advertisements`** Returns a paginated list of active (non-deleted) advertisements. Status is resynced on the way out. ### Query Parameters | Parameter | Type | Required | Description | |-----------|--------|----------|-------------| | page | number | No | Default: `1` | | limit | number | No | Default: `10`, max: `1000` | | filters | array | No | JSON array of filter objects | | sort | array | No | JSON array of sort objects | ### Response `200` ```json { "status": "success", "message": "Advertisements retrieved.", "data": [...], "pagination": { "page": 1, "limit": 10, "total": 5, "totalPages": 1 } } ``` --- ## Get Single Advertisement **`GET /api/admin/advertisements/:advertisementId`** Returns one advertisement with its `image` asset and audit user info. ### Response `200` ```json { "status": "success", "message": "Advertisement retrieved.", "data": { "advertisement_id": 1, "uuid": "...", "type": "hero", "status": "active", "badge_label": "New", "headline": "Welcome", "description": "...", "image_url": null, "image_asset_id": 12, "image": { "asset_id": 12, "display_name": "hero.jpg", "file_url": "...", "thumbnail_url": "..." }, "ctas": [{ "label": "Start", "link": "/start", "variant": "default" }], "start_date": "2026-01-01T00:00:00.000Z", "end_date": null, "order": 0, "is_active": true, "size": null, "click_count": 0, "creator": { "user_id": 1, "full_name": "Admin User" }, "updater": null, "createdAt": "2026-01-01T00:00:00.000Z", "updatedAt": "2026-01-01T00:00:00.000Z" } } ``` ### Error Responses | Status | Message | |--------|---------| | `400` | `Invalid advertisement ID.` | | `404` | `Advertisement not found.` | --- ## Create Advertisement **`POST /api/admin/advertisements`** ### Request Body | Field | Type | Required | Description | |-------|------|----------|-------------| | `type` | string | **Yes** | `hero`, `banner`, `popup`, `sidebar` | | `createdBy` | number | **Yes** | User ID of creator | | `badge_label` | string | No | Small label shown on the ad | | `headline` | string | No | Main heading | | `description` | string | No | Body text | | `image_url` | string | No | Direct image URL | | `image_asset_id` | number | No | FK to `assets` table | | `ctas` | array | No | Up to 2 CTA objects `[{ label, link, variant }]` | | `start_date` | date | No | ISO date string | | `end_date` | date | No | ISO date string | | `order` | number | No | Display order. Default: `0` | | `is_active` | boolean | No | Default: `true` | | `size` | string | No | `sm`, `md`, `lg` — banner only | ### Response `201` ```json { "status": "success", "message": "Advertisement created.", "data": { ... } } ``` ### Error Responses | Status | Message | |--------|---------| | `400` | `type is required.` | | `400` | `Invalid type. Must be one of: hero, banner, popup, sidebar` | | `400` | `createdBy is required.` | | `400` | `Invalid size. Must be one of: sm, md, lg` | --- ## Update Advertisement **`PATCH /api/admin/advertisements/:advertisementId`** Partial update. Only fields present in the body are changed. Status is recomputed after all fields are applied. ### Request Body Same optional fields as Create. Does not accept `type` once set. Accepts `updatedBy`. ### Response `200` ```json { "status": "success", "message": "Advertisement updated.", "data": { ... } } ``` --- ## Archive Advertisement **`DELETE /api/admin/advertisements/:advertisementId`** Soft-deletes the advertisement (`deletedAt` set, `status` → `archived`). ### Response `200` ```json { "status": "success", "message": "Advertisement archived." } ``` --- ## Bulk Archive Advertisements **`DELETE /api/admin/advertisements/bulk`** Rate-limited. Soft-deletes multiple advertisements. ### Request Body ```json { "ids": [1, 2, 3], "deletedBy": 1 } ``` ### Response `200` ```json { "status": "success", "message": "3 advertisement(s) archived.", "archived_ids": [1, 2, 3], "skipped_ids": [] } ``` --- ## Restore Advertisement **`PATCH /api/admin/advertisements/:advertisementId/restore`** Restores a soft-deleted advertisement. ### Response `200` ```json { "status": "success", "message": "Advertisement restored.", "data": { ... } } ``` --- ## Bulk Restore Advertisements **`PATCH /api/admin/advertisements/bulk-restore`** ### Request Body ```json { "ids": [1, 2] } ``` ### Response `200` ```json { "status": "success", "message": "2 advertisement(s) restored.", "restored_ids": [1, 2], "skipped_ids": [] } ``` --- ## Get Archived Advertisements **`GET /api/admin/advertisements/archived`** Returns paginated list of soft-deleted advertisements. ### Response `200` ```json { "status": "success", "message": "Archived advertisements retrieved.", "data": [...], "pagination": { ... } } ``` --- ## Get Field Values **`GET /api/admin/advertisements/field-values`** Returns distinct values for filterable advertisement fields. Used by DataTable filter dropdowns. ### Response `200` ```json { "status": "success", "message": "Field values retrieved.", "data": { "type": ["hero", "banner"], "status": ["active", "draft"] } } ```