"Event des Jahres" jetzt in der Verwaltung pflegbar (Bild, Text, Link)
Nutzer-Wunsch 20.08.2026: "will ich in der admin seite das selbst gestalten können, mit bild und text und am besten auch einen link." Auf Rueckfrage entschieden: Titel/Text nur auf Deutsch eingeben, die anderen 4 Sprachen werden beim Speichern automatisch uebersetzt (MyMemory, 0€, kein API-Key) -- bewusste Ausnahme von der sonst geltenden "immer echte Uebersetzung"-Regel, klar dokumentiert und im Admin-UI selbst als Hinweis sichtbar. de-CH bekommt denselben deutschen Text (Dialekt ist keine von Uebersetzungs-APIs unterstuetzte Zielsprache). Backend (server-internal): - routes/events.js: getEventsPublic (oeffentlich, keine Session), saveEvent + uploadEventImage (beide hinter neuer EVENTS_MANAGE- Berechtigung, Owner immer erlaubt). Speichert in der bereits bestehenden app_settings-Tabelle (2 feste Slots) statt einer neuen Tabelle -- es gibt nie mehr als genau 2 Events. - lib/translate.js: MyMemory-Anbindung mit "fail closed auf Deutsch" pro Sprache, falls der Dienst mal nicht antwortet. - Bild-Upload per multer, zufaelliger Dateiname (crypto.randomUUID, verhindert Path-Traversal ueber den Originalnamen komplett), 5 MB Limit, nur jpeg/png/webp, Ablage unter /var/lib/dogfather-internal/ uploads/events (NICHT im Git-Ordner -- uebersteht Deploys), oeffentlich ausgeliefert unter /uploads. - Neue Berechtigung EVENTS_MANAGE im Katalog (Gruppe "Startseite"). Frontend: - index.html: laedt /events-of-year beim Aufruf, ueberschreibt pro Slot Datum/Titel/Text/Bild/Link NUR wenn dort tatsaechlich etwas gespeichert ist -- bleibt der Abruf aus oder ist ein Slot leer, bleibt der bisherige fest eingebaute Standardinhalt (dieselben zwei echten Events) stehen. Kein Blocker, kein sichtbarer Fehler bei Ausfall. - verwaltung.html: neue Sektion "🏆 Event des Jahres" (nur mit EVENTS_MANAGE sichtbar), 2 Karten mit Bild-Upload+Vorschau, Datum, Titel, Text, Link, eigenem Speichern-Knopf pro Event. Ausfuehrlich getestet, weil server-internal wegen fehlender Visual- Studio-Build-Tools auf dieser Windows-Maschine nicht lokal mit echtem better-sqlite3 laufen kann: routes/events.js komplett isoliert gegen eine Fake-DB getestet (11 Szenarien: oeffentlicher Abruf, fehlende Session, Session ohne Recht, Owner, Rolle MIT EVENTS_MANAGE, alle Validierungen, Bild-Upload inkl. falscher Dateityp, Abruf des hochgeladenen Bilds). index.html per Playwright mit echtem Netzwerk-Mocking gegen zwei Szenarien getestet (API nicht erreichbar -> Standardinhalt bleibt; API liefert echte Daten -> nur der befuellte Slot wird ueberschrieben, der leere bleibt Standard). Dabei einen echten CSS-Bug gefunden und behoben (display:block auf .jahres-event-bild überschrieb die [hidden]-Regel des Browsers, leeres Bild waere immer sichtbar gewesen). verwaltung.html per Playwright mit gemocktem Login+API end-to-end getestet: Formular wird korrekt vorbefuellt, Bild-Upload + Speichern senden die richtigen Daten. WICHTIG: server-internal laeuft unter einem eigenen Systembenutzer (dogiintern), auf den ich (claudian) bewusst KEINEN Zugriff habe -- diese Aenderung kann ich anders als sonst nicht selbst bis auf den Server bringen. Dogi muss den Deploy-Schritt fuer server-internal selbst ausfuehren (git pull + npm install + Neustart des dogiintern-Dienstes). Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+53
-2
@@ -91,13 +91,15 @@
|
||||
<span class="tag" data-i18n="idx_jahres_events_tag">🏆 Event des Jahres</span>
|
||||
</div>
|
||||
<div class="jahres-events-grid">
|
||||
<a class="jahres-event-card" href="zeitreise.html">
|
||||
<a class="jahres-event-card" href="zeitreise.html" id="jahres-event-1">
|
||||
<img class="jahres-event-bild" alt="" hidden />
|
||||
<span class="jahres-event-datum" data-i18n="idx_jahres_event1_datum">7. März 2026</span>
|
||||
<h3 data-i18n="idx_jahres_event1_h3">Der 28-Stunden-Stream</h3>
|
||||
<p data-i18n="idx_jahres_event1_p">DogFather stellt sich einer besonderen Herausforderung und streamt 28 Stunden am Stück.</p>
|
||||
<span class="jahres-event-mehr" data-i18n="idx_jahres_events_mehr">Zur Zeitreise →</span>
|
||||
</a>
|
||||
<a class="jahres-event-card" href="zeitreise.html">
|
||||
<a class="jahres-event-card" href="zeitreise.html" id="jahres-event-2">
|
||||
<img class="jahres-event-bild" alt="" hidden />
|
||||
<span class="jahres-event-datum" data-i18n="idx_jahres_event2_datum">27. Juli bis 2. August 2026</span>
|
||||
<h3 data-i18n="idx_jahres_event2_h3">Der Creator Cup</h3>
|
||||
<p data-i18n="idx_jahres_event2_p">Gemeinsam mit Cidem organisiert DogFather den Creator Cup für die Streamer von Spicy Media.</p>
|
||||
@@ -355,6 +357,55 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
}
|
||||
|
||||
/* "Event des Jahres": seit 20.08.2026 in der Verwaltung pflegbar (Bild,
|
||||
Text, Link je Slot, siehe verwaltung.html + server-internal/routes/
|
||||
events.js). Lädt beim Seitenaufruf, was Dogi/VanVan zuletzt gespeichert
|
||||
haben — bleibt der Fetch aus (noch nie gespeichert, Dienst nicht
|
||||
erreichbar) oder ist ein Slot noch leer, bleibt bewusst der fest
|
||||
eingebaute Standardinhalt aus dem HTML stehen (dieselben zwei echten
|
||||
Events wie bisher) statt einer kaputten/leeren Kachel. */
|
||||
function wendeJahresEventAn(slot, daten) {
|
||||
if (!daten) return; // kein gespeicherter Eintrag -> Standardinhalt im HTML bleibt
|
||||
const karte = document.getElementById(`jahres-event-${slot}`);
|
||||
if (!karte) return;
|
||||
const feld = (x) => (window.dogiFeld ? window.dogiFeld(x) : x || "");
|
||||
const bild = karte.querySelector(".jahres-event-bild");
|
||||
if (daten.bild) {
|
||||
bild.src = daten.bild;
|
||||
bild.alt = feld(daten.titel);
|
||||
bild.hidden = false;
|
||||
}
|
||||
karte.querySelector(".jahres-event-datum").textContent = daten.datum || "";
|
||||
karte.querySelector("h3").textContent = feld(daten.titel);
|
||||
karte.querySelector("p").textContent = feld(daten.text);
|
||||
if (daten.link) karte.href = daten.link;
|
||||
}
|
||||
|
||||
if (API_BASE_URL) {
|
||||
let letzteJahresEvents = null;
|
||||
const ladeJahresEvents = () =>
|
||||
fetch(`${API_BASE_URL}/events-of-year`, { cache: "no-store" })
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (!data?.ok) return;
|
||||
letzteJahresEvents = data.events;
|
||||
wendeJahresEventAn(1, data.events[0]);
|
||||
wendeJahresEventAn(2, data.events[1]);
|
||||
})
|
||||
.catch(() => {
|
||||
/* Standardinhalt aus dem HTML bleibt stehen -- kein sichtbarer Fehler nötig. */
|
||||
});
|
||||
ladeJahresEvents();
|
||||
// Bei Sprachwechsel mit den zuletzt geladenen Daten neu rendern (kein
|
||||
// erneuter Server-Aufruf nötig) — Titel/Text stehen ja bereits in allen
|
||||
// Sprachen im geladenen Objekt.
|
||||
document.addEventListener("dogi-sprache-geaendert", () => {
|
||||
if (!letzteJahresEvents) return;
|
||||
wendeJahresEventAn(1, letzteJahresEvents[0]);
|
||||
wendeJahresEventAn(2, letzteJahresEvents[1]);
|
||||
});
|
||||
}
|
||||
|
||||
// Stats: Team-Größe aus den Datenobjekten berechnen
|
||||
const teamCount = (window.MODIS || []).length + (window.SCOUTS || []).length;
|
||||
const statTeam = document.getElementById("stat-team");
|
||||
|
||||
Reference in New Issue
Block a user