Added two new apis

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-04 17:37:08 -06:00
parent 2be72ae7be
commit b3788beedd
30 changed files with 2736 additions and 2 deletions

View File

@@ -0,0 +1,176 @@
"""Adaptador de repositorio de Moderación con MongoDB"""
from application.ports.moderation_repository import ModerationRepository
from domain.moderations import ModerationAction, ModerationLog
from typing import List, Optional
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
class ModerationRepositoryMongo(ModerationRepository):
"""Implementación de ModerationRepository usando MongoDB"""
def __init__(self):
self.db = None
self.moderation_actions_collection = None
self.moderation_logs_collection = None
self._initialize_db()
def _initialize_db(self):
"""Inicializa conexión a MongoDB"""
try:
from infrastructure.adapters.persistence.mongodb import mongo_db
self.db = mongo_db
# Obtener o crear colecciones
self.moderation_actions_collection = self.db['moderation_actions']
self.moderation_logs_collection = self.db['moderation_logs']
# Crear índices
self.moderation_actions_collection.create_index('action_id')
self.moderation_actions_collection.create_index('moderator_id')
self.moderation_actions_collection.create_index('target_id')
self.moderation_logs_collection.create_index('action_id')
logger.info("Moderation MongoDB adapter initialized")
except Exception as e:
logger.error(f"Error initializing MongoDB for moderations: {e}")
raise
def save_moderation_action(self, action: ModerationAction) -> bool:
"""Guarda una acción de moderación en MongoDB"""
try:
action_dict = {
'action_id': action.action_id,
'moderator_id': action.moderator_id,
'action_type': action.action_type,
'target_type': action.target_type,
'target_id': action.target_id,
'reason': action.reason,
'description': action.description,
'duration_days': action.duration_days,
'is_permanent': action.is_permanent,
'status': action.status,
'fecha_creacion': action.fecha_creacion or datetime.now(),
'fecha_ejecucion': action.fecha_ejecucion,
'observaciones': action.observaciones
}
self.moderation_actions_collection.insert_one(action_dict)
logger.info(f"Moderation action saved: {action.action_id}")
return True
except Exception as e:
logger.error(f"Error saving moderation action: {e}")
return False
def get_moderation_action(self, action_id: str) -> Optional[ModerationAction]:
"""Obtiene una acción de moderación por ID"""
try:
doc = self.moderation_actions_collection.find_one({'action_id': action_id})
if doc:
return self._doc_to_moderation_action(doc)
return None
except Exception as e:
logger.error(f"Error getting moderation action: {e}")
return None
def get_actions_by_moderator(self, moderator_id: int) -> List[ModerationAction]:
"""Obtiene todas las acciones de un moderador"""
try:
docs = self.moderation_actions_collection.find({'moderator_id': moderator_id})
return [self._doc_to_moderation_action(doc) for doc in docs]
except Exception as e:
logger.error(f"Error getting actions by moderator: {e}")
return []
def get_actions_by_target(self, target_id: str, target_type: str) -> List[ModerationAction]:
"""Obtiene todas las acciones sobre un target específico"""
try:
docs = self.moderation_actions_collection.find({
'target_id': target_id,
'target_type': target_type
})
return [self._doc_to_moderation_action(doc) for doc in docs]
except Exception as e:
logger.error(f"Error getting actions by target: {e}")
return []
def update_action_status(self, action_id: str, status: str) -> bool:
"""Actualiza el estado de una acción"""
try:
result = self.moderation_actions_collection.update_one(
{'action_id': action_id},
{'$set': {'status': status}}
)
return result.modified_count > 0
except Exception as e:
logger.error(f"Error updating action status: {e}")
return False
def save_moderation_log(self, log: ModerationLog) -> bool:
"""Guarda un log de moderación"""
try:
log_dict = {
'log_id': log.log_id,
'action_id': log.action_id,
'moderator_id': log.moderator_id,
'action_type': log.action_type,
'target_type': log.target_type,
'target_id': log.target_id,
'resultado': log.resultado,
'mensaje': log.mensaje,
'fecha_log': log.fecha_log or datetime.now(),
'ip_moderator': log.ip_moderator
}
self.moderation_logs_collection.insert_one(log_dict)
logger.info(f"Moderation log saved: {log.log_id}")
return True
except Exception as e:
logger.error(f"Error saving moderation log: {e}")
return False
def get_moderation_logs(self, action_id: str) -> List[ModerationLog]:
"""Obtiene todos los logs de una acción"""
try:
docs = self.moderation_logs_collection.find({'action_id': action_id})
return [self._doc_to_moderation_log(doc) for doc in docs]
except Exception as e:
logger.error(f"Error getting moderation logs: {e}")
return []
@staticmethod
def _doc_to_moderation_action(doc: dict) -> ModerationAction:
"""Convierte un documento de MongoDB a ModerationAction"""
return ModerationAction(
action_id=doc.get('action_id'),
moderator_id=doc.get('moderator_id'),
action_type=doc.get('action_type'),
target_type=doc.get('target_type'),
target_id=doc.get('target_id'),
reason=doc.get('reason'),
description=doc.get('description'),
duration_days=doc.get('duration_days'),
is_permanent=doc.get('is_permanent', False),
status=doc.get('status', 'pending'),
fecha_creacion=doc.get('fecha_creacion'),
fecha_ejecucion=doc.get('fecha_ejecucion'),
observaciones=doc.get('observaciones')
)
@staticmethod
def _doc_to_moderation_log(doc: dict) -> ModerationLog:
"""Convierte un documento de MongoDB a ModerationLog"""
return ModerationLog(
log_id=doc.get('log_id'),
action_id=doc.get('action_id'),
moderator_id=doc.get('moderator_id'),
action_type=doc.get('action_type'),
target_type=doc.get('target_type'),
target_id=doc.get('target_id'),
resultado=doc.get('resultado'),
mensaje=doc.get('mensaje'),
fecha_log=doc.get('fecha_log'),
ip_moderator=doc.get('ip_moderator')
)

