Skip to content

Commit 490ddf8

Browse files
authored
Ft/add alembic support (#78)
Improves #76
2 parents c41f4df + ef60996 commit 490ddf8

File tree

10 files changed

+53
-499
lines changed

10 files changed

+53
-499
lines changed

README.md

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,16 @@ A web platform for hosting and participating in collaborative online competition
6565

6666
d. Users can then click "Login with Wikimedia" on the login page
6767

68-
7. **Initialize database**
68+
7. **Initialize database with Alembic migrations**
6969
```bash
70-
python init_db.py
70+
# Apply all database migrations
71+
alembic upgrade head
72+
73+
# Or use the helper script
74+
python scripts/migrate.py upgrade head
7175
```
76+
77+
**Note**: The app does not automatically run migrations on startup. You must run Alembic migrations manually before starting the application.
7278

7379
8. **Run the application**
7480

@@ -78,7 +84,7 @@ A web platform for hosting and participating in collaborative online competition
7884

7985
Terminal 1 - Flask Backend:
8086
```bash
81-
python app.py
87+
python main.py
8288
```
8389

8490
Terminal 2 - Vue.js Frontend:
@@ -98,7 +104,7 @@ A web platform for hosting and participating in collaborative online competition
98104
npm install # Only needed first time
99105
npm run build
100106
cd ../backend
101-
python app.py
107+
python main.py
102108
```
103109

104110
Access at: `http://localhost:5000` (Flask serves built Vue.js files)
@@ -109,6 +115,8 @@ A web platform for hosting and participating in collaborative online competition
109115

110116
**Quick Testing Option**: If you want to skip MySQL setup, edit `.env` and change `DATABASE_URL` to `sqlite:///wikicontest.db`
111117

118+
**Database Migrations**: The application uses Alembic for database migrations. Always run `alembic upgrade head` before starting the app to ensure your database schema is up to date. The app does not automatically run migrations on startup.
119+
112120
**Frontend Development**: For the best development experience, run the Vue.js dev server (`npm run dev` in `frontend/` directory) alongside Flask. The dev server proxies API requests to Flask automatically.
113121

114122
## 🔧 Configuration
@@ -146,8 +154,11 @@ The `.env.example` file contains all configuration options. Copy it to `.env` an
146154
## 🧪 Testing
147155

148156
```bash
157+
# Make sure migrations are applied first
158+
alembic upgrade head
159+
149160
# Test the application
150-
python app.py
161+
python main.py
151162
# Open http://localhost:5000 and test:
152163
# - User registration/login
153164
# - Contest creation
@@ -162,21 +173,30 @@ python app.py
162173
export DATABASE_URL=your-production-mysql-url
163174
```
164175

165-
2. **Run with production server**
176+
2. **Apply database migrations**
177+
```bash
178+
alembic upgrade head
179+
```
180+
181+
3. **Run with production server**
166182
```bash
167183
pip install gunicorn
168-
gunicorn -w 4 -b 0.0.0.0:5000 app:app
184+
gunicorn -w 4 -b 0.0.0.0:5000 "app:app"
169185
```
170186

171187
## Project Structure
172188

173189
```
174190
wikicontest/
175191
├── backend/ # Flask application
176-
│ ├── app.py # Main application
177-
│ ├── models/ # Database models
178-
│ ├── routes/ # API endpoints
179-
│ └── middleware/ # Authentication
192+
│ ├── main.py # Application entry point
193+
│ ├── app/ # Application package
194+
│ │ ├── __init__.py # Flask app factory
195+
│ │ ├── models/ # Database models
196+
│ │ ├── routes/ # API endpoints
197+
│ │ └── middleware/ # Authentication
198+
│ ├── alembic/ # Database migrations (Alembic)
199+
│ └── scripts/ # Utility scripts
180200
└── frontend/ # Vue.js frontend
181201
├── src/ # Source files
182202
│ ├── views/ # Page components

backend/README.md

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,6 @@ backend/
4141
│ ├── versions/ # Migration version files
4242
│ ├── script.py.mako # Migration template
4343
│ └── README.md # Alembic documentation
44-
├── migrations/ # Custom migration scripts (one-time/data migrations)
45-
│ ├── README.md
46-
│ ├── add_article_metadata_to_submissions.py
47-
│ ├── add_expansion_bytes_to_submissions.py
48-
│ └── add_size_at_start_to_submissions.py
4944
├── scripts/ # Utility scripts
5045
│ ├── init_db.py # Database initialization
5146
│ ├── backfill_article_info.py # Backfill article metadata
@@ -333,17 +328,6 @@ For detailed Alembic documentation, see:
333328
- [`docs/ALEMBIC_MODEL_COMPATIBILITY.md`](docs/ALEMBIC_MODEL_COMPATIBILITY.md) - Model compatibility guide
334329
- [`docs/ALEMBIC_SETUP_VERIFICATION.md`](docs/ALEMBIC_SETUP_VERIFICATION.md) - Setup verification checklist
335330

336-
#### Legacy Migration Scripts
337-
338-
The application also includes custom migration scripts in the `migrations/` directory for reference. These can be run manually if needed:
339-
340-
```bash
341-
# Run a specific migration
342-
python migrations/add_article_metadata_to_submissions.py
343-
```
344-
345-
**Note**: For new schema changes, use Alembic migrations instead of custom scripts.
346-
347331
### Utility Scripts
348332

349333
Scripts in the `scripts/` directory:

backend/app/__init__.py

Lines changed: 4 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@
2929
from flask_jwt_extended.exceptions import JWTDecodeError, NoAuthorizationError
3030
from dotenv import load_dotenv
3131
import requests
32-
from sqlalchemy import text, inspect
33-
from sqlalchemy.exc import SQLAlchemyError, ProgrammingError, OperationalError
32+
from sqlalchemy.exc import SQLAlchemyError
3433

3534
# Local imports
3635
from app.database import db
@@ -743,70 +742,14 @@ def internal_error(_error):
743742
# APPLICATION STARTUP
744743
# =============================================================================
745744

746-
def migrate_database():
747-
"""
748-
Run database migrations to add new columns if they don't exist.
749-
This ensures the database schema matches the current model definitions.
750-
"""
751-
try:
752-
# Get table inspector to check existing columns
753-
inspector = inspect(db.engine)
754-
columns = [col['name'] for col in inspector.get_columns('submissions')]
755-
756-
# Check which columns need to be added
757-
columns_to_add = []
758-
if 'article_author' not in columns:
759-
columns_to_add.append(('article_author', 'VARCHAR(200) NULL'))
760-
if 'article_created_at' not in columns:
761-
columns_to_add.append(('article_created_at', 'VARCHAR(50) NULL'))
762-
if 'article_word_count' not in columns:
763-
columns_to_add.append(('article_word_count', 'INT NULL'))
764-
if 'article_page_id' not in columns:
765-
columns_to_add.append(('article_page_id', 'VARCHAR(50) NULL'))
766-
767-
# Add missing columns
768-
if columns_to_add:
769-
print("📦 Running database migration: Adding article metadata columns...")
770-
for col_name, col_type in columns_to_add:
771-
try:
772-
db.session.execute(
773-
text(f"ALTER TABLE submissions ADD COLUMN {col_name} {col_type}")
774-
)
775-
print(f" ✓ Added column: {col_name}")
776-
except (ProgrammingError, OperationalError) as error:
777-
# Column might already exist or there's a SQL/database error
778-
print(f" ⚠ Could not add column {col_name}: {error}")
779-
780-
db.session.commit()
781-
print("✓ Database migration completed")
782-
else:
783-
print("✓ Database schema is up to date")
784-
785-
except (OperationalError, SQLAlchemyError) as error:
786-
# If table doesn't exist yet, db.create_all() will handle it
787-
# Or if there's a database connection/query error
788-
print(f"⚠ Migration check skipped (table may not exist yet): {error}")
789-
db.session.rollback()
790-
791745
if __name__ == '__main__':
792-
# Main application entry point.
793-
# This section runs when the script is executed directly (not imported).
794-
# It initializes the database tables and starts the Flask development server.
795-
796-
# Initialize database tables
797-
# This creates all tables defined in the models if they don't exist
798-
with app.app_context():
799-
db.create_all()
800-
print("✓ Database tables initialized successfully")
801-
802-
# Run migrations to add new columns to existing tables
803-
migrate_database()
804-
805-
# Start the Flask development server
746+
# This file can be run directly, but main.py is the recommended entry point.
747+
# Database migrations are handled by Alembic - run 'alembic upgrade head' before starting.
806748
# Debug mode is enabled for development (disable in production)
807749
print("🚀 Starting WikiContest API server...")
808750
print("📡 Server will be available at: http://localhost:5000")
809751
print("🔧 Debug mode: ENABLED")
752+
print("ℹ Note: Use Alembic for database migrations (alembic upgrade head)")
810753

811754
app.run(
812755
debug=True, # Enable debug mode for development

backend/docs/ALEMBIC_USAGE_GUIDE.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -353,17 +353,17 @@ If you have conflicts with existing migrations:
353353

354354
10. **Use transactions** - Alembic wraps migrations in transactions by default, but be aware of database-specific limitations
355355

356-
## Integration with Existing Migrations
356+
## Migration Strategy
357357

358-
The project also has custom migration scripts in the `migrations/` directory. These can coexist with Alembic migrations:
358+
The project uses **Alembic exclusively** for all database migrations:
359359

360-
- **Custom migrations** (`migrations/`): For one-time data migrations or complex changes
361-
- **Alembic migrations** (`alembic/versions/`): For schema version control
360+
- **Alembic migrations** (`alembic/versions/`): For all schema version control
361+
- **Alembic** handles both schema changes (adding columns, tables, indexes, etc.) and data migrations
362362

363363
**Guidelines:**
364-
- **Use Alembic** for all new schema changes (adding columns, tables, indexes, etc.)
365-
- **Use custom scripts** for data migrations (backfilling data, one-time transformations)
366-
- Both can coexist, but prefer Alembic for schema changes
364+
- **Use Alembic** for all database changes (schema and data migrations)
365+
- Create migrations using `alembic revision --autogenerate` for schema changes
366+
- Create manual migrations using `alembic revision` for data migrations or complex operations
367367

368368
## Quick Reference
369369

backend/docs/ARCHITECTURE.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ backend/
1818
│ ├── routes/ # API route blueprints
1919
│ ├── middleware/ # Middleware functions
2020
│ └── utils/ # Utility functions
21-
├── migrations/ # Database migration scripts
21+
├── alembic/ # Alembic database migrations
2222
├── scripts/ # Utility scripts
2323
├── toolforge/ # Toolforge deployment files
2424
├── tests/ # Test files
@@ -162,16 +162,14 @@ Routes use the `@handle_errors` decorator for consistent error handling.
162162

163163
## Database Migrations
164164

165-
### Custom Migration Scripts
165+
The application uses **Alembic** for all database migrations:
166166

167-
Migrations are custom Python scripts in `migrations/`:
168-
- Check if columns exist before adding
169-
- Use SQLAlchemy's `text()` for raw SQL
170-
- Idempotent (safe to run multiple times)
167+
- **Migration files**: Located in `alembic/versions/`
168+
- **Version tracking**: Managed by Alembic's `alembic_version` table
169+
- **Migration commands**: Use `alembic upgrade head` to apply migrations
170+
- **Auto-generation**: Use `alembic revision --autogenerate` to create migrations from model changes
171171

172-
### Automatic Migrations
173-
174-
Migrations run automatically on application startup via `migrate_database()` function.
172+
For detailed migration documentation, see [`docs/ALEMBIC_USAGE_GUIDE.md`](../docs/ALEMBIC_USAGE_GUIDE.md).
175173

176174
## Utility Functions
177175

backend/main.py

Lines changed: 4 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,80 +2,19 @@
22
"""
33
Main entry point for running the WikiContest Flask application.
44
5-
This script initializes the database and starts the Flask development server.
6-
For production, use a WSGI server like gunicorn or uwsgi.
5+
This script starts the Flask API server.
76
8-
Usage:
9-
python main.py
7+
Note: Database migrations are handled separately by Alembic.
108
"""
119

12-
# Third-party imports should come before local imports
13-
from sqlalchemy import inspect, text
14-
from sqlalchemy.exc import SQLAlchemyError, ProgrammingError, OperationalError
15-
1610
# Local application imports
17-
from app import app, db
18-
19-
def migrate_database():
20-
"""
21-
Run database migrations to add new columns if they don't exist.
22-
This ensures the database schema matches the current model definitions.
23-
"""
24-
try:
25-
# Get table inspector to check existing columns
26-
inspector = inspect(db.engine)
27-
columns = [col['name'] for col in inspector.get_columns('submissions')]
28-
29-
# Check which columns need to be added
30-
columns_to_add = []
31-
if 'article_author' not in columns:
32-
columns_to_add.append(('article_author', 'VARCHAR(200) NULL'))
33-
if 'article_created_at' not in columns:
34-
columns_to_add.append(('article_created_at', 'VARCHAR(50) NULL'))
35-
if 'article_word_count' not in columns:
36-
columns_to_add.append(('article_word_count', 'INT NULL'))
37-
if 'article_page_id' not in columns:
38-
columns_to_add.append(('article_page_id', 'VARCHAR(50) NULL'))
39-
40-
# Add missing columns
41-
if columns_to_add:
42-
print("📦 Running database migration: Adding article metadata columns...")
43-
for col_name, col_type in columns_to_add:
44-
try:
45-
db.session.execute(
46-
text(f"ALTER TABLE submissions ADD COLUMN {col_name} {col_type}")
47-
)
48-
print(f" ✓ Added column: {col_name}")
49-
except (ProgrammingError, OperationalError) as error:
50-
# Column might already exist or there's a SQL/database error
51-
print(f" ⚠ Could not add column {col_name}: {error}")
52-
53-
db.session.commit()
54-
print("✓ Database migration completed")
55-
else:
56-
print("✓ Database schema is up to date")
57-
58-
except (OperationalError, SQLAlchemyError) as error:
59-
# If table doesn't exist yet, db.create_all() will handle it
60-
# Or if there's a database connection/query error
61-
print(f"⚠ Migration check skipped (table may not exist yet): {error}")
62-
db.session.rollback()
11+
from app import app
6312

6413
if __name__ == '__main__':
6514
# Main application entry point.
6615
# This section runs when the script is executed directly (not imported).
67-
# It initializes the database tables and starts the Flask development server.
68-
69-
# Initialize database tables
70-
# This creates all tables defined in the models if they don't exist
71-
with app.app_context():
72-
db.create_all()
73-
print("✓ Database tables initialized successfully")
7416

75-
# Run migrations to add new columns to existing tables
76-
migrate_database()
77-
78-
# Start the Flask development server
17+
# Start the Flask server
7918
# Debug mode is enabled for development (disable in production)
8019
print("🚀 Starting WikiContest API server...")
8120
print("📡 Server will be available at: http://localhost:5000")
@@ -86,4 +25,3 @@ def migrate_database():
8625
host='0.0.0.0', # Allow connections from any IP
8726
port=5000 # Default Flask development port
8827
)
89-

0 commit comments

Comments
 (0)