Files
minecraft-atm10-stats/docs/ARCHITECTURE.md
T
lucashahmannandClaude Opus 5.5 e66f89f49e feat: ATM10-Statistikportal
Statistikportal für den All-the-Mods-10-Server (Minecraft 1.21.1, NeoForge 21.1):

- mod/: serverseitige NeoForge-Mod atmstats – Ereignisse, Live-Zähler, Servergesundheit,
  FTB-Dateien; Outbox auf der Festplatte, signierter gzip-Versand per HTTPS mit Backoff
- backend/: Fastify + PostgreSQL – idempotenter Ingest, Projektionen, Parser für SNBT,
  FTB Quests/Teams, world/stats, world/advancements und latest.log; Ranglisten, Snapshots,
  Retention, öffentliche API, SSE-Live-Updates, Admin mit argon2/CSRF, SFTP-Worker
- web/: Next.js-Dashboard – Übersicht, Ranglisten, Spielerprofile, ATM10, Server,
  Datenquellen, Verwaltung
- shared/: Protokoll v1 und API-Typen; tools/: Simulator, Log-Anonymisierer, RCON-Client
- deploy/: Docker Compose hinter Traefik, Backup; docs/: Architektur, Datenquellen, Betrieb

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:25 +02:00

5.8 KiB

Architektur

Für Entwickler, die am Portal weiterarbeiten. Die vollständige Design-Begründung steht in docs/superpowers/specs/2026-10-13-atm10-stats-design.md.

Leitidee

Alle Datenquellen erzeugen Ereignisse desselben Protokolls (v1, shared/src/protocol.ts) und laufen durch eine Pipeline (backend/src/pipeline/process.ts):

Mod (Push) ──┐
             ├─▶ processEvents(events, source)
SFTP (Pull) ─┘     1. INSERT INTO events … ON CONFLICT (event_id) DO NOTHING   ← Idempotenz
                   2. nur neue Ereignisse → Projektionen (sessions, stat_current, deaths, quest_progress …)
                   3. nach COMMIT → pg_notify('atm_live') → LiveHub → SSE-Clients

Lese-API und Frontend kennen nur die projizierten Tabellen, nie die Quelle.

Komponenten

Mod atmstats (mod/)

  • core/: reines Java ohne Minecraft, vollständig unit-getestet
    • OutboxJournal: Journal auf der Festplatte (current.jsonl → seg-<n>.jsonl; ein Segment = ein Batch, gelöscht erst nach Bestätigung; Limits mit Vorrang für Ereignisse vor Snapshots; abgeschnittene letzte Zeile nach Absturz wird verworfen)
    • Shipper: Queue → Journal → HTTPS; Backoff 1 s … 5 min mit Jitter; 4xx-Batches nach rejected/
    • Signer, UuidV7, Backoff, EventJson, StatsDiff, HealthWindow, FileChangeTracker
  • mc/: dünne NeoForge-Schicht
    • GameHooks: Event-Handler auf dem Server-Thread. Sie erzeugen nur Records und legen sie in eine ConcurrentLinkedQueue
    • StatsCollector: kopiert alle 5 min die Zählertabelle online Spieler (max. 2 Spieler pro Tick; Zugriff über Access Transformer auf StatsCounter.stats); Umrechnung und Diff im Worker-Thread
    • HealthMonitor: misst Tickdauer per ServerTickEvent.Pre/Post
    • FileWatcher: reicht geänderte FTB-Dateien unverändert als file_sync weiter (Parsing im Backend)
    • WorkerThread: einziger Hintergrund-Thread für Serialisierung, Datei- und Netzwerk-I/O
    • ServerInfoCollector: Versionen und Modliste über ModList

Backend (backend/)

