Server bekommt beim Konfigurieren logaddress_add_http mit geheimem Match-Schlüssel. Ein Parser wertet Rundenende, Seitenzuordnung, Matchstart und Spielende aus; Stand, Rundenverlauf und Sieger werden pro Map gespeichert und live verteilt. Der Demo-Adapter simuliert nach der Aufwärmphase einen Matchverlauf über dieselben Logzeilen.
128 lines
3.6 KiB
JavaScript
128 lines
3.6 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import Database from 'better-sqlite3';
|
|
|
|
const SCHEMA = `
|
|
CREATE TABLE IF NOT EXISTS matches (
|
|
id TEXT PRIMARY KEY,
|
|
team_a_name TEXT NOT NULL,
|
|
team_b_name TEXT NOT NULL,
|
|
mode TEXT NOT NULL,
|
|
format TEXT NOT NULL,
|
|
flow TEXT NOT NULL,
|
|
maps TEXT NOT NULL,
|
|
veto_start TEXT NOT NULL,
|
|
first_team TEXT,
|
|
status TEXT NOT NULL,
|
|
organizer_token_hash TEXT NOT NULL,
|
|
code_a TEXT NOT NULL UNIQUE,
|
|
code_b TEXT NOT NULL UNIQUE,
|
|
code_spectator TEXT NOT NULL UNIQUE,
|
|
current_map_number INTEGER,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL,
|
|
veto_started_at INTEGER,
|
|
veto_finished_at INTEGER,
|
|
finished_at INTEGER,
|
|
last_activity_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS players (
|
|
id TEXT PRIMARY KEY,
|
|
match_id TEXT NOT NULL REFERENCES matches(id) ON DELETE CASCADE,
|
|
team TEXT NOT NULL CHECK (team IN ('A','B')),
|
|
nickname TEXT NOT NULL,
|
|
steam_id TEXT,
|
|
token_hash TEXT NOT NULL UNIQUE,
|
|
is_captain INTEGER NOT NULL DEFAULT 0,
|
|
ready INTEGER NOT NULL DEFAULT 0,
|
|
active INTEGER NOT NULL DEFAULT 1,
|
|
joined_at INTEGER NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS players_match ON players(match_id, team, active);
|
|
|
|
CREATE TABLE IF NOT EXISTS veto_actions (
|
|
match_id TEXT NOT NULL REFERENCES matches(id) ON DELETE CASCADE,
|
|
idx INTEGER NOT NULL,
|
|
type TEXT NOT NULL,
|
|
team TEXT,
|
|
map TEXT,
|
|
side TEXT,
|
|
player_id TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
PRIMARY KEY (match_id, idx)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS series_maps (
|
|
match_id TEXT NOT NULL REFERENCES matches(id) ON DELETE CASCADE,
|
|
number INTEGER NOT NULL,
|
|
map TEXT NOT NULL,
|
|
picked_by TEXT,
|
|
side_chosen_by TEXT,
|
|
ct_team TEXT,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
PRIMARY KEY (match_id, number)
|
|
);
|
|
|
|
-- Serverdaten inkl. Zugangsdaten. Diese Tabelle verlässt nie ungefiltert das Backend.
|
|
CREATE TABLE IF NOT EXISTS servers (
|
|
match_id TEXT PRIMARY KEY REFERENCES matches(id) ON DELETE CASCADE,
|
|
adapter TEXT NOT NULL,
|
|
simulated INTEGER NOT NULL,
|
|
status TEXT NOT NULL,
|
|
message TEXT,
|
|
handle TEXT,
|
|
host TEXT,
|
|
port INTEGER,
|
|
tv_port INTEGER,
|
|
rcon_password TEXT NOT NULL,
|
|
server_password TEXT NOT NULL,
|
|
tv_password TEXT NOT NULL,
|
|
config_key TEXT NOT NULL,
|
|
player_count INTEGER,
|
|
attempts INTEGER NOT NULL DEFAULT 0,
|
|
started_at INTEGER,
|
|
ready_at INTEGER,
|
|
configured_at INTEGER,
|
|
stopped_at INTEGER,
|
|
last_player_seen_at INTEGER,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
match_id TEXT NOT NULL REFERENCES matches(id) ON DELETE CASCADE,
|
|
type TEXT NOT NULL,
|
|
message TEXT NOT NULL,
|
|
created_at INTEGER NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS events_match ON events(match_id, id);
|
|
`;
|
|
|
|
export function openDatabase(file) {
|
|
if (file !== ':memory:') fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
const db = new Database(file);
|
|
db.pragma('journal_mode = WAL');
|
|
db.pragma('foreign_keys = ON');
|
|
db.exec(SCHEMA);
|
|
migrate(db);
|
|
return db;
|
|
}
|
|
|
|
/** Ergänzt Spalten, die in älteren Datenbanken noch fehlen. */
|
|
function migrate(db) {
|
|
const columns = (table) => new Set(db.prepare(`PRAGMA table_info(${table})`).all().map((c) => c.name));
|
|
const seriesColumns = columns('series_maps');
|
|
const add = [
|
|
['score_a', 'INTEGER NOT NULL DEFAULT 0'],
|
|
['score_b', 'INTEGER NOT NULL DEFAULT 0'],
|
|
['winner', 'TEXT'],
|
|
// Live-Zustand aus den Serverlogs (JSON, siehe liveScore.js)
|
|
['live', 'TEXT'],
|
|
['live_updated_at', 'INTEGER'],
|
|
];
|
|
for (const [name, type] of add) {
|
|
if (!seriesColumns.has(name)) db.exec(`ALTER TABLE series_maps ADD COLUMN ${name} ${type}`);
|
|
}
|
|
}
|