refactor
This commit is contained in:
parent
a26bd08d9c
commit
71bafe0938
@ -1,7 +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
|
from fastapi import APIRouter, Depends, HTTPException, Header, Request, Query
|
||||||
from bson import ObjectId
|
from bson import ObjectId
|
||||||
|
|
||||||
from src.services.auth_service import AuthService
|
from src.services.auth_service import AuthService
|
||||||
@ -22,22 +22,50 @@ router = APIRouter(tags=["Authentication"], prefix="/auth")
|
|||||||
auth_service = AuthService()
|
auth_service = AuthService()
|
||||||
|
|
||||||
@router.post("/api-keys", response_model=ApiKeyWithValueResponse, status_code=201)
|
@router.post("/api-keys", response_model=ApiKeyWithValueResponse, status_code=201)
|
||||||
async def create_api_key(key_data: ApiKeyCreate, request: Request, user_id: str, team_id: str):
|
async def create_api_key(
|
||||||
|
key_data: ApiKeyCreate,
|
||||||
|
request: Request,
|
||||||
|
user_id: str = Query(..., description="User ID for the API key"),
|
||||||
|
team_id: str = Query(..., description="Team ID for the API key")
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create a new API key
|
Create a new API key for a specific user and team
|
||||||
|
|
||||||
This endpoint no longer requires authentication - user_id and team_id must be provided
|
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
|
||||||
|
|
||||||
|
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(
|
||||||
{"path": request.url.path, "method": request.method, "key_data": key_data.dict(), "user_id": user_id, "team_id": team_id}
|
{
|
||||||
|
"path": request.url.path,
|
||||||
|
"method": request.method,
|
||||||
|
"key_data": key_data.dict(),
|
||||||
|
"user_id": user_id,
|
||||||
|
"team_id": team_id
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid input for API key creation: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"Resource not found for API key creation: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error creating API key: {e}")
|
||||||
@ -48,34 +76,74 @@ async def create_api_key_for_user(
|
|||||||
user_id: str,
|
user_id: str,
|
||||||
key_data: ApiKeyCreate,
|
key_data: ApiKeyCreate,
|
||||||
request: Request,
|
request: Request,
|
||||||
current_user = Depends(get_current_user)
|
current_user: UserModel = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Create a new API key for a specific user (admin only)
|
Create a new API key for a specific user (admin only)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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(
|
||||||
{"path": request.url.path, "method": request.method, "target_user_id": user_id, "key_data": key_data.dict()},
|
{
|
||||||
|
"path": request.url.path,
|
||||||
|
"method": request.method,
|
||||||
|
"target_user_id": user_id,
|
||||||
|
"key_data": key_data.dict()
|
||||||
|
},
|
||||||
user_id=str(current_user.id),
|
user_id=str(current_user.id),
|
||||||
team_id=str(current_user.team_id)
|
team_id=str(current_user.team_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
|
logger.warning(f"Permission denied for admin API key creation: {e}")
|
||||||
raise HTTPException(status_code=403, detail=str(e))
|
raise HTTPException(status_code=403, detail=str(e))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid input for admin API key creation: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as 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))
|
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}")
|
logger.error(f"Unexpected error creating API key for user: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
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(request: Request, current_user = Depends(get_current_user)):
|
async def list_api_keys(
|
||||||
|
request: Request,
|
||||||
|
current_user: UserModel = Depends(get_current_user)
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
List API keys for the current user
|
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
|
||||||
|
|
||||||
|
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},
|
||||||
@ -85,15 +153,35 @@ async def list_api_keys(request: Request, current_user = Depends(get_current_use
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
response = await auth_service.list_user_api_keys(current_user)
|
response = await auth_service.list_user_api_keys(current_user)
|
||||||
|
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}")
|
logger.error(f"Unexpected error listing API keys: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
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=204)
|
||||||
async def revoke_api_key(key_id: str, request: Request, current_user = Depends(get_current_user)):
|
async def revoke_api_key(
|
||||||
|
key_id: str,
|
||||||
|
request: Request,
|
||||||
|
current_user: UserModel = Depends(get_current_user)
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
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.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key_id: The ID of the API key to revoke
|
||||||
|
current_user: The authenticated user
|
||||||
|
|
||||||
|
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},
|
||||||
@ -103,21 +191,40 @@ async def revoke_api_key(key_id: str, request: Request, current_user = Depends(g
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return None
|
return None
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid input for API key revocation: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"API key not found for revocation: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(e))
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
|
logger.warning(f"Permission denied for API key revocation: {e}")
|
||||||
raise HTTPException(status_code=403, detail=str(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}")
|
logger.error(f"Unexpected error revoking API key: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
raise HTTPException(status_code=500, detail="Internal server error")
|
||||||
|
|
||||||
@router.get("/verify", status_code=200)
|
@router.get("/verify", status_code=200)
|
||||||
async def verify_authentication(request: Request, current_user = Depends(get_current_user)):
|
async def verify_authentication(
|
||||||
|
request: Request,
|
||||||
|
current_user: UserModel = Depends(get_current_user)
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Verify the current authentication (API key)
|
Verify the current authentication status
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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},
|
||||||
@ -127,6 +234,7 @@ async def verify_authentication(request: Request, current_user = Depends(get_cur
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
response = await auth_service.verify_user_authentication(current_user)
|
response = await auth_service.verify_user_authentication(current_user)
|
||||||
|
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}")
|
logger.error(f"Unexpected error verifying authentication: {e}")
|
||||||
|
|||||||
@ -21,26 +21,53 @@ image_service = ImageService()
|
|||||||
@router.post("", response_model=ImageResponse, status_code=201)
|
@router.post("", response_model=ImageResponse, status_code=201)
|
||||||
async def upload_image(
|
async def upload_image(
|
||||||
request: Request,
|
request: Request,
|
||||||
file: UploadFile = File(...),
|
file: UploadFile = File(..., description="Image file to upload"),
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = Query(None, description="Optional description for the image"),
|
||||||
collection_id: Optional[str] = None,
|
collection_id: Optional[str] = Query(None, description="Optional collection ID to associate with the image"),
|
||||||
current_user: UserModel = Depends(get_current_user)
|
current_user: UserModel = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Upload a new image
|
Upload a new image
|
||||||
|
|
||||||
|
Uploads an image file and processes it for storage and indexing.
|
||||||
|
The image will be associated with the current user's team and can
|
||||||
|
optionally be added to a specific collection.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file: The image file to upload (supports common image formats)
|
||||||
|
description: Optional description for the image
|
||||||
|
collection_id: Optional collection ID to organize the image
|
||||||
|
current_user: The authenticated user uploading the image
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ImageResponse: The uploaded image metadata and processing status
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid file format or validation errors
|
||||||
|
500: Upload or processing errors
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "filename": file.filename},
|
{
|
||||||
|
"path": request.url.path,
|
||||||
|
"method": request.method,
|
||||||
|
"filename": file.filename,
|
||||||
|
"content_type": file.content_type,
|
||||||
|
"has_description": description is not None,
|
||||||
|
"collection_id": collection_id
|
||||||
|
},
|
||||||
user_id=str(current_user.id),
|
user_id=str(current_user.id),
|
||||||
team_id=str(current_user.team_id)
|
team_id=str(current_user.team_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid input for image upload: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.error(f"Runtime error during image upload: {e}")
|
||||||
raise HTTPException(status_code=500, detail=str(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}")
|
logger.error(f"Unexpected error uploading image: {e}")
|
||||||
@ -49,34 +76,50 @@ async def upload_image(
|
|||||||
@router.get("", response_model=ImageListResponse)
|
@router.get("", response_model=ImageListResponse)
|
||||||
async def list_images(
|
async def list_images(
|
||||||
request: Request,
|
request: Request,
|
||||||
skip: int = Query(0, ge=0),
|
skip: int = Query(0, ge=0, description="Number of records to skip for pagination"),
|
||||||
limit: int = Query(50, ge=1, le=100),
|
limit: int = Query(50, ge=1, le=100, description="Maximum number of records to return (1-100)"),
|
||||||
collection_id: Optional[str] = None,
|
collection_id: Optional[str] = Query(None, description="Filter by collection ID"),
|
||||||
current_user: UserModel = Depends(get_current_user)
|
current_user: UserModel = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
List images for the current user's team, or all images if user is admin.
|
List images for the current user's team or all images if admin
|
||||||
|
|
||||||
Regular users can only see images from their own team.
|
Retrieves a paginated list of images. Regular users can only see images
|
||||||
Admin users can see all images across all teams.
|
from their own team, while admin users can see all images across all teams.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
skip: Number of records to skip for pagination
|
skip: Number of records to skip for pagination (default: 0)
|
||||||
limit: Maximum number of records to return (1-100)
|
limit: Maximum number of records to return, 1-100 (default: 50)
|
||||||
collection_id: Optional filter by collection ID
|
collection_id: Optional filter by collection ID
|
||||||
|
current_user: The authenticated user
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
List of images with pagination metadata
|
ImageListResponse: Paginated list of images with metadata
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid pagination parameters
|
||||||
|
500: Internal server error
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "skip": skip, "limit": limit, "is_admin": current_user.is_admin},
|
{
|
||||||
|
"path": request.url.path,
|
||||||
|
"method": request.method,
|
||||||
|
"skip": skip,
|
||||||
|
"limit": limit,
|
||||||
|
"is_admin": current_user.is_admin,
|
||||||
|
"collection_id": collection_id
|
||||||
|
},
|
||||||
user_id=str(current_user.id),
|
user_id=str(current_user.id),
|
||||||
team_id=str(current_user.team_id)
|
team_id=str(current_user.team_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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})")
|
||||||
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}")
|
logger.error(f"Unexpected error listing images: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
raise HTTPException(status_code=500, detail="Internal server error")
|
||||||
@ -89,21 +132,46 @@ async def get_image(
|
|||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Get image metadata by ID
|
Get image metadata by ID
|
||||||
|
|
||||||
|
Retrieves detailed metadata for a specific image. Users can only
|
||||||
|
access images from their own team unless they are admin.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_id: The image ID to retrieve
|
||||||
|
current_user: The authenticated user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ImageResponse: Complete image metadata
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid image ID format
|
||||||
|
403: Insufficient permissions to access this image
|
||||||
|
404: Image not found
|
||||||
|
500: Internal server error
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "image_id": image_id, "is_admin": current_user.is_admin},
|
{
|
||||||
|
"path": request.url.path,
|
||||||
|
"method": request.method,
|
||||||
|
"image_id": image_id,
|
||||||
|
"is_admin": current_user.is_admin
|
||||||
|
},
|
||||||
user_id=str(current_user.id),
|
user_id=str(current_user.id),
|
||||||
team_id=str(current_user.team_id)
|
team_id=str(current_user.team_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid image ID: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"Image not found: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(e))
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
|
logger.warning(f"Permission denied for image access: {e}")
|
||||||
raise HTTPException(status_code=403, detail=str(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}")
|
logger.error(f"Unexpected error getting image: {e}")
|
||||||
@ -117,9 +185,30 @@ async def download_image(
|
|||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Download image file
|
Download image file
|
||||||
|
|
||||||
|
Downloads the actual image file. Users can only download images
|
||||||
|
from their own team unless they are admin.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_id: The image ID to download
|
||||||
|
current_user: The authenticated user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
StreamingResponse: The image file as a download
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid image ID format
|
||||||
|
403: Insufficient permissions to download this image
|
||||||
|
404: Image not found
|
||||||
|
500: Internal server error
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "image_id": image_id, "is_admin": current_user.is_admin},
|
{
|
||||||
|
"path": request.url.path,
|
||||||
|
"method": request.method,
|
||||||
|
"image_id": image_id,
|
||||||
|
"is_admin": current_user.is_admin
|
||||||
|
},
|
||||||
user_id=str(current_user.id),
|
user_id=str(current_user.id),
|
||||||
team_id=str(current_user.team_id)
|
team_id=str(current_user.team_id)
|
||||||
)
|
)
|
||||||
@ -127,6 +216,8 @@ async def download_image(
|
|||||||
try:
|
try:
|
||||||
file_content, content_type, filename = await image_service.download_image(image_id, current_user)
|
file_content, content_type, filename = await image_service.download_image(image_id, current_user)
|
||||||
|
|
||||||
|
logger.info(f"Image {image_id} downloaded by user {current_user.id}")
|
||||||
|
|
||||||
# Return file as streaming response
|
# Return file as streaming response
|
||||||
return StreamingResponse(
|
return StreamingResponse(
|
||||||
io.BytesIO(file_content),
|
io.BytesIO(file_content),
|
||||||
@ -134,10 +225,13 @@ async def download_image(
|
|||||||
headers={"Content-Disposition": f"attachment; filename={filename}"}
|
headers={"Content-Disposition": f"attachment; filename={filename}"}
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid image ID for download: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"Image not found for download: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(e))
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
|
logger.warning(f"Permission denied for image download: {e}")
|
||||||
raise HTTPException(status_code=403, detail=str(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}")
|
logger.error(f"Unexpected error downloading image: {e}")
|
||||||
@ -152,21 +246,48 @@ async def update_image(
|
|||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Update image metadata
|
Update image metadata
|
||||||
|
|
||||||
|
Updates the metadata for a specific image. Users can only update
|
||||||
|
images from their own team unless they are admin.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_id: The image ID to update
|
||||||
|
image_data: The image update data
|
||||||
|
current_user: The authenticated user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ImageResponse: Updated image metadata
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid image ID format or validation errors
|
||||||
|
403: Insufficient permissions to update this image
|
||||||
|
404: Image not found
|
||||||
|
500: Internal server error
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "image_id": image_id, "is_admin": current_user.is_admin},
|
{
|
||||||
|
"path": request.url.path,
|
||||||
|
"method": request.method,
|
||||||
|
"image_id": image_id,
|
||||||
|
"is_admin": current_user.is_admin,
|
||||||
|
"update_data": image_data.dict()
|
||||||
|
},
|
||||||
user_id=str(current_user.id),
|
user_id=str(current_user.id),
|
||||||
team_id=str(current_user.team_id)
|
team_id=str(current_user.team_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid input for image update: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"Image not found for update: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(e))
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
|
logger.warning(f"Permission denied for image update: {e}")
|
||||||
raise HTTPException(status_code=403, detail=str(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}")
|
logger.error(f"Unexpected error updating image: {e}")
|
||||||
@ -180,21 +301,46 @@ async def delete_image(
|
|||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Delete an image
|
Delete an image
|
||||||
|
|
||||||
|
Permanently removes an image and its associated data. Users can only
|
||||||
|
delete images from their own team unless they are admin.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_id: The image ID to delete
|
||||||
|
current_user: The authenticated user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None (204 No Content)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid image ID format
|
||||||
|
403: Insufficient permissions to delete this image
|
||||||
|
404: Image not found
|
||||||
|
500: Internal server error
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "image_id": image_id, "is_admin": current_user.is_admin},
|
{
|
||||||
|
"path": request.url.path,
|
||||||
|
"method": request.method,
|
||||||
|
"image_id": image_id,
|
||||||
|
"is_admin": current_user.is_admin
|
||||||
|
},
|
||||||
user_id=str(current_user.id),
|
user_id=str(current_user.id),
|
||||||
team_id=str(current_user.team_id)
|
team_id=str(current_user.team_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return None
|
return None
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid image ID for deletion: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"Image not found for deletion: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(e))
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
except PermissionError as e:
|
except PermissionError as e:
|
||||||
|
logger.warning(f"Permission denied for image deletion: {e}")
|
||||||
raise HTTPException(status_code=403, detail=str(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}")
|
logger.error(f"Unexpected error deleting image: {e}")
|
||||||
|
|||||||
@ -18,14 +18,32 @@ search_service = SearchService()
|
|||||||
@router.get("", response_model=SearchResponse)
|
@router.get("", response_model=SearchResponse)
|
||||||
async def search_images(
|
async def search_images(
|
||||||
request: Request,
|
request: Request,
|
||||||
q: str = Query(..., description="Search query"),
|
q: str = Query(..., description="Search query for semantic image search"),
|
||||||
limit: int = Query(10, ge=1, le=50, description="Number of results to return"),
|
limit: int = Query(10, ge=1, le=50, description="Number of results to return (1-50)"),
|
||||||
similarity_threshold: float = Query(0.65, ge=0.0, le=1.0, description="Similarity threshold"),
|
similarity_threshold: float = Query(0.65, ge=0.0, le=1.0, description="Similarity threshold (0.0-1.0)"),
|
||||||
collection_id: Optional[str] = Query(None, description="Filter by collection ID"),
|
collection_id: Optional[str] = Query(None, description="Filter results by collection ID"),
|
||||||
current_user: UserModel = Depends(get_current_user)
|
current_user: UserModel = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Search for images using semantic similarity
|
Search for images using semantic similarity
|
||||||
|
|
||||||
|
Performs a semantic search across images using AI-powered similarity matching.
|
||||||
|
Regular users can only search within their team's images, while admin users
|
||||||
|
can search across all teams.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
q: The search query text to find similar images
|
||||||
|
limit: Maximum number of results to return (1-50, default: 10)
|
||||||
|
similarity_threshold: Minimum similarity score (0.0-1.0, default: 0.65)
|
||||||
|
collection_id: Optional filter to search within a specific collection
|
||||||
|
current_user: The authenticated user performing the search
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SearchResponse: List of matching images with similarity scores
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid search parameters or query format
|
||||||
|
500: Search service errors
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{
|
{
|
||||||
@ -33,7 +51,9 @@ async def search_images(
|
|||||||
"method": request.method,
|
"method": request.method,
|
||||||
"query": q,
|
"query": q,
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
"similarity_threshold": similarity_threshold
|
"similarity_threshold": similarity_threshold,
|
||||||
|
"collection_id": collection_id,
|
||||||
|
"is_admin": current_user.is_admin
|
||||||
},
|
},
|
||||||
user_id=str(current_user.id),
|
user_id=str(current_user.id),
|
||||||
team_id=str(current_user.team_id)
|
team_id=str(current_user.team_id)
|
||||||
@ -48,10 +68,13 @@ async def search_images(
|
|||||||
similarity_threshold=similarity_threshold,
|
similarity_threshold=similarity_threshold,
|
||||||
collection_id=collection_id
|
collection_id=collection_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:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid search parameters: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.error(f"Search service error: {e}")
|
||||||
raise HTTPException(status_code=500, detail=str(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}")
|
logger.error(f"Unexpected error in search: {e}")
|
||||||
@ -64,13 +87,29 @@ async def search_images_advanced(
|
|||||||
current_user: UserModel = Depends(get_current_user)
|
current_user: UserModel = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Advanced search for images with more options
|
Advanced search for images with extended options
|
||||||
|
|
||||||
|
Provides advanced search capabilities with more filtering and configuration
|
||||||
|
options than the basic search endpoint. Supports complex queries and
|
||||||
|
multiple search parameters.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
search_request: Advanced search request with detailed parameters
|
||||||
|
current_user: The authenticated user performing the search
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SearchResponse: List of matching images with similarity scores and metadata
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid search request or validation errors
|
||||||
|
500: Search service errors
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{
|
{
|
||||||
"path": request.url.path,
|
"path": request.url.path,
|
||||||
"method": request.method,
|
"method": request.method,
|
||||||
"search_request": search_request.dict()
|
"search_request": search_request.dict(),
|
||||||
|
"is_admin": current_user.is_admin
|
||||||
},
|
},
|
||||||
user_id=str(current_user.id),
|
user_id=str(current_user.id),
|
||||||
team_id=str(current_user.team_id)
|
team_id=str(current_user.team_id)
|
||||||
@ -82,10 +121,13 @@ async def search_images_advanced(
|
|||||||
user=current_user,
|
user=current_user,
|
||||||
request=request
|
request=request
|
||||||
)
|
)
|
||||||
|
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:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid advanced search request: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.error(f"Advanced search service error: {e}")
|
||||||
raise HTTPException(status_code=500, detail=str(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}")
|
logger.error(f"Unexpected error in advanced search: {e}")
|
||||||
|
|||||||
@ -14,11 +14,25 @@ router = APIRouter(tags=["Teams"], prefix="/teams")
|
|||||||
team_service = TeamService()
|
team_service = TeamService()
|
||||||
|
|
||||||
@router.post("", response_model=TeamResponse, status_code=201)
|
@router.post("", response_model=TeamResponse, status_code=201)
|
||||||
async def create_team(team_data: TeamCreate, request: Request):
|
async def create_team(
|
||||||
|
team_data: TeamCreate,
|
||||||
|
request: Request
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Create a new team
|
Create a new team
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Creates a new team with the provided information. Teams are used to
|
||||||
|
organize users and control access to resources.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_data: Team creation data including name and description
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
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()}
|
||||||
@ -26,7 +40,11 @@ async def create_team(team_data: TeamCreate, request: Request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
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}")
|
logger.error(f"Unexpected error creating team: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
raise HTTPException(status_code=500, detail="Internal server error")
|
||||||
@ -36,7 +54,14 @@ async def list_teams(request: Request):
|
|||||||
"""
|
"""
|
||||||
List all teams
|
List all teams
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Retrieves a complete list of all teams in the system with their
|
||||||
|
basic information and member counts.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
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}
|
||||||
@ -44,17 +69,33 @@ async def list_teams(request: Request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
response = await team_service.list_teams()
|
response = await team_service.list_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}")
|
logger.error(f"Unexpected error listing teams: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
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(team_id: str, request: Request):
|
async def get_team(
|
||||||
|
team_id: str,
|
||||||
|
request: Request
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Get a team by ID
|
Get a team by ID
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Retrieves detailed information for a specific team including
|
||||||
|
member count and team settings.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_id: The team ID to retrieve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
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}
|
||||||
@ -62,21 +103,41 @@ async def get_team(team_id: str, request: Request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
response = await team_service.get_team(team_id)
|
response = await team_service.get_team(team_id)
|
||||||
|
logger.info(f"Retrieved team {team_id}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid team ID: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"Team not found: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error getting team: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
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(team_id: str, team_data: TeamUpdate, request: Request):
|
async def update_team(
|
||||||
|
team_id: str,
|
||||||
|
team_data: TeamUpdate,
|
||||||
|
request: Request
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Update a team
|
Update a team
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Updates the specified team's information. Only the provided fields
|
||||||
|
will be updated, others remain unchanged.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_id: The team ID to update
|
||||||
|
team_data: The team update data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
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()}
|
||||||
@ -84,21 +145,39 @@ async def update_team(team_id: str, team_data: TeamUpdate, request: Request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid input for team update: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"Team not found for update: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error updating team: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Internal server error")
|
raise HTTPException(status_code=500, detail="Internal server error")
|
||||||
|
|
||||||
@router.delete("/{team_id}", status_code=204)
|
@router.delete("/{team_id}", status_code=204)
|
||||||
async def delete_team(team_id: str, request: Request):
|
async def delete_team(
|
||||||
|
team_id: str,
|
||||||
|
request: Request
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Delete a team
|
Delete a team
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Permanently removes a team from the system. This action cannot be undone.
|
||||||
|
All users associated with this team should be reassigned before deletion.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_id: The team ID to delete
|
||||||
|
|
||||||
|
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(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "team_id": team_id}
|
{"path": request.url.path, "method": request.method, "team_id": team_id}
|
||||||
@ -106,10 +185,13 @@ async def delete_team(team_id: str, request: Request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await team_service.delete_team(team_id)
|
await team_service.delete_team(team_id)
|
||||||
|
logger.info(f"Deleted team {team_id}")
|
||||||
return None
|
return None
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid team ID or team has dependencies: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"Team not found for deletion: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error deleting team: {e}")
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
from fastapi import APIRouter, Depends, HTTPException, Request, Query
|
||||||
|
|
||||||
from src.services.user_service import UserService
|
from src.services.user_service import UserService
|
||||||
from src.schemas.user import UserResponse, UserListResponse, UserCreate, UserUpdate
|
from src.schemas.user import UserResponse, UserListResponse, UserCreate, UserUpdate
|
||||||
@ -16,19 +16,38 @@ user_service = UserService()
|
|||||||
@router.get("/me", response_model=UserResponse)
|
@router.get("/me", response_model=UserResponse)
|
||||||
async def read_users_me(
|
async def read_users_me(
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: str # Now requires user_id as a query parameter
|
user_id: str = Query(..., description="User ID to retrieve information for")
|
||||||
):
|
):
|
||||||
"""Get user information by user ID"""
|
"""
|
||||||
|
Get user information by user ID
|
||||||
|
|
||||||
|
Retrieves detailed information for a specific user. This endpoint
|
||||||
|
requires the user_id as a query parameter.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: The user ID to retrieve information for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserResponse: Complete user information including profile data
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid user ID format
|
||||||
|
404: User not found
|
||||||
|
500: Internal server error
|
||||||
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "user_id": user_id}
|
{"path": request.url.path, "method": request.method, "user_id": user_id}
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid user ID provided: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"User not found: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error getting user: {e}")
|
||||||
@ -38,19 +57,39 @@ async def read_users_me(
|
|||||||
async def update_current_user(
|
async def update_current_user(
|
||||||
user_data: UserUpdate,
|
user_data: UserUpdate,
|
||||||
request: Request,
|
request: Request,
|
||||||
user_id: str # Now requires user_id as a query parameter
|
user_id: str = Query(..., description="User ID to update")
|
||||||
):
|
):
|
||||||
"""Update user information by user ID"""
|
"""
|
||||||
|
Update user information by user ID
|
||||||
|
|
||||||
|
Updates the specified user's profile information. Only provided fields
|
||||||
|
will be updated, others will remain unchanged.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_data: The user update data containing fields to modify
|
||||||
|
user_id: The user ID to update
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserResponse: Updated user information
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid user ID format or validation errors
|
||||||
|
404: User not found
|
||||||
|
500: Internal server error
|
||||||
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "user_data": user_data.dict(), "user_id": user_id}
|
{"path": request.url.path, "method": request.method, "user_data": user_data.dict(), "user_id": user_id}
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid input for user update: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"User not found for update: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error updating user: {e}")
|
||||||
@ -64,7 +103,19 @@ async def create_user(
|
|||||||
"""
|
"""
|
||||||
Create a new user
|
Create a new user
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Creates a new user account with the provided information. The user
|
||||||
|
will be associated with the specified team.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_data: User creation data including name, email, and team assignment
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserResponse: The created user information
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid input data or user already exists
|
||||||
|
404: Referenced team not found
|
||||||
|
500: Internal server error
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "user_data": user_data.dict()}
|
{"path": request.url.path, "method": request.method, "user_data": user_data.dict()}
|
||||||
@ -72,10 +123,13 @@ async def create_user(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid input for user creation: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"Resource not found for user creation: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error creating user: {e}")
|
||||||
@ -84,12 +138,23 @@ async def create_user(
|
|||||||
@router.get("", response_model=UserListResponse)
|
@router.get("", response_model=UserListResponse)
|
||||||
async def list_users(
|
async def list_users(
|
||||||
request: Request,
|
request: Request,
|
||||||
team_id: Optional[str] = None
|
team_id: Optional[str] = Query(None, description="Filter users by team ID")
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
List users
|
List users with optional team filtering
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Retrieves a list of all users in the system. Can be filtered by team
|
||||||
|
to show only users belonging to a specific team.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
team_id: Optional team ID to filter users by
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserListResponse: List of users with total count
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid team ID format
|
||||||
|
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}
|
||||||
@ -97,8 +162,10 @@ async def list_users(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
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 ""))
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid team ID for user listing: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(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}")
|
logger.error(f"Unexpected error listing users: {e}")
|
||||||
@ -112,7 +179,18 @@ async def get_user(
|
|||||||
"""
|
"""
|
||||||
Get user by ID
|
Get user by ID
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Retrieves detailed information for a specific user by their ID.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: The user ID to retrieve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserResponse: Complete user information
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid user ID format
|
||||||
|
404: User not found
|
||||||
|
500: Internal server error
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "user_id": user_id}
|
{"path": request.url.path, "method": request.method, "user_id": user_id}
|
||||||
@ -120,10 +198,13 @@ async def get_user(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
response = await user_service.get_user(user_id)
|
response = await user_service.get_user(user_id)
|
||||||
|
logger.info(f"Retrieved user {user_id}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid user ID: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"User not found: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error getting user: {e}")
|
||||||
@ -138,7 +219,20 @@ async def update_user(
|
|||||||
"""
|
"""
|
||||||
Update user by ID
|
Update user by ID
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Updates a specific user's information. Only the provided fields
|
||||||
|
will be updated, others remain unchanged.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: The user ID to update
|
||||||
|
user_data: The user update data
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UserResponse: Updated user information
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid user ID format or validation errors
|
||||||
|
404: User not found
|
||||||
|
500: Internal server error
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "user_id": user_id, "user_data": user_data.dict()}
|
{"path": request.url.path, "method": request.method, "user_id": user_id, "user_data": user_data.dict()}
|
||||||
@ -146,10 +240,13 @@ async def update_user(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
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}")
|
||||||
return response
|
return response
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid input for user update: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"User not found for update: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error updating user: {e}")
|
||||||
@ -163,7 +260,18 @@ async def delete_user(
|
|||||||
"""
|
"""
|
||||||
Delete user by ID
|
Delete user by ID
|
||||||
|
|
||||||
This endpoint no longer requires authentication
|
Permanently removes a user from the system. This action cannot be undone.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user_id: The user ID to delete
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None (204 No Content)
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
400: Invalid user ID format
|
||||||
|
404: User not found
|
||||||
|
500: Internal server error
|
||||||
"""
|
"""
|
||||||
log_request(
|
log_request(
|
||||||
{"path": request.url.path, "method": request.method, "user_id": user_id}
|
{"path": request.url.path, "method": request.method, "user_id": user_id}
|
||||||
@ -171,10 +279,13 @@ async def delete_user(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await user_service.delete_user(user_id)
|
await user_service.delete_user(user_id)
|
||||||
|
logger.info(f"Deleted user {user_id}")
|
||||||
return None
|
return None
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
logger.warning(f"Invalid user ID for deletion: {e}")
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
|
logger.warning(f"User not found for deletion: {e}")
|
||||||
raise HTTPException(status_code=404, detail=str(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}")
|
logger.error(f"Unexpected error deleting user: {e}")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user