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

@@ -5,10 +5,10 @@ This package contains all onboarding-related services and utilities.
All onboarding data is stored in the database with proper user isolation.
Services:
- OnboardingDatabaseService: Core database operations for onboarding data
- OnboardingDataIntegrationService: Canonical SSOT for onboarding data
- OnboardingProgressService: Progress tracking and step management
- OnboardingDataService: Data validation and processing
- OnboardingProgress: Progress tracking with database persistence (from api_key_manager)
- APIKeyManager: API key management
Architecture:
- Database-first: All data stored in PostgreSQL with proper foreign keys
@@ -18,15 +18,11 @@ Architecture:
"""
# Import all public classes for easy access
from .database_service import OnboardingDatabaseService
from .progress_service import OnboardingProgressService
from .data_service import OnboardingDataService
from .api_key_manager import OnboardingProgress, APIKeyManager, get_onboarding_progress, get_user_onboarding_progress, get_onboarding_progress_for_user
__all__ = [
'OnboardingDatabaseService',
'OnboardingProgressService',
'OnboardingDataService',
'OnboardingProgress',
'APIKeyManager',
'get_onboarding_progress',

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."""

View File

@@ -1,291 +0,0 @@
"""
Onboarding Data Service
Extracts real user data from onboarding to personalize AI inputs
"""
from typing import Dict, Any, List, Optional
from sqlalchemy.orm import Session
from loguru import logger
from datetime import datetime
import json
from services.database import get_db_session
from models.onboarding import OnboardingSession, WebsiteAnalysis, ResearchPreferences
class OnboardingDataService:
"""Service to extract and use real onboarding data for AI personalization."""
def __init__(self, db: Optional[Session] = None):
"""Initialize the onboarding data service."""
self.db = db
logger.info("OnboardingDataService initialized")
def get_user_website_analysis(self, user_id: int) -> Optional[Dict[str, Any]]:
"""
Get website analysis data for a specific user.
Args:
user_id: User ID to get data for
Returns:
Website analysis data or None if not found
"""
try:
session = self.db or get_db_session()
# Find onboarding session for user
onboarding_session = session.query(OnboardingSession).filter(
OnboardingSession.user_id == user_id
).first()
if not onboarding_session:
logger.warning(f"No onboarding session found for user {user_id}")
return None
# Get website analysis for this session
website_analysis = session.query(WebsiteAnalysis).filter(
WebsiteAnalysis.session_id == onboarding_session.id
).first()
if not website_analysis:
logger.warning(f"No website analysis found for user {user_id}")
return None
return website_analysis.to_dict()
except Exception as e:
logger.error(f"Error getting website analysis for user {user_id}: {str(e)}")
return None
def get_user_research_preferences(self, user_id: int) -> Optional[Dict[str, Any]]:
"""
Get research preferences for a specific user.
Args:
user_id: User ID to get data for
Returns:
Research preferences data or None if not found
"""
try:
session = self.db or get_db_session()
# Find onboarding session for user
onboarding_session = session.query(OnboardingSession).filter(
OnboardingSession.user_id == user_id
).first()
if not onboarding_session:
logger.warning(f"No onboarding session found for user {user_id}")
return None
# Get research preferences for this session
research_prefs = session.query(ResearchPreferences).filter(
ResearchPreferences.session_id == onboarding_session.id
).first()
if not research_prefs:
logger.warning(f"No research preferences found for user {user_id}")
return None
return research_prefs.to_dict()
except Exception as e:
logger.error(f"Error getting research preferences for user {user_id}: {str(e)}")
return None
def get_personalized_ai_inputs(self, user_id: int) -> Dict[str, Any]:
"""
Get personalized AI inputs based on user's onboarding data.
Args:
user_id: User ID to get personalized data for
Returns:
Personalized data for AI analysis
"""
try:
logger.info(f"Getting personalized AI inputs for user {user_id}")
# Get website analysis
website_analysis = self.get_user_website_analysis(user_id)
research_prefs = self.get_user_research_preferences(user_id)
if not website_analysis:
logger.warning(f"No onboarding data found for user {user_id}, using defaults")
return self._get_default_ai_inputs()
# Extract real data from website analysis
writing_style = website_analysis.get('writing_style', {})
target_audience = website_analysis.get('target_audience', {})
content_type = website_analysis.get('content_type', {})
recommended_settings = website_analysis.get('recommended_settings', {})
# Build personalized AI inputs
personalized_inputs = {
"website_analysis": {
"website_url": website_analysis.get('website_url', ''),
"content_types": self._extract_content_types(content_type),
"writing_style": writing_style.get('tone', 'professional'),
"target_audience": target_audience.get('demographics', ['professionals']),
"industry_focus": target_audience.get('industry_focus', 'general'),
"expertise_level": target_audience.get('expertise_level', 'intermediate')
},
"competitor_analysis": {
"top_performers": self._generate_competitor_suggestions(target_audience),
"industry": target_audience.get('industry_focus', 'general'),
"target_demographics": target_audience.get('demographics', [])
},
"gap_analysis": {
"content_gaps": self._identify_content_gaps(content_type, writing_style),
"target_keywords": self._generate_target_keywords(target_audience),
"content_opportunities": self._identify_opportunities(content_type)
},
"keyword_analysis": {
"high_value_keywords": self._generate_high_value_keywords(target_audience),
"content_topics": self._generate_content_topics(content_type),
"search_intent": self._analyze_search_intent(target_audience)
}
}
# Add research preferences if available
if research_prefs:
personalized_inputs["research_preferences"] = {
"research_depth": research_prefs.get('research_depth', 'Standard'),
"content_types": research_prefs.get('content_types', []),
"auto_research": research_prefs.get('auto_research', True),
"factual_content": research_prefs.get('factual_content', True)
}
logger.info(f"✅ Generated personalized AI inputs for user {user_id}")
return personalized_inputs
except Exception as e:
logger.error(f"Error generating personalized AI inputs for user {user_id}: {str(e)}")
return self._get_default_ai_inputs()
def _extract_content_types(self, content_type: Dict[str, Any]) -> List[str]:
"""Extract content types from content type analysis."""
types = []
if content_type.get('primary_type'):
types.append(content_type['primary_type'])
if content_type.get('secondary_types'):
types.extend(content_type['secondary_types'])
return types if types else ['blog', 'article']
def _generate_competitor_suggestions(self, target_audience: Dict[str, Any]) -> List[str]:
"""Generate competitor suggestions based on target audience."""
industry = target_audience.get('industry_focus', 'general')
demographics = target_audience.get('demographics', ['professionals'])
# Generate industry-specific competitors
if industry == 'technology':
return ['techcrunch.com', 'wired.com', 'theverge.com']
elif industry == 'marketing':
return ['hubspot.com', 'marketingland.com', 'moz.com']
else:
return ['competitor1.com', 'competitor2.com', 'competitor3.com']
def _identify_content_gaps(self, content_type: Dict[str, Any], writing_style: Dict[str, Any]) -> List[str]:
"""Identify content gaps based on current content type and style."""
gaps = []
primary_type = content_type.get('primary_type', 'blog')
if primary_type == 'blog':
gaps.extend(['Video tutorials', 'Case studies', 'Infographics'])
elif primary_type == 'video':
gaps.extend(['Blog posts', 'Whitepapers', 'Webinars'])
# Add style-based gaps
tone = writing_style.get('tone', 'professional')
if tone == 'professional':
gaps.append('Personal stories')
elif tone == 'casual':
gaps.append('Expert interviews')
return gaps
def _generate_target_keywords(self, target_audience: Dict[str, Any]) -> List[str]:
"""Generate target keywords based on audience analysis."""
industry = target_audience.get('industry_focus', 'general')
expertise = target_audience.get('expertise_level', 'intermediate')
if industry == 'technology':
return ['AI tools', 'Digital transformation', 'Tech trends']
elif industry == 'marketing':
return ['Content marketing', 'SEO strategies', 'Social media']
else:
return ['Industry insights', 'Best practices', 'Expert tips']
def _identify_opportunities(self, content_type: Dict[str, Any]) -> List[str]:
"""Identify content opportunities based on current content type."""
opportunities = []
purpose = content_type.get('purpose', 'informational')
if purpose == 'informational':
opportunities.extend(['How-to guides', 'Tutorials', 'Educational content'])
elif purpose == 'promotional':
opportunities.extend(['Case studies', 'Testimonials', 'Success stories'])
return opportunities
def _generate_high_value_keywords(self, target_audience: Dict[str, Any]) -> List[str]:
"""Generate high-value keywords based on audience analysis."""
industry = target_audience.get('industry_focus', 'general')
if industry == 'technology':
return ['AI marketing', 'Content automation', 'Digital strategy']
elif industry == 'marketing':
return ['Content marketing', 'SEO optimization', 'Social media strategy']
else:
return ['Industry trends', 'Best practices', 'Expert insights']
def _generate_content_topics(self, content_type: Dict[str, Any]) -> List[str]:
"""Generate content topics based on content type analysis."""
topics = []
primary_type = content_type.get('primary_type', 'blog')
if primary_type == 'blog':
topics.extend(['Industry trends', 'How-to guides', 'Expert insights'])
elif primary_type == 'video':
topics.extend(['Tutorials', 'Product demos', 'Expert interviews'])
return topics
def _analyze_search_intent(self, target_audience: Dict[str, Any]) -> Dict[str, Any]:
"""Analyze search intent based on target audience."""
expertise = target_audience.get('expertise_level', 'intermediate')
if expertise == 'beginner':
return {'intent': 'educational', 'focus': 'basic concepts'}
elif expertise == 'intermediate':
return {'intent': 'practical', 'focus': 'implementation'}
else:
return {'intent': 'advanced', 'focus': 'strategic insights'}
def _get_default_ai_inputs(self) -> Dict[str, Any]:
"""Get default AI inputs when no onboarding data is available."""
return {
"website_analysis": {
"content_types": ["blog", "video", "social"],
"writing_style": "professional",
"target_audience": ["professionals"],
"industry_focus": "general",
"expertise_level": "intermediate"
},
"competitor_analysis": {
"top_performers": ["competitor1.com", "competitor2.com"],
"industry": "general",
"target_demographics": ["professionals"]
},
"gap_analysis": {
"content_gaps": ["AI content", "Video tutorials", "Case studies"],
"target_keywords": ["Industry insights", "Best practices"],
"content_opportunities": ["How-to guides", "Tutorials"]
},
"keyword_analysis": {
"high_value_keywords": ["AI marketing", "Content automation", "Digital strategy"],
"content_topics": ["Industry trends", "Expert insights"],
"search_intent": {"intent": "practical", "focus": "implementation"}
}
}

