$classId, ':t' => $teacherId] ); } function student_owned(int $studentId, int $teacherId): bool { return (bool) v2_db_one(" SELECT s.id FROM students s JOIN classes c ON c.id = s.class_id WHERE s.id = :sid AND c.teacher_id = :tid ", [':sid' => $studentId, ':tid' => $teacherId]); } // ── GET ─────────────────────────────────────────────────────────── if ($method === 'GET') { $classId = (int) ($_GET['classId'] ?? 0); if (!$classId) v2_response_error(400, 'missing_classId', 'classId fehlt.'); if (!class_owned($classId, $teacherId)) v2_response_error(403, 'forbidden', 'Klasse nicht deine.'); $rows = v2_db_all(" SELECT s.id, s.username, s.display_name, s.avatar_slug, s.first_name, s.last_name, s.easy_language, s.high_contrast, s.reduced_motion, s.screen_reader, s.nachteilsausgleich, s.last_login FROM students s WHERE s.class_id = :cid AND s.deleted_at IS NULL ORDER BY s.display_name, s.username ", [':cid' => $classId]); v2_response_ok(['students' => array_map(fn($r) => [ 'id' => (int) $r['id'], 'username' => $r['username'], 'displayName' => $r['display_name'] ?? $r['username'], 'firstName' => $r['first_name'], 'lastName' => $r['last_name'], 'avatarSlug' => $r['avatar_slug'] ?? 'fuchs-explorer', 'easyLanguage' => (bool) $r['easy_language'], 'highContrast' => (bool) $r['high_contrast'], 'reducedMotion' => (bool) $r['reduced_motion'], 'screenReader' => (bool) $r['screen_reader'], 'nachteilsausgleich' => (bool) $r['nachteilsausgleich'], 'lastLogin' => $r['last_login'], ], $rows)]); } // ── POST: neue Schüler*in anlegen ──────────────────────────────── if ($method === 'POST') { $body = v2_request_body_json_required(); v2_validate_required($body, ['classId', 'username', 'password']); $classId = (int) $body['classId']; if (!class_owned($classId, $teacherId)) v2_response_error(403, 'forbidden', 'Klasse nicht deine.'); $username = trim((string) $body['username']); $password = (string) $body['password']; if (strlen($username) < 2) v2_response_error(400, 'username_too_short', 'Username muss mind. 2 Zeichen haben.'); if (strlen($password) < 4) v2_response_error(400, 'password_too_short', 'Passwort muss mind. 4 Zeichen haben.'); // Username-Uniqueness IN dieser Klasse $existing = v2_db_one( 'SELECT id FROM students WHERE class_id = :c AND username = :u AND deleted_at IS NULL', [':c' => $classId, ':u' => $username] ); if ($existing) v2_response_error(409, 'username_taken', "Username '$username' ist in dieser Klasse schon vergeben."); $displayName = trim((string) ($body['displayName'] ?? '')) ?: $username; $firstName = trim((string) ($body['firstName'] ?? '')) ?: null; $lastName = trim((string) ($body['lastName'] ?? '')) ?: null; $avatarSlug = trim((string) ($body['avatarSlug'] ?? '')) ?: 'fuchs-explorer'; $easyLang = !empty($body['easyLanguage']) ? 1 : 0; $nachteil = !empty($body['nachteilsausgleich']) ? 1 : 0; $hash = password_hash($password, PASSWORD_DEFAULT); v2_db_exec( 'INSERT INTO students (class_id, username, password, display_name, first_name, last_name, avatar_slug, easy_language, nachteilsausgleich, is_anonymous) VALUES (:cid, :u, :p, :dn, :fn, :ln, :av, :ez, :na, 0)', [ ':cid' => $classId, ':u' => $username, ':p' => $hash, ':dn' => $displayName, ':fn' => $firstName, ':ln' => $lastName, ':av' => $avatarSlug, ':ez' => $easyLang, ':na' => $nachteil, ] ); v2_response_created([ 'id' => (int) v2_db_insert_id(), 'username' => $username, 'displayName' => $displayName, ]); } // ── PATCH: Flag/Property updaten ───────────────────────────────── if ($method === 'PATCH') { $body = v2_request_body_json_required(); v2_validate_required($body, ['studentId']); $studentId = (int) $body['studentId']; if (!student_owned($studentId, $teacherId)) v2_response_error(403, 'forbidden', 'Schüler*in nicht in deiner Klasse.'); $updates = []; $params = [':id' => $studentId]; foreach (['nachteilsausgleich', 'easyLanguage', 'highContrast', 'reducedMotion', 'screenReader'] as $k) { if (array_key_exists($k, $body)) { $col = strtolower(preg_replace('/([A-Z])/', '_$1', $k)); $updates[] = "`$col` = :$col"; $params[":$col"] = $body[$k] ? 1 : 0; } } // Optional auch Anzeigename/Avatar updatable foreach (['displayName' => 'display_name', 'avatarSlug' => 'avatar_slug', 'firstName' => 'first_name', 'lastName' => 'last_name'] as $k => $col) { if (array_key_exists($k, $body)) { $updates[] = "`$col` = :$k"; $params[":$k"] = $body[$k]; } } if (empty($updates)) v2_response_error(400, 'no_changes', 'Keine Felder zum Aktualisieren.'); v2_db_exec("UPDATE students SET " . implode(', ', $updates) . " WHERE id = :id", $params); v2_response_ok(['ok' => true]); } // ── DELETE: Soft-Delete ────────────────────────────────────────── $studentId = (int) ($_GET['studentId'] ?? 0); if (!$studentId) v2_response_error(400, 'missing_studentId', 'studentId fehlt.'); if (!student_owned($studentId, $teacherId)) v2_response_error(403, 'forbidden', 'Schüler*in nicht in deiner Klasse.'); v2_db_exec('UPDATE students SET deleted_at = NOW() WHERE id = :id', [':id' => $studentId]); v2_response_ok(['ok' => true]);