another fix - once more

This commit is contained in:
2026-03-22 13:09:17 -06:00
parent 64727e17bb
commit 4395a81815
4 changed files with 405 additions and 99 deletions

View File

@@ -21,17 +21,40 @@ class CreateReport:
ubicacion: Optional[str] = None) -> Dict[str, Any]:
"""
Sends a create report message to RabbitMQ.
The actual database save will be done by the consumer.
Valida previamente:
- Usuario existe
- Descripción no está vacía
- Tipo de reporte válido
Returns:
Dictionary with status and message
"""
# Verify user exists (we still need to check this before queuing)
user = self.user_repo.find_by_id(id_usuario)
if not user:
# Validación: descripción requerida
if not descripcion or not descripcion.strip():
return {
"status": "error",
"message": f"Usuario con ID {id_usuario} no existe"
"message": "La descripción del reporte es requerida"
}
# Validación: tipo de reporte válido (1-5)
if tipo_reporte < 1 or tipo_reporte > 5:
return {
"status": "error",
"message": "El tipo de reporte debe estar entre 1 y 5"
}
# Validación: usuario existe (CRÍTICO)
try:
user = self.user_repo.find_by_id(id_usuario)
if not user:
return {
"status": "error",
"message": f"Usuario con ID {id_usuario} no existe"
}
except Exception as e:
return {
"status": "error",
"message": f"Error al validar usuario: {str(e)}"
}
id_reporte = str(uuid4())
@@ -43,7 +66,7 @@ class CreateReport:
id_reporte=id_reporte,
id_usuario=id_usuario,
tipo_reporte=tipo_reporte,
descripcion=descripcion,
descripcion=descripcion.strip(),
ubicacion=ubicacion,
visibilidad=50.0, # Visibilidad inicial neutral
fecha_creacion=fecha_creacion.isoformat()
@@ -105,11 +128,27 @@ class UpdateReportVisibility:
def execute(self, report_id: str, new_visibility: float, penalize_author: bool = False) -> Dict[str, Any]:
"""
Sends an update report visibility message to RabbitMQ.
The actual database update will be done by the consumer.
Valida previamente:
- Reporte existe
- Visibilidad en rango válido (0-100)
Returns:
Dictionary with status and message
"""
# Validación: reporte existe
try:
report = self.repo.find_by_id(report_id)
if not report:
return {
"status": "error",
"message": f"Reporte con ID {report_id} no existe"
}
except Exception as e:
return {
"status": "error",
"message": f"Error al buscar reporte: {str(e)}"
}
# Validar rango de visibilidad
if new_visibility < 0 or new_visibility > 100:
return {
@@ -161,11 +200,26 @@ class DeleteReport:
def execute(self, report_id: str) -> Dict[str, Any]:
"""
Sends a delete report message to RabbitMQ.
The actual database deletion will be done by the consumer.
Valida previamente:
- Reporte existe
Returns:
Dictionary with status and message
"""
# Validación: reporte existe
try:
report = self.repo.find_by_id(report_id)
if not report:
return {
"status": "error",
"message": f"Reporte con ID {report_id} no existe"
}
except Exception as e:
return {
"status": "error",
"message": f"Error al buscar reporte: {str(e)}"
}
# Create message object
message = ReportMessage(
event_type=ReportEventType.DELETE,

View File

@@ -4,6 +4,7 @@ from infrastructure.adapters.rabbitmq.sender import send_to_queue
from infrastructure.adapters.rabbitmq.messages import UserMessage, UserEventType
from datetime import datetime
from typing import List, Optional, Dict, Any
import re
class CreateUser:
"""Use case para crear un nuevo usuario - envía mensaje a RabbitMQ"""
@@ -19,17 +20,61 @@ class CreateUser:
Sends a create user message to RabbitMQ.
The actual database save will be done by the consumer.
Valida previamente:
- Email válido
- Email no duplicado
- Campos no vacíos
Returns:
Dictionary with status and message
"""
# Validación de campos requeridos
if not nombre or not nombre.strip():
return {
"status": "error",
"message": "El nombre es requerido"
}
if not apellido or not apellido.strip():
return {
"status": "error",
"message": "El apellido es requerido"
}
if not email or not email.strip():
return {
"status": "error",
"message": "El email es requerido"
}
# Validación de formato email
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, email):
return {
"status": "error",
"message": "El email no tiene un formato válido"
}
# Validación de email duplicado (CRÍTICO)
try:
existing_user = self.repo.find_by_email(email)
if existing_user:
return {
"status": "error",
"message": f"El email '{email}' ya está registrado"
}
except Exception as e:
return {
"status": "error",
"message": f"Error al validar email: {str(e)}"
}
fecha_creacion = datetime.now()
# Create message object
message = UserMessage(
event_type=UserEventType.CREATE,
nombre=nombre,
apellido=apellido,
email=email,
nombre=nombre.strip(),
apellido=apellido.strip(),
email=email.strip(),
fecha_nacimiento=fecha_nacimiento.isoformat(),
fecha_creacion=fecha_creacion.isoformat(),
calificacion=50.0,
@@ -94,17 +139,40 @@ class UpdateUser:
url_foto_perfil: str = None, biografia: str = None) -> Dict[str, Any]:
"""
Sends an update user message to RabbitMQ.
The actual database update will be done by the consumer.
Valida previamente:
- Usuario existe
Returns:
Dictionary with status and message
"""
# Validación: usuario existe
try:
user = self.repo.find_by_id(user_id)
if not user:
return {
"status": "error",
"message": f"Usuario con ID {user_id} no existe"
}
except Exception as e:
return {
"status": "error",
"message": f"Error al buscar usuario: {str(e)}"
}
# Validación: al menos un campo para actualizar
if not any([nombre, apellido, url_foto_perfil, biografia]):
return {
"status": "error",
"message": "Se requiere al menos un campo para actualizar"
}
# Create message object with only the fields to update
message = UserMessage(
event_type=UserEventType.UPDATE,
user_id=user_id,
nombre=nombre,
apellido=apellido,
nombre=nombre.strip() if nombre else None,
apellido=apellido.strip() if apellido else None,
url_foto_perfil=url_foto_perfil,
biografia=biografia
)
@@ -134,11 +202,27 @@ class DeleteUser:
def execute(self, user_id: int) -> Dict[str, Any]:
"""
Sends a delete user message to RabbitMQ.
The actual database deletion will be done by the consumer.
Valida previamente:
- Usuario existe
Returns:
Dictionary with status and message
"""
# Validación: usuario existe
try:
user = self.repo.find_by_id(user_id)
if not user:
return {
"status": "error",
"message": f"Usuario con ID {user_id} no existe"
}
except Exception as e:
return {
"status": "error",
"message": f"Error al buscar usuario: {str(e)}"
}
# Create message object
message = UserMessage(
event_type=UserEventType.DELETE,

View File

@@ -6,14 +6,16 @@ from application.services.report_services import (
)
from infrastructure.adapters.persistence.report_repository_mongo import ReportRepositoryMongo
from infrastructure.adapters.persistence.user_repository_sql import UserRepositorySQL
import logging
router = APIRouter()
report_repo = ReportRepositoryMongo()
user_repo = UserRepositorySQL()
logger = logging.getLogger(__name__)
@router.post("/", status_code=status.HTTP_202_ACCEPTED)
async def create_report(report_data: ReportCreateRequest):
"""Crea un nuevo reporte - envía a cola de procesamiento"""
"""Crea un nuevo reporte - envía a cola de procesamiento con validaciones previas"""
try:
create_use_case = CreateReport(report_repo, user_repo)
result = create_use_case.execute(
@@ -22,54 +24,98 @@ async def create_report(report_data: ReportCreateRequest):
descripcion=report_data.descripcion,
ubicacion=report_data.ubicacion
)
if result["status"] == "error":
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result["message"]
)
message = result["message"]
if "no existe" in message:
# 404 Not Found: usuario no existe
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=message
)
else:
# 400 Bad Request: error de validación
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=message
)
# 202 Accepted: enviado a la cola correctamente
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error inesperado en create_report: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Error al crear reporte: {str(e)}"
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
@router.get("/{report_id}", response_model=ReportResponse)
async def get_report(report_id: str):
"""Obtiene un reporte por ID"""
get_use_case = GetReportById(report_repo)
report = get_use_case.execute(report_id)
if not report:
try:
get_use_case = GetReportById(report_repo)
report = get_use_case.execute(report_id)
if not report:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Reporte con ID {report_id} no encontrado"
)
return report
except HTTPException:
raise
except Exception as e:
logger.error(f"Error al obtener reporte {report_id}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Reporte con ID {report_id} no encontrado"
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
return report
@router.get("/user/{user_id}", response_model=list[ReportResponse])
@router.get("/user/{user_id}")
async def get_user_reports(user_id: int):
"""Obtiene todos los reportes de un usuario"""
get_use_case = GetReportsByUser(report_repo)
reports = get_use_case.execute(user_id)
return reports
"""Obtiene todos los reportes de un usuario - retorna lista vacía si no hay reportes"""
try:
get_use_case = GetReportsByUser(report_repo)
reports = get_use_case.execute(user_id)
return reports
except Exception as e:
logger.error(f"Error al obtener reportes del usuario {user_id}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
@router.get("/", response_model=list[ReportResponse])
@router.get("/")
async def list_reports():
"""Obtiene todos los reportes"""
list_use_case = ListAllReports(report_repo)
return list_use_case.execute()
"""Obtiene todos los reportes - retorna lista vacía si no hay reportes"""
try:
list_use_case = ListAllReports(report_repo)
return list_use_case.execute()
except Exception as e:
logger.error(f"Error al listar reportes: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
@router.get("/shadowbanned/list", response_model=list[ReportResponse])
@router.get("/shadowbanned/list")
async def get_shadowbanned_reports(threshold: float = 20):
"""Obtiene reportes shadowbaneados (baja visibilidad)"""
get_use_case = GetShadowbannedReports(report_repo)
return get_use_case.execute(threshold)
"""Obtiene reportes shadowbaneados (baja visibilidad) - retorna lista vacía si no hay"""
try:
get_use_case = GetShadowbannedReports(report_repo)
return get_use_case.execute(threshold)
except Exception as e:
logger.error(f"Error al obtener reportes shadowbaneados: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
@router.put("/{report_id}/visibility", status_code=status.HTTP_202_ACCEPTED)
async def update_report_visibility(report_id: str, visibility_data: ReportUpdateVisibilityRequest):
"""Actualiza la visibilidad de un reporte - envía a cola de procesamiento"""
"""Actualiza la visibilidad de un reporte - envía a cola de procesamiento con validaciones previas"""
try:
update_use_case = UpdateReportVisibility(report_repo, user_repo)
result = update_use_case.execute(
@@ -77,28 +123,64 @@ async def update_report_visibility(report_id: str, visibility_data: ReportUpdate
new_visibility=visibility_data.new_visibility,
penalize_author=visibility_data.penalize_author
)
if result["status"] == "error":
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result["message"]
)
message = result["message"]
if "no existe" in message:
# 404 Not Found: reporte no existe
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=message
)
else:
# 400 Bad Request: error de validación
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=message
)
# 202 Accepted: enviado a la cola correctamente
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error al actualizar visibilidad del reporte {report_id}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Error al actualizar visibilidad: {str(e)}"
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
@router.delete("/{report_id}", status_code=status.HTTP_202_ACCEPTED)
async def delete_report(report_id: str):
"""Elimina un reporte - envía a cola de procesamiento"""
delete_use_case = DeleteReport(report_repo)
result = delete_use_case.execute(report_id)
if result["status"] == "error":
"""Elimina un reporte - envía a cola de procesamiento con validaciones previas"""
try:
delete_use_case = DeleteReport(report_repo)
result = delete_use_case.execute(report_id)
if result["status"] == "error":
message = result["message"]
if "no existe" in message:
# 404 Not Found: reporte no existe
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=message
)
else:
# 400 Bad Request: error de validación
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=message
)
# 202 Accepted: enviado a la cola correctamente
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error al eliminar reporte {report_id}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result["message"]
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
return result

View File

@@ -4,13 +4,15 @@ from application.services.user_services import (
CreateUser, GetUserById, GetUserByEmail, ListAllUsers, UpdateUser, DeleteUser
)
from infrastructure.adapters.persistence.user_repository_sql import UserRepositorySQL
import logging
router = APIRouter()
user_repo = UserRepositorySQL()
logger = logging.getLogger(__name__)
@router.post("/", status_code=status.HTTP_202_ACCEPTED)
async def create_user(user_data: UserCreateRequest):
"""Crea un nuevo usuario - envía a cola de procesamiento"""
"""Crea un nuevo usuario - envía a cola de procesamiento con validaciones previas"""
try:
create_use_case = CreateUser(user_repo)
result = create_use_case.execute(
@@ -21,76 +23,160 @@ async def create_user(user_data: UserCreateRequest):
url_foto_perfil=user_data.url_foto_perfil,
biografia=user_data.biografia
)
if result["status"] == "error":
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result["message"]
)
# Detectar tipo de error para código HTTP apropiado
message = result["message"]
if "ya está registrado" in message:
# 409 Conflict: email duplicado
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=message
)
else:
# 400 Bad Request: error de validación
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=message
)
# 202 Accepted: enviado a la cola correctamente
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error inesperado en create_user: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Error al crear usuario: {str(e)}"
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
@router.get("/{user_id}", response_model=UserResponse)
async def get_user(user_id: int):
"""Obtiene un usuario por ID"""
get_use_case = GetUserById(user_repo)
user = get_use_case.execute(user_id)
if not user:
try:
get_use_case = GetUserById(user_repo)
user = get_use_case.execute(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Usuario con ID {user_id} no encontrado"
)
return user
except HTTPException:
raise
except Exception as e:
logger.error(f"Error al obtener usuario {user_id}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Usuario con ID {user_id} no encontrado"
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
return user
@router.get("/email/{email}", response_model=UserResponse)
async def get_user_by_email(email: str):
"""Obtiene un usuario por email"""
get_use_case = GetUserByEmail(user_repo)
user = get_use_case.execute(email)
if not user:
try:
get_use_case = GetUserByEmail(user_repo)
user = get_use_case.execute(email)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Usuario con email {email} no encontrado"
)
return user
except HTTPException:
raise
except Exception as e:
logger.error(f"Error al obtener usuario por email {email}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Usuario con email {email} no encontrado"
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
return user
@router.get("/", response_model=list[UserResponse])
@router.get("/")
async def list_users():
"""Obtiene todos los usuarios"""
list_use_case = ListAllUsers(user_repo)
return list_use_case.execute()
"""Obtiene todos los usuarios - retorna lista vacía si no hay registros"""
try:
list_use_case = ListAllUsers(user_repo)
return list_use_case.execute()
except Exception as e:
logger.error(f"Error al listar usuarios: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
@router.put("/{user_id}", status_code=status.HTTP_202_ACCEPTED)
async def update_user(user_id: int, user_data: UserUpdateRequest):
"""Actualiza un usuario - envía a cola de procesamiento"""
update_use_case = UpdateUser(user_repo)
result = update_use_case.execute(
user_id=user_id,
nombre=user_data.nombre,
apellido=user_data.apellido,
url_foto_perfil=user_data.url_foto_perfil,
biografia=user_data.biografia
)
if result["status"] == "error":
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result["message"]
"""Actualiza un usuario - envía a cola de procesamiento con validaciones previas"""
try:
update_use_case = UpdateUser(user_repo)
result = update_use_case.execute(
user_id=user_id,
nombre=user_data.nombre,
apellido=user_data.apellido,
url_foto_perfil=user_data.url_foto_perfil,
biografia=user_data.biografia
)
if result["status"] == "error":
message = result["message"]
if "no existe" in message:
# 404 Not Found: usuario no existe
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=message
)
else:
# 400 Bad Request: error de validación
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=message
)
# 202 Accepted: enviado a la cola correctamente
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error al actualizar usuario {user_id}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
return result
@router.delete("/{user_id}", status_code=status.HTTP_202_ACCEPTED)
async def delete_user(user_id: int):
"""Elimina un usuario - envía a cola de procesamiento"""
delete_use_case = DeleteUser(user_repo)
result = delete_use_case.execute(user_id)
if result["status"] == "error":
"""Elimina un usuario - envía a cola de procesamiento con validaciones previas"""
try:
delete_use_case = DeleteUser(user_repo)
result = delete_use_case.execute(user_id)
if result["status"] == "error":
message = result["message"]
if "no existe" in message:
# 404 Not Found: usuario no existe
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=message
)
else:
# 400 Bad Request: error de validación
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=message
)
# 202 Accepted: enviado a la cola correctamente
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error al eliminar usuario {user_id}: {e}", exc_info=True)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=result["message"]
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Error interno del servidor"
)
return result