""" Evolution Todo API - FastAPI Backend Task: 1.6 Spec: specs/overview.md """ from dotenv import load_dotenv load_dotenv() # Load .env file first from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from db import create_db_and_tables import os import traceback # Initialize FastAPI app app = FastAPI( title="Evolution Todo API", version="1.0.0", description="RESTful API for Evolution Todo application" ) # CORS Configuration CORS_ORIGINS = os.getenv( "CORS_ORIGINS", "https://frontend-umber-nine-80.vercel.app,https://frontend-qnzzeug89-asma-yaseens-projects.vercel.app,http://localhost:3000" ).split(",") app.add_middleware( CORSMiddleware, allow_origins=CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Global exception handler to ensure CORS headers on errors @app.exception_handler(Exception) async def global_exception_handler(request: Request, exc: Exception): """Catch all exceptions and return with proper CORS headers.""" print(f"❌ Global Exception: {exc}") traceback.print_exc() return JSONResponse( status_code=500, content={"detail": str(exc)}, headers={ "Access-Control-Allow-Origin": request.headers.get("origin", "http://localhost:3000"), "Access-Control-Allow-Credentials": "true", } ) @app.on_event("startup") def on_startup(): """Initialize database tables on startup.""" create_db_and_tables() @app.get("/") def root(): """Root endpoint - API status check.""" return { "message": "Evolution Todo API", "status": "running", "version": "1.0.0" } # Import and include routers from routes.tasks import router as tasks_router from routes.auth import router as auth_router from routes.recurrence import router as recurrence_router from routes.search import router as search_router from routes.bulk import router as bulk_router from routes.history import router as history_router from routes.notifications import router as notifications_router from routes.preferences import router as preferences_router from routes.stats import router as stats_router from routes.export_import import router as export_import_router from routes.chat import router as chat_router # Phase III: AI Chatbot from routes.voice import router as voice_router # Phase III: Voice Input (Whisper) app.include_router(tasks_router) app.include_router(auth_router) app.include_router(recurrence_router) app.include_router(search_router) app.include_router(bulk_router) app.include_router(history_router) app.include_router(notifications_router) app.include_router(preferences_router) app.include_router(stats_router) app.include_router(export_import_router) app.include_router(chat_router) # Phase III: AI Chatbot (T-CHAT-012) app.include_router(voice_router) # Phase III: Voice Input (T-CHAT-015) # Phase III: ChatKit Integration (T-CHATKIT-003) # NOTE: ChatKit SDK not publicly available yet - using REST API in routes/chat.py instead # Uncomment when OpenAI releases chatkit package to PyPI # from fastapi import Depends, HTTPException, status # from fastapi.responses import JSONResponse # from pydantic import BaseModel # from middleware.auth import verify_token # from chatkit_server import chatkit_server, TodoRequestContext # import secrets # class ChatKitSessionRequest(BaseModel): # """Request model for ChatKit session creation.""" # user_id: str # class ChatKitSessionResponse(BaseModel): # """Response model for ChatKit session creation.""" # client_secret: str # server_url: str # @app.post("/api/chatkit/session", response_model=ChatKitSessionResponse) # async def create_chatkit_session( # request: ChatKitSessionRequest, # authenticated_user_id: str = Depends(verify_token) # ): # """ # Create a ChatKit session with client_secret for frontend integration. # # Flow: # 1. Verify JWT token (user is authenticated) # 2. Generate client_secret for ChatKit session # 3. Create RequestContext with user_id # 4. Return client_secret and server_url to frontend # # Frontend Usage: # ```typescript # // 1. Get session from backend # const response = await fetch('/api/chatkit/session', { # method: 'POST', # headers: { # 'Authorization': `Bearer ${jwtToken}`, # 'Content-Type': 'application/json' # }, # body: JSON.stringify({ user_id: userId }) # }); # const { client_secret, server_url } = await response.json(); # // 2. Initialize ChatKit # import { useChatKit } from '@openai/chatkit'; # # const chatkit = useChatKit({ # clientSecret: client_secret, # serverUrl: server_url # }); # ``` # # Args: # request: ChatKitSessionRequest with user_id # authenticated_user_id: User ID from JWT token # # Returns: # ChatKitSessionResponse with client_secret and server_url # # Raises: # HTTPException 403: If user_id doesn't match authenticated user # # Task: T-CHATKIT-003 # Spec: specs/phase-3-chatbot/spec.md (ChatKit Integration) # """ # # Verify user_id matches authenticated user # if request.user_id != authenticated_user_id: # raise HTTPException( # status_code=status.HTTP_403_FORBIDDEN, # detail="Cannot create session for another user" # ) # # # Generate client_secret (secure random token) # client_secret = secrets.token_urlsafe(32) # # # Get server URL from environment or use default # server_url = os.getenv("CHATKIT_SERVER_URL", "http://localhost:8000/api/chatkit") # # # Store client_secret in session storage (if needed for verification) # # For now, we trust JWT authentication on each request # # return ChatKitSessionResponse( # client_secret=client_secret, # server_url=server_url # ) # @app.post("/api/chatkit/respond") # async def chatkit_respond( # request: Request, # authenticated_user_id: str = Depends(verify_token) # ): # """ # ChatKit respond endpoint - handles chat requests from ChatKit frontend. # # This endpoint is called by ChatKit frontend when user sends a message. # It delegates to ChatKitServer.respond() for processing. # # Flow: # 1. Extract user_id from JWT token # 2. Parse request body (thread_id, message) # 3. Create RequestContext with user_id # 4. Call chatkit_server.respond() # 5. Stream response events to client # # Args: # request: FastAPI Request object # authenticated_user_id: User ID from JWT token # # Returns: # Streaming response with ChatKit events # # Task: T-CHATKIT-003 # Spec: specs/phase-3-chatbot/spec.md (ChatKit Integration) # """ # # Parse request body # body = await request.json() # thread_id = body.get("thread_id") # user_message = body.get("message") # # if not user_message: # raise HTTPException( # status_code=status.HTTP_400_BAD_REQUEST, # detail="Message is required" # ) # # # Create RequestContext with authenticated user # context = TodoRequestContext(user_id=authenticated_user_id) # # # Call ChatKit server to process message # events = [] # async for event in chatkit_server.respond(context, thread_id, user_message): # events.append(event) # # return JSONResponse(content={"events": [e.dict() for e in events]}) # @app.get("/api/chatkit/threads") # async def chatkit_list_threads( # authenticated_user_id: str = Depends(verify_token) # ): # """ # List all ChatKit threads (conversations) for authenticated user. # # Args: # authenticated_user_id: User ID from JWT token # # Returns: # List of threads with id, created_at, updated_at # # Task: T-CHATKIT-003 # Spec: specs/phase-3-chatbot/spec.md (ChatKit Integration) # """ # context = TodoRequestContext(user_id=authenticated_user_id) # threads = await chatkit_server.get_threads(context) # return JSONResponse(content={"threads": threads}) # @app.get("/api/chatkit/threads/{thread_id}/messages") # async def chatkit_get_thread_messages( # thread_id: str, # authenticated_user_id: str = Depends(verify_token) # ): # """ # Get all messages in a ChatKit thread. # # Args: # thread_id: Thread ID to get messages from # authenticated_user_id: User ID from JWT token # # Returns: # List of messages with role, content, created_at # # Task: T-CHATKIT-003 # Spec: specs/phase-3-chatbot/spec.md (ChatKit Integration) # """ # context = TodoRequestContext(user_id=authenticated_user_id) # messages = await chatkit_server.get_thread_messages(context, thread_id) # return JSONResponse(content={"messages": messages}) # @app.delete("/api/chatkit/threads/{thread_id}") # async def chatkit_delete_thread( # thread_id: str, # authenticated_user_id: str = Depends(verify_token) # ): # """ # Delete a ChatKit thread and all its messages. # # Args: # thread_id: Thread ID to delete # authenticated_user_id: User ID from JWT token # # Returns: # Success status # # Task: T-CHATKIT-003 # Spec: specs/phase-3-chatbot/spec.md (ChatKit Integration) # """ # context = TodoRequestContext(user_id=authenticated_user_id) # success = await chatkit_server.delete_thread(context, thread_id) # # if not success: # raise HTTPException( # status_code=status.HTTP_404_NOT_FOUND, # detail="Thread not found" # ) # # return JSONResponse(content={"success": True})