Files
geograsim/v2-platform/php/api/teacher/live-stream.php
T
Adminator 1e51ef7def Nachtrag: alle bisher untracked Ordner + hängende Änderungen mit-committen
- Konzept/, didaktik_geografie/, didaktik_simulation/, v2-modules/, v2-platform/
- 12 code-workspace-Files
- STATUS-*.md
- viele M/D/R-Änderungen an bereits getrackten Files
- .gitignore verstärkt: **/.humaninput/, **/secret_keys.txt

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-08 02:27:02 +02:00

85 lines
2.6 KiB
PHP

<?php
/**
* GET /api/teacher/live-stream?classId=42
*
* Server-Sent Events für Lehrer-Live-Dashboard.
* Pollt regelmäßig die telemetry_events_v2 nach neuen Events der Klasse
* und sendet sie als SSE.
*
* NOTE: Apache nutzt häufig prefork und blockiert lange Connections.
* Für Produktion ggf. auf Long-Polling oder Websocket umstellen.
*/
declare(strict_types=1);
require_once __DIR__ . '/../../bootstrap.php';
v2_validate_method('GET');
$auth = v2_auth_require('teacher');
$teacherId = (int) ($auth['sub'] ?? 0);
$classId = (int) ($_GET['classId'] ?? 0);
if (!$classId) {
v2_response_error(400, 'missing_classId', 'Parameter "classId" fehlt.');
}
// Lehrer-Berechtigung für die Klasse prüfen
$ok = v2_db_one("
SELECT id FROM classes WHERE id = :cid AND teacher_id = :tid
", [':cid' => $classId, ':tid' => $teacherId]);
if (!$ok) {
v2_response_error(403, 'forbidden', 'Diese Klasse gehört nicht dieser Lehrperson.');
}
// SSE-Header
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('X-Accel-Buffering: no');
ob_implicit_flush(true);
@ob_end_flush();
$lastId = 0;
$startTs = time();
// Initial dump: letzte 30 Events der Klasse, falls Lehrperson mitten in Stunde reinhängt
$initial = v2_db_all("
SELECT id, student_id, module_slug, event_type, phase, phase_label, step,
current_score, score_max, time_in_phase_s, milestone_id, milestone_label,
idle_s, hint, blocker_type, ts
FROM telemetry_events_v2
WHERE class_id = :cid AND ts > NOW() - INTERVAL 5 MINUTE
ORDER BY id ASC
LIMIT 30
", [':cid' => $classId]);
foreach ($initial as $row) {
echo "id: {$row['id']}\n";
echo "event: telemetry\n";
echo 'data: ' . json_encode($row, JSON_UNESCAPED_UNICODE) . "\n\n";
$lastId = max($lastId, (int) $row['id']);
}
// Loop — max 60s, dann Client-Reconnect
while (time() - $startTs < 60) {
if (connection_aborted()) break;
$rows = v2_db_all("
SELECT id, student_id, module_slug, event_type, phase, phase_label, step,
current_score, score_max, time_in_phase_s, milestone_id, milestone_label,
idle_s, hint, blocker_type, ts
FROM telemetry_events_v2
WHERE class_id = :cid AND id > :lid
ORDER BY id ASC
LIMIT 50
", [':cid' => $classId, ':lid' => $lastId]);
foreach ($rows as $row) {
echo "id: {$row['id']}\n";
echo "event: telemetry\n";
echo 'data: ' . json_encode($row, JSON_UNESCAPED_UNICODE) . "\n\n";
$lastId = (int) $row['id'];
}
// Heartbeat-Comment damit Verbindung nicht abreißt
echo ": ping\n\n";
@flush();
sleep(2);
}