500 lines
22 KiB
Python
500 lines
22 KiB
Python
"""
|
|
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"
|
|
}
|