from fastapi import FastAPI, HTTPException from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from pydantic import BaseModel, Field from database.supabase_manager import SupabaseManager from services.scheduler import DailyChallengeScheduler from services.challenge_agent import ChallengeAgentService from services.challenge_service import challenge_service from config.settings import settings from utils.logger import configure_app_logging from utils.error_handler import StandardAPIResponse, create_http_exception # Configure logging logger = configure_app_logging() # Pydantic models for API requests/responses class UserRegistrationRequest(BaseModel): """Request model for user registration.""" email: str = Field(description="User's email address") preferences: str = Field(description="User's challenge preferences in natural language") class UserToggleRequest(BaseModel): """Request model for toggling user active status.""" email: str = Field(description="User's email address") active: bool = Field(description="Whether notifications should be active") class TestNotificationRequest(BaseModel): """Request model for test notifications.""" email: str = Field(description="User's email address") preferences: str = Field(description="User's challenge preferences for testing") app = FastAPI(title="Topcoder Challenge Steward Agent", version="1.0.0") # Mount static files app.mount("/static", StaticFiles(directory="static"), name="static") @app.get("/") def serve_index(): return FileResponse("static/index.html") @app.post("/api/topcoder-dry-run") def topcoder_dry_run(request: dict): """Dry run endpoint showing Topcoder MCP server integration with JSON response""" try: # Get user preferences from request user_preferences = request.get("preferences", "") logger.info(f"Dry run request with preferences: {user_preferences}") # Use the centralized challenge service result = challenge_service.get_challenges_dry_run(user_preferences) # Check if the result indicates success if result.get("success"): # Return the challenges data in the format expected by frontend challenges = result.get("data", {}).get("challenges", []) return {"challenges": challenges} else: # Return error response return { "error": result.get("error", "Unknown error occurred"), "raw_response": result } except Exception as e: logger.error(f"Error in topcoder_dry_run: {e}") return {"error": f"Topcoder dry run failed: {str(e)}"} # === NEW SUPABASE-POWERED ENDPOINTS === # Initialize services supabase_manager = SupabaseManager() challenge_agent_service = ChallengeAgentService() daily_scheduler = DailyChallengeScheduler() @app.post("/api/register-user") async def register_user(request: UserRegistrationRequest): """Register a user with email and preferences for challenge notifications""" try: logger.info(f"Registering user: {request.email}") user_data = await supabase_manager.save_user_preferences( email=request.email, preferences=request.preferences ) # Get the actual user data from database to ensure accurate status actual_user = await supabase_manager.get_user_by_email(request.email) return { "success": True, "message": "User registered successfully", "user": { "email": request.email, "preferences": request.preferences, "active": actual_user.get("active", True) if actual_user else True } } except Exception as e: logger.error(f"Error registering user: {e}") raise HTTPException(status_code=500, detail=f"Failed to register user: {str(e)}") @app.post("/api/toggle-user") async def toggle_user_active(request: UserToggleRequest): """Toggle user's active status for notifications""" try: logger.info(f"Toggling user {request.email} to active={request.active}") success = await supabase_manager.toggle_user_active( email=request.email, active=request.active ) if success: action = "activated" if request.active else "deactivated" return { "success": True, "message": f"User notifications {action} successfully" } else: raise HTTPException(status_code=404, detail="User not found") except HTTPException: # Re-raise HTTPExceptions (like 404) without modification raise except Exception as e: logger.error(f"Error toggling user status: {e}") raise HTTPException(status_code=500, detail=f"Failed to toggle user status: {str(e)}") @app.post("/api/update-preferences") async def update_user_preferences(request: UserRegistrationRequest): """Update existing user's preferences""" try: logger.info(f"Updating preferences for user: {request.email}") # Check if user exists first existing_user = await supabase_manager.get_user_by_email(request.email) if not existing_user: raise HTTPException(status_code=404, detail="User not found") user_data = await supabase_manager.save_user_preferences( email=request.email, preferences=request.preferences ) return { "success": True, "message": "User preferences updated successfully", "user": { "email": request.email, "preferences": request.preferences, "active": existing_user.get("active", True) } } except HTTPException: raise except Exception as e: logger.error(f"Error updating user preferences: {e}") raise HTTPException(status_code=500, detail=f"Failed to update user preferences: {str(e)}") @app.get("/api/user/{email}") async def get_user(email: str): """Get user information by email""" try: user = await supabase_manager.get_user_by_email(email) if user: return { "success": True, "user": user } else: raise HTTPException(status_code=404, detail="User not found") except HTTPException: # Re-raise HTTPExceptions (like 404) without modification raise except Exception as e: logger.error(f"Error getting user: {e}") raise HTTPException(status_code=500, detail=f"Failed to get user: {str(e)}") @app.post("/api/trigger-daily-notifications") async def trigger_daily_notifications(): """Manually trigger daily notifications for testing""" try: logger.info("Manually triggering daily notifications") await daily_scheduler.run_now_for_testing() return { "success": True, "message": "Daily notifications triggered successfully" } except Exception as e: logger.error(f"Error triggering daily notifications: {e}") raise HTTPException(status_code=500, detail=f"Failed to trigger daily notifications: {str(e)}") # Initialize scheduler on startup @app.on_event("startup") async def startup_event(): """Initialize the daily challenge scheduler on app startup""" try: logger.info("Starting up Topcoder Challenge Steward Agent") logger.info("Initializing daily challenge scheduler...") # Start the scheduler daily_scheduler.start_scheduler() logger.info("🚀 Topcoder Challenge Steward Agent is running at http://localhost:8000/") except Exception as e: logger.error(f"Error during startup: {e}") @app.on_event("shutdown") async def shutdown_event(): """Clean up on app shutdown""" try: logger.info("Shutting down Topcoder Challenge Steward Agent") daily_scheduler.stop_scheduler() logger.info("Application shutdown completed successfully") except Exception as e: logger.error(f"Error during shutdown: {e}")