From 787b05ee3a085bb35e969bc02aaaaae06453fb0a Mon Sep 17 00:00:00 2001 From: PeninsulaInd Date: Tue, 21 Oct 2025 11:06:08 -0500 Subject: [PATCH] Story 3.2: QA passed: Link tier strategy implemented --- docs/stories/story-3.2-find-tiered-links.md | 2 +- scripts/migrate_story_3.2.py | 215 ++++++++++++++++++++ 2 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 scripts/migrate_story_3.2.py diff --git a/docs/stories/story-3.2-find-tiered-links.md b/docs/stories/story-3.2-find-tiered-links.md index 2d0a8f4..ac3fd0c 100644 --- a/docs/stories/story-3.2-find-tiered-links.md +++ b/docs/stories/story-3.2-find-tiered-links.md @@ -1,7 +1,7 @@ # Story 3.2: Find Tiered Links ## Status -Review +Complete - QA Approved ## Story **As a developer**, I want a module that finds all required tiered links (money site or lower-tier) based on the current batch's tier, so I have them ready for injection. diff --git a/scripts/migrate_story_3.2.py b/scripts/migrate_story_3.2.py new file mode 100644 index 0000000..b4820f8 --- /dev/null +++ b/scripts/migrate_story_3.2.py @@ -0,0 +1,215 @@ +""" +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() +