1e51ef7def
- 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>
77 lines
2.7 KiB
PHP
77 lines
2.7 KiB
PHP
<?php
|
|
/**
|
|
* GET /api/teacher/live-status?classId=42&sinceId=0
|
|
*
|
|
* Polling-Variante des Live-Dashboards. Liefert pro Schüler*in den
|
|
* neuesten Telemetry-Stand der letzten 5 Min plus alle Events
|
|
* mit id > 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,
|
|
]);
|