Skip to content

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 — added CMS_API_BASE_URL const derived from VITE_CMS_API_VIA_GATEWAY + VITE_API_BASE_URL. CMS_BASE_URL (used by getMediaUrl) is unchanged. cmsFetch() now reads from CMS_API_BASE_URL.
  • about-page.service.ts swapped to CMS_API_BASE_URL for its raw /api/about-page URL builders.
  • frontends/public-fo/DockerfileARG + ENV for the new var.
  • .gitlab-ci/services.yml--build-arg VITE_CMS_API_VIA_GATEWAY on both public-fo build paths (shared template + standalone job).
  • 6 new vitest specs in cms-client.spec.ts cover 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/adminSettings → 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 #162done

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=true in 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: HIT observable on second request for any Strapi-backed page.
  • ✅ Strapi content change → cache bust → next request returns fresh data.
  • GET /cache/stats shows non-zero hits after 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