View File

@@ -0,0 +1,136 @@
from datetime import datetime
from typing import List, Dict, Optional
from sqlalchemy import Column, Integer, String, DateTime, JSON, create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
from sqlalchemy.sql import func
from domain.metrics import Metric, DailyStats, AnalyticsReport, EventType
from application.ports.metrics_repository import MetricsRepository
from core.config import ConfSettings
Base = declarative_base()
class MetricModel(Base):
"""Modelo SQLAlchemy para métricas en Postgres"""
__tablename__ = "metrics"
id = Column(Integer, primary_key=True, autoincrement=True)
event_type = Column(String, nullable=False, index=True)
entity_id = Column(String, nullable=False)
entity_type = Column(String, nullable=False)
timestamp = Column(DateTime, nullable=False, default=datetime.now, index=True)
metadata = Column(JSON, default={})
user_id = Column(Integer, nullable=True)
class MetricsRepositoryPostgres(MetricsRepository):
"""Implementación de repositorio de métricas con PostgreSQL"""
def __init__(self):
db_url = f"postgresql://voxpopuli:voxpopuli_pass@localhost:5432/voxpopuli_metrics"
self.engine = create_engine(db_url, echo=False)
Base.metadata.create_all(self.engine)
self.SessionLocal = sessionmaker(bind=self.engine)
def save_metric(self, metric: Metric) -> Metric:
"""Guarda una métrica en la base de datos"""
session = self.SessionLocal()
try:
db_metric = MetricModel(
event_type=metric.event_type,
entity_id=metric.entity_id,
entity_type=metric.entity_type,
timestamp=metric.timestamp,
metadata=metric.metadata,
user_id=metric.user_id
)
session.add(db_metric)
session.commit()
metric.metric_id = db_metric.id
return metric
finally:
session.close()
def get_metrics_by_date_range(self, start_date: datetime, end_date: datetime) -> List[Metric]:
"""Obtiene métricas en un rango de fechas"""
session = self.SessionLocal()
try:
db_metrics = session.query(MetricModel).filter(
MetricModel.timestamp >= start_date,
MetricModel.timestamp <= end_date
).all()
return [
Metric(
metric_id=m.id,
event_type=m.event_type,
entity_id=m.entity_id,
entity_type=m.entity_type,
timestamp=m.timestamp,
metadata=m.metadata or {},
user_id=m.user_id
)
for m in db_metrics
]
finally:
session.close()
def get_daily_stats(self, date: datetime) -> List[DailyStats]:
"""Obtiene estadísticas diarias"""
session = self.SessionLocal()
try:
start = datetime(date.year, date.month, date.day)
end = datetime(date.year, date.month, date.day, 23, 59, 59)
results = session.query(
MetricModel.event_type,
func.count(MetricModel.id).label('count')
).filter(
MetricModel.timestamp >= start,
MetricModel.timestamp <= end
).group_by(MetricModel.event_type).all()
return [
DailyStats(date=date, event_type=r[0], count=r[1])
for r in results
]
finally:
session.close()
def get_event_count_by_type(self, start_date: datetime, end_date: datetime) -> Dict[str, int]:
"""Obtiene conteo de eventos por tipo"""
session = self.SessionLocal()
try:
results = session.query(
MetricModel.event_type,
func.count(MetricModel.id).label('count')
).filter(
MetricModel.timestamp >= start_date,
MetricModel.timestamp <= end_date
).group_by(MetricModel.event_type).all()
return {r[0]: r[1] for r in results}
finally:
session.close()
def generate_report(self, start_date: datetime, end_date: datetime) -> AnalyticsReport:
"""Genera reporte de analítica"""
session = self.SessionLocal()
try:
total_events = session.query(func.count(MetricModel.id)).filter(
MetricModel.timestamp >= start_date,
MetricModel.timestamp <= end_date
).scalar() or 0
events_by_type = self.get_event_count_by_type(start_date, end_date)
return AnalyticsReport(
report_id=None,
start_date=start_date,
end_date=end_date,
total_events=total_events,
events_by_type=events_by_type
)
finally:
session.close()

