21 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6772079e86 |
Reflect group stage in World Cup sim; seed R32 from real 2026 bracket
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 2m53s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m19s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
🚀 Deploy / 🧪 Test (push) Successful in 3m6s
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m22s
🚀 Deploy / 🐳 Build (push) Successful in 1m13s
🚀 Deploy / 🚀 Deploy (push) Successful in 13s
The World Cup Monte Carlo simulator ignored the admin-populated knockout draw and re-paired its own simulated group output sequentially, so bracket paths from the Round of 32 onward did not match the real tournament. It also silently invented 12 synthetic groups when none were configured, discarding real group-stage results. Rework the simulator around three regimes: - draw set → simulate the real R32 matchups, advancing through the bracket tree via the canonical ceil(matchNumber/2) rule (matching advanceWinnerTemplate) so sim paths match the admin tooling. - groups only → simulate remaining group matches, then seed the R32 with the official 2026 group-position template + FIFA's exact Annex C best-third-place allocation (495-row published table). - neither → futures-seeded bracket fallback, flagged via result source. Completed group and knockout matches still lock in real results at every round. New app/lib/fifa-2026-bracket.ts encodes the R32 group-position template (official FIFA matches 73-88 mapped onto DB matchNumbers 1-16 so the tree reproduces the real halves) and assignThirdPlaceSlots(). The Annex C table is generated deterministically by scripts/gen-fifa-third-place.mjs into app/lib/fifa-2026-third-place-allocation.ts. Tests assert exact Annex C values, cross-check the template's eligible groups against all 495 combinations, and cover each regime including the partial-draw dedup and fully-drawn fast path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
9480501932 |
Unify majors: score once, fan out across windows + tennis bracket EV
All checks were successful
🚀 Deploy / 🧪 Test (pull_request) Successful in 3m12s
🚀 Deploy / ʦ🔍 Typecheck & Lint (pull_request) Successful in 1m24s
🚀 Deploy / 🐳 Build (pull_request) Has been skipped
🚀 Deploy / 🚀 Deploy (pull_request) Has been skipped
🚀 Deploy / 🧪 Test (push) Successful in 3m27s
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m25s
🚀 Deploy / 🐳 Build (push) Successful in 1m14s
🚀 Deploy / 🚀 Deploy (push) Successful in 14s
Make a "major" (golf/tennis/CS2) scored once on its canonical tournament and fan out to every linked sports_season window and league. Fan-out & completion (app/services/sync-tournament-results.ts): - syncTournamentResults now marks each synced window's event complete (gated by markComplete), recalculates affected leagues, and counts recalc failures so a stale league can't hide behind a "completed" badge - syncMajorFromPrimaryEvent promotes a primary window's derived results to canonical tournament_results (deleting rows for dropped placements) and fans out to siblings; fanOutMajorIfPrimary guards on the primary - placement removals now propagate (stale rows reset to filler) Primary-event model (scoring_events.is_primary, migration 0122): - getPrimaryEventForTournament / isReadOnlySibling / ensurePrimaryEvent / setPrimaryEvent; event creation auto-seeds a primary for bracket majors; "Make primary" button on the tournament page - per-window event/bracket/cs2 pages are read-only for non-primary linked events (not-participating stays editable) Tennis Grand Slam bracket (tennis_128 template + TEMPLATE_ROUND_CONFIG): - bracket-scored qualifying major via the existing bracket pipeline - simulator conditions in-progress EV on the real bracket (honoring completed matches, walkover for withdrawals), QP derived from config, round structure read from the template; CS2 + tennis share resolveStructureSource Backfill (scripts/backfill-major-linking.ts): one-time idempotent reconcile of existing majors (link orphans, designate primary, promote canonical, sync). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
| d31c23d63b |
claude/practical-newton-4v041g (#98)
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Chris Parsons <chrisparsons1127@gmail.com> Reviewed-on: #98 |
|||
|
|
2978ba787d |
Add robots.txt and sitemap.xml.gz for SEO
Adds a static robots.txt blocking admin/API/auth routes, a sitemap generation script (scripts/generate-sitemap.mjs) that writes both sitemap.xml and sitemap.xml.gz to public/, and hooks it into the build so the sitemap is always fresh on deploy. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
04a92ec7de
|
Add branded dark-themed HTML email template (#402)
Introduces a shared email module that wraps all transactional emails in a cohesive dark-themed template matching the site's visual identity (Barlow font, #14171e background, #adf661 CTA buttons, logomark PNG header). - app/lib/email.server.ts: new module with sendEmail, wrapInEmailTemplate, emailButton, emailParagraph, and escapeHtml helpers; Resend instance is lazily initialized as a singleton - public/email-logo.png: 96×124px PNG of the logomark for email use (SVG gradients are not supported in Outlook) - auth.server.ts: password reset and email verification now use the branded template; errors are thrown so Better Auth can surface failures - support.tsx: internal notification uses sendEmail; adds a user-facing branded confirmation email (fire-and-forget so failures don't surface to the user); plain text fallback included - scripts/sync-prod-db.sh: remove first_name/last_name from sanitize query (columns were dropped in #399) - 13 unit tests covering all template helpers Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
c8748d1b14
|
Add email verification for email/password signups (#400)
New email/password accounts must verify before signing in. Social login users (Google, Discord) are unaffected. Existing users are grandfathered via an idempotent migration. Adds a /check-email interstitial with a resend button, and a --success CSS token for consistent styling. Closes #343 Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
d342f1ad25
|
Remove backfill-flag-configs script (#389)
* Remove backfill-flag-configs script One-time migration has been run; script is no longer needed. https://claude.ai/code/session_014RUwPfm1qc539xLxW4m9Uy * Clean up flag config code - FlagSvg: remove redundant getDisplayFlagConfig call; config is already a valid FlagConfig by type, so the validation/fallback was dead code. Also replace the unreachable `?? "#adf661"` fallback with a non-null assertion. - team/user models: pre-generate ID before insert so flagConfig is persisted immediately on creation rather than relying on display-time generation. - auth.server.ts: add create.after hook so BetterAuth-created users also get a flagConfig written to the DB on signup. https://claude.ai/code/session_014RUwPfm1qc539xLxW4m9Uy * Fix lint: replace non-null assertion in FlagSvg with destructure defaults oxlint forbids the ! operator; destructuring with empty-string defaults is equivalent since FlagConfig always has 3 colors. https://claude.ai/code/session_014RUwPfm1qc539xLxW4m9Uy --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a447ba54de
|
Add Cloudinary-backed avatar system (#385) | ||
|
|
48b1d470f1
|
Canonical tournament layer:
cutover + cleanup (2/2) (#367) * refactor(schema): rename per-window tables to season_* prefix Renames participants, participant_expected_values, participant_qualifying_totals, participant_results, participant_surface_elos to season_* prefixed names. Renames event_results.participant_id to season_participant_id. Phase 1a of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor: rename participant.ts model file to season-participant.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(models): update model layer to use renamed schema exports Updated all model files to use the renamed schema exports from Task 1: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantQualifyingTotals → seasonParticipantQualifyingTotals - participantResults → seasonParticipantResults - participantSurfaceElos → seasonParticipantSurfaceElos - eventResults.participantId → eventResults.seasonParticipantId - db.query relation accessors updated - Relation field .participant → .seasonParticipant where applicable - Import paths updated: ./participant → ./season-participant Files updated (14 model files + 3 test files): - draft-pick.ts - draft-utils.ts - event-result.ts - group-stage-match.ts - participant-result.ts - qualifying-points.ts - scoring-calculator.ts - scoring-event.ts - sports-season.ts - surface-elo.ts - team-score-events.ts - cs2-major-stage.ts - golf-skills.ts - participant-expected-value.ts - __tests__/sports-season.clone.test.ts - __tests__/auto-pick.test.ts - __tests__/executeAutoPick.timer.test.ts Typecheck errors decreased: 779 → 499 (280 fewer) All model file errors related to renamed schemas resolved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route layer to use renamed schema exports - Update model import from ~/models/participant to ~/models/season-participant - Rename schema.participants to schema.seasonParticipants - Rename schema.participantResults to schema.seasonParticipantResults - Rename db.query.participants to db.query.seasonParticipants - Update 9 route files and 1 test file Affected files: - admin.sports-seasons.$id.events.$eventId.bracket.server.ts - admin.sports-seasons.$id.participants.tsx - api/draft.force-manual-pick.ts - api/draft.make-pick.ts - api/draft.replace-pick.ts - api/seasons.$seasonId.draft.ts - leagues/$leagueId.draft-board.$seasonId.tsx - leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts - admin/__tests__/sports-seasons-participants.test.ts Error count reduced from 499 to 453 (46 errors fixed). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route files for schema rename Update route imports from ~/models/participant to ~/models/season-participant and fix references to .participant/.participantId on event results to use .seasonParticipant/.seasonParticipantId after schema rename. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(services): update simulators and services for renamed schema Update all simulators, services, and server files to use renamed schema tables: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantResults → seasonParticipantResults - eventResults.participantId → eventResults.seasonParticipantId Files updated: - 20 sport simulators (NBA, NHL, NFL, MLB, etc.) - probability-updater.ts - standings-sync/index.ts - sports-data-sync.server.ts - server/socket.ts Typecheck errors reduced from 365 to 0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * migration: rename per-window tables to season_* prefix * fix(tests): update mock query keys after participants table rename Change mock db.query.participants to db.query.seasonParticipants in test files to match the schema rename from commit |
||
|
|
2848231235
|
Canonical tournament layer: schema + backfill (1/2) (#365)
* refactor(schema): rename per-window tables to season_* prefix Renames participants, participant_expected_values, participant_qualifying_totals, participant_results, participant_surface_elos to season_* prefixed names. Renames event_results.participant_id to season_participant_id. Phase 1a of canonical tournament layer migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor: rename participant.ts model file to season-participant.ts Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(models): update model layer to use renamed schema exports Updated all model files to use the renamed schema exports from Task 1: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantQualifyingTotals → seasonParticipantQualifyingTotals - participantResults → seasonParticipantResults - participantSurfaceElos → seasonParticipantSurfaceElos - eventResults.participantId → eventResults.seasonParticipantId - db.query relation accessors updated - Relation field .participant → .seasonParticipant where applicable - Import paths updated: ./participant → ./season-participant Files updated (14 model files + 3 test files): - draft-pick.ts - draft-utils.ts - event-result.ts - group-stage-match.ts - participant-result.ts - qualifying-points.ts - scoring-calculator.ts - scoring-event.ts - sports-season.ts - surface-elo.ts - team-score-events.ts - cs2-major-stage.ts - golf-skills.ts - participant-expected-value.ts - __tests__/sports-season.clone.test.ts - __tests__/auto-pick.test.ts - __tests__/executeAutoPick.timer.test.ts Typecheck errors decreased: 779 → 499 (280 fewer) All model file errors related to renamed schemas resolved. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route layer to use renamed schema exports - Update model import from ~/models/participant to ~/models/season-participant - Rename schema.participants to schema.seasonParticipants - Rename schema.participantResults to schema.seasonParticipantResults - Rename db.query.participants to db.query.seasonParticipants - Update 9 route files and 1 test file Affected files: - admin.sports-seasons.$id.events.$eventId.bracket.server.ts - admin.sports-seasons.$id.participants.tsx - api/draft.force-manual-pick.ts - api/draft.make-pick.ts - api/draft.replace-pick.ts - api/seasons.$seasonId.draft.ts - leagues/$leagueId.draft-board.$seasonId.tsx - leagues/$leagueId.sports-seasons.$sportsSeasonId.server.ts - admin/__tests__/sports-seasons-participants.test.ts Error count reduced from 499 to 453 (46 errors fixed). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(routes): update route files for schema rename Update route imports from ~/models/participant to ~/models/season-participant and fix references to .participant/.participantId on event results to use .seasonParticipant/.seasonParticipantId after schema rename. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * refactor(services): update simulators and services for renamed schema Update all simulators, services, and server files to use renamed schema tables: - participants → seasonParticipants - participantExpectedValues → seasonParticipantExpectedValues - participantResults → seasonParticipantResults - eventResults.participantId → eventResults.seasonParticipantId Files updated: - 20 sport simulators (NBA, NHL, NFL, MLB, etc.) - probability-updater.ts - standings-sync/index.ts - sports-data-sync.server.ts - server/socket.ts Typecheck errors reduced from 365 to 0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * migration: rename per-window tables to season_* prefix * fix(tests): update mock query keys after participants table rename Change mock db.query.participants to db.query.seasonParticipants in test files to match the schema rename from commit |
||
|
|
28244c5f43
|
Fix capture-baseline.ts: postgres-js returns rows directly
db.execute() with drizzle-orm/postgres-js returns rows as an array, not wrapped in a .rows property (that's node-postgres style). Writing qpTotals.rows etc. produced undefined and tripped writeFileSync. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
cdba27a106 |
Fix migrate.mjs: also convert leagues.created_by from Clerk IDs to UUIDs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
2bad3dd75e | refactor: migrate server code from JavaScript to TypeScript with build pipeline |