Make Mike runnable locally: Postgres + Auth.js + MinIO
From the PR description
Summary
Make the project end-to-end runnable from docker compose up -d alone, with no external accounts required for first-run development. Two coupled efforts under one branch:
- Replace Supabase with local Postgres + Auth.js + Drizzle (ADR-0001) - Supabase handled both DB hosting and auth; both now run locally. Postgres 16 in Docker, Auth.js v5 (Credentials provider, JWT-in-cookie), Drizzle ORM as the single source of truth for the schema. Cookie-based auth bridge between Auth.js (Next.js) and the Express backend via a Next.js rewrite for same-origin.
- Add MinIO for local S3-compatible object storage (ADR-0002) - backend storage layer was R2-only; now provider-agnostic via two new env vars (
R2_REGION,R2_FORCE_PATH_STYLE) whose defaults preserve current R2 prod behavior. MinIO + a one-shotmcsidecar bring up a workingmikebucket on every compose-up.
Also folded in: deleted unused frontend storage.ts duplicate (frontend never holds S3 creds - only signed URLs), uninstalled the now-unused @aws-sdk/* and resend packages, and tightened the local-development runbook.
What's new in docs/
docs/adr/0001-...anddocs/adr/0002-...capture the two architectural decisions in the same Context / Decision / Consequences / Alternatives shape.docs/runbooks/local-development.md,architecture.md,schema-changes.md,troubleshooting.md- first-time runbook coverage.docs/deferred.mdtracks the consciously-deferred items (production cookie-name salt, generic 500 responses, MinIO image-tag pinning, R2_* → S3_* rename, frontend Next.js peer-dep conflict, lint baseline, etc.).docs/superpowers/specs/anddocs/superpowers/plans/contain the design + implementation plans for both efforts.
Test plan
-
docker compose up -dbrings upmike-postgres,mike-minio, andmike-minio-init(the init exits 0 - that's success). -
docker compose ps -ashowsmike-postgresandmike-miniohealthy. - MinIO web console at http://localhost:9001 (
minioadmin/minioadmin) shows themikebucket. -
npm run db:migrate --prefix backendapplies the Drizzle migration against the fresh Postgres. -
npm run build --prefix backendandnpm run build --prefix frontendboth succeed. -
npm install --prefix frontend --legacy-peer-depssucceeds (the flag is required and documented; tracked indocs/deferred.md). - Sign up via the UI at http://localhost:3000, log in, create a project.
- Upload a document - file appears under
documents/<userId>/<docId>/...in the MinIO console. - Trigger a download (signed URL flow) - the file downloads in the browser.
- Delete the document - object disappears from the bucket.
Known follow-ups (tracked in docs/deferred.md, not blocking this PR)
- Pre-prod hardening: production cookie salt, generic 500 responses, same-origin enforcement.
- Pin MinIO image tags away from
:latest. - Rename
R2_*env vars to a neutralS3_*prefix (deferred until a real provider swap). - Resolve the
next↔@opennextjs/cloudflarepeer-dep conflict so--legacy-peer-depsbecomes unnecessary. - Frontend lint baseline (38 errors / 69 warnings - none introduced here).
🤖 Generated with Claude Code
Our analysis
Project meta: CLAUDE.md, Serena workspace, runbooks, ADRs, deferred tracker — read the full analysis →
Final Supabase teardown: uninstall packages, scrub env and docs — read the full analysis →
Backend Drizzle migration (Plan 2: every remaining Supabase call site) — read the full analysis →
Mechanical Drizzle migration of remaining backend routes and lib helpers (Plan 2) — read the full analysis →
Frontend Supabase auth removal - switch to cookie credentials and backend API — read the full analysis →
Auth.js signup/login + cookie bridge to Express backend (Plan 1 vertical slice) — read the full analysis →
Postgres + Drizzle ORM foundation (Plan 1, infra layer) — read the full analysis →
Spec and plan docs for replacing Supabase with local Postgres + Auth.js + Drizzle — read the full analysis →
Think the analysis missed something the PR description covers?
Commits in this PR (52)
| SHA | Subject | Author | Date | |
|---|---|---|---|---|
82854596 | Add spec: replace Supabase with local Postgres + Auth.js + Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
bd2b9451 | Add Plan 1: local-Postgres foundation (Postgres + Drizzle + Auth.js + vertical slice) | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
258d1a7a | feat(infra): add Postgres docker-compose with pgcrypto extension | Juan Vidal | 2026-05-08 | ↗ GitHub |
5e645c6d | feat(backend): add Drizzle ORM scaffolding (client + drizzle-kit config) | Juan Vidal | 2026-05-08 | ↗ GitHub |
64ac55ac | chore(backend): clarify connection-pool comment | Juan Vidal | 2026-05-08 | ↗ GitHub |
267ec65a | feat(backend): Drizzle schema + initial migration (replaces Supabase auth schema) | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
c9b98685 | chore(backend): add trailing newline to initial migration | Juan Vidal | 2026-05-08 | ↗ GitHub |
20ea4b25 | feat(frontend): wire Auth.js + Drizzle (signup, login, session provider) | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
1ab63b75 | fix(frontend): align signup min-length with server (6→8) | Juan Vidal | 2026-05-08 | ↗ GitHub |
2d8ee13b | fix(signup): max-length cap + unique-race backstop + wire org/name to profile | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
7baab523 | feat: cookie-based auth bridge between Auth.js and Express | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
9e99b3ac | chore(backend): document salt hard-coding in auth middleware | Juan Vidal | 2026-05-08 | ↗ GitHub |
c7d112e2 | feat(backend): migrate user-profile route to Drizzle (vertical slice) | Juan Vidal | 2026-05-08 | ↗ GitHub |
Adds GET /user/me; ports POST /user/profile and DELETE /user/account from supabase-js to Drizzle. Cookie-based requireAuth still gates all three. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
d2a95f5f | docs(serena): update memories for local-Postgres + Auth.js stack (Plan 1) | Juan Vidal | 2026-05-08 | ↗ GitHub |
2aecb8dd | Add Plan 2: mechanical migration of remaining Supabase call sites | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
68d18876 | feat(backend): replace Supabase auth.admin.listUsers with Drizzle helper | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
cfc84fbf | chore(backend): remove dead Supabase admin client from workflows.ts | Juan Vidal | 2026-05-08 | ↗ GitHub |
36c889ce | feat(backend): migrate lib/access.ts to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
a41622ab | feat(backend): migrate lib/userSettings.ts to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
3a5c8b6d | feat(backend): migrate lib/documentVersions.ts to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
d352d15c | feat(backend): migrate lib/chatTools.ts to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
ff63879b | feat(backend): migrate downloads.ts to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
d2f30d55 | feat(backend): migrate projectChat.ts to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
318e5594 | feat(backend): migrate chat.ts to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
ed0cc463 | fix(projectChat): guard outer catch against post-headers-sent SSE crash | Juan Vidal | 2026-05-08 | ↗ GitHub |
5e802ac6 | feat(backend): migrate projects route to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyReplace all supabase-js calls in backend/src/routes/projects.ts with Drizzle ORM equivalents, add projectToWire/docToWire/folderToWire helpers for snake_case wire shape preservation, and wrap every async handler in try/catch. Drop the redundant _db shim arg from all checkProjectAccess call sites. The listAllUsers() usage from P2-1 is preserved intact. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
3e8d1813 | feat(backend): migrate documents route to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyReplace all 30 supabase-js calls in documents.ts with Drizzle ORM queries. Drop _db shim args from lib calls; preserve snake_case wire shapes via inline mappers; wrap all handlers in try/catch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
b691f92c | feat(backend): migrate workflows route to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyReplace all 18 supabase-js calls in workflows.ts with Drizzle ORM queries. Add workflowToWire mapper for snake_case wire shape; drop _db shim args; preserve listAllUsers() from P2-1; wrap all handlers in try/catch. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
7cf1e82e | fix(backend): workflow list ordering + tighten Drizzle update types | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
022434f9 | feat(backend): migrate tabular route to Drizzle | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
bdb80c46 | fix(tabular): correct snake_case→camelCase mismatch in buildTabularContext | Juan Vidal | 2026-05-08 | ↗ GitHub |
dde61d26 | feat(frontend): drop Supabase auth from hooks (use cookie credentials) | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyReplace supabase.auth.getSession()+Bearer header pattern with credentials:"include" across useFetchSingleDoc, useFetchDocxBytes, and useDocumentVersions. Switch absolute NEXT_PUBLIC_API_BASE_URL URLs to relative /api/backend/* so the Next.js rewrite forwards the Auth.js session cookie automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
fca7df4c | feat(frontend): drop Supabase auth from components (use cookie credentials) | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyReplace supabase.auth.getSession()+Bearer header pattern with credentials:"include" in DocPanel (EditResolveButtons + DownloadButton), DocxView (tagWIdsOnRenderedDom), EditCard (handle), and AssistantMessage (BulkEditActions + DocDownloadBlock). Switch all absolute NEXT_PUBLIC_API_BASE_URL URLs to relative /api/backend/* paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
c3bdd90f | feat(frontend): rewrite lib/auth.ts to use Auth.js instead of Supabase | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyReplace the Bearer-token validation via Supabase with Auth.js's auth() call. getUserFromRequest no longer takes a NextRequest argument since Auth.js reads the session cookie internally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
10d66a58 | feat(backend): add PATCH /user/me for profile updates | Juan Vidal | 2026-05-08 | ↗ GitHub |
125982eb | feat(frontend): migrate UserProfileContext to backend API; delete lib/supabase.ts | Juan Vidal | 2026-05-08 | ↗ GitHub |
511c8e7e | chore: uninstall Supabase, deprecate legacy schema, update docs (Plan 2 complete) | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit body- git rm backend/src/lib/supabase.ts (no remaining importers) - npm uninstall @supabase/supabase-js from backend - npm uninstall @supabase/supabase-js @supabase/auth-helpers-nextjs @supabase/auth-js from frontend - Add DEPRECATED header to backend/migrations/000_one_shot_schema.sql - Update README.md: Supabase → Postgres/Drizzle setup instructions - Update .serena/memories: reflect Plan 1 + Plan 2 both complete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
74e9d7c2 | chore: drop stale Supabase env vars from .env.example and downloadTokens fallback | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> | ||||
9e099c90 | docs(serena): scrub remaining Supabase references from memories | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
ac9ea169 | chore: add CLAUDE.md, Serena workspace files, and gitignore stray lockfiles | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
c47ab161 | docs: add runbooks, ADRs, and deferred-items tracker | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit body- Slim README to point at runbooks under docs/ - runbooks/: local-development, schema-changes, architecture, troubleshooting - adr/: README + template + ADR-0001 capturing the Supabase removal decision - deferred.md: consolidated tracker for known follow-ups (pre-prod hardening, code quality, auth features, operational gaps) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
f576fca1 | docs: spec for MinIO local S3-compatible storage | Juan Vidal | 2026-05-08 | ↗ GitHub |
Add MinIO + bucket-init sidecar to docker-compose; make storage.ts provider-agnostic via R2_REGION and R2_FORCE_PATH_STYLE; delete the unused frontend storage duplicate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
c5d06a24 | docs: implementation plan for MinIO local storage | Juan Vidal | 2026-05-08 | ↗ GitHub |
Six tasks: compose changes, env defaults, storage.ts provider-agnostic client, delete unused frontend duplicate, README docs, manual smoke test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
ef16e3a6 | feat(compose): add MinIO and bucket-init for local S3 storage | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyAdds a minio service (S3 API on :9000, console on :9001) and a one-shot minio/mc sidecar that creates the 'mike' bucket on first compose-up. Persistence via mike_minio_data named volume. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
cf2effd2 | chore(compose): parameterize minio credentials, document init exit-0 | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyUse ${MINIO_ROOT_USER:-minioadmin} / ${MINIO_ROOT_PASSWORD:-minioadmin}
in both the minio env block and the minio-init entrypoint, matching
the postgres service convention. Adds a comment so contributors don't
mistake minio-init's Exited (0) for an error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| ||||
720353fb | chore(env): default R2_* vars to local MinIO | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyUpdates root and backend .env.example with MinIO endpoint and dev credentials, plus the new R2_REGION and R2_FORCE_PATH_STYLE knobs. Prod R2 config is unaffected - both new vars default to R2-friendly values when unset in the storage client. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
ea0707d6 | feat(storage): make S3 client region and path-style env-driven | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyR2_REGION (default 'auto') and R2_FORCE_PATH_STYLE (default false) preserve current R2 prod behavior when unset, and let MinIO drop in for local dev (region 'us-east-1', forcePathStyle true). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
2a1cc6ff | chore(frontend): remove unused storage.ts duplicate | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyfrontend/src/lib/storage.ts had zero importers - a leftover copy of the backend module. Storage credentials only ever live on the server; the frontend gets a signed URL from the backend and never holds keys. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
8c2a0737 | docs(readme): document local MinIO setup | Juan Vidal | 2026-05-08 | ↗ GitHub |
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
aad90dab | chore(frontend): drop unused AWS SDK dependencies | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit bodyThe frontend storage.ts duplicate was removed in 2a1cc6f, leaving @aws-sdk/client-s3 and @aws-sdk/s3-request-presigner unreferenced. Uninstalling shrinks the dep tree and prevents accidental future imports of S3 SDKs into client-bundled code. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
9aeaae61 | docs: ADR-0002, runbooks, and deferred items for MinIO | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit body- ADR-0002 captures the MinIO + provider-agnostic-storage decision, consequences, and alternatives, in the same shape as ADR-0001. - local-development runbook: drop the "needs an R2 account" prereq, document the MinIO services in step 1, note that the pre-populated R2_* env block targets local MinIO out of the box. - architecture runbook: storage line now mentions MinIO locally; add lib/storage.ts to the backend file map. - deferred.md: pin MinIO image tags, R2_* → S3_* rename, frontend lint baseline, and the next/opennextjs peer-dep conflict that surfaced when uninstalling unused AWS SDK deps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | ||||
596ee51f | chore: drop unused resend dep, smooth local-dev onboarding | Juan Vidal | 2026-05-08 | ↗ GitHub |
commit body- Uninstall the 'resend' package from both backend and frontend.
Zero source references in either tree; RESEND_API_KEY was env-only
ceremony for a feature that doesn't exist yet (email is in
docs/deferred.md as a future item).
- Drop RESEND_API_KEY from backend/.env.example and add a comment
clarifying that LLM keys are optional for first-run exploration.
- Runbook updates:
- Prerequisites: LLM keys marked optional with what works without one.
- Step 1: note that db/init/01-extensions.sql enables pgcrypto on
first Postgres boot.
- Step 3: document --legacy-peer-deps requirement for frontend
npm install (cross-references the deferred item).
- Step 5: drop Resend from the fillables list, clarify LLM keys.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| ||||
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-1.md from
inside the repo you want the changes in.