Make Mike runnable locally: Postgres + Auth.js + MinIO

✅ merged · #1 · juanjo/mike ← juanjo/mike · opened 18d ago by juanjo · merged 18d ago by juanjo · self · +14,335-5,039 across 84 files · ↗ on GitHub

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-shot mc sidecar bring up a working mike bucket 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-... and docs/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.md tracks 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/ and docs/superpowers/plans/ contain the design + implementation plans for both efforts.

Test plan

  • docker compose up -d brings up mike-postgres, mike-minio, and mike-minio-init (the init exits 0 - that's success).
  • docker compose ps -a shows mike-postgres and mike-minio healthy.
  • MinIO web console at http://localhost:9001 (minioadmin / minioadmin) shows the mike bucket.
  • npm run db:migrate --prefix backend applies the Drizzle migration against the fresh Postgres.
  • npm run build --prefix backend and npm run build --prefix frontend both succeed.
  • npm install --prefix frontend --legacy-peer-deps succeeds (the flag is required and documented; tracked in docs/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 neutral S3_* prefix (deferred until a real provider swap).
  • Resolve the next@opennextjs/cloudflare peer-dep conflict so --legacy-peer-deps becomes 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 body
Replace 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 body
Replace 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 body
Replace 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 body
Replace 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 body
Replace 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 body
Replace 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 body
Adds 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 body
Use ${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 body
Updates 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 body
R2_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 body
frontend/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 body
The 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.

⬇ Download capture-pull-1.md