legt einen aktiven Durchgang an, liefert attempt_id zurueck * * POST /api/logistik-sessions {action: "end", attemptId, finalBalance, success, stats} * -> schliesst den Durchgang ab * * GET /api/logistik-sessions?action=status * -> liefert aktiven Durchgang der aktuellen Schueler-Session (oder null) * * Persistenz: nutzt game_saves (save_key = "logistik:active-attempt" + * "logistik:history") — keine eigene Tabelle noetig. * * Security: Session-ID aus Cookie wird gegen student_sessions validiert. */ $method = $_SERVER['REQUEST_METHOD']; $db = Database::get(); /** Student-Session validieren (nicht nur Cookie-Praesenz, sondern DB-Existenz). */ function lg_requireValidStudent(): string { $sid = Session::requireStudent(); $db = Database::get(); $row = $db->fetchOne('SELECT id FROM student_sessions WHERE id = ?', [$sid]); if (!$row) { http_response_code(401); echo json_encode(['error' => 'Session ungueltig']); exit; } return $sid; } function lg_uuid(): string { return sprintf('%s-%s-%s-%s-%s', bin2hex(random_bytes(4)), bin2hex(random_bytes(2)), bin2hex(random_bytes(2)), bin2hex(random_bytes(2)), bin2hex(random_bytes(6)) ); } if ($method === 'GET') { $action = $_GET['action'] ?? 'status'; if ($action !== 'status') Response::error('Unbekannte Aktion', 400); $sid = lg_requireValidStudent(); $row = $db->fetchOne( 'SELECT save_data FROM game_saves WHERE session_id = ? AND save_key = ?', [$sid, 'logistik:active-attempt'] ); if (!$row) Response::ok(['active' => null]); $payload = json_decode($row['save_data'], true); Response::ok(['active' => $payload]); } if ($method === 'POST') { $body = json_decode(file_get_contents('php://input'), true); if (!is_array($body)) Response::error('Body fehlt oder kein JSON', 400); $action = $body['action'] ?? ''; $sid = lg_requireValidStudent(); if ($action === 'start') { $levelId = (int)($body['levelId'] ?? 0); if ($levelId < 1) Response::error('levelId fehlt oder ungueltig', 400); $attemptId = lg_uuid(); $payload = [ 'attemptId' => $attemptId, 'levelId' => $levelId, 'startedAt' => gmdate('c'), 'status' => 'active', ]; $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)', [$sid, 'logistik:active-attempt', json_encode($payload), 1] ); Response::ok(['attemptId' => $attemptId, 'levelId' => $levelId]); } if ($action === 'end') { $attemptId = trim($body['attemptId'] ?? ''); if (strlen($attemptId) < 8) Response::error('attemptId fehlt', 400); $row = $db->fetchOne( 'SELECT save_data FROM game_saves WHERE session_id = ? AND save_key = ?', [$sid, 'logistik:active-attempt'] ); if (!$row) Response::error('Kein aktiver Durchgang', 404); $active = json_decode($row['save_data'], true); if (($active['attemptId'] ?? null) !== $attemptId) { Response::error('attemptId passt nicht zum aktiven Durchgang', 409); } $finalBalance = (int)($body['finalBalance'] ?? 0); $success = (bool)($body['success'] ?? false); $stats = is_array($body['stats'] ?? null) ? $body['stats'] : []; $closed = array_merge($active, [ 'status' => $success ? 'success' : 'fail', 'finalBalance' => $finalBalance, 'endedAt' => gmdate('c'), 'stats' => $stats, ]); // Aktiven Slot leeren, Historie anhaengen (letzte 20 Durchgaenge) $db->execute( 'DELETE FROM game_saves WHERE session_id = ? AND save_key = ?', [$sid, 'logistik:active-attempt'] ); $hist = $db->fetchOne( 'SELECT save_data FROM game_saves WHERE session_id = ? AND save_key = ?', [$sid, 'logistik:history'] ); $list = $hist ? (json_decode($hist['save_data'], true) ?: []) : []; $list[] = $closed; if (count($list) > 20) $list = array_slice($list, -20); $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)', [$sid, 'logistik:history', json_encode($list), 1] ); Response::ok(['closed' => $closed]); } Response::error('Unbekannte Aktion', 400); } Response::error('Methode nicht erlaubt', 405);