sistema A2S
This commit is contained in:
@@ -1,5 +1,389 @@
|
||||
# Backend
|
||||
|
||||
Esta carpeta queda preparada para la futura implementación del backend principal en Python.
|
||||
Esta carpeta contiene el bootstrap minimo del futuro backend principal en Python para HLL Vietnam.
|
||||
|
||||
En esta fase inicial no se incluye lógica funcional ni servicios activos. La intención es reservar una estructura clara para incorporar la aplicación, dependencias y configuración sin reorganizar el repositorio más adelante.
|
||||
## Objetivo en esta fase
|
||||
|
||||
- dejar un punto de entrada claro para la aplicacion
|
||||
- validar que el backend puede arrancar localmente
|
||||
- exponer rutas placeholder coherentes con el contrato frontend-backend
|
||||
|
||||
## Stack actual del bootstrap
|
||||
|
||||
- Python 3
|
||||
- libreria estandar de Python (`http.server`, sin frameworks ni dependencias externas)
|
||||
|
||||
## Estructura minima
|
||||
|
||||
```text
|
||||
backend/
|
||||
|-- README.md
|
||||
|-- requirements.txt
|
||||
`-- app/
|
||||
|-- a2s_client.py
|
||||
|-- __init__.py
|
||||
|-- collector.py
|
||||
|-- main.py
|
||||
|-- normalizers.py
|
||||
|-- payloads.py
|
||||
|-- routes.py
|
||||
|-- server_targets.py
|
||||
`-- snapshots.py
|
||||
```
|
||||
|
||||
La persistencia local de desarrollo se crea bajo `backend/data/` cuando el
|
||||
colector la necesita por primera vez.
|
||||
|
||||
`app` es el paquete Python del backend. El archivo correcto del paquete es
|
||||
`backend/app/__init__.py`; no debe existir una variante `init.py`.
|
||||
|
||||
## Punto de entrada
|
||||
|
||||
El entrypoint real del backend es el modulo `app.main`, ubicado en
|
||||
`backend/app/main.py`.
|
||||
|
||||
Desde la carpeta `backend/`, se puede arrancar localmente con:
|
||||
|
||||
```powershell
|
||||
python -m app.main
|
||||
```
|
||||
|
||||
Ese comando usa imports relativos de paquete (`from .routes import ...`), por lo
|
||||
que la forma soportada de arranque es por modulo y no ejecutando el archivo como
|
||||
script suelto.
|
||||
|
||||
Por defecto escuchara en `127.0.0.1:8000`.
|
||||
|
||||
Variables opcionales:
|
||||
|
||||
- `HLL_BACKEND_HOST`
|
||||
- `HLL_BACKEND_PORT`
|
||||
- `HLL_BACKEND_ALLOWED_ORIGINS`
|
||||
- `HLL_BACKEND_REFRESH_INTERVAL_SECONDS`
|
||||
|
||||
Valor por defecto de `HLL_BACKEND_ALLOWED_ORIGINS`:
|
||||
|
||||
- `null`
|
||||
- `http://127.0.0.1:5500`
|
||||
- `http://127.0.0.1:8080`
|
||||
- `http://localhost:5500`
|
||||
- `http://localhost:8080`
|
||||
|
||||
Esto cubre el caso de abrir `frontend/index.html` directamente desde `file://`
|
||||
y los puertos locales mas habituales cuando el frontend se sirve con un
|
||||
servidor sencillo.
|
||||
|
||||
Prueba local recomendada para validar frontend y backend juntos:
|
||||
|
||||
1. En una terminal, desde `backend/`, arrancar el backend:
|
||||
|
||||
```powershell
|
||||
python -m app.main
|
||||
```
|
||||
|
||||
2. En otra terminal, desde `frontend/`, servir la landing:
|
||||
|
||||
```powershell
|
||||
python -m http.server 8080
|
||||
```
|
||||
|
||||
3. Abrir `http://localhost:8080`.
|
||||
|
||||
Si se necesita otra combinacion de origenes locales, puede sobrescribirse
|
||||
`HLL_BACKEND_ALLOWED_ORIGINS` con una lista separada por comas. El backend
|
||||
normaliza espacios y barras finales para mantener la comparacion con el header
|
||||
`Origin` del navegador.
|
||||
|
||||
## Endpoints placeholder disponibles
|
||||
|
||||
- `GET /health`
|
||||
- `GET /api/community`
|
||||
- `GET /api/trailer`
|
||||
- `GET /api/discord`
|
||||
- `GET /api/servers`
|
||||
- `GET /api/servers/latest`
|
||||
- `GET /api/servers/history?limit=20`
|
||||
- `GET /api/servers/{id}/history?limit=20`
|
||||
|
||||
`GET /api/servers` devuelve en esta fase un payload controlado con servidores
|
||||
actuales de Hell Let Loose usados como referencia provisional. La respuesta
|
||||
incluye `title`, `context`, `source` e `items`, y no representa todavia datos
|
||||
reales de HLL Vietnam ni una integracion externa validada.
|
||||
|
||||
Los endpoints historicos leen la persistencia local SQLite creada por el
|
||||
colector. Si todavia no hay snapshots guardados, responden `status: "ok"` con
|
||||
`items: []` para mantener un contrato simple en desarrollo.
|
||||
|
||||
## Criterio de estructura
|
||||
|
||||
- `__init__.py` declara el paquete `app` y reexporta las utilidades publicas
|
||||
minimas del bootstrap.
|
||||
- `collector.py` define el flujo minimo de captura para desarrollo usando una
|
||||
fuente controlada.
|
||||
- `a2s_client.py` encapsula una consulta minima A2S_INFO por UDP para probar
|
||||
servidores reales sin acoplar todavia el backend a una fuente mas compleja.
|
||||
- `config.py` centraliza host, puerto y allowlist minima de origenes locales.
|
||||
- `main.py` contiene el entrypoint HTTP y la creacion del servidor.
|
||||
- `normalizers.py` transforma registros crudos o respuestas A2S a un modelo
|
||||
comun del colector.
|
||||
- `routes.py` resuelve las rutas GET soportadas.
|
||||
- `payloads.py` centraliza respuestas placeholder y mock.
|
||||
- `server_targets.py` registra targets A2S de prueba de forma desacoplada del
|
||||
flujo principal del colector.
|
||||
- `snapshots.py` construye snapshots consistentes con timestamp comun de
|
||||
captura.
|
||||
- `storage.py` prepara una persistencia local minima en SQLite para
|
||||
`game_sources`, `servers` y `server_snapshots`.
|
||||
|
||||
## Persistencia local minima
|
||||
|
||||
El backend ya puede guardar snapshots en un SQLite local de desarrollo usando
|
||||
solo libreria estandar de Python. Esta base minima sigue el modelo logico de:
|
||||
|
||||
- `game_sources`
|
||||
- `servers`
|
||||
- `server_snapshots`
|
||||
|
||||
Por defecto el archivo se crea en:
|
||||
|
||||
```text
|
||||
backend/data/hll_vietnam_dev.sqlite3
|
||||
```
|
||||
|
||||
Variable opcional:
|
||||
|
||||
- `HLL_BACKEND_STORAGE_PATH`
|
||||
- `HLL_BACKEND_A2S_TARGETS`
|
||||
|
||||
La base logica sigue documentada en
|
||||
`docs/stats-database-schema-foundation.md`. Esta implementacion no introduce
|
||||
ORM, migraciones ni una decision de almacenamiento productivo.
|
||||
|
||||
## Bootstrap del colector
|
||||
|
||||
El backend incluye un bootstrap minimo para el futuro flujo de snapshots:
|
||||
|
||||
- `fetch_controlled_server_source()` obtiene datos controlados de desarrollo
|
||||
- `query_server_info()` permite consultar metadata basica real por A2S_INFO
|
||||
- `fetch_a2s_probe()` adapta una consulta A2S real al modelo interno del colector
|
||||
- `fetch_configured_a2s_probes()` consulta la lista configurada de targets A2S
|
||||
- `normalize_server_record()` reduce los registros a una forma comun
|
||||
- `normalize_a2s_server_info()` reduce una respuesta A2S al mismo contrato interno
|
||||
- `build_server_snapshot()` y `build_snapshot_batch()` generan snapshots con
|
||||
`captured_at`
|
||||
- `collect_server_snapshots()` orquesta captura, normalizacion, ensamblado y
|
||||
persistencia opcional
|
||||
- `persist_snapshot_batch()` escribe el lote en SQLite y mantiene identidad de
|
||||
servidor separada del historico
|
||||
|
||||
Ejecucion manual desde `backend/`:
|
||||
|
||||
```powershell
|
||||
python -m app.collector --source auto
|
||||
```
|
||||
|
||||
Ese comando intenta consultar primero los targets A2S configurados. Si ninguno
|
||||
responde y no se ha desactivado el fallback, usa la fuente controlada de
|
||||
desarrollo para no romper el flujo local. El resultado imprime el modo usado,
|
||||
los errores de consulta y el lote de snapshots persistido en SQLite.
|
||||
|
||||
Si se quiere forzar solo A2S real:
|
||||
|
||||
```powershell
|
||||
python -m app.collector --source a2s --no-fallback
|
||||
```
|
||||
|
||||
Ese flujo es la validacion local minima extremo a extremo para los targets
|
||||
reales configurados de Comunidad Hispana. El timeout por defecto del cliente
|
||||
A2S es `6.0s` para tolerar mejor latencia puntual entre multiples consultas
|
||||
reales consecutivas. Cuando responden ambos targets por defecto, el comando
|
||||
debe devolver:
|
||||
|
||||
- `collection_mode: "a2s"`
|
||||
- `target_count: 2`
|
||||
- `success_count: 2`
|
||||
- un snapshot con `external_server_id: "comunidad-hispana-01"`
|
||||
- un snapshot con `external_server_id: "comunidad-hispana-02"`
|
||||
- `source_name: "community-hispana-a2s"`
|
||||
- `snapshot_origin: "real-a2s"` en ambos
|
||||
- `source_ref: "a2s://152.114.195.174:7778"`
|
||||
- `source_ref: "a2s://152.114.195.150:7878"`
|
||||
- persistencia en `backend/data/hll_vietnam_dev.sqlite3`
|
||||
|
||||
Si la consulta se ejecuta desde un entorno con red restringida, sin salida UDP
|
||||
o con latencia puntual alta, el cliente puede devolver timeout aunque el target
|
||||
este sano. En ese caso el resultado conserva errores controlados por target y
|
||||
puede acabar con `success_count` parcial o `0` segun cuantas consultas fallen.
|
||||
|
||||
Los snapshots persistidos y los endpoints historicos exponen ademas:
|
||||
|
||||
- `snapshot_origin` para distinguir `real-a2s` frente a `controlled-fallback`
|
||||
- `source_ref` para conservar una referencia de procedencia util en historico
|
||||
|
||||
Si se quiere seguir usando solo datos controlados:
|
||||
|
||||
```powershell
|
||||
python -m app.collector --source controlled
|
||||
```
|
||||
|
||||
## Refresco local periodico de snapshots
|
||||
|
||||
Para evitar lanzar el colector manualmente en cada captura, el backend incluye
|
||||
un bucle local de refresco periodico pensado solo para desarrollo:
|
||||
|
||||
```powershell
|
||||
python -m app.scheduler
|
||||
```
|
||||
|
||||
Ese comando ejecuta capturas persistidas de forma repetida usando el mismo
|
||||
flujo del colector y la base SQLite local. Por defecto:
|
||||
|
||||
- usa `--source auto`
|
||||
- espera `300` segundos entre ejecuciones
|
||||
- permite fallback controlado si A2S no responde
|
||||
- sigue en ejecucion hasta que se detiene manualmente
|
||||
|
||||
Se puede detener de forma segura con `Ctrl+C`.
|
||||
|
||||
Variables y flags utiles:
|
||||
|
||||
- `HLL_BACKEND_REFRESH_INTERVAL_SECONDS` para cambiar el intervalo por defecto
|
||||
- `--interval 120` para fijar el intervalo en segundos en una ejecucion concreta
|
||||
- `--source a2s --no-fallback` para forzar solo capturas reales
|
||||
- `--max-runs 3` para limitar el numero de ciclos y evitar un bucle indefinido
|
||||
|
||||
Ejemplos:
|
||||
|
||||
```powershell
|
||||
python -m app.scheduler --interval 120
|
||||
python -m app.scheduler --source a2s --no-fallback --max-runs 2
|
||||
```
|
||||
|
||||
Este mecanismo deja el refresco desacoplado del servidor HTTP y es facil de
|
||||
reemplazar mas adelante por un scheduler mas serio sin rehacer el colector.
|
||||
|
||||
Prueba manual minima de A2S desde `backend/`:
|
||||
|
||||
```powershell
|
||||
python -m app.a2s_client 203.0.113.10 27015
|
||||
```
|
||||
|
||||
Ese comando lanza una consulta `A2S_INFO` por UDP y devuelve JSON con nombre de
|
||||
servidor, mapa, jugadores y capacidad maxima cuando el query port responde.
|
||||
Tambien puede reutilizarse desde Python con `query_server_info()` o
|
||||
`fetch_a2s_probe()`. Si el servidor no responde o el puerto es incorrecto, el
|
||||
cliente eleva errores controlados de timeout o protocolo para que la siguiente
|
||||
task pueda integrarlo en el pipeline de snapshots sin romper el backend.
|
||||
|
||||
## Registro local de targets A2S
|
||||
|
||||
La lista de targets A2S vive en `app/server_targets.py`. Por defecto el backend
|
||||
registra solo el primer target real verificado del proyecto:
|
||||
|
||||
- `Comunidad Hispana #01`
|
||||
- host/IP: `152.114.195.174`
|
||||
- `query_port`: `7778`
|
||||
- `game_port`: `7777`
|
||||
- `source_name`: `community-hispana-a2s`
|
||||
- `external_server_id`: `comunidad-hispana-01`
|
||||
|
||||
`query_port` es el puerto usado para `A2S_INFO`; `game_port` se conserva por
|
||||
separado para documentar el puerto de juego real sin mezclar ambos conceptos en
|
||||
la configuracion.
|
||||
|
||||
El registro por defecto incluye dos targets reales verificados:
|
||||
|
||||
- `Comunidad Hispana #01`
|
||||
- host/IP: `152.114.195.174`
|
||||
- `query_port`: `7778`
|
||||
- `game_port`: `7777`
|
||||
- `external_server_id`: `comunidad-hispana-01`
|
||||
- `Comunidad Hispana #02`
|
||||
- host/IP: `152.114.195.150`
|
||||
- `query_port`: `7878`
|
||||
- `game_port`: `7877`
|
||||
- `external_server_id`: `comunidad-hispana-02`
|
||||
|
||||
Si se quiere cambiar la lista sin editar codigo, puede definirse
|
||||
`HLL_BACKEND_A2S_TARGETS` como un array JSON:
|
||||
|
||||
```powershell
|
||||
$env:HLL_BACKEND_A2S_TARGETS='[
|
||||
{
|
||||
"name": "Comunidad Hispana #01",
|
||||
"host": "152.114.195.174",
|
||||
"query_port": 7778,
|
||||
"game_port": 7777,
|
||||
"source_name": "community-hispana-a2s",
|
||||
"external_server_id": "comunidad-hispana-01",
|
||||
"region": "ES"
|
||||
},
|
||||
{
|
||||
"name": "Comunidad Hispana #02",
|
||||
"host": "152.114.195.150",
|
||||
"query_port": 7878,
|
||||
"game_port": 7877,
|
||||
"source_name": "community-hispana-a2s",
|
||||
"external_server_id": "comunidad-hispana-02",
|
||||
"region": "ES"
|
||||
}
|
||||
]'
|
||||
```
|
||||
|
||||
Cada target soporta:
|
||||
|
||||
- `name`
|
||||
- `host`
|
||||
- `query_port`
|
||||
- `game_port` opcional
|
||||
- `source_name`
|
||||
- `external_server_id` opcional
|
||||
- `region` opcional
|
||||
|
||||
El colector puede resolver esos targets con `load_a2s_targets()` o
|
||||
`fetch_configured_a2s_probes()` sin depender de constantes dispersas.
|
||||
|
||||
## Consulta historica minima
|
||||
|
||||
Una vez existen snapshots persistidos, el backend expone una primera capa de
|
||||
consulta historica:
|
||||
|
||||
- `/api/servers/latest` devuelve el ultimo snapshot conocido por servidor
|
||||
- `/api/servers/history` devuelve snapshots recientes agregados
|
||||
- `/api/servers/{id}/history` devuelve el historial reciente de un servidor
|
||||
|
||||
`{id}` acepta el `server_id` numerico interno o el `external_server_id`
|
||||
persistido por el colector. El parametro opcional `limit` acepta valores entre
|
||||
`1` y `100`.
|
||||
|
||||
## CORS local minimo
|
||||
|
||||
El backend responde con `Access-Control-Allow-Origin` solo si la peticion llega
|
||||
desde uno de los origenes permitidos en desarrollo local. No se habilita un
|
||||
comodin global ni configuracion de produccion en esta fase.
|
||||
|
||||
La allowlist por defecto cubre `file://` mediante el origen `null` y los flujos
|
||||
locales mas comunes del proyecto:
|
||||
|
||||
- `http://127.0.0.1:5500`
|
||||
- `http://localhost:5500`
|
||||
- `http://127.0.0.1:8080`
|
||||
- `http://localhost:8080`
|
||||
|
||||
Las respuestas `GET` y `OPTIONS` incluyen `Access-Control-Allow-Origin` cuando
|
||||
el origen esta permitido, suficiente para probar la landing contra la API local
|
||||
sin tocar endpoints ni payloads.
|
||||
|
||||
Esta separacion mantiene el backend simple y deja una base clara para futuras tasks sin introducir integraciones reales todavia.
|
||||
|
||||
## Alcance
|
||||
|
||||
Esta fase no implementa:
|
||||
|
||||
- logica real de Discord
|
||||
- integraciones con servidores de juego
|
||||
- base de datos
|
||||
- autenticacion
|
||||
- dependencias nuevas
|
||||
|
||||
La idea es dejar un esqueleto funcional, pequeno y coherente con `docs/frontend-backend-contract.md`.
|
||||
|
||||
Reference in New Issue
Block a user