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); json_success(['questionnaires' => $rows]); break; case 'POST': require_role(['admin', 'supervisor'], $tokenRec); $body = read_json_body(); if (($body['action'] ?? '') === 'importBundle') { $bundle = $body['bundle'] ?? null; if (!is_array($bundle)) { json_error('MISSING_FIELDS', 'bundle object is required', 400); } $replaceIfExists = !empty($body['replaceIfExists']); [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $pdo->exec('PRAGMA foreign_keys = OFF;'); $result = qdb_import_questionnaires_bundle($pdo, $bundle, $replaceIfExists); $pdo->exec('PRAGMA foreign_keys = ON;'); qdb_save($tmpDb, $lockFp); json_success($result); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); error_log('importBundle: ' . $e->getMessage()); json_error('SERVER_ERROR', $e->getMessage(), 500); } break; } if (empty($body['name'])) { json_error('NAME_REQUIRED', 'name is required', 400); } $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); json_success(['questionnaire' => [ 'questionnaireID' => $id, 'name' => $name, 'version' => $version, 'state' => $state, 'orderIndex' => $order, 'showPoints' => $showPts, 'conditionJson' => $condJson, 'questionCount' => 0, ]]); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); error_log($e->getMessage()); json_error('SERVER_ERROR', 'Server error', 500); } break; case 'PUT': require_role(['admin', 'supervisor'], $tokenRec); $body = read_json_body(); if (empty($body['questionnaireID'])) { json_error('QUESTIONNAIRE_ID_REQUIRED', 'questionnaireID is required', 400); } $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); json_error('NOT_FOUND', 'Questionnaire not found', 404); } $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); json_success(['questionnaire' => [ 'questionnaireID' => $id, 'name' => $name, 'version' => $version, 'state' => $state, 'orderIndex' => $order, 'showPoints' => $showPts, 'conditionJson' => $condJson, ]]); } catch (Throwable $e) { qdb_discard($tmpDb, $lockFp); error_log($e->getMessage()); json_error('SERVER_ERROR', 'Server error', 500); } break; case 'DELETE': require_role(['admin'], $tokenRec); $body = read_json_body(); if (empty($body['questionnaireID'])) { json_error('QUESTIONNAIRE_ID_REQUIRED', 'questionnaireID is required', 400); } $id = $body['questionnaireID']; [$pdo, $tmpDb, $lockFp] = qdb_open(true); try { $pdo->exec('PRAGMA foreign_keys = OFF;'); $pdo->beginTransaction(); $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); json_success([]); } catch (Throwable $e) { if ($pdo->inTransaction()) { $pdo->rollBack(); } qdb_discard($tmpDb, $lockFp); error_log($e->getMessage()); json_error('SERVER_ERROR', 'Server error', 500); } break; default: json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405); }