Recovered state: integrated TrendSurferAgent, restored frontend/backend files, and cleaned up recovery scripts

This commit is contained in:
ajaysi
2026-02-08 13:56:57 +05:30
parent 1db10ccd0f
commit e404a86502
333 changed files with 42223 additions and 10875 deletions

View File

@@ -11,7 +11,8 @@ from datetime import datetime
from loguru import logger
from enum import Enum
from services.database import get_db_session
from services.database import get_session_for_user
from models.onboarding import OnboardingSession, APIKey, WebsiteAnalysis, ResearchPreferences, PersonaData
class StepStatus(Enum):
@@ -50,15 +51,16 @@ class OnboardingProgress:
# Initialize database service for persistence
try:
from .database_service import OnboardingDatabaseService
self.db_service = OnboardingDatabaseService()
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
self.integration_service = OnboardingDataIntegrationService()
self.use_database = True
logger.info(f"Database service initialized for user {user_id}")
logger.info(f"Database/Integration service initialized for user {user_id}")
except Exception as e:
logger.error(f"Database service not available: {e}")
self.db_service = None
self.integration_service = None
self.use_database = False
raise Exception(f"Database service required but not available: {e}")
# raise Exception(f"Database service required but not available: {e}") # Don't raise, fallback gracefully if possible
# Load existing progress from database if available
if self.use_database and self.user_id:
@@ -219,23 +221,136 @@ class OnboardingProgress:
self.save_progress()
logger.info("Onboarding completed successfully")
def _save_api_key_to_db(self, db, provider: str, key: str):
"""Save API key to database."""
try:
api_key_record = db.query(APIKey).filter(
APIKey.user_id == self.user_id,
APIKey.provider == provider
).first()
if not api_key_record:
api_key_record = APIKey(
user_id=self.user_id,
provider=provider,
api_key=key,
is_active=True,
created_at=datetime.utcnow()
)
db.add(api_key_record)
else:
api_key_record.api_key = key
api_key_record.updated_at = datetime.utcnow()
db.commit()
except Exception as e:
logger.error(f"Error saving API key to DB: {e}")
# db.rollback() # Handled by outer try/except
def _save_website_analysis_to_db(self, db, analysis_data: Dict[str, Any]):
"""Save website analysis to database."""
try:
# Get session ID
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == self.user_id).first()
if not session:
logger.warning(f"No session found for user {self.user_id} when saving website analysis")
return
analysis = db.query(WebsiteAnalysis).filter(WebsiteAnalysis.session_id == session.id).first()
# Filter valid columns only to avoid errors
valid_cols = WebsiteAnalysis.__table__.columns.keys()
filtered_data = {k: v for k, v in analysis_data.items() if k in valid_cols}
if not analysis:
analysis = WebsiteAnalysis(session_id=session.id, **filtered_data)
db.add(analysis)
else:
for k, v in filtered_data.items():
setattr(analysis, k, v)
analysis.updated_at = datetime.utcnow()
db.commit()
except Exception as e:
logger.error(f"Error saving website analysis to DB: {e}")
def _save_research_preferences_to_db(self, db, prefs_data: Dict[str, Any]):
"""Save research preferences to database."""
try:
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == self.user_id).first()
if not session:
return
prefs = db.query(ResearchPreferences).filter(ResearchPreferences.session_id == session.id).first()
valid_cols = ResearchPreferences.__table__.columns.keys()
filtered_data = {k: v for k, v in prefs_data.items() if k in valid_cols}
if not prefs:
prefs = ResearchPreferences(session_id=session.id, **filtered_data)
db.add(prefs)
else:
for k, v in filtered_data.items():
setattr(prefs, k, v)
prefs.updated_at = datetime.utcnow()
db.commit()
except Exception as e:
logger.error(f"Error saving research prefs to DB: {e}")
def _save_persona_data_to_db(self, db, persona_data: Dict[str, Any]):
"""Save persona data to database."""
try:
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == self.user_id).first()
if not session:
return
persona = db.query(PersonaData).filter(PersonaData.session_id == session.id).first()
valid_cols = PersonaData.__table__.columns.keys()
filtered_data = {k: v for k, v in persona_data.items() if k in valid_cols}
if not persona:
persona = PersonaData(session_id=session.id, **filtered_data)
db.add(persona)
else:
for k, v in filtered_data.items():
setattr(persona, k, v)
persona.updated_at = datetime.utcnow()
db.commit()
except Exception as e:
logger.error(f"Error saving persona data to DB: {e}")
def save_progress(self):
"""Save progress to database."""
if not self.use_database or not self.db_service or not self.user_id:
"""Save progress to database using direct access (no legacy service)."""
if not self.use_database or not self.user_id:
logger.error("Cannot save progress: database service not available or user_id not set")
return
try:
from services.database import SessionLocal
db = SessionLocal()
db = get_session_for_user(self.user_id)
try:
# Update session progress
self.db_service.update_step(self.user_id, self.current_step, db)
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == self.user_id).first()
if not session:
session = OnboardingSession(
user_id=self.user_id,
current_step=self.current_step,
progress=0.0,
started_at=datetime.utcnow()
)
db.add(session)
session.current_step = self.current_step
# Calculate progress percentage
completed_count = sum(1 for s in self.steps if s.status == StepStatus.COMPLETED)
progress_pct = (completed_count / len(self.steps)) * 100
self.db_service.update_progress(self.user_id, progress_pct, db)
session.progress = progress_pct
session.updated_at = datetime.utcnow()
db.commit()
# Save step-specific data to appropriate tables
for step in self.steps:
@@ -245,11 +360,9 @@ class OnboardingProgress:
for provider, key in api_keys.items():
if key:
# Save to database (for user isolation in production)
self.db_service.save_api_key(self.user_id, provider, key, db)
self._save_api_key_to_db(db, provider, key)
# Also save to .env file ONLY in local development
# This allows local developers to have keys in .env for convenience
# In production, keys are fetched from database per user
is_local = os.getenv('DEPLOY_ENV', 'local') == 'local'
if is_local:
try:
@@ -285,13 +398,13 @@ class OnboardingProgress:
if 'status' not in analysis_for_db:
analysis_for_db['status'] = 'completed'
self.db_service.save_website_analysis(self.user_id, analysis_for_db, db)
self._save_website_analysis_to_db(db, analysis_for_db)
logger.info(f"✅ DATABASE: Website analysis saved to database for user {self.user_id}")
elif step.step_number == 3: # Research Preferences
self.db_service.save_research_preferences(self.user_id, step.data, db)
self._save_research_preferences_to_db(db, step.data)
logger.info(f"✅ DATABASE: Research preferences saved to database for user {self.user_id}")
elif step.step_number == 4: # Persona Generation
self.db_service.save_persona_data(self.user_id, step.data, db)
self._save_persona_data_to_db(db, step.data)
logger.info(f"✅ DATABASE: Persona data saved to database for user {self.user_id}")
logger.info(f"Progress saved to database for user {self.user_id}")
@@ -303,46 +416,56 @@ class OnboardingProgress:
raise
def load_progress_from_db(self):
"""Load progress from database."""
if not self.use_database or not self.db_service or not self.user_id:
"""Load progress from database using SSOT Integration Service."""
if not self.use_database or not self.user_id:
logger.warning("Cannot load progress: database service not available or user_id not set")
return
try:
from services.database import SessionLocal
db = SessionLocal()
db = get_session_for_user(self.user_id)
try:
# Get integrated data (SSOT)
integrated_data = self.integration_service.get_integrated_data_sync(self.user_id, db)
# Get session data
session = self.db_service.get_session_by_user(self.user_id, db)
if not session:
session_data = integrated_data.get('onboarding_session', {})
if not session_data:
logger.info(f"No existing onboarding session found for user {self.user_id}, starting fresh")
return
# Restore session data
self.current_step = session.current_step or 1
self.started_at = session.started_at.isoformat() if session.started_at else self.started_at
self.last_updated = session.last_updated.isoformat() if session.last_updated else self.last_updated
self.is_completed = session.is_completed or False
self.completed_at = session.completed_at.isoformat() if session.completed_at else None
self.current_step = session_data.get('current_step', 1)
self.started_at = session_data.get('started_at') or self.started_at
self.last_updated = session_data.get('updated_at') or self.last_updated
# Load step-specific data from database
self._load_step_data_from_db(db)
# Calculate completion status
self.is_completed = (self.current_step >= 6) or (session_data.get('progress', 0) >= 100.0)
if self.is_completed:
self.completed_at = session_data.get('updated_at')
# Load step-specific data from integrated data
self._load_step_data_from_integrated_data(integrated_data)
# Fix any corrupted state
self._fix_corrupted_state()
logger.info(f"Progress loaded from database for user {self.user_id}")
logger.info(f"Progress loaded from database (SSOT) for user {self.user_id}")
finally:
db.close()
except Exception as e:
logger.error(f"Error loading progress from database: {str(e)}")
# Don't fail if database loading fails - start fresh
def _load_step_data_from_db(self, db):
"""Load step-specific data from database tables."""
def _load_step_data_from_integrated_data(self, integrated_data: Dict[str, Any]):
"""Load step-specific data from integrated data dictionary."""
try:
# Load API keys (step 1)
api_keys = self.db_service.get_api_keys(self.user_id, db)
api_keys_data = integrated_data.get('api_keys_data', {})
# api_keys_data structure from integration service might be different, let's check
# It usually returns {'openai_api_key': '...', ...}
# We need to filter for actual keys
api_keys = {k: v for k, v in api_keys_data.items() if v and 'api_key' in k}
if api_keys:
step1 = self.get_step_data(1)
if step1:
@@ -351,7 +474,7 @@ class OnboardingProgress:
step1.completed_at = datetime.now().isoformat()
# Load website analysis (step 2)
website_analysis = self.db_service.get_website_analysis(self.user_id, db)
website_analysis = integrated_data.get('website_analysis', {})
if website_analysis:
step2 = self.get_step_data(2)
if step2:
@@ -360,7 +483,7 @@ class OnboardingProgress:
step2.completed_at = datetime.now().isoformat()
# Load research preferences (step 3)
research_prefs = self.db_service.get_research_preferences(self.user_id, db)
research_prefs = integrated_data.get('research_preferences', {})
if research_prefs:
step3 = self.get_step_data(3)
if step3:
@@ -369,7 +492,7 @@ class OnboardingProgress:
step3.completed_at = datetime.now().isoformat()
# Load persona data (step 4)
persona_data = self.db_service.get_persona_data(self.user_id, db)
persona_data = integrated_data.get('persona_data', {})
if persona_data:
step4 = self.get_step_data(4)
if step4:
@@ -377,9 +500,9 @@ class OnboardingProgress:
step4.data = persona_data
step4.completed_at = datetime.now().isoformat()
logger.info("Step data loaded from database")
logger.info("Step data loaded from integrated data")
except Exception as e:
logger.error(f"Error loading step data from database: {str(e)}")
logger.error(f"Error loading step data from integrated data: {str(e)}")
def _fix_corrupted_state(self):
"""Fix any corrupted progress state."""