Server-Tab (DiscordGSM-Stil), Team-Rechte und Bot-Profil-Beschreibung

- Game-Server: eigene Tabelle statt CSV-Setting (automatische Migration), eigener
  Setup-Tab mit Server-Builder (Name, Typ fivem/http, Query-URL, Anzeige-Adresse);
  pro Server ein Live-Embed (gruen/rot, Spieler-Balken, Map, Adresse als Code-Block),
  edit in place; Embed wird beim Server-Loeschen mit entfernt
- Team-System: web_admins mit Bereichs-Scopes (content/community/rollen/bewerbungen/
  server/settings/devlogs); Owner behaelt Brand/System/API-Keys/Team exklusiv;
  32 Routen-Guards auf Scopes umgestellt, sensible PUT-Felder fuer Team gestrippt;
  Team-Tab (Owner) + Tab-Filterung im Frontend, /api/me liefert Scopes
- Bot-Karte: Profil-Beschreibung (Ueber mich) via application.edit
- Fix: settings-Tabelle wird vor der Gameserver-Migration angelegt (frische DBs
  crashten sonst beim Start)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 01:23:11 +02:00
co-authored by Claude Fable 5
parent 19ec2802ac
commit 332e89dcbd
6 changed files with 533 additions and 115 deletions
+66
View File
@@ -404,6 +404,72 @@ export const listTempVoice = () => listTempVoiceStmt.all().map((r) => r.channel_
const isTempVoiceStmt = db.prepare('SELECT 1 FROM temp_voice WHERE channel_id = ?');
export const isTempVoice = (id) => Boolean(isTempVoiceStmt.get(id));
// Team: Web-Zugriff mit Bereichs-Rechten (Owner = ADMIN_DISCORD_ID darf immer alles)
db.exec(`
CREATE TABLE IF NOT EXISTS web_admins (
user_id TEXT PRIMARY KEY,
username TEXT,
scopes TEXT NOT NULL DEFAULT ''
);
`);
const upsertWebAdmin = db.prepare(`
INSERT INTO web_admins (user_id, username, scopes) VALUES (?, ?, ?)
ON CONFLICT(user_id) DO UPDATE SET username = excluded.username, scopes = excluded.scopes
`);
const webAdminStmt = db.prepare('SELECT * FROM web_admins WHERE user_id = ?');
const listWebAdminsStmt = db.prepare('SELECT * FROM web_admins ORDER BY username');
const deleteWebAdminStmt = db.prepare('DELETE FROM web_admins WHERE user_id = ?');
export const saveWebAdmin = (userId, username, scopes) =>
upsertWebAdmin.run(userId, username, scopes.join(','));
export const webAdminScopes = (userId) =>
(webAdminStmt.get(userId)?.scopes ?? '').split(',').filter(Boolean);
export const listWebAdmins = () => listWebAdminsStmt.all();
export const deleteWebAdmin = (userId) => deleteWebAdminStmt.run(userId).changes > 0;
// Game-Server (DiscordGSM-Stil): strukturierte Server statt CSV-Setting
db.exec(`
CREATE TABLE IF NOT EXISTS gameservers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'fivem',
query_url TEXT NOT NULL,
address TEXT NOT NULL DEFAULT '',
message_id TEXT
);
`);
// Einmalige Migration aus dem alten "gameservers"-CSV-Setting ("Name=URL, …")
{
// settings-Tabelle kann hier noch fehlen (wird sonst erst weiter unten angelegt)
db.exec('CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT NOT NULL)');
const hasServers = db.prepare('SELECT count(*) AS n FROM gameservers').get().n > 0;
const legacy = db.prepare(`SELECT value FROM settings WHERE key = 'gameservers'`).get()?.value;
if (!hasServers && legacy) {
const ins = db.prepare(`INSERT INTO gameservers (name, type, query_url) VALUES (?, 'fivem', ?)`);
for (const entry of legacy.split(',')) {
const [name, ...rest] = entry.split('=');
const url = rest.join('=').trim().replace(/\/$/, '');
if (name?.trim() && /^https?:\/\//.test(url)) ins.run(name.trim(), url);
}
db.prepare(`DELETE FROM settings WHERE key = 'gameservers'`).run();
}
}
const insertGameserver = db.prepare(`
INSERT INTO gameservers (name, type, query_url, address) VALUES (@name, @type, @query_url, @address)
`);
const updateGameserverStmt = db.prepare(`
UPDATE gameservers SET name = @name, type = @type, query_url = @query_url, address = @address WHERE id = @id
`);
const setGameserverMessageStmt = db.prepare('UPDATE gameservers SET message_id = ? WHERE id = ?');
const listGameserversStmt = db.prepare('SELECT * FROM gameservers ORDER BY id');
const getGameserverStmt = db.prepare('SELECT * FROM gameservers WHERE id = ?');
const deleteGameserverStmt = db.prepare('DELETE FROM gameservers WHERE id = ?');
export const createGameserver = (s) => insertGameserver.run(s).lastInsertRowid;
export const updateGameserver = (s) => updateGameserverStmt.run(s).changes > 0;
export const setGameserverMessage = (id, messageId) => setGameserverMessageStmt.run(messageId, id);
export const listGameservers = () => listGameserversStmt.all();
export const getGameserver = (id) => getGameserverStmt.get(id) ?? null;
export const deleteGameserver = (id) => deleteGameserverStmt.run(id).changes > 0;
// Moderation: Verwarnungen + Sticky-Roles
db.exec(`
CREATE TABLE IF NOT EXISTS warns (