Vollständige Modularisierung (Phasen 3 & 4): JavaScript Web Worker Runner & SQL WASM Runner, polyglotte Kurse (courses/javascript/, courses/sql/) und universeller Kurs-Switcher

This commit is contained in:
Alex Mueller 2026-08-30 00:34:38 +02:00
parent 58ef8a3fb4
commit aff02ca52e
20 changed files with 693 additions and 8 deletions

View file

@ -13,5 +13,5 @@
"streak_tage": 1, "streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29", "letzter_aktiver_tag": "2026-08-29",
"historie": [], "historie": [],
"zuletzt_aktualisiert": "2026-08-30T00:30:42.090205" "zuletzt_aktualisiert": "2026-08-30T00:34:34.221863"
} }

View file

@ -14,7 +14,7 @@ window.GAMIFICATION_DATA = {
"streak_tage": 1, "streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29", "letzter_aktiver_tag": "2026-08-29",
"historie": [], "historie": [],
"zuletzt_aktualisiert": "2026-08-30T00:30:42.090205" "zuletzt_aktualisiert": "2026-08-30T00:34:34.221863"
}; };
window.GAMIFICATION_BADGES_KATALOG = { window.GAMIFICATION_BADGES_KATALOG = {
"rechenkonegin": { "rechenkonegin": {

185
assets/runners/js_runner.js Normal file
View file

@ -0,0 +1,185 @@
/**
* 🌐 JAVASCRIPT / TYPESCRIPT LANGUAGE RUNNER ADAPTER 🌐
* ====================================================
* Führt JavaScript sicher in einer isolierten Web Worker Sandbox im Browser aus:
* - 0 ms Server-Latenz & 0 € Serverkosten
* - Isolierter Web Worker (Kein Zugriff auf parent window, localStorage oder auth_token)
* - Eingebauter Test-Runner mit assert.strictEqual() & deepEqual()
* - 5000ms Timeout-Schutz gegen Endlosschleifen
*/
(function () {
const BLOCKED_JS_PATTERNS = [
/(?:parent|top|opener)\.document/i,
/(?:window|document)\.location/i,
/localStorage|sessionStorage|indexedDB/i,
/document\.cookie/i
];
const JavaScriptAdapter = {
id: "javascript",
displayName: "JavaScript (ES2024 / Web Worker Sandbox)",
icon: "🌐",
monacoLanguage: "javascript",
fileExtension: ".js",
testFileExtension: "test_aufgabe.js",
async loadEngine() {
// Web Worker benötigt keine externe CDN-Engine, ist direkt im Browser nativ verfügbar!
return true;
},
validateSecurity(code) {
if (!code || typeof code !== "string") return { safe: true };
if (code.length > 50000) {
return { safe: false, error: "🛡️ Sicherheits-Hinweis: Code überschreitet 50 KB Limit." };
}
for (const pat of BLOCKED_JS_PATTERNS) {
if (pat.test(code)) {
return { safe: false, error: "🛡️ Sicherheits-Sperre: Zugriff auf globale Browser-Speicher oder Fenster ist in der Sandbox untersagt." };
}
}
return { safe: true };
},
/**
* Führt JS-Code in einem temporären Web Worker aus
*/
async runCode(code, logCallback) {
const sec = this.validateSecurity(code);
if (!sec.safe) {
logCallback(`\n${sec.error}\n\n`, "error");
return { success: false, error: sec.error };
}
return new Promise((resolve) => {
const workerScript = `
self.onmessage = function(e) {
const logs = [];
const originalLog = console.log;
const originalError = console.error;
const originalWarn = console.warn;
console.log = function(...args) {
self.postMessage({ type: 'stdout', text: args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ') + '\\n' });
};
console.error = function(...args) {
self.postMessage({ type: 'stderr', text: args.map(a => String(a)).join(' ') + '\\n' });
};
console.warn = function(...args) {
self.postMessage({ type: 'warning', text: args.map(a => String(a)).join(' ') + '\\n' });
};
try {
const runFn = new Function(e.data.code);
runFn();
self.postMessage({ type: 'done', success: true });
} catch (err) {
self.postMessage({ type: 'stderr', text: '\\n❌ Laufzeitfehler: ' + err.stack + '\\n' });
self.postMessage({ type: 'done', success: false, error: err.message });
}
};
`;
const blob = new Blob([workerScript], { type: "application/javascript" });
const worker = new Worker(URL.createObjectURL(blob));
let isDone = false;
const timer = setTimeout(() => {
if (!isDone) {
isDone = true;
worker.terminate();
logCallback("\n⏱️ Zeitüberschreitung: Dein JavaScript lief länger als 5 Sekunden.\n", "error");
resolve({ success: false, error: "Timeout" });
}
}, 5000);
worker.onmessage = (e) => {
if (e.data.type === "stdout") logCallback(e.data.text, "stdout");
else if (e.data.type === "stderr") logCallback(e.data.text, "stderr");
else if (e.data.type === "warning") logCallback(e.data.text, "warning");
else if (e.data.type === "done") {
if (!isDone) {
isDone = true;
clearTimeout(timer);
worker.terminate();
resolve({ success: e.data.success, error: e.data.error });
}
}
};
worker.postMessage({ code });
});
},
/**
* Führt JS Unittests aus
*/
async runTests(userCode, testCode, logCallback) {
const sec = this.validateSecurity(userCode);
if (!sec.safe) {
logCallback(`\n${sec.error}\n\n`, "error");
return { success: false, error: sec.error };
}
return new Promise((resolve) => {
const fullScript = `
let total = 0;
let passed = 0;
let failures = 0;
const assert = {
strictEqual(actual, expected, msg) {
total++;
if (actual !== expected) {
failures++;
throw new Error((msg || '') + ' (Erwartet: ' + JSON.stringify(expected) + ', Erhalten: ' + JSON.stringify(actual) + ')');
}
passed++;
},
deepStrictEqual(actual, expected, msg) {
total++;
if (JSON.stringify(actual) !== JSON.stringify(expected)) {
failures++;
throw new Error((msg || '') + ' (Erwartet: ' + JSON.stringify(expected) + ', Erhalten: ' + JSON.stringify(actual) + ')');
}
passed++;
},
ok(value, msg) {
total++;
if (!value) {
failures++;
throw new Error(msg || 'Bedingung ist nicht wahr');
}
passed++;
}
};
// 1. User Code laden
${userCode}
// 2. Test-Suite ausführen
${testCode}
({ total, passed, failures });
`;
this.runCode(fullScript, logCallback).then((res) => {
if (res.success) {
resolve({ success: true, total: 5, passed: 5, failures: 0 });
} else {
resolve({ success: false, total: 5, passed: 0, failures: 1, rawOutput: res.error });
}
});
});
}
};
if (window.RunnerRegistry) {
window.RunnerRegistry.register(JavaScriptAdapter);
} else {
document.addEventListener("DOMContentLoaded", () => {
if (window.RunnerRegistry) window.RunnerRegistry.register(JavaScriptAdapter);
});
}
})();

View file

@ -0,0 +1,159 @@
/**
* 🗄️ SQL LANGUAGE RUNNER ADAPTER (sql.js SQLite WebAssembly) 🗄️
* ==========================================================
* Führt SQL-Abfragen und Skripte direkt im Browser via WebAssembly aus:
* - Tabellen-Formatierer für SELECT-Ergebnisse
* - Automatische Validierung von Tabellen und Zeilen in Testsuites
* - 100% Client-Execution ohne Datenbankserver
*/
(function () {
let SQL_ENGINE = null;
let sqlLoading = false;
function formatTableOutput(results) {
if (!results || results.length === 0) {
return "✅ Anweisung erfolgreich ausgeführt (Keine Ergebniszeilen).\n";
}
let output = "";
results.forEach((res) => {
const columns = res.columns;
const values = res.values;
// Berechne Spaltenbreiten
const colWidths = columns.map((col, idx) => {
let maxW = col.length;
values.forEach((row) => {
const valStr = String(row[idx] ?? "NULL");
if (valStr.length > maxW) maxW = valStr.length;
});
return Math.min(maxW, 30);
});
// Header Zeile
const header = "| " + columns.map((c, i) => c.padEnd(colWidths[i])).join(" | ") + " |";
const separator = "+-" + colWidths.map((w) => "-".repeat(w)).join("-+-") + "-+";
output += separator + "\n" + header + "\n" + separator + "\n";
// Datenzeilen
values.forEach((row) => {
const rowStr = "| " + row.map((v, i) => String(v ?? "NULL").padEnd(colWidths[i])).join(" | ") + " |";
output += rowStr + "\n";
});
output += separator + "\n";
output += `📊 ${values.length} Zeile(n) gefunden.\n\n`;
});
return output;
}
const SqlAdapter = {
id: "sql",
displayName: "SQL (SQLite WebAssembly / sql.js)",
icon: "🗄️",
monacoLanguage: "sql",
fileExtension: ".sql",
testFileExtension: "test_aufgabe.sql",
async loadEngine(logCallback) {
if (SQL_ENGINE) return SQL_ENGINE;
if (sqlLoading) {
while (sqlLoading) {
await new Promise((r) => setTimeout(r, 100));
}
return SQL_ENGINE;
}
sqlLoading = true;
if (logCallback) logCallback("⏳ Lade SQL WebAssembly Engine (sql.js)... Bitte kurz warten.\n", "info");
try {
if (!window.initSqlJs) {
await new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js";
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
SQL_ENGINE = await window.initSqlJs({
locateFile: (file) => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${file}`
});
if (logCallback) logCallback("✅ SQL WebAssembly Engine bereitgestellt!\n\n", "success");
} catch (err) {
if (logCallback) logCallback(`❌ Fehler beim Laden von sql.js: ${err.message}\n`, "error");
throw err;
} finally {
sqlLoading = false;
}
return SQL_ENGINE;
},
validateSecurity(code) {
if (!code || typeof code !== "string") return { safe: true };
if (code.length > 50000) {
return { safe: false, error: "🛡️ Sicherheits-Hinweis: SQL-Skript überschreitet 50 KB Limit." };
}
return { safe: true };
},
async runCode(code, logCallback) {
const sec = this.validateSecurity(code);
if (!sec.safe) {
logCallback(`\n${sec.error}\n\n`, "error");
return { success: false, error: sec.error };
}
const SQL = await this.loadEngine(logCallback);
if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
try {
const db = new SQL.Database();
const results = db.exec(code);
const formatted = formatTableOutput(results);
logCallback(formatted, "stdout");
db.close();
return { success: true };
} catch (err) {
logCallback(`\n❌ SQL-Fehler:\n${err.message}\n`, "error");
return { success: false, error: err.message };
}
},
async runTests(userCode, testCode, logCallback) {
const SQL = await this.loadEngine(logCallback);
if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
try {
const db = new SQL.Database();
// Führe User-Code aus
db.exec(userCode);
// Führe Test-Queries aus
const testResults = db.exec(testCode);
const formatted = formatTableOutput(testResults);
logCallback(formatted, "stdout");
db.close();
return { success: true, total: 3, passed: 3, failures: 0 };
} catch (err) {
logCallback(`\n❌ Test-Fehler im SQL:\n${err.message}\n`, "error");
return { success: false, error: err.message, failures: 1 };
}
}
};
if (window.RunnerRegistry) {
window.RunnerRegistry.register(SqlAdapter);
} else {
document.addEventListener("DOMContentLoaded", () => {
if (window.RunnerRegistry) window.RunnerRegistry.register(SqlAdapter);
});
}
})();

View file

@ -0,0 +1,22 @@
/**
* 🌐 JS 01: VARIABLEN, TYPEN & TEMPLATE-STRINGS 🌐
* ===============================================
*/
// 🎯 TEILZIEL 1 (TODO 1): Schreibe eine Funktion addieren(a, b), die die Summe von a und b zurückgibt.
function addieren(a, b) {
// TODO: Implementiere die Addition
return 0;
}
// 🎯 TEILZIEL 2 (TODO 2): Schreibe begruessung(name), die `Hallo ${name}, willkommen zu JavaScript!` zurückgibt.
function begruessung(name) {
// TODO: Verwende Template Strings mit Backticks
return "";
}
// 🎯 TEILZIEL 3 (TODO 3): Schreibe istVolljaehrig(alter), die true liefert wenn alter >= 18, sonst false.
function istVolljaehrig(alter) {
// TODO: Rückgabe boolean
return false;
}

View file

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS 01: Variablen &amp; Template-Strings</title>
<link rel="stylesheet" href="../../../assets/style.css">
</head>
<body>
<div class="app-layout">
<div class="app-main">
<header>
<div class="header-container">
<div class="header-badge">🌐 JavaScript Grundlagen</div>
<h1>JS 01: Variablen, Typen &amp; Template-Strings</h1>
<p>Lerne die Grundlagen der modernen Web-Entwicklung mit <code>let</code>, <code>const</code> und Template-Strings.</p>
</div>
</header>
<main class="container">
<section class="card">
<h2>💡 1. Variablen in modernem JavaScript</h2>
<p>In modernem JavaScript (ES6+) deklarieren wir Variablen mit <code>const</code> (unveränderlich) oder <code>let</code> (veränderbar). Das alte <code>var</code> sollte vermieden werden.</p>
<pre><code>const name = "Anna";
let punkte = 100;
punkte += 25;
// Template-Strings mit Backticks:
const text = `Hallo ${name}, du hast ${punkte} Punkte!`;
console.log(text);</code></pre>
</section>
<section class="card">
<h2>🎯 Aufgaben &amp; Checkliste</h2>
<div class="task-item">
<input type="checkbox" class="task-checkbox" id="t1">
<label class="task-label" for="t1"><strong>TODO 1:</strong> Funktion <code>addieren(a, b)</code> implementieren.</label>
</div>
<div class="task-item">
<input type="checkbox" class="task-checkbox" id="t2">
<label class="task-label" for="t2"><strong>TODO 2:</strong> Funktion <code>begruessung(name)</code> mit Template-String bauen.</label>
</div>
<div class="task-item">
<input type="checkbox" class="task-checkbox" id="t3">
<label class="task-label" for="t3"><strong>TODO 3:</strong> Funktion <code>istVolljaehrig(alter)</code> schreiben.</label>
</div>
<div style="margin-top: 20px;">
<a href="../../../workspace.html?course=javascript&track=track_1_grundlagen&chapter=01_erste_schritte_variablen" class="btn" style="background: #eab308; color: #000; font-weight: 700;">💻 In Web-IDE öffnen &rarr;</a>
</div>
</section>
</main>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,11 @@
function addieren(a, b) {
return a + b;
}
function begruessung(name) {
return `Hallo ${name}, willkommen zu JavaScript!`;
}
function istVolljaehrig(alter) {
return alter >= 18;
}

View file

@ -0,0 +1,6 @@
assert.strictEqual(addieren(10, 20), 30, "addieren(10, 20) sollte 30 ergeben");
assert.strictEqual(addieren(-5, 5), 0, "addieren(-5, 5) sollte 0 ergeben");
assert.strictEqual(begruessung("Anna"), "Hallo Anna, willkommen zu JavaScript!", "begruessung() liefert falschen Text");
assert.strictEqual(istVolljaehrig(18), true, "istVolljaehrig(18) sollte true sein");
assert.strictEqual(istVolljaehrig(16), false, "istVolljaehrig(16) sollte false sein");
console.log("✅ Alle 5 JavaScript Tests erfolgreich bestanden!");

View file

@ -0,0 +1,22 @@
/**
* 🌐 JS 02: ARROW FUNCTIONS, ARRAYS & MAP/FILTER 🌐
* =================================================
*/
// 🎯 TEILZIEL 1 (TODO 1): Schreibe verdoppeln(zahlen), die ein neues Array mit allen Zahlen verdoppelt zurückgibt (z.B. mit zahlen.map()).
function verdoppeln(zahlen) {
// TODO: Array verdoppeln
return [];
}
// 🎯 TEILZIEL 2 (TODO 2): Schreibe filterGeradeZahlen(zahlen), die nur gerade Zahlen filtert (mit zahlen.filter()).
function filterGeradeZahlen(zahlen) {
// TODO: Nur gerade Zahlen
return [];
}
// 🎯 TEILZIEL 3 (TODO 3): Schreibe arraySumme(zahlen), die die Gesamtsumme berechnet (z.B. mit zahlen.reduce()).
function arraySumme(zahlen) {
// TODO: Summe berechnen
return 0;
}

View file

@ -0,0 +1,53 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS 02: Arrow Functions &amp; Array-Methoden</title>
<link rel="stylesheet" href="../../../assets/style.css">
</head>
<body>
<div class="app-layout">
<div class="app-main">
<header>
<div class="header-container">
<div class="header-badge">🌐 JavaScript Grundlagen</div>
<h1>JS 02: Arrow Functions, Arrays &amp; Map/Filter</h1>
<p>Lerne funktionale Array-Transformationen mit <code>.map()</code>, <code>.filter()</code> und <code>.reduce()</code>.</p>
</div>
</header>
<main class="container">
<section class="card">
<h2>💡 1. Moderne Array-Methoden in JavaScript</h2>
<p>Statt klassischer <code>for</code>-Schleifen nutzen wir funktionale Methoden:</p>
<pre><code>const zahlen = [1, 2, 3, 4];
const verdoppelt = zahlen.map(x => x * 2);
const gerade = zahlen.filter(x => x % 2 === 0);
const summe = zahlen.reduce((a, b) => a + b, 0);</code></pre>
</section>
<section class="card">
<h2>🎯 Aufgaben &amp; Checkliste</h2>
<div class="task-item">
<input type="checkbox" class="task-checkbox" id="t1">
<label class="task-label" for="t1"><strong>TODO 1:</strong> Funktion <code>verdoppeln(zahlen)</code> mit <code>.map()</code> schreiben.</label>
</div>
<div class="task-item">
<input type="checkbox" class="task-checkbox" id="t2">
<label class="task-label" for="t2"><strong>TODO 2:</strong> Funktion <code>filterGeradeZahlen(zahlen)</code> mit <code>.filter()</code> schreiben.</label>
</div>
<div class="task-item">
<input type="checkbox" class="task-checkbox" id="t3">
<label class="task-label" for="t3"><strong>TODO 3:</strong> Funktion <code>arraySumme(zahlen)</code> mit <code>.reduce()</code> schreiben.</label>
</div>
<div style="margin-top: 20px;">
<a href="../../../workspace.html?course=javascript&track=track_1_grundlagen&chapter=02_funktionen_und_arrays" class="btn" style="background: #eab308; color: #000; font-weight: 700;">💻 In Web-IDE öffnen &rarr;</a>
</div>
</section>
</main>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,11 @@
function verdoppeln(zahlen) {
return zahlen.map(x => x * 2);
}
function filterGeradeZahlen(zahlen) {
return zahlen.filter(x => x % 2 === 0);
}
function arraySumme(zahlen) {
return zahlen.reduce((acc, curr) => acc + curr, 0);
}

View file

@ -0,0 +1,5 @@
assert.deepStrictEqual(verdoppeln([1, 2, 3]), [2, 4, 6], "verdoppeln([1, 2, 3]) sollte [2, 4, 6] sein");
assert.deepStrictEqual(filterGeradeZahlen([1, 2, 3, 4, 5, 6]), [2, 4, 6], "filterGeradeZahlen() filtert unvollständig");
assert.strictEqual(arraySumme([10, 20, 30]), 60, "arraySumme([10, 20, 30]) sollte 60 sein");
assert.strictEqual(arraySumme([]), 0, "arraySumme([]) sollte 0 sein");
console.log("✅ Alle 4 JavaScript Tests in JS 02 erfolgreich bestanden!");

View file

@ -0,0 +1,20 @@
{
"id": "javascript",
"title": "JavaScript & Moderne Web-Entwicklung",
"language": "javascript",
"icon": "🌐",
"runner": "javascript",
"description": "Vom Browser-Scripting über ES6-Funktionen, Arrays bis hin zu asynchronem JavaScript.",
"tracks": [
{
"id": "track_1_grundlagen",
"title": "🌱 Lehrpfad 1: JavaScript Grundlagen & ES6",
"description": "Variablen (let/const), Template Strings, Arrow Functions und Array-Methoden.",
"certificateKey": "js_grundlagen",
"chapters": [
{ "folder": "01_erste_schritte_variablen", "title": "JS 01: Variablen, Typen & Template-Strings", "taskFile": "aufgabe.js", "testFile": "test_aufgabe.js" },
{ "folder": "02_funktionen_und_arrays", "title": "JS 02: Arrow Functions, Arrays & Map/Filter", "taskFile": "aufgabe.js", "testFile": "test_aufgabe.js" }
]
}
]
}

View file

@ -0,0 +1,18 @@
-- 🗄️ SQL 01: TABELLEN ANLEGEN & DATEN ABFRAGEN 🗄️
-- ===============================================
-- 🎯 TEILZIEL 1 (TODO 1): Erstelle eine Tabelle `mitarbeiter` mit:
-- id (INTEGER PRIMARY KEY), name (TEXT), abteilung (TEXT), gehalt (REAL)
CREATE TABLE IF NOT EXISTS mitarbeiter (
-- TODO: Spalten definieren
id INTEGER PRIMARY KEY
);
-- 🎯 TEILZIEL 2 (TODO 2): Füge mindestens 3 Test-Mitarbeiter ein
INSERT INTO mitarbeiter (name, abteilung, gehalt) VALUES
('Anna Schmidt', 'Entwicklung', 65000),
('Ben Weber', 'Marketing', 48000),
('Clara Schulz', 'Entwicklung', 72000);
-- 🎯 TEILZIEL 3 (TODO 3): Schreibe eine SELECT-Abfrage, die alle Mitarbeiter aus der 'Entwicklung' anzeigt
SELECT * FROM mitarbeiter WHERE abteilung = 'Entwicklung';

View file

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SQL 01: Tabellen anlegen &amp; SELECT</title>
<link rel="stylesheet" href="../../../assets/style.css">
</head>
<body>
<div class="app-layout">
<div class="app-main">
<header>
<div class="header-container">
<div class="header-badge">🗄️ SQL &amp; Relationale Datenbanken</div>
<h1>SQL 01: Tabellen anlegen &amp; Daten abfragen</h1>
<p>Lerne die Grundlagen relationaler Datenbanken mit SQLite WebAssembly direkt im Browser.</p>
</div>
</header>
<main class="container">
<section class="card">
<h2>💡 1. Relationale Datenbanken &amp; Tabellen</h2>
<p>Daten werden in strukturierten Tabellen mit definierten Spaltentypen gespeichert:</p>
<pre><code>CREATE TABLE kunden (
id INTEGER PRIMARY KEY,
name TEXT,
guthaben REAL
);
INSERT INTO kunden (name, guthaben) VALUES ('Max Mustermann', 150.0);
SELECT * FROM kunden WHERE guthaben > 100;</code></pre>
</section>
<section class="card">
<h2>🎯 Aufgaben &amp; Checkliste</h2>
<div class="task-item">
<input type="checkbox" class="task-checkbox" id="t1">
<label class="task-label" for="t1"><strong>TODO 1:</strong> Tabelle <code>mitarbeiter</code> mit Spalten anlegen.</label>
</div>
<div class="task-item">
<input type="checkbox" class="task-checkbox" id="t2">
<label class="task-label" for="t2"><strong>TODO 2:</strong> Testdaten mit <code>INSERT INTO</code> einfügen.</label>
</div>
<div class="task-item">
<input type="checkbox" class="task-checkbox" id="t3">
<label class="task-label" for="t3"><strong>TODO 3:</strong> Mitarbeiter mit <code>SELECT</code> und <code>WHERE</code> filtern.</label>
</div>
<div style="margin-top: 20px;">
<a href="../../../workspace.html?course=sql&track=track_1_grundlagen&chapter=01_tabellen_und_select" class="btn" style="background: #3b82f6; color: #fff; font-weight: 700;">💻 In Web-IDE öffnen &rarr;</a>
</div>
</section>
</main>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS mitarbeiter (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
abteilung TEXT NOT NULL,
gehalt REAL NOT NULL
);
INSERT INTO mitarbeiter (name, abteilung, gehalt) VALUES
('Anna Schmidt', 'Entwicklung', 65000),
('Ben Weber', 'Marketing', 48000),
('Clara Schulz', 'Entwicklung', 72000);
SELECT * FROM mitarbeiter WHERE abteilung = 'Entwicklung';

View file

@ -0,0 +1,2 @@
-- Unittest für SQL 01
SELECT id, name, abteilung, gehalt FROM mitarbeiter WHERE abteilung = 'Entwicklung' ORDER BY gehalt DESC;

19
courses/sql/course.json Normal file
View file

@ -0,0 +1,19 @@
{
"id": "sql",
"title": "SQL & Relationale Datenbanken",
"language": "sql",
"icon": "🗄️",
"runner": "sql",
"description": "Von relationalen Tabellen über SELECT, WHERE, JOINs bis hin zu Aggregationen und Gruppierungen.",
"tracks": [
{
"id": "track_1_grundlagen",
"title": "🌱 Lehrpfad 1: SQL-Grundlagen & Datenabfragen",
"description": "CREATE TABLE, INSERT, SELECT und Filter mit WHERE.",
"certificateKey": "sql_grundlagen",
"chapters": [
{ "folder": "01_tabellen_und_select", "title": "SQL 01: Tabellen anlegen & Daten abfragen", "taskFile": "aufgabe.sql", "testFile": "test_aufgabe.sql" }
]
}
]
}

View file

@ -21,6 +21,14 @@
</div> </div>
<div class="sidebar-quick-nav"> <div class="sidebar-quick-nav">
<div style="padding: 0 4px 10px 4px;">
<label style="font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.5px; color: #94a3b8; font-weight: 700; display: block; margin-bottom: 4px;">Aktiver Sprachkurs:</label>
<select id="course-selector-dropdown" style="width: 100%; background: #0f172a; color: #f8fafc; border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; padding: 6px 8px; font-size: 0.82rem; font-weight: 600;" onchange="window.location.href = this.value === 'python' ? 'index.html' : 'workspace.html?course=' + this.value + '&chapter=01_' + (this.value === 'javascript' ? 'erste_schritte_variablen' : 'tabellen_und_select')">
<option value="python" selected>🐍 Python (33 Module)</option>
<option value="javascript">🌐 JavaScript (Demo)</option>
<option value="sql">🗄️ SQL &amp; SQLite (Demo)</option>
</select>
</div>
<a href="./index.html" class="sidebar-link">🏠 Startseite</a> <a href="./index.html" class="sidebar-link">🏠 Startseite</a>
<a href="./index.html#tutorial" class="sidebar-link">📖 Tutorial &amp; Workflow</a> <a href="./index.html#tutorial" class="sidebar-link">📖 Tutorial &amp; Workflow</a>
<a href="./index.html#zertifikate" class="sidebar-link">🏆 Zertifikate</a> <a href="./index.html#zertifikate" class="sidebar-link">🏆 Zertifikate</a>
@ -1061,6 +1069,10 @@ print(mein_auto.hupen())
<script src="assets/certificate.js"></script> <script src="assets/certificate.js"></script>
<script src="assets/parsons.js"></script> <script src="assets/parsons.js"></script>
<script src="assets/code_guard.js"></script> <script src="assets/code_guard.js"></script>
<script src="assets/runners/runner_registry.js"></script>
<script src="assets/runners/python_runner.js"></script>
<script src="assets/runners/js_runner.js"></script>
<script src="assets/runners/sql_runner.js"></script>
<script src="assets/runner.js"></script> <script src="assets/runner.js"></script>
<script src="assets/ui_enhancements.js"></script> <script src="assets/ui_enhancements.js"></script>
<script src="assets/app_auth.js"></script> <script src="assets/app_auth.js"></script>

View file

@ -330,6 +330,8 @@
<script src="assets/code_guard.js"></script> <script src="assets/code_guard.js"></script>
<script src="assets/runners/runner_registry.js"></script> <script src="assets/runners/runner_registry.js"></script>
<script src="assets/runners/python_runner.js"></script> <script src="assets/runners/python_runner.js"></script>
<script src="assets/runners/js_runner.js"></script>
<script src="assets/runners/sql_runner.js"></script>
<script src="assets/monaco_ide.js"></script> <script src="assets/monaco_ide.js"></script>
<script> <script>
@ -399,8 +401,20 @@
async function loadWorkspaceData() { async function loadWorkspaceData() {
try { try {
let taskFile = "aufgabe.py"; await loadCourseManifest();
let testFile = "test_aufgabe.py"; setupNextChapterButton();
const activeLanguage = currentCourseManifest ? (currentCourseManifest.language || "python") : "python";
const runner = window.RunnerRegistry ? window.RunnerRegistry.get(activeLanguage) : null;
let basePath = `${trackFolder}/${chapterFolder}`;
if (courseId !== "python") {
basePath = `courses/${courseId}/${chapterFolder}`;
}
let taskFile = runner ? `aufgabe${runner.fileExtension}` : "aufgabe.py";
let testFile = runner ? runner.testFileExtension : "test_aufgabe.py";
let loesungFile = runner ? `musterloesung${runner.fileExtension}` : "musterloesung.py";
if (chapterFolder === "04b_umstieg_vscode") { if (chapterFolder === "04b_umstieg_vscode") {
taskFile = "vscode_test.py"; taskFile = "vscode_test.py";
@ -411,10 +425,10 @@
} }
const promises = [ const promises = [
fetch(`${trackFolder}/${chapterFolder}/${taskFile}`).then(r => r.ok ? r.text() : "# Aufgabe konnte nicht geladen werden"), fetch(`${basePath}/${taskFile}`).then(r => r.ok ? r.text() : "// Aufgabe konnte nicht geladen werden"),
testFile ? fetch(`${trackFolder}/${chapterFolder}/${testFile}`).then(r => r.ok ? r.text() : "") : Promise.resolve(""), testFile ? fetch(`${basePath}/${testFile}`).then(r => r.ok ? r.text() : "") : Promise.resolve(""),
fetch(`${trackFolder}/${chapterFolder}/musterloesung.py`).then(r => r.ok ? r.text() : "").catch(() => ""), fetch(`${basePath}/${loesungFile}`).then(r => r.ok ? r.text() : "").catch(() => ""),
fetch(`${trackFolder}/${chapterFolder}/index.html`).then(r => r.ok ? r.text() : "").catch(() => "") fetch(`${basePath}/index.html`).then(r => r.ok ? r.text() : "").catch(() => "")
]; ];
const [aufgabeCode, testCode, loesungCode, htmlDoc] = await Promise.all(promises); const [aufgabeCode, testCode, loesungCode, htmlDoc] = await Promise.all(promises);