System Summary
- Single Cloudflare Worker (
worker.js) handles all requests for pub.ihnyc-rc.org - Static files are served from
./publicvia Cloudflare’s Assets binding with SPA-style fallback for unknown asset paths /api/eventsqueries the events, shifts, and announcements data sources and assembles a unified payload for the landing page, announcements page, and TV board/api/tendersreturns the active tender directory, either from an explicit Pub Tenders DB id or by discovering the tender database through recent shift assignments/api/image/:kind/:pageIdresolves short same-origin image URLs and can mirror Notion-hosted images into R2 so TV/announcement surfaces do not depend on long-lived access to signed Notion file URLs/api/sync-tender-qrsis an authenticated admin route that creates or updates Short.io tender links and writes the resulting QR SVG into Notion/tender/:idis a compatibility redirect into the modal-backed/tenders/experience- API responses are cached at the edge for 2 minutes (
CACHE_TTL_SECONDS = 120) /tv/itself is served withno-storeheaders so screens are less likely to stay on stale frontend assets- No build step; plain HTML, CSS, and JavaScript for all static pages
Sources: worker.js, wrangler.toml
Request Routing
flowchart TD REQ["Incoming Request"] --> WORKER["Cloudflare Worker (worker.js)"] WORKER --> ROUTE{Route type?} ROUTE -- "/api/events" --> EVENTS_CACHE{Edge Cache Hit?} EVENTS_CACHE -- Yes --> EVENTS_CACHED["Return Cached /api/events"] EVENTS_CACHE -- No --> EVENTS_NOTION["Query Notion APIs"] EVENTS_NOTION --> EVENTS_ASSEMBLE["Assemble event payload"] EVENTS_ASSEMBLE --> EVENTS_STORE["Store in edge cache (2 min TTL)"] EVENTS_STORE --> EVENTS_RETURN["Return JSON"] ROUTE -- "/api/tenders" --> TENDERS_CACHE{Edge Cache Hit?} TENDERS_CACHE -- Yes --> TENDERS_CACHED["Return Cached /api/tenders"] TENDERS_CACHE -- No --> TENDERS_NOTION["Query or discover tender DB"] TENDERS_NOTION --> TENDERS_ASSEMBLE["Assemble tender directory"] TENDERS_ASSEMBLE --> TENDERS_STORE["Store in edge cache (2 min TTL)"] TENDERS_STORE --> TENDERS_RETURN["Return JSON"] ROUTE -- "/api/image/:kind/:pageId" --> IMAGE_R2{"R2 mirror hit?"} IMAGE_R2 -- Yes --> IMAGE_R2_RETURN["Return mirrored image from R2"] IMAGE_R2 -- No --> IMAGE_RESOLVE["Resolve Notion file URL"] IMAGE_RESOLVE --> IMAGE_FETCH["Fetch upstream image"] IMAGE_FETCH --> IMAGE_STORE["Store bytes in R2 mirror"] IMAGE_STORE --> IMAGE_RETURN["Return proxied image"] ROUTE -- "/api/sync-tender-qrs" --> QR_AUTH{Authorized POST?} QR_AUTH -- No --> QR_DENY["401 / 405"] QR_AUTH -- Yes --> QR_SYNC["Sync Short.io link + SVG back to Notion"] QR_SYNC --> QR_RETURN["Return JSON summary"] ROUTE -- "/tender/:id" --> REDIRECT["302 redirect to /tenders/?tender=:id"] ROUTE -- "Static asset or page" --> ASSETS["Cloudflare Assets (./public)"] ASSETS --> FOUND{Asset found?} FOUND -- Yes --> SERVE["Serve static file"] FOUND -- No --> DIRCHECK{Slashless directory page?} DIRCHECK -- Yes --> DIRREDIRECT["302 redirect to trailing-slash path"] DIRCHECK -- No --> FALLBACK["Serve index.html (SPA fallback)"]
Sources: worker.js (fetch handler, CACHING section)
Notion Databases
| Env Var | Purpose | Required |
|---|---|---|
NOTION_DATABASE_ID | Pub events for todayEvents and weekEvents | Yes for live events |
NOTION_SHIFTS_DATABASE_ID | Shift windows, pub hours, footer state, and assignment traversal | Optional |
NOTION_ANNOUNCEMENTS_DATABASE_ID | Announcement rows for the slideshow and /announcements/ | Optional |
NOTION_PUB_TENDERS_DATABASE_ID | Explicit tender directory database for /api/tenders | Optional |
NOTION_DATABASE_IDis required when live Notion data is enabled- If
NOTION_API_KEYis absent, the worker skips live Notion queries and returns placeholder data NOTION_API_KEYis required and is stored as a Wrangler secret- If
NOTION_PUB_TENDERS_DATABASE_IDis missing, the worker attempts to discover the directory database by walking recent shift → assignment → tender relations
Sources: worker.js (CONFIG section), wrangler.toml
/api/events Payload Assembly
When /api/events is called, the worker:
- Determines “today” using the normal calendar date in
America/New_York - Queries the Events DB for
todayEventsusing a broad date window, then narrows it to rows whose local Eastern start date equalstodayISO - Queries the Events DB again for
weekEventscovering calendar tomorrow through the next 7 days - Queries the Announcements DB for TV-included announcements, if configured
- Rewrites announcement images to short same-origin
/api/image/announcement/:pageIdURLs - Queries the Shifts DB for the active shift and current pub-tender footer state, if configured
- Rewrites tender photos and tender QR images to short same-origin
/api/image/tender/:pageIdand/api/image/tender-qr/:pageIdURLs - Synthesizes
slidesfrom announcements plus any active tender profiles markedMeet your Tender - Returns
lastUpdated, which the TV client uses as the shared server clock anchor - Builds
pubHoursWeekfrom the Shifts DB, if configured - Returns a unified JSON object used by
/,/announcements/, and/tv/
When an image route is requested, the worker can now use an R2 mirror bucket bound as IMAGE_MIRROR_BUCKET:
- first request: resolve the current Notion file URL, fetch the bytes, store them in R2, then serve the image
- later requests: serve the mirrored copy from R2 when it exists
- stale mirrored objects are refreshed from Notion after
IMAGE_R2_REFRESH_TTL_SECONDS - if the bucket binding is absent, the worker falls back to direct proxying plus edge caching only
Sources: worker.js (handleEventsApi, fetchFromNotion, fetchPubTenderFromNotion)
/api/tenders Payload Assembly
When /api/tenders is called, the worker:
- Resolves the Pub Tenders database id
- Uses
NOTION_PUB_TENDERS_DATABASE_IDdirectly if it exists - Otherwise inspects recent in-pub shifts and follows their assignment relations until it finds a tender page parent database
- Queries the full tender directory database
- Keeps only rows whose
Activecheckbox is true - Maps each row into a lightweight directory card object for
/tenders/
This lets the tender directory work even when the explicit Pub Tenders DB env var has not been set yet, as long as the shifts and assignments relations are healthy.
Sources: worker.js (handleTendersApi, fetchTenderDirectoryFromNotion, resolveTenderDirectoryDatabaseId)
Caching
- Cache TTL: 2 minutes (
CACHE_TTL_SECONDS = 120) - Cache key: full request URL
- Cache stored in Cloudflare’s edge cache, not KV or D1
/api/image/*responses use browser and edge cache headers, and can use R2 as a durable origin mirror whenIMAGE_MIRROR_BUCKETis configured- TV display client fetches
/api/eventswith a timestamp query string andno-storerequest headers, then anchors shared slideshow and featured-event state to the response’slastUpdated /tv/HTML is returned withCache-Control: no-store, no-cache, must-revalidate/tv/soft-refreshes/api/eventsevery 2 minutes, caches the last successful payload inlocalStorage, and repaints from that cache immediately after reload/tv/polls/api/tv-buildevery minute and reloads only when the deployed TV build fingerprint changes, preferably at a slide boundary- Hard
<meta http-equiv="refresh" content="3600">fallback every hour in case JavaScript hangs completely - Overnight serving-hours state on
/and/tv/is computed againstAmerica/New_York, not the device timezone
Sources: worker.js (CACHING section), public/tv/index.html, public/tv/script.js
Static Site Structure
public/
|-- index.html # Landing page with serving hours + nav
|-- styles.css # Shared styles used by several interior pages
|-- assets/ # Logos and static imagery
|-- tv/
| |-- index.html # TV board shell
| |-- styles.css # TV-specific layout and motion
| `-- script.js # TV rendering and refresh logic
|-- announcements/ # Live announcements page
|-- tenders/ # Modal-backed tender directory
|-- pub-tenders/ # Tender-facing static guide
|-- tutorials/
| |-- index.html # Tutorial index card grid
| `-- bar-left-controls/ # Interactive bar-left A/V, lights, and HDMI help page
|-- menu/ # Menu page
|-- specials/ # Specials placeholder
|-- events/ # Events placeholder
`-- resources/ # Resources placeholderSources: public/ directory, wrangler.toml ([assets] binding)