REFACTOR ALL APIs

This commit is contained in:
Urtzi Alfaro
2025-10-06 15:27:01 +02:00
parent dc8221bd2f
commit 38fb98bc27
166 changed files with 18454 additions and 13605 deletions

View File

@@ -1,6 +1,6 @@
"""
Enhanced Authentication API Endpoints
Updated to use repository pattern with dependency injection and improved error handling
Authentication Operations API Endpoints
Business logic for login, register, token refresh, password reset, and email verification
"""
from fastapi import APIRouter, Depends, HTTPException, status, Request
@@ -15,11 +15,13 @@ from app.services.auth_service import EnhancedAuthService
from shared.database.base import create_database_manager
from shared.monitoring.decorators import track_execution_time
from shared.monitoring.metrics import get_metrics_collector
from shared.routing import RouteBuilder
from app.core.config import settings
logger = structlog.get_logger()
router = APIRouter(tags=["enhanced-auth"])
router = APIRouter(tags=["auth-operations"])
security = HTTPBearer()
route_builder = RouteBuilder('auth')
def get_auth_service():
@@ -28,7 +30,7 @@ def get_auth_service():
return EnhancedAuthService(database_manager)
@router.post("/register", response_model=TokenResponse)
@router.post(route_builder.build_base_route("register", include_tenant_prefix=False), response_model=TokenResponse)
@track_execution_time("enhanced_registration_duration_seconds", "auth-service")
async def register(
user_data: UserRegistration,
@@ -37,10 +39,10 @@ async def register(
):
"""Register new user using enhanced repository pattern"""
metrics = get_metrics_collector(request)
logger.info("Registration attempt using repository pattern",
email=user_data.email)
try:
# Enhanced input validation
if not user_data.email or not user_data.email.strip():
@@ -48,57 +50,57 @@ async def register(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email is required"
)
if not user_data.password or len(user_data.password) < 8:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Password must be at least 8 characters long"
)
if not user_data.full_name or not user_data.full_name.strip():
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Full name is required"
)
# Register user using enhanced service
result = await auth_service.register_user(user_data)
# Record successful registration
if metrics:
metrics.increment_counter("enhanced_registration_total", labels={"status": "success"})
logger.info("Registration successful using repository pattern",
user_id=result.user.id,
email=user_data.email)
return result
except HTTPException as e:
if metrics:
error_type = "validation_error" if e.status_code == 400 else "conflict" if e.status_code == 409 else "failed"
metrics.increment_counter("enhanced_registration_total", labels={"status": error_type})
logger.warning("Registration failed using repository pattern",
email=user_data.email,
error=e.detail)
raise
except Exception as e:
if metrics:
metrics.increment_counter("enhanced_registration_total", labels={"status": "error"})
logger.error("Registration system error using repository pattern",
email=user_data.email,
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Registration failed"
)
@router.post("/login", response_model=TokenResponse)
@router.post(route_builder.build_base_route("login", include_tenant_prefix=False), response_model=TokenResponse)
@track_execution_time("enhanced_login_duration_seconds", "auth-service")
async def login(
login_data: UserLogin,
@@ -107,10 +109,10 @@ async def login(
):
"""Login user using enhanced repository pattern"""
metrics = get_metrics_collector(request)
logger.info("Login attempt using repository pattern",
email=login_data.email)
try:
# Enhanced input validation
if not login_data.email or not login_data.email.strip():
@@ -118,51 +120,51 @@ async def login(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Email is required"
)
if not login_data.password:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Password is required"
)
# Login using enhanced service
result = await auth_service.login_user(login_data)
# Record successful login
if metrics:
metrics.increment_counter("enhanced_login_success_total")
logger.info("Login successful using repository pattern",
user_id=result.user.id,
email=login_data.email)
return result
except HTTPException as e:
if metrics:
reason = "validation_error" if e.status_code == 400 else "auth_failed"
metrics.increment_counter("enhanced_login_failure_total", labels={"reason": reason})
logger.warning("Login failed using repository pattern",
email=login_data.email,
error=e.detail)
raise
except Exception as e:
if metrics:
metrics.increment_counter("enhanced_login_failure_total", labels={"reason": "error"})
logger.error("Login system error using repository pattern",
email=login_data.email,
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Login failed"
)
@router.post("/refresh")
@router.post(route_builder.build_base_route("refresh", include_tenant_prefix=False))
@track_execution_time("enhanced_token_refresh_duration_seconds", "auth-service")
async def refresh_token(
refresh_data: RefreshTokenRequest,
@@ -171,24 +173,24 @@ async def refresh_token(
):
"""Refresh access token using repository pattern"""
metrics = get_metrics_collector(request)
try:
result = await auth_service.refresh_access_token(refresh_data.refresh_token)
# Record successful refresh
if metrics:
metrics.increment_counter("enhanced_token_refresh_success_total")
logger.debug("Access token refreshed using repository pattern")
return result
except HTTPException as e:
if metrics:
metrics.increment_counter("enhanced_token_refresh_failure_total")
logger.warning("Token refresh failed using repository pattern", error=e.detail)
raise
except Exception as e:
if metrics:
metrics.increment_counter("enhanced_token_refresh_failure_total")
@@ -199,7 +201,7 @@ async def refresh_token(
)
@router.post("/verify")
@router.post(route_builder.build_base_route("verify", include_tenant_prefix=False))
@track_execution_time("enhanced_token_verify_duration_seconds", "auth-service")
async def verify_token(
credentials: HTTPAuthorizationCredentials = Depends(security),
@@ -208,20 +210,20 @@ async def verify_token(
):
"""Verify access token using repository pattern"""
metrics = get_metrics_collector(request) if request else None
try:
if not credentials:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication required"
)
result = await auth_service.verify_user_token(credentials.credentials)
# Record successful verification
if metrics:
metrics.increment_counter("enhanced_token_verify_success_total")
return {
"valid": True,
"user_id": result.get("user_id"),
@@ -230,13 +232,13 @@ async def verify_token(
"exp": result.get("exp"),
"message": None
}
except HTTPException as e:
if metrics:
metrics.increment_counter("enhanced_token_verify_failure_total")
logger.warning("Token verification failed using repository pattern", error=e.detail)
raise
except Exception as e:
if metrics:
metrics.increment_counter("enhanced_token_verify_failure_total")
@@ -247,7 +249,7 @@ async def verify_token(
)
@router.post("/logout")
@router.post(route_builder.build_base_route("logout", include_tenant_prefix=False))
@track_execution_time("enhanced_logout_duration_seconds", "auth-service")
async def logout(
refresh_data: RefreshTokenRequest,
@@ -257,30 +259,30 @@ async def logout(
):
"""Logout user using repository pattern"""
metrics = get_metrics_collector(request)
try:
# Verify token to get user_id
payload = await auth_service.verify_user_token(credentials.credentials)
user_id = payload.get("user_id")
if not user_id:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
success = await auth_service.logout_user(user_id, refresh_data.refresh_token)
if metrics:
status_label = "success" if success else "failed"
metrics.increment_counter("enhanced_logout_total", labels={"status": status_label})
logger.info("Logout using repository pattern",
user_id=user_id,
success=success)
return {"message": "Logout successful" if success else "Logout failed"}
except HTTPException:
raise
except Exception as e:
@@ -293,7 +295,7 @@ async def logout(
)
@router.post("/change-password")
@router.post(route_builder.build_base_route("change-password", include_tenant_prefix=False))
async def change_password(
password_data: PasswordChange,
credentials: HTTPAuthorizationCredentials = Depends(security),
@@ -302,48 +304,48 @@ async def change_password(
):
"""Change user password using repository pattern"""
metrics = get_metrics_collector(request) if request else None
try:
if not credentials:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication required"
)
# Verify current token
payload = await auth_service.verify_user_token(credentials.credentials)
user_id = payload.get("user_id")
if not user_id:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
# Validate new password length
if len(password_data.new_password) < 8:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="New password must be at least 8 characters long"
)
# Change password using enhanced service
success = await auth_service.change_password(
user_id,
password_data.current_password,
password_data.new_password
)
if metrics:
status_label = "success" if success else "failed"
metrics.increment_counter("enhanced_password_change_total", labels={"status": status_label})
logger.info("Password changed using repository pattern",
user_id=user_id,
success=success)
return {"message": "Password changed successfully"}
except HTTPException:
raise
except Exception as e:
@@ -356,7 +358,7 @@ async def change_password(
)
@router.get("/profile", response_model=UserResponse)
@router.get(route_builder.build_base_route("profile", include_tenant_prefix=False), response_model=UserResponse)
async def get_profile(
credentials: HTTPAuthorizationCredentials = Depends(security),
auth_service: EnhancedAuthService = Depends(get_auth_service)
@@ -368,17 +370,17 @@ async def get_profile(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication required"
)
# Verify token and get user_id
payload = await auth_service.verify_user_token(credentials.credentials)
user_id = payload.get("user_id")
if not user_id:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
# Get user profile using enhanced service
profile = await auth_service.get_user_profile(user_id)
if not profile:
@@ -386,9 +388,9 @@ async def get_profile(
status_code=status.HTTP_404_NOT_FOUND,
detail="User profile not found"
)
return profile
except HTTPException:
raise
except Exception as e:
@@ -399,7 +401,7 @@ async def get_profile(
)
@router.put("/profile", response_model=UserResponse)
@router.put(route_builder.build_base_route("profile", include_tenant_prefix=False), response_model=UserResponse)
async def update_profile(
update_data: dict,
credentials: HTTPAuthorizationCredentials = Depends(security),
@@ -412,17 +414,17 @@ async def update_profile(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication required"
)
# Verify token and get user_id
payload = await auth_service.verify_user_token(credentials.credentials)
user_id = payload.get("user_id")
if not user_id:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
# Update profile using enhanced service
updated_profile = await auth_service.update_user_profile(user_id, update_data)
if not updated_profile:
@@ -430,13 +432,13 @@ async def update_profile(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
logger.info("Profile updated using repository pattern",
user_id=user_id,
updated_fields=list(update_data.keys()))
return updated_profile
except HTTPException:
raise
except Exception as e:
@@ -447,7 +449,7 @@ async def update_profile(
)
@router.post("/verify-email")
@router.post(route_builder.build_base_route("verify-email", include_tenant_prefix=False))
async def verify_email(
user_id: str,
verification_token: str,
@@ -456,13 +458,13 @@ async def verify_email(
"""Verify user email using repository pattern"""
try:
success = await auth_service.verify_user_email(user_id, verification_token)
logger.info("Email verification using repository pattern",
user_id=user_id,
success=success)
return {"message": "Email verified successfully" if success else "Email verification failed"}
except Exception as e:
logger.error("Email verification error using repository pattern", error=str(e))
raise HTTPException(
@@ -471,7 +473,7 @@ async def verify_email(
)
@router.post("/reset-password")
@router.post(route_builder.build_base_route("reset-password", include_tenant_prefix=False))
async def reset_password(
reset_data: PasswordReset,
request: Request,
@@ -479,19 +481,19 @@ async def reset_password(
):
"""Request password reset using repository pattern"""
metrics = get_metrics_collector(request)
try:
# In a full implementation, you'd send an email with a reset token
# For now, just log the request
if metrics:
metrics.increment_counter("enhanced_password_reset_total", labels={"status": "requested"})
logger.info("Password reset requested using repository pattern",
email=reset_data.email)
return {"message": "Password reset email sent if account exists"}
except Exception as e:
if metrics:
metrics.increment_counter("enhanced_password_reset_total", labels={"status": "error"})
@@ -502,7 +504,7 @@ async def reset_password(
)
@router.get("/health")
@router.get(route_builder.build_base_route("health", include_tenant_prefix=False))
async def health_check():
"""Health check endpoint for enhanced auth service"""
return {
@@ -510,4 +512,4 @@ async def health_check():
"service": "enhanced-auth-service",
"version": "2.0.0",
"features": ["repository-pattern", "dependency-injection", "enhanced-error-handling"]
}
}

View File

@@ -13,9 +13,11 @@ from app.core.database import get_db
from app.services.user_service import UserService
from app.repositories.onboarding_repository import OnboardingRepository
from shared.auth.decorators import get_current_user_dep
from shared.routing import RouteBuilder
logger = structlog.get_logger()
router = APIRouter(tags=["onboarding"])
route_builder = RouteBuilder('auth')
# Request/Response Models
class OnboardingStepStatus(BaseModel):
@@ -354,7 +356,7 @@ class OnboardingService:
# API Routes
@router.get("/me/onboarding/progress", response_model=UserProgress)
@router.get(route_builder.build_base_route("me/onboarding/progress", include_tenant_prefix=False), response_model=UserProgress)
async def get_user_progress(
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
@@ -373,7 +375,7 @@ async def get_user_progress(
detail="Failed to get onboarding progress"
)
@router.get("/{user_id}/onboarding/progress", response_model=UserProgress)
@router.get(route_builder.build_base_route("{user_id}/onboarding/progress", include_tenant_prefix=False), response_model=UserProgress)
async def get_user_progress_by_id(
user_id: str,
current_user: Dict[str, Any] = Depends(get_current_user_dep),
@@ -406,7 +408,7 @@ async def get_user_progress_by_id(
detail="Failed to get onboarding progress"
)
@router.put("/me/onboarding/step", response_model=UserProgress)
@router.put(route_builder.build_base_route("me/onboarding/step", include_tenant_prefix=False), response_model=UserProgress)
async def update_onboarding_step(
update_request: UpdateStepRequest,
current_user: Dict[str, Any] = Depends(get_current_user_dep),
@@ -431,7 +433,7 @@ async def update_onboarding_step(
detail="Failed to update onboarding step"
)
@router.get("/me/onboarding/next-step")
@router.get(route_builder.build_base_route("me/onboarding/next-step", include_tenant_prefix=False))
async def get_next_step(
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
@@ -450,7 +452,7 @@ async def get_next_step(
detail="Failed to get next step"
)
@router.get("/me/onboarding/can-access/{step_name}")
@router.get(route_builder.build_base_route("me/onboarding/can-access/{step_name}", include_tenant_prefix=False))
async def can_access_step(
step_name: str,
current_user: Dict[str, Any] = Depends(get_current_user_dep),
@@ -473,7 +475,7 @@ async def can_access_step(
detail="Failed to check step access"
)
@router.post("/me/onboarding/complete")
@router.post(route_builder.build_base_route("me/onboarding/complete", include_tenant_prefix=False))
async def complete_onboarding(
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)

View File

@@ -13,7 +13,7 @@ from app.core.database import get_db, get_background_db_session
from app.schemas.auth import UserResponse, PasswordChange
from app.schemas.users import UserUpdate
from app.services.user_service import UserService
from app.models.users import User
from app.models.users import User
from sqlalchemy.ext.asyncio import AsyncSession
@@ -24,12 +24,14 @@ from shared.auth.decorators import (
get_current_user_dep,
require_admin_role_dep
)
from shared.routing import RouteBuilder
logger = structlog.get_logger()
router = APIRouter(tags=["users"])
route_builder = RouteBuilder('auth')
@router.get("/me", response_model=UserResponse)
@router.get(route_builder.build_base_route("me", include_tenant_prefix=False), response_model=UserResponse)
async def get_current_user_info(
current_user: Dict[str, Any] = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
@@ -97,7 +99,7 @@ async def get_current_user_info(
detail="Failed to get user information"
)
@router.put("/me", response_model=UserResponse)
@router.put(route_builder.build_base_route("me", include_tenant_prefix=False), response_model=UserResponse)
async def update_current_user(
user_update: UserUpdate,
current_user: Dict[str, Any] = Depends(get_current_user_dep),
@@ -144,7 +146,7 @@ async def update_current_user(
detail="Failed to update user"
)
@router.delete("/delete/{user_id}")
@router.delete(route_builder.build_base_route("delete/{user_id}", include_tenant_prefix=False))
async def delete_admin_user(
background_tasks: BackgroundTasks,
user_id: str = Path(..., description="User ID"),
@@ -220,7 +222,7 @@ async def execute_admin_user_deletion(user_id: str, requesting_user_id: str):
result=result)
@router.get("/delete/{user_id}/deletion-preview")
@router.get(route_builder.build_base_route("delete/{user_id}/deletion-preview", include_tenant_prefix=False))
async def preview_user_deletion(
user_id: str = Path(..., description="User ID"),
db: AsyncSession = Depends(get_db)