Stand 2026-04-13: PHP/MySQL Infrastruktur, Flussmanagement, Stadt-Prototyp

- PHP/MySQL Backend (XAMPP + Produktionsserver)
- Front-Controller, API-Endpunkte, Session-Management
- Flussmanagement-Simulation (Echtzeit, Punkt-basierter Fluss)
- Stadt & Raumplanung (Prototyp, Top-Down Kachelsystem)
- Klimawaechter 3D: Deiche kleiner, Baeume kippen, Budget angepasst
- persistence.ts: Dualer Speicher (localStorage + Server-API)
- 6 Unit-Test-Dateien fuer bestehende Simulationen

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 16:43:42 +02:00
commit f22c5ebbfe
83 changed files with 22709 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
<?php
/**
* API: Assessment-Daten speichern
* POST /api/assessment {simId, processLog, predictions, results, reflections, duration, completedPhases}
*/
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
Response::error('Nur POST erlaubt', 405);
}
$sessionId = Session::requireStudent();
$body = json_decode(file_get_contents('php://input'), true);
if (!$body || !isset($body['simId'])) {
Response::error('simId erforderlich');
}
$db = Database::get();
// class_id aus student_session holen
$session = $db->fetchOne('SELECT class_id FROM student_sessions WHERE id = ?', [$sessionId]);
$classId = $session ? $session['class_id'] : null;
$db->execute(
'INSERT INTO assessments (session_id, sim_id, class_id, process_log, predictions, results, reflections, duration_ms, completed_phases)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
[
$sessionId,
$body['simId'],
$classId,
json_encode($body['processLog'] ?? []),
json_encode($body['predictions'] ?? []),
json_encode($body['results'] ?? []),
json_encode($body['reflections'] ?? []),
$body['duration'] ?? 0,
json_encode($body['completedPhases'] ?? []),
]
);
Response::ok(['id' => $db->lastInsertId()]);
+68
View File
@@ -0,0 +1,68 @@
<?php
/**
* API: Lehrkraefte-Dashboard Daten
* GET /api/dashboard?class_id=X
*/
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
Response::error('Nur GET erlaubt', 405);
}
$teacherId = Session::requireTeacher();
$db = Database::get();
$classId = (int)($_GET['class_id'] ?? 0);
// Verifizieren dass die Klasse dem Lehrer gehoert
if ($classId) {
$class = $db->fetchOne(
'SELECT * FROM classes WHERE id = ? AND teacher_id = ?',
[$classId, $teacherId]
);
if (!$class) Response::error('Klasse nicht gefunden', 404);
}
// Klassen des Lehrers
$classes = $db->fetchAll('SELECT * FROM classes WHERE teacher_id = ? ORDER BY created_at DESC', [$teacherId]);
if (!$classId && !empty($classes)) {
$classId = $classes[0]['id'];
}
// Aktive Schueler (letzte 60 Minuten)
$activeStudents = $db->fetchAll(
'SELECT id, display_name, last_seen FROM student_sessions
WHERE class_id = ? AND last_seen > DATE_SUB(NOW(), INTERVAL 60 MINUTE)
ORDER BY last_seen DESC',
[$classId]
);
// Alle Schueler der Klasse
$allStudents = $db->fetchAll(
'SELECT id, display_name, created_at, last_seen FROM student_sessions WHERE class_id = ? ORDER BY display_name',
[$classId]
);
// Assessments fuer diese Klasse
$assessments = $db->fetchAll(
'SELECT a.*, s.display_name FROM assessments a
JOIN student_sessions s ON s.id = a.session_id
WHERE a.class_id = ?
ORDER BY a.submitted_at DESC
LIMIT 100',
[$classId]
);
// Modulfreigaben
$modules = $db->fetchAll('SELECT * FROM class_modules WHERE class_id = ?', [$classId]);
Response::ok([
'classes' => $classes,
'currentClassId' => $classId,
'activeStudents' => $activeStudents,
'allStudents' => $allStudents,
'assessments' => $assessments,
'modules' => $modules,
]);
+44
View File
@@ -0,0 +1,44 @@
<?php
/**
* API: Spielstaende speichern/laden
* GET /api/saves?key=klimawaechter-save → Spielstand laden
* POST /api/saves {key, data, version} → Spielstand speichern
*/
header('Content-Type: application/json; charset=utf-8');
$method = $_SERVER['REQUEST_METHOD'];
$db = Database::get();
if ($method === 'GET') {
$sessionId = Session::studentId();
if (!$sessionId) {
Response::json(['data' => null]);
}
$key = $_GET['key'] ?? '';
if (!$key) Response::error('key fehlt');
$row = $db->fetchOne(
'SELECT save_data, save_version FROM game_saves WHERE session_id = ? AND save_key = ?',
[$sessionId, $key]
);
Response::json(['data' => $row ? $row['save_data'] : null, 'version' => $row ? (int)$row['save_version'] : null]);
}
if ($method === 'POST') {
$sessionId = Session::requireStudent();
$body = json_decode(file_get_contents('php://input'), true);
if (!$body || !isset($body['key']) || !isset($body['data'])) {
Response::error('key und data erforderlich');
}
$db->execute(
'INSERT INTO game_saves (session_id, save_key, save_data, save_version)
VALUES (?, ?, ?, ?)
ON DUPLICATE KEY UPDATE save_data = VALUES(save_data), save_version = VALUES(save_version)',
[$sessionId, $body['key'], $body['data'], $body['version'] ?? 2]
);
Response::ok();
}
Response::error('Methode nicht erlaubt', 405);
+69
View File
@@ -0,0 +1,69 @@
<?php
/**
* API: Session-Management
* POST /api/sessions {action: "join", joinCode: "ABC123", displayName: "Anna"}
* POST /api/sessions {action: "status"}
*/
header('Content-Type: application/json; charset=utf-8');
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
Response::error('Nur POST erlaubt', 405);
}
$body = json_decode(file_get_contents('php://input'), true);
$action = $body['action'] ?? '';
$db = Database::get();
if ($action === 'join') {
$joinCode = strtoupper(trim($body['joinCode'] ?? ''));
if (strlen($joinCode) !== 6) {
Response::error('Ungültiger Code');
}
$class = $db->fetchOne('SELECT id, name FROM classes WHERE join_code = ?', [$joinCode]);
if (!$class) {
Response::error('Klasse nicht gefunden');
}
$displayName = trim($body['displayName'] ?? '');
$uuid = Session::createStudent((int)$class['id'], $displayName);
Response::ok([
'sessionId' => $uuid,
'className' => $class['name'],
'classId' => (int)$class['id'],
]);
}
if ($action === 'status') {
$sessionId = Session::studentId();
if (!$sessionId) {
Response::json(['loggedIn' => false]);
}
$session = $db->fetchOne(
'SELECT s.display_name, s.class_id, c.name as class_name
FROM student_sessions s
LEFT JOIN classes c ON c.id = s.class_id
WHERE s.id = ?',
[$sessionId]
);
if (!$session) {
Response::json(['loggedIn' => false]);
}
// last_seen aktualisieren
$db->execute('UPDATE student_sessions SET last_seen = NOW() WHERE id = ?', [$sessionId]);
Response::json([
'loggedIn' => true,
'sessionId' => $sessionId,
'displayName' => $session['display_name'],
'classId' => $session['class_id'],
'className' => $session['class_name'],
]);
}
Response::error('Unbekannte Aktion');