From 99e7592cdc2d2921c407c00b81f5f36bc4a8fc7b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 13:29:29 +0000 Subject: [PATCH 1/2] fix: Add automatic database migration for missing columns - Add run_migrations() function that runs at app startup - Automatically adds price_selector column to search_sites if missing - Prevents UndefinedColumn errors on existing databases --- app/main.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/main.py b/app/main.py index 8d78da9..266956e 100644 --- a/app/main.py +++ b/app/main.py @@ -9,7 +9,9 @@ from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from slowapi import _rate_limit_exceeded_handler from slowapi.errors import RateLimitExceeded +from sqlalchemy import text +from app.database import engine from app.limiter import limiter from app.routers import items, jobs, notifications, openrouter, search, search_sites, settings from app.services.scheduler_service import scheduled_refresh, scheduler @@ -22,8 +24,34 @@ logging.basicConfig( logger = logging.getLogger(__name__) +def run_migrations(): + """Run database migrations for missing columns""" + migrations = [ + # Add price_selector to search_sites if missing + ("search_sites", "price_selector", "ALTER TABLE search_sites ADD COLUMN price_selector VARCHAR(512)"), + ] + + with engine.connect() as conn: + for table, column, sql in migrations: + result = conn.execute(text( + f"SELECT 1 FROM information_schema.columns WHERE table_name = '{table}' AND column_name = '{column}'" + )) + if not result.fetchone(): + logger.info(f"Adding missing column: {table}.{column}") + conn.execute(text(sql)) + conn.commit() + + @asynccontextmanager async def lifespan(app: FastAPI): + # Run database migrations + logger.info("Running database migrations...") + try: + run_migrations() + logger.info("Database migrations completed") + except Exception as e: + logger.error(f"Migration error: {e}") + logger.info("Starting smart scheduler (Heartbeat: 1 minute)") scheduler.add_job(scheduled_refresh, IntervalTrigger(minutes=1), id="refresh_job", replace_existing=True) scheduler.start() From 1c07f084579ab74b8d0787a0e3eab5f255edfcb3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 13:32:24 +0000 Subject: [PATCH 2/2] fix: Improve database migrations - create table if missing - Create search_sites table if it doesn't exist - Add price_selector column only if table exists but column doesn't - Fixes 400 Bad Request when creating search sites --- app/main.py | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/app/main.py b/app/main.py index 266956e..804ddd3 100644 --- a/app/main.py +++ b/app/main.py @@ -25,21 +25,43 @@ logger = logging.getLogger(__name__) def run_migrations(): - """Run database migrations for missing columns""" - migrations = [ - # Add price_selector to search_sites if missing - ("search_sites", "price_selector", "ALTER TABLE search_sites ADD COLUMN price_selector VARCHAR(512)"), - ] - + """Run database migrations for missing tables and columns""" with engine.connect() as conn: - for table, column, sql in migrations: + # 1. Create search_sites table if it doesn't exist + result = conn.execute(text( + "SELECT 1 FROM information_schema.tables WHERE table_name = 'search_sites'" + )) + if not result.fetchone(): + logger.info("Creating search_sites table...") + conn.execute(text(""" + CREATE TABLE search_sites ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + domain VARCHAR(512) UNIQUE NOT NULL, + logo_url VARCHAR(1024), + category VARCHAR(255), + is_active BOOLEAN DEFAULT TRUE, + priority INTEGER DEFAULT 0, + requires_js BOOLEAN DEFAULT FALSE, + price_selector VARCHAR(512), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """)) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_search_sites_domain ON search_sites(domain)")) + conn.execute(text("CREATE INDEX IF NOT EXISTS idx_search_sites_is_active ON search_sites(is_active)")) + conn.commit() + logger.info("search_sites table created") + else: + # 2. Add price_selector column if missing result = conn.execute(text( - f"SELECT 1 FROM information_schema.columns WHERE table_name = '{table}' AND column_name = '{column}'" + "SELECT 1 FROM information_schema.columns WHERE table_name = 'search_sites' AND column_name = 'price_selector'" )) if not result.fetchone(): - logger.info(f"Adding missing column: {table}.{column}") - conn.execute(text(sql)) + logger.info("Adding price_selector column to search_sites...") + conn.execute(text("ALTER TABLE search_sites ADD COLUMN price_selector VARCHAR(512)")) conn.commit() + logger.info("price_selector column added") @asynccontextmanager