# ECM Worker · deploy the real backend **Goal:** take ECM on `https://console.pattayatogether.com/ecm/` out of DEMO mode and onto a real backend — without renting a VPS or waiting for DNS. **How:** deploy the Cloudflare Worker shipped in this folder. The Worker ports every access-service and note-service endpoint to JavaScript, backs its state with a KV namespace, and is routed at `console.pattayatogether.com/api/*` so ECM gets a **same-origin** backend (no CORS, cookies just work). **Cost:** $0 on the Cloudflare Free plan for internal-team load. **Time:** 5–10 minutes. --- ## Prerequisites | Item | Check | |------|-------| | Cloudflare zone `pattayatogether.com` is active on your CF account | Already true per Batch 15 | | Node.js 18+ on your machine | `node -v` | | `wrangler` CLI 3.x | `npm i -g wrangler` | --- ## One-time setup From the repo root: ```bash cd docs/ecm/deploy # 1 · log in (opens a browser window) wrangler login # 2 · create the KV namespace the Worker writes to wrangler kv:namespace create ECM_KV # Output: # 🌀 Creating namespace with title "ecm-worker-ECM_KV" # ✨ Success! # Add the following to your configuration file in your kv_namespaces array: # { binding = "ECM_KV", id = "abcd1234…" } # 3 · paste the id into wrangler.toml, replacing REPLACE_ME_KV_NAMESPACE_ID # 4 · (optional) set a shared team secret — users must enter it at login # Leave unset for the "any email in the roster signs in" behaviour that # matches the Python backend's local/dev posture. wrangler secret put DAS_TEAM_SECRET # Paste the secret when prompted. Ctrl-D when done. # 5 · deploy wrangler deploy # Output: # ✨ Success! Uploaded 1 files (…) # Published ecm-worker (x.x sec) # console.pattayatogether.com/api/* ``` --- ## Verify the Worker is live ```bash curl -I https://console.pattayatogether.com/api/access/health # → 200 OK · content-type: application/json curl https://console.pattayatogether.com/api/access/health | jq . # { # "service_version": "ecm-worker-1.0 (batch 18)", # "user_store_loaded": 7, # "team_secret_enforced": false, # or true if you set DAS_TEAM_SECRET # "mode": "worker · same-origin-or-cors" # } curl https://console.pattayatogether.com/api/notes/health | jq . # { "service_version": "…", "notes_total": 0, "store": "kv", "mode": "worker" } ``` If the health endpoints respond with 200, the Worker is routed correctly and the Pages site is still serving everything else on the same host. --- ## Flip ECM into backend mode One of two options: ### Option A · temporary (per browser session · no commit) Visit ECM with the backend query flag: ``` https://console.pattayatogether.com/ecm/?backend=1 ``` `ecm-config.js` sets `ECM_AUTH_BASE = 'same-origin'` for that browser session; ECM probes `/api/access/me`, finds the Worker live, and stays in BACKEND mode. The topbar shows a teal `BACKEND` chip next to the session email. You can also add `?backend=1` to any ECM page (`/ecm/portal.html?backend=1`, `/ecm/documents.html?backend=1`, etc.). The old `/ecm/login.html` was hard-deleted in Batch 20.3 — sign in at `/login.html` first. ### Option B · permanent (one-line commit) Edit `docs/ecm/ecm-config.js` and change the production default: ```js // before: var DEFAULT_AUTH = LOCAL ? 'http://127.0.0.1:8090' : 'none'; var DEFAULT_NOTES = LOCAL ? 'http://127.0.0.1:8091' : 'none'; // after: var DEFAULT_AUTH = LOCAL ? 'http://127.0.0.1:8090' : 'same-origin'; var DEFAULT_NOTES = LOCAL ? 'http://127.0.0.1:8091' : 'same-origin'; ``` Commit + push. GitHub Pages rebuilds in 1–2 minutes. Every ECM page loads in BACKEND mode by default. --- ## Smoke test (cookies + cross-page session) 1. Open `https://console.pattayatogether.com/login.html` (root login) and sign in. Then visit `https://console.pattayatogether.com/ecm/portal.html?backend=1`. 2. Topbar should show the teal `BACKEND` chip. 3. Sign in as `admin@pattayatogether.example`. You should be redirected to the portal. 4. Open a new tab with `/ecm/status.html?backend=1` — the Deployment-identity panel should read: - Current mode: **Real backend configured** - Auth mode state: **authenticated** - Last probe duration: a ms value 5. Open a document, create a note. Refresh. The note persists because it is in KV. 6. Open `/ecm/notes.html?backend=1` in a private window. Sign in as `dev@pattayatogether.example`. The note you just wrote is visible to this user too (shared KV store across all users), as it should be in backend mode. --- ## Roll back The Worker is strictly additive. If anything goes wrong: ```bash wrangler delete ``` …and visit any ECM URL without `?backend=1`. The shell is back to DEMO mode with zero change to the 70-doc UI, no "Failed to fetch", no broken pages. --- ## What this does NOT do | Not covered | Why | |------|-----| | OAuth / SSO login | still email-only (gap-oauth) | | Password hashing | there are no passwords — identity = email (gap-password-hashing) | | SMTP / send-by-email | not in the Worker (gap-smtp) | | HTTP-level gating for direct doc URLs | the Worker doesn't sit in front of `/planning/*.html`; `gap-http-gating` stays open. Use the nginx recipe in `nginx-subdomain.conf` if you need it | | `/api/access/render` | not ported; ECM viewer uses iframe fallback to the source HTML | | Audit trail | no append-only log (gap-audit) | All of these remain honestly surfaced on `ecm/known-gaps.html`. --- ## KV free-tier capacity Cloudflare Free plan KV: - 100,000 reads / day - 1,000 writes / day - 1,000 deletes / day - 100,000 keys total For an internal team with ~10 users and ~200 notes, expected load is a tiny fraction of these quotas. If you exceed them, Cloudflare returns 429 and the ECM shell will fall into DEGRADED mode with a rose banner citing `backend_unreachable`. No data loss; shell keeps working via the localStorage fallback.