refactor removing mixed logic

This commit is contained in:
johnpccd 2025-05-25 19:37:23 +02:00
parent 43c2bcce83
commit fba3662776
6 changed files with 158 additions and 292 deletions

View File

@ -1,8 +1,7 @@
import logging import logging
from typing import Optional from typing import Optional
from datetime import datetime from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Header, Request, Query from fastapi import APIRouter, Depends, HTTPException, Header, Request, Query, status
from bson import ObjectId
from src.dependencies import AuthServiceDep from src.dependencies import AuthServiceDep
from src.schemas.api_key import ApiKeyCreate, ApiKeyResponse, ApiKeyWithValueResponse, ApiKeyListResponse 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.team import TeamModel
from src.models.user import UserModel from src.models.user import UserModel
from src.utils.logging import log_request from src.utils.logging import log_request
from src.api.v1.error_handlers import handle_service_error
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(tags=["Authentication"], prefix="/auth") 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( async def create_api_key(
key_data: ApiKeyCreate, key_data: ApiKeyCreate,
request: Request, request: Request,
@ -31,20 +31,6 @@ async def create_api_key(
This endpoint creates an API key without requiring authentication. This endpoint creates an API key without requiring authentication.
Both user_id and team_id must be provided as query parameters. 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( 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) 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}") logger.info(f"API key created successfully for user {user_id} in team {team_id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error creating API key: {e}") raise handle_service_error(e, "API key creation")
raise HTTPException(status_code=500, detail="Internal server error")
@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( async def create_api_key_for_user(
user_id: str, user_id: str,
key_data: ApiKeyCreate, key_data: ApiKeyCreate,
@ -83,21 +62,6 @@ async def create_api_key_for_user(
This endpoint requires admin authentication and allows creating API keys This endpoint requires admin authentication and allows creating API keys
for any user in the system. 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( 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) 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}") logger.info(f"Admin {current_user.id} created API key for user {user_id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error creating API key for user: {e}") raise handle_service_error(e, "admin API key creation")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/api-keys", response_model=ApiKeyListResponse) @router.get("/api-keys", response_model=ApiKeyListResponse)
async def list_api_keys( async def list_api_keys(
@ -137,16 +91,6 @@ async def list_api_keys(
List API keys for the current authenticated user List API keys for the current authenticated user
Returns all active and inactive API keys belonging to the 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( log_request(
{"path": request.url.path, "method": request.method}, {"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}") logger.info(f"Listed {response.total} API keys for user {current_user.id}")
return response return response
except Exception as e: except Exception as e:
logger.error(f"Unexpected error listing API keys: {e}") raise handle_service_error(e, "API key listing")
raise HTTPException(status_code=500, detail="Internal server error")
@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( async def revoke_api_key(
key_id: str, key_id: str,
request: Request, request: Request,
@ -173,20 +116,6 @@ async def revoke_api_key(
Revoke (deactivate) an API key Revoke (deactivate) an API key
Deactivates the specified API key. Only the key owner or an admin can revoke keys. 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( log_request(
{"path": request.url.path, "method": request.method, "key_id": key_id}, {"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) await auth_service.revoke_api_key(key_id, current_user)
logger.info(f"API key {key_id} revoked by user {current_user.id}") logger.info(f"API key {key_id} revoked by user {current_user.id}")
return None 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: except Exception as e:
logger.error(f"Unexpected error revoking API key: {e}") raise handle_service_error(e, "API key revocation")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/verify", status_code=200) @router.get("/verify", status_code=status.HTTP_200_OK)
async def verify_authentication( async def verify_authentication(
request: Request, request: Request,
current_user: UserModel = Depends(get_current_user), current_user: UserModel = Depends(get_current_user),
@ -222,16 +141,6 @@ async def verify_authentication(
Validates the current API key and returns user information. Validates the current API key and returns user information.
Useful for checking if an API key is still valid and active. 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( log_request(
{"path": request.url.path, "method": request.method}, {"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}") logger.info(f"Authentication verified for user {current_user.id}")
return response return response
except Exception as e: except Exception as e:
logger.error(f"Unexpected error verifying authentication: {e}") raise handle_service_error(e, "authentication verification")
raise HTTPException(status_code=500, detail="Internal server error")

View File

@ -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)

View File

@ -1,6 +1,6 @@
import logging import logging
from typing import Optional, List 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 fastapi.responses import StreamingResponse
from bson import ObjectId from bson import ObjectId
import io import io
@ -10,12 +10,13 @@ from src.dependencies import ImageServiceDep
from src.models.user import UserModel from src.models.user import UserModel
from src.schemas.image import ImageResponse, ImageListResponse, ImageCreate, ImageUpdate from src.schemas.image import ImageResponse, ImageListResponse, ImageCreate, ImageUpdate
from src.utils.logging import log_request from src.utils.logging import log_request
from src.api.v1.error_handlers import handle_service_error
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(tags=["Images"], prefix="/images") 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( async def upload_image(
request: Request, request: Request,
file: UploadFile = File(..., description="Image file to upload"), 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) 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}") logger.info(f"Image uploaded successfully: {file.filename} by user {current_user.id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error uploading image: {e}") raise handle_service_error(e, "image upload")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("", response_model=ImageListResponse) @router.get("", response_model=ImageListResponse)
async def list_images( 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) 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})") logger.info(f"Listed {len(response.images)} images for user {current_user.id} (admin: {current_user.is_admin})")
return response 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: except Exception as e:
logger.error(f"Unexpected error listing images: {e}") raise handle_service_error(e, "image listing")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{image_id}", response_model=ImageResponse) @router.get("/{image_id}", response_model=ImageResponse)
async def get_image( async def get_image(
@ -167,18 +157,8 @@ async def get_image(
response = await image_service.get_image(image_id, current_user, request) response = await image_service.get_image(image_id, current_user, request)
logger.info(f"Retrieved image {image_id} for user {current_user.id}") logger.info(f"Retrieved image {image_id} for user {current_user.id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error getting image: {e}") raise handle_service_error(e, "image retrieval")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{image_id}/download") @router.get("/{image_id}/download")
async def download_image( async def download_image(
@ -229,18 +209,8 @@ async def download_image(
media_type=content_type, media_type=content_type,
headers={"Content-Disposition": f"attachment; filename={filename}"} 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: except Exception as e:
logger.error(f"Unexpected error downloading image: {e}") raise handle_service_error(e, "image download")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/{image_id}", response_model=ImageResponse) @router.put("/{image_id}", response_model=ImageResponse)
async def update_image( 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) 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}") logger.info(f"Image {image_id} updated by user {current_user.id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error updating image: {e}") raise handle_service_error(e, "image update")
raise HTTPException(status_code=500, detail="Internal server error")
@router.delete("/{image_id}", status_code=204) @router.delete("/{image_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_image( async def delete_image(
image_id: str, image_id: str,
request: Request, request: Request,
@ -342,15 +302,5 @@ async def delete_image(
await image_service.delete_image(image_id, current_user) await image_service.delete_image(image_id, current_user)
logger.info(f"Image {image_id} deleted by user {current_user.id}") logger.info(f"Image {image_id} deleted by user {current_user.id}")
return None 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: except Exception as e:
logger.error(f"Unexpected error deleting image: {e}") raise handle_service_error(e, "image deletion")
raise HTTPException(status_code=500, detail="Internal server error")

View File

@ -1,12 +1,13 @@
import logging import logging
from typing import Optional, List, Dict, Any 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.auth.security import get_current_user
from src.dependencies import SearchServiceDep from src.dependencies import SearchServiceDep
from src.models.user import UserModel from src.models.user import UserModel
from src.schemas.search import SearchResponse, SearchRequest from src.schemas.search import SearchResponse, SearchRequest
from src.utils.logging import log_request from src.utils.logging import log_request
from src.api.v1.error_handlers import handle_service_error
logger = logging.getLogger(__name__) 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}") logger.info(f"Search completed: '{q}' returned {len(response.results)} results for user {current_user.id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error in search: {e}") raise handle_service_error(e, "image search")
raise HTTPException(status_code=500, detail="Internal server error")
@router.post("", response_model=SearchResponse) @router.post("", response_model=SearchResponse)
async def search_images_advanced( 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}") logger.info(f"Advanced search completed: '{search_request.query}' returned {len(response.results)} results for user {current_user.id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error in advanced search: {e}") raise handle_service_error(e, "advanced image search")
raise HTTPException(status_code=500, detail="Internal server error")

View File

@ -1,16 +1,17 @@
import logging import logging
from fastapi import APIRouter, Depends, HTTPException, Request from fastapi import APIRouter, Depends, HTTPException, Request, status
from bson import ObjectId from bson import ObjectId
from src.dependencies import TeamServiceDep from src.dependencies import TeamServiceDep
from src.schemas.team import TeamCreate, TeamUpdate, TeamResponse, TeamListResponse from src.schemas.team import TeamCreate, TeamUpdate, TeamResponse, TeamListResponse
from src.utils.logging import log_request from src.utils.logging import log_request
from src.api.v1.error_handlers import handle_service_error
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
router = APIRouter(tags=["Teams"], prefix="/teams") 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( async def create_team(
team_data: TeamCreate, team_data: TeamCreate,
request: Request, request: Request,
@ -28,10 +29,6 @@ async def create_team(
Returns: Returns:
TeamResponse: The created team information TeamResponse: The created team information
Raises:
400: Invalid input data or team already exists
500: Internal server error
""" """
log_request( log_request(
{"path": request.url.path, "method": request.method, "team_data": team_data.dict()} {"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) response = await team_service.create_team(team_data)
logger.info(f"Created new team: {team_data.name}") logger.info(f"Created new team: {team_data.name}")
return response 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: except Exception as e:
logger.error(f"Unexpected error creating team: {e}") raise handle_service_error(e, "team creation")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("", response_model=TeamListResponse) @router.get("", response_model=TeamListResponse)
async def list_teams( async def list_teams(
@ -64,9 +57,6 @@ async def list_teams(
Returns: Returns:
TeamListResponse: List of all teams with total count TeamListResponse: List of all teams with total count
Raises:
500: Internal server error
""" """
log_request( log_request(
{"path": request.url.path, "method": request.method} {"path": request.url.path, "method": request.method}
@ -77,8 +67,7 @@ async def list_teams(
logger.info(f"Listed {response.total} teams") logger.info(f"Listed {response.total} teams")
return response return response
except Exception as e: except Exception as e:
logger.error(f"Unexpected error listing teams: {e}") raise handle_service_error(e, "team listing")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{team_id}", response_model=TeamResponse) @router.get("/{team_id}", response_model=TeamResponse)
async def get_team( async def get_team(
@ -98,11 +87,6 @@ async def get_team(
Returns: Returns:
TeamResponse: Complete team information TeamResponse: Complete team information
Raises:
400: Invalid team ID format
404: Team not found
500: Internal server error
""" """
log_request( log_request(
{"path": request.url.path, "method": request.method, "team_id": team_id} {"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) response = await team_service.get_team(team_id)
logger.info(f"Retrieved team {team_id}") logger.info(f"Retrieved team {team_id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error getting team: {e}") raise handle_service_error(e, "team retrieval")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/{team_id}", response_model=TeamResponse) @router.put("/{team_id}", response_model=TeamResponse)
async def update_team( async def update_team(
@ -142,11 +119,6 @@ async def update_team(
Returns: Returns:
TeamResponse: Updated team information TeamResponse: Updated team information
Raises:
400: Invalid team ID format or validation errors
404: Team not found
500: Internal server error
""" """
log_request( log_request(
{"path": request.url.path, "method": request.method, "team_id": team_id, "team_data": team_data.dict()} {"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) response = await team_service.update_team(team_id, team_data)
logger.info(f"Updated team {team_id}") logger.info(f"Updated team {team_id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error updating team: {e}") raise handle_service_error(e, "team update")
raise HTTPException(status_code=500, detail="Internal server error")
@router.delete("/{team_id}", status_code=204) @router.delete("/{team_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_team( async def delete_team(
team_id: str, team_id: str,
request: Request, request: Request,
@ -184,11 +149,6 @@ async def delete_team(
Returns: Returns:
None (204 No Content) None (204 No Content)
Raises:
400: Invalid team ID format or team has dependencies
404: Team not found
500: Internal server error
""" """
log_request( log_request(
{"path": request.url.path, "method": request.method, "team_id": team_id} {"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) await team_service.delete_team(team_id)
logger.info(f"Deleted team {team_id}") logger.info(f"Deleted team {team_id}")
return None 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: except Exception as e:
logger.error(f"Unexpected error deleting team: {e}") raise handle_service_error(e, "team deletion")
raise HTTPException(status_code=500, detail="Internal server error")

View File

@ -1,10 +1,11 @@
import logging import logging
from typing import Optional 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.dependencies import UserServiceDep
from src.schemas.user import UserResponse, UserListResponse, UserCreate, UserUpdate from src.schemas.user import UserResponse, UserListResponse, UserCreate, UserUpdate
from src.utils.logging import log_request from src.utils.logging import log_request
from src.api.v1.error_handlers import handle_service_error
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -42,15 +43,8 @@ async def read_users_me(
response = await user_service.get_user_by_id(user_id) response = await user_service.get_user_by_id(user_id)
logger.info(f"Retrieved user information for user {user_id}") logger.info(f"Retrieved user information for user {user_id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error getting user: {e}") raise handle_service_error(e, "user retrieval by ID")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/me", response_model=UserResponse) @router.put("/me", response_model=UserResponse)
async def update_current_user( 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) response = await user_service.update_user_by_id(user_id, user_data)
logger.info(f"Updated user information for user {user_id}") logger.info(f"Updated user information for user {user_id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error updating user: {e}") raise handle_service_error(e, "user update by ID")
raise HTTPException(status_code=500, detail="Internal server error")
@router.post("", response_model=UserResponse, status_code=201) @router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user( async def create_user(
user_data: UserCreate, user_data: UserCreate,
request: Request, request: Request,
@ -128,15 +115,8 @@ async def create_user(
response = await user_service.create_user(user_data) response = await user_service.create_user(user_data)
logger.info(f"Created new user with email {user_data.email}") logger.info(f"Created new user with email {user_data.email}")
return response 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: except Exception as e:
logger.error(f"Unexpected error creating user: {e}") raise handle_service_error(e, "user creation")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("", response_model=UserListResponse) @router.get("", response_model=UserListResponse)
async def list_users( async def list_users(
@ -169,12 +149,8 @@ async def list_users(
response = await user_service.list_users(team_id) response = await user_service.list_users(team_id)
logger.info(f"Listed {response.total} users" + (f" for team {team_id}" if team_id else "")) logger.info(f"Listed {response.total} users" + (f" for team {team_id}" if team_id else ""))
return response 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: except Exception as e:
logger.error(f"Unexpected error listing users: {e}") raise handle_service_error(e, "user listing")
raise HTTPException(status_code=500, detail="Internal server error")
@router.get("/{user_id}", response_model=UserResponse) @router.get("/{user_id}", response_model=UserResponse)
async def get_user( async def get_user(
@ -207,15 +183,8 @@ async def get_user(
response = await user_service.get_user(user_id) response = await user_service.get_user(user_id)
logger.info(f"Retrieved user {user_id}") logger.info(f"Retrieved user {user_id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error getting user: {e}") raise handle_service_error(e, "user retrieval")
raise HTTPException(status_code=500, detail="Internal server error")
@router.put("/{user_id}", response_model=UserResponse) @router.put("/{user_id}", response_model=UserResponse)
async def update_user( async def update_user(
@ -251,17 +220,10 @@ async def update_user(
response = await user_service.update_user(user_id, user_data) response = await user_service.update_user(user_id, user_data)
logger.info(f"Updated user {user_id}") logger.info(f"Updated user {user_id}")
return response 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: except Exception as e:
logger.error(f"Unexpected error updating user: {e}") raise handle_service_error(e, "user update")
raise HTTPException(status_code=500, detail="Internal server error")
@router.delete("/{user_id}", status_code=204) @router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user( async def delete_user(
user_id: str, user_id: str,
request: Request, request: Request,
@ -292,12 +254,5 @@ async def delete_user(
await user_service.delete_user(user_id) await user_service.delete_user(user_id)
logger.info(f"Deleted user {user_id}") logger.info(f"Deleted user {user_id}")
return None 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: except Exception as e:
logger.error(f"Unexpected error deleting user: {e}") raise handle_service_error(e, "user deletion")
raise HTTPException(status_code=500, detail="Internal server error")