0
src/infrastructure/api/moderations/__init__.py
Normal file
0
src/infrastructure/api/moderations/__init__.py
Normal file
46
src/infrastructure/api/moderations/app.py
Normal file
46
src/infrastructure/api/moderations/app.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""Aplicación FastAPI para API de Moderación"""
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from infrastructure.api.moderations.router import router
|
||||
from core.config import ConfSettings
|
||||
import logging
|
||||
|
||||
# Configurar logging
|
||||
logging.basicConfig(
|
||||
level=ConfSettings.log_level.upper(),
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""
|
||||
Factory function para crear la aplicación FastAPI de Moderación
|
||||
|
||||
Returns:
|
||||
FastAPI application instance
|
||||
"""
|
||||
app = FastAPI(
|
||||
title="VoxPopuli Moderation API",
|
||||
description="API para gestión de acciones de moderación en VoxPopuli",
|
||||
version="1.0.0",
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
openapi_url="/openapi.json"
|
||||
)
|
||||
|
||||
# Agregar CORS middleware
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=ConfSettings.cors_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Incluir rutas
|
||||
app.include_router(router)
|
||||
|
||||
logger.info("Moderation API initialized")
|
||||
|
||||
return app
|
||||
182
src/infrastructure/api/moderations/moderations.py
Normal file
182
src/infrastructure/api/moderations/moderations.py
Normal file
@@ -0,0 +1,182 @@
|
||||
"""Endpoints de moderación para gestión de reportes, cuentas y usuarios"""
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
from infrastructure.api.moderations.schemas import (
|
||||
DeleteReportRequest,
|
||||
CloseAccountRequest,
|
||||
BanUserRequest,
|
||||
WarnUserRequest,
|
||||
ReviewContentRequest,
|
||||
ModerationActionResponse
|
||||
)
|
||||
from application.services.moderation_services import (
|
||||
DeleteReportUseCase,
|
||||
CloseAccountUseCase,
|
||||
BanUserUseCase,
|
||||
WarnUserUseCase,
|
||||
ReviewContentUseCase
|
||||
)
|
||||
from infrastructure.adapters.persistence.mongodb import mongo_db
|
||||
from infrastructure.adapters.persistence.db import get_db
|
||||
from infrastructure.adapters.moderation_repository_mongo import ModerationRepositoryMongo
|
||||
from sqlalchemy.orm import Session
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
# Instanciar repositorio de moderación
|
||||
moderation_repo = ModerationRepositoryMongo()
|
||||
|
||||
|
||||
@router.post("/reports/delete", response_model=ModerationActionResponse)
|
||||
async def delete_report(request: DeleteReportRequest):
|
||||
"""
|
||||
Eliminar un reporte como moderador
|
||||
|
||||
- **moderator_id**: ID del moderador que ejecuta la acción
|
||||
- **report_id**: ID del reporte a eliminar
|
||||
- **reason**: Razón de la eliminación (mínimo 5 caracteres)
|
||||
- **description**: Descripción adicional (opcional)
|
||||
"""
|
||||
try:
|
||||
use_case = DeleteReportUseCase(moderation_repo)
|
||||
result = use_case.execute(
|
||||
moderator_id=request.moderator_id,
|
||||
report_id=request.report_id,
|
||||
reason=request.reason,
|
||||
description=request.description
|
||||
)
|
||||
|
||||
if result["status"] == "error":
|
||||
raise HTTPException(status_code=400, detail=result["message"])
|
||||
|
||||
return ModerationActionResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in delete_report: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||
|
||||
|
||||
@router.post("/accounts/close", response_model=ModerationActionResponse)
|
||||
async def close_account(request: CloseAccountRequest):
|
||||
"""
|
||||
Cerrar una cuenta de usuario
|
||||
|
||||
- **moderator_id**: ID del moderador
|
||||
- **user_id**: ID del usuario cuya cuenta cerrar
|
||||
- **reason**: Razón del cierre (mínimo 5 caracteres)
|
||||
- **description**: Descripción adicional (opcional)
|
||||
- **is_permanent**: Si el cierre es permanente (por defecto True)
|
||||
"""
|
||||
try:
|
||||
use_case = CloseAccountUseCase(moderation_repo)
|
||||
result = use_case.execute(
|
||||
moderator_id=request.moderator_id,
|
||||
user_id=request.user_id,
|
||||
reason=request.reason,
|
||||
description=request.description,
|
||||
is_permanent=request.is_permanent
|
||||
)
|
||||
|
||||
if result["status"] == "error":
|
||||
raise HTTPException(status_code=400, detail=result["message"])
|
||||
|
||||
return ModerationActionResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in close_account: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||
|
||||
|
||||
@router.post("/users/ban", response_model=ModerationActionResponse)
|
||||
async def ban_user(request: BanUserRequest):
|
||||
"""
|
||||
Banear a un usuario
|
||||
|
||||
- **moderator_id**: ID del moderador
|
||||
- **user_id**: ID del usuario a banear
|
||||
- **reason**: Razón del ban (mínimo 5 caracteres)
|
||||
- **duration_days**: Duración del ban en días (None para permanente)
|
||||
- **description**: Descripción adicional (opcional)
|
||||
"""
|
||||
try:
|
||||
use_case = BanUserUseCase(moderation_repo)
|
||||
result = use_case.execute(
|
||||
moderator_id=request.moderator_id,
|
||||
user_id=request.user_id,
|
||||
reason=request.reason,
|
||||
duration_days=request.duration_days,
|
||||
description=request.description
|
||||
)
|
||||
|
||||
if result["status"] == "error":
|
||||
raise HTTPException(status_code=400, detail=result["message"])
|
||||
|
||||
return ModerationActionResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in ban_user: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||
|
||||
|
||||
@router.post("/users/warn", response_model=ModerationActionResponse)
|
||||
async def warn_user(request: WarnUserRequest):
|
||||
"""
|
||||
Advertir a un usuario
|
||||
|
||||
- **moderator_id**: ID del moderador
|
||||
- **user_id**: ID del usuario a advertir
|
||||
- **reason**: Razón de la advertencia (mínimo 5 caracteres)
|
||||
- **description**: Descripción adicional (opcional)
|
||||
"""
|
||||
try:
|
||||
use_case = WarnUserUseCase(moderation_repo)
|
||||
result = use_case.execute(
|
||||
moderator_id=request.moderator_id,
|
||||
user_id=request.user_id,
|
||||
reason=request.reason,
|
||||
description=request.description
|
||||
)
|
||||
|
||||
if result["status"] == "error":
|
||||
raise HTTPException(status_code=400, detail=result["message"])
|
||||
|
||||
return ModerationActionResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in warn_user: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||
|
||||
|
||||
@router.post("/content/review", response_model=ModerationActionResponse)
|
||||
async def review_content(request: ReviewContentRequest):
|
||||
"""
|
||||
Revisar y actuar sobre contenido reportado
|
||||
|
||||
- **moderator_id**: ID del moderador
|
||||
- **report_id**: ID del reporte a revisar
|
||||
- **action**: Acción a tomar (approve, reject, needs_more_info)
|
||||
- **reason**: Razón de la decisión (opcional)
|
||||
- **notes**: Notas adicionales (opcional)
|
||||
"""
|
||||
try:
|
||||
use_case = ReviewContentUseCase(moderation_repo)
|
||||
result = use_case.execute(
|
||||
moderator_id=request.moderator_id,
|
||||
report_id=request.report_id,
|
||||
action=request.action,
|
||||
reason=request.reason,
|
||||
notes=request.notes
|
||||
)
|
||||
|
||||
if result["status"] == "error":
|
||||
raise HTTPException(status_code=400, detail=result["message"])
|
||||
|
||||
return ModerationActionResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in review_content: {e}")
|
||||
raise HTTPException(status_code=500, detail="Error interno del servidor")
|
||||
32
src/infrastructure/api/moderations/root.py
Normal file
32
src/infrastructure/api/moderations/root.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""Rutas raíz para API de Moderación"""
|
||||
from fastapi import APIRouter, Response
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def root():
|
||||
"""Root endpoint - información de la API"""
|
||||
return JSONResponse({
|
||||
"status": "ok",
|
||||
"service": "VoxPopuli Moderation API",
|
||||
"version": "1.0.0",
|
||||
"description": "API para gestión de acciones de moderación",
|
||||
"endpoints": {
|
||||
"delete_report": "POST /moderation/reports/delete",
|
||||
"close_account": "POST /moderation/accounts/close",
|
||||
"ban_user": "POST /moderation/users/ban",
|
||||
"warn_user": "POST /moderation/users/warn",
|
||||
"review_content": "POST /moderation/content/review"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint"""
|
||||
return JSONResponse({
|
||||
"status": "healthy",
|
||||
"service": "Moderation API"
|
||||
})
|
||||
18
src/infrastructure/api/moderations/router.py
Normal file
18
src/infrastructure/api/moderations/router.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""Router principal para API de Moderación"""
|
||||
from fastapi import APIRouter
|
||||
from infrastructure.api.moderations.moderations import router as moderations_router
|
||||
from infrastructure.api.moderations.root import router as root_router
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(
|
||||
moderations_router,
|
||||
prefix="/moderation",
|
||||
tags=["moderation"]
|
||||
)
|
||||
|
||||
router.include_router(
|
||||
root_router,
|
||||
prefix='',
|
||||
tags=["root"]
|
||||
)
|
||||
60
src/infrastructure/api/moderations/schemas.py
Normal file
60
src/infrastructure/api/moderations/schemas.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Pydantic schemas para validación de datos en API de Moderación"""
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, Literal
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DeleteReportRequest(BaseModel):
|
||||
"""Esquema para solicitud de eliminación de reporte"""
|
||||
moderator_id: int = Field(..., description="ID del moderador")
|
||||
report_id: str = Field(..., description="ID del reporte a eliminar")
|
||||
reason: str = Field(..., min_length=5, description="Razón de la eliminación")
|
||||
description: Optional[str] = Field(None, description="Descripción adicional")
|
||||
|
||||
|
||||
class CloseAccountRequest(BaseModel):
|
||||
"""Esquema para solicitud de cierre de cuenta"""
|
||||
moderator_id: int = Field(..., description="ID del moderador")
|
||||
user_id: int = Field(..., description="ID del usuario")
|
||||
reason: str = Field(..., min_length=5, description="Razón del cierre")
|
||||
description: Optional[str] = Field(None, description="Descripción adicional")
|
||||
is_permanent: bool = Field(True, description="Si el cierre es permanente")
|
||||
|
||||
|
||||
class BanUserRequest(BaseModel):
|
||||
"""Esquema para solicitud de ban de usuario"""
|
||||
moderator_id: int = Field(..., description="ID del moderador")
|
||||
user_id: int = Field(..., description="ID del usuario a banear")
|
||||
reason: str = Field(..., min_length=5, description="Razón del ban")
|
||||
duration_days: Optional[int] = Field(None, description="Duración en días (None para permanente)")
|
||||
description: Optional[str] = Field(None, description="Descripción adicional")
|
||||
|
||||
|
||||
class WarnUserRequest(BaseModel):
|
||||
"""Esquema para solicitud de advertencia"""
|
||||
moderator_id: int = Field(..., description="ID del moderador")
|
||||
user_id: int = Field(..., description="ID del usuario a advertir")
|
||||
reason: str = Field(..., min_length=5, description="Razón de la advertencia")
|
||||
description: Optional[str] = Field(None, description="Descripción adicional")
|
||||
|
||||
|
||||
class ReviewContentRequest(BaseModel):
|
||||
"""Esquema para solicitud de revisión de contenido"""
|
||||
moderator_id: int = Field(..., description="ID del moderador")
|
||||
report_id: str = Field(..., description="ID del reporte a revisar")
|
||||
action: Literal["approve", "reject", "needs_more_info"] = Field(..., description="Acción de revisión")
|
||||
reason: Optional[str] = Field(None, description="Razón de la decisión")
|
||||
notes: Optional[str] = Field(None, description="Notas adicionales")
|
||||
|
||||
|
||||
class ModerationActionResponse(BaseModel):
|
||||
"""Esquema de respuesta para acciones de moderación"""
|
||||
status: str = Field(..., description="Estado de la acción (success/error)")
|
||||
message: str = Field(..., description="Mensaje descriptivo")
|
||||
action_id: Optional[str] = Field(None, description="ID de la acción creada")
|
||||
|
||||
|
||||
class ActionStatusRequest(BaseModel):
|
||||
"""Esquema para actualizar estado de acción"""
|
||||
status: Literal["pending", "approved", "rejected", "executed"] = Field(..., description="Nuevo estado")
|
||||
notes: Optional[str] = Field(None, description="Notas sobre el cambio de estado")
|
||||
Reference in New Issue
Block a user