Files
EscapistsReintroduced/services/telemetry-worker/migrations/0001_telemetry.sql
T
2026-10-30 14:19:39 +01:00

88 lines
3.8 KiB
SQL

CREATE TABLE servers (
server_id TEXT PRIMARY KEY,
name TEXT NOT NULL,
token_hash TEXT NOT NULL,
active INTEGER NOT NULL DEFAULT 1 CHECK (active IN (0, 1)),
created_at INTEGER NOT NULL
);
CREATE TABLE events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
server_id TEXT NOT NULL REFERENCES servers(server_id),
event_id TEXT NOT NULL,
mission_id TEXT NOT NULL,
received_at INTEGER NOT NULL,
payload_hash TEXT NOT NULL,
payload TEXT NOT NULL CHECK (json_valid(payload)),
UNIQUE (server_id, event_id)
);
CREATE INDEX events_mission ON events(server_id, mission_id, id);
CREATE TABLE missions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
server_id TEXT NOT NULL REFERENCES servers(server_id),
mission_id TEXT NOT NULL,
event_id TEXT NOT NULL,
terminal INTEGER NOT NULL,
outcome TEXT NOT NULL,
elapsed_seconds INTEGER NOT NULL,
occurred_at INTEGER NOT NULL,
received_at INTEGER NOT NULL,
partial INTEGER NOT NULL,
peak_players INTEGER NOT NULL,
dropped_events INTEGER NOT NULL,
payload TEXT NOT NULL,
UNIQUE (server_id, mission_id)
);
CREATE INDEX missions_server_page ON missions(server_id, id DESC);
CREATE INDEX missions_outcome ON missions(outcome, server_id);
-- An event and its mission projection commit together. Duplicate inserts never
-- fire this trigger. First terminal snapshot wins; late starts/heartbeats cannot
-- reopen a mission. Elapsed mission time takes precedence over wall-clock skew.
CREATE TRIGGER project_mission AFTER INSERT ON events BEGIN
INSERT INTO missions (
server_id, mission_id, event_id, terminal, outcome, elapsed_seconds,
occurred_at, received_at, partial, peak_players, dropped_events, payload
) VALUES (
NEW.server_id, NEW.mission_id, NEW.event_id,
json_extract(NEW.payload, '$.type') = 'mission_finished',
json_extract(NEW.payload, '$.outcome'),
json_extract(NEW.payload, '$.elapsedSeconds'),
json_extract(NEW.payload, '$.occurredAt'), NEW.received_at,
json_extract(NEW.payload, '$.partial'), json_extract(NEW.payload, '$.peakPlayers'),
json_extract(NEW.payload, '$.droppedEvents'), NEW.payload
) ON CONFLICT(server_id, mission_id) DO UPDATE SET
event_id = excluded.event_id, terminal = excluded.terminal,
outcome = excluded.outcome, elapsed_seconds = excluded.elapsed_seconds,
occurred_at = excluded.occurred_at, received_at = excluded.received_at,
partial = MAX(missions.partial, excluded.partial),
peak_players = MAX(missions.peak_players, excluded.peak_players),
dropped_events = MAX(missions.dropped_events, excluded.dropped_events),
payload = excluded.payload
WHERE missions.terminal = 0 AND (
excluded.terminal = 1 OR excluded.elapsed_seconds > missions.elapsed_seconds OR
(excluded.elapsed_seconds = missions.elapsed_seconds AND excluded.occurred_at >= missions.occurred_at)
);
END;
-- Final snapshots alone feed rankings: spectator and temporary connection IDs
-- never become global player records. Missing legacy counters remain NULL.
CREATE VIEW final_players AS
SELECT m.server_id, m.mission_id, m.outcome, m.elapsed_seconds,
m.partial, json_extract(m.payload, '$.playersTruncated') AS players_truncated,
json_extract(p.value, '$.id') AS player_id,
json_extract(p.value, '$.name') AS name,
json_extract(p.value, '$.escaped') AS escaped,
json_extract(p.value, '$.connected') AS connected,
json_extract(p.value, '$.connectedSeconds') AS connected_seconds,
json_extract(p.value, '$.kills') AS kills,
json_extract(p.value, '$.friendlyKills') AS friendly_kills,
json_extract(p.value, '$.deaths') AS deaths,
json_extract(p.value, '$.downed') AS downed,
json_extract(p.value, '$.recoveries') AS recoveries
FROM missions m, json_each(m.payload, '$.players') p
WHERE m.terminal = 1 AND m.outcome IN ('victory', 'defeat')
AND json_extract(p.value, '$.authenticated') = 1
AND json_extract(p.value, '$.participated') = 1;