Backend Clerk verification + Supabase data layer

✅ merged · #7 · zgbrenner/gary ← zgbrenner/gary · opened 7d ago by zgbrenner · merged 7d ago by zgbrenner · self · +492-292 across 19 files · ↗ on GitHub

From the PR description

Summary

Completes the Clerk migration on the backend and consolidates the data layer onto Supabase (database and file storage), so Gary can run without Cloudflare. After this PR, once the backend is deployed, the app works end to end.

Follows PR #6 (frontend Clerk auth). Scope confirmed with the user: backend Clerk verification + schema fix + Supabase Storage.

What changed

Backend authentication

  • requireAuth now verifies Clerk session JWTs via @clerk/backend (verifyToken) instead of Supabase tokens. The Clerk user id flows to res.locals.userId.
  • Clerk session tokens don't carry email, so it's resolved through the Clerk API and cached (5 min). Non-critical - an empty email just yields no shared items.
  • The sharing/"people" endpoints (projects, tabular, workflows) now look users up via Clerk (getClerkUserEmail / listClerkUsers) instead of supabase.auth.admin.
  • Removed the unused Supabase JWT helper (getUserIdFromRequest).

Database schema (backend/schema.sql)

  • user_profiles.user_id and user_api_keys.user_id changed from uuid references auth.users(id) to plain text - Clerk user ids (user_xxx) aren't UUIDs. The other tables already used user_id text.
  • Dropped the on_auth_user_created Supabase Auth trigger; the backend already creates profiles lazily (ensureProfileRow).
  • Schema no longer references auth.users at all.

Account deletion

  • DELETE /user/account deletes the Clerk user + the profile/api-key rows (the previous code called the Supabase Auth admin API).

Storage → Supabase Storage

  • Storage client switched from R2-specific R2_* env vars to generic S3_* vars with a configurable S3_REGION. Supabase Storage is S3-compatible, so the existing @aws-sdk/client-s3 code works against it.
  • Removed the dead, unused frontend/src/lib/storage.ts.

Docs / env

  • New docs/SUPABASE_SETUP.md - step-by-step: create the Supabase project, run the schema, set up the storage bucket + S3 keys, configure and deploy the backend.
  • Updated backend/.env.example, frontend/.env.vercel.example, docs/VERCEL_DEPLOYMENT.md, README.md, docs/README.md. Fixed a stale SUPABASE_SERVICE_ROLE_KEYSUPABASE_SECRET_KEY name mismatch.

Env vars

Backend now needs: CLERK_SECRET_KEY (verifies Clerk tokens) and S3_ENDPOINT_URL / S3_REGION / S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEY / S3_BUCKET_NAME (replacing the R2_* set). SUPABASE_URL / SUPABASE_SECRET_KEY stay (database).

How identity lines up

The frontend useAuth().user.id and the backend res.locals.userId are now the same Clerk user id, so existing ownership checks (row.user_id === user.id) work consistently across the stack.

Test plan

  • Backend tsc build - passes.
  • Frontend next build - passes.
  • Frontend tests (Clerk auth, branding) - pass.
  • No supabase.auth / auth.users / R2_ references remain in code.
  • Manual end-to-end: requires a Supabase project + deployed backend (see docs/SUPABASE_SETUP.md).

Notes

  • Per-user data scoping: most tables filter by user_id; Gary remains effectively single-user. No multi-tenant isolation was added (out of scope).
  • No backend test runner exists in the repo, so backend validation is via tsc. Adding a test framework was left out of scope.

https://claude.ai/code/session_018GMAE164ehpBTxzBdsof9r


Generated by Claude Code

Our analysis

Complete backend Clerk migration and consolidate the data layer on Supabase — read the full analysis →

Think the analysis missed something the PR description covers?

Commits in this PR (1)

SHA Subject Author Date
a7ea67d0 feat: verify Clerk tokens in backend, use Supabase for storage Claude 2026-05-20 ↗ GitHub
commit body
Complete the Clerk migration on the backend side and consolidate the data
layer onto Supabase so Gary can run without Cloudflare.

- Backend auth: requireAuth verifies Clerk session JWTs via @clerk/backend
  instead of Supabase tokens; Clerk user id flows to res.locals.userId
- Resolve user emails through the Clerk API (cached); the sharing/"people"
  endpoints now look users up via Clerk, not Supabase Auth
- Schema: user_profiles and user_api_keys store the Clerk user id as text
  and no longer depend on auth.users; drop the Supabase Auth signup trigger
- Account deletion removes the Clerk user plus the profile/api-key rows
- Storage: switch the S3 client config from R2-specific env vars to generic
  S3_* vars (with a configurable region) so Supabase Storage works
- Remove the dead frontend storage helper and the unused Supabase JWT helper
- Docs: add docs/SUPABASE_SETUP.md and update env examples / deployment docs

https://claude.ai/code/session_018GMAE164ehpBTxzBdsof9r

Capture this PR into my fork

Download a Markdown prompt that tells Claude how to port every commit in this PR into your working tree. Run it via claude -p < capture-pull-7.md from inside the repo you want the changes in.

⬇ Download capture-pull-7.md