setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // Init Tabellen $pdo->exec(" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, salt TEXT NOT NULL, name TEXT NOT NULL, role TEXT DEFAULT 'student', xp INTEGER DEFAULT 0, level INTEGER DEFAULT 1, streak_days INTEGER DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS chapter_progress ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, chapter_id TEXT NOT NULL, code_draft TEXT, subgoals_json TEXT, is_solved INTEGER DEFAULT 0, solved_at TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(user_id, chapter_id) ); CREATE TABLE IF NOT EXISTS classrooms ( id INTEGER PRIMARY KEY AUTOINCREMENT, teacher_id INTEGER NOT NULL, name TEXT NOT NULL, invite_code TEXT UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS class_enrollments ( id INTEGER PRIMARY KEY AUTOINCREMENT, classroom_id INTEGER NOT NULL, user_id INTEGER NOT NULL, joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE(classroom_id, user_id) ); "); $path = $_SERVER['PATH_INFO'] ?? $_GET['action'] ?? ''; $body = json_decode(file_get_contents('php://input'), true) ?? []; // Helper Auth function get_auth_user($pdo) { $headers = getallheaders(); $auth = $headers['Authorization'] ?? $headers['authorization'] ?? ''; if (strpos($auth, 'Bearer ') === 0) { $token = substr($auth, 7); $parts = explode('.', $token); if (count($parts) === 2) { $payload = json_decode(base64_decode($parts[0]), true); if ($payload && isset($payload['uid'])) { return $payload; } } } return null; } function create_token($uid, $email, $role) { $payload = base64_encode(json_encode(['uid' => $uid, 'email' => $email, 'role' => $role])); $sig = hash_hmac('sha256', $payload, 'python_lernportal_secret'); return $payload . '.' . $sig; } // 1. REGISTER if ($path === '/auth/register') { $email = strtolower(trim($body['email'] ?? '')); $password = $body['password'] ?? ''; $name = trim($body['name'] ?? ''); $role = $body['role'] ?? 'student'; if (!$email || !$password || !$name) { http_response_code(400); echo json_encode(['error' => 'Pflichtfelder fehlen']); exit; } $salt = bin2hex(random_bytes(16)); $hash = hash_pbkdf2('sha256', $password, $salt, 100000); try { $stmt = $pdo->prepare("INSERT INTO users (email, password_hash, salt, name, role) VALUES (?, ?, ?, ?, ?)"); $stmt->execute([$email, $hash, $salt, $name, $role]); $uid = $pdo->lastInsertId(); $token = create_token($uid, $email, $role); http_response_code(201); echo json_encode(['token' => $token, 'user' => ['id' => $uid, 'email' => $email, 'name' => $name, 'role' => $role, 'xp' => 0, 'level' => 1]]); } catch (Exception $e) { http_response_code(409); echo json_encode(['error' => 'E-Mail existiert bereits']); } exit; } // 2. LOGIN if ($path === '/auth/login') { $email = strtolower(trim($body['email'] ?? '')); $password = $body['password'] ?? ''; $stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?"); $stmt->execute([$email]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if ($user) { $hash = hash_pbkdf2('sha256', $password, $user['salt'], 100000); if (hash_equals($hash, $user['password_hash'])) { $token = create_token($user['id'], $user['email'], $user['role']); echo json_encode([ 'token' => $token, 'user' => [ 'id' => $user['id'], 'email' => $user['email'], 'name' => $user['name'], 'role' => $user['role'], 'xp' => (int)$user['xp'], 'level' => (int)$user['level'] ] ]); exit; } } http_response_code(401); echo json_encode(['error' => 'Ungültige Anmeldedaten']); exit; } // 3. PROGRESS SAVE if ($path === '/progress/save') { $user = get_auth_user($pdo); if (!$user) { http_response_code(401); echo json_encode(['error' => 'Nicht angemeldet']); exit; } $chapter_id = $body['chapter_id'] ?? ''; $code_draft = $body['code_draft'] ?? ''; $stmt = $pdo->prepare("INSERT INTO chapter_progress (user_id, chapter_id, code_draft) VALUES (?, ?, ?) ON CONFLICT(user_id, chapter_id) DO UPDATE SET code_draft = excluded.code_draft, updated_at = CURRENT_TIMESTAMP"); $stmt->execute([$user['uid'], $chapter_id, $code_draft]); echo json_encode(['status' => 'saved']); exit; } // 4. PROGRESS SOLVE if ($path === '/progress/solve') { $user = get_auth_user($pdo); if (!$user) { http_response_code(401); echo json_encode(['error' => 'Nicht angemeldet']); exit; } $chapter_id = $body['chapter_id'] ?? ''; $xp_reward = (int)($body['xp_reward'] ?? 100); $stmt = $pdo->prepare("INSERT INTO chapter_progress (user_id, chapter_id, is_solved, solved_at) VALUES (?, ?, 1, CURRENT_TIMESTAMP) ON CONFLICT(user_id, chapter_id) DO UPDATE SET is_solved = 1, solved_at = CURRENT_TIMESTAMP"); $stmt->execute([$user['uid'], $chapter_id]); $stmt = $pdo->prepare("UPDATE users SET xp = xp + ?, level = ((xp + ?) / 100) + 1 WHERE id = ?"); $stmt->execute([$xp_reward, $xp_reward, $user['uid']]); echo json_encode(['status' => 'solved', 'earned_xp' => $xp_reward]); exit; } http_response_code(404); echo json_encode(['error' => 'Endpunkt nicht gefunden']);