Pausierte Matches zeigen ein Badge und den Knopf Fortsetzen. Laufende Matches zeigen Map, Stand und Runde. Der Pausenzustand wird gespeichert und bei Mapwechsel oder Serverstopp zurückgesetzt.
134 lines
3.8 KiB
JavaScript
134 lines
3.8 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);
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
);
|
|
`;
|
|
|
|
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}`);
|
|
}
|
|
const serverColumns = columns('servers');
|
|
if (!serverColumns.has('paused')) db.exec('ALTER TABLE servers ADD COLUMN paused INTEGER NOT NULL DEFAULT 0');
|
|
}
|