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>
30 lines
1.6 KiB
TypeScript
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],
|
|
);
|
|
}
|
|
}
|