AUTH_MODE=local: self-hosted Postgres, JWT auth, and Docker Compose as a Supabase-free option
rglauco adds a runtime flag that swaps Supabase for a self-hosted Postgres instance with JWT auth and a full Docker Compose stack. The existing route layer stays untouched. Getting it to actually work took five follow-up patches.
The main commit (b8efde9f) introduces AUTH_MODE=local and a PgAdapter class at backend/src/lib/pgAdapter.ts (358 lines) that mimics the Supabase client query interface over pg.Pool. A DbClient abstraction in db.ts lets every existing Express route work in either mode without modification. New local auth endpoints (/auth/login, /auth/register) land in localAuth.ts. The Docker side is a multi-stage Node 22 Dockerfile for the backend (with LibreOffice for DOCX conversion), a frontend Dockerfile, and a compose.yml with named volumes and a Postgres health check. The frontend gets a local JWT path in AuthContext and a localAuth.ts helper.
Five bugs surfaced in live testing and were patched in the days following the initial commit. The frontend crashed entirely when NEXT_PUBLIC_SUPABASE_URL was missing because createClient("", "") throws - fixed by introducing a getSessionToken() helper across 7 components and hooks. AuthContext read localStorage once on mount, so successful sign-in immediately redirected back to /login - fixed with a refreshAuth callback. PgAdapter.maybeSingle() was returning arrays rather than single rows, breaking every POST /chat. JS arrays bound to jsonb columns serialized as {a,b} PostgreSQL array literals rather than valid JSON. Bare strings in jsonb content columns weren't JSON-encoded before insert. An upsert with only conflict-key columns generated invalid SQL (DO UPDATE SET with nothing to set). An Ollama availability check tested apiKeys.ollama !== undefined, which was always true since the object initializes ollama: null.
The PgAdapter parity gaps are the main thing to audit before adopting. The bugs that shipped suggest the adapter was written against a partial mental model of the Supabase client and not tested end-to-end. Specific areas to verify: all .single() and .maybeSingle() call sites, array filter operations, and any upsert paths with composite conflict keys.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?