View File

@@ -27,6 +27,15 @@ class NotificationEventType(str, Enum):
REPORT_STATUS_CHANGE = "notification.report_status_change"
class ModerationEventType(str, Enum):
"""Types of moderation events"""
DELETE_REPORT = "moderation.delete_report"
CLOSE_ACCOUNT = "moderation.close_account"
BAN_USER = "moderation.ban_user"
WARN_USER = "moderation.warn_user"
REVIEW_CONTENT = "moderation.review_content"
@dataclass
class UserMessage:
"""Message for user events"""
@@ -122,3 +131,36 @@ class NotificationMessage:
"""Create from dictionary"""
data['event_type'] = NotificationEventType(data['event_type'])
return NotificationMessage(**data)
@dataclass
class ModerationMessage:
"""Message for moderation events"""
event_type: ModerationEventType
action_id: Optional[str] = None
moderator_id: Optional[int] = None
report_id: Optional[str] = None
user_id: Optional[int] = None
reason: Optional[str] = None
description: Optional[str] = None
duration_days: Optional[int] = None # Para bans temporales
is_permanent: Optional[bool] = None
review_action: Optional[str] = None # approve, reject, needs_more_info
notes: Optional[str] = None
fecha_creacion: Optional[str] = None # ISO format datetime string
def to_dict(self):
"""Convert to dictionary"""
data = asdict(self)
data['event_type'] = self.event_type.value
return data
def to_json(self) -> str:
"""Convert to JSON string"""
return json.dumps(self.to_dict())
@staticmethod
def from_dict(data: dict) -> 'ModerationMessage':
"""Create from dictionary"""
data['event_type'] = ModerationEventType(data['event_type'])
return ModerationMessage(**data)