Fixed metrics api to recieve all metrics
This commit is contained in:
@@ -4,7 +4,6 @@ import os
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
# Add src to path to import modules
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
|
||||||
from infrastructure.adapters.rabbitmq.consumer import RabbitMQConsumer
|
from infrastructure.adapters.rabbitmq.consumer import RabbitMQConsumer
|
||||||
@@ -14,7 +13,6 @@ from infrastructure.adapters.rabbitmq.messages import (
|
|||||||
from application.services.metrics_services import MetricsService
|
from application.services.metrics_services import MetricsService
|
||||||
from infrastructure.adapters.persistence.metrics_repository_postgres import MetricsRepositoryPostgres
|
from infrastructure.adapters.persistence.metrics_repository_postgres import MetricsRepositoryPostgres
|
||||||
|
|
||||||
# Set up logging
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
@@ -25,27 +23,32 @@ logging.basicConfig(
|
|||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Colas dedicadas para métricas — no compiten con los consumers de dominio
|
||||||
|
METRICS_QUEUES = {
|
||||||
|
'user': 'metrics_users_queue',
|
||||||
|
'report': 'metrics_reports_queue',
|
||||||
|
'notification': 'metrics_notifications_queue',
|
||||||
|
'moderation': 'metrics_moderations_queue',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class MetricsConsumer:
|
class MetricsConsumer:
|
||||||
"""Consumer for all system events from RabbitMQ"""
|
"""Consumer for all system events from RabbitMQ"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.metrics_service = MetricsService(MetricsRepositoryPostgres())
|
self.metrics_service = MetricsService(MetricsRepositoryPostgres())
|
||||||
|
|
||||||
# Create separate consumers for each event type
|
self.user_consumer = RabbitMQConsumer(queue_name=METRICS_QUEUES['user'])
|
||||||
self.user_consumer = RabbitMQConsumer(queue_name='users_queue')
|
self.report_consumer = RabbitMQConsumer(queue_name=METRICS_QUEUES['report'])
|
||||||
self.report_consumer = RabbitMQConsumer(queue_name='reports_queue')
|
self.notification_consumer = RabbitMQConsumer(queue_name=METRICS_QUEUES['notification'])
|
||||||
self.notification_consumer = RabbitMQConsumer(queue_name='notifications_queue')
|
self.moderation_consumer = RabbitMQConsumer(queue_name=METRICS_QUEUES['moderation'])
|
||||||
self.moderation_consumer = RabbitMQConsumer(queue_name='moderations_queue')
|
|
||||||
|
|
||||||
# Set callbacks
|
|
||||||
self.user_consumer.set_callback(self.process_user_event)
|
self.user_consumer.set_callback(self.process_user_event)
|
||||||
self.report_consumer.set_callback(self.process_report_event)
|
self.report_consumer.set_callback(self.process_report_event)
|
||||||
self.notification_consumer.set_callback(self.process_notification_event)
|
self.notification_consumer.set_callback(self.process_notification_event)
|
||||||
self.moderation_consumer.set_callback(self.process_moderation_event)
|
self.moderation_consumer.set_callback(self.process_moderation_event)
|
||||||
|
|
||||||
def process_user_event(self, message_dict: dict):
|
def process_user_event(self, message_dict: dict):
|
||||||
"""Procesa eventos de usuario"""
|
|
||||||
try:
|
try:
|
||||||
message = UserMessage.from_dict(message_dict)
|
message = UserMessage.from_dict(message_dict)
|
||||||
self.metrics_service.record_event(
|
self.metrics_service.record_event(
|
||||||
@@ -58,107 +61,83 @@ class MetricsConsumer:
|
|||||||
logger.info(f"Métrica registrada: user_{message.event_type.value}")
|
logger.info(f"Métrica registrada: user_{message.event_type.value}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error procesando evento de usuario: {e}")
|
logger.error(f"Error procesando evento de usuario: {e}")
|
||||||
|
|
||||||
def process_report_event(self, message_dict: dict):
|
def process_report_event(self, message_dict: dict):
|
||||||
"""Procesa eventos de reportes"""
|
|
||||||
try:
|
try:
|
||||||
message = ReportMessage.from_dict(message_dict)
|
message = ReportMessage.from_dict(message_dict)
|
||||||
self.metrics_service.record_event(
|
self.metrics_service.record_event(
|
||||||
event_type=f"report_{message.event_type.value}",
|
event_type=f"report_{message.event_type.value}",
|
||||||
entity_id=str(message.report_id),
|
entity_id=str(message.id_reporte),
|
||||||
entity_type="report",
|
entity_type="report",
|
||||||
user_id=message.user_id,
|
user_id=message.id_usuario,
|
||||||
metadata={"status": message.status}
|
metadata={"estado": message.estado}
|
||||||
)
|
)
|
||||||
logger.info(f"Métrica registrada: report_{message.event_type.value}")
|
logger.info(f"Métrica registrada: report_{message.event_type.value}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error procesando evento de reporte: {e}")
|
logger.error(f"Error procesando evento de reporte: {e}")
|
||||||
|
|
||||||
def process_notification_event(self, message_dict: dict):
|
def process_notification_event(self, message_dict: dict):
|
||||||
"""Procesa eventos de notificaciones"""
|
|
||||||
try:
|
try:
|
||||||
message = NotificationMessage.from_dict(message_dict)
|
message = NotificationMessage.from_dict(message_dict)
|
||||||
self.metrics_service.record_event(
|
self.metrics_service.record_event(
|
||||||
event_type=f"notification_{message.event_type.value}",
|
event_type=f"notification_{message.event_type.value}",
|
||||||
entity_id=str(message.notification_id),
|
entity_id=str(message.id_notificacion),
|
||||||
entity_type="notification",
|
entity_type="notification",
|
||||||
user_id=message.user_id,
|
user_id=message.id_usuario,
|
||||||
metadata={"type": message.notification_type}
|
metadata={"type": message.tipo_notificacion}
|
||||||
)
|
)
|
||||||
logger.info(f"Métrica registrada: notification_{message.event_type.value}")
|
logger.info(f"Métrica registrada: notification_{message.event_type.value}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error procesando evento de notificación: {e}")
|
logger.error(f"Error procesando evento de notificación: {e}")
|
||||||
|
|
||||||
def process_moderation_event(self, message_dict: dict):
|
def process_moderation_event(self, message_dict: dict):
|
||||||
"""Procesa eventos de moderación"""
|
|
||||||
try:
|
try:
|
||||||
message = ModerationMessage.from_dict(message_dict)
|
message = ModerationMessage.from_dict(message_dict)
|
||||||
self.metrics_service.record_event(
|
self.metrics_service.record_event(
|
||||||
event_type=f"moderation_{message.event_type.value}",
|
event_type=f"moderation_{message.event_type.value}",
|
||||||
entity_id=str(message.moderation_id),
|
entity_id=str(message.action_id),
|
||||||
entity_type="moderation",
|
entity_type="moderation",
|
||||||
user_id=message.moderator_id,
|
user_id=message.moderator_id,
|
||||||
metadata={"action": message.action}
|
metadata={"action": message.review_action, "reason": message.reason}
|
||||||
)
|
)
|
||||||
logger.info(f"Métrica registrada: moderation_{message.event_type.value}")
|
logger.info(f"Métrica registrada: moderation_{message.event_type.value}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error procesando evento de moderación: {e}")
|
logger.error(f"Error procesando evento de moderación: {e}")
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Inicia todos los consumers"""
|
|
||||||
logger.info("Iniciando Metrics Consumer...")
|
logger.info("Iniciando Metrics Consumer...")
|
||||||
try:
|
try:
|
||||||
# Start consumers in separate threads
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
def start_user_consumer():
|
|
||||||
try:
|
|
||||||
self.user_consumer.start_consuming()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error en user consumer: {e}")
|
|
||||||
|
|
||||||
def start_report_consumer():
|
|
||||||
try:
|
|
||||||
self.report_consumer.start_consuming()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error en report consumer: {e}")
|
|
||||||
|
|
||||||
def start_notification_consumer():
|
|
||||||
try:
|
|
||||||
self.notification_consumer.start_consuming()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error en notification consumer: {e}")
|
|
||||||
|
|
||||||
def start_moderation_consumer():
|
|
||||||
try:
|
|
||||||
self.moderation_consumer.start_consuming()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error en moderation consumer: {e}")
|
|
||||||
|
|
||||||
threads = [
|
threads = [
|
||||||
threading.Thread(target=start_user_consumer, daemon=True),
|
threading.Thread(target=self._safe_consume, args=(self.user_consumer,), daemon=True),
|
||||||
threading.Thread(target=start_report_consumer, daemon=True),
|
threading.Thread(target=self._safe_consume, args=(self.report_consumer,), daemon=True),
|
||||||
threading.Thread(target=start_notification_consumer, daemon=True),
|
threading.Thread(target=self._safe_consume, args=(self.notification_consumer,), daemon=True),
|
||||||
threading.Thread(target=start_moderation_consumer, daemon=True),
|
threading.Thread(target=self._safe_consume, args=(self.moderation_consumer,), daemon=True),
|
||||||
]
|
]
|
||||||
for t in threads:
|
for t in threads:
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
# Keep main thread alive
|
|
||||||
for t in threads:
|
for t in threads:
|
||||||
t.join()
|
t.join()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
logger.info("Metrics Consumer detenido")
|
logger.info("Metrics Consumer detenido")
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
|
def _safe_consume(self, consumer: RabbitMQConsumer):
|
||||||
|
try:
|
||||||
|
consumer.start_consuming()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error en consumer de {consumer.queue_name}: {e}")
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
"""Detiene los consumers"""
|
for c in [self.user_consumer, self.report_consumer,
|
||||||
self.user_consumer.stop()
|
self.notification_consumer, self.moderation_consumer]:
|
||||||
self.report_consumer.stop()
|
try:
|
||||||
self.notification_consumer.stop()
|
c.stop()
|
||||||
self.moderation_consumer.stop()
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
consumer = MetricsConsumer()
|
consumer = MetricsConsumer()
|
||||||
consumer.start()
|
consumer.start()
|
||||||
@@ -3,41 +3,34 @@ import pika
|
|||||||
import json
|
import json
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_rabbitmq_host() -> str:
|
||||||
|
try:
|
||||||
|
from core.config import ConfSettings
|
||||||
|
return ConfSettings.rabbitmq
|
||||||
|
except Exception:
|
||||||
|
return os.getenv("RABBITMQ_URI", "localhost")
|
||||||
|
|
||||||
|
|
||||||
class RabbitMQSender:
|
class RabbitMQSender:
|
||||||
"""Generic RabbitMQ sender for publishing messages to queues"""
|
"""Generic RabbitMQ sender for publishing messages to queues"""
|
||||||
|
|
||||||
def __init__(self, host: str = 'localhost', port: int = 5672):
|
def __init__(self, host: str = None, port: int = 5672):
|
||||||
self.host = host
|
self.host = host or _get_rabbitmq_host()
|
||||||
self.port = port
|
self.port = port
|
||||||
|
|
||||||
def send_message(self, queue_name: str, message: Dict[str, Any]) -> bool:
|
def send_message(self, queue_name: str, message: Dict[str, Any]) -> bool:
|
||||||
"""
|
|
||||||
Sends a message to a RabbitMQ queue
|
|
||||||
|
|
||||||
Args:
|
|
||||||
queue_name: Name of the queue to send to
|
|
||||||
message: Dictionary containing the message data
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if successful, False otherwise
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
connection = pika.BlockingConnection(
|
connection = pika.BlockingConnection(
|
||||||
pika.ConnectionParameters(host=self.host, port=self.port)
|
pika.ConnectionParameters(host=self.host, port=self.port)
|
||||||
)
|
)
|
||||||
channel = connection.channel()
|
channel = connection.channel()
|
||||||
|
|
||||||
# Declare queue to ensure it exists
|
|
||||||
channel.queue_declare(queue=queue_name, durable=True)
|
channel.queue_declare(queue=queue_name, durable=True)
|
||||||
|
|
||||||
# Convert message to JSON
|
|
||||||
message_json = json.dumps(message)
|
message_json = json.dumps(message)
|
||||||
|
|
||||||
# Publish the message
|
|
||||||
channel.basic_publish(
|
channel.basic_publish(
|
||||||
exchange='',
|
exchange='',
|
||||||
routing_key=queue_name,
|
routing_key=queue_name,
|
||||||
@@ -46,29 +39,15 @@ class RabbitMQSender:
|
|||||||
delivery_mode=pika.spec.PERSISTENT_DELIVERY_MODE
|
delivery_mode=pika.spec.PERSISTENT_DELIVERY_MODE
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
connection.close()
|
connection.close()
|
||||||
logger.info(f"Message sent to queue '{queue_name}': {message_json}")
|
logger.info(f"Message sent to queue '{queue_name}': {message_json}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error sending message to RabbitMQ: {e}")
|
logger.error(f"Error sending message to RabbitMQ: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def send_to_queue(queue_name: str, message: Dict[str, Any],
|
def send_to_queue(queue_name: str, message: Dict[str, Any],
|
||||||
host: str = 'localhost', port: int = 5672) -> bool:
|
host: str = None, port: int = 5672) -> bool:
|
||||||
"""
|
|
||||||
Convenience function to send a message to RabbitMQ
|
|
||||||
|
|
||||||
Args:
|
|
||||||
queue_name: Name of the queue
|
|
||||||
message: Message dictionary
|
|
||||||
host: RabbitMQ host
|
|
||||||
port: RabbitMQ port
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
True if successful, False otherwise
|
|
||||||
"""
|
|
||||||
sender = RabbitMQSender(host=host, port=port)
|
sender = RabbitMQSender(host=host, port=port)
|
||||||
return sender.send_message(queue_name, message)
|
return sender.send_message(queue_name, message)
|
||||||
@@ -2,76 +2,67 @@
|
|||||||
from fastapi import APIRouter, HTTPException, Depends
|
from fastapi import APIRouter, HTTPException, Depends
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from infrastructure.api.moderations.schemas import (
|
from infrastructure.api.moderations.schemas import (
|
||||||
DeleteReportRequest,
|
DeleteReportRequest, CloseAccountRequest, BanUserRequest,
|
||||||
CloseAccountRequest,
|
WarnUserRequest, ReviewContentRequest, ModerationActionResponse
|
||||||
BanUserRequest,
|
|
||||||
WarnUserRequest,
|
|
||||||
ReviewContentRequest,
|
|
||||||
ModerationActionResponse
|
|
||||||
)
|
)
|
||||||
from infrastructure.api.auth import get_current_admin_user
|
from infrastructure.api.auth import get_current_admin_user
|
||||||
from application.services.moderation_services import (
|
from application.services.moderation_services import (
|
||||||
DeleteReportUseCase,
|
DeleteReportUseCase, CloseAccountUseCase, BanUserUseCase,
|
||||||
CloseAccountUseCase,
|
WarnUserUseCase, ReviewContentUseCase
|
||||||
BanUserUseCase,
|
|
||||||
WarnUserUseCase,
|
|
||||||
ReviewContentUseCase
|
|
||||||
)
|
)
|
||||||
from infrastructure.adapters.persistence.mongodb import mongodb
|
from infrastructure.adapters.persistence.mongodb import mongodb
|
||||||
from infrastructure.adapters.persistence.db import get_db
|
from infrastructure.adapters.persistence.db import get_db
|
||||||
from infrastructure.adapters.moderation_repository_mongo import ModerationRepositoryMongo
|
from infrastructure.adapters.moderation_repository_mongo import ModerationRepositoryMongo
|
||||||
|
from infrastructure.adapters.rabbitmq.sender import send_to_queue
|
||||||
|
from infrastructure.adapters.rabbitmq.messages import ModerationMessage, ModerationEventType
|
||||||
from domain.users import User
|
from domain.users import User
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
# Instanciar repositorio de moderación
|
|
||||||
moderation_repo = ModerationRepositoryMongo()
|
moderation_repo = ModerationRepositoryMongo()
|
||||||
|
|
||||||
|
|
||||||
|
def _publish_moderation_event(event_type: ModerationEventType, **kwargs):
|
||||||
|
try:
|
||||||
|
msg = ModerationMessage(event_type=event_type, fecha_creacion=datetime.utcnow().isoformat(), **kwargs)
|
||||||
|
payload = msg.to_dict()
|
||||||
|
send_to_queue('moderations_queue', payload)
|
||||||
|
send_to_queue('metrics_moderations_queue', payload)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error publicando evento de moderación a RabbitMQ: {e}")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/reports/delete", response_model=ModerationActionResponse)
|
@router.post("/reports/delete", response_model=ModerationActionResponse)
|
||||||
async def delete_report(
|
async def delete_report(request: DeleteReportRequest, current_admin: User = Depends(get_current_admin_user)):
|
||||||
request: DeleteReportRequest,
|
|
||||||
current_admin: User = Depends(get_current_admin_user)
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Eliminar un reporte como moderador (requiere permisos de admin)
|
Eliminar un reporte como moderador (requiere permisos de admin)
|
||||||
|
|
||||||
- **report_id**: ID del reporte a eliminar
|
- **report_id**: ID del reporte a eliminar
|
||||||
- **reason**: Razón de la eliminación (mínimo 5 caracteres)
|
- **reason**: Razón de la eliminación (mínimo 5 caracteres)
|
||||||
- **description**: Descripción adicional (opcional)
|
- **description**: Descripción adicional (opcional)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
use_case = DeleteReportUseCase(moderation_repo)
|
use_case = DeleteReportUseCase(moderation_repo)
|
||||||
result = use_case.execute(
|
result = use_case.execute(moderator_id=current_admin.user_id, report_id=request.report_id, reason=request.reason, description=request.description)
|
||||||
moderator_id=current_admin.user_id,
|
|
||||||
report_id=request.report_id,
|
|
||||||
reason=request.reason,
|
|
||||||
description=request.description
|
|
||||||
)
|
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
raise HTTPException(status_code=400, detail=result["message"])
|
raise HTTPException(status_code=400, detail=result["message"])
|
||||||
|
_publish_moderation_event(ModerationEventType.DELETE_REPORT, moderator_id=current_admin.user_id, report_id=request.report_id, reason=request.reason, description=request.description, action_id=result.get("action_id"))
|
||||||
return ModerationActionResponse(**result)
|
return ModerationActionResponse(**result)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in delete_report: {e}")
|
logger.error(f"Error in delete_report: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/accounts/close", response_model=ModerationActionResponse)
|
@router.post("/accounts/close", response_model=ModerationActionResponse)
|
||||||
async def close_account(
|
async def close_account(request: CloseAccountRequest, current_admin: User = Depends(get_current_admin_user)):
|
||||||
request: CloseAccountRequest,
|
|
||||||
current_admin: User = Depends(get_current_admin_user)
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Cerrar una cuenta de usuario (requiere permisos de admin)
|
Cerrar una cuenta de usuario (requiere permisos de admin)
|
||||||
|
|
||||||
- **user_id**: ID del usuario cuya cuenta cerrar
|
- **user_id**: ID del usuario cuya cuenta cerrar
|
||||||
- **reason**: Razón del cierre (mínimo 5 caracteres)
|
- **reason**: Razón del cierre (mínimo 5 caracteres)
|
||||||
- **description**: Descripción adicional (opcional)
|
- **description**: Descripción adicional (opcional)
|
||||||
@@ -79,32 +70,23 @@ async def close_account(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
use_case = CloseAccountUseCase(moderation_repo)
|
use_case = CloseAccountUseCase(moderation_repo)
|
||||||
result = use_case.execute(
|
result = use_case.execute(moderator_id=current_admin.user_id, user_id=request.user_id, reason=request.reason, description=request.description, is_permanent=request.is_permanent)
|
||||||
moderator_id=current_admin.user_id,
|
|
||||||
user_id=request.user_id,
|
|
||||||
reason=request.reason,
|
|
||||||
description=request.description,
|
|
||||||
is_permanent=request.is_permanent
|
|
||||||
)
|
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
raise HTTPException(status_code=400, detail=result["message"])
|
raise HTTPException(status_code=400, detail=result["message"])
|
||||||
|
_publish_moderation_event(ModerationEventType.CLOSE_ACCOUNT, moderator_id=current_admin.user_id, user_id=request.user_id, reason=request.reason, description=request.description, is_permanent=request.is_permanent, action_id=result.get("action_id"))
|
||||||
return ModerationActionResponse(**result)
|
return ModerationActionResponse(**result)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in close_account: {e}")
|
logger.error(f"Error in close_account: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/users/ban", response_model=ModerationActionResponse)
|
@router.post("/users/ban", response_model=ModerationActionResponse)
|
||||||
async def ban_user(
|
async def ban_user(request: BanUserRequest, current_admin: User = Depends(get_current_admin_user)):
|
||||||
request: BanUserRequest,
|
|
||||||
current_admin: User = Depends(get_current_admin_user)
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Banear a un usuario (requiere permisos de admin)
|
Banear a un usuario (requiere permisos de admin)
|
||||||
|
|
||||||
- **user_id**: ID del usuario a banear
|
- **user_id**: ID del usuario a banear
|
||||||
- **reason**: Razón del ban (mínimo 5 caracteres)
|
- **reason**: Razón del ban (mínimo 5 caracteres)
|
||||||
- **duration_days**: Duración del ban en días (None para permanente)
|
- **duration_days**: Duración del ban en días (None para permanente)
|
||||||
@@ -112,63 +94,46 @@ async def ban_user(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
use_case = BanUserUseCase(moderation_repo)
|
use_case = BanUserUseCase(moderation_repo)
|
||||||
result = use_case.execute(
|
result = use_case.execute(moderator_id=current_admin.user_id, user_id=request.user_id, reason=request.reason, duration_days=request.duration_days, description=request.description)
|
||||||
moderator_id=current_admin.user_id,
|
|
||||||
user_id=request.user_id,
|
|
||||||
reason=request.reason,
|
|
||||||
duration_days=request.duration_days,
|
|
||||||
description=request.description
|
|
||||||
)
|
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
raise HTTPException(status_code=400, detail=result["message"])
|
raise HTTPException(status_code=400, detail=result["message"])
|
||||||
|
_publish_moderation_event(ModerationEventType.BAN_USER, moderator_id=current_admin.user_id, user_id=request.user_id, reason=request.reason, duration_days=request.duration_days, description=request.description, is_permanent=request.duration_days is None, action_id=result.get("action_id"))
|
||||||
return ModerationActionResponse(**result)
|
return ModerationActionResponse(**result)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in ban_user: {e}")
|
logger.error(f"Error in ban_user: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/users/warn", response_model=ModerationActionResponse)
|
@router.post("/users/warn", response_model=ModerationActionResponse)
|
||||||
async def warn_user(
|
async def warn_user(request: WarnUserRequest, current_admin: User = Depends(get_current_admin_user)):
|
||||||
request: WarnUserRequest,
|
|
||||||
current_admin: User = Depends(get_current_admin_user)
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Advertir a un usuario (requiere permisos de admin)
|
Advertir a un usuario (requiere permisos de admin)
|
||||||
|
|
||||||
- **user_id**: ID del usuario a advertir
|
- **user_id**: ID del usuario a advertir
|
||||||
- **reason**: Razón de la advertencia (mínimo 5 caracteres)
|
- **reason**: Razón de la advertencia (mínimo 5 caracteres)
|
||||||
- **description**: Descripción adicional (opcional)
|
- **description**: Descripción adicional (opcional)
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
use_case = WarnUserUseCase(moderation_repo)
|
use_case = WarnUserUseCase(moderation_repo)
|
||||||
result = use_case.execute(
|
result = use_case.execute(moderator_id=current_admin.user_id, user_id=request.user_id, reason=request.reason, description=request.description)
|
||||||
moderator_id=current_admin.user_id,
|
|
||||||
user_id=request.user_id,
|
|
||||||
reason=request.reason,
|
|
||||||
description=request.description
|
|
||||||
)
|
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
raise HTTPException(status_code=400, detail=result["message"])
|
raise HTTPException(status_code=400, detail=result["message"])
|
||||||
|
_publish_moderation_event(ModerationEventType.WARN_USER, moderator_id=current_admin.user_id, user_id=request.user_id, reason=request.reason, description=request.description, action_id=result.get("action_id"))
|
||||||
return ModerationActionResponse(**result)
|
return ModerationActionResponse(**result)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in warn_user: {e}")
|
logger.error(f"Error in warn_user: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/content/review", response_model=ModerationActionResponse)
|
@router.post("/content/review", response_model=ModerationActionResponse)
|
||||||
async def review_content(
|
async def review_content(request: ReviewContentRequest, current_admin: User = Depends(get_current_admin_user)):
|
||||||
request: ReviewContentRequest,
|
|
||||||
current_admin: User = Depends(get_current_admin_user)
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
Revisar y actuar sobre contenido reportado (requiere permisos de admin)
|
Revisar y actuar sobre contenido reportado (requiere permisos de admin)
|
||||||
|
|
||||||
- **report_id**: ID del reporte a revisar
|
- **report_id**: ID del reporte a revisar
|
||||||
- **action**: Acción a tomar (approve, reject, needs_more_info)
|
- **action**: Acción a tomar (approve, reject, needs_more_info)
|
||||||
- **reason**: Razón de la decisión (opcional)
|
- **reason**: Razón de la decisión (opcional)
|
||||||
@@ -176,19 +141,13 @@ async def review_content(
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
use_case = ReviewContentUseCase(moderation_repo)
|
use_case = ReviewContentUseCase(moderation_repo)
|
||||||
result = use_case.execute(
|
result = use_case.execute(moderator_id=current_admin.user_id, report_id=request.report_id, action=request.action, reason=request.reason, notes=request.notes)
|
||||||
moderator_id=current_admin.user_id,
|
|
||||||
report_id=request.report_id,
|
|
||||||
action=request.action,
|
|
||||||
reason=request.reason,
|
|
||||||
notes=request.notes
|
|
||||||
)
|
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
raise HTTPException(status_code=400, detail=result["message"])
|
raise HTTPException(status_code=400, detail=result["message"])
|
||||||
|
_publish_moderation_event(ModerationEventType.REVIEW_CONTENT, moderator_id=current_admin.user_id, report_id=request.report_id, review_action=request.action, reason=request.reason, notes=request.notes, action_id=result.get("action_id"))
|
||||||
return ModerationActionResponse(**result)
|
return ModerationActionResponse(**result)
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in review_content: {e}")
|
logger.error(f"Error in review_content: {e}")
|
||||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||||
@@ -8,7 +8,10 @@ from infrastructure.adapters.persistence.report_repository_mongo import ReportRe
|
|||||||
from infrastructure.adapters.persistence.user_repository_sql import UserRepositorySQL
|
from infrastructure.adapters.persistence.user_repository_sql import UserRepositorySQL
|
||||||
from infrastructure.adapters.file_storage import image_storage
|
from infrastructure.adapters.file_storage import image_storage
|
||||||
from infrastructure.adapters.rabbitmq.sender import send_to_queue
|
from infrastructure.adapters.rabbitmq.sender import send_to_queue
|
||||||
from infrastructure.adapters.rabbitmq.messages import NotificationMessage, NotificationEventType
|
from infrastructure.adapters.rabbitmq.messages import (
|
||||||
|
NotificationMessage, NotificationEventType,
|
||||||
|
ReportMessage, ReportEventType
|
||||||
|
)
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -21,7 +24,6 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def _report_to_response(report) -> dict:
|
def _report_to_response(report) -> dict:
|
||||||
"""Convierte un objeto Report a dict con image_url"""
|
|
||||||
return {
|
return {
|
||||||
"id_reporte": report.id_reporte,
|
"id_reporte": report.id_reporte,
|
||||||
"id_usuario": report.id_usuario,
|
"id_usuario": report.id_usuario,
|
||||||
@@ -36,6 +38,17 @@ def _report_to_response(report) -> dict:
|
|||||||
"fecha_creacion": report.fecha_creacion
|
"fecha_creacion": report.fecha_creacion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _publish_report_event(event_type: ReportEventType, **kwargs):
|
||||||
|
try:
|
||||||
|
msg = ReportMessage(event_type=event_type, **kwargs)
|
||||||
|
payload = msg.to_dict()
|
||||||
|
send_to_queue('reports_queue', payload)
|
||||||
|
send_to_queue('metrics_reports_queue', payload)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error publicando evento de reporte a RabbitMQ: {e}")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/", status_code=status.HTTP_202_ACCEPTED)
|
@router.post("/", status_code=status.HTTP_202_ACCEPTED)
|
||||||
async def create_report(
|
async def create_report(
|
||||||
id_usuario: int = Form(...),
|
id_usuario: int = Form(...),
|
||||||
@@ -49,54 +62,38 @@ async def create_report(
|
|||||||
):
|
):
|
||||||
"""Crea un nuevo reporte - envía a cola de procesamiento con validaciones previas"""
|
"""Crea un nuevo reporte - envía a cola de procesamiento con validaciones previas"""
|
||||||
try:
|
try:
|
||||||
# Procesar imagen si fue proporcionada
|
|
||||||
image_filename = None
|
image_filename = None
|
||||||
if file:
|
if file:
|
||||||
logger.info(f"Processing image file: {file.filename} ({file.content_type})")
|
logger.info(f"Processing image file: {file.filename} ({file.content_type})")
|
||||||
image_filename = image_storage.validate_and_save_image(file, f"temp_{id_usuario}_{tipo_reporte}")
|
image_filename = image_storage.validate_and_save_image(file, f"temp_{id_usuario}_{tipo_reporte}")
|
||||||
|
|
||||||
create_use_case = CreateReport(report_repo, user_repo)
|
create_use_case = CreateReport(report_repo, user_repo)
|
||||||
result = create_use_case.execute(
|
result = create_use_case.execute(
|
||||||
id_usuario=id_usuario,
|
id_usuario=id_usuario, tipo_reporte=tipo_reporte, descripcion=descripcion,
|
||||||
tipo_reporte=tipo_reporte,
|
ubicacion=ubicacion, lat=lat, lng=lng, image_filename=image_filename, estado=estado
|
||||||
descripcion=descripcion,
|
|
||||||
ubicacion=ubicacion,
|
|
||||||
lat=lat,
|
|
||||||
lng=lng,
|
|
||||||
image_filename=image_filename,
|
|
||||||
estado=estado
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
# Si hay error, eliminar imagen si fue guardada
|
|
||||||
if image_filename:
|
if image_filename:
|
||||||
image_storage.delete_image(image_filename)
|
image_storage.delete_image(image_filename)
|
||||||
|
|
||||||
message = result["message"]
|
message = result["message"]
|
||||||
if "no existe" in message:
|
if "no existe" in message:
|
||||||
# 404 Not Found: usuario no existe
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=message)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=message
|
_publish_report_event(
|
||||||
)
|
ReportEventType.CREATE,
|
||||||
else:
|
id_reporte=result.get("id_reporte"), id_usuario=id_usuario, tipo_reporte=tipo_reporte,
|
||||||
# 400 Bad Request: error de validación
|
descripcion=descripcion, ubicacion=ubicacion, lat=lat, lng=lng, estado=estado,
|
||||||
raise HTTPException(
|
fecha_creacion=datetime.utcnow().isoformat()
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
)
|
||||||
detail=message
|
|
||||||
)
|
|
||||||
|
|
||||||
# 202 Accepted: enviado a la cola correctamente
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error inesperado en create_report: {e}", exc_info=True)
|
logger.error(f"Error inesperado en create_report: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get("/{report_id}", response_model=ReportResponse)
|
@router.get("/{report_id}", response_model=ReportResponse)
|
||||||
async def get_report(report_id: str):
|
async def get_report(report_id: str):
|
||||||
@@ -105,19 +102,13 @@ async def get_report(report_id: str):
|
|||||||
get_use_case = GetReportById(report_repo)
|
get_use_case = GetReportById(report_repo)
|
||||||
report = get_use_case.execute(report_id)
|
report = get_use_case.execute(report_id)
|
||||||
if not report:
|
if not report:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Reporte con ID {report_id} no encontrado")
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"Reporte con ID {report_id} no encontrado"
|
|
||||||
)
|
|
||||||
return _report_to_response(report)
|
return _report_to_response(report)
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al obtener reporte {report_id}: {e}", exc_info=True)
|
logger.error(f"Error al obtener reporte {report_id}: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get("/user/{user_id}")
|
@router.get("/user/{user_id}")
|
||||||
async def get_user_reports(user_id: int):
|
async def get_user_reports(user_id: int):
|
||||||
@@ -128,10 +119,7 @@ async def get_user_reports(user_id: int):
|
|||||||
return [_report_to_response(report) for report in reports]
|
return [_report_to_response(report) for report in reports]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al obtener reportes del usuario {user_id}: {e}", exc_info=True)
|
logger.error(f"Error al obtener reportes del usuario {user_id}: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def list_reports():
|
async def list_reports():
|
||||||
@@ -142,10 +130,7 @@ async def list_reports():
|
|||||||
return [_report_to_response(report) for report in reports]
|
return [_report_to_response(report) for report in reports]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al listar reportes: {e}", exc_info=True)
|
logger.error(f"Error al listar reportes: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get("/shadowbanned/list")
|
@router.get("/shadowbanned/list")
|
||||||
async def get_shadowbanned_reports(threshold: float = 20):
|
async def get_shadowbanned_reports(threshold: float = 20):
|
||||||
@@ -156,48 +141,26 @@ async def get_shadowbanned_reports(threshold: float = 20):
|
|||||||
return [_report_to_response(report) for report in reports]
|
return [_report_to_response(report) for report in reports]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al obtener reportes shadowbaneados: {e}", exc_info=True)
|
logger.error(f"Error al obtener reportes shadowbaneados: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.put("/{report_id}/visibility", status_code=status.HTTP_202_ACCEPTED)
|
@router.put("/{report_id}/visibility", status_code=status.HTTP_202_ACCEPTED)
|
||||||
async def update_report_visibility(report_id: str, visibility_data: ReportUpdateVisibilityRequest):
|
async def update_report_visibility(report_id: str, visibility_data: ReportUpdateVisibilityRequest):
|
||||||
"""Actualiza la visibilidad de un reporte - envía a cola de procesamiento con validaciones previas"""
|
"""Actualiza la visibilidad de un reporte - envía a cola de procesamiento con validaciones previas"""
|
||||||
try:
|
try:
|
||||||
update_use_case = UpdateReportVisibility(report_repo, user_repo)
|
update_use_case = UpdateReportVisibility(report_repo, user_repo)
|
||||||
result = update_use_case.execute(
|
result = update_use_case.execute(report_id=report_id, new_visibility=visibility_data.new_visibility, penalize_author=visibility_data.penalize_author)
|
||||||
report_id=report_id,
|
|
||||||
new_visibility=visibility_data.new_visibility,
|
|
||||||
penalize_author=visibility_data.penalize_author
|
|
||||||
)
|
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
message = result["message"]
|
message = result["message"]
|
||||||
if "no existe" in message:
|
if "no existe" in message:
|
||||||
# 404 Not Found: reporte no existe
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=message)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
_publish_report_event(ReportEventType.UPDATE_VISIBILITY, id_reporte=report_id, visibilidad=visibility_data.new_visibility, penalize_author=visibility_data.penalize_author)
|
||||||
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
|
return result
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al actualizar visibilidad del reporte {report_id}: {e}", exc_info=True)
|
logger.error(f"Error al actualizar visibilidad del reporte {report_id}: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.delete("/{report_id}", status_code=status.HTTP_202_ACCEPTED)
|
@router.delete("/{report_id}", status_code=status.HTTP_202_ACCEPTED)
|
||||||
async def delete_report(report_id: str):
|
async def delete_report(report_id: str):
|
||||||
@@ -205,69 +168,38 @@ async def delete_report(report_id: str):
|
|||||||
try:
|
try:
|
||||||
delete_use_case = DeleteReport(report_repo)
|
delete_use_case = DeleteReport(report_repo)
|
||||||
result = delete_use_case.execute(report_id)
|
result = delete_use_case.execute(report_id)
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
message = result["message"]
|
message = result["message"]
|
||||||
if "no existe" in message:
|
if "no existe" in message:
|
||||||
# 404 Not Found: reporte no existe
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=message)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
_publish_report_event(ReportEventType.DELETE, id_reporte=report_id)
|
||||||
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
|
return result
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al eliminar reporte {report_id}: {e}", exc_info=True)
|
logger.error(f"Error al eliminar reporte {report_id}: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.put("/{report_id}/status", status_code=status.HTTP_200_OK)
|
@router.put("/{report_id}/status", status_code=status.HTTP_200_OK)
|
||||||
async def update_report_status(report_id: str, status_data: ReportUpdateStatusRequest):
|
async def update_report_status(report_id: str, status_data: ReportUpdateStatusRequest):
|
||||||
"""Actualiza el estado de un reporte y envía notificación al usuario"""
|
"""Actualiza el estado de un reporte y envía notificación al usuario"""
|
||||||
try:
|
try:
|
||||||
# Obtener el reporte actual para saber el usuario creador
|
|
||||||
report = report_repo.find_by_id(report_id)
|
report = report_repo.find_by_id(report_id)
|
||||||
if not report:
|
if not report:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Reporte con ID {report_id} no encontrado")
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"Reporte con ID {report_id} no encontrado"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Actualizar el estado
|
|
||||||
update_use_case = UpdateReportStatus(report_repo)
|
update_use_case = UpdateReportStatus(report_repo)
|
||||||
result = update_use_case.execute(
|
result = update_use_case.execute(report_id=report_id, new_estado=status_data.estado)
|
||||||
report_id=report_id,
|
|
||||||
new_estado=status_data.estado
|
|
||||||
)
|
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
message = result["message"]
|
message = result["message"]
|
||||||
if "no existe" in message:
|
if "no existe" in message:
|
||||||
# 404 Not Found: reporte no existe
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=message)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=message
|
_publish_report_event(ReportEventType.UPDATE_STATUS, id_reporte=report_id, id_usuario=report.id_usuario, estado=status_data.estado)
|
||||||
)
|
|
||||||
else:
|
|
||||||
# 400 Bad Request: error de validación
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
|
||||||
detail=message
|
|
||||||
)
|
|
||||||
|
|
||||||
# Enviar notificación al usuario creador del reporte
|
|
||||||
try:
|
try:
|
||||||
notification_message = NotificationMessage(
|
notification_message = NotificationMessage(
|
||||||
event_type=NotificationEventType.REPORT_STATUS_CHANGE,
|
event_type=NotificationEventType.REPORT_STATUS_CHANGE,
|
||||||
@@ -280,25 +212,16 @@ async def update_report_status(report_id: str, status_data: ReportUpdateStatusRe
|
|||||||
estado_reporte=status_data.estado,
|
estado_reporte=status_data.estado,
|
||||||
fecha_creacion=datetime.utcnow().isoformat()
|
fecha_creacion=datetime.utcnow().isoformat()
|
||||||
)
|
)
|
||||||
|
send_to_queue('notifications_queue', notification_message.to_dict())
|
||||||
# Enviar a la cola de notificaciones
|
send_to_queue('metrics_notifications_queue', notification_message.to_dict())
|
||||||
send_to_queue(
|
|
||||||
queue_name='notifications_queue',
|
|
||||||
message=notification_message.to_dict()
|
|
||||||
)
|
|
||||||
logger.info(f"Notification sent to user {report.id_usuario} for report {report_id}")
|
logger.info(f"Notification sent to user {report.id_usuario} for report {report_id}")
|
||||||
except Exception as notification_error:
|
except Exception as notification_error:
|
||||||
logger.warning(f"Error sending notification for report {report_id}: {notification_error}")
|
logger.warning(f"Error sending notification for report {report_id}: {notification_error}")
|
||||||
# No fallar la actualización si hay error en notificación
|
|
||||||
|
|
||||||
# 200 OK: estado actualizado correctamente
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al actualizar estado del reporte {report_id}: {e}", exc_info=True)
|
logger.error(f"Error al actualizar estado del reporte {report_id}: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter, HTTPException, status, Depends
|
from fastapi import APIRouter, HTTPException, status, Depends
|
||||||
from infrastructure.api.users.schemas import (
|
from infrastructure.api.users.schemas import (
|
||||||
UserCreateRequest, UserUpdateRequest, UserResponse,
|
UserCreateRequest, UserUpdateRequest, UserResponse,
|
||||||
UserLoginRequest, UserLoginResponse
|
UserLoginRequest, UserLoginResponse
|
||||||
)
|
)
|
||||||
from application.services.user_services import (
|
from application.services.user_services import (
|
||||||
@@ -8,21 +8,35 @@ from application.services.user_services import (
|
|||||||
)
|
)
|
||||||
from infrastructure.adapters.persistence.user_repository_sql import UserRepositorySQL
|
from infrastructure.adapters.persistence.user_repository_sql import UserRepositorySQL
|
||||||
from infrastructure.api.users.auth_service import auth_service
|
from infrastructure.api.users.auth_service import auth_service
|
||||||
|
from infrastructure.adapters.rabbitmq.sender import send_to_queue
|
||||||
|
from infrastructure.adapters.rabbitmq.messages import UserMessage, UserEventType
|
||||||
import logging
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
user_repo = UserRepositorySQL()
|
user_repo = UserRepositorySQL()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _publish_user_event(event_type: UserEventType, **kwargs):
|
||||||
|
try:
|
||||||
|
msg = UserMessage(event_type=event_type, fecha_creacion=datetime.utcnow().isoformat(), **kwargs)
|
||||||
|
payload = msg.to_dict()
|
||||||
|
send_to_queue('users_queue', payload)
|
||||||
|
send_to_queue('metrics_users_queue', payload)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Error publicando evento de usuario a RabbitMQ: {e}")
|
||||||
|
|
||||||
|
|
||||||
@router.post("/login", response_model=UserLoginResponse, status_code=status.HTTP_200_OK)
|
@router.post("/login", response_model=UserLoginResponse, status_code=status.HTTP_200_OK)
|
||||||
async def login_user(credentials: UserLoginRequest):
|
async def login_user(credentials: UserLoginRequest):
|
||||||
"""
|
"""
|
||||||
Autentica un usuario y retorna un token JWT
|
Autentica un usuario y retorna un token JWT
|
||||||
|
|
||||||
**Parámetros:**
|
**Parámetros:**
|
||||||
- email: Email del usuario
|
- email: Email del usuario
|
||||||
- contraseña: Contraseña del usuario
|
- contraseña: Contraseña del usuario
|
||||||
|
|
||||||
**Retorna:**
|
**Retorna:**
|
||||||
- access_token: Token JWT para usar en requests autenticados
|
- access_token: Token JWT para usar en requests autenticados
|
||||||
- token_type: Tipo de token (bearer)
|
- token_type: Tipo de token (bearer)
|
||||||
@@ -30,55 +44,26 @@ async def login_user(credentials: UserLoginRequest):
|
|||||||
- email: Email confirmado
|
- email: Email confirmado
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Obtener usuario por email
|
|
||||||
get_use_case = GetUserByEmail(user_repo)
|
get_use_case = GetUserByEmail(user_repo)
|
||||||
user = get_use_case.execute(credentials.email)
|
user = get_use_case.execute(credentials.email)
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Email o contraseña incorrectos", headers={"WWW-Authenticate": "Bearer"})
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Email o contraseña incorrectos",
|
|
||||||
headers={"WWW-Authenticate": "Bearer"}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verificar contraseña
|
|
||||||
# Necesitamos obtener el hash de contraseña del modelo
|
|
||||||
user_model = user_repo.find_by_email_with_password(credentials.email)
|
user_model = user_repo.find_by_email_with_password(credentials.email)
|
||||||
if not user_model or not auth_service.verify_password(credentials.contraseña, user_model.contraseña_hash):
|
if not user_model or not auth_service.verify_password(credentials.contraseña, user_model.contraseña_hash):
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Email o contraseña incorrectos", headers={"WWW-Authenticate": "Bearer"})
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
access_token = auth_service.create_access_token(user_id=user.user_id, email=user.email)
|
||||||
detail="Email o contraseña incorrectos",
|
return {"access_token": access_token, "token_type": "bearer", "user_id": user.user_id, "email": user.email, "is_admin": user.is_admin}
|
||||||
headers={"WWW-Authenticate": "Bearer"}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Crear token JWT
|
|
||||||
access_token = auth_service.create_access_token(
|
|
||||||
user_id=user.user_id,
|
|
||||||
email=user.email
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"access_token": access_token,
|
|
||||||
"token_type": "bearer",
|
|
||||||
"user_id": user.user_id,
|
|
||||||
"email": user.email,
|
|
||||||
"is_admin": user.is_admin
|
|
||||||
}
|
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error en login_user: {e}", exc_info=True)
|
logger.error(f"Error en login_user: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.post("/", status_code=status.HTTP_202_ACCEPTED)
|
@router.post("/", status_code=status.HTTP_202_ACCEPTED)
|
||||||
async def create_user(user_data: UserCreateRequest):
|
async def create_user(user_data: UserCreateRequest):
|
||||||
"""
|
"""
|
||||||
Crea un nuevo usuario - envía a cola de procesamiento con validaciones previas
|
Crea un nuevo usuario - envía a cola de procesamiento con validaciones previas
|
||||||
|
|
||||||
**Parámetros:**
|
**Parámetros:**
|
||||||
- nombre: Nombre del usuario (requerido)
|
- nombre: Nombre del usuario (requerido)
|
||||||
- apellido: Apellido del usuario (requerido)
|
- apellido: Apellido del usuario (requerido)
|
||||||
@@ -91,42 +76,22 @@ async def create_user(user_data: UserCreateRequest):
|
|||||||
try:
|
try:
|
||||||
create_use_case = CreateUser(user_repo)
|
create_use_case = CreateUser(user_repo)
|
||||||
result = create_use_case.execute(
|
result = create_use_case.execute(
|
||||||
nombre=user_data.nombre,
|
nombre=user_data.nombre, apellido=user_data.apellido, email=user_data.email,
|
||||||
apellido=user_data.apellido,
|
contraseña=user_data.contraseña, fecha_nacimiento=user_data.fecha_nacimiento,
|
||||||
email=user_data.email,
|
url_foto_perfil=user_data.url_foto_perfil, biografia=user_data.biografia
|
||||||
contraseña=user_data.contraseña,
|
|
||||||
fecha_nacimiento=user_data.fecha_nacimiento,
|
|
||||||
url_foto_perfil=user_data.url_foto_perfil,
|
|
||||||
biografia=user_data.biografia
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
# Detectar tipo de error para código HTTP apropiado
|
|
||||||
message = result["message"]
|
message = result["message"]
|
||||||
if "ya está registrado" in message:
|
if "ya está registrado" in message:
|
||||||
# 409 Conflict: email duplicado
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=message)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||||
status_code=status.HTTP_409_CONFLICT,
|
_publish_user_event(UserEventType.CREATE, user_id=result.get("user_id"), email=user_data.email, nombre=user_data.nombre, apellido=user_data.apellido)
|
||||||
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
|
return result
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error inesperado en create_user: {e}", exc_info=True)
|
logger.error(f"Error inesperado en create_user: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get("/{user_id}", response_model=UserResponse)
|
@router.get("/{user_id}", response_model=UserResponse)
|
||||||
async def get_user(user_id: int):
|
async def get_user(user_id: int):
|
||||||
@@ -135,19 +100,13 @@ async def get_user(user_id: int):
|
|||||||
get_use_case = GetUserById(user_repo)
|
get_use_case = GetUserById(user_repo)
|
||||||
user = get_use_case.execute(user_id)
|
user = get_use_case.execute(user_id)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Usuario con ID {user_id} no encontrado")
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"Usuario con ID {user_id} no encontrado"
|
|
||||||
)
|
|
||||||
return user
|
return user
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al obtener usuario {user_id}: {e}", exc_info=True)
|
logger.error(f"Error al obtener usuario {user_id}: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get("/email/{email}", response_model=UserResponse)
|
@router.get("/email/{email}", response_model=UserResponse)
|
||||||
async def get_user_by_email(email: str):
|
async def get_user_by_email(email: str):
|
||||||
@@ -156,19 +115,13 @@ async def get_user_by_email(email: str):
|
|||||||
get_use_case = GetUserByEmail(user_repo)
|
get_use_case = GetUserByEmail(user_repo)
|
||||||
user = get_use_case.execute(email)
|
user = get_use_case.execute(email)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Usuario con email {email} no encontrado")
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
|
||||||
detail=f"Usuario con email {email} no encontrado"
|
|
||||||
)
|
|
||||||
return user
|
return user
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al obtener usuario por email {email}: {e}", exc_info=True)
|
logger.error(f"Error al obtener usuario por email {email}: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/")
|
||||||
async def list_users():
|
async def list_users():
|
||||||
@@ -178,50 +131,26 @@ async def list_users():
|
|||||||
return list_use_case.execute()
|
return list_use_case.execute()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al listar usuarios: {e}", exc_info=True)
|
logger.error(f"Error al listar usuarios: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.put("/{user_id}", status_code=status.HTTP_202_ACCEPTED)
|
@router.put("/{user_id}", status_code=status.HTTP_202_ACCEPTED)
|
||||||
async def update_user(user_id: int, user_data: UserUpdateRequest):
|
async def update_user(user_id: int, user_data: UserUpdateRequest):
|
||||||
"""Actualiza un usuario - envía a cola de procesamiento con validaciones previas"""
|
"""Actualiza un usuario - envía a cola de procesamiento con validaciones previas"""
|
||||||
try:
|
try:
|
||||||
update_use_case = UpdateUser(user_repo)
|
update_use_case = UpdateUser(user_repo)
|
||||||
result = update_use_case.execute(
|
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)
|
||||||
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":
|
if result["status"] == "error":
|
||||||
message = result["message"]
|
message = result["message"]
|
||||||
if "no existe" in message:
|
if "no existe" in message:
|
||||||
# 404 Not Found: usuario no existe
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=message)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
_publish_user_event(UserEventType.UPDATE, user_id=user_id, nombre=user_data.nombre, apellido=user_data.apellido)
|
||||||
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
|
return result
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al actualizar usuario {user_id}: {e}", exc_info=True)
|
logger.error(f"Error al actualizar usuario {user_id}: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
|
|
||||||
@router.delete("/{user_id}", status_code=status.HTTP_202_ACCEPTED)
|
@router.delete("/{user_id}", status_code=status.HTTP_202_ACCEPTED)
|
||||||
async def delete_user(user_id: int):
|
async def delete_user(user_id: int):
|
||||||
@@ -229,30 +158,15 @@ async def delete_user(user_id: int):
|
|||||||
try:
|
try:
|
||||||
delete_use_case = DeleteUser(user_repo)
|
delete_use_case = DeleteUser(user_repo)
|
||||||
result = delete_use_case.execute(user_id)
|
result = delete_use_case.execute(user_id)
|
||||||
|
|
||||||
if result["status"] == "error":
|
if result["status"] == "error":
|
||||||
message = result["message"]
|
message = result["message"]
|
||||||
if "no existe" in message:
|
if "no existe" in message:
|
||||||
# 404 Not Found: usuario no existe
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=message)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=message)
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
_publish_user_event(UserEventType.DELETE, user_id=user_id)
|
||||||
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
|
return result
|
||||||
|
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error al eliminar usuario {user_id}: {e}", exc_info=True)
|
logger.error(f"Error al eliminar usuario {user_id}: {e}", exc_info=True)
|
||||||
raise HTTPException(
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Error interno del servidor")
|
||||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
||||||
detail="Error interno del servidor"
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user