Files
minecraft-atm10-stats/backend/src/pipeline/projections/players.ts
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

30 lines
1.6 KiB
TypeScript

import type { PoolClient } from 'pg';
/**
* Upserts a player. A non-null name replaces the stored one only if it was observed at least as late as the
* current name (late replays never revert a rename) and is recorded in name_history;
* `seen` updates last_seen (only for events proving the player was online).
*/
export async function ensurePlayer(c: PoolClient, uuid: string, name: string | null, ts: number, seen: boolean): Promise<void> {
await c.query(
`INSERT INTO players(uuid, name, name_seen_at, first_seen, last_seen)
VALUES ($1, $2, CASE WHEN $2::text IS NOT NULL THEN to_timestamp($3 / 1000.0) END,
to_timestamp($3 / 1000.0), CASE WHEN $4 THEN to_timestamp($3 / 1000.0) END)
ON CONFLICT (uuid) DO UPDATE SET
name = CASE WHEN EXCLUDED.name IS NOT NULL AND (players.name_seen_at IS NULL OR EXCLUDED.name_seen_at >= players.name_seen_at)
THEN EXCLUDED.name ELSE players.name END,
name_seen_at = CASE WHEN EXCLUDED.name IS NOT NULL AND (players.name_seen_at IS NULL OR EXCLUDED.name_seen_at >= players.name_seen_at)
THEN EXCLUDED.name_seen_at ELSE players.name_seen_at END,
first_seen = LEAST(players.first_seen, EXCLUDED.first_seen),
last_seen = CASE WHEN $4 THEN GREATEST(players.last_seen, EXCLUDED.last_seen) ELSE players.last_seen END`,
[uuid, name, ts, seen],
);
if (name) {
await c.query(
`INSERT INTO name_history(uuid, name, seen_at) VALUES ($1, $2, to_timestamp($3 / 1000.0))
ON CONFLICT (uuid, name) DO UPDATE SET seen_at = GREATEST(name_history.seen_at, EXCLUDED.seen_at)`,
[uuid, name, ts],
);
}
}