This commit is contained in:
2026-05-04 22:07:30 -06:00
parent a8ee92afc8
commit b4fc640c1a
5 changed files with 245 additions and 27 deletions

53
ADMIN_SETUP.md Normal file
View File

@@ -0,0 +1,53 @@
# Admin User Setup - SQL Commands
## 1. Agregar columna a BD (Auto-ejecutado)
La migración SQL se encuentra en: `migrations/001_add_is_admin_to_usuarios.sql`
Ejecutar manualmente si es necesario:
```sql
ALTER TABLE `voxpopuli_users`.`usuarios`
ADD COLUMN `is_admin` BOOLEAN NOT NULL DEFAULT FALSE AFTER `biografia`,
ADD INDEX `idx_is_admin` (`is_admin`);
```
## 2. Promover usuario a admin
```sql
UPDATE `voxpopuli_users`.`usuarios`
SET `is_admin` = TRUE
WHERE `user_id` = 1;
```
## 3. Listar usuarios admin
```sql
SELECT `user_id`, `nombre`, `email`, `is_admin`
FROM `voxpopuli_users`.`usuarios`
WHERE `is_admin` = TRUE;
```
## 4. Revocar permisos admin
```sql
UPDATE `voxpopuli_users`.`usuarios`
SET `is_admin` = FALSE
WHERE `user_id` = 1;
```
## API Usage
### Endpoints de Moderación (requieren token JWT de admin)
```bash
# Usar token JWT en header Authorization
curl -X POST "http://localhost:8003/moderation/reports/delete" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"report_id": 123,
"reason": "Contenido ofensivo",
"description": "Violación de políticas"
}'
```
### Obtener usuario y verificar is_admin
```bash
GET http://localhost:8000/users/{user_id}
# Response incluye "is_admin": true/false
```

View File

@@ -1,6 +1,6 @@
# Metrics API Examples # Metrics API Examples
## Registrar métrica manualmente ## Record Metric (Manual)
```bash ```bash
curl -X POST "http://localhost:8004/metrics/record" \ curl -X POST "http://localhost:8004/metrics/record" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
@@ -13,29 +13,74 @@ curl -X POST "http://localhost:8004/metrics/record" \
}' }'
``` ```
## Obtener reporte de últimos 7 días ## Get Analytics Report (Last N Days)
```bash ```bash
curl "http://localhost:8004/metrics/report?days=7" curl "http://localhost:8004/metrics/report?days=7"
``` ```
## Obtener estadísticas de un día específico Response:
```json
{
"start_date": "2026-04-27T00:00:00",
"end_date": "2026-05-04T23:59:59",
"total_events": 245,
"events_by_type": {
"user_created": 12,
"report_created": 45,
"notification_sent": 188
}
}
```
## Get Daily Statistics
```bash ```bash
curl "http://localhost:8004/metrics/daily-stats?date=2026-05-04T00:00:00" curl "http://localhost:8004/metrics/daily-stats?date=2026-05-04T00:00:00"
``` ```
## Obtener resumen de eventos en rango Response:
```json
[
{
"date": "2026-05-04T00:00:00",
"event_type": "user_created",
"count": 3
},
{
"date": "2026-05-04T00:00:00",
"event_type": "report_created",
"count": 8
}
]
```
## Get Event Summary (Date Range)
```bash ```bash
curl "http://localhost:8004/metrics/summary?start_date=2026-04-27T00:00:00&end_date=2026-05-04T23:59:59" curl "http://localhost:8004/metrics/summary?start_date=2026-04-27T00:00:00&end_date=2026-05-04T23:59:59"
``` ```
## Health check Response:
```json
{
"summary": {
"user_created": 12,
"report_created": 45,
"notification_sent": 188,
"moderation_completed": 5
},
"date_range": {
"start_date": "2026-04-27T00:00:00",
"end_date": "2026-05-04T23:59:59"
}
}
```
## Health Check
```bash ```bash
curl "http://localhost:8004/metrics/health" curl "http://localhost:8004/metrics/health"
``` ```
## Eventos automáticos (RabbitMQ) ## Automatic Events (RabbitMQ)
El consumer de métricas escucha automáticamente eventos de: - `user_created`, `user_updated`, `user_deleted`
- Users Queue: `user_created`, `user_updated`, `user_deleted` - `report_created`, `report_resolved`
- Reports Queue: `report_created`, `report_resolved` - `notification_sent`
- Notifications Queue: `notification_sent` - `moderation_completed`
- Moderations Queue: `moderation_completed`

View File

