""" Onboarding Manager Module Handles all onboarding-related endpoints and functionality. """ from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks from fastapi.responses import FileResponse from typing import Dict, Any, Optional from loguru import logger # Import onboarding functions from api.onboarding import ( health_check, initialize_onboarding, get_onboarding_status, get_onboarding_progress_full, get_step_data, complete_step, skip_step, validate_step_access, get_api_keys, get_api_keys_for_onboarding, save_api_key, validate_api_keys, start_onboarding, complete_onboarding, reset_onboarding, get_resume_info, get_onboarding_config, get_provider_setup_info, get_all_providers_info, validate_provider_key, get_enhanced_validation_status, get_onboarding_summary, get_website_analysis_data, get_research_preferences_data, save_business_info, get_business_info, get_business_info_by_user, update_business_info, generate_writing_personas, generate_writing_personas_async, get_persona_task_status, assess_persona_quality, regenerate_persona, get_persona_generation_options, get_latest_persona, save_persona_update, StepCompletionRequest, APIKeyRequest ) from middleware.auth_middleware import get_current_user class OnboardingManager: """Manages all onboarding-related endpoints and functionality.""" def __init__(self, app: FastAPI): self.app = app self.setup_onboarding_endpoints() def setup_onboarding_endpoints(self): """Set up all onboarding-related endpoints.""" # Onboarding initialization - BATCH ENDPOINT (reduces 4 API calls to 1) @self.app.get("/api/onboarding/init") async def onboarding_init(current_user: dict = Depends(get_current_user)): """ Batch initialization endpoint - combines user info, status, and progress. This eliminates 3-4 separate API calls on initial load, reducing latency by 60-75%. """ try: return await initialize_onboarding(current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in onboarding_init: {e}") raise HTTPException(status_code=500, detail=str(e)) # Onboarding status endpoints @self.app.get("/api/onboarding/status") async def onboarding_status(current_user: dict = Depends(get_current_user)): """Get the current onboarding status.""" try: return await get_onboarding_status(current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in onboarding_status: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/progress") async def onboarding_progress(current_user: dict = Depends(get_current_user)): """Get the full onboarding progress data.""" try: return await get_onboarding_progress_full(current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in onboarding_progress: {e}") raise HTTPException(status_code=500, detail=str(e)) # Step management endpoints @self.app.get("/api/onboarding/step/{step_number}") async def step_data(step_number: int, current_user: dict = Depends(get_current_user)): """Get data for a specific step.""" try: return await get_step_data(step_number, current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in step_data: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/step/{step_number}/complete") async def step_complete(step_number: int, request: StepCompletionRequest, current_user: dict = Depends(get_current_user)): """Mark a step as completed.""" try: return await complete_step(step_number, request, current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in step_complete: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/step/{step_number}/skip") async def step_skip(step_number: int, current_user: dict = Depends(get_current_user)): """Skip a step (for optional steps).""" try: return await skip_step(step_number, current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in step_skip: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/step/{step_number}/validate") async def step_validate(step_number: int, current_user: dict = Depends(get_current_user)): """Validate if user can access a specific step.""" try: return await validate_step_access(step_number, current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in step_validate: {e}") raise HTTPException(status_code=500, detail=str(e)) # API key management endpoints @self.app.get("/api/onboarding/api-keys") async def api_keys(): """Get all configured API keys (masked).""" try: return await get_api_keys() except Exception as e: logger.error(f"Error in api_keys: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/api-keys/onboarding") async def api_keys_for_onboarding(current_user: dict = Depends(get_current_user)): """Get all configured API keys for onboarding (unmasked).""" try: return await get_api_keys_for_onboarding(current_user) except Exception as e: logger.error(f"Error in api_keys_for_onboarding: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/api-keys") async def api_key_save(request: APIKeyRequest, current_user: dict = Depends(get_current_user)): """Save an API key for a provider.""" try: return await save_api_key(request, current_user) except Exception as e: logger.error(f"Error in api_key_save: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/api-keys/validate") async def api_key_validate(): """Get API key validation status and configuration.""" try: import os from dotenv import load_dotenv # Load environment variables backend_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) env_path = os.path.join(backend_dir, ".env") load_dotenv(env_path, override=True) # Check for required API keys (backend only) api_keys = {} required_keys = { 'GEMINI_API_KEY': 'gemini', 'EXA_API_KEY': 'exa' # Note: CopilotKit is frontend-only, validated separately } missing_keys = [] configured_providers = [] for env_var, provider in required_keys.items(): key_value = os.getenv(env_var) if key_value and key_value.strip(): api_keys[provider] = key_value.strip() configured_providers.append(provider) else: missing_keys.append(provider) # Determine if all required keys are present required_providers = ['gemini', 'exa'] # Backend keys only all_required_present = all(provider in configured_providers for provider in required_providers) result = { "api_keys": api_keys, "validation_results": { "gemini": {"valid": 'gemini' in configured_providers, "status": "configured" if 'gemini' in configured_providers else "missing"}, "exa": {"valid": 'exa' in configured_providers, "status": "configured" if 'exa' in configured_providers else "missing"} }, "all_valid": all_required_present, "total_providers": len(configured_providers), "configured_providers": configured_providers, "missing_keys": missing_keys } logger.info(f"API Key Validation Result: {result}") return result except Exception as e: logger.error(f"Error in api_key_validate: {e}") raise HTTPException(status_code=500, detail=str(e)) # Onboarding control endpoints @self.app.post("/api/onboarding/start") async def onboarding_start(current_user: dict = Depends(get_current_user)): """Start a new onboarding session.""" try: return await start_onboarding(current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in onboarding_start: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/complete") async def onboarding_complete(current_user: dict = Depends(get_current_user)): """Complete the onboarding process.""" try: return await complete_onboarding(current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in onboarding_complete: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/reset") async def onboarding_reset(): """Reset the onboarding progress.""" try: return await reset_onboarding() except Exception as e: logger.error(f"Error in onboarding_reset: {e}") raise HTTPException(status_code=500, detail=str(e)) # Resume functionality @self.app.get("/api/onboarding/resume") async def onboarding_resume(): """Get information for resuming onboarding.""" try: return await get_resume_info() except Exception as e: logger.error(f"Error in onboarding_resume: {e}") raise HTTPException(status_code=500, detail=str(e)) # Configuration endpoints @self.app.get("/api/onboarding/config") async def onboarding_config(): """Get onboarding configuration and requirements.""" try: return get_onboarding_config() except Exception as e: logger.error(f"Error in onboarding_config: {e}") raise HTTPException(status_code=500, detail=str(e)) # Enhanced provider endpoints @self.app.get("/api/onboarding/providers/{provider}/setup") async def provider_setup_info(provider: str): """Get setup information for a specific provider.""" try: return await get_provider_setup_info(provider) except Exception as e: logger.error(f"Error in provider_setup_info: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/providers") async def all_providers_info(): """Get setup information for all providers.""" try: return await get_all_providers_info() except Exception as e: logger.error(f"Error in all_providers_info: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/providers/{provider}/validate") async def validate_provider_key_endpoint(provider: str, request: APIKeyRequest): """Validate a specific provider's API key.""" try: return await validate_provider_key(provider, request) except Exception as e: logger.error(f"Error in validate_provider_key: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/validation/enhanced") async def enhanced_validation_status(): """Get enhanced validation status for all configured services.""" try: return await get_enhanced_validation_status() except Exception as e: logger.error(f"Error in enhanced_validation_status: {e}") raise HTTPException(status_code=500, detail=str(e)) # New endpoints for FinalStep data loading @self.app.get("/api/onboarding/summary") async def onboarding_summary(current_user: dict = Depends(get_current_user)): """Get comprehensive onboarding summary for FinalStep.""" try: return await get_onboarding_summary(current_user) except Exception as e: logger.error(f"Error in onboarding_summary: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/website-analysis") async def website_analysis_data(current_user: dict = Depends(get_current_user)): """Get website analysis data for FinalStep.""" try: return await get_website_analysis_data(current_user) except Exception as e: logger.error(f"Error in website_analysis_data: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/research-preferences") async def research_preferences_data(current_user: dict = Depends(get_current_user)): """Get research preferences data for FinalStep.""" try: return await get_research_preferences_data(current_user) except Exception as e: logger.error(f"Error in research_preferences_data: {e}") raise HTTPException(status_code=500, detail=str(e)) # Business Information endpoints @self.app.post("/api/onboarding/business-info") async def business_info_save(request: dict): """Save business information for users without websites.""" try: from models.business_info_request import BusinessInfoRequest return await save_business_info(request) except Exception as e: logger.error(f"Error in business_info_save: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/business-info/{business_info_id}") async def business_info_get(business_info_id: int): """Get business information by ID.""" try: return await get_business_info(business_info_id) except Exception as e: logger.error(f"Error in business_info_get: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/business-info/user/{user_id}") async def business_info_get_by_user(user_id: int): """Get business information by user ID.""" try: return await get_business_info_by_user(user_id) except Exception as e: logger.error(f"Error in business_info_get_by_user: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.put("/api/onboarding/business-info/{business_info_id}") async def business_info_update(business_info_id: int, request: dict): """Update business information.""" try: from models.business_info_request import BusinessInfoRequest return await update_business_info(business_info_id, request) except Exception as e: logger.error(f"Error in business_info_update: {e}") raise HTTPException(status_code=500, detail=str(e)) # Persona generation endpoints @self.app.post("/api/onboarding/step4/generate-personas") async def generate_personas(request: dict, current_user: dict = Depends(get_current_user)): """Generate AI writing personas for Step 4.""" try: return await generate_writing_personas(request, current_user) except Exception as e: logger.error(f"Error in generate_personas: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/step4/generate-personas-async") async def generate_personas_async(request: dict, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)): """Start async persona generation task.""" try: return await generate_writing_personas_async(request, current_user, background_tasks) except Exception as e: logger.error(f"Error in generate_personas_async: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/step4/persona-task/{task_id}") async def get_persona_task(task_id: str): """Get persona generation task status.""" try: return await get_persona_task_status(task_id) except Exception as e: logger.error(f"Error in get_persona_task: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/step4/persona-latest") async def persona_latest(current_user: dict = Depends(get_current_user)): """Get latest cached persona for current user.""" try: return await get_latest_persona(current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in persona_latest: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/step4/persona-save") async def persona_save(request: dict, current_user: dict = Depends(get_current_user)): """Save edited persona back to cache.""" try: return await save_persona_update(request, current_user) except HTTPException as he: raise he except Exception as e: logger.error(f"Error in persona_save: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/step4/assess-persona-quality") async def assess_persona_quality_endpoint(request: dict, current_user: dict = Depends(get_current_user)): """Assess the quality of generated personas.""" try: return await assess_persona_quality(request, current_user) except Exception as e: logger.error(f"Error in assess_persona_quality: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.post("/api/onboarding/step4/regenerate-persona") async def regenerate_persona_endpoint(request: dict, current_user: dict = Depends(get_current_user)): """Regenerate a specific persona with improvements.""" try: return await regenerate_persona(request, current_user) except Exception as e: logger.error(f"Error in regenerate_persona: {e}") raise HTTPException(status_code=500, detail=str(e)) @self.app.get("/api/onboarding/step4/persona-options") async def get_persona_options(current_user: dict = Depends(get_current_user)): """Get persona generation options and configurations.""" try: return await get_persona_generation_options(current_user) except Exception as e: logger.error(f"Error in get_persona_options: {e}") raise HTTPException(status_code=500, detail=str(e)) def get_onboarding_status(self) -> Dict[str, Any]: """Get the status of onboarding endpoints.""" return { "onboarding_endpoints": [ "/api/onboarding/init", "/api/onboarding/status", "/api/onboarding/progress", "/api/onboarding/step/{step_number}", "/api/onboarding/step/{step_number}/complete", "/api/onboarding/step/{step_number}/skip", "/api/onboarding/step/{step_number}/validate", "/api/onboarding/api-keys", "/api/onboarding/api-keys/onboarding", "/api/onboarding/start", "/api/onboarding/complete", "/api/onboarding/reset", "/api/onboarding/resume", "/api/onboarding/config", "/api/onboarding/providers/{provider}/setup", "/api/onboarding/providers", "/api/onboarding/providers/{provider}/validate", "/api/onboarding/validation/enhanced", "/api/onboarding/summary", "/api/onboarding/website-analysis", "/api/onboarding/research-preferences", "/api/onboarding/business-info", "/api/onboarding/step4/generate-personas", "/api/onboarding/step4/generate-personas-async", "/api/onboarding/step4/persona-task/{task_id}", "/api/onboarding/step4/persona-latest", "/api/onboarding/step4/persona-save", "/api/onboarding/step4/assess-persona-quality", "/api/onboarding/step4/regenerate-persona", "/api/onboarding/step4/persona-options" ], "total_endpoints": 30, "status": "active" }