- Passwortgeschütztes Panel unter /admin (Auto-Passwort 20 Zeichen, per CLI abrufbar: `node server/admin.js password` / `password reset`), In-Memory-Sessions + Cookie, Login-Rate-Limit. - Spieler-Tracking: pro Score IP (CF/XFF/req.ip), Gerät/Browser (ua-parser-js), grober Ort (geoip-lite, offline) -> data/players.json; Aggregation pro Name. - Live-Tuning: configStore (data/config.json) für Timer + Schwierigkeitskurve; Server (makeRound) und Client (/api/config) lesen daraus -> Änderungen ohne Neustart. - Bild-Verwaltung: Upload/Löschen von Füßen & Deko (multer) + rescanLibrary(). - Panel-Aktionen: Spieler bannen/entbannen, Rangliste heute/alles leeren. - Fix: .item object-fit:contain -> nicht-quadratische Bilder werden nicht mehr verzerrt. - trust proxy für korrekte Client-IP hinter Reverse-Proxy/Cloudflare. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
138 lines
4.8 KiB
JavaScript
138 lines
4.8 KiB
JavaScript
// Rangliste per Kommandozeile verwalten.
|
|
// Aufruf: node server/admin.js <befehl> [argument]
|
|
// (oder ueber npm: npm run lb -- <befehl> [argument])
|
|
//
|
|
// Befehle:
|
|
// list zeigt beide Ranglisten (Allzeit + Heute)
|
|
// delete "<name>" loescht ALLE Eintraege eines Spielers (Name exakt, Gross/Klein egal)
|
|
// clear-today loescht nur die heutigen Eintraege
|
|
// clear-all loescht die komplette Rangliste
|
|
|
|
import { readLeaderboard, buildBoards, deletePlayer, clearAll, clearToday } from './leaderboardStore.js';
|
|
import { getMode, setMode } from './modeStore.js';
|
|
import { banPlayer, unbanPlayer, readBans } from './banStore.js';
|
|
import { getPassword, resetPassword } from './adminAuth.js';
|
|
|
|
const [, , cmd, ...rest] = process.argv;
|
|
const arg = rest.join(' ').trim();
|
|
|
|
function printBoards() {
|
|
const { allTime, today } = buildBoards(readLeaderboard());
|
|
const fmt = (list) =>
|
|
list.length
|
|
? list.map((e, i) => ` ${String(i + 1).padStart(2)}. ${e.name} — ${e.score}`).join('\n')
|
|
: ' (leer)';
|
|
console.log('\n🏆 Allzeit (Top 5)\n' + fmt(allTime));
|
|
console.log('\n📅 Heute (Top 10)\n' + fmt(today) + '\n');
|
|
}
|
|
|
|
switch (cmd) {
|
|
case 'list':
|
|
printBoards();
|
|
break;
|
|
|
|
case 'delete': {
|
|
if (!arg) {
|
|
console.error('Bitte einen Namen angeben: node server/admin.js delete "Name"');
|
|
process.exit(1);
|
|
}
|
|
const n = deletePlayer(arg);
|
|
console.log(n > 0 ? `✅ ${n} Eintrag/Eintraege von "${arg}" geloescht.` : `Keine Eintraege für "${arg}" gefunden.`);
|
|
break;
|
|
}
|
|
|
|
case 'clear-today': {
|
|
const n = clearToday();
|
|
console.log(`✅ ${n} heutige Eintrag/Eintraege geloescht.`);
|
|
break;
|
|
}
|
|
|
|
case 'clear-all': {
|
|
const n = clearAll();
|
|
console.log(`✅ Rangliste geleert (${n} Eintraege entfernt).`);
|
|
break;
|
|
}
|
|
|
|
case 'ban': {
|
|
if (!arg) {
|
|
console.error('Bitte einen Namen angeben: node server/admin.js ban "Name"');
|
|
process.exit(1);
|
|
}
|
|
const added = banPlayer(arg);
|
|
const removed = deletePlayer(arg); // bestehende Eintraege gleich entfernen
|
|
if (added) console.log(`🚫 "${arg}" gebannt (kann keinen Score mehr eintragen).`);
|
|
else console.log(`"${arg}" war bereits gebannt.`);
|
|
if (removed) console.log(` ${removed} bestehende(r) Eintrag/Eintraege entfernt.`);
|
|
break;
|
|
}
|
|
|
|
case 'unban': {
|
|
if (!arg) {
|
|
console.error('Bitte einen Namen angeben: node server/admin.js unban "Name"');
|
|
process.exit(1);
|
|
}
|
|
console.log(unbanPlayer(arg) ? `✅ "${arg}" entsperrt.` : `"${arg}" war nicht gebannt.`);
|
|
break;
|
|
}
|
|
|
|
case 'bans': {
|
|
const bans = readBans();
|
|
console.log(bans.length ? '🚫 Gebannt:\n' + bans.map((b) => ` - ${b}`).join('\n') : 'Keine Bans.');
|
|
break;
|
|
}
|
|
|
|
case 'password': {
|
|
if (arg === 'reset') {
|
|
console.log('🔑 Neues Admin-Passwort: ' + resetPassword());
|
|
} else {
|
|
console.log('🔑 Admin-Passwort: ' + getPassword());
|
|
console.log(' (neu erzeugen: node server/admin.js password reset)');
|
|
}
|
|
console.log(' Login unter: /admin');
|
|
break;
|
|
}
|
|
|
|
case 'mode': {
|
|
const sub = (rest[0] || '').toLowerCase();
|
|
if (sub === 'public') {
|
|
setMode('public');
|
|
console.log('✅ Modus: PUBLIC — das Spiel ist unter "/" erreichbar.');
|
|
} else if (sub === 'private') {
|
|
const slug = rest[1] || getMode().path;
|
|
const m = setMode('private', slug);
|
|
console.log(`✅ Modus: PRIVATE — "/" zeigt "nicht erlaubt".`);
|
|
console.log(` Spiel jetzt nur unter: /${m.path}`);
|
|
} else {
|
|
const m = getMode();
|
|
console.log(`Aktueller Modus: ${m.mode.toUpperCase()}` + (m.mode === 'private' ? ` (Spiel unter /${m.path})` : ''));
|
|
console.log('Umschalten: node server/admin.js mode public | node server/admin.js mode private [pfad]');
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
console.log(`füsse.sexy — Verwaltung:
|
|
|
|
Rangliste:
|
|
node server/admin.js list beide Ranglisten anzeigen
|
|
node server/admin.js delete "<name>" einen bestimmten Spieler loeschen
|
|
node server/admin.js clear-today nur die heutige Rangliste leeren
|
|
node server/admin.js clear-all komplette Rangliste leeren
|
|
|
|
Bannen (namensbasiert):
|
|
node server/admin.js ban "<name>" Spieler sperren (+ Eintraege entfernen)
|
|
node server/admin.js unban "<name>" Sperre aufheben
|
|
node server/admin.js bans gebannte Namen anzeigen
|
|
|
|
Admin-Panel (/admin):
|
|
node server/admin.js password Admin-Passwort anzeigen
|
|
node server/admin.js password reset neues Passwort erzeugen
|
|
|
|
Betriebsmodus (Spiel oeffentlich / privat):
|
|
node server/admin.js mode aktuellen Modus anzeigen
|
|
node server/admin.js mode public Spiel oeffentlich unter "/"
|
|
node server/admin.js mode private [pfad] "/" gesperrt, Spiel unter "/<pfad>" (Default: play)
|
|
`);
|
|
if (cmd) process.exit(1);
|
|
}
|