$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); }