From fba366277672a55c6c85ddb38b0c7d8f6c919f23 Mon Sep 17 00:00:00 2001 From: johnpccd Date: Sun, 25 May 2025 19:37:23 +0200 Subject: [PATCH] refactor removing mixed logic --- src/api/v1/auth.py | 114 ++++------------------------------- src/api/v1/error_handlers.py | 113 ++++++++++++++++++++++++++++++++++ src/api/v1/images.py | 70 +++------------------ src/api/v1/search.py | 21 ++----- src/api/v1/teams.py | 65 +++----------------- src/api/v1/users.py | 67 ++++---------------- 6 files changed, 158 insertions(+), 292 deletions(-) create mode 100644 src/api/v1/error_handlers.py diff --git a/src/api/v1/auth.py b/src/api/v1/auth.py index 1a0ff51..9bac0df 100644 --- a/src/api/v1/auth.py +++ b/src/api/v1/auth.py @@ -1,8 +1,7 @@ import logging from typing import Optional from datetime import datetime -from fastapi import APIRouter, Depends, HTTPException, Header, Request, Query -from bson import ObjectId +from fastapi import APIRouter, Depends, HTTPException, Header, Request, Query, status from src.dependencies import AuthServiceDep from src.schemas.api_key import ApiKeyCreate, ApiKeyResponse, ApiKeyWithValueResponse, ApiKeyListResponse @@ -13,12 +12,13 @@ from src.models.api_key import ApiKeyModel from src.models.team import TeamModel from src.models.user import UserModel from src.utils.logging import log_request +from src.api.v1.error_handlers import handle_service_error logger = logging.getLogger(__name__) router = APIRouter(tags=["Authentication"], prefix="/auth") -@router.post("/api-keys", response_model=ApiKeyWithValueResponse, status_code=201) +@router.post("/api-keys", response_model=ApiKeyWithValueResponse, status_code=status.HTTP_201_CREATED) async def create_api_key( key_data: ApiKeyCreate, request: Request, @@ -31,20 +31,6 @@ async def create_api_key( This endpoint creates an API key without requiring authentication. Both user_id and team_id must be provided as query parameters. - - Args: - key_data: API key creation data including name and description - user_id: The user ID to create the key for - team_id: The team ID the user belongs to - auth_service: Injected authentication service - - Returns: - ApiKeyWithValueResponse: The created API key with the raw key value - - Raises: - 400: Invalid input data or user/team validation errors - 404: User or team not found - 500: Internal server error """ log_request( { @@ -60,17 +46,10 @@ async def create_api_key( response = await auth_service.create_api_key_for_user_and_team(user_id, team_id, key_data) logger.info(f"API key created successfully for user {user_id} in team {team_id}") return response - except ValueError as e: - logger.warning(f"Invalid input for API key creation: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Resource not found for API key creation: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error creating API key: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "API key creation") -@router.post("/admin/api-keys/{user_id}", response_model=ApiKeyWithValueResponse, status_code=201) +@router.post("/admin/api-keys/{user_id}", response_model=ApiKeyWithValueResponse, status_code=status.HTTP_201_CREATED) async def create_api_key_for_user( user_id: str, key_data: ApiKeyCreate, @@ -83,21 +62,6 @@ async def create_api_key_for_user( This endpoint requires admin authentication and allows creating API keys for any user in the system. - - Args: - user_id: The target user ID to create the key for - key_data: API key creation data including name and description - current_user: The authenticated admin user - auth_service: Injected authentication service - - Returns: - ApiKeyWithValueResponse: The created API key with the raw key value - - Raises: - 400: Invalid input data - 403: Insufficient permissions (not admin) - 404: Target user or team not found - 500: Internal server error """ log_request( { @@ -114,18 +78,8 @@ async def create_api_key_for_user( response = await auth_service.create_api_key_for_user_by_admin(user_id, key_data, current_user) logger.info(f"Admin {current_user.id} created API key for user {user_id}") return response - except PermissionError as e: - logger.warning(f"Permission denied for admin API key creation: {e}") - raise HTTPException(status_code=403, detail=str(e)) - except ValueError as e: - logger.warning(f"Invalid input for admin API key creation: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Resource not found for admin API key creation: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error creating API key for user: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "admin API key creation") @router.get("/api-keys", response_model=ApiKeyListResponse) async def list_api_keys( @@ -137,16 +91,6 @@ async def list_api_keys( List API keys for the current authenticated user Returns all active and inactive API keys belonging to the authenticated user. - - Args: - current_user: The authenticated user - auth_service: Injected authentication service - - Returns: - ApiKeyListResponse: List of API keys with metadata - - Raises: - 500: Internal server error """ log_request( {"path": request.url.path, "method": request.method}, @@ -159,10 +103,9 @@ async def list_api_keys( logger.info(f"Listed {response.total} API keys for user {current_user.id}") return response except Exception as e: - logger.error(f"Unexpected error listing API keys: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "API key listing") -@router.delete("/api-keys/{key_id}", status_code=204) +@router.delete("/api-keys/{key_id}", status_code=status.HTTP_204_NO_CONTENT) async def revoke_api_key( key_id: str, request: Request, @@ -173,20 +116,6 @@ async def revoke_api_key( Revoke (deactivate) an API key Deactivates the specified API key. Only the key owner or an admin can revoke keys. - - Args: - key_id: The ID of the API key to revoke - current_user: The authenticated user - auth_service: Injected authentication service - - Returns: - None (204 No Content) - - Raises: - 400: Invalid key ID format - 403: Insufficient permissions to revoke this key - 404: API key not found - 500: Internal server error """ log_request( {"path": request.url.path, "method": request.method, "key_id": key_id}, @@ -198,20 +127,10 @@ async def revoke_api_key( await auth_service.revoke_api_key(key_id, current_user) logger.info(f"API key {key_id} revoked by user {current_user.id}") return None - except ValueError as e: - logger.warning(f"Invalid input for API key revocation: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"API key not found for revocation: {e}") - raise HTTPException(status_code=404, detail=str(e)) - except PermissionError as e: - logger.warning(f"Permission denied for API key revocation: {e}") - raise HTTPException(status_code=403, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error revoking API key: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "API key revocation") -@router.get("/verify", status_code=200) +@router.get("/verify", status_code=status.HTTP_200_OK) async def verify_authentication( request: Request, current_user: UserModel = Depends(get_current_user), @@ -222,16 +141,6 @@ async def verify_authentication( Validates the current API key and returns user information. Useful for checking if an API key is still valid and active. - - Args: - current_user: The authenticated user - auth_service: Injected authentication service - - Returns: - dict: Authentication verification response with user details - - Raises: - 500: Internal server error """ log_request( {"path": request.url.path, "method": request.method}, @@ -244,5 +153,4 @@ async def verify_authentication( logger.info(f"Authentication verified for user {current_user.id}") return response except Exception as e: - logger.error(f"Unexpected error verifying authentication: {e}") - raise HTTPException(status_code=500, detail="Internal server error") \ No newline at end of file + raise handle_service_error(e, "authentication verification") \ No newline at end of file diff --git a/src/api/v1/error_handlers.py b/src/api/v1/error_handlers.py new file mode 100644 index 0000000..50c762c --- /dev/null +++ b/src/api/v1/error_handlers.py @@ -0,0 +1,113 @@ +""" +Shared error handling utilities for API endpoints. + +This module provides centralized error handling to ensure consistent +HTTP status codes and error responses across all API endpoints. +""" + +import logging +from typing import Dict, Type +from fastapi import HTTPException, status + +logger = logging.getLogger(__name__) + +# HTTP Status Code Mapping for consistent error responses +HTTP_ERROR_MAP: Dict[Type[Exception], int] = { + ValueError: status.HTTP_400_BAD_REQUEST, + RuntimeError: status.HTTP_404_NOT_FOUND, + PermissionError: status.HTTP_403_FORBIDDEN, + FileNotFoundError: status.HTTP_404_NOT_FOUND, + TimeoutError: status.HTTP_408_REQUEST_TIMEOUT, + ConnectionError: status.HTTP_503_SERVICE_UNAVAILABLE, + Exception: status.HTTP_500_INTERNAL_SERVER_ERROR +} + +def handle_service_error(error: Exception, operation: str) -> HTTPException: + """ + Centralized error handling for service layer exceptions + + This function maps service layer exceptions to appropriate HTTP status codes + and provides consistent error logging and response formatting. + + Args: + error: The exception raised by the service layer + operation: Description of the operation that failed (for logging) + + Returns: + HTTPException: Properly formatted HTTP exception with appropriate status code + + Examples: + >>> try: + ... await some_service_method() + ... except Exception as e: + ... raise handle_service_error(e, "user creation") + """ + error_type = type(error) + status_code = HTTP_ERROR_MAP.get(error_type, status.HTTP_500_INTERNAL_SERVER_ERROR) + + # Log errors appropriately based on severity + if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR: + logger.error(f"Unexpected error in {operation}: {error}", exc_info=True) + detail = "Internal server error" + elif status_code >= 500: + logger.error(f"Server error in {operation}: {error}") + detail = "Service temporarily unavailable" + else: + logger.warning(f"Client error in {operation}: {error}") + detail = str(error) + + return HTTPException(status_code=status_code, detail=detail) + +def handle_validation_error(error: Exception, field_name: str = None) -> HTTPException: + """ + Handle validation errors with specific formatting + + Args: + error: The validation exception + field_name: Optional field name that failed validation + + Returns: + HTTPException: Formatted validation error response + """ + detail = f"Validation error" + if field_name: + detail += f" for field '{field_name}'" + detail += f": {str(error)}" + + logger.warning(f"Validation error: {detail}") + return HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=detail) + +def handle_authentication_error(error: Exception) -> HTTPException: + """ + Handle authentication-related errors + + Args: + error: The authentication exception + + Returns: + HTTPException: Formatted authentication error response + """ + logger.warning(f"Authentication error: {error}") + return HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Authentication failed", + headers={"WWW-Authenticate": "Bearer"} + ) + +def handle_authorization_error(error: Exception, resource: str = None) -> HTTPException: + """ + Handle authorization-related errors + + Args: + error: The authorization exception + resource: Optional resource name that access was denied to + + Returns: + HTTPException: Formatted authorization error response + """ + detail = "Access denied" + if resource: + detail += f" to {resource}" + + logger.warning(f"Authorization error: {detail} - {error}") + return HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=detail) \ No newline at end of file diff --git a/src/api/v1/images.py b/src/api/v1/images.py index 7bbb8ab..d08a3e2 100644 --- a/src/api/v1/images.py +++ b/src/api/v1/images.py @@ -1,6 +1,6 @@ import logging from typing import Optional, List -from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query, Request, Response +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Query, Request, Response, status from fastapi.responses import StreamingResponse from bson import ObjectId import io @@ -10,12 +10,13 @@ from src.dependencies import ImageServiceDep from src.models.user import UserModel from src.schemas.image import ImageResponse, ImageListResponse, ImageCreate, ImageUpdate from src.utils.logging import log_request +from src.api.v1.error_handlers import handle_service_error logger = logging.getLogger(__name__) router = APIRouter(tags=["Images"], prefix="/images") -@router.post("", response_model=ImageResponse, status_code=201) +@router.post("", response_model=ImageResponse, status_code=status.HTTP_201_CREATED) async def upload_image( request: Request, file: UploadFile = File(..., description="Image file to upload"), @@ -62,15 +63,8 @@ async def upload_image( response = await image_service.upload_image(file, current_user, request, description, collection_id) logger.info(f"Image uploaded successfully: {file.filename} by user {current_user.id}") return response - except ValueError as e: - logger.warning(f"Invalid input for image upload: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.error(f"Runtime error during image upload: {e}") - raise HTTPException(status_code=500, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error uploading image: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "image upload") @router.get("", response_model=ImageListResponse) async def list_images( @@ -118,12 +112,8 @@ async def list_images( response = await image_service.list_images(current_user, request, skip, limit, collection_id) logger.info(f"Listed {len(response.images)} images for user {current_user.id} (admin: {current_user.is_admin})") return response - except ValueError as e: - logger.warning(f"Invalid parameters for image listing: {e}") - raise HTTPException(status_code=400, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error listing images: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "image listing") @router.get("/{image_id}", response_model=ImageResponse) async def get_image( @@ -167,18 +157,8 @@ async def get_image( response = await image_service.get_image(image_id, current_user, request) logger.info(f"Retrieved image {image_id} for user {current_user.id}") return response - except ValueError as e: - logger.warning(f"Invalid image ID: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Image not found: {e}") - raise HTTPException(status_code=404, detail=str(e)) - except PermissionError as e: - logger.warning(f"Permission denied for image access: {e}") - raise HTTPException(status_code=403, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error getting image: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "image retrieval") @router.get("/{image_id}/download") async def download_image( @@ -229,18 +209,8 @@ async def download_image( media_type=content_type, headers={"Content-Disposition": f"attachment; filename={filename}"} ) - except ValueError as e: - logger.warning(f"Invalid image ID for download: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Image not found for download: {e}") - raise HTTPException(status_code=404, detail=str(e)) - except PermissionError as e: - logger.warning(f"Permission denied for image download: {e}") - raise HTTPException(status_code=403, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error downloading image: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "image download") @router.put("/{image_id}", response_model=ImageResponse) async def update_image( @@ -287,20 +257,10 @@ async def update_image( response = await image_service.update_image(image_id, image_data, current_user, request) logger.info(f"Image {image_id} updated by user {current_user.id}") return response - except ValueError as e: - logger.warning(f"Invalid input for image update: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Image not found for update: {e}") - raise HTTPException(status_code=404, detail=str(e)) - except PermissionError as e: - logger.warning(f"Permission denied for image update: {e}") - raise HTTPException(status_code=403, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error updating image: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "image update") -@router.delete("/{image_id}", status_code=204) +@router.delete("/{image_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_image( image_id: str, request: Request, @@ -342,15 +302,5 @@ async def delete_image( await image_service.delete_image(image_id, current_user) logger.info(f"Image {image_id} deleted by user {current_user.id}") return None - except ValueError as e: - logger.warning(f"Invalid image ID for deletion: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Image not found for deletion: {e}") - raise HTTPException(status_code=404, detail=str(e)) - except PermissionError as e: - logger.warning(f"Permission denied for image deletion: {e}") - raise HTTPException(status_code=403, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error deleting image: {e}") - raise HTTPException(status_code=500, detail="Internal server error") \ No newline at end of file + raise handle_service_error(e, "image deletion") \ No newline at end of file diff --git a/src/api/v1/search.py b/src/api/v1/search.py index 1dcc321..0ad6296 100644 --- a/src/api/v1/search.py +++ b/src/api/v1/search.py @@ -1,12 +1,13 @@ import logging from typing import Optional, List, Dict, Any -from fastapi import APIRouter, Depends, Query, Request, HTTPException +from fastapi import APIRouter, Depends, Query, Request, HTTPException, status from src.auth.security import get_current_user from src.dependencies import SearchServiceDep from src.models.user import UserModel from src.schemas.search import SearchResponse, SearchRequest from src.utils.logging import log_request +from src.api.v1.error_handlers import handle_service_error logger = logging.getLogger(__name__) @@ -69,15 +70,8 @@ async def search_images( ) logger.info(f"Search completed: '{q}' returned {len(response.results)} results for user {current_user.id}") return response - except ValueError as e: - logger.warning(f"Invalid search parameters: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.error(f"Search service error: {e}") - raise HTTPException(status_code=500, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error in search: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "image search") @router.post("", response_model=SearchResponse) async def search_images_advanced( @@ -124,12 +118,5 @@ async def search_images_advanced( ) logger.info(f"Advanced search completed: '{search_request.query}' returned {len(response.results)} results for user {current_user.id}") return response - except ValueError as e: - logger.warning(f"Invalid advanced search request: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.error(f"Advanced search service error: {e}") - raise HTTPException(status_code=500, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error in advanced search: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "advanced image search") diff --git a/src/api/v1/teams.py b/src/api/v1/teams.py index fe5330a..65fb74d 100644 --- a/src/api/v1/teams.py +++ b/src/api/v1/teams.py @@ -1,16 +1,17 @@ import logging -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, Depends, HTTPException, Request, status from bson import ObjectId from src.dependencies import TeamServiceDep from src.schemas.team import TeamCreate, TeamUpdate, TeamResponse, TeamListResponse from src.utils.logging import log_request +from src.api.v1.error_handlers import handle_service_error logger = logging.getLogger(__name__) router = APIRouter(tags=["Teams"], prefix="/teams") -@router.post("", response_model=TeamResponse, status_code=201) +@router.post("", response_model=TeamResponse, status_code=status.HTTP_201_CREATED) async def create_team( team_data: TeamCreate, request: Request, @@ -28,10 +29,6 @@ async def create_team( Returns: TeamResponse: The created team information - - Raises: - 400: Invalid input data or team already exists - 500: Internal server error """ log_request( {"path": request.url.path, "method": request.method, "team_data": team_data.dict()} @@ -41,12 +38,8 @@ async def create_team( response = await team_service.create_team(team_data) logger.info(f"Created new team: {team_data.name}") return response - except ValueError as e: - logger.warning(f"Invalid input for team creation: {e}") - raise HTTPException(status_code=400, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error creating team: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "team creation") @router.get("", response_model=TeamListResponse) async def list_teams( @@ -64,9 +57,6 @@ async def list_teams( Returns: TeamListResponse: List of all teams with total count - - Raises: - 500: Internal server error """ log_request( {"path": request.url.path, "method": request.method} @@ -77,8 +67,7 @@ async def list_teams( logger.info(f"Listed {response.total} teams") return response except Exception as e: - logger.error(f"Unexpected error listing teams: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "team listing") @router.get("/{team_id}", response_model=TeamResponse) async def get_team( @@ -98,11 +87,6 @@ async def get_team( Returns: TeamResponse: Complete team information - - Raises: - 400: Invalid team ID format - 404: Team not found - 500: Internal server error """ log_request( {"path": request.url.path, "method": request.method, "team_id": team_id} @@ -112,15 +96,8 @@ async def get_team( response = await team_service.get_team(team_id) logger.info(f"Retrieved team {team_id}") return response - except ValueError as e: - logger.warning(f"Invalid team ID: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Team not found: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error getting team: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "team retrieval") @router.put("/{team_id}", response_model=TeamResponse) async def update_team( @@ -142,11 +119,6 @@ async def update_team( Returns: TeamResponse: Updated team information - - Raises: - 400: Invalid team ID format or validation errors - 404: Team not found - 500: Internal server error """ log_request( {"path": request.url.path, "method": request.method, "team_id": team_id, "team_data": team_data.dict()} @@ -156,17 +128,10 @@ async def update_team( response = await team_service.update_team(team_id, team_data) logger.info(f"Updated team {team_id}") return response - except ValueError as e: - logger.warning(f"Invalid input for team update: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Team not found for update: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error updating team: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "team update") -@router.delete("/{team_id}", status_code=204) +@router.delete("/{team_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_team( team_id: str, request: Request, @@ -184,11 +149,6 @@ async def delete_team( Returns: None (204 No Content) - - Raises: - 400: Invalid team ID format or team has dependencies - 404: Team not found - 500: Internal server error """ log_request( {"path": request.url.path, "method": request.method, "team_id": team_id} @@ -198,12 +158,5 @@ async def delete_team( await team_service.delete_team(team_id) logger.info(f"Deleted team {team_id}") return None - except ValueError as e: - logger.warning(f"Invalid team ID or team has dependencies: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Team not found for deletion: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error deleting team: {e}") - raise HTTPException(status_code=500, detail="Internal server error") \ No newline at end of file + raise handle_service_error(e, "team deletion") \ No newline at end of file diff --git a/src/api/v1/users.py b/src/api/v1/users.py index 9671f6c..c63e9e4 100644 --- a/src/api/v1/users.py +++ b/src/api/v1/users.py @@ -1,10 +1,11 @@ import logging from typing import Optional -from fastapi import APIRouter, Depends, HTTPException, Request, Query +from fastapi import APIRouter, Depends, HTTPException, Request, Query, status from src.dependencies import UserServiceDep from src.schemas.user import UserResponse, UserListResponse, UserCreate, UserUpdate from src.utils.logging import log_request +from src.api.v1.error_handlers import handle_service_error logger = logging.getLogger(__name__) @@ -42,15 +43,8 @@ async def read_users_me( response = await user_service.get_user_by_id(user_id) logger.info(f"Retrieved user information for user {user_id}") return response - except ValueError as e: - logger.warning(f"Invalid user ID provided: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"User not found: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error getting user: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "user retrieval by ID") @router.put("/me", response_model=UserResponse) async def update_current_user( @@ -86,17 +80,10 @@ async def update_current_user( response = await user_service.update_user_by_id(user_id, user_data) logger.info(f"Updated user information for user {user_id}") return response - except ValueError as e: - logger.warning(f"Invalid input for user update: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"User not found for update: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error updating user: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "user update by ID") -@router.post("", response_model=UserResponse, status_code=201) +@router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED) async def create_user( user_data: UserCreate, request: Request, @@ -128,15 +115,8 @@ async def create_user( response = await user_service.create_user(user_data) logger.info(f"Created new user with email {user_data.email}") return response - except ValueError as e: - logger.warning(f"Invalid input for user creation: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"Resource not found for user creation: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error creating user: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "user creation") @router.get("", response_model=UserListResponse) async def list_users( @@ -169,12 +149,8 @@ async def list_users( response = await user_service.list_users(team_id) logger.info(f"Listed {response.total} users" + (f" for team {team_id}" if team_id else "")) return response - except ValueError as e: - logger.warning(f"Invalid team ID for user listing: {e}") - raise HTTPException(status_code=400, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error listing users: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "user listing") @router.get("/{user_id}", response_model=UserResponse) async def get_user( @@ -207,15 +183,8 @@ async def get_user( response = await user_service.get_user(user_id) logger.info(f"Retrieved user {user_id}") return response - except ValueError as e: - logger.warning(f"Invalid user ID: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"User not found: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error getting user: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "user retrieval") @router.put("/{user_id}", response_model=UserResponse) async def update_user( @@ -251,17 +220,10 @@ async def update_user( response = await user_service.update_user(user_id, user_data) logger.info(f"Updated user {user_id}") return response - except ValueError as e: - logger.warning(f"Invalid input for user update: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"User not found for update: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error updating user: {e}") - raise HTTPException(status_code=500, detail="Internal server error") + raise handle_service_error(e, "user update") -@router.delete("/{user_id}", status_code=204) +@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_user( user_id: str, request: Request, @@ -292,12 +254,5 @@ async def delete_user( await user_service.delete_user(user_id) logger.info(f"Deleted user {user_id}") return None - except ValueError as e: - logger.warning(f"Invalid user ID for deletion: {e}") - raise HTTPException(status_code=400, detail=str(e)) - except RuntimeError as e: - logger.warning(f"User not found for deletion: {e}") - raise HTTPException(status_code=404, detail=str(e)) except Exception as e: - logger.error(f"Unexpected error deleting user: {e}") - raise HTTPException(status_code=500, detail="Internal server error") \ No newline at end of file + raise handle_service_error(e, "user deletion") \ No newline at end of file