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>
This commit is contained in:
1 parent
56d2078e6a
commit
e66f89f49e
214 files changed
+27077
No files matched your search
@@ -0,0 +1,29 @@
|
||||
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],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user