diff --git a/MIGRATIONS.md b/MIGRATIONS.md new file mode 100644 index 0000000..fc975df --- /dev/null +++ b/MIGRATIONS.md @@ -0,0 +1,147 @@ +# Database Migrations Guide + +This document covers the proper workflow for database migrations and how to fix common issues. + +## Migration Workflow Checklist + +When making model changes, **always follow this order**: + +### 1. Make model changes +Edit `backend/models.py` with your changes. + +### 2. Create the migration +```bash +# Local development +uv run flask --app backend.app:create_app db migrate -m "Description of changes" + +# Docker development +docker compose exec backend uv run flask --app backend.app:create_app db migrate -m "Description of changes" +``` + +### 3. Review the generated migration +Check `migrations/versions/` for the new file. Verify it does what you expect. + +### 4. Apply locally and test +```bash +uv run flask --app backend.app:create_app db upgrade +``` + +### 5. Commit BOTH the model AND migration together +```bash +git add backend/models.py migrations/versions/ +git commit -m "Add field_name to Model" +``` + +**CRITICAL: Never commit model changes without the corresponding migration file!** + +### 6. Deploy and run migrations on production +```bash +# Rebuild to include new migration file +docker compose -f docker-compose.production.yml up --build -d + +# Apply the migration +docker compose -f docker-compose.production.yml exec backend uv run flask --app backend.app:create_app db upgrade +``` + +## Common Issues and Fixes + +### Error: "Can't locate revision identified by 'xxxxx'" + +**Cause:** The database references a migration that doesn't exist in the codebase (migration was applied but never committed). + +**Fix:** +```bash +# 1. Check what revision the DB thinks it's at +docker compose -f docker-compose.production.yml exec backend python -c " +import sqlite3 +conn = sqlite3.connect('backend/instance/trivia.db') +print(conn.execute('SELECT * FROM alembic_version').fetchall()) +" + +# 2. Find the latest valid migration in your codebase +ls migrations/versions/ + +# 3. Force the DB to point to a valid migration +docker compose -f docker-compose.production.yml exec backend python -c " +import sqlite3 +conn = sqlite3.connect('backend/instance/trivia.db') +conn.execute(\"UPDATE alembic_version SET version_num = 'YOUR_VALID_REVISION'\") +conn.commit() +print('Done') +" + +# 4. Manually add any missing columns +docker compose -f docker-compose.production.yml exec backend python -c " +import sqlite3 +conn = sqlite3.connect('backend/instance/trivia.db') +conn.execute('ALTER TABLE tablename ADD COLUMN columnname TYPE') +conn.commit() +print('Column added') +" +``` + +### Error: "table has no column named X" + +**Cause:** Model has columns that don't exist in the database. + +**Fix:** Add the columns manually: +```bash +docker compose -f docker-compose.production.yml exec backend python -c " +import sqlite3 +conn = sqlite3.connect('backend/instance/trivia.db') +cursor = conn.execute('PRAGMA table_info(tablename)') +print('Current columns:', [col[1] for col in cursor.fetchall()]) +" + +# Then add missing columns: +docker compose -f docker-compose.production.yml exec backend python -c " +import sqlite3 +conn = sqlite3.connect('backend/instance/trivia.db') +conn.execute('ALTER TABLE games ADD COLUMN completed_at DATETIME') +conn.execute('ALTER TABLE games ADD COLUMN winners JSON') +conn.commit() +print('Done') +" +``` + +### Checking current migration state + +```bash +# What migration does the DB think it's at? +docker compose -f docker-compose.production.yml exec backend uv run flask --app backend.app:create_app db current + +# What's the latest migration in the codebase? +docker compose -f docker-compose.production.yml exec backend uv run flask --app backend.app:create_app db heads + +# Show migration history +docker compose -f docker-compose.production.yml exec backend uv run flask --app backend.app:create_app db history +``` + +### Nuclear option: Reset migrations (destroys all data!) + +Only use if you can recreate all data: +```bash +# Delete database +docker compose -f docker-compose.production.yml exec backend rm -f backend/instance/trivia.db + +# Recreate from scratch +docker compose -f docker-compose.production.yml exec backend uv run flask --app backend.app:create_app db upgrade +``` + +## SQLite Column Type Reference + +When adding columns manually, use these SQLite types: +- `INTEGER` - for integers, booleans +- `TEXT` or `VARCHAR(N)` - for strings +- `DATETIME` - for dates/times +- `JSON` - for JSON data +- `FLOAT` or `REAL` - for decimals + +## Pre-deployment Checklist + +Before deploying model changes: + +- [ ] Migration file exists in `migrations/versions/` +- [ ] Migration file is committed to git +- [ ] Migration tested locally with `db upgrade` and `db downgrade` +- [ ] Model changes and migration are in the same commit diff --git a/frontend/frontend/src/components/contestant/ContestantView.jsx b/frontend/frontend/src/components/contestant/ContestantView.jsx index a2cc8a0..b4188c8 100644 --- a/frontend/frontend/src/components/contestant/ContestantView.jsx +++ b/frontend/frontend/src/components/contestant/ContestantView.jsx @@ -20,6 +20,8 @@ export default function ContestantView() { const [categories, setCategories] = useState([]); const [currentTurnTeamId, setCurrentTurnTeamId] = useState(null); const [currentTurnTeamName, setCurrentTurnTeamName] = useState(null); + const [questionIndex, setQuestionIndex] = useState(0); + const [totalQuestions, setTotalQuestions] = useState(0); useEffect(() => { // Load initial game state @@ -100,11 +102,14 @@ export default function ContestantView() { console.log("Game started:", data); setGameName(data.game_name); setGameStartTime(new Date()); + setTotalQuestions(data.total_questions); }); socket.on("question_changed", (data) => { console.log("Question changed:", data); setCurrentQuestion(data.question); + setQuestionIndex(data.question_index); + setTotalQuestions(data.total_questions); setShowAnswer(false); // Hide answer when question changes setAnswer(""); setTimerSeconds(30); @@ -339,6 +344,17 @@ export default function ContestantView() { /> + {totalQuestions > 0 && ( +
+ Question {questionIndex + 1} of {totalQuestions} +
+ )} {currentQuestion.category && (