Nutzer-Wunsch 21.08.2026: "wenn ich eine änderung mache ist es auch für
jeden für die die schon da sind und die neuen" + "ich will ich titel
und namen von kategorien und seiten ändern können in der verwaltung".
Backend (braucht Filipes manuellen Deploy):
- Migration 0008: team_members bekommt intro/bioHtml/extraCta-Spalten
-- volle Feld-Parität mit den von Hand gepflegten Profilen (Diene/
Patrick/Bananenstift/Marina nutzen diese Felder).
- routes/team.js: neue importLegacyMember()-Funktion -- übernimmt ein
bestehendes Profil 1:1 in die Datenbank, OHNE erneut zu übersetzen
(die vorhandenen, von Hand geschriebenen Übersetzungen bleiben
erhalten). Idempotent: mehrfacher Import erzeugt keine Duplikate.
- routes/site-texts.js (neu): admin-editierbare Kategorie-Namen und
Seitentitel, generischer key->{de,...}-Override über app_settings
(wie "Event des Jahres"), fester Schlüssel-Katalog aus
Sicherheitsgründen. Leeres Feld setzt auf den Standardtext zurück.
- 27 Tests in isolierter Testumgebung geprüft (kein better-sqlite3
lokal kompilierbar).
Frontend:
- window.dogiTeamZusammenfuehren() (main.js): admin-Mitglieder
ÜBERSCHREIBEN jetzt gleichnamige statische Einträge (per slug) statt
sie zu duplizieren -- eine Bearbeitung wirkt dadurch für alle.
- window.dogiSiteTexteLaden() + applyTranslations() erweitert um
data-site-text-key -- Kategorie-Seitentitel (team-modis.html/
team-scouts.html/creator.html/manager.html) sind jetzt live editierbar.
- Verwaltung: "📥 Bestehende Mitglieder importieren"-Knopf (holt VanVan/
Diene/Funny/Miss/Marina/Ghost/Patrick/Bananenstift aus den data-*.js-
Dateien, VanVan bewusst ausgenommen -- eigene Sonderkarte + Seite),
erweitertes Formular (Intro/ausführliche Vorstellung/zweiter Button,
eingeklappt unter "Erweitert"), neue Sektion "Kategorien &
Seitentitel" (4 Karten, sofort wirksam auf Website UND Verwaltung).
Beim Testen einen echten UX-Bug gefunden und gefixt: die "Gespeichert"-
Meldung nach dem Speichern eines Mitglieds wurde von der direkt
anschließenden Formular-Zurücksetzung sofort wieder überschrieben und
war nie sichtbar.
Cache-Busting-Version auf 20260821k erhöht.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
96 lines
4.3 KiB
JavaScript
96 lines
4.3 KiB
JavaScript
/* =====================================================================
|
|
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 });
|
|
}
|