Migrate infrastructure to AWS (SST + Fargate + RDS + Clerk + S3 + SES) (#1)

↗ view on GitHub · Sarat Pediredla · 2026-05-15 · 03011e63

* docs: add CLAUDE.md for repo navigation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* infra: scaffold SST + Fargate Dockerfile

* db: add Drizzle schema and migration, swap supabase-js dep

Translates the Supabase schema in backend/schema.sql to a Drizzle ORM
definition targeting AWS RDS/Aurora Postgres. Lays the foundation for
Stages D/E, which rewrite the routes and middleware off supabase-js.

- backend/src/db/schema.ts: 16 tables translated 1:1 with schema.sql.
  user_id columns that previously referenced auth.users(id) are now
  plain text (Clerk owns identity). RLS policies, the handle_new_user
  trigger, and Supabase grants are not modelled - access control moves
  to the Express layer.
- backend/drizzle.config.ts: drizzle-kit config with a dummy default
  DATABASE_URL so `generate` works without env setup.
- backend/drizzle/0000_init.sql: initial migration with all tables,
  indexes (including GIN on shared_with), check constraints, and
  foreign keys. Prepended CREATE EXTENSION pgcrypto. Manually appended
  the two circular FKs (documents.current_version_id and
  document_edits.chat_message_id) that Drizzle could not emit inline.
- backend/package.json: dropped @supabase/supabase-js; added
  drizzle-orm, drizzle-kit, pg, @types/pg, @clerk/backend. Added
  db:generate / db:migrate / db:push / db:studio scripts.

The legacy backend/schema.sql is kept in place until upstream merges
clear; Stage D will start removing supabase.ts and the route consumers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* auth: swap Supabase verification for Clerk JWT + add Drizzle client

- Drop @supabase/supabase-js from middleware/auth.ts; verify Clerk JWTs
  with @clerk/backend (verifyToken), supporting both JWKS and offline
  jwtKey verification.
- First-request profile bootstrap (in-memory cache) replaces the old
  Supabase handle_new_user trigger.
- Add backend/src/lib/db.ts exporting Drizzle client + pg.Pool +
  withTransaction helper.
- Delete backend/src/lib/supabase.ts. Routes and several libs still
  import it; Stages E1/E2 will rewrite them.

tsc --noEmit fails (42 errors), all confined to files Stage E1/E2 will
rewrite. db.ts and middleware/auth.ts typecheck cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* routes: migrate simpler routes to Drizzle (Stage E1)

Rewrite user, downloads, workflows, and projectChat routes - plus the
access, userSettings, and userApiKeys libs - to query Postgres directly
via Drizzle instead of Supabase. Brings the backend tsc error count from
40 to 24; remaining errors are all in Stage E2 files (chat, projects,
documents, tabular, chatTools, documentVersions).

- Lib helper signatures keep a trailing optional `_db` arg so out-of-scope
  E2 callers don't all immediately break on extra-argument errors before
  Stage E2 lands.
- Workflows route now backfills sharer display names via the Clerk
  Backend API instead of the Supabase admin listUsers call.
- `/user/account` no longer calls supabase.auth.admin.deleteUser; it
  removes the local profile row and leaves Clerk-side deletion to a
  separate path.
- userApiKeys keeps the AES-GCM crypto unchanged; only the DB plumbing
  swapped to Drizzle's onConflictDoUpdate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* routes: migrate heavy routes + chatTools to Drizzle (Stage E2)

Rewrites projects, documents, tabular, chat, chatTools, and
documentVersions to use Drizzle queries against the shared db
singleton. Drops the trailing _db?: unknown bridge parameter that
Stage E1 added to access/userSettings/userApiKeys helpers.

Behavioral notes:
- Member email->id lookups in projects.ts and tabular.ts now use
  Clerk's users.getUserList({ emailAddress: [...] }) and
  users.getUser(id) instead of Supabase's auth.admin.listUsers.
- chat.ts /chat OR-filter converted from Supabase's PostgREST .or()
  string syntax to Drizzle's or(eq, inArray).
- Dead buildTabularContext helper removed from tabular.ts (no
  callers anywhere).
- jsonb inserts retain as any casts to match Stage E1 pattern.

tsc --noEmit -p backend: 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* storage+email: swap R2 for S3, Resend for SES (Stage F)

- Rewrite backend/src/lib/storage.ts to use native AWS S3 via the default credential chain (Fargate task role). Drop R2 endpoint, forcePathStyle, and explicit access-key env vars. BUCKET reads S3_BUCKET_NAME with a transitional R2_BUCKET_NAME fallback.
- Add backend/src/lib/email.ts wrapping @aws-sdk/client-sesv2 with a sendEmail helper that no-ops when SES_FROM_ADDRESS is unset.
- Swap deps in backend/package.json: drop resend, add @aws-sdk/client-sesv2. Refresh package-lock.json.
- No existing Resend call sites in backend/src - helper added for future use.

tsc --noEmit -p backend: 0 errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* frontend: swap Supabase auth for Clerk (Stage G)

Replaces every frontend Supabase auth touchpoint with Clerk equivalents. ClerkProvider wraps the app, clerkMiddleware protects routes, and Clerk's hosted SignIn/SignUp components render at /login and /signup via catch-all segments. mikeApi reads the bearer token from window.Clerk.session, and hooks/components that previously called supabase.auth.getSession() now use useAuth().getToken() from @clerk/nextjs. AuthContext is removed; consumers read user/userId from Clerk's useAuth/useUser hooks directly.

Notes:
- @clerk/nextjs 7.3.3 requires react ~19.2.3; project pins 19.2.0, so npm install was run with --legacy-peer-deps. Runtime works correctly.
- tagWIdsOnRenderedDom in DocxView refactored to accept a token argument since it runs outside React hooks.
- tsc --noEmit -p frontend: clean. Lint: 104 problems (-1 vs baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* frontend: swap OpenNext Cloudflare adapter for AWS (Stage H)

* docs: update README and CLAUDE.md for AWS stack (Stage I)

* docs: warn about gh pr create defaulting to upstream on forks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* backend+frontend: delete Clerk user on account close; drop --legacy-peer-deps

- DELETE /user/account now removes the Clerk identity before nuking the
  local user_profiles row. Without this the frontend's "Delete account"
  flow only cleared the profile, leaving the Clerk user able to sign
  back in. Exports getClerkClient + new forgetUser helper from the auth
  middleware so the route can reuse the same cached Clerk client and
  evict the just-deleted user from the in-process bootstrap caches.
- Bump react/react-dom to ^19.2.6 so npm install no longer needs
  --legacy-peer-deps for @clerk/nextjs@7.3.3 (which peer-deps ~19.2.3).
  CLAUDE.md and README.md updated to drop the workaround note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Repository LevelFive-Studio/helix-tribune
Author Sarat Pediredla <sarat.pediredla@level5.ventures>
Authored
Parents 56c6051f
Stats 73 files changed , +8560 , -6343
Part of AWS migration: SST + Fargate + RDS + Clerk + S3 + SES

Capture this commit into my fork

Download a Markdown prompt that tells Claude how to port this exact commit into your working tree. Run it via claude -p < capture-commit-03011e63.md from inside the repo you want the change in.

⬇ Download capture-commit-03011e63.md