sinceId. * * Lehrer-Cockpit pollt alle 3s. Robuster als SSE auf XAMPP-Apache. */ 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); $sinceId = (int) ($_GET['sinceId'] ?? 0); if (!$classId) v2_response_error(400, 'missing_classId', 'classId fehlt.'); $own = v2_db_one('SELECT id FROM classes WHERE id = :c AND teacher_id = :t', [':c' => $classId, ':t' => $teacherId]); if (!$own) v2_response_error(403, 'forbidden', 'Klasse nicht deine.'); // Pro Schüler*in das jüngste Event (für Status-Cards) $status = v2_db_all(" SELECT t.student_id, t.module_slug, t.event_type, t.phase, t.phase_label, t.step, t.current_score, t.score_max, t.idle_s, t.ts, s.display_name, s.username, s.avatar_slug, s.nachteilsausgleich FROM telemetry_events_v2 t JOIN ( SELECT student_id, MAX(id) AS max_id FROM telemetry_events_v2 WHERE class_id = :cid AND ts > NOW() - INTERVAL 5 MINUTE GROUP BY student_id ) latest ON latest.max_id = t.id JOIN students s ON s.id = t.student_id ORDER BY s.display_name, s.username ", [':cid' => $classId]); // Neue Events seit sinceId (für Event-Feed) $newEvents = v2_db_all(" SELECT id, student_id, module_slug, event_type, phase, phase_label, milestone_id, milestone_label, idle_s, ts FROM telemetry_events_v2 WHERE class_id = :cid AND id > :since ORDER BY id ASC LIMIT 100 ", [':cid' => $classId, ':since' => $sinceId]); $maxId = $sinceId; foreach ($newEvents as $e) { if ((int) $e['id'] > $maxId) $maxId = (int) $e['id']; } v2_response_ok([ 'studentStatus' => array_map(fn($r) => [ 'studentId' => (int) $r['student_id'], 'displayName' => $r['display_name'] ?? $r['username'], 'avatarSlug' => $r['avatar_slug'], 'nachteilsausgleich' => (bool) $r['nachteilsausgleich'], 'moduleSlug' => $r['module_slug'], 'eventType' => $r['event_type'], 'phase' => $r['phase'], 'phaseLabel' => $r['phase_label'], 'step' => $r['step'], 'currentScore' => $r['current_score'] !== null ? (int) $r['current_score'] : null, 'scoreMax' => $r['score_max'] !== null ? (int) $r['score_max'] : null, 'idleS' => $r['idle_s'] !== null ? (int) $r['idle_s'] : null, 'lastTs' => $r['ts'], 'isStuck' => $r['event_type'] === 'stuck', ], $status), 'newEvents' => $newEvents, 'maxId' => $maxId, ]);