stuffies
This commit is contained in:
53
ADMIN_SETUP.md
Normal file
53
ADMIN_SETUP.md
Normal 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
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# Metrics API Examples
|
||||
|
||||
## Registrar métrica manualmente
|
||||
## Record Metric (Manual)
|
||||
```bash
|
||||
curl -X POST "http://localhost:8004/metrics/record" \
|
||||
-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
|
||||
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
|
||||
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
|
||||
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
|
||||
curl "http://localhost:8004/metrics/health"
|
||||
```
|
||||
|
||||
## Eventos automáticos (RabbitMQ)
|
||||
El consumer de métricas escucha automáticamente eventos de:
|
||||
- Users Queue: `user_created`, `user_updated`, `user_deleted`
|
||||
- Reports Queue: `report_created`, `report_resolved`
|
||||
- Notifications Queue: `notification_sent`
|
||||
- Moderations Queue: `moderation_completed`
|
||||
## Automatic Events (RabbitMQ)
|
||||
- `user_created`, `user_updated`, `user_deleted`
|
||||
- `report_created`, `report_resolved`
|
||||
- `notification_sent`
|
||||
- `moderation_completed`
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"moderation_examples": {
|
||||
"delete_report": {
|
||||
"description": "Eliminar un reporte inapropiado",
|
||||
"description": "Eliminar un reporte (requiere token JWT de admin)",
|
||||
"endpoint": "POST /moderation/reports/delete",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer YOUR_JWT_TOKEN"
|
||||
},
|
||||
"request_body": {
|
||||
"moderator_id": 1,
|
||||
"report_id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"reason": "Contenido violento",
|
||||
"description": "La imagen contiene violencia explícita"
|
||||
@@ -16,40 +16,110 @@
|
||||
"status": 200,
|
||||
"body": {
|
||||
"status": "success",
|
||||
"message": "Reporte marcado para eliminación",
|
||||
"message": "Reporte eliminado",
|
||||
"action_id": "action-uuid-123"
|
||||
}
|
||||
},
|
||||
"response_error": {
|
||||
"status": 400,
|
||||
"body": {
|
||||
"detail": "Razón debe tener al menos 5 caracteres"
|
||||
}
|
||||
"response_error_401": {
|
||||
"status": 401,
|
||||
"body": {"detail": "Token inválido o expirado"}
|
||||
},
|
||||
"response_error_403": {
|
||||
"status": 403,
|
||||
"body": {"detail": "Permisos insuficientes"}
|
||||
}
|
||||
},
|
||||
|
||||
"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",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer YOUR_JWT_TOKEN"
|
||||
},
|
||||
"request_body": {
|
||||
"moderator_id": 1,
|
||||
"user_id": 42,
|
||||
"reason": "Violación grave de términos de servicio",
|
||||
"description": "Comportamiento acosador y spam sistemático",
|
||||
"reason": "Violación grave de términos",
|
||||
"description": "Comportamiento acosador",
|
||||
"is_permanent": true
|
||||
},
|
||||
"response_success": {
|
||||
"status": 200,
|
||||
"body": {
|
||||
"status": "success",
|
||||
"message": "Cuenta marcada para cierre",
|
||||
"message": "Cuenta cerrada",
|
||||
"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": {
|
||||
"description": "Banear un usuario temporalmente",
|
||||
|
||||
@@ -12,3 +12,4 @@ PyJWT
|
||||
passlib[argon2]
|
||||
python-multipart
|
||||
psycopg2-binary
|
||||
cryptography
|
||||
|
||||
49
src/infrastructure/api/auth.py
Normal file
49
src/infrastructure/api/auth.py
Normal 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
|
||||
Reference in New Issue
Block a user