Download app/api/tasks.py from umar-30/AI-todo: direct link, hf CLI and curl.
- Browser
- Download file 8.65 kB
-
https://huggingface.co/spaces/umar-30/AI-todo/resolve/main/app/api/tasks.py
- Command line
-
hf download hf://spaces/umar-30/AI-todo/app/api/tasks.py
-
curl -L -o tasks.py https://huggingface.co/spaces/umar-30/AI-todo/resolve/main/app/api/tasks.py
8.65 kB
| """ | |
| Tasks API endpoint for Todo AI Chatbot. | |
| Implements GET /api/tasks (authenticated) for fetching user tasks. | |
| Includes SSE endpoint for real-time task updates. | |
| """ | |
| import logging | |
| from fastapi import APIRouter, HTTPException, Depends, Request | |
| from fastapi.responses import StreamingResponse | |
| from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials | |
| from pydantic import BaseModel | |
| from typing import Optional | |
| import jwt | |
| import os | |
| from ..mcp.tools import list_tasks, complete_task, uncomplete_task, delete_task, ValidationError, TaskNotFoundError | |
| from ..events import task_event_manager | |
| logger = logging.getLogger(__name__) | |
| router = APIRouter(prefix="/api", tags=["Tasks"]) | |
| # Security scheme for bearer token | |
| security = HTTPBearer() | |
| def verify_token(token: str) -> dict: | |
| """Verify JWT token and return user data.""" | |
| try: | |
| payload = jwt.decode(token, os.getenv("JWT_SECRET", "todo-ai-chatbot-secret"), algorithms=["HS256"]) | |
| return payload | |
| except jwt.ExpiredSignatureError: | |
| raise HTTPException( | |
| status_code=401, | |
| detail="Token has expired", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| except jwt.JWTError: | |
| raise HTTPException( | |
| status_code=401, | |
| detail="Could not validate credentials", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| class TaskResponse(BaseModel): | |
| """Task data model for API responses.""" | |
| id: str | |
| user_id: str | |
| title: str | |
| description: Optional[str] | |
| completed: bool | |
| due_date: Optional[str] | |
| priority: Optional[str] | |
| category: Optional[str] | |
| created_at: str | |
| updated_at: str | |
| class TaskListResponse(BaseModel): | |
| """Response model for task list endpoint.""" | |
| tasks: list[TaskResponse] | |
| async def get_tasks_by_user_id(user_id: str) -> TaskListResponse: | |
| """ | |
| Fetch all tasks for a user by user_id. | |
| """ | |
| logger.info(f"Fetching tasks for user {user_id}") | |
| try: | |
| tasks = list_tasks(user_id) | |
| logger.info(f"Retrieved {len(tasks)} tasks for user {user_id}") | |
| return TaskListResponse(tasks=tasks) | |
| except ValidationError as e: | |
| logger.warning(f"Validation error: {e}") | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| except Exception as e: | |
| logger.error(f"Error fetching tasks: {e}") | |
| raise HTTPException(status_code=500, detail="Failed to fetch tasks") | |
| async def get_tasks( | |
| credentials: HTTPAuthorizationCredentials = Depends(security), | |
| ) -> TaskListResponse: | |
| """ | |
| Fetch all tasks for the authenticated user. | |
| Uses the MCP list_tasks tool internally for database access. | |
| Requires authentication via Bearer token. | |
| """ | |
| # Verify the token and extract user data | |
| user_data = verify_token(credentials.credentials) | |
| user_id = user_data.get("sub") | |
| logger.info(f"Fetching tasks for authenticated user {user_id}") | |
| try: | |
| tasks = list_tasks(user_id) | |
| logger.info(f"Retrieved {len(tasks)} tasks for user {user_id}") | |
| return TaskListResponse(tasks=tasks) | |
| except ValidationError as e: | |
| logger.warning(f"Validation error: {e}") | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| except Exception as e: | |
| logger.error(f"Error fetching tasks: {e}") | |
| raise HTTPException(status_code=500, detail="Failed to fetch tasks") | |
| class DeleteResponse(BaseModel): | |
| """Response model for delete operation.""" | |
| deleted: bool | |
| task_id: str | |
| async def mark_task_complete( | |
| task_id: str, | |
| credentials: HTTPAuthorizationCredentials = Depends(security), | |
| ) -> TaskResponse: | |
| """Mark a task as completed.""" | |
| user_data = verify_token(credentials.credentials) | |
| user_id = user_data.get("sub") | |
| try: | |
| task = complete_task(user_id, task_id) | |
| return TaskResponse(**task) | |
| except TaskNotFoundError: | |
| raise HTTPException(status_code=404, detail="Task not found") | |
| except ValidationError as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| async def mark_task_uncomplete( | |
| task_id: str, | |
| credentials: HTTPAuthorizationCredentials = Depends(security), | |
| ) -> TaskResponse: | |
| """Mark a task as not completed (pending).""" | |
| user_data = verify_token(credentials.credentials) | |
| user_id = user_data.get("sub") | |
| try: | |
| task = uncomplete_task(user_id, task_id) | |
| return TaskResponse(**task) | |
| except TaskNotFoundError: | |
| raise HTTPException(status_code=404, detail="Task not found") | |
| except ValidationError as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| async def delete_task_endpoint( | |
| task_id: str, | |
| credentials: HTTPAuthorizationCredentials = Depends(security), | |
| ) -> DeleteResponse: | |
| """Permanently delete a task.""" | |
| user_data = verify_token(credentials.credentials) | |
| user_id = user_data.get("sub") | |
| try: | |
| result = delete_task(user_id, task_id) | |
| return DeleteResponse(**result) | |
| except TaskNotFoundError: | |
| raise HTTPException(status_code=404, detail="Task not found") | |
| except ValidationError as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| async def stream_task_updates( | |
| request: Request, | |
| token: Optional[str] = None, | |
| ): | |
| """ | |
| SSE endpoint for real-time task updates. | |
| Clients receive events when tasks are created, updated, completed, or deleted. | |
| Events include: task.created, task.updated, task.completed, task.uncompleted, task.deleted | |
| Note: Token is passed via query param since EventSource doesn't support headers. | |
| """ | |
| if not token: | |
| raise HTTPException( | |
| status_code=401, | |
| detail="Token required", | |
| headers={"WWW-Authenticate": "Bearer"}, | |
| ) | |
| user_data = verify_token(token) | |
| user_id = user_data.get("sub") | |
| logger.info(f"Starting SSE stream for user {user_id}") | |
| async def event_generator(): | |
| try: | |
| async for event in task_event_manager.subscribe(user_id): | |
| # Check if client disconnected | |
| if await request.is_disconnected(): | |
| logger.info(f"Client disconnected, closing SSE stream for user {user_id}") | |
| break | |
| yield event | |
| except Exception as e: | |
| logger.error(f"SSE stream error for user {user_id}: {e}") | |
| finally: | |
| logger.info(f"SSE stream ended for user {user_id}") | |
| return StreamingResponse( | |
| event_generator(), | |
| media_type="text/event-stream", | |
| headers={ | |
| "Cache-Control": "no-cache", | |
| "Connection": "keep-alive", | |
| "X-Accel-Buffering": "no", # Disable nginx buffering | |
| }, | |
| ) | |