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.
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:
1.**server.ts** - Main entry point that creates the HTTP server and initializes Socket.IO
2.**server/app.ts** - Express app with React Router request handler and database context
3.**server/socket.ts** - Socket.IO server for real-time draft updates
4.**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
**Socket Integration**: The server uses `getSocketIO()` from `server/socket.ts` to emit events. Clients use the `useDraftSocket()` hook (`app/hooks/useDraftSocket.ts`) to listen for events.
Game/event dates are stored in **two different places** depending on the event type, and any date-based query must account for both:
1.**`scoringEvents.eventDate`** (`date` column, `YYYY-MM-DD` string) — set directly on the event. Used for non-bracket events (majors, final standings) and sometimes for bracket events when the admin sets a single date for the whole round.
2.**`playoffMatchGames.scheduledAt`** (`timestamp` column) — set on individual games within a bracket match. Bracket events (e.g. a playoff series) often have `eventDate = null` on the scoring event itself, with each game's exact time stored here instead.
**Pattern for date-range queries:** Use a two-step approach:
1.`selectDistinct` event IDs via left joins through `playoffMatches → playoffMatchGames`, filtering with `eventDate IN [dates] OR (scheduledAt >= dayStart AND scheduledAt <= dayEnd)`
2.`findMany` on the matched IDs to load full event data with relations
See `getEventsForDates` and `getUpcomingEventsForDraftedParticipants` in `app/models/scoring-event.ts` for the reference implementation. All timestamp bounds use UTC (`T00:00:00.000Z` / `T23:59:59.999Z`).
3.**Verify** the generated migration SQL in `drizzle/` is correct
4.**Verify** a corresponding snapshot was created in `drizzle/meta/` (e.g., `0070_snapshot.json`)
5. Run `npm run db:migrate` to apply migration
6. Add model functions in `app/models/` for new queries
7. Update TypeScript types if needed
**Drizzle Migration Rules (CRITICAL):**
- **NEVER** manually create migration SQL files or journal entries. Always use `drizzle-kit generate` or `drizzle-kit generate --custom` so that snapshot files are created automatically.
- **NEVER** edit `drizzle/meta/_journal.json` by hand. The journal and snapshots must stay in sync — drizzle-kit manages both.
- Every migration **must** have a matching snapshot in `drizzle/meta/`. Missing snapshots cause `drizzle-kit generate` to fail with "data is malformed" errors, which blocks all future migration generation.
- Snapshot JSON must only contain PostgreSQL-valid fields. Do not add `"autoincrement"` to column definitions — PostgreSQL uses sequences/serial instead, and invalid fields cause Zod validation failures.
- If `drizzle-kit generate` prompts for column renames interactively, use `drizzle-kit generate --custom` instead and write the migration SQL by hand (include data backfill logic as needed).
- After generating, run `drizzle-kit generate` again to confirm "No schema changes" — this validates the snapshot chain is healthy.
- For destructive migrations (dropping columns, renaming), make the SQL idempotent with `IF EXISTS`/`IF NOT EXISTS` guards where possible.