49b8e27219
- Delete supabase/ directory (config.toml, push-migrations.js, ADMIN_CREATE_STOP_FIX.sql, 137 archived migration files) - Remove @supabase/ssr and @supabase/supabase-js from package.json - Strip *.supabase.co from next.config.ts image hostnames - Strip https://*.supabase.co from vercel.json CSP connect-src - Remove 'supabase/**' ignore from eslint.config.mjs - Clean supabase references from src/lib/db.ts, vitest.config.ts, db/seeds/2026-tuxedo-tour-stops.sql, src/actions/storefront.ts, scripts/import-tuxedo-stops.ts comments - Rewrite env-var docs in README, ENVIRONMENT, PRODUCTION_SETUP, PRODUCTION_DEPLOYMENT_CHECKLIST, LAUNCH_CHECKLIST, CLAUDE, MEMORY, REPORT to drop NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY / supabase link / supabase CLI references The canonical migration runner is now scripts/migrate.js (uses pg directly via DATABASE_URL). Migrations live in db/migrations/. The Supabase CLI is no longer in the codebase. The only remaining '@supabase/*' in the dep tree is @supabase/auth-js as a transitive of @neondatabase/auth (Neon Auth / Better Auth). Final source scan: grep -rln '@supabase\|rest/v1\|supabase\.co' src/ tests/ db/ scripts/ next.config.ts vercel.json eslint.config.mjs package.json returns zero. Verification: npm install clean (11 added, 21 removed, 12 changed), tsc shows same 17 pre-existing errors (Stripe dahlia API version + fetch preconnect mocks), vitest 172/175 (3 pre-existing failures in getAdminUser.test.ts), lint shows same 14 pre-existing errors. Live-tested: dev server boots in 248ms, public storefronts (/) (/login) (/pricing) (/tuxedo) (/indian-river-direct) all return 200; /admin/v2?demo=1 reaches 200 after dev_session redirect; storefront renders real brand content.
216 lines
9.3 KiB
Markdown
216 lines
9.3 KiB
Markdown
# YOLO Auth Migration — Final Report
|
||
|
||
**Date:** 2026-06-06 → 2026-06-07
|
||
**Mission:** Stand up a working Auth.js v5 + Google sign-in flow, then rip out
|
||
all Supabase references from the auth/admin path.
|
||
|
||
> **Note (2026-06-25):** Auth has since moved from Auth.js (NextAuth v5) to **Neon Auth (Better Auth)** — see `CLAUDE.md` for the current architecture. The Supabase purge described below is complete: `supabase/` directory deleted, `@supabase/*` deps removed from `package.json`, `next.config.ts` / `vercel.json` / `eslint.config.mjs` cleaned of Supabase hostnames and ignore patterns, and the remaining `*.supabase.co` references are limited to historical/educational comments in `MEMORY.md` and `docs/`.
|
||
|
||
---
|
||
|
||
## What was built
|
||
|
||
### 1. Auth.js v5 (NextAuth) integration — DONE
|
||
|
||
- **`src/lib/auth.ts`** — Auth.js v5 config. Google is the only provider
|
||
(no Supabase-backed Credentials provider). Sessions are JWTs; no DB
|
||
adapter. Exports `handlers`, `auth`, `signIn`, `signOut`.
|
||
- **`src/lib/admin-permissions.ts`** — `getAdminUser()` resolves the
|
||
current admin. Three branches in precedence order:
|
||
1. `NEXT_PUBLIC_USE_MOCK_DATA=true` → platform_admin dev shim
|
||
2. `dev_session` cookie → matching dev shim (platform/brand/store)
|
||
3. Auth.js session → `null` (no DB lookup yet — see Follow-ups)
|
||
- **`src/actions/auth-actions.ts`** — `signInWithGoogle()` and
|
||
`signOutAction()`. `signInWithPassword` was removed.
|
||
- **`src/app/login/LoginClient.tsx`** + **`page.tsx`** — Login page
|
||
renders "Continue with Google" (when configured) and three demo
|
||
buttons (Platform / Brand / Store Employee) via `?demo=1`. Email /
|
||
password form and "Forgot password" are gone.
|
||
- **`src/app/api/auth/[...nextauth]/route.ts`** — Auth.js route handler.
|
||
|
||
### 2. Direct Postgres connection (`src/lib/db.ts`) — DONE
|
||
|
||
- Single shared `pg.Pool` with lazy initialization. Throws a clear
|
||
error if `DATABASE_URL` is not set.
|
||
- Exports `getPool()`, a `pool` proxy alias, `query<T>()` helper, and
|
||
`withTx()` for transactions.
|
||
- Tuned for Vercel/serverless: 10 max conns, 30s idle, 10s connect
|
||
timeout. All overridable via `PG_POOL_*` env vars.
|
||
|
||
### 3. Server actions → `pg` (no Supabase) — DONE
|
||
|
||
- **`src/actions/admin/users.ts`** — Full rewrite. `getAdminUsers`,
|
||
`createAdminUser`, `updateAdminUser`, `deleteAdminUser`,
|
||
`setMustChangePassword`, `getBrands` now hit Postgres directly.
|
||
`sendPasswordResetEmail` returns a stub error (no auth service
|
||
anymore — the function is preserved for call-site compatibility).
|
||
- The `dev_session` cookie continues to be the demo-flow bypass.
|
||
|
||
### 4. Cleanup — DONE
|
||
|
||
- **`src/actions/admin/force-login.ts`** — Deleted (the
|
||
Emergency-Force-Login page and its `/api/force-admin` route were
|
||
removed in the previous session).
|
||
- **`src/actions/admin/users.ts`** — Removed the `DEV_FORCE_UID`
|
||
magic value, the `rc_auth_uid` cookie reads (×3), the
|
||
`headerStore` lookup, the `getServiceClient` / `getAuthClient` /
|
||
`callRpcWithAuth` helpers, and the `devListAdminUsers` auto-provision
|
||
branch. All Supabase imports gone.
|
||
- **`src/app/admin/me/AdminMeClient.tsx`** — Profile / email-change
|
||
handlers neutralized (return "contact a platform admin" — the
|
||
Supabase auth backend that backed them is gone).
|
||
- **`src/middleware.ts`** — `dev_session` auto-login preserved
|
||
(issues a `platform_admin` cookie on unauthed `/admin` so the rest
|
||
of the admin shell renders). `auth()` wrapper in place.
|
||
|
||
### 5. Test infrastructure — DONE
|
||
|
||
- **Vitest 2.1.9** with `@vitejs/plugin-react`, jsdom, manual
|
||
`@/* → src/*` alias. No `vite-tsconfig-paths` (ESM/require
|
||
incompatibility).
|
||
- **`tests/setup.ts`** — Mocks `next/navigation` (not in jsdom).
|
||
- **`tests/unit/getAdminUser.test.ts`** — 12 tests covering
|
||
dev_session, mock-data, Auth.js session, defensive error handling.
|
||
- **`tests/unit/auth-actions.test.ts`** — 2 tests for
|
||
`signInWithGoogle` + `signOutAction`.
|
||
- Both test files stub `server-only` with `vi.mock("server-only", () => ({}))`.
|
||
- **Playwright config** — `webServer` block for `npm run dev`,
|
||
`PLAYWRIGHT_URL` override, `reuseExistingServer: !process.env.CI`.
|
||
- **`tests/login/login-flow.spec.ts`** — Login form rendering +
|
||
demo mode (3 roles) tests.
|
||
- **`tests/e2e/auth.spec.ts`** — Auth.js endpoints, middleware
|
||
redirect behavior, /admin load with dev_session, logout.
|
||
|
||
### 6. `import "server-only"` markers — DONE
|
||
|
||
- Added to `src/lib/auth.ts`, `src/lib/admin-permissions.ts`,
|
||
`src/lib/db.ts`, `src/actions/auth-actions.ts`,
|
||
`src/actions/admin/users.ts`.
|
||
- ⚠️ In `"use server"` files, the directive must be first
|
||
(`"use server";` on line 1, then `import "server-only";`). The dev
|
||
build surfaced this; fixed in `auth-actions.ts` and `users.ts`.
|
||
|
||
---
|
||
|
||
## Verification status
|
||
|
||
| Check | Result |
|
||
|---|---|
|
||
| `npx tsc --noEmit` | ✅ 0 errors |
|
||
| `npx vitest run` | ✅ 14/14 tests pass |
|
||
| `npm run dev` | ✅ Boots in ~440ms |
|
||
| `GET /login` | ✅ 200 |
|
||
| `GET /login?demo=1` | ✅ 200 |
|
||
| `GET /api/auth/providers` | ✅ 200 (empty list — Google not configured) |
|
||
| `GET /` | ✅ 200 |
|
||
| `GET /admin` (unauthed) | ✅ 307 → `/admin?demo=1` + `dev_session` cookie |
|
||
| `GET /admin?demo=1` (with cookie) | ✅ 200 (admin layout renders) |
|
||
|
||
---
|
||
|
||
## Files changed
|
||
|
||
```
|
||
M src/actions/admin/users.ts # full rewrite, pg-only
|
||
M src/actions/auth-actions.ts # signInWithPassword removed
|
||
M src/app/admin/me/AdminMeClient.tsx # handlers stubbed
|
||
M src/app/login/LoginClient.tsx # email/password form removed
|
||
M src/app/login/page.tsx # passes hasGoogle prop
|
||
M src/lib/admin-permissions.ts # Supabase REST calls removed
|
||
M src/lib/auth.ts # Credentials provider removed
|
||
M tests/unit/auth-actions.test.ts # signInWithPassword tests removed
|
||
M tests/unit/getAdminUser.test.ts # reflects no-DB behavior
|
||
```
|
||
|
||
Plus deletions and creations recorded in the prior session.
|
||
|
||
---
|
||
|
||
## Follow-ups (the bigger fish)
|
||
|
||
The user's directive was: "git rid of all supabase references, we are
|
||
not using it for login or anything anymore." The auth/admin path is
|
||
now Supabase-free. **33 files** still import from Supabase — they use
|
||
it for **data fetching** (`supabase.from(...).select(...)`), not auth.
|
||
|
||
**Migrating those to `pg` is a follow-up.** The pattern is the same
|
||
in every file:
|
||
|
||
```ts
|
||
// Before
|
||
const { data } = await supabase.from("products").select("*").eq("brand_id", id);
|
||
|
||
// After
|
||
import { query } from "@/lib/db";
|
||
const { rows } = await query<ProductRow>(
|
||
"SELECT * FROM products WHERE brand_id = $1",
|
||
[id],
|
||
);
|
||
```
|
||
|
||
### 33 files still importing Supabase
|
||
|
||
```
|
||
src/app/admin/page.tsx, products/[id]/page.tsx, products/page.tsx,
|
||
orders/page.tsx, stops/[id]/page.tsx, stops/new/page.tsx,
|
||
stops/page.tsx, reports/page.tsx, taxes/page.tsx,
|
||
settings/billing/page.tsx, settings/integrations/page.tsx,
|
||
settings/shipping/page.tsx
|
||
src/app/tuxedo/page.tsx, about/page.tsx, contact/ContactClientPage.tsx,
|
||
stops/[slug]/page.tsx
|
||
src/app/indian-river-direct/page.tsx, faq/FAQClientPage.tsx,
|
||
contact/ContactClientPage.tsx, stops/[slug]/page.tsx
|
||
src/app/api/tuxedo/schedule-pdf/route.ts,
|
||
src/app/api/indian-river-direct/schedule-pdf/route.ts
|
||
src/app/reset-password/page.tsx, src/app/test/page.tsx
|
||
src/components/admin/AdminHeader.tsx, AdminSidebar.tsx
|
||
src/lib/supabase.ts, src/lib/supabase/server.ts # the client modules
|
||
src/actions/wholesale-auth.ts, reset-admin.ts, audit.ts
|
||
src/actions/ai/preferences.ts
|
||
```
|
||
|
||
### Additional follow-ups
|
||
|
||
1. **Provision a `pg` pool in production.** `DATABASE_URL` must be
|
||
set in the hosting dashboard (same env var as
|
||
`scripts/migrate.js` uses).
|
||
2. **Apply migration 204** if not already applied — adds
|
||
`email`, `auth_provider`, `auth_subject` columns to `admin_users`
|
||
and the `get_admin_user_for_session` RPC.
|
||
3. **Wire `getAdminUser()`'s Auth.js session branch** to call
|
||
`get_admin_user_for_session` via `pg` once a `DATABASE_URL` is
|
||
configured. Today it returns `null` (Access Denied) — correct for
|
||
an unprovisioned user, but a Google sign-in to a brand that
|
||
exists won't resolve yet.
|
||
4. **Drop the `next-auth` Credentials provider module path** in
|
||
`auth.ts` entirely once production confirms Google is the only
|
||
provider in use. Currently it's gated on env var presence.
|
||
5. **Re-implement `sendPasswordResetEmail`** if/when Auth.js v5 ships
|
||
its built-in email provider — until then, a platform admin must
|
||
handle resets manually.
|
||
6. **Decommission `src/lib/supabase.ts` and `src/lib/supabase/server.ts`**
|
||
once all 33 remaining files have been ported to `pg`. They are
|
||
the only remaining `@supabase/*` import surface.
|
||
7. **Delete `@supabase/ssr` and `@supabase/supabase-js` from
|
||
`package.json`** (last step once no file imports them).
|
||
|
||
### Resolution (2026-06-25)
|
||
|
||
Follow-ups 6 and 7 (and the data-fetching migration of all 33 files) are
|
||
complete. The codebase is now Supabase-free at the source-code level —
|
||
`grep -rln '@supabase\|rest/v1' src/` returns zero matches. The only
|
||
remaining mentions of "Supabase" are in this report, `MEMORY.md`, and a
|
||
handful of historical `docs/superpowers/{plans,specs}/` files, all kept
|
||
as historical record of the migration.
|
||
|
||
---
|
||
|
||
## Commands
|
||
|
||
```bash
|
||
npm run dev # Dev server (boots in ~440ms)
|
||
npm test # Vitest: 14/14 pass
|
||
npx tsc --noEmit # Typecheck: 0 errors
|
||
npx playwright test # E2E (skips credentials tests if env unset)
|
||
```
|