@@ -1,13 +1,13 @@
{ {
"moderation_examples": { "moderation_examples": {
"delete_report": { "delete_report": {
"description": "Eliminar un reporte inapropiado", "description": "Eliminar un reporte (requiere token JWT de admin)",
"endpoint": "POST /moderation/reports/delete", "endpoint": "POST /moderation/reports/delete",
"headers": { "headers": {
"Content-Type": "application/json" "Content-Type": "application/json",
"Authorization": "Bearer YOUR_JWT_TOKEN"
}, },
"request_body": { "request_body": {
"moderator_id": 1,
"report_id": "550e8400-e29b-41d4-a716-446655440000", "report_id": "550e8400-e29b-41d4-a716-446655440000",
"reason": "Contenido violento", "reason": "Contenido violento",
"description": "La imagen contiene violencia explícita" "description": "La imagen contiene violencia explícita"
@@ -16,40 +16,110 @@
"status": 200, "status": 200,
"body": { "body": {
"status": "success", "status": "success",
"message": "Reporte marcado para eliminación", "message": "Reporte eliminado",
"action_id": "action-uuid-123" "action_id": "action-uuid-123"
} }
}, },
"response_error": { "response_error_401": {
"status": 400, "status": 401,
"body": { "body": {"detail": "Token inválido o expirado"}
"detail": "Razón debe tener al menos 5 caracteres" },
} "response_error_403": {
"status": 403,
"body": {"detail": "Permisos insuficientes"}
} }
}, },
"close_account": { "close_account": {
"description": "Cerrar una cuenta de usuario por violación de términos", "description": "Cerrar cuenta de usuario (requiere token JWT de admin)",
"endpoint": "POST /moderation/accounts/close", "endpoint": "POST /moderation/accounts/close",
"headers": { "headers": {
"Content-Type": "application/json" "Content-Type": "application/json",
"Authorization": "Bearer YOUR_JWT_TOKEN"
}, },
"request_body": { "request_body": {
"moderator_id": 1,
"user_id": 42, "user_id": 42,
"reason": "Violación grave de términos de servicio", "reason": "Violación grave de términos",
"description": "Comportamiento acosador y spam sistemático", "description": "Comportamiento acosador",
"is_permanent": true "is_permanent": true
}, },
"response_success": { "response_success": {
"status": 200, "status": 200,
"body": { "body": {
"status": "success", "status": "success",
"message": "Cuenta marcada para cierre", "message": "Cuenta cerrada",
"action_id": "action-uuid-456" "action_id": "action-uuid-456"
} }
} }
}, },
"ban_user": {
"description": "Banear usuario (requiere token JWT de admin)",
"endpoint": "POST /moderation/users/ban",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_JWT_TOKEN"
},
"request_body": {
"user_id": 42,
"reason": "Spam sistemático",
"duration_days": 30,
"description": "Múltiples reportes de spam"
},
"response_success": {
"status": 200,
"body": {
"status": "success",
"message": "Usuario baneado por 30 días",
"action_id": "action-uuid-789"
}
}
},
"warn_user": {
"description": "Advertir usuario (requiere token JWT de admin)",
"endpoint": "POST /moderation/users/warn",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_JWT_TOKEN"
},
"request_body": {
"user_id": 42,
"reason": "Lenguaje inapropiado",
"description": "Primera advertencia"
},
"response_success": {
"status": 200,
"body": {
"status": "success",
"message": "Usuario advertido",
"action_id": "action-uuid-101"
}
}
},
"review_content": {
"description": "Revisar contenido reportado (requiere token JWT de admin)",
"endpoint": "POST /moderation/content/review",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer YOUR_JWT_TOKEN"
},
"request_body": {
"report_id": "550e8400-e29b-41d4-a716-446655440000",
"action": "approve",
"reason": "Contenido válido según políticas",
"notes": "Aprobado después de revisión"
},
"response_success": {
"status": 200,
"body": {
"status": "success",
"message": "Decisión registrada",
"action_id": "action-uuid-202"
}
}
}
}
}
}
},
"ban_user": { "ban_user": {
"description": "Banear un usuario temporalmente", "description": "Banear un usuario temporalmente",

View File

@@ -12,3 +12,4 @@ PyJWT
passlib[argon2] passlib[argon2]
python-multipart python-multipart
psycopg2-binary psycopg2-binary
cryptography

View File

@@ -0,0 +1,49 @@
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer
import jwt
from core.config import ConfSettings
from infrastructure.adapters.persistence.user_repository_sql import UserRepositorySQL
import logging
logger = logging.getLogger(__name__)
security = HTTPBearer()
async def get_current_admin_user(credentials = Depends(security)):
try:
payload = jwt.decode(
credentials.credentials,
ConfSettings.jwt_secret_key,
algorithms=[ConfSettings.jwt_algorithm]
)
user_id: int = payload.get("sub")
if user_id is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token inválido",
headers={"WWW-Authenticate": "Bearer"},
)
except jwt.InvalidTokenError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token inválido o expirado",
headers={"WWW-Authenticate": "Bearer"},
)
user_repo = UserRepositorySQL()
user = user_repo.find_by_id(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Usuario no encontrado",
)
if not user.is_admin:
logger.warning(f"Intento de acceso no autorizado a moderación por usuario {user_id}")
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Permisos insuficientes",
)
return user