The pre-existing failing test (the one baseline failure that survived
across every refactor step) was a mock mismatch, not a code bug:
The real code in src/lib/admin-permissions.ts runs:
db.select({...}).from(adminUserBrands).innerJoin(...).where(...)
…and awaits that result directly. The mock chain provided .where()
returning { limit: async () => [...] }, so what got awaited was the
{ limit } object — not an array. Memberships resolved to undefined,
membershipRows.length was undefined, membershipRows.map threw, and
the outer try/catch in getAdminUser silently returned null.
Fix: in both tests that exercise the membership branch, make the
.where() call itself the thenable (matches the real code shape).
Result: 175/175 tests pass (was 174/175 baseline).
The membership query in getAdminUser() was selecting role from
adminUserBrands.adminUserId, which is wrong - admin_user_brands has no
role column. Role is stored in admin_users. Also added try/catch
around withPlatformAdmin to prevent DB errors from throwing.
- Add MinIO/S3-compatible storage client (src/lib/storage.ts) with uploadObject,
deleteObject, presigned URL helpers, and BUCKETS constant
- Wire product images, brand logos, and water log photos to MinIO via the
new storage client
- Migrate forgot-password to Neon Auth (remove Supabase /auth/v1/recover call)
- Migrate send-scheduled cron to direct Postgres + Resend (remove Supabase Edge
Function proxy)
- Add logoUrl to email types (OrderReceipt, Welcome, PasswordReset) and pass
brand_settings.logo_url from all call sites
- Update email templates to use dynamic logoUrl instead of hardcoded Supabase
bucket URLs
- Remove hardcoded Supabase URLs from TuxedoVideoHero, TuxedoAboutPage,
TimeTrackingFieldClient; use brand_settings props + local public/ fallback
- Download brand logos (3) and tuxedo-hero.mp4 (36MB) from Supabase bucket to
public/ for local development
- Add MinIO env vars to .env.example (endpoint, access key, secret, buckets)
- Fix TimeTrackingFieldClient to destructure logoUrl and brandAccent props
- Fix admin/users.ts logoUrl type (null → undefined for optional string)
- Remove stale sb- cookie from wholesale-auth
- Migrate tuxedo/about page to remove supabase import and use pool query for
wholesale_settings lookup
- Create src/lib/db.ts (shared pg.Pool, query/withTx helpers, server-only)
- Add @types/pg devDep
- Migration 204: add email, auth_provider, auth_subject columns to
admin_users; backfill from auth.users; new upsert_admin_user accepts
multi-provider args; new get_admin_user_for_session RPC resolves
Auth.js session id (UUID or Google sub) to an admin row
- Refactor getAdminUser() to use pg + new RPC; auto-provisions on first
Google sign-in using session.user.email
- Refactor updatePasswordAction to call update_user_password via pg
(drops the Supabase REST hop)
- Delete orphaned src/actions/login.ts (replaced by auth-actions.ts)
- Drop remaining DEV_FORCE_UID references in users.ts; dev path now
uses dev_session cookie (the only cookie the dev flow can set)
- Update AdminUser type: user_id is now string | null (Google users
have no Supabase auth id); add email + auth_provider fields
- Fix downstream type errors: StopProductAssignment.callerUid accepts
null; pickup.ts performedBy widens to string | null
- Bump vitest config, tests/, and other earlier cleanup changes