""" Database migration for Story 3.2: Find Tiered Links This script adds: 1. money_site_url column to projects table 2. article_links table for tracking link relationships Run this script BEFORE deploying Story 3.2 code to production. Usage: python scripts/migrate_story_3.2.py """ import sys from pathlib import Path project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root)) from sqlalchemy import text, inspect from src.database.session import db_manager from src.core.config import get_config def check_column_exists(inspector, table_name: str, column_name: str) -> bool: """Check if a column exists in a table""" columns = inspector.get_columns(table_name) return any(col['name'] == column_name for col in columns) def check_table_exists(inspector, table_name: str) -> bool: """Check if a table exists""" return table_name in inspector.get_table_names() def migrate_add_money_site_url(connection): """Add money_site_url column to projects table""" print("\n[1/2] Adding money_site_url to projects table...") inspector = inspect(connection) if not check_table_exists(inspector, 'projects'): print(" [ERROR] projects table does not exist!") return False if check_column_exists(inspector, 'projects', 'money_site_url'): print(" [INFO] Column money_site_url already exists, skipping") return True try: connection.execute(text(""" ALTER TABLE projects ADD COLUMN money_site_url VARCHAR(500) NULL """)) print(" [OK] Added money_site_url column") connection.execute(text(""" CREATE INDEX idx_projects_money_site_url ON projects(money_site_url) """)) print(" [OK] Created index on money_site_url") connection.commit() return True except Exception as e: print(f" [ERROR] {e}") connection.rollback() return False def migrate_create_article_links_table(connection): """Create article_links table""" print("\n[2/2] Creating article_links table...") inspector = inspect(connection) if check_table_exists(inspector, 'article_links'): print(" [INFO] Table article_links already exists, skipping") return True try: connection.execute(text(""" CREATE TABLE article_links ( id INTEGER PRIMARY KEY AUTOINCREMENT, from_content_id INTEGER NOT NULL, to_content_id INTEGER NULL, to_url TEXT NULL, link_type VARCHAR(20) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (from_content_id) REFERENCES generated_content(id) ON DELETE CASCADE, FOREIGN KEY (to_content_id) REFERENCES generated_content(id) ON DELETE CASCADE, CHECK (to_content_id IS NOT NULL OR to_url IS NOT NULL) ) """)) print(" [OK] Created article_links table") connection.execute(text(""" CREATE INDEX idx_article_links_from ON article_links(from_content_id) """)) print(" [OK] Created index on from_content_id") connection.execute(text(""" CREATE INDEX idx_article_links_to ON article_links(to_content_id) """)) print(" [OK] Created index on to_content_id") connection.execute(text(""" CREATE INDEX idx_article_links_type ON article_links(link_type) """)) print(" [OK] Created index on link_type") connection.commit() return True except Exception as e: print(f" [ERROR] {e}") connection.rollback() return False def verify_migration(connection): """Verify the migration was successful""" print("\n[Verification] Checking migration results...") inspector = inspect(connection) success = True if check_column_exists(inspector, 'projects', 'money_site_url'): print(" [OK] projects.money_site_url exists") else: print(" [ERROR] projects.money_site_url MISSING") success = False if check_table_exists(inspector, 'article_links'): print(" [OK] article_links table exists") columns = inspector.get_columns('article_links') expected_columns = ['id', 'from_content_id', 'to_content_id', 'to_url', 'link_type', 'created_at'] actual_columns = [col['name'] for col in columns] for col in expected_columns: if col in actual_columns: print(f" [OK] article_links.{col} exists") else: print(f" [ERROR] article_links.{col} MISSING") success = False indexes = inspector.get_indexes('article_links') index_names = [idx['name'] for idx in indexes] print(f" [INFO] Indexes: {index_names}") else: print(" [ERROR] article_links table MISSING") success = False return success def main(): """Run the migration""" print("=" * 60) print("Story 3.2 Database Migration") print("=" * 60) try: config = get_config() print(f"\nDatabase: {config.database.url}") except Exception as e: print(f"[ERROR] Error loading configuration: {e}") sys.exit(1) try: db_manager.initialize() engine = db_manager.get_engine() connection = engine.connect() except Exception as e: print(f"[ERROR] Error connecting to database: {e}") sys.exit(1) try: success = True if not migrate_add_money_site_url(connection): success = False if not migrate_create_article_links_table(connection): success = False if success: if verify_migration(connection): print("\n" + "=" * 60) print("[SUCCESS] Migration completed successfully!") print("=" * 60) else: print("\n" + "=" * 60) print("[WARNING] Migration completed with warnings") print("=" * 60) else: print("\n" + "=" * 60) print("[FAILED] Migration failed!") print("=" * 60) sys.exit(1) except Exception as e: print(f"\n[ERROR] Unexpected error during migration: {e}") import traceback traceback.print_exc() sys.exit(1) finally: connection.close() db_manager.close() if __name__ == "__main__": main()