Runbook: Riff #162 — public-fo cms-client cutover to api-gateway /api/cms¶
Status: code shipped (off by default). Operator flip when Cristina's
active UAT closes.
Tracks: Riff #162 (parent: Riff #28 cache plane).
Effort: ~5 min operator + ~5 min pipeline wait + ~5 min smoke.
Why¶
Riff #28 shipped a Redis-backed /api/cms/* proxy on api-gateway with
response caching. public-fo's cms-client.ts was still hitting Strapi
directly via VITE_CMS_URL, so the cache plane sat armed but unused.
Riff #162 wires the cutover behind an opt-in env var so it doesn't disturb any in-flight UAT on public-fo. With the toggle unset (default), behavior is unchanged.
Engineering shipped (commit <this commit>)¶
frontends/public-fo/src/services/cms/cms-client.ts— addedCMS_API_BASE_URLconst derived fromVITE_CMS_API_VIA_GATEWAY+VITE_API_BASE_URL.CMS_BASE_URL(used bygetMediaUrl) is unchanged.cmsFetch()now reads fromCMS_API_BASE_URL.about-page.service.tsswapped toCMS_API_BASE_URLfor its raw/api/about-pageURL builders.frontends/public-fo/Dockerfile—ARG+ENVfor the new var..gitlab-ci/services.yml—--build-arg VITE_CMS_API_VIA_GATEWAYon both public-fo build paths (shared template + standalone job).- 6 new vitest specs in
cms-client.spec.tscover default-off, truthy values, missing-API-base fallback, trailing-slash handling, and the unknown-value falsy guardrail.
Operator flip — when Cristina's UAT closes¶
1. Add the CI/CD variable¶
GitLab → Settings → CI/CD → Variables → Add variable:
| Field | Value |
|---|---|
| Key | VITE_CMS_API_VIA_GATEWAY |
| Value | true |
| Protected | off (so unprotected branches can rebuild during smoke) |
| Masked | off (it's a non-secret toggle) |
| Environment scope | All (or *) |
2. Configure Strapi cache-bust webhook (qual + prod)¶
Without webhooks, the cache stays warm beyond CMS edits and Cristina /
operators see stale content for up to CACHE_TTL_CMS_SECONDS (300s
qual/prod, 60s dev). Add a webhook so Strapi pings api-gateway to evict
relevant entries on every publish.
In https://cms.qual.portugalodyssey.pt/admin → Settings → Webhooks
→ Add new webhook:
| Field | Value |
|---|---|
| Name | api-gateway cache bust (qual) |
| URL | https://api.qual.portugalodyssey.pt/cache/bust |
| Headers | X-Cache-Bust-Token: ${CACHE_BUST_TOKEN_QUAL} |
| Body | {"patterns": ["cms:*"]} (broad flush; per-model can land later) |
| Events | entry.create, entry.update, entry.delete, entry.publish, entry.unpublish |
Repeat for prod when prod stack first deploys (api.portugalodyssey.pt/cache/bust + ${CACHE_BUST_TOKEN}).
The CACHE_BUST_TOKEN_QUAL value must match the env var set on
api-gateway-qual (per .env.qual). Generate one with
openssl rand -hex 32 if not already provisioned.
3. Trigger a public-fo rebuild¶
Vite bakes env vars at build time. Push any trivial change to
frontends/public-fo/ (or run the manual pipeline) — the
docker-build-public-fo job will pick up the new build arg and emit a
bundle with the gateway-routed cms-client baked in.
# Trivial trigger (CI parses + redeploys; cache plane activates after deploy-qual)
echo "// Riff #162 cutover active" >> frontends/public-fo/src/main.tsx
git add frontends/public-fo/src/main.tsx
git commit -m "[sA] chore(public-fo): activate Riff #162 cms cache cutover"
git push
4. Smoke¶
# Direct probe — public-fo bundle should now embed /api/cms URLs
curl -sI https://qual.portugalodyssey.pt/ | head -5
# Hard probe — load the home page in a browser, open devtools Network tab.
# CMS calls should now go to api.qual.portugalodyssey.pt/api/cms/*
# (not cms.qual.portugalodyssey.pt directly).
# Cache hit confirmation — second visit should show X-Cache: HIT header.
curl -sI https://api.qual.portugalodyssey.pt/api/cms/api/partner-categories | grep -i x-cache
# Stats endpoint
curl -s https://api.qual.portugalodyssey.pt/cache/stats | jq
5. Confirm media URLs still work¶
getMediaUrl() deliberately uses CMS_BASE_URL (direct Strapi origin),
not the gateway, because the gateway doesn't proxy /uploads/*. After
the cutover, any image / file embedded from Strapi should still load
from cms.qual.portugalodyssey.pt/uploads/... — verify by inspecting
the rendered <img src=...> on a partner detail page.
6. Flip Riff #162 → done¶
After smoke is green and Cache:HIT confirmed on second request.
Rollback¶
If anything misbehaves post-cutover, revert by removing the CI variable and triggering one more public-fo rebuild:
# In GitLab UI: Settings → CI/CD → Variables → delete VITE_CMS_API_VIA_GATEWAY
git commit --allow-empty -m "[sA] chore(public-fo): rollback Riff #162 cutover"
git push
The next bundle reverts to direct Strapi origin. Zero code change needed.
Acceptance criteria (Riff #162)¶
- ✅
VITE_CMS_API_VIA_GATEWAY=truein CI variables. - ✅ Strapi webhook configured on qual (and prod when applicable).
- ✅ public-fo home + services + partner pages load through
api-gateway/api/cms. - ✅
X-Cache: HITobservable on second request for any Strapi-backed page. - ✅ Strapi content change → cache bust → next request returns fresh data.
- ✅
GET /cache/statsshows non-zerohitsafter warm-up. - ✅ Media URLs continue to load from
cms.qual.portugalodyssey.pt.
Cross-references¶
- Riff
#162: tasks-prod - Cache plane (parent, Riff
#28):services/api-gateway/README.md§ Cache layer - cms-client:
frontends/public-fo/src/services/cms/cms-client.ts