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
|
# 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`
|
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -12,3 +12,4 @@ PyJWT
|
|||||||
passlib[argon2]
|
passlib[argon2]
|
||||||
python-multipart
|
python-multipart
|
||||||
psycopg2-binary
|
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