26 — Signage & Digital Displays (product: Beacon)

Tenant-scoped ad catalog, display profiles, public player, bridge push — product name Beacon · see 31 — Domains & Beacon

Product naming (locked for rollout) User-facing name is Beacon. Page title / H1: Beacon Screen. Dashboard nav: Beacon. Target player host: beacon.gopastearth.com. Admin config lives under the dashboard host. Code and API paths may still say signage during transition (/api/signage/*, SignagePlayer.jsx). Domain architecture and cutover phases: Chapter 31.

CEO Summary

Beacon (legacy docs: Signage) lets each tenant manage in-venue digital ads: folders, individual creatives (image, video, URL iframe, HTML snippet), and displays (named players with stable URLs). Data lives in the same per-tenant MySQL as the rest of the app — no separate signage database. Business value: Menu boards, promo loops, lobby screens without a third-party digital-signage contract. Bridge: When the on-prem bridge is connected, catalog changes push a WebSocket signageUpdate so local browsers or custom players can refresh immediately. If this breaks: Admin can still edit the catalog; TVs using the public player only stop updating until the API is back (optional ?reloadSec= on the player URL polls the cloud).

Signage in the platform stack

Fullscreen players are first-class HTTPS clients: they load the public player route and poll or fetch the playlist from the same Nginx → Express path as the marketing site and dashboard SPA. They are called out on the 30 — Full-stack architecture deploy diagram. They do not require the on-site bridge for basic playback; the bridge adds LAN-side instant refresh (WebSocket signageUpdate) when connected — see 13 — Bridge & Devices.

flowchart TB subgraph clients [Clients] TV[Fullscreen signage player] WEB[Staff browser POS / admin] PUB[Public marketing site] end subgraph edge [Edge] NX[Nginx TLS] end subgraph cloud [Cloud app] EX[Express API] end subgraph opt [Optional on-site] BRG[Bridge WebSocket] LAN[Local relay players] end TV -->|playlist assets| NX WEB --> NX PUB --> NX NX --> EX EX -.->|signageUpdate when bridge connected| BRG BRG --> LAN

Architecture

flowchart TB subgraph Admin["Admin (session)"] UI["Signage admin UI /admin/signage"] end subgraph Public["Public (no auth)"] PL["Player SPA
/:tenantSlug/signage/:displaySlug"] end subgraph API["Express"] SR["signage.routes.js
/api/signage"] BR["bridge.routes.js
POST /bridge/notify-signage"] BCM["bridgeConnectionManager
pushEnvelope"] end subgraph Data["Tenant MySQL"] F["SignageFolders"] A["SignageAds"] D["SignageDisplays"] end subgraph Bridge["On-prem Bridge"] WS["WebSocket client"] end UI -->|REST| SR PL -->|GET playlist JSON| SR SR --> F SR --> A SR --> D SR -->|catalogChanged| BCM BR --> BCM BCM -->|signageUpdate JSON| WS

REST API

Mount: app.use('/api/signage', signageRouter) in server.js.

MethodPathAuthPurpose
GET/api/signage/playlist/:tenantSlug/:displaySlugNoneJSON playlist for the player (resolves tenant via master Tenants, then tenant DB)
GET/POST/api/signage/foldersSession + tenantList / create folders
PUT/DELETE/api/signage/folders/:idSession + tenantUpdate / delete (ads in folder become unfiled on delete)
GET/POST/api/signage/adsSession + tenantList (optional ?folderId= / null) / create ad
PUT/DELETE/api/signage/ads/:idSession + tenantUpdate / delete
GET/POST/api/signage/displaysSession + tenantList / create display (slug, optional root_folder_id)
PUT/DELETE/api/signage/displays/:idSession + tenantUpdate / delete

Tenant database tables

Created idempotently on first use (CREATE TABLE IF NOT EXISTS) in ensureSignageTables():

Frontend

Bridge integration

See 13 — Bridge & Devices for WebSocket setup. Signage-specific behavior:

Sequence: catalog save → bridge → local player

sequenceDiagram participant Admin as Admin browser participant API as Express signage routes participant DB as Tenant MySQL participant BCM as bridgeConnectionManager participant WS as Bridge WebSocket participant Local as Local signage client Admin->>API: PUT /api/signage/ads/:id API->>DB: UPDATE SignageAds DB-->>API: OK API->>BCM: pushEnvelope(signageUpdate catalogChanged) BCM->>WS: JSON message API-->>Admin: 200 JSON WS->>Local: forward / handle Local->>API: GET /api/signage/playlist/tenant/display API-->>Local: items[]

Source files

Roadmap (not in current slice)

Daypart schedules, calendar campaigns, device registry heartbeats, and fine-grained RBAC for signage admin are natural extensions; the current module is catalog + player + bridge hooks.