215 lines
8.0 KiB
Markdown
215 lines
8.0 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project Overview
|
|
|
|
A real-time trivia game web application with Flask backend and React frontend, featuring WebSocket support for live score updates and question displays. The app supports creating question banks, setting up games with teams, and running live trivia games with separate contestant (TV display) and admin control views.
|
|
|
|
## Tech Stack
|
|
|
|
**Backend:**
|
|
- Flask 3.0+ with Flask-SocketIO for WebSocket support
|
|
- SQLAlchemy ORM with Flask-Migrate for database migrations
|
|
- SQLite database (located at `backend/instance/trivia.db`)
|
|
- Eventlet for async/WebSocket server
|
|
|
|
**Frontend:**
|
|
- React 19 with React Router 7
|
|
- Vite for build tooling and dev server
|
|
- Socket.IO client for WebSocket communication
|
|
- Axios for HTTP requests
|
|
- **Note:** Frontend source is nested at `frontend/frontend/` (double directory structure)
|
|
|
|
## Development Commands
|
|
|
|
### Backend Setup and Running
|
|
|
|
```bash
|
|
# Install Python dependencies
|
|
uv sync
|
|
|
|
# Initialize database (first time only)
|
|
export FLASK_APP=backend.app:create_app
|
|
uv run flask db init
|
|
|
|
# Create and apply migrations
|
|
uv run flask db migrate -m "Description of changes"
|
|
uv run flask db upgrade
|
|
|
|
# Run backend server (use PORT=5001 to avoid macOS AirPlay conflict on 5000)
|
|
PORT=5001 uv run python main.py
|
|
```
|
|
|
|
### Frontend Setup and Running
|
|
|
|
```bash
|
|
# Install frontend dependencies
|
|
cd frontend/frontend # Note the nested directory
|
|
npm install
|
|
|
|
# Run frontend dev server (with proxy to backend)
|
|
npm run dev # Runs on port 3000
|
|
|
|
# Build for production (outputs to backend/static/)
|
|
npm run build
|
|
```
|
|
|
|
### Database Migrations
|
|
|
|
When you modify models in `backend/models.py`:
|
|
```bash
|
|
export FLASK_APP=backend.app:create_app
|
|
uv run flask db migrate -m "Description of changes"
|
|
uv run flask db upgrade
|
|
```
|
|
|
|
### Docker Development (Recommended)
|
|
|
|
Docker Compose provides the easiest development setup with automatic hot reload for both frontend and backend:
|
|
|
|
```bash
|
|
# Start all services (backend + frontend with hot reload)
|
|
docker compose up
|
|
|
|
# Start in detached mode
|
|
docker compose up -d
|
|
|
|
# View logs
|
|
docker compose logs -f
|
|
|
|
# Stop all services
|
|
docker compose down
|
|
|
|
# Rebuild images after dependency changes
|
|
docker compose up --build
|
|
|
|
# Run database migrations inside container
|
|
docker compose exec backend uv run flask db migrate -m "Description"
|
|
docker compose exec backend uv run flask db upgrade
|
|
```
|
|
|
|
**Services:**
|
|
- Backend: http://localhost:5001 (Flask + SocketIO)
|
|
- Frontend: http://localhost:3000 (Vite dev server)
|
|
|
|
**Hot Reload:**
|
|
- Backend: Changes to `backend/`, `main.py`, and `migrations/` automatically reload Flask
|
|
- Frontend: Changes to `src/`, `public/`, and config files automatically reload Vite
|
|
- Database and uploaded images persist in volumes
|
|
|
|
**Architecture:**
|
|
- `Dockerfile.backend`: Python 3.14 with uv, Flask in debug mode
|
|
- `Dockerfile.frontend`: Node 18 with Vite dev server
|
|
- `docker-compose.yml`: Orchestrates both services with volume mounts for hot reload
|
|
- Named volume for `node_modules` to avoid host/container conflicts
|
|
- Health check on backend before starting frontend
|
|
|
|
## Architecture
|
|
|
|
### Application Factory Pattern
|
|
|
|
The Flask app uses an application factory (`backend/app.py:create_app()`) which:
|
|
- Initializes extensions (SQLAlchemy, Flask-Migrate, Flask-SocketIO, CORS)
|
|
- Registers blueprints for routes
|
|
- Serves React frontend in production from `backend/static/`
|
|
- Configured via `backend/config.py` with environment-based settings
|
|
|
|
### Database Models (`backend/models.py`)
|
|
|
|
Core entities:
|
|
- **Question**: Stores trivia questions (text or image type) with answers and optional categories
|
|
- **Game**: Represents a trivia game session with `is_active` flag and `current_question_index`
|
|
- **GameQuestion**: Junction table linking questions to games with ordering
|
|
- **Team**: Teams participating in a game
|
|
- **Score**: Tracks points awarded per team per question (composite unique constraint on team_id + question_index)
|
|
- **Category**: Optional categorization for questions
|
|
|
|
Key relationships:
|
|
- Games can only be active one at a time (`Game.get_active()` class method)
|
|
- Teams calculate `total_score` as a property by summing all Score records
|
|
- Questions can be reused across multiple games via GameQuestion junction table
|
|
|
|
### WebSocket Architecture (`backend/sockets/events.py`, `backend/services/game_service.py`)
|
|
|
|
**Room-based design:**
|
|
- Each game has two rooms: `game_{id}_contestant` and `game_{id}_admin`
|
|
- Contestants join the contestant room and see questions WITHOUT answers
|
|
- Admin joins the admin room and sees questions WITH answers
|
|
- Score updates broadcast to both rooms
|
|
|
|
**Key events:**
|
|
- `join_game` / `leave_game`: Client joins/leaves game rooms with role (contestant/admin)
|
|
- `game_started`: Emitted when game starts
|
|
- `question_changed`: Sent to contestant room (no answer)
|
|
- `question_with_answer`: Sent to admin room (with answer)
|
|
- `score_updated`: Broadcasts score changes to both rooms
|
|
- `answer_visibility_changed`: Toggles answer display on contestant screen
|
|
|
|
**Game flow:**
|
|
- Admin starts game via `/api/admin/game/<id>/start` → broadcasts first question
|
|
- Admin navigates questions with `/next` and `/prev` → broadcasts to all clients
|
|
- Admin awards points via `/award` → creates/updates Score records → broadcasts to all
|
|
- Optional answer reveal to contestants via `/toggle-answer`
|
|
|
|
### API Routes
|
|
|
|
Routes are organized into blueprints:
|
|
- `backend/routes/questions.py`: Question CRUD (`/api/questions`)
|
|
- `backend/routes/games.py`: Game management (`/api/games`)
|
|
- `backend/routes/teams.py`: Team management (`/api/teams`)
|
|
- `backend/routes/admin.py`: Game control endpoints (`/api/admin/game/<id>/...`)
|
|
- `backend/routes/categories.py`: Category management (`/api/categories`)
|
|
|
|
Admin endpoints use `game_service.py` functions which handle both database updates AND WebSocket broadcasts.
|
|
|
|
### Frontend Build Configuration
|
|
|
|
**Development:**
|
|
- Vite dev server runs on port 3000
|
|
- Proxies `/api` and `/socket.io` requests to Flask backend on port 5001
|
|
- Configuration in `frontend/frontend/vite.config.js`
|
|
|
|
**Production:**
|
|
- `npm run build` outputs to `backend/static/` (configured via `build.outDir`)
|
|
- Flask serves the built React app and handles client-side routing
|
|
- All non-API routes return `index.html` for React Router
|
|
|
|
### Image Handling
|
|
|
|
- Images uploaded to `backend/static/images/` via `backend/services/image_service.py`
|
|
- Image questions store `image_path` in the Question model
|
|
- Images served as static files by Flask in production
|
|
|
|
## Common Development Patterns
|
|
|
|
### Adding a New Model Field
|
|
|
|
1. Modify the model in `backend/models.py`
|
|
2. Update the model's `to_dict()` method if the field should be serialized
|
|
3. Create migration: `uv run flask db migrate -m "Add field_name to Model"`
|
|
4. Apply migration: `uv run flask db upgrade`
|
|
|
|
### Adding a New WebSocket Event
|
|
|
|
1. Define event handler in `backend/sockets/events.py` using `@socketio.on('event_name')`
|
|
2. Use `emit()` to send responses or `join_room()`/`leave_room()` for room management
|
|
3. To broadcast to specific rooms, use `emit('event', data, room='room_name')`
|
|
4. Update frontend Socket.IO hook to listen for the new event
|
|
|
|
### Adding a New API Endpoint
|
|
|
|
1. Add route function to appropriate blueprint in `backend/routes/`
|
|
2. Use `db.session.commit()` for database changes with try/except for rollback
|
|
3. Return JSON responses with appropriate status codes
|
|
4. For admin actions affecting game state, use functions from `game_service.py` to ensure WebSocket broadcasts
|
|
|
|
## Important Notes
|
|
|
|
- **Port 5000 conflict**: macOS AirPlay Receiver uses port 5000 by default. Use `PORT=5001` when running the backend.
|
|
- **Nested frontend directory**: Frontend source code is at `frontend/frontend/`, not `frontend/src/`
|
|
- **Single active game**: Only one game can be active at a time (enforced in `start_game()`)
|
|
- **Score uniqueness**: A team can only have one score per question (enforced by database constraint)
|
|
- **WebSocket server**: Must use `socketio.run(app)` not `app.run()` for WebSocket support (configured in `main.py`)
|
|
- **CORS**: Configured to allow all origins in development via `CORS_ORIGINS=*`
|