from typing import Tuple, Optionalfrom ...application.dto.auth_dto import LoginRequestDTO, LoginResponseDTOfrom ...application.interfaces.repositories.user_repository import IUserRepositoryfrom ...application.interfaces.services.token_service import ITokenServicefrom ...domain.exceptions.auth_exceptions import InvalidCredentialsExceptionclass LoginUserUseCase: """Caso de uso: Login de usuario""" def __init__( self, user_repository: IUserRepository, token_service: ITokenService, password_hasher: Any ): self.user_repository = user_repository self.token_service = token_service self.password_hasher = password_hasher def execute(self, request: LoginRequestDTO) -> Tuple[Optional[LoginResponseDTO], Optional[str]]: # 1. Buscar usuario user = self.user_repository.find_by_email(request.email) if not user: raise UserNotFoundException() # 2. Verificar si está bloqueado if user.is_locked(): raise AccountLockedException() # 3. Verificar password if not self.password_hasher.verify(request.password, user.hashed_password): user.login_failed() self.user_repository.save(user) raise InvalidCredentialsException() # 4. Login exitoso user.login_successful() self.user_repository.save(user) # 5. Generar tokens access_token = self.token_service.create_access_token(token_data) refresh_token = self.token_service.create_refresh_token(token_data) return LoginResponseDTO( access_token=access_token, refresh_token=refresh_token, user=user_data ), None
The use case depends on interfaces (IUserRepository, ITokenService), not concrete implementations. This is the Dependency Inversion Principle in action.
from flask import Blueprint, request, jsonifyfrom dependency_injector.wiring import inject, Providefrom .....application.use_cases.login_user import LoginUserUseCasefrom src.core.dependencies.containers import MainContainer as Containerauth_bp = Blueprint('auth_v1', __name__, url_prefix='/api/v1/auth')@auth_bp.route('/login', methods=['POST'])@injectdef login( login_use_case: LoginUserUseCase = Provide[Container.auth.login_use_case]): """Login endpoint""" # Validar input data = LoginSchema().load(request.json) # Convertir a DTO login_request = LoginRequestDTO(**data) # Ejecutar caso de uso result, error = login_use_case.execute(login_request) if error: return jsonify({"error": error}), 401 return jsonify(result), 200
The endpoint is just a thin adapter that validates input, calls the use case, and formats the response. All business logic lives in the domain and application layers.