66 KiB
Mindflux – Detailpläne für die 4 geplanten Spiele + Verbesserungsplan
Kontext
Das Dashboard von Mindflux (mindflux2, Spring Boot 3.4.4 / Java 17 / PostgreSQL + Flyway / Thymeleaf / Vanilla-JS) zeigt 9 Spiele. Vier davon sind nur Platzhalter-Karten, die auf /comingsoon (dummy.html) zeigen und keinerlei Mechanik, Controller, Tabellen oder Inhalte besitzen:
- Code Escape Room – „Entkomme mit IT-Rätseln aus digitalen Räumen – sei schnell und clever."
- Service Clash – „Der chaotische Admin-Alltag als Spiel – meistere Anfragen & halte Systeme am Laufen!"
- Bug Hunter – „Finde Fehler im Code, denke wie ein Debugger und löse knifflige Bugs."
- Netzwerker – Das Router-Rätselspiel – „Verbinde Netzwerke, konfiguriere IPs und löse Routing-Herausforderungen."
Die Infrastruktur wartet bereits auf sie: Die Spiel-Keys codeescape, serviceclash, bughunter, networker stehen in der Score-Allowlist (GameScoreController.KNOWN_GAMES) und in ProgressService.GAMES; die Dashboard-Icons (lock, sword, bug, network) existieren in dashboard.html. Ziel dieses Plans: detaillierte, umsetzungsreife Designs für alle vier Spiele nach dem etablierten Architektur-Rezept der vorhandenen Spiele (MindMatch, TechTacular, SyntaxSaga, DevLab) sowie ein priorisierter Verbesserungs-/Optimierungskatalog für den Rest der Plattform.
Zielgruppe & inhaltlicher Anspruch: die GESAMTE Fachinformatiker-Ausbildung. Nicht nur AP1 – abgedeckt werden muss die komplette Ausbildung bis einschließlich AP2 (beide Fachrichtungen: Anwendungsentwicklung/FIAE und Systemintegration/FISI). Konsequenzen für alle Spiele und Inhalte:
- Schwierigkeitsstufen = Ausbildungsprogression. Die in allen vier Designs vorhandenen Stufen (Junior/Senior/Principal, leicht/mittel/schwer, Welten 1–3, Schicht 1–3) werden inhaltlich gemappt: untere Stufen ≈ 1. Lehrjahr/AP1-Stoff, obere Stufen ≈ AP2-Niveau. Kein Schema-Umbau nötig – die
difficulty-Felder existieren in allen Datenmodellen bereits; es ist eine Content-Kuratierungs-Regel. - AP2-Content pro Spiel (Startrichtung): CodeEscape → Szenarien zu Verschlüsselung/Zertifikaten, Virtualisierung/Container, Backup-Strategien; BugHunter → Principal-Fälle auf FIAE-AP2-Niveau (Nebenläufigkeit/Threads, Transaktions-/SQL-Fehler, Design-Pattern-Missbrauch, OWASP-Lücken); ServiceClash → Schichten mit Change-Management, Monitoring/Eskalationsketten, Security-Incidents (FISI-AP2); NetWorker → Welten zu VLAN, statischem Routing über mehrere Hops, Firewall-Regeln und (Ausbau) IPv6/Subnetting-Transfer.
- Content-Tagging: Neue Parent-Inhalte erhalten von Anfang an ein persistiertes Feld
exam_scope VARCHAR(20) NOT NULL DEFAULT 'AP1'mit der AllowlistAP1 | AP2_FIAE | AP2_FISI | CROSS. Seeder und Admin validieren dieses Feld; Katalog-DTOs geben es aus. Titelpräfixe sind nur Anzeige, nie Datenquelle. Lobby-Filter können dadurch später ohne Migration ergänzt werden. - FinalFlux erweitern: Das bestehende Lernmodul ist rein AP1-fokussiert (28 Seiten + FluxChecks). Die Struktur (statische Themenseiten + Frage-JSONs) trägt problemlos einen AP2-Ast – als eigener Sidebar-Block „AP2 Anwendungsentwicklung" / „AP2 Systemintegration" (in Teil 2 als Punkt S10 aufgenommen).
- Die vorhandenen FinalFlux-Inhalte (IP-Berechnung, VLAN, OSI, Wirtschaft) bleiben die AP1-Basis, auf der die Spiele aufsetzen.
Deliverable: Dieser Plan liegt als mindflux2/mindfluxfuture.md im Repo (neben STYLEGUIDE.md/CHANGELOG.md).
Namensschema: einheitlich, englisch, zusammengeschrieben
✅ Umgesetzt am 2026-07-12: Anzeigenamen und Spiel-Keys sind auf das Schema umgestellt (Dashboard-
GAMES-Array,KNOWN_GAMES,ProgressService).
Vorgabe: Alle Spielnamen folgen dem MindMatch-Muster – ein Wort, englisch, CamelCase. „Netzwerker – Das Router-Rätselspiel" passt nicht ins Schema und wird umbenannt. Neue Namenstabelle (Beschreibungen bleiben deutsch):
| Bisher | Neu (Anzeigename) | Route | Spiel-Key (DB/API) |
|---|---|---|---|
| MindMatch | MindMatch ✓ | /mindmatch |
mindmatch |
| TechTacular | TechTacular ✓ | /techtacular |
techtacular |
| Dev Lab | DevLab | /devlab |
devlab |
| Syntax Saga | SyntaxSaga | /syntaxsaga |
syntax-saga (Key behalten – es existieren bereits Scores) |
| FinalFlux | FinalFlux ✓ | /finalflux |
– |
| Code Escape Room | CodeEscape | /codeescape |
codeescape |
| Service Clash | ServiceClash | /serviceclash |
serviceclash |
| Bug Hunter | BugHunter | /bughunter |
bughunter |
| Netzwerker – Das Router-Rätselspiel | NetWorker | /networker |
networker |
Begründung Keys: Die vorhandenen Routen sind alle klein und zusammengeschrieben (/mindmatch, /syntaxsaga …) – die neuen Routen folgen dem. Die historischen Plan-Aliasse escape-room, service-clash, bug-hunter und netzwerker hatten noch keine Scores und wurden deshalb vor der Implementierung auf codeescape, serviceclash, bughunter und networker vereinheitlicht. Nur syntax-saga behält seinen Key wegen möglicher Bestandsdaten. Die Tabelle und der Abschnitt „Gemeinsames Architektur-Rezept" sind die verbindliche Quelle; verbliebene Altbegriffe in erklärenden Designpassagen sind keine Routen- oder Schema-Vorgaben.
Visuelle Identität: vier Spiele, vier Welten
◐ Fundament umgesetzt: Die Dark-/Light-Akzent-Tokens für alle vier Spiele sind in
theme.cssangelegt. Offen bleiben die konkreten Karten-Designs, Spiel-Layouts, Illustrationen und Animationen; diese entstehen jeweils zusammen mit dem betreffenden Spiel.
Vorgabe: Die Spiele sollen grafisch stark ausgeprägt sein und sich sehr deutlich voneinander unterscheiden – nicht nur mechanisch, sondern auf den ersten Blick. Dazu zwei Maßnahmen:
1. Kontrollierte Styleguide-Erweiterung – Spiel-Akzente: theme.css bekommt pro Spiel ein Akzent-Token-Paar (z. B. --esc-accent, --bh-accent, --sc-accent, --nw-accent, je mit Dark-/Light-Wert). Jedes Spiel-CSS überschreibt im Seiten-Root lokal --accent/--glow-* mit seinem Spiel-Akzent. Die Grundregeln bleiben: Erfolg = blaues ✓, Fehler = rotes ✗, nie grün als Erfolgsfarbe; beide Themes müssen funktionieren.
2. Eigenes visuelles Vokabular pro Spiel (Layout, Illustration, Animation, Typografie-Akzente):
| Spiel | Welt-Metapher | Farbstimmung | Prägende visuelle Elemente |
|---|---|---|---|
| CodeEscape | Dunkle Facility bei Nacht | Bernstein/Orange auf Tiefschwarz | CRT-Terminals mit Scanline-Overlay, physisches Keypad mit Tasten-Feedback, Raum-„Bühnenbild" als SVG-Illustration (Racks, Türen, flackernde Neonröhre), Fragment-Chips wie Hardware-Token, Alarm-Puls bei Zeitablauf, Tür-Öffnungs-Animation zwischen Räumen |
| BugHunter | Noir-Detektivbüro | Entsättigt + Crimson-Akzente | Fallakten-Optik (Aktendeckel, Büroklammer, „VERTRAULICH"-Stempel), IDE-Look mit Zeilennummern-Gutter und Tab-Reitern, roter Ermittlungsfaden zum markierten Bug, „GELÖST"-Stempel-Animation im Debriefing, Schreibmaschinen-Typo für Briefings |
| ServiceClash | Hektischer Leitstand/Cockpit | Kühles Cyan + Warnrot | Kanban-artige Ticket-Queue mit einfliegenden Karten, große SLA-Ring-Gauges und Gesundheits-Meter, laufender Event-Ticker unten, Wellen-Countdown-Banner, komisch überzeichnete Ticket-Icons (brennender Drucker 🔥), Screen-Shake bei Major Incidents |
| NetWorker | Technische Blaupause | Blueprint-Blau auf Millimeterpapier | Raster-Hintergrund wie Konstruktionszeichnung, Geräte als Schematic-Glyphen, Gummiband-Kabelzug, leuchtende Paket-Punkte, die den echten Trace entlangfliegen und am Fehler-Hop rot zerplatzen, Diagnose-Callouts wie Plan-Anmerkungen |
Dazu je Spiel ein eigenes Karten-Design auf dem Dashboard (Hover-Animation im jeweiligen Spiel-Akzent), damit die Unterscheidbarkeit schon in der Übersicht beginnt. Die Interaktionsformen sind ohnehin grundverschieden: Modal-Rätsel + Keypad (CodeEscape), klickbarer Code-Viewer (BugHunter), Echtzeit-Queue (ServiceClash), frei bespielbare SVG-Canvas (NetWorker).
Das gemeinsame Architektur-Rezept (gilt für alle 4 Spiele)
Jedes neue Spiel folgt exakt dem Muster der vorhandenen Spiele (bester Bauplan: Dev Lab, DevLabService.java + devlabgame.js):
- Flyway-Migration ab
db/migration/V11__…(V9/V10 sind durch User-/Datenintegrität belegt;ddl-auto=validate⇒ Migration ist Pflicht). Reserviert: V11 CodeEscape, V12 BugHunter, V13 NetWorker, V14 ServiceClash; danach FluxNet. SQL-Stil:BIGINT GENERATED BY DEFAULT AS IDENTITY,slug VARCHAR UNIQUE,exam_scope VARCHAR(20),sort_order INT DEFAULT 0,active BOOLEAN DEFAULT TRUE,FK … ON DELETE CASCADE, Indizes. - Entities in
model/(Parent mit@OneToMany(cascade=ALL, orphanRemoval=true), Lösungs-Logik nur serverseitig auf der Entity). - Repository in
repository/(findAllByActiveTrueOrderBySortOrderAscIdAsc(),findBySlug,countBy…Id). - Service in
service/mit KonstanteGAME = "<key>", injiziertGameScoreService;@Transactional(readOnly=true)für Build/Check,@Transactionalfürfinish. Score wird ausschließlich serverseitig berechnet (gameScoreService.recordScore(user, GAME, deck, score, moves, seconds)); Client sendet nur Roh-Antworten. - Punkte-Konventionen der Plattform: 1. Versuch volle Punkte, 2. Versuch ×0.6, danach ×0.3, aufgedeckte Lösung = 0; Sterne: Quote ≥0.8 → 3, ≥0.5 → 2, sonst 1;
MAX_ATTEMPTS = 3. - DTOs in
dto/: lösungsfreie View-DTOs, Request-DTOs,*Result(score, maxScore, solved, total, stars, highscore, newHighscore, plays). - Controller:
@Controller(GET /<spiel>→ Thymeleaf-View) +@RestController @RequestMapping("/api/<spiel>")mitGET /session|board,POST /check(mitreveal-Flag),POST /finish;Principalfür den Usernamen. - Seeder (
CommandLineRunnerinconfig/, Guardcount() > 0, liestresources/<spiel>/content.json). - Admin-CRUD
@Controller @RequestMapping("/admin/<spiel>")mit Redirect-after-Post + Karte intemplates/admin/dashboard.html. - Frontend: Template in
templates/, eigenes CSS mit eindeutigem Präfix (nurtheme.css-Tokens, Erfolg = blaues ✓, Fehler = rotes ✗, nie grün), JS als IIFE instatic/js/, Highscore viaGET /api/games/<key>/stats, Cache-Busting?v=N. - Integration: Route in
SecurityConfig-CSRF-Ignore-Liste aufnehmen, Dashboard-Karte indashboard.htmlvon/comingsoonauf die echte Route umstellen. (KNOWN_GAMES+ProgressServiceenthalten die Keys bereits.)
Verbindlicher Sicherheitsvertrag für neue Spiele (S3)
- Session-/Katalogantworten enthalten nur opaque IDs und lösungsfreie DTOs; Lösung, Reveal-Status und Scoring-Metadaten bleiben serverseitig.
finishakzeptiert nur IDs aus der angeforderten Runde und verwirft fremde, doppelte oder nicht zum Parent gehörende IDs.- Punkte, Sterne, Highscore, gelöste Anzahl und Boni werden ausschließlich serverseitig aus kanonischen Daten neu berechnet.
durationSecondsmuss plausibel sein (>= 1und höchstens das konfigurierte Limit plus dokumentierte Toleranz); Listen und Zähler erhalten fachliche Obergrenzen.- Reveal-/Hint-Nutzung wird serverseitig protokolliert oder beim Finish erneut validiert und kann nicht durch Weglassen im Request verborgen werden.
- Deterministische Seeds werden spätestens bei ServiceClash mit einem serverseitigen HMAC signiert; der Client darf keinen frei wählbaren Seed zur Wertung einreichen.
- Für jedes Spiel entstehen Negativtests: fremde IDs, Duplikate, überlange Listen, unmögliche Zeiten, manipulierte gesperrte Felder und Reveal-vor-Finish.
Teil 1: Spieldesigns
Spiel 1: CodeEscape (Key: codeescape, Migration V11 + Datenfix V11.1) ✅ MVP inklusive Spielführung umgesetzt
Konzept: Der Spieler ist in einer digitalen Umgebung eingesperrt (Serverraum, Admin-Büro, Rechenzentrum). Ein Szenario = ein Durchlauf mit 3 Räumen in fester Reihenfolge; jeder Raum enthält 2–3 IT-Rätsel plus ein Tür-Keypad. Jedes gelöste Rätsel liefert ein Code-Fragment (Ziffern/Hex-Chunk in einer Inventar-Leiste); die Fragmente ergeben zusammen den Türcode zum nächsten Raum. Countdown erzeugt Zeitdruck; ein 2-stufiges Hinweis-System tauscht Punkte gegen Hilfe.
Lernziele (AP1 → AP2): Zahlensysteme (Bin/Hex/Dez), Ports & Protokolle, Subnetting, TCP-Handshake/Boot-Reihenfolgen, RAID, Hardware, SQL/Shell-Grundlagen – als Rätsel verpackt statt als Quiz. Schwere Szenarien tragen AP2-Stoff (Verschlüsselung/Zertifikate, Virtualisierung/Container, Backup-Strategien).
Spielablauf:
- Lobby: Szenario-Karten (Titel, Thema, Schwierigkeit, Zeitlimit, persönliche Bestzeit).
- Raum-Ansicht: Rätsel-Karten im Terminal-Look, Fragment-Inventar, Tür-Keypad. Rätsel öffnen sich im Modal.
- Rätsel-Versuch:
MAX_ATTEMPTS = 3wie Dev Lab; danach „Auflösen"-Button. Bei richtig oder aufgedeckt liefert der Server das Code-Fragment – Aufdecken blockiert nie den Fortschritt, es nullt nur die Punkte (Plattform-Konvention). - Tür: Spieler tippt den zusammengesetzten Code; Server validiert (
POST /door). Falsch → rotes Blinken, keine Punktstrafe (die laufende Uhr ist die Strafe). - Flucht/Ergebnis:
POST /finishrechnet alles serverseitig nach; Ergebnis-Screen mit Score-Aufschlüsselung (Rätselpunkte, Türboni, Hinweis-Abzug, Zeitbonus), Sternen, Highscore. „Aufgeben"-Button beendet früher (kein Zeitbonus,escaped=false).
Punkteformel (serverseitig in /finish nachgerechnet):
- Pro Rätsel:
punkte × versuchsfaktor × hinweisfaktor(Versuch: 1.0/0.6/0.3, aufgedeckt 0; Hinweis: keiner ×1.0, Stufe 1 ×0.8, Stufe 2 ×0.6). Hinweis-Nutzung wird serverseitig in der HttpSession protokolliert (der/hint-Endpoint merkt sich die höchste gelieferte Stufe pro Rätsel) – der Client kann sie nicht unterschlagen. (Einzige bewusste Abweichung vom sonst zustandslosen Muster.) - Pro geöffneter Tür: pauschal +50.
- Sterne aus
baseScore/maxScore(≥0.8→3, ≥0.5→2). Zeitbonus danach, nur bei Flucht:max(0, round(500 × (zeitlimit − sekunden) / zeitlimit))– die MindMatch-Idee, auf 15-Minuten-Läufe skaliert. Timer auf 0 bricht nicht ab: HUD wechselt in „ALARM"-Zustand (pulsierendesvar(--danger)), der Bonus ist nur weg. recordScore(user, "codeescape", szenarioSlug, baseScore + zeitbonus, gelösteRätsel, sekunden).
Datenmodell (V11__codeescape.sql): 3 Tabellen im V8-Stil:
escape_scenarios(id, slug UNIQUE, title, difficulty, description, time_limit_seconds INT DEFAULT 900, sort_order, active)escape_rooms(id, scenario_id FK CASCADE, title, intro, door_prompt, door_code VARCHAR(50), success_text, sort_order)escape_puzzles(id, room_id FK CASCADE, puzzle_type VARCHAR(30), prompt, points INT DEFAULT 100, code_fragment VARCHAR(20), sort_order, payload VARCHAR(8000))- Indizes auf beide FKs.
code_fragment/door_codeals Spalten (nicht im Payload), damit Check/Door sie ohne JSON-Parsing haben und der Admin sie auf einen Blick sieht. Hinweise liegen im Payload als"hints": [...].
Backend: Entities EscapeScenario/EscapeRoom/EscapePuzzle (Struktur wie DevLabGame/DevLabTask, LAZY, addRoom/addPuzzle-Helper). Repositories mit den Standard-Derived-Queries. EscapeRoomService mit KNOWN_TYPES = Set.of("code","choice","order","match"):
buildCatalog()/buildSession(slug)– Szenario mit allen Räumen, Rätsel persanitize()lösungsfrei (keine Lösungen, Fragmente, Hints, Türcodes). Raum-Sperre ist Client-Gate, abgesichert durch Server-Türvalidierung.check(puzzleId, reveal, submission)– Typcode: neuer trivialer Checker viaanyEquals(payload.solutions, answer, caseSensitive);choice/order/match: Checker 1:1 ausDevLabServicekopieren. Beicorrect || revealkommt"fragment"in die Antwort.openDoor(roomId, code),hint(session, puzzleId, level),finish(...)wie oben; plus Admin-Methoden nach Dev-Lab-Vorbild.
API (/api/codeescape): GET /scenarios, GET /session/{slug}, POST /check ({puzzleId, reveal, submission:{answer}} → {correct, fragment, successText}), POST /door ({roomId, code} → {open, successText}), POST /hint ({puzzleId, level} → {text, level}), POST /finish ({slug, durationSeconds, escaped, items:[{puzzleId, submission, attempts, revealed}], doors:[{roomId, code}]} → {score, baseScore, maxScore, timeBonus, solvedPuzzles, roomsOpened, escaped, stars, highscore, newHighscore, plays}).
Frontend: templates/escape-room.html, static/js/escaperoom.js (IIFE-State-Machine LOBBY → INTRO → ROOM(i) → PUZZLE_MODAL → DOOR_TRY → TRANSITION → … → RESULT), static/css/escaperoom.css mit Präfix esc-. Die choice/order/match-Renderer aus devlabgame.js (Vertrag {render, collect, lock, mark}) portieren, nur ein code-Renderer (Terminal-Artefakt + Textfeld) kommt neu dazu. Layout: HUD oben (Raumtitel, Raum-Punkte ● ● ○, Timer-Chip), Mitte Rätselkarten, unten Fragment-Chips + Keypad-Button. Nur Theme-Tokens, Erfolg blau ✓, Fehler rot ✗.
Content & Seeder: EscapeRoomSeeder liest resources/escaperoom/content.json. Startumfang: 3 Szenarien (leicht/mittel/schwer) × 3 Räume × 2–3 Rätsel ≈ 22–25 Rätsel. Themen: „Der verlassene Serverraum" (Netzwerk/Zahlensysteme), „Lockdown im Rechenzentrum" (Hardware/RAID/USV), „Das gehackte Büro" (Security/Passwörter/Verschlüsselung). Beispiel-Rätsel (ausgearbeitet im Design): PIN = Dezimalwert von 0b101001 → „41", SSH-Port-Choice, Dienst↔Port-Matching (Türcode „427"), TCP-Handshake-Reihenfolge, nutzbare Hosts im /26-Netz → „62" (Tresorcode „3E62").
Admin: AdminEscapeRoomController (/admin/escape-room), 3 Drilldown-Templates (Szenario-Liste → Szenario mit Räumen → Raum mit Rätseln; Rätsel-Payload als JSON-Textarea wie Dev Lab).
Aufwand: MVP ≈ 5,5 PT (Migration/Entities 0,5 · Service 1,5 · API 0,5 · Frontend 2 · Content 0,5 · Admin+Integration 0,5). Ausbau 1 (+1,5 PT): Bestzeiten in der Lobby, „Perfekter Lauf"-Badge. Ausbau 2 (+1–2 PT): Rätseltypen regex/cloze (Checker existieren wortgleich in DevLabService), 2 weitere Szenarien.
Spiel 2: BugHunter (Key: bughunter, Migration V12)
Konzept: Der Spieler ist Debugging-Detektiv und arbeitet Fallakten ab. Ein Fall („Hunt") ist ein Mini-Projekt aus 2–4 Dateien, das sich falsch verhält; Beweismittel sind simulierte Konsolen-Ausgabe / Stacktrace / Ticket-Text. Pro Bug eine Drei-Phasen-Schleife: ORTEN (fehlerhafte Zeile im Tab-Code-Viewer anklicken) → KLASSIFIZIEREN (Syntax / Logik / Laufzeit / Sicherheit) → FIXEN (korrigierte Zeile aus 4 Kandidaten wählen). Saubere Bugs bauen eine Combo-Serie auf. Stufen: Junior / Senior / Principal.
Abgrenzung zu Dev Lab: Dev Labs spot-Typ ist „finde die eine falsche Zeile". Bug Hunter ist bewusst reicher: Multi-Datei-Navigation, beweisgetriebenes Denken (Stacktrace lesen → Zeile zuordnen), explizite Fehler-Taxonomie (Prüfungsthema!) und Fix-Auswahl – das vollständige Debugging-Denkmodell. Junior-Fälle decken AP1-Stoff ab; Senior/Principal-Fälle gehen auf FIAE-AP2-Niveau (Nebenläufigkeit/Threads, Transaktions-/SQL-Fehler, Design-Pattern-Missbrauch, OWASP-Sicherheitslücken).
Punkteformel (serverseitig in /finish):
- Phasengewichte pro Bug: Orten 40 %, Klassifizieren 20 %, Fixen 40 %. Phasen-Award =
gewicht × punkte × versuchsfaktor(1.0/0.6/0.3, aufgedeckt 0). - Sterne aus
baseScore/maxScore(Genauigkeitsmaß, ohne Combo). - Combo: Ein Bug ist sauber, wenn alle 3 Phasen im 1. Versuch ohne Aufdecken gelingen. Serienposition n bringt
(min(n,4) − 1) × 10 % × punkteBonus (2. saubere in Folge +10 %, … 4.+ +30 %); Bonus wird nach der Sternberechnung addiert. Kein Timer – Bug Hunter belohnt Präzision, Zeitdruck gehört dem Escape Room. - Basispunkte pro Bug nach Stufe: Junior 100, Senior 150, Principal 200.
recordScore(user, "bughunter", huntSlug, baseScore + comboBonus, gelösteBugs, sekunden).
Datenmodell (V12__bughunter.sql):
bughunter_hunts(id, slug UNIQUE, title, code_language, difficulty DEFAULT 'junior', description, briefing VARCHAR(2000), console_output VARCHAR(4000), sort_order, active)bughunter_files(id, hunt_id FK CASCADE, filename, content VARCHAR(8000), sort_order) – eigene Tabelle, damit der Code-Viewer-Inhalt von allen Bugs geteilt wird und im Admin als Textarea editierbar istbughunter_bugs(id, hunt_id FK CASCADE, points DEFAULT 100, sort_order, payload VARCHAR(8000)) – Payload:{"file","line","category","evidence","fixOptions":[4],"fixIndex","explanation"}- Indizes auf beide FKs.
Backend: Entities/Repos nach Dev-Lab-Muster. BugHunterService mit CATEGORIES = Set.of("syntax","logik","laufzeit","sicherheit"), Gewichten 0.4/0.2/0.4:
buildSession(slug): Briefing +consoleOutput+ Dateien als[{filename, lines:[…]}](serverseitig in Zeilen gesplittet, damit Zeilenindizes kanonisch sind) + Bugs sanitisiert: nur{bugId, points, evidence}– keine Zeile, keine Kategorie, keine Fix-Optionen.checkPhase(bugId, phase, reveal, submission):locatevergleicht file+line, liefert bei Erfolg/Aufdeckenlocationzurück;classifyvergleicht die Kategorie und liefert bei Auflösung erst die gemischtenfixOptions(Phasen-Gate: der Fix-Text würde sonst die Position verraten);fixprüft die Wahl (Logik = Dev LabscheckChoice) und liefertfixedLine+explanationfürs Debriefing.finish(...): prüft alle Phasen aus den Roh-Antworten neu, Gewichte + Versuchsfaktoren, Streak/Combo, Sterne,recordScore.
API (/api/bughunter): GET /hunts, GET /session/{slug}, POST /check ({bugId, phase, reveal, submission}), POST /finish ({slug, durationSeconds, items:[{bugId, locate:{file,line,attempts,revealed}, classify:{category,…}, fix:{choice,…}}]} → {score, baseScore, maxScore, comboBonus, maxCombo, bugsSolved, totalBugs, stars, highscore, newHighscore, plays}).
Frontend: templates/bug-hunter.html, static/js/bughunter.js (State-Machine LOBBY → BRIEFING → HUNT {pro Bug: LOCATE → CLASSIFY → FIX → DEBRIEF} → RESULT), static/css/bughunter.css Präfix bh-. Layout: links 60 % Datei-Tabs + Monospace-Code-Viewer mit Zeilennummern-Gutter (Zeilenklick nur in LOCATE aktiv); rechts 40 % Beweis-Karte (Terminal-Style consoleOutput, Briefing, Bug-Hinweis), Aktions-Panel (wechselt pro Phase; Kategorie-/Fix-Buttons nutzen das choice-Renderer-Muster aus devlabgame.js), Combo-Chip + Fortschritt („Bug 2/3"). Debriefing tauscht die Bug-Zeile sichtbar gegen fixedLine (blaues Diff-Highlight).
Content & Seeder: BugHunterSeeder liest resources/bughunter/content.json; der komplette Bug-Knoten wird wie bei DevLabSeeder per toString() zum Payload. Startumfang: 6 Fälle – 3 Junior (1 Datei, 1–2 Bugs: Off-by-one, = vs. ==, fehlendes break), 2 Senior (2 Dateien: NPE via Stacktrace, SQL-Injection, Integer-Division), 1 Principal (3 Dateien, Symptom in Datei A, Ursache in Datei B) ≈ 13–15 Bugs. Drei Beispiel-Fälle sind im Design voll ausgearbeitet („Fall #1: Die falsche Rechnung" – Off-by-one in Java; „Fall #2: Das offene Login" – SQL-Injection in PHP; „Fall #3: Der Absturz um Mitternacht" – NPE mit Stacktrace-Lesen), inkl. Code, Fix-Optionen und Erklärungen.
Admin: AdminBugHunterController (/admin/bug-hunter), 2 Templates (Fall-Liste + Fall-Detail mit Datei-Textareas und Bug-Payload-JSON). Service validiert beim Speichern, dass file im Fall existiert und line im Zeilenbereich liegt.
Aufwand: MVP ≈ 4,5 PT. Ausbau 1 (+1 PT): Freitext-Fix statt Optionen (anyEquals mit Whitespace-Normalisierung wie Dev-Lab-cloze), „Sauber gelöst"-Badge. Ausbau 2 (+1 PT): „Blitzjagd"-Tagesmodus (3 zufällige Junior-Bugs, 90-s-Soft-Timer).
Spiel 3: ServiceClash (Key: serviceclash, Migration V14)
Konzept: „Deine Schicht am Service Desk." Der Spieler ist Junior-Admin im First-Level-Support. Tickets treffen auf einer Zeitleiste ein; jedes muss eingeordnet (Incident vs. Service Request), priorisiert (Hoch/Mittel/Niedrig anhand von Impact-×-Dringlichkeit-Hinweisen im Tickettext) und gelöst werden (richtige Erstmaßnahme aus 3–4 Optionen) – bevor sein SLA-Timer abläuft. Unbehandelte Tickets beschädigen die „Systemgesundheit"; bei 0 endet die Schicht vorzeitig.
Lernziele (AP1 → AP2): Incident-/Service-Request-Klassifikation (ITIL-light, explizites IHK-Thema), Prioritätsbewertung, korrekte Erstmaßnahmen (Troubleshooting-Reihenfolge, Eskalationsdisziplin, Phishing erkennen statt „lösen"). Höhere Schichten bringen FISI-AP2-Stoff: Change Management, Monitoring-/Eskalationsketten, Security-Incident-Behandlung, Backup-/Restore-Entscheidungen.
Spielablauf: Eine Schicht = ein Level mit 3 Wellen (Vormittag/Mittag/Nachmittag), je 60–90 Echtzeit-Sekunden mit steigender Spawn-Rate; Gesamtspielzeit 3–5 Minuten, zwischen Wellen 5 s Verschnaufpause. Pro Ticket im Detail-Panel:
- Einordnung [Incident]/[Service Request] – Single-Shot, falsch = 0 Punkte für diesen Anteil, richtige Antwort wird sofort gezeigt (Lerneffekt). 20 % der Ticket-Basispunkte.
- Priorität [Hoch]/[Mittel]/[Niedrig] – Single-Shot. 30 %.
- Lösung – 3–4 Aktionen (servergemischt), Plattform-Versuchsleiter 1.0/0.6/0.3; nach dem 3. Fehlgriff Auflösung + Erklärung, Ticket zählt als „eskaliert" (0 für diesen Anteil). 50 %.
SLA & Gesundheit: slaSeconds je Priorität. In SLA gelöst → ×1.0, verspätet → ×0.5, nie gelöst → 0 + Gesundheitsschaden. Start 100; SLA-Bruch −10 (Hoch: −20), ungelöst am Schichtende −15. Endbonus: gesundheit × 2 (max. 200).
Punkteformel (nur serverseitig):
ticketPunkte = B × (0.2·einordnungOk + 0.3·prioOk + 0.5·lösungsLeiter) × slaFaktor
score = Σ ticketPunkte + gesundheit×2 (B: Routine 60, Standard 100, Major Incident 150)
maxScore = Σ B + 200 ; Sterne nach Plattform-Konvention
Der Server-autoritäts-Trick für Echtzeit (Kernidee): Runden bleiben zustandslos –
GET /session/{slug}: Server zieht einen Seed, generiert daraus deterministisch den Schichtplan (Ticket-Auswahl aus dem Pool, Spawn-Zeitpunkte, Options-Mischung; Payloads sanitisiert ohne Lösungsfelder) und liefert Plan + Seed.- Jeder Klick geht an
POST /check(sofortiges ✓/✗-Feedback; Lösungen verlassen den Server nie). POST /finishsendet{slug, seed, decisions[], durationSeconds}; der Server regeneriert die Schicht aus dem Seed und re-simuliert das Entscheidungslog (Plausibilitätschecks: Zeitstempel ≥ Spawn-Offset, ≤ Schichtdauer, monoton pro Ticket) und berechnet Gesundheit, SLA-Faktoren und Score neu. Härtungs-Ausbau: HMAC-signierter Seed.
Datenmodell (V14__serviceclash.sql):
service_clash_shifts(id, slug UNIQUE, title, description, difficulty INT, wave_count INT DEFAULT 3, wave_seconds INT DEFAULT 60, spawn_count INT DEFAULT 12, settings VARCHAR(4000) /* JSON: SLA-Defaults, Health-Regeln, Prio-Stufe an/aus */, sort_order, active)service_clash_tickets(id, shift_id FK CASCADE, wave INT, base_points INT DEFAULT 100, sort_order, payload VARCHAR(8000)) + FK-Index- Ticket-Payload:
{title, requester, text, kind (normal|phishing), options[], correctIndex✂, category✂, priority✂, slaSeconds, explanation✂, successText}(✂ = wird vonsanitize()entfernt).
Backend: Entities ServiceClashShift/ServiceClashTicket, Standard-Repos (findAllByShiftIdOrderByWaveAscSortOrderAscIdAsc, countByShiftId). ServiceClashService: buildCatalog(), buildSession(slug) (Seed + generatePlan()), check(templateId, step, value, reveal) mit step ∈ classify|priority|solve (Vergleich immer über Options-Text, nie Indizes – immun gegen Mischungs-Versatz), finish(...) (Plan aus Seed regenerieren, Log re-simulieren, recordScore(user, "service-clash", slug, score, gelöste, sekunden)), Admin-Methoden nach Dev-Lab-Vorbild.
API (/api/serviceclash): GET /catalog, GET /session/{slug} (liefert seed, Wellen-Konfiguration, Ticket-Instanzen {instanceId, templateId, wave, spawnAtMs, …}), POST /check, POST /finish → {score, maxScore, stars, resolved, total, slaBreaches, health, highscore, newHighscore, plays, tickets[Debrief-Aufschlüsselung]}.
Frontend: templates/service-clash.html, static/js/serviceclash.js (State-Machine SHIFT_SELECT → BRIEFING → RUNNING ⇄ WAVE_BREAK → DEBRIEF; ein setInterval(250 ms)-Tick: fällige Tickets spawnen, SLA-Ringe aktualisieren), static/css/service-clash.css Präfix sc-. Layout: Topbar (Schicht-Uhr, Wellen-Pips, Gesundheits-Meter, „Gelöst 4/8"), links die Ticket-Queue (Karten mit SLA-Ring, pulsierender roter Rand bei Überfälligkeit), Mitte das aktive Ticket mit 3-Stufen-Flow, unten ein Event-Log („14:02 Ticket ‚ERP down' eingegangen"). SLA-Ringe: var(--accent), unter 25 % var(--danger).
Schwierigkeitskurve: Schicht 1 „Erster Arbeitstag" (8 Tickets, großzügige SLAs, Prio-Stufe deaktiviert), Schicht 2 „Normalbetrieb" (12 Tickets, Prio aktiv, erster Phishing-Köder), Schicht 3 „Patch Day Chaos" (16 Tickets, knappe SLAs, Major Incident mit Folgetickets in Welle 3). Replay-Wert: Ticket-Pool größer als spawnCount → jede Runde ein anderer Mix (Seed).
Content & Seeder: ServiceClashSeeder liest resources/service-clash/content.json. Startumfang: 3 Schichten × 18–20 Ticket-Vorlagen ≈ 55 Tickets. Vier Beispiel-Tickets sind voll ausgearbeitet (Drucker-Queue/Incident-mittel, Azubi-Onboarding/Service-Request-niedrig, ERP-Totalausfall/Major-Incident-hoch mit Sofort-Eskalation, Phishing-Mail mit verdächtiger Absenderdomain).
Admin: AdminServiceClashController (/admin/service-clash), Schicht-Liste + Detailseite (Schicht-Formular inkl. Settings-JSON, Ticket-Tabelle, Payload-JSON-Textarea mit Schema-Hinweis; Speichern validiert correctIndex gegen options).
Aufwand: MVP ≈ 7,5 PT (Service inkl. Seed-Plan + Re-Simulation 2 · Frontend 3 · Content 1 · Admin 1 · Rest 0,5+0,5). Ausbau (+3 PT): Major-Incident-Folgeketten, Combo-Serien, HMAC-Seed, Endlosmodus „Überstunden".
Spiel 4: NetWorker (Key: networker, Migration V13)
Konzept: Netzwerk-Puzzle in der Tradition von Schaltkreis-Spielen. Jedes Level zeigt eine SVG-Topologie (PCs, Switch, Router, Server, Cloud). Geräte haben fehlende oder kaputte Konfiguration (IP, Maske, Gateway, Kabel, statische Routen). Ziel: alle Ping-Ziele erfüllen („PC1 muss SRV1 erreichen", später auch negativ: „Gast-PC darf den Server NICHT erreichen"). „Pings testen" ruft einen serverseitigen Netzwerk-Simulator, der jeden Ping Hop-für-Hop auflöst und Pass/Fail plus didaktische Diagnose liefert („Das Paket verlässt PC1 nicht: Laut Maske liegt das Ziel im eigenen Netz – stimmt die Maske?").
Lernziele (AP1-Kernstoff → AP2, passt zu vorhandenem FinalFlux-Content IP-Berechnung/VLAN): IPv4-Adressierung, Subnetzmasken/CIDR, Netz-/Broadcast-Adressen, Gateway-Logik, doppelte IPs, statische Routen; auf AP2-Niveau (FISI): VLAN-Segmentierung mit Negativ-Zielen, Routing über mehrere Hops, Firewall-Regeln, im Ausbau IPv6.
Spielablauf: Session = eine Welt aus 4–6 Leveln in Folge (Dev-Lab-Session-Form). Fünf Level-Typen bei einheitlichem Datenmodell: assign (leere IP/Maske/Gateway-Felder ausfüllen), cable (fehlende Verbindungen ziehen), fix (1–3 eingebaute Fehler finden: falsche Maske, Gateway außerhalb des Subnetzes, doppelte IP, Host auf Netzadresse), route (statische Routen-Zeilen: Zielnetz-CIDR + Next Hop), vlan (Ausbau, mit Negativ-Zielen).
Versuche & Punkte: Jeder „Pings testen"-Klick mit mindestens einem roten Ziel verbraucht einen Versuch (1.0/0.6/0.3, MAX_ATTEMPTS=3, danach „Lösung anzeigen" = 0 Punkte). Bewertet wird ausschließlich per Simulation, nie per Exact-Match – viele Konfigurationen sind gültig (jede freie Host-IP funktioniert); die Musterlösung dient nur dem Aufdecken. Levelpunkte 100–200; Sterne am Weltende nach Konvention; /finish re-simuliert die final eingereichten Konfigurationen serverseitig. recordScore(user, "netzwerker", weltSlug, score, gelöste, sekunden).
Datenmodell (V13__networker.sql):
netzwerker_worlds(id, slug UNIQUE, title, description, difficulty INT, sort_order, active)netzwerker_levels(id, world_id FK CASCADE, title, level_type VARCHAR(30) /* assign|cable|fix|route|vlan /, points INT DEFAULT 100, sort_order, payload VARCHAR(8000) / JSON-Topologie inkl. Musterlösung */) + FK-Index
Der Simulator – service/NetSimulator.java (Herzstück, pures Java ohne Spring, voll unit-testbar):
- Records:
Net(nodes, links, routes, vlans),NodeCfg,IfaceCfg(ipLong, prefix, gatewayLong),Route(destNet, prefix, nextHop). ping(net, from, to): (1) Validierung: unparsebare IP/Maske, Host-Teil 0/alles-1 →network_address/broadcast_address, doppelte IP im L2-Segment →duplicate_ip. (2) Gleiches Subnetz aus Quellsicht ((ip & maske) == (ziel & maske)) → L2-BFS über Links durch Switches (nie durch Router; VLAN-gefiltert). (3) Anderes Subnetz → Gateway nötig: gesetzt? im Quell-Subnetz? L2-erreichbar? Dann leitet der Router weiter: Ziel in direkt verbundenem Subnetz → zustellen; sonst statische Routen (Longest-Prefix-Match) → Next Hop → Rekursion mit TTL 8 (routing_loop-Schutz). Kein Treffer →no_route.- Fehlergründe werden im Service auf didaktische deutsche Meldungen gemappt.
NetzwerkerService: buildCatalog(), buildSession(slug) (Level per sanitizeLevel(): Werte editierbarer Felder, zu ziehende Links, Routen-Lösungszeilen und solution-Block entfernt; gesperrte Werte, Positionen, Ziele, Hints gehen mit), check(levelId, reveal, config) (mergeSubmission() legt eingereichte Werte nur über editierbare Felder – Manipulation gesperrter Felder wird ignoriert; alle Ziele durch NetSimulator.ping; Antwort mit Trace/Diagnose pro Ziel), finish(...), Admin-CRUD plus Qualitäts-Gate beim Speichern: die hinterlegte Musterlösung wird durch den Simulator geschickt – Speichern wird abgelehnt, wenn sie nicht alle Ziele erfüllt.
API (/api/networker): GET /catalog, GET /session/{slug}, POST /check ({levelId, reveal, config:{ifaces[], links[], routes[], vlans[]}} → {allPassed, validation[], goals:[{id, passed, trace[] | failAt, reason, message}]}; Masken-Eingabe akzeptiert 24, /24 oder 255.255.255.0 – Server normalisiert), POST /finish → Dev-Lab-Result-Form.
Frontend: templates/netzwerker.html, static/js/netzwerker.js (State-Machine WORLD_SELECT → LEVEL_INTRO → EDITING ⇄ CHECKING → LEVEL_DONE → SUMMARY), static/css/netzwerker.css Präfix nw-. SVG-Ansatz: ein <svg viewBox="0 0 1000 620">, Geräte-Glyphen als <symbol>-Defs im Strichstil der Dashboard-Icons, Positionen aus dem Payload; Links als <path>, Ports als Kreise an der Gerätekante. Die Paket-Animation ist die Belohnung: bei Erfolg wandert ein leuchtender Punkt entlang des tatsächlich vom Server gelieferten trace-Pfads (requestAnimationFrame); bei Fehlschlag stirbt er exakt am failAt-Hop mit rotem Burst. Kabelmodus: Port A klicken → Gummiband-Linie → Port B; gezogenes Kabel per Klick löschen. Layout: Canvas links/Mitte, rechts Ziel-Liste (mit Diagnosetexten unter roten Zielen) + Konfigurations-Panel des gewählten Geräts, Footer mit „Pings testen" / „Lösung anzeigen" / Hinweis-Toggle.
Schwierigkeitskurve (Welten): 1. „Ein Netz für alle" (Same-Subnet-Basics), 2. „Über den Router" (Gateways, zwei Subnetze, erstes Negativ-Ziel), 3. „Routen & Fehlersuche" (zwei Router, statische Routen, Duplikat-IP-Jagd), 4. (Ausbau) „VLAN-Werkstatt".
Content & Seeder: NetzwerkerSeeder liest resources/netzwerker/content.json. Startumfang: 3 Welten × 5 Level = 15 Level. Drei Beispiel-Level voll ausgearbeitet: „Erste Verbindung" (assign: PC1 passend zu 192.168.10.20/24 konfigurieren), „Die falsche Maske" (fix: /25-vs-/24-Konflikt, .200 liegt außerhalb des ersten /25-Teilnetzes), „Tor zum Serverraum" (assign: IP+Maske+Gateway über Router zwischen 192.168.1.0/24 und 192.168.2.0/24).
Admin: AdminNetzwerkerController (/admin/netzwerker), Welt-Liste + Detailseite mit Level-Payload-JSON-Textarea (eingeklapptes Schema-Cheatsheet) und dem Musterlösungs-Validierungs-Gate.
Aufwand: MVP ≈ 9,5 PT (NetSimulator + Unit-Tests zuerst: 2,5 · Migration/Entities/Service/API 1,5 · SVG-Frontend 3,5 · Content 1 · Admin 1). Ausbau (+4,5 PT): VLAN-Welt, DHCP-Modus, Sandbox, visueller Level-Editor.
Empfohlene Baureihenfolge & Gesamtaufwand
- Vorarbeiten ✅: Namensschema, Dashboard-Namen und Keys,
devlabinProgressService, K2, W8, V11–V14-Migrationsreservierung,exam_scope-Konvention, S3-Sicherheitsvertrag und Spiel-Akzent-Tokens intheme.csssind umgesetzt. - FluxTimer ✅ – risikoarmer, rein lokaler Ausbau; validiert den globalen UI-Bootstrap und belegt die Glocke vor FluxNet. ≈ 1,5–2 PT
- CodeEscape (V11) ✅ – serverautoritatives MVP mit 3 Szenarien, 9 Räumen, 24 Rätseln,
Admin-CRUD,
exam_scope, Session-Tracking und Anti-Cheat-Negativtests umgesetzt. - BugHunter (V12) – übernimmt Terminal-Styling und Choice-Renderer aus Schritt 2. ≈ 4,5 PT
- NetWorker (V13) – Simulator zuerst, testgetrieben (pures Java, kein UI-Risiko), dann SVG-Frontend. ≈ 9,5 PT
- ServiceClash (V14) – komplexestes Frontend (Echtzeit-Tick); profitiert von allen Vorarbeiten. ≈ 7,5 PT
Gesamt-MVP aller vier Spiele: ≈ 28 Personentage. Der Navigations-Umbau (Abschnitt 2.3) und die restlichen Teil-2-Punkte können parallel oder dazwischen erfolgen; empfohlen ist der Sidebar-/Tab-Umbau vor dem vierten Spiel, damit alle neuen Seiten die Fragmente nutzen.
Teil 2: Verbesserungen & Optimierungen am Bestand
✅ Umgesetzt am 2026-07-12 (K1–K3, W1–W8, N1–N6, S1–S8, S10 – Details im CHANGELOG). Offen bleiben: S9/i18n (bewusst zurückgestellt), S3 (Regel für die neuen Spiele) sowie die schrittweise Fragment-Migration der restlichen Spiel-/Admin-Templates. K1 vollständig abgeschlossen: Gitea-Secret angelegt, Produktionspasswort rotiert und Runner-Abgleich erfolgreich deployt (Workflow 43, Commit
4f5042d). Das Verfahren und der manuelle Notfallweg stehen inpw_anleitung.md.
2.1 Kritisch (vor/mit dem nächsten Release beheben)
| # | Problem | Ort | Maßnahme |
|---|---|---|---|
| K1 ✅ | DB-Passwort im Repo (SPRING_DATASOURCE_PASSWORD: mindflux_pass) |
docker-compose.yml:11 |
In Gitea-Secret auslagern (wie die Admin-Passwörter bereits); Passwort rotieren. |
| K2 ✅ | Unsicherer Legacy-Endpunkt /users/save: speichert User ohne BCrypt-Hashing, ohne Validierung, mit frei wählbarer Rolle (Mass Assignment) – für jeden eingeloggten Nutzer erreichbar |
UserViewController.java:25–29 |
UserViewController + user-form.html + user-success.html ersatzlos löschen (toter Legacy-Code). |
| K3 ✅ | Keine Tests – src/test existiert nicht, obwohl spring-boot-starter-test deklariert ist |
pom.xml:88–93 |
Mindestens: Unit-Tests für Score-Berechnungen (GameScoreService, SyntaxSagaService, DevLabService.check-Dispatcher) und einen @WebMvcTest für die Security-Regeln (/admin/** nur ADMIN). Die Netzwerk-Simulation des neuen Netzwerker-Spiels unbedingt testgetrieben bauen. |
2.2 Wichtig (zeitnah)
| # | Problem | Ort | Maßnahme |
|---|---|---|---|
| W1 ✅ | Dev Lab fehlt auf der Statistik-Seite – Scores werden geschrieben, aber nie angezeigt | ProgressService.GAMES (Zeile 18–27) |
GAMES.put("devlab", "Dev Lab"); ergänzen (Reihenfolge wie Dashboard). |
| W2 ✅ | XP/Level/Streak sind Dummy-Daten (getOverallProgress hardcoded 432/2400/Level 5) |
ProgressService.java:35–44 |
Echte Aggregation aus game_scores: XP = Summe aller Scores (oder gedeckelt pro Spiel), Level per Schwellen-Tabelle, richtigen Antworten aus moves ableiten. Gleiches Fundament später für Achievements nutzen. |
| W3 ✅ | Achievements sind Dummy-Daten, nicht mit game_scores verdrahtet |
AchievementServiceImpl |
Nach recordScore() einen Achievement-Check auslösen (z. B. „Erster Sieg", „3 Sterne in Syntax Saga", „5 Spiele an einem Tag"). Ereignisgesteuert im GameScoreService (ein Hook-Punkt statt in jedem Spiel). |
| W4 ✅ | Kein globales Error-Handling – Admin-Controller werfen IllegalArgumentException → Whitelabel-500; APIs liefern leere DTOs statt 404 |
alle Controller | Ein @ControllerAdvice (HTML: eigene error.html) + @RestControllerAdvice (JSON: {error, message} mit korrekten Statuscodes). ResponseStatusException in Services nutzen. |
| W5 ✅ | Keine Bean Validation – kein spring-boot-starter-validation, keine @Valid/@NotBlank/@Size; keine Passwort-Mindestlänge, kein E-Mail-Formatcheck |
pom.xml, RegisterController.java:34–42, User.java |
Starter ergänzen; DTO-Validierung für Registrierung (Passwort ≥ 8 Zeichen, E-Mail-Format) und die neuen Spiel-APIs. |
| W6 ✅ | Registrierungs-Race + tote Templates: App prüft Username case-insensitiv, DB-UNIQUE ist case-sensitiv → Race mit unbehandeltem 500; register.html (ohne confirmPassword-Feld) und login.html sind tot/kaputt |
RegisterController.java:40, V1__initial_schema.sql |
Funktionalen Index UNIQUE (lower(username)) per Migration; DataIntegrityViolationException abfangen → Fehlermeldung; tote Templates register.html/login.html löschen. |
| W7 ✅ | System.out.println / printStackTrace statt Logging (~20 Stellen, u. a. ProfileController.java:144) |
Seeder, AdminSetup, ProfileController, AppStartCheck |
Auf SLF4J (LoggerFactory.getLogger) umstellen; e.printStackTrace() durch log.error(..., e) ersetzen. |
| W8 ✅ | N+1-Queries: TechtacularService.buildBoard lädt Fragen+Antworten lazy pro Kategorie; categoryRows macht ein count pro Kategorie |
TechtacularService.java:46–73, 133–137 |
@EntityGraph/JOIN FETCH für Board-Aufbau; Zeilen-Counts als eine GROUP BY-Query. Bei Seed-Datenmengen unkritisch, aber das Muster wird sonst in die 4 neuen Spiele kopiert – daher vor deren Bau beheben bzw. in den neuen Services von Anfang an richtig machen. |
2.3 Navigation & Profil-Bereich (vom Nutzer gemeldet – bestätigt)
Der Verdacht „Profilbutton fehlt / Einstellungen nicht im Profil / irgendwas ist schief" trifft zu. Befund:
| # | Befund | Ort |
|---|---|---|
| N1 ✅ | Kein „Profil"-Eintrag in der Dashboard-Sidebar-Navigation. Das Profil ist nur über den kleinen User-Block unten in der Sidebar erreichbar (<a class="mf-profile" href="/profile">), der nicht als Button erkennbar ist – und dessen Chevron-Pfeil (▼) ein Dropdown suggeriert, das nicht existiert. |
dashboard.html:72–82 |
| N2 ✅ | Zwei konkurrierende Navigationssysteme: Die Sidebar führt zu Knowledge Core / Statistiken / Achievements / Einstellungen; die Profilseite hat eine eigene Tab-Leiste (Profil / Fortschritt / Erfolge / Soziales / Einstellungen). Benennungen inkonsistent („Statistiken" vs. „Fortschritt", „Achievements" vs. „Erfolge"), Mengen inkonsistent (Knowledge Core fehlt in den Tabs, Soziales fehlt in der Sidebar). | dashboard.html:45–70 vs. profile.html:35–41 |
| N3 ✅ | Die Tab-Leiste existiert nur auf profile.html. Klickt man einen „Tab" (z. B. Einstellungen), landet man auf einer Seite ohne Tabs – die Tab-Metapher bricht sofort. settings.html hat nur „⟵ Zurück zum Profil", auch wenn man von der Dashboard-Sidebar kam (asymmetrische Navigation). |
Grep mf-tab-nav: nur profile.html (+ eine Admin-Seite) |
| N4 ✅ | /profile/settings gehört nicht zum Profil-Code: Die URL suggeriert Profil-Zugehörigkeit, wird aber von einem separaten SettingsController bedient; die Passwort-Änderung dagegen liegt im ProfileController (/profile/change-password) und redirectet nach settings. Zuständigkeiten sind verschränkt. |
SettingsController.java, ProfileController.java:154–177 |
| N5 ✅ | settings.html ist zur Hälfte Attrappe: Theme-Select (Hinweistext verweist auf den echten Umschalter), Sprach-Select und Benachrichtigungs-Checkboxen haben keine Funktion/kein Formular. Real sind nur Passwort ändern und Profil löschen. Zudem hartkodierte Farbe #c0392b statt var(--danger) (Token-Regel-Verstoß). |
settings.html:40–70, 37 |
| N6 ✅ | Sidebar-Widgets sind Dummy-Daten: LEVEL 12 / 3250/5000 XP / „STREAK 7 Tage" sind hart ins Template geschrieben – deckt sich mit W2 (XP/Level-Dummies im ProgressService). |
dashboard.html:84–91 |
Lösungsansatz (ein zusammenhängender Umbau, verzahnt mit S1/Layout-Fragmenten):
- Ein Navigationsmodell: Die Sidebar wird als Thymeleaf-Fragment extrahiert und um einen expliziten Eintrag „Profil" →
/profileergänzt; der User-Block bleibt als Shortcut, verliert aber den irreführenden Chevron (oder bekommt ein echtes Dropdown mit Profil/Einstellungen/Logout). - Profil-Bereich konsolidieren: Die Tab-Leiste wird ebenfalls Fragment und erscheint auf allen Profil-Unterseiten (Profil, Statistiken, Erfolge, Soziales, Einstellungen) mit einheitlichen Namen (Empfehlung: „Statistiken" und „Erfolge" überall). Knowledge Core entweder in die Tabs aufnehmen oder bewusst nur in der Sidebar führen – einmal entscheiden.
- Controller aufräumen:
SettingsControllerin denProfileControllermergen (oder Settings vollständig auslagern inkl. Passwort-Endpunkt) – eine Zuständigkeit pro Bereich. - Attrappen entfernen oder verdrahten: Theme-Select an
theme.jsanbinden (Auswahl persistiert inlocalStorage, ersetzt den Hinweistext); Sprach-Select und Benachrichtigungen ausbauen, bis es die Funktionen gibt.#c0392b→var(--danger). - N6 wird durch W2 (echte XP/Level aus
game_scores) gelöst; bis dahin Widgets ausblenden statt Fantasiewerte zeigen.
2.4 Sinnvoll (mittelfristig)
| # | Thema | Maßnahme |
|---|---|---|
| S1 ✅ teilweise | Template-Duplikation – 33 Templates ohne ein einziges th:fragment; jede Seite dupliziert <head>, Sidebar, Footer |
templates/fragments/layout.html mit head, sidebar, footer-Fragmenten einführen; schrittweise umstellen (zuerst bei den 4 neuen Spielen verwenden, dann Bestand migrieren). |
| S2 ✅ | 4 parallele User-Anlage-Pfade mit inkonsistenten Regeln (RegisterController, REST-UserController, AdminUserController, UserViewController) |
Nach Löschung von K2: Restlogik in einen UserService konsolidieren (ein Ort für Encoding, Rollen, Uniqueness). |
| S3 ✅ Fundament | Score-Manipulation durch eigene Nutzer möglich (Roh-Werte fälschbar, Reveal-vor-Finish) | Verbindlicher Sicherheitsvertrag ist oben dokumentiert und gilt für jedes neue Spiel; konkrete Implementierung und Negativtests entstehen jeweils mit dem Spiel. |
| S4 ✅ | Infrastruktur: kein Actuator/Healthcheck, Dockerfile läuft als root, kein Dependency-Layer-Caching, CI ohne Tests, Java-Versionskonflikt (pom: 17, Dockerfile: 21) | spring-boot-starter-actuator (+ HEALTHCHECK in compose), USER-Directive, Dockerfile zweistufig mit dependency:go-offline-Layer, Test-Stage in .gitea/workflows/mindflux.yml, Java-Version angleichen (empfohlen: beide auf 21). |
| S5 ✅ | Datenmodell: game_scores/achievements referenzieren username ohne FK; email ohne UNIQUE |
FK auf users(username) + UNIQUE(email) per Migration ergänzen (vorher Dubletten bereinigen). |
| S6 ✅ | Logout per GET (SecurityConfig.java:36) – kleiner Logout-CSRF-Vektor |
Auf POST-Logout mit CSRF-Token umstellen (kleines Formular im Sidebar-Footer). |
| S7 ✅ | Kein Brute-Force-Schutz auf /login |
Einfacher In-Memory-Rate-Limiter (z. B. Bucket pro IP/Username) oder Verzögerung nach Fehlversuchen. |
| S8 ✅ | Toter Code: AppStartCheck, ProfileController.getFileExtension, Legacy-CSS (auth.css, …) |
Aufräumen (niedrige Priorität, aber billig). |
| S9 ☐ | i18n: alles hart deutsch | Nur angehen, falls Mehrsprachigkeit je geplant ist – sonst bewusst so lassen (Zielgruppe ist deutsch). |
| S10 ✅ | FinalFlux auf die gesamte Ausbildung erweitern (AP2) – aktuell rein AP1-fokussiert | Eigener AP2-Ast mit Sidebar-Blöcken „AP2 Anwendungsentwicklung" und „AP2 Systemintegration"; gleiche Struktur wie bisher (statische Themenseiten unter static/finalflux/ + FluxCheck-Frage-JSONs). Startthemen FIAE: Entwurfsmuster, Testverfahren, SQL-Vertiefung, Softwarequalität; FISI: Netzwerksicherheit, Monitoring, Hochverfügbarkeit, Automatisierung. |
Teil 3: FluxNet – Community & Soziales
Ersetzt den
/soziales-Platzhalter („Straßen? Wo wir hingehen, brauchen wir keine Straßen!"). Ziel: User können sich befreunden, intern miteinander schreiben, gemeinsam lernen und Achievements vergleichen – vollständig selbst gehostet, ohne einen einzigen externen Dienst, und DSGVO-konform von Grund auf.
3.1 Funktionsumfang
| Funktion | Beschreibung |
|---|---|
| Freundschaften | Anfrage per Username-Suche senden → Gegenseite bestätigt/lehnt ab. Nur bestätigte Freunde sehen einander im FluxNet. |
| Interner Messenger | 1:1-Direktnachrichten zwischen bestätigten Freunden. Ungelesen-Badge am Glocken-Icon der Topbar. Kein Gruppenchat in Phase 1. |
| Blockieren | Blockiert = keine Anfragen, keine Nachrichten, keine Profileinsicht – in beide Richtungen serverseitig geprüft. Bestehende Freundschaft wird getrennt. Der Blockierte erhält keine Benachrichtigung (still). Entblocken jederzeit über die Einstellungen. |
| Achievements vergleichen | Freundesprofil zeigt den Achievement-Katalog nebeneinander (meine/seine freigeschalteten) und die Spiel-Highscores im Vergleich – nur, was die Privacy-Einstellungen des anderen erlauben. |
| Gemeinsam lernen | Phase 2: Lern-Duell – einen Freund zu Spiel + Deck herausfordern, beide spielen binnen Frist, der Server vergleicht die echten game_scores-Ergebnisse. Phase 3: gemeinsame Lernräume (identisches Board für beide über die Seed-Mechanik aus dem ServiceClash-Design). |
3.2 Datenmodell (nächste freie Flyway-Migrationen)
Alle Tabellen mit FK auf users(username) und ON DELETE CASCADE ON UPDATE CASCADE (Muster aus V10) – damit greift das Löschkonzept automatisch.
CREATE TABLE privacy_settings (
username VARCHAR(100) PRIMARY KEY REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
community_enabled BOOLEAN NOT NULL DEFAULT FALSE, -- DSGVO: Opt-in!
profile_visibility VARCHAR(20) NOT NULL DEFAULT 'FRIENDS', -- FRIENDS | ALL
show_achievements BOOLEAN NOT NULL DEFAULT TRUE,
show_stats BOOLEAN NOT NULL DEFAULT TRUE,
allow_requests BOOLEAN NOT NULL DEFAULT TRUE
);
CREATE TABLE friendships (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
requester VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
addressee VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING', -- PENDING | ACCEPTED
created_at TIMESTAMP NOT NULL,
responded_at TIMESTAMP,
CONSTRAINT uq_friend_pair UNIQUE (requester, addressee),
CONSTRAINT chk_no_self CHECK (requester <> addressee)
);
CREATE TABLE user_blocks (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
blocker VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
blocked VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
created_at TIMESTAMP NOT NULL,
CONSTRAINT uq_block_pair UNIQUE (blocker, blocked)
);
CREATE TABLE messages (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
sender VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
recipient VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
body VARCHAR(2000) NOT NULL,
sent_at TIMESTAMP NOT NULL,
read_at TIMESTAMP,
reported BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE INDEX idx_messages_unread ON messages (recipient, read_at);
CREATE INDEX idx_messages_pair ON messages (sender, recipient, sent_at);
CREATE TABLE challenges (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
challenger VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
challenged VARCHAR(100) NOT NULL REFERENCES users (username) ON DELETE CASCADE ON UPDATE CASCADE,
game VARCHAR(50) NOT NULL,
deck VARCHAR(100),
status VARCHAR(20) NOT NULL DEFAULT 'OPEN', -- OPEN | DONE | EXPIRED | DECLINED
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP NOT NULL
);
Eine Freundschaft zwischen A und B liegt genau einmal vor (Anfrage-Richtung = requester); Abfragen prüfen beide Richtungen ((requester=?1 AND addressee=?2) OR (requester=?2 AND addressee=?1)).
3.3 Backend (bewährtes Schichtmuster)
- Services:
FriendshipService(anfragen/annehmen/ablehnen/entfernen),MessageService(senden, Verlauf paginiert laden, als gelesen markieren, eigene Nachricht löschen, melden),BlockService(blocken/entblocken,isBlockedEitherWay(a, b)),PrivacyService(Opt-in, Sichtbarkeiten). - Zentrale Guard-Methode, die JEDE soziale Aktion durchläuft: Community aktiviert (beide Seiten)? Block in irgendeiner Richtung? Freundschaft bestätigt (für Nachrichten/Profile)? → sonst 403/404, ohne dem Angreifer zu verraten, ob der Zielnutzer existiert oder blockt.
- Controller:
SozialesController(View/soziales, Freundesprofil/soziales/profil/{username}) +@RestController /api/social/**(Endpunkte:GET /friends,POST /friends/request|accept|decline|remove,GET /messages/{friend}?after=,POST /messages,POST /messages/{id}/report,DELETE /messages/{id},GET /unread-count,POST /block,DELETE /block/{username},GET/PUT /privacy). Route in die CSRF-Ignore-Liste derSecurityConfigaufnehmen (Muster der Spiel-APIs). - Moderation: gemeldete Nachrichten landen in einer Admin-Ansicht (
/admin/fluxnet) – der Admin sieht ausschließlich gemeldete Nachrichten (Verhältnismäßigkeit), kann löschen und Nutzer verwarnen/sperren.
3.4 Messenger-Transport: voll intern, keine externen Dienste
- Phase 1 – Polling: offener Chat pollt alle 5 s
GET /api/social/messages/{friend}?after=<letzteId>, das Ungelesen-Badge alle 30 sGET /api/social/unread-count. Passt exakt zur zustandslosen Vanilla-JS-Architektur, braucht null neue Infrastruktur und skaliert für die Zielgruppe locker. - Phase 2 – Server-Sent Events:
GET /api/social/streammit SpringSseEmitter(imMessageServicewird beisend()an offene Emitter des Empfängers publiziert). Weiterhin 100 % selbst gehostet. nginx:proxy_buffering off;+ langesproxy_read_timeoutfür den Stream-Pfad. - Bewusst NICHT: WebSocket/STOMP (Overkill für 1:1-Chat) und Web-Push/FCM/Service-Worker-Push – Letzteres liefe zwingend über die Push-Infrastruktur der Browserhersteller (Google/Mozilla/Apple) und wäre damit ein externer Dienst.
3.5 DSGVO-Konzept
| Prinzip | Umsetzung |
|---|---|
| Opt-in (Art. 6, 25) | community_enabled = FALSE als Default. Beim ersten Besuch von /soziales: Aktivierungsdialog mit klarem Hinweistext (welche Daten sichtbar werden, an wen). Ohne Aktivierung ist der Nutzer für andere unsichtbar und unauffindbar. Deaktivierung jederzeit in den Einstellungen (versteckt Profil, pausiert Eingang von Anfragen/Nachrichten). |
| Datenminimierung (Art. 5) | Nur Username-Referenzen und Nachrichtentexte – keine neuen Pflicht-Personendaten, keine Telefonnummern, keine Kontakte-Uploads. |
| Privacy by Default (Art. 25) | Sichtbarkeit ab Werk „nur Freunde"; Nachrichten nur zwischen bestätigten Freunden (verhindert nebenbei Spam). |
| Löschung (Art. 17) | Konto-Löschung kaskadiert per FK automatisch über Freundschaften, Blocks, Nachrichten und Challenges – nichts bleibt zurück. Einzelne eigene Nachrichten kann der Nutzer selbst löschen. |
| Auskunft/Portabilität (Art. 15/20) | Ausbau Phase 3: „Meine Daten exportieren"-Button (JSON-Dump der eigenen Profil-, Freundes-, Nachrichten- und Score-Daten). |
| Vertraulichkeit (Art. 32) | Transport via TLS (nginx). Speicherung serverseitig im Klartext in der eigenen Postgres – bewusste, dokumentierte Entscheidung (Moderationsfähigkeit); keine Weitergabe an Dritte, kein externer Dienst, keine Analytik. |
| Transparenz (Art. 13) | datenschutz.html um den Abschnitt FluxNet erweitern (Zwecke, Speicherdauer, Empfängerkreis „nur befreundete Nutzer", Moderationsregel); nutzungsbedingungen.html um Verhaltensregeln + Melde-/Sperrprozess. |
| Betroffenenschutz | Blockieren ist still (kein „X hat dich blockiert"); Melden einer Nachricht legt nur DIESE Nachricht dem Admin vor. |
3.6 Frontend
soziales.htmlwird zum FluxNet-Hub (der Profil-Tab „Soziales" existiert schon): links Freundesliste (mit Online-Reihenfolge nach letzter Aktivität) + offene Anfragen + Username-Suche; rechts Chat-Panel des gewählten Freundes (Verlauf, Eingabefeld, „Nachricht melden/löschen" im Kontextmenü).- Freundesprofil: Avatar, Level, Streak, Achievement-Vergleich (zwei Spalten, gemeinsame freigeschaltete hervorgehoben), Highscore-Tabelle je Spiel, Buttons „Herausfordern" / „Blockieren".
- Neue Dateien:
static/js/fluxnet.js(IIFE, Polling-Logik, Render-Funktionen),static/css/fluxnet.css(Präfixfn-, nur Theme-Tokens, Erfolg blau ✓ / Fehler rot ✗). Ungelesen-Badge am Glocken-Icon der Dashboard-Topbar (zusammen mit dem FluxTimer-Popover aus Teil 4).
3.7 Phasen & Aufwand
- Phase 1 (≈ 7 PT): Migrationen, Privacy-Opt-in, Freundschaften, Blockieren, Polling-Messenger, Ungelesen-Badge, Rechtstexte, Tests (Guard-Logik!).
- Phase 2 (≈ 4 PT): SSE-Stream, Lern-Duelle, Freundesprofil mit Achievement-/Statistik-Vergleich, Melden + Admin-Moderationsansicht.
- Phase 3 (≈ 4 PT): gemeinsame Lernräume (Seed-Boards), Datenexport (Art. 15/20), Feinschliff (Emoji-Reaktionen, Streak-Anstupsen).
Teil 4: FluxTimer – Der Pausen-Lerntimer
✅ MVP umgesetzt: lokaler Zustand, globaler Countdown, Einstellungen, eigener Uhr-mit-Blitz- Schnellzugriff in der Topbar, In-App-Overlay, Snooze, Probelauf sowie optionale Browser- Benachrichtigung und Web-Audio-Signal. Die Glocke bleibt für FluxNet-/Systembenachrichtigungen reserviert. Eine serverseitige Synchronisation bleibt bewusst optional.
Opt-in-Erinnerungstimer im Look der Zeitschaltkreise des DeLorean: Nach frei wählbarer Zeit erinnert Mindflux ans Aufstehen, Trinken, Essen – oder was immer man einträgt. Standardmäßig deaktiviert, jederzeit abschaltbar, komplett frei einstellbar.
4.1 Funktionsumfang
- Aktivieren/Deaktivieren ✅ per Toggle in den Einstellungen und über das eigene Uhr-mit-Blitz-Icon der Dashboard-Topbar.
- Intervall frei wählbar: freie Minuteneingabe, sinnvolle Grenzen 5–240 Minuten (Default-Vorschlag 45).
- Erinnerungsarten wählbar (Mehrfachauswahl, Rotation): Aufstehen 🧍 · Trinken 💧 · Essen 🍎 · Augenpause 👀 · eigener Text (Freitextfeld).
- Snooze (+5 Minuten) und „Pause erledigt“ (startet das Intervall neu).
- Probelauf-Button in den Einstellungen (löst die Erinnerung sofort testweise aus).
- Optional zuschaltbar: Browser-Benachrichtigung (mit Permission-Abfrage) und dezenter Sound.
4.2 Technik: rein clientseitig, kein externer Dienst, DSGVO-minimal
static/js/fluxtimer.js(IIFE), über das Layout-Fragment auf allen eingeloggten Seiten eingebunden.- Zustand in
localStorage:fluxtimer = { enabled, intervalMinutes, reminders: ["aufstehen","trinken",…], customText, sound, notify, nextFireAt, lastFiredAt }. Beim Seitenwechsel/Reload wird die Restzeit ausnextFireAtweitergeführt – der Timer „überlebt“ Navigation, ohne dass der Server irgendetwas weiß. Kein Personenbezug, keine Serverdaten, nichts einwilligungspflichtig (localStorage-Nutzung wird in der Datenschutzerklärung als technisch notwendig/funktional erwähnt). - Auslösung dreistufig: (1) immer das In-App-Overlay (siehe 4.3); (2) optional lokale Browser-
Notification– wird lokal im Tab erzeugt, kein Push-Server beteiligt; (3) optional kurzer Sound (Web Audio, eingebettet, kein CDN). - Bewusst kein Service-Worker-Web-Push: Echte Push-Nachrichten bei geschlossenem Tab liefen zwingend über die Push-Gateways der Browserhersteller – das wäre ein externer Dienst und für einen Pausen-Timer unnötig (wenn kein Mindflux-Tab offen ist, lernt man gerade nicht in Mindflux).
- Ausbau (optional, +0,5 PT):
user_settings-Tabelle für geräteübergreifende Einstellungen; localStorage bleibt der schnelle Cache.
4.3 BTTF-Optik („Zeitschaltkreise“)
- Panel im Zeitschaltkreis-Look: drei Anzeigezeilen in Segmentanzeigen-Typografie (Monospace + Glow):
NÄCHSTE PAUSE– rot/bernsteinfarbene Anzeige (Countdown hh:mm:ss)JETZT– Akzentfarbe des Themes (laufende Uhrzeit)LETZTE PAUSE– gedimmt (wann zuletzt pausiert wurde)
- Beim Auslösen: kurzer Flux-Blitz (CSS-Animation über dem Overlay), 88-mph-Anspielung und Sprüche im BTTF-Ton, rotierend je Erinnerungsart – z. B. „Großer Scott! Zeit, etwas zu trinken.“ · „88 mph erreicht – kurz aufstehen, McFly!“ · „Dein Fluxkompensator braucht einen Snack.“
- Styleguide-konform: alle Farben über Theme-Tokens (die Rot-/Bernsteintöne als neue
--fluxtimer-*-Tokens intheme.css, Dark/Light getestet); Grün bleibt tabu als Erfolgsfarbe. CSS instatic/css/fluxtimer.css(Präfixft-), Cache-Busting?v=1.
4.4 UI-Einstiegspunkte & Dateien
| Ort | Änderung |
|---|---|
settings.html ✅ |
Sektion „FluxTimer“: Toggle, Minuten-Eingabe, Erinnerungs-Checkboxen, Freitext, Benachrichtigung/Sound-Optionen, Probelauf-Button |
dashboard.html (Topbar) ✅ |
Eigenes Uhr-mit-Blitz-Icon öffnet das Popover mit Status, Countdown, Schnell-Toggle und Link zu den Einstellungen; Glocke bleibt frei |
theme.js ✅ |
Zentraler, idempotenter Asset-Bootstrap, damit der Timer über Seitennavigationen weiterläuft |
static/js/fluxtimer.js, static/css/fluxtimer.css ✅ |
Timer-Logik + Zeitschaltkreis-Optik |
4.5 Aufwand
≈ 1,5–2 PT (JS-Logik + Settings-UI + Popover + CSS/Animationen), +0,5 PT für die optionale serverseitige Persistenz.
Schlüsseldateien als Bauplan-Referenz
| Datei | Rolle |
|---|---|
src/main/java/com/example/mindflux/service/DevLabService.java |
Struktureller Bauplan für alle vier Services: sanitize/check*/finish/awardedPoints/stars + Text-Normalisierungs-Helfer (Checker für choice/order/match/regex/cloze teils 1:1 kopierbar) |
src/main/resources/static/js/devlabgame.js |
Renderer-Vertrag {render, collect, lock, mark}, Versuchsleiter-Frontend-Muster |
src/main/resources/db/migration/V8__devlab.sql |
SQL-Stilvorlage für V9–V12 |
src/main/java/com/example/mindflux/config/SecurityConfig.java (Zeile 43) |
CSRF-Ignore-Liste: /api/codeescape/**, /api/bughunter/**, /api/serviceclash/**, /api/networker/** ergänzen |
src/main/resources/templates/dashboard.html (Zeilen 186–196) |
GAMES-Array: Namen aufs neue Schema umstellen (DevLab, SyntaxSaga, CodeEscape, ServiceClash, BugHunter, NetWorker) und die vier '/comingsoon'-Hrefs gegen echte Routen tauschen (Icons lock/sword/bug/network existieren bereits) |
GameScoreController.KNOWN_GAMES + ProgressService.GAMES |
Vier reservierte Keys aufs neue Schema umbenennen (codeescape, serviceclash, bughunter, networker), devlab in ProgressService ergänzen (W1) |
src/main/java/com/example/mindflux/service/GameScoreService.java |
recordScore(...)-Anschlusspunkt aller Spiele |
STYLEGUIDE.md, CHANGELOG.md |
Design-Regeln (blau ✓ / rot ✗, nie grün; Theme-Tokens; ?v=N) und Pflege der Historie |
Verifikation
Pro Spiel (nach Implementierung):
mvn test– insbesondereNetSimulatorTest(Same-Subnet, Gateway, Longest-Prefix-Routen, Loop-Guard, Netz-/Broadcast-Adresse, Duplikat-IP) undServiceClashServiceTest(Seed-Determinismus: gleicher Seed ⇒ identischer Plan; Re-Simulations-Scoring).- App starten (
docker-composeoder lokal, Port 8282), einloggen und den vollen Spielfluss durchspielen: Lobby → Runde → absichtlich falsche Antworten (Versuchsleiter 1.0/0.6/0.3 prüfen) → Aufdecken (0 Punkte) → Abschluss → Score/Sterne/Highscore-Anzeige. - Server-Autorität prüfen:
POST /finishmit manipulierten Daten (fremde IDs, unmögliche Zeitstempel) darf keinen überhöhten Score ergeben; View-DTOs im Netzwerk-Tab dürfen keine Lösungsfelder enthalten. - Highscore-Chip via
GET /api/games/<key>/stats; Eintrag erscheint auf/progress. - Admin-CRUD: Anlegen/Ändern/Löschen inkl. Payload-Validierung (Netzwerker: Musterlösungs-Gate; Bug Hunter:
file/line-Konsistenz; Service Clash:correctIndexinoptions). - Dark/Light-Theme umschalten und mobile Breite prüfen (Theme-Tokens, kein Grün).
Namensschema & visuelle Differenzierung: Dashboard zeigt alle neun Spiele mit einheitlichen CamelCase-Namen; jede der vier neuen Spielseiten hat ihren eigenen Akzent und ihr eigenes visuelles Vokabular – nebeneinander geöffnet müssen sie sofort unterscheidbar sein (Dark und Light prüfen).
Navigation (Abschnitt 2.3): Von jeder Profil-Unterseite aus sind alle anderen per Tab-Leiste erreichbar; „Profil" ist als Sidebar-Eintrag vorhanden; settings.html enthält keine funktionslosen Bedienelemente mehr; kein irreführender Chevron ohne Dropdown.
Für die Verbesserungen (Teil 2): Nach K1/K2: Neustart via docker-compose mit Secret-basiertem Passwort; /users/save liefert 404. Nach W1: DevLab erscheint auf /progress. Nach W4: bewusst kaputte Requests → saubere Fehlerseite/JSON-Fehler statt Whitelabel-500. Nach W6: doppelte Registrierung (Groß-/Kleinschreibung) liefert Fehlermeldung statt 500.