diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 0385787..75640c2 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -8,6 +8,58 @@ from app.config import Config from app.extensions import db, bcrypt, migrate +def _auto_migrate(db): + """Add missing columns to existing tables by comparing model definitions + with actual database schema. This handles the case where new columns are + added to models but db.create_all() only creates new tables.""" + import sqlalchemy + + with db.engine.connect() as conn: + inspector = sqlalchemy.inspect(db.engine) + + for table_name, table in db.metadata.tables.items(): + if not inspector.has_table(table_name): + continue + + existing_cols = {col['name'] for col in inspector.get_columns(table_name)} + + for column in table.columns: + if column.name not in existing_cols: + # Determine SQL type + col_type = column.type.compile(db.engine.dialect) + + # Determine default value + default = '' + if column.default is not None: + if hasattr(column.default, 'arg'): + val = column.default.arg + if callable(val): + default = '' + elif isinstance(val, str): + default = f" DEFAULT '{val}'" + elif isinstance(val, bool): + default = f" DEFAULT {1 if val else 0}" + elif isinstance(val, (int, float)): + default = f" DEFAULT {val}" + elif column.nullable: + default = ' DEFAULT NULL' + elif col_type.upper().startswith(('VARCHAR', 'TEXT')): + default = " DEFAULT ''" + elif col_type.upper().startswith(('INTEGER', 'BIGINT', 'FLOAT')): + default = " DEFAULT 0" + elif col_type.upper() == 'BOOLEAN': + default = " DEFAULT 0" + + sql = f'ALTER TABLE "{table_name}" ADD COLUMN "{column.name}" {col_type}{default}' + try: + conn.execute(db.text(sql)) + print(f'[Auto-Migrate] Added column {table_name}.{column.name} ({col_type})') + except Exception as e: + print(f'[Auto-Migrate] Failed to add {table_name}.{column.name}: {e}') + + conn.commit() + + def create_app(config_class=Config): # Check if static frontend build exists (Docker production mode) static_dir = Path(__file__).resolve().parent.parent / 'static' @@ -61,11 +113,14 @@ def create_app(config_class=Config): def serve_spa(e): return send_from_directory(str(static_dir), 'index.html') - # Create tables + # Create tables + auto-migrate missing columns with app.app_context(): from app import models # noqa: F401 db.create_all() + # Auto-migrate: add missing columns to existing tables + _auto_migrate(db) + # Enable WAL mode for SQLite with db.engine.connect() as conn: conn.execute(db.text('PRAGMA journal_mode=WAL'))