Add migrations troubleshooting guide
This commit is contained in:
147
MIGRATIONS.md
Normal file
147
MIGRATIONS.md
Normal file
@@ -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
|
||||||
@@ -20,6 +20,8 @@ export default function ContestantView() {
|
|||||||
const [categories, setCategories] = useState([]);
|
const [categories, setCategories] = useState([]);
|
||||||
const [currentTurnTeamId, setCurrentTurnTeamId] = useState(null);
|
const [currentTurnTeamId, setCurrentTurnTeamId] = useState(null);
|
||||||
const [currentTurnTeamName, setCurrentTurnTeamName] = useState(null);
|
const [currentTurnTeamName, setCurrentTurnTeamName] = useState(null);
|
||||||
|
const [questionIndex, setQuestionIndex] = useState(0);
|
||||||
|
const [totalQuestions, setTotalQuestions] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load initial game state
|
// Load initial game state
|
||||||
@@ -100,11 +102,14 @@ export default function ContestantView() {
|
|||||||
console.log("Game started:", data);
|
console.log("Game started:", data);
|
||||||
setGameName(data.game_name);
|
setGameName(data.game_name);
|
||||||
setGameStartTime(new Date());
|
setGameStartTime(new Date());
|
||||||
|
setTotalQuestions(data.total_questions);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("question_changed", (data) => {
|
socket.on("question_changed", (data) => {
|
||||||
console.log("Question changed:", data);
|
console.log("Question changed:", data);
|
||||||
setCurrentQuestion(data.question);
|
setCurrentQuestion(data.question);
|
||||||
|
setQuestionIndex(data.question_index);
|
||||||
|
setTotalQuestions(data.total_questions);
|
||||||
setShowAnswer(false); // Hide answer when question changes
|
setShowAnswer(false); // Hide answer when question changes
|
||||||
setAnswer("");
|
setAnswer("");
|
||||||
setTimerSeconds(30);
|
setTimerSeconds(30);
|
||||||
@@ -339,6 +344,17 @@ export default function ContestantView() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{totalQuestions > 0 && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontSize: "1.5rem",
|
||||||
|
color: "#999",
|
||||||
|
marginBottom: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Question {questionIndex + 1} of {totalQuestions}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{currentQuestion.category && (
|
{currentQuestion.category && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
Reference in New Issue
Block a user