View File

@@ -1,6 +1,7 @@
"""
Database-only Onboarding Progress Service
Replaces file-based progress tracking with database-only implementation.
Refactored to use direct DB access and eliminate legacy OnboardingDatabaseService dependency.
"""
from typing import Dict, Any, List, Optional
@@ -9,23 +10,47 @@ from loguru import logger
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
from services.database import SessionLocal
from .database_service import OnboardingDatabaseService
from services.database import SessionLocal, get_session_for_user
from models.onboarding import OnboardingSession
class OnboardingProgressService:
"""Database-only onboarding progress management."""
def __init__(self):
self.db_service = OnboardingDatabaseService()
from api.content_planning.services.content_strategy.onboarding import OnboardingDataIntegrationService
self.integration_service = OnboardingDataIntegrationService()
def get_onboarding_status(self, user_id: str) -> Dict[str, Any]:
"""Get current onboarding status from database only."""
def get_completion_data(self, user_id: str) -> Dict[str, Any]:
"""Get full completion data for all steps using SSOT."""
try:
db = SessionLocal()
db = get_session_for_user(user_id)
try:
# Get session data
session = self.db_service.get_session_by_user(user_id, db)
# Use SSOT integration service to get all data
integrated_data = self.integration_service.get_integrated_data_sync(user_id, db)
# Map to format expected by StepManagementService
return {
"api_keys": integrated_data.get('api_keys_data', {}),
"website_analysis": integrated_data.get('website_analysis', {}),
"research_preferences": integrated_data.get('research_preferences', {}),
"persona_data": integrated_data.get('persona_data', {}),
"onboarding_session": integrated_data.get('onboarding_session', {})
}
finally:
db.close()
except Exception as e:
logger.error(f"Error getting completion data: {e}")
return {}
def get_onboarding_status(self, user_id: str) -> Dict[str, Any]:
"""Get current onboarding status from database."""
try:
db = get_session_for_user(user_id)
try:
# Direct DB access to SSOT session
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == user_id).first()
if not session:
return {
"is_completed": False,
@@ -38,7 +63,6 @@ class OnboardingProgressService:
# Check if onboarding is complete
# Consider complete if either the final step is reached OR progress hit 100%
# This guards against partial writes where one field persisted but the other didn't.
is_completed = (session.current_step >= 6) or (session.progress >= 100.0)
return {
@@ -67,12 +91,26 @@ class OnboardingProgressService:
def update_step(self, user_id: str, step_number: int) -> bool:
"""Update current step in database."""
try:
db = SessionLocal()
db = get_session_for_user(user_id)
try:
success = self.db_service.update_step(user_id, step_number, db)
if success:
logger.info(f"Updated user {user_id} to step {step_number}")
return success
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == user_id).first()
if not session:
# Create session if not exists
session = OnboardingSession(
user_id=user_id,
current_step=step_number,
progress=0.0,
started_at=datetime.utcnow(),
updated_at=datetime.utcnow()
)
db.add(session)
else:
session.current_step = step_number
session.updated_at = datetime.utcnow()
db.commit()
logger.info(f"Updated user {user_id} to step {step_number}")
return True
finally:
db.close()
except Exception as e:
@@ -82,12 +120,16 @@ class OnboardingProgressService:
def update_progress(self, user_id: str, progress_percentage: float) -> bool:
"""Update progress percentage in database."""
try:
db = SessionLocal()
db = get_session_for_user(user_id)
try:
success = self.db_service.update_progress(user_id, progress_percentage, db)
if success:
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == user_id).first()
if session:
session.progress = progress_percentage
session.updated_at = datetime.utcnow()
db.commit()
logger.info(f"Updated user {user_id} progress to {progress_percentage}%")
return success
return True
return False
finally:
db.close()
except Exception as e:
@@ -97,67 +139,18 @@ class OnboardingProgressService:
def complete_onboarding(self, user_id: str) -> bool:
"""Mark onboarding as complete in database."""
try:
db = SessionLocal()
db = get_session_for_user(user_id)
try:
success = self.db_service.mark_onboarding_complete(user_id, db)
if success:
logger.info(f"Marked onboarding complete for user {user_id}")
return success
session = db.query(OnboardingSession).filter(OnboardingSession.user_id == user_id).first()
if session:
session.progress = 100.0
session.current_step = 6 # Assuming 6 is complete
session.updated_at = datetime.utcnow()
db.commit()
return True
return False
finally:
db.close()
except Exception as e:
logger.error(f"Error completing onboarding: {e}")
return False
def reset_onboarding(self, user_id: str) -> bool:
"""Reset onboarding progress in database."""
try:
db = SessionLocal()
try:
# Reset to step 1, 0% progress
success = self.db_service.update_step(user_id, 1, db)
if success:
self.db_service.update_progress(user_id, 0.0, db)
logger.info(f"Reset onboarding for user {user_id}")
return success
finally:
db.close()
except Exception as e:
logger.error(f"Error resetting onboarding: {e}")
return False
def get_completion_data(self, user_id: str) -> Dict[str, Any]:
"""Get completion data for validation."""
try:
db = SessionLocal()
try:
# Get all relevant data for completion validation
session = self.db_service.get_session_by_user(user_id, db)
api_keys = self.db_service.get_api_keys(user_id, db)
website_analysis = self.db_service.get_website_analysis(user_id, db)
research_preferences = self.db_service.get_research_preferences(user_id, db)
persona_data = self.db_service.get_persona_data(user_id, db)
return {
"session": session,
"api_keys": api_keys,
"website_analysis": website_analysis,
"research_preferences": research_preferences,
"persona_data": persona_data
}
finally:
db.close()
except Exception as e:
logger.error(f"Error getting completion data: {e}")
return {}
# Global instance
_onboarding_progress_service = None
def get_onboarding_progress_service() -> OnboardingProgressService:
"""Get the global onboarding progress service instance."""
global _onboarding_progress_service
if _onboarding_progress_service is None:
_onboarding_progress_service = OnboardingProgressService()
return _onboarding_progress_service