"questionID query param required"]); exit; } [$pdo, $tmpDb, $lockFp] = qdb_open(false); $stmt = $pdo->prepare(" SELECT answerOptionID, questionID, defaultText, points, orderIndex, nextQuestionId FROM answer_option WHERE questionID = :qid ORDER BY orderIndex "); $stmt->execute([':qid' => $qID]); $options = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($options as &$o) { $o['points'] = (int)$o['points']; $o['orderIndex'] = (int)$o['orderIndex']; $tr = $pdo->prepare("SELECT languageCode, text FROM answer_option_translation WHERE answerOptionID = :id"); $tr->execute([':id' => $o['answerOptionID']]); $o['translations'] = $tr->fetchAll(PDO::FETCH_ASSOC); } unset($o); qdb_discard($tmpDb, $lockFp); echo json_encode(["success" => true, "answerOptions" => $options]); break; case 'POST': require_role(['admin', 'supervisor'], $tokenRec); $body = json_decode(file_get_contents('php://input'), true); if (!$body || empty($body['questionID']) || !isset($body['defaultText'])) { http_response_code(400); echo json_encode(["error" => "questionID and defaultText required"]); exit; } $id = bin2hex(random_bytes(16)); $qID = $body['questionID']; $text = trim($body['defaultText']); $points = (int)($body['points'] ?? 0); $order = (int)($body['orderIndex'] ?? 0); $nextQ = trim($body['nextQuestionId'] ?? ''); [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $chk = $pdo->prepare("SELECT 1 FROM question WHERE questionID = :id"); $chk->execute([':id' => $qID]); if (!$chk->fetch()) { qdb_discard($tmpDb, $lockFp); http_response_code(404); echo json_encode(["error" => "Question not found"]); exit; } if ($order === 0) { $max = $pdo->prepare("SELECT COALESCE(MAX(orderIndex),0)+1 FROM answer_option WHERE questionID = :id"); $max->execute([':id' => $qID]); $order = (int)$max->fetchColumn(); } $pdo->prepare("INSERT INTO answer_option (answerOptionID, questionID, defaultText, points, orderIndex, nextQuestionId) VALUES (:id, :qid, :t, :p, :o, :nq)") ->execute([':id' => $id, ':qid' => $qID, ':t' => $text, ':p' => $points, ':o' => $order, ':nq' => $nextQ]); qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true, "answerOption" => [ "answerOptionID" => $id, "questionID" => $qID, "defaultText" => $text, "points" => $points, "orderIndex" => $order, "nextQuestionId" => $nextQ, "translations" => [] ]]); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); http_response_code(500); error_log($e->getMessage()); echo json_encode(["error" => "Server error"]); } break; case 'PUT': require_role(['admin', 'supervisor'], $tokenRec); $body = json_decode(file_get_contents('php://input'), true); if (!$body || empty($body['answerOptionID'])) { http_response_code(400); echo json_encode(["error" => "answerOptionID is required"]); exit; } $id = $body['answerOptionID']; [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $existing = $pdo->prepare("SELECT * FROM answer_option WHERE answerOptionID = :id"); $existing->execute([':id' => $id]); $row = $existing->fetch(PDO::FETCH_ASSOC); if (!$row) { qdb_discard($tmpDb, $lockFp); http_response_code(404); echo json_encode(["error" => "Answer option not found"]); exit; } $text = trim($body['defaultText'] ?? $row['defaultText']); $points = (int)($body['points'] ?? $row['points']); $order = (int)($body['orderIndex'] ?? $row['orderIndex']); $nextQ = trim($body['nextQuestionId'] ?? $row['nextQuestionId']); $pdo->prepare("UPDATE answer_option SET defaultText = :t, points = :p, orderIndex = :o, nextQuestionId = :nq WHERE answerOptionID = :id") ->execute([':t' => $text, ':p' => $points, ':o' => $order, ':nq' => $nextQ, ':id' => $id]); qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true, "answerOption" => [ "answerOptionID" => $id, "questionID" => $row['questionID'], "defaultText" => $text, "points" => $points, "orderIndex" => $order, "nextQuestionId" => $nextQ ]]); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); http_response_code(500); error_log($e->getMessage()); echo json_encode(["error" => "Server error"]); } break; case 'DELETE': require_role(['admin', 'supervisor'], $tokenRec); $body = json_decode(file_get_contents('php://input'), true); if (!$body || empty($body['answerOptionID'])) { http_response_code(400); echo json_encode(["error" => "answerOptionID is required"]); exit; } $id = $body['answerOptionID']; [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $pdo->exec("PRAGMA foreign_keys = OFF;"); $pdo->beginTransaction(); $pdo->prepare("DELETE FROM answer_option_translation WHERE answerOptionID = :id")->execute([':id' => $id]); $pdo->prepare("UPDATE client_answer SET answerOptionID = NULL WHERE answerOptionID = :id")->execute([':id' => $id]); $pdo->prepare("DELETE FROM answer_option WHERE answerOptionID = :id")->execute([':id' => $id]); $pdo->commit(); $pdo->exec("PRAGMA foreign_keys = ON;"); qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true]); } catch (Throwable $e) { if ($pdo->inTransaction()) $pdo->rollBack(); qdb_discard($tmpDb, $lockFp); http_response_code(500); error_log($e->getMessage()); echo json_encode(["error" => "Server error"]); } break; case 'PATCH': require_role(['admin', 'supervisor'], $tokenRec); $body = json_decode(file_get_contents('php://input'), true); if (!$body || empty($body['questionID']) || !is_array($body['order'] ?? null)) { http_response_code(400); echo json_encode(["error" => "questionID and order[] required"]); exit; } [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $stmt = $pdo->prepare("UPDATE answer_option SET orderIndex = :o WHERE answerOptionID = :id AND questionID = :qid"); foreach ($body['order'] as $idx => $aoid) { $stmt->execute([':o' => $idx, ':id' => $aoid, ':qid' => $body['questionID']]); } qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true]); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); http_response_code(500); error_log($e->getMessage()); echo json_encode(["error" => "Server error"]); } break; default: http_response_code(405); echo json_encode(["error" => "Method not allowed"]); }