query(" SELECT q.questionnaireID, q.name, q.version, q.state, q.orderIndex, q.showPoints, q.conditionJson, COUNT(qu.questionID) AS questionCount FROM questionnaire q LEFT JOIN question qu ON qu.questionnaireID = q.questionnaireID GROUP BY q.questionnaireID ORDER BY q.orderIndex, q.name ")->fetchAll(PDO::FETCH_ASSOC); foreach ($rows as &$r) { $r['orderIndex'] = (int)$r['orderIndex']; $r['showPoints'] = (int)$r['showPoints']; $r['conditionJson'] = $r['conditionJson'] ?: '{}'; } unset($r); qdb_discard($tmpDb, $lockFp); echo json_encode(["success" => true, "questionnaires" => $rows]); break; case 'POST': require_role(['admin', 'supervisor'], $tokenRec); $body = json_decode(file_get_contents('php://input'), true); if (!$body || empty($body['name'])) { http_response_code(400); echo json_encode(["error" => "name is required"]); exit; } $id = bin2hex(random_bytes(16)); $name = trim($body['name']); $version = trim($body['version'] ?? ''); $state = trim($body['state'] ?? 'draft'); $order = (int)($body['orderIndex'] ?? 0); $showPts = (int)($body['showPoints'] ?? 0); $condJson = $body['conditionJson'] ?? '{}'; [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { if ($order === 0) { $order = (int)$pdo->query("SELECT COALESCE(MAX(orderIndex),0)+1 FROM questionnaire")->fetchColumn(); } $pdo->prepare("INSERT INTO questionnaire (questionnaireID, name, version, state, orderIndex, showPoints, conditionJson) VALUES (:id, :n, :v, :s, :o, :sp, :cj)") ->execute([':id' => $id, ':n' => $name, ':v' => $version, ':s' => $state, ':o' => $order, ':sp' => $showPts, ':cj' => $condJson]); qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true, "questionnaire" => [ "questionnaireID" => $id, "name" => $name, "version" => $version, "state" => $state, "orderIndex" => $order, "showPoints" => $showPts, "conditionJson" => $condJson, "questionCount" => 0 ]]); } 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['questionnaireID'])) { http_response_code(400); echo json_encode(["error" => "questionnaireID is required"]); exit; } $id = $body['questionnaireID']; [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $existing = $pdo->prepare("SELECT * FROM questionnaire WHERE questionnaireID = :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" => "Questionnaire not found"]); exit; } $name = trim($body['name'] ?? $row['name']); $version = trim($body['version'] ?? $row['version']); $state = trim($body['state'] ?? $row['state']); $order = (int)($body['orderIndex'] ?? $row['orderIndex']); $showPts = (int)($body['showPoints'] ?? $row['showPoints']); $condJson = $body['conditionJson'] ?? $row['conditionJson']; $pdo->prepare("UPDATE questionnaire SET name = :n, version = :v, state = :s, orderIndex = :o, showPoints = :sp, conditionJson = :cj WHERE questionnaireID = :id") ->execute([':n' => $name, ':v' => $version, ':s' => $state, ':o' => $order, ':sp' => $showPts, ':cj' => $condJson, ':id' => $id]); qdb_save($tmpDb, $lockFp); echo json_encode(["success" => true, "questionnaire" => [ "questionnaireID" => $id, "name" => $name, "version" => $version, "state" => $state, "orderIndex" => $order, "showPoints" => $showPts, "conditionJson" => $condJson ]]); } 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'], $tokenRec); $body = json_decode(file_get_contents('php://input'), true); if (!$body || empty($body['questionnaireID'])) { http_response_code(400); echo json_encode(["error" => "questionnaireID is required"]); exit; } $id = $body['questionnaireID']; [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $pdo->exec("PRAGMA foreign_keys = OFF;"); $pdo->beginTransaction(); // Get question IDs to cascade-delete answer options and translations $qIds = $pdo->prepare("SELECT questionID FROM question WHERE questionnaireID = :id"); $qIds->execute([':id' => $id]); $questionIDs = $qIds->fetchAll(PDO::FETCH_COLUMN); foreach ($questionIDs as $qid) { $aoIds = $pdo->prepare("SELECT answerOptionID FROM answer_option WHERE questionID = :qid"); $aoIds->execute([':qid' => $qid]); $optionIDs = $aoIds->fetchAll(PDO::FETCH_COLUMN); foreach ($optionIDs as $aoid) { $pdo->prepare("DELETE FROM answer_option_translation WHERE answerOptionID = :id")->execute([':id' => $aoid]); } $pdo->prepare("DELETE FROM answer_option WHERE questionID = :qid")->execute([':qid' => $qid]); $pdo->prepare("DELETE FROM question_translation WHERE questionID = :qid")->execute([':qid' => $qid]); $pdo->prepare("DELETE FROM client_answer WHERE questionID = :qid")->execute([':qid' => $qid]); } $pdo->prepare("DELETE FROM question WHERE questionnaireID = :id")->execute([':id' => $id]); $pdo->prepare("DELETE FROM completed_questionnaire WHERE questionnaireID = :id")->execute([':id' => $id]); $pdo->prepare("DELETE FROM questionnaire WHERE questionnaireID = :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; default: http_response_code(405); echo json_encode(["error" => "Method not allowed"]); }