Content Admin

/admin is the CMS for everything the public site serves — docs, firmware source, and CAD files — editable without a redeploy. Restricted to team members with a Supabase login.


Sign In

/admin/login — email + password, checked against Supabase Auth (signInWithPassword). On success the server sets an eb-admin cookie (httpOnly, secure, sameSite=lax, 24h expiry) containing the Supabase access token; every admin API route checks for that cookie and returns 401 without it. Log out (top-right of /admin) clears the cookie via DELETE /api/admin/auth.

There's no self-service signup — accounts are created directly in the Supabase project's Auth panel by whoever administers the project.


Docs Tab

Edit any of the doc pages the sidebar lists (this site) as raw markdown:

  1. Pick a page from the slug list.
  2. Edit the textarea.
  3. SavePUT /api/admin/docs → upserts a row in docs_pages, keyed by slug.

Once a slug has a docs_pages row, it permanently overrides the hardcoded fallback in lib/docs-content.ts — a future code-level edit to that page silently does nothing until the DB row is removed or re-saved. See the precedence note in Platform Overview.


Code Tab

Manages the Code Browser's file list.

  • Upload: pick a .cpp / .h / .ino / .c file → uploads to R2 (POST /api/admin/upload) → creates a code_files row (POST /api/admin/code).
  • Delete: removes both the R2 object and the DB row.
  • Bulk reseed: POST /api/admin/seed-code re-uploads every firmware file embedded by sync-code.mjs in one shot — the normal way to push a firmware update, rather than uploading files one at a time. See Platform Overview for the full pipeline.

Design Tab

Same upload/delete pattern as Code, for the CAD Viewer: accepts .glb .gltf .step .stp .stl .sldasm .sldprt, writes to design_files.


Runs Tab

Removed. Run data is never stored server-side — it goes robot → downloaded results.json → dropped into the Visualiser in the browser. There is nothing to administer.


API Reference

All routes below require the eb-admin cookie (401 without it).

Method Endpoint Purpose
GET / PUT /api/admin/docs Fetch / upsert a doc page ({slug, title?, content})
GET / POST / DELETE /api/admin/code List / create / delete a code_files row
GET / POST / DELETE /api/admin/design List / create / delete a design_files row
POST /api/admin/upload?key=&type= Raw-byte upload to R2 at key
POST /api/admin/seed-code Bulk-reseed all firmware files from the embedded sync-code.mjs output
POST / DELETE /api/admin/auth Sign in / sign out

Every create/delete flow that touches R2 does the storage operation first, then the DB write — a failed DB write after a successful upload leaves an orphaned R2 object (harmless, just unreferenced) rather than a DB row pointing at nothing.


Security Notes

  • SUPABASE_SERVICE_ROLE_KEY (used by every admin route to bypass RLS) is a server-only secret — never sent to the browser, never logged.
  • The eb-admin cookie is the only gate on admin routes — there's no per-route role check, so any authenticated account has full access to every tab.
  • The firmware carries no private credentials to leak: WiFi is AP-only, and the RobotAP SSID/password are printed on the robot for judges anyway. sync-code.mjs therefore publishes firmware verbatim.