Files
Cs2-Mapveto/server/src/db.js
T

158 lines
4.7 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);
-- Messer-Server für die Seitenwahl: laufen dauerhaft, gehören keinem Match
CREATE TABLE IF NOT EXISTS knife_servers (
id TEXT PRIMARY KEY,
number INTEGER NOT NULL,
name TEXT NOT NULL,
mode TEXT NOT NULL DEFAULT 'competitive',
map TEXT NOT NULL,
status TEXT NOT NULL,
message TEXT,
handle TEXT,
host TEXT,
port INTEGER,
tv_port INTEGER,
rcon_password TEXT NOT NULL,
player_count INTEGER,
restarts INTEGER NOT NULL DEFAULT 0,
started_at INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
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');
// Abgeschlossene Matches lassen sich archivieren; sie verschwinden dann aus der normalen Admin-Liste
if (!columns('matches').has('archived_at')) db.exec('ALTER TABLE matches ADD COLUMN archived_at INTEGER');
if (!columns('knife_servers').has('mode')) db.exec("ALTER TABLE knife_servers ADD COLUMN mode TEXT NOT NULL DEFAULT 'competitive'");
}