feat: quiz, cheat sheets, admin modules - MC quiz, search, user mgmt, content import
Dieser Commit ist enthalten in:
Ursprung
03778e4bed
Commit
3cd865ccbc
6 geänderte Dateien mit 425 neuen und 0 gelöschten Zeilen
51
edu/api/admin.php
Normale Datei
51
edu/api/admin.php
Normale Datei
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/import.php';
|
||||
$admin_action = $segments[1] ?? '';
|
||||
|
||||
// GET /api/admin/users
|
||||
if (get_method() === 'GET' && $admin_action === 'users') {
|
||||
require_admin();
|
||||
$stmt = $pdo->query("SELECT id, username, display_name, is_admin, created_at FROM users ORDER BY id");
|
||||
json_ok(['users' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
// POST /api/admin/users
|
||||
if (get_method() === 'POST' && $admin_action === 'users') {
|
||||
require_admin();
|
||||
$body = get_json_body();
|
||||
$username = trim($body['username'] ?? '');
|
||||
$display_name = trim($body['display_name'] ?? '');
|
||||
$password = $body['password'] ?? '';
|
||||
$is_admin = !empty($body['is_admin']);
|
||||
|
||||
if (!$username || !$password || !$display_name) json_error('Benutzername, Anzeigename und Passwort erforderlich');
|
||||
if (mb_strlen($password) < 6) json_error('Passwort muss mindestens 6 Zeichen haben');
|
||||
|
||||
$hash = password_hash($password, PASSWORD_DEFAULT);
|
||||
try {
|
||||
$stmt = $pdo->prepare("INSERT INTO users (username, password_hash, display_name, is_admin) VALUES (:u, :h, :d, :a) RETURNING id");
|
||||
$stmt->execute([':u' => $username, ':h' => $hash, ':d' => $display_name, ':a' => $is_admin]);
|
||||
json_ok(['id' => $stmt->fetchColumn(), 'message' => 'Benutzer erstellt']);
|
||||
} catch (PDOException $e) {
|
||||
if (strpos($e->getMessage(), 'unique') !== false) json_error('Benutzername existiert bereits');
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/admin/users/{id}
|
||||
if (get_method() === 'DELETE' && $admin_action === 'users' && !empty($segments[2])) {
|
||||
require_admin();
|
||||
$del_id = (int) $segments[2];
|
||||
if ($del_id === $_SESSION['user_id']) json_error('Eigenen Account kann man nicht loeschen');
|
||||
$pdo->prepare("DELETE FROM users WHERE id = :id")->execute([':id' => $del_id]);
|
||||
json_ok(['ok' => true]);
|
||||
}
|
||||
|
||||
// POST /api/admin/import
|
||||
if (get_method() === 'POST' && $admin_action === 'import') {
|
||||
require_admin();
|
||||
$stats = import_all_content($pdo);
|
||||
json_ok(['message' => 'Import abgeschlossen', 'stats' => $stats]);
|
||||
}
|
||||
|
||||
json_error('Unbekannter Admin-Endpunkt', 404);
|
||||
28
edu/api/cheatsheets.php
Normale Datei
28
edu/api/cheatsheets.php
Normale Datei
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
require_auth();
|
||||
|
||||
// GET /api/cheatsheets
|
||||
if (get_method() === 'GET' && empty($segments[1])) {
|
||||
$stmt = $pdo->query("SELECT id, slug, title, category FROM cheatsheets ORDER BY category, title");
|
||||
json_ok(['cheatsheets' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
// GET /api/cheatsheets/search?q=...
|
||||
if (get_method() === 'GET' && ($segments[1] ?? '') === 'search') {
|
||||
$q = get_param('q', '');
|
||||
if (mb_strlen($q) < 2) json_error('Mindestens 2 Zeichen');
|
||||
$stmt = $pdo->prepare("SELECT id, slug, title, category FROM cheatsheets WHERE content_md ILIKE :q OR title ILIKE :q ORDER BY title");
|
||||
$stmt->execute([':q' => '%' . $q . '%']);
|
||||
json_ok(['cheatsheets' => $stmt->fetchAll(), 'query' => $q]);
|
||||
}
|
||||
|
||||
// GET /api/cheatsheets/{slug}
|
||||
if (get_method() === 'GET' && !empty($segments[1])) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM cheatsheets WHERE slug = :slug");
|
||||
$stmt->execute([':slug' => $segments[1]]);
|
||||
$sheet = $stmt->fetch();
|
||||
if (!$sheet) json_error('Cheat Sheet nicht gefunden', 404);
|
||||
json_ok(['cheatsheet' => $sheet]);
|
||||
}
|
||||
|
||||
json_error('Unbekannter Cheatsheet-Endpunkt', 404);
|
||||
77
edu/api/quiz.php
Normale Datei
77
edu/api/quiz.php
Normale Datei
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
$user_id = require_auth();
|
||||
|
||||
// GET /api/quiz/generate?deck={slug}&count=10
|
||||
if (get_method() === 'GET' && ($segments[1] ?? '') === 'generate') {
|
||||
$slug = get_param('deck', '');
|
||||
$count = max(1, min(50, (int) get_param('count', 10)));
|
||||
|
||||
$query = "SELECT c.id, c.question, c.answer, c.level, d.slug AS deck_slug, d.name AS deck_name
|
||||
FROM cards c JOIN decks d ON d.id = c.deck_id";
|
||||
$params = [];
|
||||
if ($slug) {
|
||||
$query .= " WHERE d.slug = :slug";
|
||||
$params[':slug'] = $slug;
|
||||
}
|
||||
$query .= " ORDER BY RANDOM() LIMIT " . $count;
|
||||
|
||||
$stmt = $pdo->prepare($query);
|
||||
$stmt->execute($params);
|
||||
$cards = $stmt->fetchAll();
|
||||
|
||||
$all_answers = $pdo->query("SELECT DISTINCT answer FROM cards ORDER BY RANDOM() LIMIT 200")->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
$questions = [];
|
||||
foreach ($cards as $card) {
|
||||
$wrong = [];
|
||||
$shuffled = $all_answers;
|
||||
shuffle($shuffled);
|
||||
foreach ($shuffled as $a) {
|
||||
if ($a !== $card['answer'] && count($wrong) < 3) {
|
||||
$wrong[] = mb_substr($a, 0, 120) . (mb_strlen($a) > 120 ? '...' : '');
|
||||
}
|
||||
}
|
||||
|
||||
$correct_short = mb_substr($card['answer'], 0, 120) . (mb_strlen($card['answer']) > 120 ? '...' : '');
|
||||
$options = $wrong;
|
||||
$options[] = $correct_short;
|
||||
shuffle($options);
|
||||
|
||||
$questions[] = [
|
||||
'card_id' => $card['id'],
|
||||
'question' => $card['question'],
|
||||
'options' => $options,
|
||||
'correct_index' => array_search($correct_short, $options),
|
||||
'full_answer' => $card['answer'],
|
||||
'deck_name' => $card['deck_name']
|
||||
];
|
||||
}
|
||||
json_ok(['questions' => $questions, 'deck' => $slug ?: 'all']);
|
||||
}
|
||||
|
||||
// POST /api/quiz/submit
|
||||
if (get_method() === 'POST' && ($segments[1] ?? '') === 'submit') {
|
||||
$body = get_json_body();
|
||||
$total = (int) ($body['total'] ?? 0);
|
||||
$correct = (int) ($body['correct'] ?? 0);
|
||||
$score = $total > 0 ? round(($correct / $total) * 100) : 0;
|
||||
|
||||
$stmt = $pdo->prepare("
|
||||
INSERT INTO quiz_results (user_id, deck_slug, quiz_type, score_percent, total_questions, correct_answers)
|
||||
VALUES (:uid, :deck, 'multiple_choice', :score, :total, :correct)
|
||||
");
|
||||
$stmt->execute([':uid' => $user_id, ':deck' => $body['deck'] ?? 'all', ':score' => $score, ':total' => $total, ':correct' => $correct]);
|
||||
json_ok(['score' => $score]);
|
||||
}
|
||||
|
||||
// GET /api/quiz/history
|
||||
if (get_method() === 'GET' && ($segments[1] ?? '') === 'history') {
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT deck_slug, quiz_type, score_percent, total_questions, correct_answers, created_at
|
||||
FROM quiz_results WHERE user_id = :uid ORDER BY created_at DESC LIMIT 20
|
||||
");
|
||||
$stmt->execute([':uid' => $user_id]);
|
||||
json_ok(['results' => $stmt->fetchAll()]);
|
||||
}
|
||||
|
||||
json_error('Unbekannter Quiz-Endpunkt', 404);
|
||||
Laden …
Tabelle hinzufügen
Einen Link hinzufügen
In neuem Issue referenzieren