Arquitectura general del backend
Visión de capas
El backend sigue una arquitectura en capas clásica de Spring MVC, con separación clara entre:
┌─────────────────────────────────────────────┐
│ Clientes externos │
│ Angular SPA (4200) · Scrapy (futuro) │
└───────────────┬──────────────────┬──────────┘
│ HTTP/REST │ WebSocket STOMP
┌───────────────▼──────────────────▼──────────┐
│ Controllers / Handlers │
│ @RestController · @MessageMapping │
└───────────────┬─────────────────────────────┘
│
┌───────────────▼─────────────────────────────┐
│ Services │
│ @Service · @Transactional · Business logic │
└───────────────┬─────────────────────────────┘
│
┌───────────────▼─────────────────────────────┐
│ Repositories │
│ Spring Data JPA · JpaRepository │
└───────────────┬─────────────────────────────┘
│
┌───────────────▼─────────────────────────────┐
│ PostgreSQL (5432) │
└─────────────────────────────────────────────┘
Mapa de paquetes
graph TD
root["com.versus.api"]
root --> config
root --> common
root --> auth
root --> users
root --> questions
root --> game
root --> match
root --> stats
root --> moderation
root --> scraping
root --> media
root --> storage
common --> common_exc["common.exception"]
common --> common_dto["common.dto"]
auth --> auth_domain["auth.domain"]
auth --> auth_dto["auth.dto"]
auth --> auth_repo["auth.repo"]
users --> users_domain["users.domain"]
users --> users_dto["users.dto"]
users --> users_repo["users.repo"]
questions --> q_domain["questions.domain"]
questions --> q_dto["questions.dto"]
questions --> q_repo["questions.repo"]
game --> game_dto["game.dto"]
match --> match_domain["match.domain"]
match --> match_repo["match.repo"]
stats --> stats_domain["stats.domain"]
stats --> stats_dto["stats.dto"]
stats --> stats_repo["stats.repo"]
moderation --> mod_domain["moderation.domain"]
moderation --> mod_repo["moderation.repo"]
scraping --> scraping_domain["scraping.domain"]
scraping --> scraping_repo["scraping.repo"]
media --> media_domain["media.domain"]
media --> media_dto["media.dto"]
media --> media_repo["media.repo"]
Flujo de una petición HTTP autenticada
sequenceDiagram
participant C as Angular Client
participant F as JwtAuthFilter
participant SC as SecurityContext
participant Ctrl as Controller
participant Svc as Service
participant Repo as Repository
participant DB as PostgreSQL
C->>F: HTTP + Authorization: Bearer <token>
F->>F: JwtService.validate(token)
F->>SC: setAuthentication(userId, role)
F->>Ctrl: continúa la cadena de filtros
Ctrl->>Svc: llama al método de negocio
Svc->>Repo: consulta/mutación
Repo->>DB: SQL
DB-->>Repo: resultado
Repo-->>Svc: entidad JPA
Svc-->>Ctrl: DTO de respuesta
Ctrl-->>C: JSON (200/201/4xx)
Módulos y sus responsabilidades
| Paquete | Responsabilidad única |
|---|---|
config |
Filtros de seguridad, CORS, Swagger, seeding de dev |
common.exception |
Jerarquía de excepciones, handler global, ErrorCode |
auth |
Registro, login, rotación de refresh token, JWT |
users |
Perfil propio y públicos, sin lógica de juego |
questions |
Acceso a preguntas activas (sin respuestas correctas en la respuesta) |
game |
Lógica de partidas singleplayer: Survival y Precision |
match |
Entidades de partida (usadas por game y futuro multiplayer) |
stats |
Estadísticas acumuladas por modo de juego |
moderation |
Reportes de preguntas por usuarios |
scraping |
Gestión de spiders y sus ejecuciones |
media |
Metadatos, permisos y API de assets multimedia |
storage |
Abstracción de almacenamiento local de archivos |
Manejo de errores
Todas las excepciones de negocio pasan por GlobalExceptionHandler y producen siempre la misma forma de respuesta:
{
"error": "NOT_FOUND",
"message": "Question not found",
"status": 404
}
Ver common.md para la jerarquía completa.
Seguridad
- Sin estado:
SessionCreationPolicy.STATELESS— no hay sesiones HTTP. - JWT de doble token: access token (15 min) + refresh token (7 días, almacenado como hash SHA-256).
- CORS: sólo permite
http://localhost:4200en desarrollo. - Endpoints públicos:
/api/auth/**,/v3/api-docs/**,/swagger-ui/**. - Endpoints protegidos: todo lo demás requiere Bearer token válido.
Ver modules/auth.md para detalles del flujo JWT.