Commit graph

10 commits

Author SHA1 Message Date
Chris Parsons
c1df2d8edc
Add baseline capture script for canonical layer migration
Captures QP totals, completed event results, surface Elo, and Monte Carlo
simulator output for every in-flight qualifying-points sports_season.
Used across Phase 1a-4 to verify no scoring drift.

Note: simulators don't currently accept a seed, so simulator output
fixtures are non-deterministic (noted in the script header).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 05:54:27 +00:00
Chris Parsons
cdba27a106 Fix migrate.mjs: also convert leagues.created_by from Clerk IDs to UUIDs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:46:14 -07:00
Chris Parsons
cd8074ef10 Fix migrate.mjs: run FK conversion unconditionally, not gated on CLERK_SECRET_KEY
The FK conversion (teams.owner_id, commissioners.user_id → UUIDs) and
email_verified update were inside runClerkMigration() which only ran when
CLERK_SECRET_KEY was set. If the key wasn't in the environment at deploy
time, all Clerk IDs were left unconverted, causing UUID parse errors at
runtime.

Split into two functions:
- convertForeignKeys(): always runs, idempotent WHERE clauses
- importClerkOAuthAccounts(): only runs when CLERK_SECRET_KEY is set

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:41:41 -07:00
Chris Parsons
ba9bf64e37
Migrate authentication from Clerk to BetterAuth (#324)
* Migrate authentication from Clerk to BetterAuth (#322)

Replaces @clerk/react-router with self-hosted better-auth to eliminate
the external Clerk dependency and keep all user/session data in our own
PostgreSQL database.

**What changed**
- New: auth.server.ts (BetterAuth config w/ Drizzle adapter, bcrypt, Resend), auth-client.ts, api.auth.$.ts handler
- New: /login and /register pages with email+password and Google/Discord OAuth; open-redirect guard on redirectTo param
- New: UserMenu component replacing Clerk's UserButton
- Schema: sessions, accounts, verifications tables; emailVerified column; clerkId made nullable
- Migrations 0081 (BetterAuth tables) and 0082 (accounts extra columns for v1.6.9)
- All ~30 route files: getAuth → auth.api.getSession, isUserAdminByClerkId → isUserAdmin
- root.tsx: isAdmin read directly from session.user.isAdmin (no extra DB query)
- useDraftAuthRecovery: removed Clerk JWT refresh logic; replaced with cookie-session check
- models/user.ts: removed findUserByClerkId, findOrCreateUser, updateUserByClerkId (webhook pattern)
- Deleted: app/routes/api/webhooks/clerk.ts; uninstalled @clerk/react-router, @clerk/themes, svix
- scripts/migrate.mjs: extended with idempotent Clerk → BetterAuth data migration (FK conversion, email_verified, OAuth accounts)
- scripts/migrate-clerk-passwords.mjs: one-time script to import bcrypt hashes from Clerk CSV export
- BETTERAUTH_MIGRATION.md: dev and production runbooks
- All test mocks updated: vi.mock('~/lib/auth.server') instead of @clerk/react-router/server
- Test fixtures: added emailVerified field

**Follow-up (post-stable)**
- Rename actor_clerk_id column → actor_user_id in commissioner_audit_log
- Drop clerk_id column from users once migration confirmed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Add .npmrc with legacy-peer-deps for better-auth/drizzle peer dep conflict

better-auth@1.6.9 declares peerOptional deps on drizzle-orm ^0.45.2 and
drizzle-kit >=0.31.4, but we run drizzle-orm ~0.36.3 / drizzle-kit ~0.28.1.
The adapter works correctly at runtime with our versions — the peer dep is
only for stricter type checking. This unblocks npm ci in CI without a risky
drizzle major-version upgrade.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 22:00:49 -07:00
Chris Parsons
787b817fa7
Add PgBouncer connection pooling support for Vultr managed PostgreSQL (#271)
Disables prepared statements when PGBOUNCER=true (required for PgBouncer
transaction mode). Adds DATABASE_DIRECT_URL so migrations always use a
direct connection, bypassing the pool.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 17:15:18 -04:00
Claude
0f97ab258f
Fix migrate.mjs exiting with code 1 after successful migration
client.end() in the finally block was throwing (postgres driver already
cleaned up connections internally during migrate()), causing an unhandled
rejection and exit code 1 despite the migration succeeding.

- Use explicit process.exit(0)/exit(1) instead of relying on implicit exit
- Wrap both client.end() calls with .catch(() => {}) to tolerate cleanup errors
- Add onnotice: () => {} to suppress noisy NOTICE messages (schema/table
  already exists) that are normal on every idempotent re-run

https://claude.ai/code/session_01ReaqH3o9NVH4QU4qE9WMMQ
2026-04-06 04:24:04 +00:00
Chris Parsons
f1af4b5171
Decouple database migrations from server startup (#265)
* Use Docker Compose init service pattern for database migrations

Replaces the fragile double-migration approach (server.ts startup +
drizzle-kit CLI in CI) with a one-shot migrate service in Docker Compose
that the app depends on via condition: service_completed_successfully.

- Add scripts/migrate.mjs: programmatic drizzle-orm migration (uses
  production deps, not drizzle-kit which is devOnly and absent from image)
- Dockerfile: copy scripts/ into image, remove drizzle.config.ts (only
  needed by drizzle-kit CLI)
- server.ts: remove runMigrations() entirely; migrations are now handled
  by the migrate container before the app starts
- deploy.yml: remove explicit docker run migration step; replace sleep 10
  health check with a poll loop and explicit migrate exit-code check

Production server's docker-compose.yaml needs a one-time manual update
to add the migrate service — see plan for exact config.

https://claude.ai/code/session_01ReaqH3o9NVH4QU4qE9WMMQ

* Move drizzle-kit to devDependencies; use docker compose wait in deploy

drizzle-kit is a dev-only tool (schema generation and local migrations).
Now that production migrations run via the programmatic drizzle-orm API
in the migrate init container, drizzle-kit has no runtime role.

Also replaces the polling health check loop with docker compose wait,
which blocks until the migrate service exits and returns its exit code
cleanly — no sleep or manual status inspection needed.

https://claude.ai/code/session_01ReaqH3o9NVH4QU4qE9WMMQ

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-06 00:07:36 -04:00
Chris Parsons
61715b43d2
Load PROD_DATABASE_URL from .env automatically (#259)
Mirrors how DATABASE_URL is loaded, so users can store both in their
local .env instead of passing PROD_DATABASE_URL inline each time.

https://claude.ai/code/session_01EiYh5ZiuWAnpo1PND45Yi1

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-03 15:38:09 -07:00
Chris Parsons
139061e702
Add production database sync script with sanitization (#258)
* Add prod-to-dev database sync script

Adds scripts/sync-prod-db.sh and npm run db:sync-prod to safely copy
production data to the development database. After restoring, the script
nulls all Discord webhook URLs and anonymizes non-admin user PII (email,
name, avatar) to prevent notification leaks and reduce PII exposure on
developer machines. Admin accounts are preserved intact.

https://claude.ai/code/session_01EiYh5ZiuWAnpo1PND45Yi1

* Fix six code review issues in sync-prod-db.sh

- Add upfront check for pg_dump/pg_restore/psql availability (#5)
- Fix password masking to use sed instead of broken bash glob (#6)
- Replace DROP/CREATE DATABASE with pg_restore --clean --if-exists,
  which works on managed cloud databases without superuser access (#2)
- Remove dead ADMIN_DB assignment that was immediately overwritten (#3)
- Handle pg_restore exit code 1 (non-fatal warnings) gracefully
  instead of aborting via set -e (#1)
- Count webhook URLs before the UPDATE for an accurate cleared count (#4)

https://claude.ai/code/session_01EiYh5ZiuWAnpo1PND45Yi1

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-03 15:17:58 -07:00
Chris Parsons
2bad3dd75e refactor: migrate server code from JavaScript to TypeScript with build pipeline 2025-10-18 22:16:04 -07:00