* Update claude.md to push more tests. * Archiving old plans. * fix: run DB migrations programmatically on server startup Replace unreliable drizzle-kit CLI migration with drizzle-orm's built-in migrator running in the server process before accepting connections. This ensures migrations are always applied on deploy and fails fast if they error. - Add runMigrations() to server.ts using drizzle-orm/postgres-js/migrator - Skip migrations in development (handled manually via db:migrate) - Add DATABASE_URL guard with a clear error message - Remove start:production script (now identical to start) - Update Dockerfile CMD to use npm run start Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
9.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Brackt.com is a fantasy sports drafting platform built with React Router 7, featuring real-time draft functionality with Socket.IO, PostgreSQL database with DrizzleORM, and Clerk authentication.
Tech Stack
- Framework: React Router 7 with SSR
- Runtime: Node.js with Express server
- Database: PostgreSQL with DrizzleORM
- Auth: Clerk (@clerk/react-router)
- Real-time: Socket.IO (server/socket.ts)
- Styling: TailwindCSS + ShadCN UI components
- Testing: Vitest (unit/component) + Cypress (E2E)
- TypeScript: Strict mode enabled
Development Commands
# Development (HMR enabled)
npm run dev
# Build
npm run build # Build both Remix and server
npm run build:remix # Build React Router app only
npm run build:server # Build Express server only
# Database
npm run db:generate # Generate Drizzle migrations
npm run db:migrate # Run migrations
# Testing
npm test # Unit tests (watch mode)
npm run test:run # Unit tests (run once)
npm run test:ui # Unit tests with UI
npm run test:coverage # Coverage report
npm run test:e2e # Cypress interactive
npm run test:e2e:headless # Cypress headless
npm run test:all # Run all tests
# Type checking
npm run typecheck # Check all TypeScript files
# Production
npm start # Start production server (requires build)
npm run start:production # Run migrations + start server
Architecture Overview
Server Architecture
The application uses a dual-server architecture:
- server.ts - Main entry point that creates the HTTP server and initializes Socket.IO
- server/app.ts - Express app with React Router request handler and database context
- server/socket.ts - Socket.IO server for real-time draft updates
- server/timer.ts - Draft timer system that runs every second to update active draft timers
Important: The timer system (server/timer.ts) must be started for draft functionality. It uses a dedicated database connection and setInterval to manage all active draft timers.
Database Layer
- Schema:
database/schema.ts- All Drizzle table definitions - Context: Database connection provided via AsyncLocalStorage context (
database/context.ts) - Models:
app/models/- Business logic and database queries organized by domain- Each model exports typed functions for CRUD operations
- Models use Drizzle relations for joins
- Example:
app/models/league.ts,app/models/season.ts,app/models/draft-pick.ts
Frontend Architecture
- Routes: Defined in
app/routes.tsusing React Router config syntax- IMPORTANT: React Router 7 requires ALL routes to be explicitly registered in
app/routes.ts - When creating a new route file, you MUST also add it to
app/routes.ts - Example: If you create
app/routes/admin.sports-seasons.$id.new-feature.tsx, you must add:route( "sports-seasons/:id/new-feature", "routes/admin.sports-seasons.$id.new-feature.tsx" ),
- IMPORTANT: React Router 7 requires ALL routes to be explicitly registered in
- File-based routing: Route files in
app/routes/map to URL patterns$param= dynamic segment (e.g.,$leagueId.tsx)_index.tsx= index route.separator for nested routes (e.g.,leagues/$leagueId.settings.tsx)
- Loaders/Actions: Use React Router's loader/action pattern for data fetching and mutations
- Components: Organized in
app/components/with UI components inapp/components/ui/
Real-time Features
Socket.IO is used for live draft updates:
- Join Draft: Clients join a room for their season (
join-draftevent) - Timer Updates: Server broadcasts
timer-updateevery second during active drafts - Pick Made: Broadcasts
pick-madewhen a participant is drafted - Autodraft: Broadcasts
autodraft-updatedwhen teams enable/disable autodraft - Team Connection: Broadcasts
team-connected/team-disconnectedfor presence
Socket Integration: The server uses getSocketIO() from server/socket.ts to emit events. Clients use useSocket() hook (if implemented) to listen for events.
Key Domain Models
Fantasy Draft System
- League - Container for multiple seasons, has commissioners
- Season - Single year/iteration of a league with specific settings
- Status:
pre_draft→draft→active→completed - Stores draft settings (rounds, timer settings, etc.)
- Status:
- Team - Belongs to a season, owned by a user
- Draft Slot - Defines draft order for teams in a season
- Draft Pick - Record of each pick made during draft
- Draft Queue - Pre-ordered list of participants a team wants to draft
- Draft Timer - Active timer for current team on the clock
- Autodraft Settings - Per-team settings for automatic drafting
Sports Data System
- Sport - Base sport (e.g., NFL, NBA) with type (team/individual)
- Sports Season - Specific season of a sport (e.g., "2024 NFL Season")
- Participant - Individual athlete or team that can be drafted
- Season Template - Reusable configuration for league seasons
- Season Sport - Links a fantasy season to sports it covers
- Participant Result - Stores final standings/scores for participants
Important Patterns
Database Queries
Always use the model layer instead of direct Drizzle queries in routes:
// Good
import { findLeagueById } from "~/models/league";
const league = await findLeagueById(leagueId);
// Avoid - don't query directly in routes
const league = await db.query.leagues.findFirst({...});
Authentication
- User data synced from Clerk via webhook (
routes/api/webhooks/clerk.ts) - Check
clerkIdfor ownership validation - Admin checks via
isAdminboolean on users table
Draft Logic
The draft system implements snake draft:
- Draft slots define the base order (1, 2, 3, ...)
- Each round alternates: odd rounds go 1→N, even rounds go N→1
- Current pick determined by
currentPickNumberon season - Timer system in
server/timer.tshandles autopicks when time expires
Testing Strategy
- Unit tests: Pure functions and utilities in
__tests__/directories - Component tests: React components using React Testing Library
- E2E tests: Critical user flows in
cypress/e2e/ - Fixtures: Reusable test data in
app/test/fixtures/ - See
TESTING.mdfor comprehensive testing guide
IMPORTANT: Tests are required for new features. When adding any new feature, you must include appropriate tests:
- New model functions → unit tests in a co-located
__tests__/directory - New route loaders/actions → unit or integration tests for the logic
- New utility functions → unit tests
- New components with non-trivial logic → component tests
- Critical user flows → Cypress E2E tests
Do not consider a feature complete until tests are written and passing.
Common Workflows
Adding a New Route
CRITICAL: Both steps 1 and 2 are required - the route will not work if you skip step 1!
- FIRST: Add route config to
app/routes.ts- Example:
route("admin/sports-seasons/:id/new-page", "routes/admin.sports-seasons.$id.new-page.tsx") - Nested admin routes go inside the
route("admin", ...)array
- Example:
- THEN: Create file in
app/routes/with corresponding name - Export loader/action if needed
- TypeScript types are auto-generated via
react-router typegen
Common mistake: Creating the route file but forgetting to register it in app/routes.ts - this will result in a 404!
Database Changes
- Update
database/schema.tswith new tables/columns - Run
npm run db:generateto create migration - Run
npm run db:migrateto apply migration - Add model functions in
app/models/for new queries - Update TypeScript types if needed
Adding Socket.IO Events
- Define event types in
server/socket.d.tsinterfaces - Add event handler in
server/socket.ts - Emit events from server code using
getSocketIO().to(room).emit() - Listen for events in client components
Admin Features
Admin routes are nested under /admin and require isAdmin: true on the user:
- Sports management (
admin.sports.*) - Sports seasons (
admin.sports-seasons.*) - Season templates (
admin.templates.*) - Data sync utilities (
admin.data-sync.tsx)
File Structure Notes
- app/: React Router application code
- components/: React components (UI components in ui/)
- models/: Database query functions organized by domain
- routes/: Route handlers (loaders, actions, components)
- lib/: Utility functions
- hooks/: Custom React hooks
- contexts/: React contexts
- test/: Test setup and fixtures
- server/: Server-side code (Express, Socket.IO, timers)
- database/: Drizzle schema and context
- drizzle/: Generated migrations
- cypress/: E2E tests
Environment Variables
Required in .env:
DATABASE_URL- PostgreSQL connection string- Clerk variables for authentication (see
.env.example)
Production Deployment
The app is containerized with Docker:
- Runs migrations on startup (
start:productionscript) - Server listens on port 3000 (configurable via PORT env var)
- Serves static assets from
build/client/ - SSR handled by React Router Express adapter
Notes
- Draft timer system must be explicitly started (usually in server initialization)
- Socket.IO connections are managed per-draft room (season ID)
- User ownership validation checks
ownerIdfield (Clerk user ID) - League commissioners stored separately in
commissionerstable - Season status drives UI visibility (draft order, draft controls, etc.)