From e52cada445c63db9d4d35d5eeb46f711e8d3f0de Mon Sep 17 00:00:00 2001 From: AliceDarkest Date: Mon, 20 Jul 2026 23:37:55 +0200 Subject: [PATCH] AP0 Quick Wins: Chart.js lokal, Scaffolding weg, Umlaute, FinalProtocol-Titel - Chart.js von jsDelivr-CDN lokal vendored (static/js/vendor/chart.umd.min.js v4.4.7), progress.html darauf umgestellt -> schliesst das einzige externe Asset / DSGVO-Datenleck. - Scaffolding entfernt: DummyController.java, dummy.html, static/hello.txt. - Umlaute umfassend korrigiert (wort-/morphemgenau, Slugs + Code-Identifier wie ZahlungPruefung/GEPRUEFT/Daemon/Boehm bewusst geschont): usv.json, devlab/techtacular/bughunter content.json, techtacular.js, zahlensysteme.html, style.css-Mojibake. V21-Migration reseedet geseedete DBs aus korrigiertem JSON. - FinalProtocol: Titel "AP1" entfernt, Hero deckt jetzt AP1 und AP2 ab. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01WFxcjtc9HNP4VECT6Ur1tE --- .../mindflux/controller/DummyController.java | 13 - src/main/resources/bughunter/content.json | 10 +- .../V21__fix_umlauts_seeded_content.sql | 21 ++ src/main/resources/devlab/content.json | 234 +++++++++--------- src/main/resources/static/css/style.css | 6 +- .../static/finalflux/ap1/zahlensysteme.html | 2 +- .../static/finalflux/questions/usv.json | 134 +++++----- src/main/resources/static/hello.txt | 1 - src/main/resources/static/js/techtacular.js | 2 +- .../static/js/vendor/chart.umd.min.js | 20 ++ src/main/resources/techtacular/content.json | 114 ++++----- src/main/resources/templates/dummy.html | 46 ---- src/main/resources/templates/finalflux.html | 13 +- src/main/resources/templates/progress.html | 2 +- 14 files changed, 300 insertions(+), 318 deletions(-) delete mode 100644 src/main/java/com/example/mindflux/controller/DummyController.java create mode 100644 src/main/resources/db/migration/V21__fix_umlauts_seeded_content.sql delete mode 100644 src/main/resources/static/hello.txt create mode 100644 src/main/resources/static/js/vendor/chart.umd.min.js delete mode 100644 src/main/resources/templates/dummy.html diff --git a/src/main/java/com/example/mindflux/controller/DummyController.java b/src/main/java/com/example/mindflux/controller/DummyController.java deleted file mode 100644 index b4ca6f2..0000000 --- a/src/main/java/com/example/mindflux/controller/DummyController.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.mindflux.controller; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; - -@Controller -public class DummyController { - - @GetMapping("/comingsoon") - public String comingSoon() { - return "dummy"; // verweist auf dummy.html in src/main/resources/templates - } -} diff --git a/src/main/resources/bughunter/content.json b/src/main/resources/bughunter/content.json index f1ee4dd..85ca47c 100644 --- a/src/main/resources/bughunter/content.json +++ b/src/main/resources/bughunter/content.json @@ -15,8 +15,8 @@ { "slug": "menue-fallt-durch", "title": "Fall #3: Das fallende Menü", "language": "Java", "difficulty": "junior", "examScope": "AP1", "description": "Der Punkt ‚Status‘ öffnet unerwartet den Abmelde-Dialog.", "briefing": "Ein Azubi meldet einen Fehler im Konsolenmenü. Prüfe die Verzweigungen.", "consoleOutput": "Eingabe: 2\nStatus: OK\nAbmeldung gestartet", - "files": [{"filename":"Menue.java","content":"switch (auswahl) {\n case 1:\n System.out.println(\"Start\");\n break;\n case 2:\n System.out.println(\"Status: OK\");\n case 3:\n System.out.println(\"Abmeldung gestartet\");\n break;\n default:\n System.out.println(\"Ungültig\");\n}"}], - "bugs": [{"points":100,"file":"Menue.java","line":6,"category":"logik","evidence":"Nach dem Status-Fall wird der nächste case ebenfalls ausgeführt.","fixOptions":[" break;"," continue;"," return false;"," case 2: break;"],"fixIndex":0,"fixedLine":" break;","explanation":"Ohne break fällt ein switch in den nächsten Fall durch (fall-through)."}] + "files": [{"filename":"Menü.java","content":"switch (auswahl) {\n case 1:\n System.out.println(\"Start\");\n break;\n case 2:\n System.out.println(\"Status: OK\");\n case 3:\n System.out.println(\"Abmeldung gestartet\");\n break;\n default:\n System.out.println(\"Ungültig\");\n}"}], + "bugs": [{"points":100,"file":"Menü.java","line":6,"category":"logik","evidence":"Nach dem Status-Fall wird der nächste case ebenfalls ausgeführt.","fixOptions":[" break;"," continue;"," return false;"," case 2: break;"],"fixIndex":0,"fixedLine":" break;","explanation":"Ohne break fällt ein switch in den nächsten Fall durch (fall-through)."}] }, { "slug": "absturz-um-mitternacht", "title": "Fall #4: Der Absturz um Mitternacht", "language": "Java", "difficulty": "senior", "examScope": "AP2_FIAE", @@ -43,11 +43,11 @@ "slug": "archiv-unter-druck", "title": "Fall #7: Archiv unter Druck", "language": "Java", "difficulty": "senior", "examScope": "AP2_FIAE", "description": "Der tägliche Audit-Export bricht bei einem leeren Archiv ab und die CSV-Route stürzt nur in der Nachtverarbeitung ab.", "briefing": "Das Compliance-Team benötigt den Export vor 06:00 Uhr. Zwei Fehler liegen in getrennten Dateien; der Stacktrace nennt nur den Controller.", "consoleOutput": "java.lang.NullPointerException: Cannot invoke \"String.equals(Object)\"\n at ExportController.ausliefern(ExportController.java:4)\n[warn] Audit-Archiv enthält heute keine Einträge", "files": [ - {"filename":"AuditExport.java","content":"import java.util.List;\n\npublic class AuditExport {\n public String export(List eintraege) {\n if (eintraege == null && eintraege.isEmpty()) {\n return \"Keine Einträge\";\n }\n return String.join(\"\\n\", eintraege);\n }\n}"}, - {"filename":"ExportController.java","content":"public class ExportController {\n public String ausliefern(String format, AuditExport export, java.util.List eintraege) {\n if (format.equals(\"csv\")) {\n return export.export(eintraege);\n }\n return \"Format nicht unterstützt\";\n }\n}"} + {"filename":"AuditExport.java","content":"import java.util.List;\n\npublic class AuditExport {\n public String export(List einträge) {\n if (einträge == null && einträge.isEmpty()) {\n return \"Keine Einträge\";\n }\n return String.join(\"\\n\", einträge);\n }\n}"}, + {"filename":"ExportController.java","content":"public class ExportController {\n public String ausliefern(String format, AuditExport export, java.util.List einträge) {\n if (format.equals(\"csv\")) {\n return export.export(einträge);\n }\n return \"Format nicht unterstützt\";\n }\n}"} ], "bugs": [ - {"points":150,"file":"AuditExport.java","line":5,"category":"laufzeit","evidence":"Bei null wird rechts von && trotzdem ein Methodenaufruf ausgeführt.","fixOptions":[" if (eintraege == null || eintraege.isEmpty()) {"," if (eintraege != null && eintraege.isEmpty()) {"," if (eintraege.isEmpty() || eintraege == null) {"," if (eintraege == null & eintraege.isEmpty()) {"],"fixIndex":0,"fixedLine":" if (eintraege == null || eintraege.isEmpty()) {","explanation":"Die ODER-Verknüpfung beendet die Auswertung sofort, wenn die Liste null ist. Danach kann isEmpty sicher geprüft werden."}, + {"points":150,"file":"AuditExport.java","line":5,"category":"laufzeit","evidence":"Bei null wird rechts von && trotzdem ein Methodenaufruf ausgeführt.","fixOptions":[" if (einträge == null || einträge.isEmpty()) {"," if (einträge != null && einträge.isEmpty()) {"," if (einträge.isEmpty() || einträge == null) {"," if (einträge == null & einträge.isEmpty()) {"],"fixIndex":0,"fixedLine":" if (einträge == null || einträge.isEmpty()) {","explanation":"Die ODER-Verknüpfung beendet die Auswertung sofort, wenn die Liste null ist. Danach kann isEmpty sicher geprüft werden."}, {"points":150,"file":"ExportController.java","line":3,"category":"laufzeit","evidence":"Der Stacktrace zeigt auf den Methodenaufruf equals; format kann aus einem optionalen Jobparameter stammen.","fixOptions":[" if (\"csv\".equals(format)) {"," if (format == \"csv\") {"," if (format.equalsIgnoreCase == \"csv\") {"," if (format != \"csv\") {"],"fixIndex":0,"fixedLine":" if (\"csv\".equals(format)) {","explanation":"Wird die feste Zeichenkette links verwendet, liefert equals bei einem null-Wert einfach false statt eine NullPointerException auszulösen."} ] }, diff --git a/src/main/resources/db/migration/V21__fix_umlauts_seeded_content.sql b/src/main/resources/db/migration/V21__fix_umlauts_seeded_content.sql new file mode 100644 index 0000000..c9498a9 --- /dev/null +++ b/src/main/resources/db/migration/V21__fix_umlauts_seeded_content.sql @@ -0,0 +1,21 @@ +-- V21: Umlaut-Korrektur fuer bereits geseedete Spiel-Inhalte (bestehende Datenbanken). +-- +-- Die Inhalte von DevLab, TechTacular und BugHunter wurden einmalig aus den +-- content.json-Dateien geseedet (Seeder laufen nur bei leerer Tabelle). Die JSON- +-- Quellen sind jetzt umlautkorrekt; frische Installationen sind dadurch bereits +-- korrekt. Fuer schon befuellte DBs leeren wir die Content-Tabellen, woraufhin die +-- CommandLineRunner-Seeder sie beim naechsten Start aus dem korrigierten JSON neu +-- aufbauen (gleiche Vorgehensweise wie V6 fuer TechTacular). +-- +-- CASCADE raeumt nur die abhaengigen Content-Tabellen (Tasks/Questions/Answers/ +-- Files/Bugs). Spielstaende (game_scores speichert das Spiel als String, kein FK) +-- und der Fortschritt (bughunter_hunt_progress hat keinen FK auf die Hunts) bleiben +-- erhalten; durch RESTART IDENTITY und die deterministische Seed-Reihenfolge sind +-- die neuen IDs identisch zu vorher. +-- +-- Networker ist NICHT betroffen: dort war ausschliesslich der (bewusst stabile) +-- Slug transliteriert, die sichtbaren Welt-Titel/-Beschreibungen sind bereits korrekt. + +TRUNCATE TABLE devlab_games RESTART IDENTITY CASCADE; +TRUNCATE TABLE techtacular_categories RESTART IDENTITY CASCADE; +TRUNCATE TABLE bughunter_hunts RESTART IDENTITY CASCADE; diff --git a/src/main/resources/devlab/content.json b/src/main/resources/devlab/content.json index a20a466..4886bfd 100644 --- a/src/main/resources/devlab/content.json +++ b/src/main/resources/devlab/content.json @@ -1253,7 +1253,7 @@ "type": "cloze", "prompt": "Kopfbereich auszeichnen", "points": 100, - "narrative": "Oben auf der Seite stehen Logo und Titel. Welches Element umschliesst diesen Kopfbereich?", + "narrative": "Oben auf der Seite stehen Logo und Titel. Welches Element umschließt diesen Kopfbereich?", "codeTemplate": "<{{1}}>\n

Mindflux

\n", "gaps": [ { @@ -1268,7 +1268,7 @@ { "position": 2, "placeholder": "Tag", - "hint": "Gleiches Tag wie das oeffnende.", + "hint": "Gleiches Tag wie das öffnende.", "caseSensitive": false, "solutions": [ "header" @@ -1290,19 +1290,19 @@ ], "correctIndex": 0, "hint": "Es darf pro Seite nur einmal sichtbar vorkommen.", - "successText": "Genau,
umschliesst den Hauptinhalt." + "successText": "Genau,
umschließt den Hauptinhalt." }, { "type": "cloze", "prompt": "Navigation", "points": 100, - "narrative": "Die Linkliste fuehrt durch die Seite.", - "codeTemplate": "<{{1}} aria-label=\"Hauptmenue\">\n Start\n", + "narrative": "Die Linkliste führt durch die Seite.", + "codeTemplate": "<{{1}} aria-label=\"Hauptmenü\">\n Start\n", "gaps": [ { "position": 1, "placeholder": "Tag", - "hint": "Steht fuer Navigation.", + "hint": "Steht für Navigation.", "caseSensitive": false, "solutions": [ "nav" @@ -1311,32 +1311,32 @@ { "position": 2, "placeholder": "Tag", - "hint": "Gleiches Tag schliessen.", + "hint": "Gleiches Tag schließen.", "caseSensitive": false, "solutions": [ "nav" ] } ], - "successText": "