Files
fuesse-sexy/server/configStore.js
Guido 5018ab753a Admin-Panel (/admin): Auth, Spieler-Tracking, Live-Tuning, Bild-Verwaltung
- 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>
2026-08-05 22:19:52 +02:00

98 lines
2.8 KiB
JavaScript

// Spiel-Parameter, live per Admin-Panel einstellbar, persistent in data/config.json.
// Wird pro Request frisch gelesen -> Aenderungen wirken SOFORT (Server + Client).
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA_DIR = join(__dirname, '..', 'data');
const CONFIG_FILE = join(DATA_DIR, 'config.json');
// Defaults = die bisherigen Konstanten aus public/js/app.js + server/index.js
export const DEFAULT_CONFIG = {
// Timer
startTime: 15,
maxTime: 30,
bonus: 1.2,
penalty: 2.0,
// Objekt-Anzahl-Kurve
countBase: 10,
countPerScore: 1.5,
stepStart: 25,
stepEvery: 3,
stepAdd: 2,
countCap: 100,
// Schwierigkeits-Stufen (Score-Grenzen) + Tempo
stageScatter: 3, // ab hier "scatter"
stageGroups: 6, // ab hier "groups"
stageIndividual: 11, // ab hier "individual"
speedGroupsBase: 45,
speedGroupsPer: 10,
speedGroupsMax: 150,
speedIndivBase: 70,
speedIndivPer: 12,
speedIndivMax: 320,
};
// Nur bekannte Keys uebernehmen, Werte auf sinnvolle Zahlen begrenzen
function sanitize(input) {
const out = { ...DEFAULT_CONFIG };
if (input && typeof input === 'object') {
for (const key of Object.keys(DEFAULT_CONFIG)) {
const v = Number(input[key]);
if (Number.isFinite(v)) out[key] = clampField(key, v);
}
}
return out;
}
function clampField(key, v) {
const bounds = {
startTime: [3, 120],
maxTime: [5, 300],
bonus: [0, 30],
penalty: [0, 30],
countBase: [1, 200],
countPerScore: [0, 20],
stepStart: [0, 500],
stepEvery: [1, 100],
stepAdd: [0, 50],
countCap: [5, 400],
stageScatter: [0, 1000],
stageGroups: [0, 1000],
stageIndividual: [0, 1000],
speedGroupsBase: [0, 2000],
speedGroupsPer: [0, 500],
speedGroupsMax: [0, 5000],
speedIndivBase: [0, 2000],
speedIndivPer: [0, 500],
speedIndivMax: [0, 5000],
};
const [min, max] = bounds[key] || [-1e9, 1e9];
return Math.max(min, Math.min(max, v));
}
export function getConfig() {
try {
if (!existsSync(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
return sanitize(JSON.parse(readFileSync(CONFIG_FILE, 'utf8')));
} catch {
return { ...DEFAULT_CONFIG };
}
}
export function setConfig(input) {
if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true });
const next = sanitize(input);
writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2));
return next;
}
// Objekt-Anzahl aus Config + Score (identisch fuer Server & Client)
export function objectCount(cfg, score) {
let count = cfg.countBase + Math.floor(score * cfg.countPerScore);
if (score >= cfg.stepStart) count += Math.floor((score - cfg.stepStart) / cfg.stepEvery) * cfg.stepAdd;
return Math.min(count, cfg.countCap);
}