/* ===================================================================== routes/site-texts.js — admin-editierbare Kategorie-Namen & Seitentitel (21.08.2026, Nutzer-Wunsch: "ich will ich titel und namen von kategorien und seiten ändern können in der verwaltung"). Bewusst als generischer key->{de,"de-CH",en,fr,pt}-Override gebaut (wiederverwendet die vorhandene app_settings-Tabelle, key-Präfix "site_text_"), NICHT als eigene Tabelle -- dieselbe Technik wie "Event des Jahres". Ein fester, serverseitiger Katalog erlaubter Keys (ERLAUBTE_KEYS) verhindert, dass beliebige/kaputte Keys gespeichert werden können. Aktuell nur die vier Team-Kategorien + ihre Seitentitel belegt -- das Muster ist aber bewusst so gebaut, dass in einer künftigen Runde weitere Seiten/Überschriften einfach per neuem Key ergänzt werden können (siehe main.js: applyTranslations() prüft data-site-text-key genau wie data-i18n). Öffentlich lesbar (jede Seite braucht das ohne Login), Schreiben nur mit TEAM_MANAGE (dieselbe Berechtigung wie die Team-Verwaltung, bewusst keine eigene -- beides gehört zusammen). ===================================================================== */ import { db } from "../db.js"; import { hasPermission } from "../lib/permissions.js"; import { json } from "../lib/http.js"; import { logAction } from "../lib/audit.js"; import { uebersetzeAlleSprachen } from "../lib/translate.js"; function can(session, key) { if (!session) return false; if (session.isOwner) return true; return hasPermission(session.permissions, session.overrides, key); } function forbidden(res) { return json(res, { ok: false, error: "Keine Berechtigung." }, 403); } // key -> { label, maxLen } -- label ist nur für Log-/Fehlermeldungen. const ERLAUBTE_KEYS = { kategorie_modi: { label: "Kategorie-Name: Modi", maxLen: 40 }, kategorie_scout: { label: "Kategorie-Name: Scout", maxLen: 40 }, kategorie_creator: { label: "Kategorie-Name: Creator", maxLen: 40 }, kategorie_manager: { label: "Kategorie-Name: Manager", maxLen: 40 }, seitentitel_modi: { label: "Seitentitel: Modi-Team", maxLen: 100 }, seitentitel_scout: { label: "Seitentitel: Scout-Team", maxLen: 100 }, seitentitel_creator: { label: "Seitentitel: Creator", maxLen: 100 }, seitentitel_manager: { label: "Seitentitel: Manager", maxLen: 100 }, }; function settingsKey(key) { return `site_text_${key}`; } /** Öffentlich, keine Session: liefert alle aktuell GESETZTEN Overrides als { key: {de,"de-CH",en,fr,pt} }. Keys ohne eigenen Override fehlen einfach im Ergebnis -- die aufrufende Seite behält dann ihren eingebauten Standardtext (siehe main.js). */ export async function getSiteTextsPublic(req, res) { const rows = db .prepare(`SELECT key, value FROM app_settings WHERE key LIKE 'site_text_%'`) .all(); const texte = {}; for (const row of rows) { const kurzKey = row.key.replace(/^site_text_/, ""); try { texte[kurzKey] = JSON.parse(row.value); } catch { /* kaputter Eintrag -> einfach überspringen, kein Fehler für alle anderen Keys */ } } return json(res, { ok: true, texte }); } export async function saveSiteText(req, res, session) { if (!can(session, "TEAM_MANAGE")) return forbidden(res); const body = req.body || {}; const key = String(body.key || "").trim(); const eintrag = ERLAUBTE_KEYS[key]; if (!eintrag) return json(res, { ok: false, error: "Unbekannter Text-Schlüssel." }, 400); const textDe = String(body.text || "").trim().slice(0, eintrag.maxLen); if (!textDe) { // Leeres Feld = Override entfernen -> Seite fällt zurück auf ihren // eingebauten Standardtext (Dogi kann so jederzeit "zurücksetzen"). db.prepare(`DELETE FROM app_settings WHERE key = ?`).run(settingsKey(key)); logAction(session.actor, "site_text.reset", key, null); return json(res, { ok: true, text: null }); } const text = await uebersetzeAlleSprachen(textDe); db.prepare( `INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at` ).run(settingsKey(key), JSON.stringify(text), new Date().toISOString()); logAction(session.actor, "site_text.saved", key, { textDe }); return json(res, { ok: true, text }); }