Files
Cs2-Mapveto/server/src/db.js
T
SPP UserandClaude Haiku 5.5 9af83cb47c Discord-Webhook für Streamer-Meldungen mit wählbaren Ereignissen
Meldungen zu Veto, Match-Start, Map- und Serienende, Matchende und
Serverfehlern gehen an DISCORD_WEBHOOK_URL. Im Admin-Bereich wählt man,
welche Ereignisse gemeldet werden, und kann eine Testmeldung senden.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-26 01:12:15 +01:00

132 lines
3.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);
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}`);
}
}