WIP: AI Podcast Maker and YouTube Creator Studio integration
This commit is contained in:
149
backend/scripts/create_podcast_tables.py
Normal file
149
backend/scripts/create_podcast_tables.py
Normal file
@@ -0,0 +1,149 @@
|
||||
"""
|
||||
Database Migration Script for Podcast Maker
|
||||
Creates the podcast_projects table for cross-device project persistence.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Add the backend directory to Python path
|
||||
backend_dir = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from sqlalchemy import create_engine, text
|
||||
from loguru import logger
|
||||
import traceback
|
||||
|
||||
# Import models - PodcastProject uses SubscriptionBase
|
||||
from models.subscription_models import Base as SubscriptionBase
|
||||
from models.podcast_models import PodcastProject
|
||||
from services.database import DATABASE_URL
|
||||
|
||||
def create_podcast_tables():
|
||||
"""Create podcast-related tables."""
|
||||
|
||||
try:
|
||||
# Create engine
|
||||
engine = create_engine(DATABASE_URL, echo=False)
|
||||
|
||||
# Create all tables (PodcastProject uses SubscriptionBase, so it will be created)
|
||||
logger.info("Creating podcast maker tables...")
|
||||
SubscriptionBase.metadata.create_all(bind=engine)
|
||||
logger.info("✅ Podcast tables created successfully")
|
||||
|
||||
# Verify table was created
|
||||
display_setup_summary(engine)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error creating podcast tables: {e}")
|
||||
logger.error(traceback.format_exc())
|
||||
raise
|
||||
|
||||
def display_setup_summary(engine):
|
||||
"""Display a summary of the created tables."""
|
||||
|
||||
try:
|
||||
with engine.connect() as conn:
|
||||
logger.info("\n" + "="*60)
|
||||
logger.info("PODCAST MAKER SETUP SUMMARY")
|
||||
logger.info("="*60)
|
||||
|
||||
# Check if table exists
|
||||
check_query = text("""
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name='podcast_projects'
|
||||
""")
|
||||
|
||||
result = conn.execute(check_query)
|
||||
table_exists = result.fetchone()
|
||||
|
||||
if table_exists:
|
||||
logger.info("✅ Table 'podcast_projects' created successfully")
|
||||
|
||||
# Get table schema
|
||||
schema_query = text("""
|
||||
SELECT sql FROM sqlite_master
|
||||
WHERE type='table' AND name='podcast_projects'
|
||||
""")
|
||||
result = conn.execute(schema_query)
|
||||
schema = result.fetchone()
|
||||
if schema:
|
||||
logger.info("\n📋 Table Schema:")
|
||||
logger.info(schema[0])
|
||||
|
||||
# Check indexes
|
||||
indexes_query = text("""
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='index' AND tbl_name='podcast_projects'
|
||||
""")
|
||||
result = conn.execute(indexes_query)
|
||||
indexes = result.fetchall()
|
||||
|
||||
if indexes:
|
||||
logger.info(f"\n📊 Indexes ({len(indexes)}):")
|
||||
for idx in indexes:
|
||||
logger.info(f" • {idx[0]}")
|
||||
|
||||
else:
|
||||
logger.warning("⚠️ Table 'podcast_projects' not found after creation")
|
||||
|
||||
logger.info("\n" + "="*60)
|
||||
logger.info("NEXT STEPS:")
|
||||
logger.info("="*60)
|
||||
logger.info("1. The podcast_projects table is ready for use")
|
||||
logger.info("2. Projects will automatically sync to database after major steps")
|
||||
logger.info("3. Users can resume projects from any device")
|
||||
logger.info("4. Use the 'My Projects' button in the Podcast Dashboard to view saved projects")
|
||||
logger.info("="*60)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error displaying summary: {e}")
|
||||
|
||||
def check_existing_table(engine):
|
||||
"""Check if podcast_projects table already exists."""
|
||||
|
||||
try:
|
||||
with engine.connect() as conn:
|
||||
check_query = text("""
|
||||
SELECT name FROM sqlite_master
|
||||
WHERE type='table' AND name='podcast_projects'
|
||||
""")
|
||||
|
||||
result = conn.execute(check_query)
|
||||
table_exists = result.fetchone()
|
||||
|
||||
if table_exists:
|
||||
logger.info("ℹ️ Table 'podcast_projects' already exists")
|
||||
logger.info(" Running migration will ensure schema is up to date...")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking existing table: {e}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("🚀 Starting podcast maker database migration...")
|
||||
|
||||
try:
|
||||
# Create engine to check existing table
|
||||
engine = create_engine(DATABASE_URL, echo=False)
|
||||
|
||||
# Check existing table
|
||||
table_exists = check_existing_table(engine)
|
||||
|
||||
# Create tables (idempotent - won't recreate if exists)
|
||||
create_podcast_tables()
|
||||
|
||||
logger.info("✅ Migration completed successfully!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Migration cancelled by user")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Migration failed: {e}")
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
141
backend/scripts/migrate_all_tables_to_string.py
Normal file
141
backend/scripts/migrate_all_tables_to_string.py
Normal file
@@ -0,0 +1,141 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from loguru import logger
|
||||
from sqlalchemy import text
|
||||
from services.database import SessionLocal, engine
|
||||
|
||||
# Import models to ensure they are registered and we can recreate them
|
||||
from models.content_planning import (
|
||||
ContentStrategy, ContentGapAnalysis, ContentRecommendation, AIAnalysisResult,
|
||||
Base as ContentPlanningBase
|
||||
)
|
||||
from models.enhanced_calendar_models import (
|
||||
ContentCalendarTemplate, AICalendarRecommendation, ContentPerformanceTracking,
|
||||
ContentTrendAnalysis, ContentOptimization, CalendarGenerationSession,
|
||||
Base as EnhancedCalendarBase
|
||||
)
|
||||
|
||||
def migrate_table(db, table_name, base_metadata):
|
||||
"""Migrate user_id column for a specific table from INTEGER to VARCHAR(255)."""
|
||||
try:
|
||||
logger.info(f"Checking table: {table_name}")
|
||||
|
||||
# Check if table exists
|
||||
check_table_query = f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';"
|
||||
result = db.execute(text(check_table_query))
|
||||
if not result.scalar():
|
||||
logger.warning(f"Table '{table_name}' does not exist. Skipping check, but will try to create it.")
|
||||
# If it doesn't exist, we can just create it with the new schema
|
||||
try:
|
||||
base_metadata.create_all(bind=engine, tables=[base_metadata.tables[table_name]], checkfirst=True)
|
||||
logger.success(f"✅ Created {table_name} with new schema")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create {table_name}: {e}")
|
||||
return True
|
||||
|
||||
# Check current column type
|
||||
check_column_query = f"SELECT type FROM pragma_table_info('{table_name}') WHERE name = 'user_id';"
|
||||
result = db.execute(text(check_column_query))
|
||||
current_type = result.scalar()
|
||||
|
||||
if not current_type:
|
||||
logger.info(f"Table {table_name} does not have user_id column. Skipping.")
|
||||
return True
|
||||
|
||||
if 'varchar' in current_type.lower() or 'text' in current_type.lower():
|
||||
logger.info(f"✅ {table_name}.user_id is already {current_type}. No migration needed.")
|
||||
return True
|
||||
|
||||
logger.info(f"Migrating {table_name}.user_id from {current_type} to VARCHAR...")
|
||||
|
||||
# Backup data
|
||||
backup_table = f"{table_name}_backup"
|
||||
db.execute(text(f"DROP TABLE IF EXISTS {backup_table}")) # Ensure clean state
|
||||
db.execute(text(f"CREATE TABLE {backup_table} AS SELECT * FROM {table_name}"))
|
||||
|
||||
# Drop old table
|
||||
db.execute(text(f"DROP TABLE {table_name}"))
|
||||
|
||||
# Recreate table
|
||||
# We need to find the Table object in metadata
|
||||
table_obj = base_metadata.tables.get(table_name)
|
||||
if table_obj is not None:
|
||||
base_metadata.create_all(bind=engine, tables=[table_obj], checkfirst=False)
|
||||
else:
|
||||
logger.error(f"Could not find Table object for {table_name} in metadata")
|
||||
# Restore backup and abort
|
||||
db.execute(text(f"ALTER TABLE {backup_table} RENAME TO {table_name}"))
|
||||
return False
|
||||
|
||||
# Restore data
|
||||
# We need to list columns to construct INSERT statement, excluding those that might be auto-generated if needed,
|
||||
# but usually for restore we want all.
|
||||
# However, we need to cast user_id to TEXT.
|
||||
|
||||
# Get columns from backup
|
||||
columns_result = db.execute(text(f"PRAGMA table_info({backup_table})"))
|
||||
columns = [row[1] for row in columns_result]
|
||||
|
||||
cols_str = ", ".join(columns)
|
||||
|
||||
# Construct select list with cast
|
||||
select_parts = []
|
||||
for col in columns:
|
||||
if col == 'user_id':
|
||||
select_parts.append("CAST(user_id AS TEXT)")
|
||||
else:
|
||||
select_parts.append(col)
|
||||
select_str = ", ".join(select_parts)
|
||||
|
||||
restore_query = f"INSERT INTO {table_name} ({cols_str}) SELECT {select_str} FROM {backup_table}"
|
||||
db.execute(text(restore_query))
|
||||
|
||||
# Drop backup
|
||||
db.execute(text(f"DROP TABLE {backup_table}"))
|
||||
|
||||
db.commit()
|
||||
logger.success(f"✅ Migrated {table_name} successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Failed to migrate {table_name}: {e}")
|
||||
db.rollback()
|
||||
return False
|
||||
|
||||
def migrate_all():
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Content Planning Tables
|
||||
cp_tables = [
|
||||
"content_strategies",
|
||||
"content_gap_analyses",
|
||||
"content_recommendations",
|
||||
"ai_analysis_results"
|
||||
]
|
||||
|
||||
for table in cp_tables:
|
||||
migrate_table(db, table, ContentPlanningBase.metadata)
|
||||
|
||||
# Enhanced Calendar Tables
|
||||
ec_tables = [
|
||||
"content_calendar_templates",
|
||||
"ai_calendar_recommendations",
|
||||
"content_performance_tracking",
|
||||
"content_trend_analysis",
|
||||
"content_optimizations",
|
||||
"calendar_generation_sessions"
|
||||
]
|
||||
|
||||
for table in ec_tables:
|
||||
migrate_table(db, table, EnhancedCalendarBase.metadata)
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("Starting comprehensive user_id migration...")
|
||||
migrate_all()
|
||||
logger.info("Migration finished.")
|
||||
42
backend/scripts/verify_podcast_table.py
Normal file
42
backend/scripts/verify_podcast_table.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Verify that the podcast_projects table exists and has the correct structure.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
backend_dir = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(backend_dir))
|
||||
|
||||
from sqlalchemy import inspect
|
||||
from services.database import engine
|
||||
|
||||
def verify_table():
|
||||
"""Verify the podcast_projects table exists."""
|
||||
inspector = inspect(engine)
|
||||
tables = inspector.get_table_names()
|
||||
|
||||
if 'podcast_projects' in tables:
|
||||
print("✅ Table 'podcast_projects' exists")
|
||||
|
||||
columns = inspector.get_columns('podcast_projects')
|
||||
print(f"\n📊 Columns ({len(columns)}):")
|
||||
for col in columns:
|
||||
print(f" • {col['name']}: {col['type']}")
|
||||
|
||||
indexes = inspector.get_indexes('podcast_projects')
|
||||
print(f"\n📈 Indexes ({len(indexes)}):")
|
||||
for idx in indexes:
|
||||
print(f" • {idx['name']}: {idx['column_names']}")
|
||||
|
||||
return True
|
||||
else:
|
||||
print("❌ Table 'podcast_projects' not found")
|
||||
print(f"Available tables: {', '.join(tables)}")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = verify_table()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
Reference in New Issue
Block a user