Modul Aufgabe
ingest/ POST /api/ingest: Bearer-Token (als SHA-256 gespeichert), Zeitfenster ±5 min, HMAC über die übertragenen Bytes, gzip mit 2-MB-Grenze, Validierung je Ereignis
pipeline/ processEvents, Projektionen je Ereignistyp
parsers/ SNBT (FTB-Format ohne Kommas), FTB-Quests, world/stats, world/advancements, latest.log
stats/ Ranglisten-Definitionen (metrics.ts), SQL je Board, Snapshots, Retention
routes/ öffentliche API, SSE (/api/live), Admin
worker/ SFTP-Abgleich (FileSource: SFTP oder lokal), Jobs mit pg_try_advisory_lock
live/ Notifier (Ereignis → Live-Meldung, ausgeblendete Spieler gefiltert) und LiveHub (LISTEN, Ringpuffer 500, Resume über Last-Event-ID)

Zwei Prozesse aus einer Codebasis: server.ts (api) und worker.ts. Migrationen (backend/migrations/*.sql) führt der eigene Migrator beim Start aus.

Frontend (web/)

Next.js App Router, Server Components holen Daten direkt bei der API (API_INTERNAL_URL), der Browser nutzt same-origin /api. Live-Teile (Ticker, Diagramm-Umschalter, Admin) sind Client-Komponenten. Design-Tokens in src/app/globals.css: Deepslate-Flächen, ATM-Erzfarben als Akzente, validierte Diagrammpalette.

Protokoll v1

POST /api/ingest
Authorization: Bearer <token>
X-ATM-Server: <serverId>
X-ATM-Timestamp: <unix ms>
X-ATM-Signature: hex(HMAC-SHA256(secret, timestamp + "." + hex(sha256(body))))
Content-Encoding: gzip
{ "protocol": 1, "events": [ … ] }        → { accepted, duplicate, rejected }

Ereignistypen: server_start, server_stop, player_join, player_leave, player_death, entity_kill, advancement, dimension_change, stats_snapshot, file_sync, health. Beispiele: shared/fixtures/protocol/. Push-Ereignisse tragen UUIDv7, SFTP-Ereignisse deterministische UUIDv5 aus Datei und Inhalt. Dadurch ist auch wiederholtes Einlesen idempotent.

Zählerregel: Werte sind absolut. Pro (Spieler, Kategorie, Schlüssel) gewinnt die Beobachtung mit dem neuesten Zeitstempel, unabhängig von der Quelle und auch wenn der Wert kleiner ist.

Datenmodell (Kern)

players, name_history, sessions, events, stat_current, stat_snapshots, deaths, kills, advancements, dimension_visits, teams, team_members, quest_progress, quest_defs, file_cache, server_info, server_health, server_health_hourly, sync_state, sync_requests, app_errors, admin_users, admin_sessions, ingest_tokens. Ranglisten werden live aus stat_current berechnet (Index (category, key, value DESC)), Zeiträume aus den Tages-Snapshots.

Sicherheit

  • Ingest: Token + HMAC + Zeitfenster + Rate-Limit + IP-Allowlist in Traefik; Payload-Grenzen; Pfad-Traversal-Schutz für file_sync
  • Admin: argon2id, Session-Cookie httpOnly; Secure; SameSite=Strict; Path=/api/admin, CSRF-Token-Header, Login-Rate-Limit
  • Keine Tokens, Signaturen oder Cookies in Logs (pino-Redaction); Spieler-IPs werden nirgends gespeichert
  • Spielerköpfe lädt die API serverseitig (Cache 24 h), damit Besucher keine Drittanbieter kontaktieren
  • Datenbank nur im internen Docker-Netz

Tests

Ebene Was
shared Fixtures gegen Schemas
backend (Vitest, echte Postgres) Ingest-Auth und Idempotenz, alle Projektionen inkl. Absturz-Sitzungen und Namenswechsel, Parser an echten Serverdateien, Ranglisten, Snapshots/Retention, API inkl. ausgeblendeter Spieler, SSE, Admin/CSRF, Worker inkl. Log-Rotation
mod (JUnit) Journal (Neustart, Absturz, Rotation, Limits), Shipper (Backoff, Ablehnung, Uhrabweichung), Serialisierung gegen shared/fixtures, Signatur-Testvektor
web (Vitest + Testing Library) Formatierung, Ranglisten-Karte, Heatmap
E2E (Playwright) Smoke-Test gegen den Compose-Stack, Desktop und Mobil