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>
180 lines
5.2 KiB
SQL
180 lines
5.2 KiB
SQL
-- Kernschema (Spec §6)
|
|
|
|
CREATE TABLE players (
|
|
uuid uuid PRIMARY KEY,
|
|
name text,
|
|
first_seen timestamptz NOT NULL DEFAULT now(),
|
|
last_seen timestamptz,
|
|
hidden boolean NOT NULL DEFAULT false
|
|
);
|
|
CREATE INDEX players_name_lower ON players (lower(name));
|
|
|
|
CREATE TABLE name_history (
|
|
uuid uuid NOT NULL REFERENCES players(uuid) ON DELETE CASCADE,
|
|
name text NOT NULL,
|
|
seen_at timestamptz NOT NULL,
|
|
PRIMARY KEY (uuid, name)
|
|
);
|
|
CREATE INDEX name_history_name_lower ON name_history (lower(name));
|
|
|
|
CREATE TABLE teams (
|
|
team_id uuid PRIMARY KEY,
|
|
name text,
|
|
type text
|
|
);
|
|
|
|
CREATE TABLE team_members (
|
|
team_id uuid NOT NULL REFERENCES teams(team_id) ON DELETE CASCADE,
|
|
uuid uuid NOT NULL,
|
|
PRIMARY KEY (team_id, uuid)
|
|
);
|
|
CREATE INDEX team_members_uuid ON team_members (uuid);
|
|
|
|
CREATE TABLE events (
|
|
event_id uuid PRIMARY KEY,
|
|
server_id text NOT NULL,
|
|
type text NOT NULL,
|
|
ts timestamptz NOT NULL,
|
|
received_at timestamptz NOT NULL DEFAULT now(),
|
|
source text NOT NULL CHECK (source IN ('push', 'sftp')),
|
|
payload jsonb NOT NULL
|
|
);
|
|
CREATE INDEX events_ts ON events (ts);
|
|
CREATE INDEX events_source_received ON events (source, received_at DESC);
|
|
CREATE INDEX events_player ON events ((payload->>'uuid'), ts);
|
|
CREATE INDEX events_type_ts ON events (type, ts);
|
|
|
|
CREATE TABLE sessions (
|
|
id bigserial PRIMARY KEY,
|
|
uuid uuid NOT NULL REFERENCES players(uuid) ON DELETE CASCADE,
|
|
started_at timestamptz NOT NULL,
|
|
ended_at timestamptz,
|
|
end_reason text
|
|
);
|
|
CREATE INDEX sessions_open ON sessions (uuid) WHERE ended_at IS NULL;
|
|
CREATE INDEX sessions_uuid_started ON sessions (uuid, started_at DESC);
|
|
|
|
CREATE TABLE stat_current (
|
|
uuid uuid NOT NULL REFERENCES players(uuid) ON DELETE CASCADE,
|
|
category text NOT NULL,
|
|
key text NOT NULL,
|
|
value bigint NOT NULL,
|
|
observed_at timestamptz NOT NULL,
|
|
source text NOT NULL,
|
|
PRIMARY KEY (uuid, category, key)
|
|
);
|
|
CREATE INDEX stat_current_board ON stat_current (category, key, value DESC);
|
|
|
|
CREATE TABLE stat_snapshots (
|
|
uuid uuid NOT NULL REFERENCES players(uuid) ON DELETE CASCADE,
|
|
metric text NOT NULL,
|
|
bucket timestamptz NOT NULL,
|
|
granularity text NOT NULL CHECK (granularity IN ('hour', 'day')),
|
|
value bigint NOT NULL,
|
|
PRIMARY KEY (uuid, metric, granularity, bucket)
|
|
);
|
|
CREATE INDEX stat_snapshots_bucket ON stat_snapshots (granularity, bucket);
|
|
|
|
CREATE TABLE deaths (
|
|
event_id uuid PRIMARY KEY,
|
|
uuid uuid NOT NULL REFERENCES players(uuid) ON DELETE CASCADE,
|
|
ts timestamptz NOT NULL,
|
|
dimension text NOT NULL,
|
|
cause text NOT NULL,
|
|
killer_type text,
|
|
killer_name text
|
|
);
|
|
CREATE INDEX deaths_uuid ON deaths (uuid, ts DESC);
|
|
|
|
CREATE TABLE kills (
|
|
event_id uuid PRIMARY KEY,
|
|
uuid uuid NOT NULL REFERENCES players(uuid) ON DELETE CASCADE,
|
|
ts timestamptz NOT NULL,
|
|
dimension text NOT NULL,
|
|
victim_type text NOT NULL,
|
|
is_boss boolean NOT NULL,
|
|
weapon text
|
|
);
|
|
CREATE INDEX kills_uuid ON kills (uuid, ts DESC);
|
|
|
|
CREATE TABLE advancements (
|
|
uuid uuid NOT NULL REFERENCES players(uuid) ON DELETE CASCADE,
|
|
advancement_id text NOT NULL,
|
|
achieved_at timestamptz NOT NULL,
|
|
title text,
|
|
PRIMARY KEY (uuid, advancement_id)
|
|
);
|
|
|
|
CREATE TABLE dimension_visits (
|
|
uuid uuid NOT NULL REFERENCES players(uuid) ON DELETE CASCADE,
|
|
dimension text NOT NULL,
|
|
first_at timestamptz NOT NULL,
|
|
PRIMARY KEY (uuid, dimension)
|
|
);
|
|
|
|
CREATE TABLE quest_defs (
|
|
quest_id text PRIMARY KEY,
|
|
chapter_id text,
|
|
chapter_title text,
|
|
title text,
|
|
def_hash text
|
|
);
|
|
|
|
CREATE TABLE quest_progress (
|
|
team_id uuid NOT NULL,
|
|
quest_id text NOT NULL,
|
|
completed_at timestamptz NOT NULL,
|
|
PRIMARY KEY (team_id, quest_id)
|
|
);
|
|
|
|
CREATE TABLE server_info (
|
|
id bigserial PRIMARY KEY,
|
|
started_at timestamptz NOT NULL,
|
|
mc_version text NOT NULL,
|
|
neoforge_version text NOT NULL,
|
|
java_version text NOT NULL,
|
|
mods jsonb NOT NULL
|
|
);
|
|
|
|
CREATE TABLE server_health (
|
|
bucket timestamptz PRIMARY KEY,
|
|
mspt_avg real NOT NULL,
|
|
mspt_p95 real NOT NULL,
|
|
mspt_max real NOT NULL,
|
|
tps real NOT NULL,
|
|
players integer NOT NULL,
|
|
chunks integer NOT NULL,
|
|
heap_used_mb real NOT NULL,
|
|
heap_max_mb real NOT NULL
|
|
);
|
|
|
|
CREATE TABLE sync_state (
|
|
path text PRIMARY KEY,
|
|
size bigint NOT NULL,
|
|
mtime bigint NOT NULL,
|
|
"offset" bigint NOT NULL DEFAULT 0,
|
|
hash text,
|
|
synced_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE admin_users (
|
|
id bigserial PRIMARY KEY,
|
|
username text NOT NULL UNIQUE,
|
|
password_hash text NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE TABLE admin_sessions (
|
|
id text PRIMARY KEY,
|
|
user_id bigint NOT NULL REFERENCES admin_users(id) ON DELETE CASCADE,
|
|
csrf text NOT NULL,
|
|
expires_at timestamptz NOT NULL
|
|
);
|
|
|
|
CREATE TABLE ingest_tokens (
|
|
id bigserial PRIMARY KEY,
|
|
hash text NOT NULL UNIQUE,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
revoked_at timestamptz
|
|
);
|