Spaces:
Running
Running
| """ | |
| Task CRUD API endpoints. | |
| """ | |
| from typing import List | |
| from fastapi import APIRouter, Depends, HTTPException, status, Query, Header | |
| from sqlmodel import Session | |
| from src.db.session import get_session | |
| from src.models.task import TaskCreate, TaskUpdate, TaskResponse, Task | |
| from src.models.user import User | |
| from src.services.task_service import TaskService | |
| from src.services.user_registration_service import user_registration_service | |
| from src.middleware.auth import get_current_user | |
| router = APIRouter(prefix="/tasks", tags=["tasks"]) | |
| def get_or_create_backend_user( | |
| session: Session, | |
| user_id: str, | |
| user_email: str = None, | |
| user_name: str = None | |
| ) -> str: | |
| """ | |
| Get or create backend user from Better Auth user info. | |
| Args: | |
| session: Database session | |
| user_id: Better Auth user ID (email or UUID) | |
| user_email: User email | |
| user_name: User name | |
| Returns: | |
| str: String user ID (always VARCHAR) | |
| Note: Always returns string ID for Better Auth compatibility | |
| """ | |
| try: | |
| # Ensure user_id is not None and is string | |
| if not user_id or not isinstance(user_id, str): | |
| if user_email: | |
| user_id = user_email | |
| else: | |
| raise ValueError("user_id or user_email must be provided") | |
| user = user_registration_service.get_or_create_user( | |
| session=session, | |
| user_id=str(user_id), # Always convert to string | |
| email=user_email, | |
| name=user_name | |
| ) | |
| return str(user.id) # Ensure return is string | |
| except Exception as e: | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| logger.error(f"Error in get_or_create_backend_user: {e}", exc_info=True) | |
| raise | |
| def create_task( | |
| task_data: TaskCreate, | |
| user_id: str = Query(None, description="User ID for demo/unauthenticated users"), | |
| x_user_email: str = Header(None, alias="X-User-Email"), | |
| x_user_name: str = Header(None, alias="X-User-Name"), | |
| session: Session = Depends(get_session), | |
| current_user: User = Depends(lambda: None) # Make auth optional | |
| ): | |
| """ | |
| Create a new task for the authenticated user or demo user. | |
| Args: | |
| task_data: Task creation data (subitems or shopping_list as JSON array) | |
| user_id: Optional user ID for demo users (if not authenticated) | |
| x_user_email: User email from Better Auth (header) | |
| x_user_name: User name from Better Auth (header) | |
| session: Database session | |
| current_user: Authenticated user (optional) | |
| Returns: | |
| TaskResponse: Created task | |
| Raises: | |
| HTTPException: If validation or creation fails | |
| """ | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| try: | |
| # Determine user ID: use authenticated user if available, otherwise use provided user_id | |
| if current_user and hasattr(current_user, 'id'): | |
| effective_user_id = str(current_user.id) | |
| elif user_id: | |
| # Auto-register Better Auth user and get backend string ID | |
| effective_user_id = get_or_create_backend_user( | |
| session=session, | |
| user_id=user_id, | |
| user_email=x_user_email, | |
| user_name=x_user_name | |
| ) | |
| else: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Authentication required or user_id must be provided" | |
| ) | |
| logger.info(f"Creating task for user: {effective_user_id}, title: {task_data.title}") | |
| task = TaskService.create_task(session, effective_user_id, task_data) | |
| logger.info(f"Task created successfully: ID={task.id}") | |
| return TaskResponse(**task.model_dump()) | |
| except ValueError as e: | |
| logger.error(f"Validation error: {e}") | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail=str(e) | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error creating task: {e}", exc_info=True) | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail="Failed to create task" | |
| ) | |
| def get_tasks( | |
| skip: int = Query(0, ge=0), | |
| limit: int = Query(100, ge=1, le=200), | |
| user_id: str = Query(None, description="User ID for demo/unauthenticated users"), | |
| x_user_email: str = Header(None, alias="X-User-Email"), | |
| x_user_name: str = Header(None, alias="X-User-Name"), | |
| session: Session = Depends(get_session), | |
| current_user: User = Depends(lambda: None) # Make auth optional | |
| ): | |
| """ | |
| Get all tasks for the authenticated user or demo user with pagination. | |
| Args: | |
| skip: Number of records to skip | |
| limit: Maximum number of records to return | |
| user_id: Optional user ID for demo users (if not authenticated) | |
| x_user_email: User email from Better Auth (header) | |
| x_user_name: User name from Better Auth (header) | |
| session: Database session | |
| current_user: Authenticated user (optional) | |
| Returns: | |
| List[TaskResponse]: List of user's tasks | |
| """ | |
| # Determine user ID: use authenticated user if available, otherwise use provided user_id | |
| if current_user and hasattr(current_user, 'id'): | |
| effective_user_id = current_user.id | |
| elif user_id: | |
| # Auto-register Better Auth user and get backend integer ID | |
| effective_user_id = get_or_create_backend_user( | |
| session=session, | |
| user_id=user_id, | |
| user_email=x_user_email, | |
| user_name=x_user_name | |
| ) | |
| else: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Authentication required or user_id must be provided" | |
| ) | |
| tasks = TaskService.get_tasks(session, effective_user_id, skip, limit) | |
| return [TaskResponse(**task.model_dump()) for task in tasks] | |
| def get_task( | |
| task_id: str, | |
| user_id: str = Query(None, description="User ID for demo/unauthenticated users"), | |
| x_user_email: str = Header(None, alias="X-User-Email"), | |
| x_user_name: str = Header(None, alias="X-User-Name"), | |
| session: Session = Depends(get_session), | |
| current_user: User = Depends(lambda: None) # Make auth optional | |
| ): | |
| """ | |
| Get a specific task by ID (either server ID or client ID). | |
| Args: | |
| task_id: Task ID (can be integer ID or string client_id) | |
| user_id: Optional user ID for demo users (if not authenticated) | |
| x_user_email: User email from Better Auth (header) | |
| x_user_name: User name from Better Auth (header) | |
| session: Database session | |
| current_user: Authenticated user (optional) | |
| Returns: | |
| TaskResponse: Requested task | |
| Raises: | |
| HTTPException: If task not found or doesn't belong to user | |
| """ | |
| # Determine user ID: use authenticated user if available, otherwise use provided user_id | |
| if current_user and hasattr(current_user, 'id'): | |
| effective_user_id = current_user.id | |
| elif user_id: | |
| # Auto-register Better Auth user and get backend integer ID | |
| effective_user_id = get_or_create_backend_user( | |
| session=session, | |
| user_id=user_id, | |
| user_email=x_user_email, | |
| user_name=x_user_name | |
| ) | |
| else: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Authentication required or user_id must be provided" | |
| ) | |
| # Check if task_id is an integer (server ID) or string (client ID) | |
| try: | |
| # Try parsing as integer first | |
| task_id_int = int(task_id) | |
| task = TaskService.get_task_by_id(session, task_id_int, effective_user_id) | |
| except ValueError: | |
| # If it's not an integer, treat as client_id | |
| task = TaskService.get_task_by_client_id(session, task_id, effective_user_id) | |
| if not task: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Task not found" | |
| ) | |
| return TaskResponse(**task.model_dump()) | |
| def update_task( | |
| task_id: str, | |
| task_data: TaskUpdate, | |
| user_id: str = Query(None, description="User ID for demo/unauthenticated users"), | |
| x_user_email: str = Header(None, alias="X-User-Email"), | |
| x_user_name: str = Header(None, alias="X-User-Name"), | |
| session: Session = Depends(get_session), | |
| current_user: User = Depends(lambda: None) # Make auth optional | |
| ): | |
| """ | |
| Update a task (by either server ID or client ID). | |
| Args: | |
| task_id: Task ID to update (can be integer ID or string client_id) | |
| task_data: Updated task data (subitems or shopping_list as JSON array) | |
| user_id: Optional user ID for demo users (if not authenticated) | |
| x_user_email: User email from Better Auth (header) | |
| x_user_name: User name from Better Auth (header) | |
| session: Database session | |
| current_user: Authenticated user (optional) | |
| Returns: | |
| TaskResponse: Updated task | |
| Raises: | |
| HTTPException: If task not found or doesn't belong to user | |
| """ | |
| import logging | |
| logger = logging.getLogger(__name__) | |
| try: | |
| # Determine user ID: use authenticated user if available, otherwise use provided user_id | |
| if current_user and hasattr(current_user, 'id'): | |
| effective_user_id = str(current_user.id) | |
| elif user_id: | |
| # Auto-register Better Auth user and get backend string ID | |
| effective_user_id = get_or_create_backend_user( | |
| session=session, | |
| user_id=user_id, | |
| user_email=x_user_email, | |
| user_name=x_user_name | |
| ) | |
| else: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Authentication required or user_id must be provided" | |
| ) | |
| logger.info(f"Updating task {task_id} for user: {effective_user_id}") | |
| # Check if task_id is an integer (server ID) or string (client ID) | |
| try: | |
| # Try parsing as integer first | |
| task_id_int = int(task_id) | |
| task = TaskService.update_task(session, task_id_int, effective_user_id, task_data) | |
| except ValueError: | |
| # If it's not an integer, treat as client_id | |
| # For client_id, we need to first get the task to get its server ID | |
| task_obj = TaskService.get_task_by_client_id(session, task_id, effective_user_id) | |
| if not task_obj: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Task not found" | |
| ) | |
| task = TaskService.update_task(session, task_obj.id, effective_user_id, task_data) | |
| if not task: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Task not found" | |
| ) | |
| logger.info(f"Task updated successfully: ID={task.id}") | |
| return TaskResponse(**task.model_dump()) | |
| except HTTPException: | |
| raise | |
| except ValueError as e: | |
| logger.error(f"Validation error: {e}") | |
| raise HTTPException( | |
| status_code=status.HTTP_400_BAD_REQUEST, | |
| detail=str(e) | |
| ) | |
| except Exception as e: | |
| logger.error(f"Error updating task: {e}", exc_info=True) | |
| raise HTTPException( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| detail="Failed to update task" | |
| ) | |
| def delete_task( | |
| task_id: str, | |
| user_id: str = Query(None, description="User ID for demo/unauthenticated users"), | |
| x_user_email: str = Header(None, alias="X-User-Email"), | |
| x_user_name: str = Header(None, alias="X-User-Name"), | |
| session: Session = Depends(get_session), | |
| current_user: User = Depends(lambda: None) # Make auth optional | |
| ): | |
| """ | |
| Delete a task (by either server ID or client ID). | |
| Args: | |
| task_id: Task ID to delete (can be integer ID or string client_id) | |
| user_id: Optional user ID for demo users (if not authenticated) | |
| x_user_email: User email from Better Auth (header) | |
| x_user_name: User name from Better Auth (header) | |
| session: Database session | |
| current_user: Authenticated user (optional) | |
| Raises: | |
| HTTPException: If task not found or doesn't belong to user | |
| """ | |
| # Determine user ID: use authenticated user if available, otherwise use provided user_id | |
| if current_user and hasattr(current_user, 'id'): | |
| effective_user_id = current_user.id | |
| elif user_id: | |
| # Auto-register Better Auth user and get backend integer ID | |
| effective_user_id = get_or_create_backend_user( | |
| session=session, | |
| user_id=user_id, | |
| user_email=x_user_email, | |
| user_name=x_user_name | |
| ) | |
| else: | |
| raise HTTPException( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| detail="Authentication required or user_id must be provided" | |
| ) | |
| # Check if task_id is an integer (server ID) or string (client ID) | |
| try: | |
| # Try parsing as integer first | |
| task_id_int = int(task_id) | |
| deleted = TaskService.delete_task(session, task_id_int, effective_user_id) | |
| except ValueError: | |
| # If it's not an integer, treat as client_id | |
| # For client_id, we need to first get the task to get its server ID | |
| task_obj = TaskService.get_task_by_client_id(session, task_id, effective_user_id) | |
| if not task_obj: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Task not found" | |
| ) | |
| deleted = TaskService.delete_task(session, task_obj.id, effective_user_id) | |
| if not deleted: | |
| raise HTTPException( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| detail="Task not found" | |
| ) | |