Story 3.2: QA passed: Link tier strategy implemented
parent
87bf317207
commit
787b05ee3a
|
|
@ -1,7 +1,7 @@
|
||||||
# Story 3.2: Find Tiered Links
|
# Story 3.2: Find Tiered Links
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
Review
|
Complete - QA Approved
|
||||||
|
|
||||||
## Story
|
## 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.
|
**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.
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
||||||
Loading…
Reference in New Issue