further enhanced error handling + added retries in case of parallel writes
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -49,7 +49,7 @@ Thumbs.db
|
|||||||
# Misc
|
# Misc
|
||||||
*.orig
|
*.orig
|
||||||
*.rej
|
*.rej
|
||||||
|
*.md
|
||||||
|
|
||||||
# Ignore browser build artifacts (if any)
|
# Ignore browser build artifacts (if any)
|
||||||
website/js/dist/
|
website/js/dist/
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
require_once __DIR__ . '/../common.php';
|
require_once __DIR__ . '/../common.php';
|
||||||
require_once __DIR__ . '/../db_init.php';
|
require_once __DIR__ . '/../db_init.php';
|
||||||
require_once __DIR__ . '/../lib/response.php';
|
require_once __DIR__ . '/../lib/response.php';
|
||||||
|
require_once __DIR__ . '/../lib/errors.php';
|
||||||
require_once __DIR__ . '/../lib/validate.php';
|
require_once __DIR__ . '/../lib/validate.php';
|
||||||
require_once __DIR__ . '/../lib/api_log.php';
|
require_once __DIR__ . '/../lib/api_log.php';
|
||||||
|
|
||||||
@ -71,7 +72,6 @@ try {
|
|||||||
require $handlerFile;
|
require $handlerFile;
|
||||||
|
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_api_log_note_exception($e);
|
require_once __DIR__ . '/../lib/errors.php';
|
||||||
error_log("Unhandled error on $method $route: " . $e->getMessage());
|
qdb_emit_api_error($e, "API $method $route");
|
||||||
json_error('SERVER_ERROR', 'Internal server error', 500);
|
|
||||||
}
|
}
|
||||||
|
|||||||
59
db_init.php
59
db_init.php
@ -140,18 +140,37 @@ function qdb_apply_migrations(PDO $pdo, string $schemaSql): bool {
|
|||||||
return $changed;
|
return $changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function qdb_acquire_lock() {
|
/**
|
||||||
|
* Exclusive lock with short retries (non-blocking attempts). Avoids hung PHP workers
|
||||||
|
* when two writes overlap (app upload + website edit).
|
||||||
|
*/
|
||||||
|
function qdb_flock_exclusive_with_retry($lockFp, int $maxAttempts = 12, int $sleepMicros = 75000): void
|
||||||
|
{
|
||||||
|
for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
|
||||||
|
if (flock($lockFp, LOCK_EX | LOCK_NB)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($attempt < $maxAttempts - 1) {
|
||||||
|
usleep($sleepMicros);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception('Could not acquire lock');
|
||||||
|
}
|
||||||
|
|
||||||
|
function qdb_open_writable_lock()
|
||||||
|
{
|
||||||
$lockFp = fopen(QDB_LOCK, 'c');
|
$lockFp = fopen(QDB_LOCK, 'c');
|
||||||
if ($lockFp === false) {
|
if ($lockFp === false) {
|
||||||
throw new Exception('Could not open lock file');
|
throw new Exception('Could not open lock file');
|
||||||
}
|
}
|
||||||
if (!flock($lockFp, LOCK_EX)) {
|
qdb_flock_exclusive_with_retry($lockFp);
|
||||||
fclose($lockFp);
|
|
||||||
throw new Exception('Could not acquire lock');
|
|
||||||
}
|
|
||||||
return $lockFp;
|
return $lockFp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function qdb_acquire_lock() {
|
||||||
|
return qdb_open_writable_lock();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decrypt the master DB file into a temp SQLite file, or create a fresh one
|
* Decrypt the master DB file into a temp SQLite file, or create a fresh one
|
||||||
* from schema.sql if no DB exists yet.
|
* from schema.sql if no DB exists yet.
|
||||||
@ -171,12 +190,7 @@ function qdb_open(bool $writable = false): array {
|
|||||||
|
|
||||||
$lockFp = null;
|
$lockFp = null;
|
||||||
if ($writable) {
|
if ($writable) {
|
||||||
$lockFp = fopen(QDB_LOCK, 'c');
|
$lockFp = qdb_open_writable_lock();
|
||||||
if ($lockFp === false) throw new Exception("Could not open lock file");
|
|
||||||
if (!flock($lockFp, LOCK_EX)) {
|
|
||||||
fclose($lockFp);
|
|
||||||
throw new Exception("Could not acquire lock");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmpDb = tempnam(sys_get_temp_dir(), 'qdb_');
|
$tmpDb = tempnam(sys_get_temp_dir(), 'qdb_');
|
||||||
@ -289,33 +303,26 @@ function qdb_discard(string $tmpDb, $lockFp): void {
|
|||||||
*
|
*
|
||||||
* @return array{0: PDO, 1: string, 2: resource|null}|null
|
* @return array{0: PDO, 1: string, 2: resource|null}|null
|
||||||
*/
|
*/
|
||||||
function qdb_open_read_or_fail(): ?array {
|
/**
|
||||||
|
* @return array{0: PDO, 1: string, 2: resource|null}
|
||||||
|
*/
|
||||||
|
function qdb_open_read_or_fail(): array {
|
||||||
try {
|
try {
|
||||||
return qdb_open(false);
|
return qdb_open(false);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/lib/errors.php';
|
require_once __DIR__ . '/lib/errors.php';
|
||||||
error_log('qdb_open_read: ' . $e->getMessage());
|
qdb_emit_api_error($e, 'Database read');
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Database read'), 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a writable DB connection; on failure logs and returns a JSON error response.
|
* @return array{0: PDO, 1: string, 2: resource|null}
|
||||||
*
|
|
||||||
* @return array{0: PDO, 1: string, 2: resource|null}|null
|
|
||||||
*/
|
*/
|
||||||
function qdb_open_write_or_fail(): ?array {
|
function qdb_open_write_or_fail(): array {
|
||||||
try {
|
try {
|
||||||
return qdb_open(true);
|
return qdb_open(true);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/lib/errors.php';
|
require_once __DIR__ . '/lib/errors.php';
|
||||||
error_log('qdb_open_write: ' . $e->getMessage());
|
qdb_emit_api_error($e, 'Database write');
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Database write'), 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,15 +26,7 @@ case 'GET':
|
|||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
json_error('BAD_REQUEST', 'Unknown query', 400);
|
json_error('BAD_REQUEST', 'Unknown query', 400);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Analytics', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('analytics GET: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Analytics'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -50,15 +42,7 @@ case 'PUT':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success(['clientCode' => $clientCode, 'note' => $note]);
|
json_success(['clientCode' => $clientCode, 'note' => $note]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Save follow-up note', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('analytics PUT: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Save note'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -29,15 +29,7 @@ case 'GET':
|
|||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
json_success(['answerOptions' => $options]);
|
json_success(['answerOptions' => $options]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Load answer options', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('answer_options GET: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Load answer options'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -56,7 +48,7 @@ case 'POST':
|
|||||||
$order = (int)($body['orderIndex'] ?? 0);
|
$order = (int)($body['orderIndex'] ?? 0);
|
||||||
$nextQ = trim($body['nextQuestionId'] ?? '');
|
$nextQ = trim($body['nextQuestionId'] ?? '');
|
||||||
|
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$chk = $pdo->prepare('SELECT 1 FROM question WHERE questionID = :id');
|
$chk = $pdo->prepare('SELECT 1 FROM question WHERE questionID = :id');
|
||||||
$chk->execute([':id' => $qID]);
|
$chk->execute([':id' => $qID]);
|
||||||
@ -86,9 +78,7 @@ case 'POST':
|
|||||||
'translations' => [],
|
'translations' => [],
|
||||||
]]);
|
]]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Answer options', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -100,7 +90,7 @@ case 'PUT':
|
|||||||
}
|
}
|
||||||
$id = $body['answerOptionID'];
|
$id = $body['answerOptionID'];
|
||||||
|
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$existing = $pdo->prepare('SELECT * FROM answer_option WHERE answerOptionID = :id');
|
$existing = $pdo->prepare('SELECT * FROM answer_option WHERE answerOptionID = :id');
|
||||||
$existing->execute([':id' => $id]);
|
$existing->execute([':id' => $id]);
|
||||||
@ -140,9 +130,7 @@ case 'PUT':
|
|||||||
'nextQuestionId' => $nextQ,
|
'nextQuestionId' => $nextQ,
|
||||||
]]);
|
]]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Answer options', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -154,7 +142,7 @@ case 'DELETE':
|
|||||||
}
|
}
|
||||||
$id = $body['answerOptionID'];
|
$id = $body['answerOptionID'];
|
||||||
|
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$pdo->exec('PRAGMA foreign_keys = OFF;');
|
$pdo->exec('PRAGMA foreign_keys = OFF;');
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
@ -166,12 +154,7 @@ case 'DELETE':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success(['deleted' => true]);
|
json_success(['deleted' => true]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if ($pdo->inTransaction()) {
|
qdb_handler_fail($e, 'Delete answer option', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
$pdo->rollBack();
|
|
||||||
}
|
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -181,7 +164,7 @@ case 'PATCH':
|
|||||||
if (empty($body['questionID']) || !is_array($body['order'] ?? null)) {
|
if (empty($body['questionID']) || !is_array($body['order'] ?? null)) {
|
||||||
json_error('MISSING_FIELDS', 'questionID and order[] required', 400);
|
json_error('MISSING_FIELDS', 'questionID and order[] required', 400);
|
||||||
}
|
}
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare('UPDATE answer_option SET orderIndex = :o WHERE answerOptionID = :id AND questionID = :qid');
|
$stmt = $pdo->prepare('UPDATE answer_option SET orderIndex = :o WHERE answerOptionID = :id AND questionID = :qid');
|
||||||
foreach ($body['order'] as $idx => $aoid) {
|
foreach ($body['order'] as $idx => $aoid) {
|
||||||
@ -190,9 +173,7 @@ case 'PATCH':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success(['reordered' => true]);
|
json_success(['reordered' => true]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Answer options', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -254,18 +254,7 @@ if ($method === 'POST') {
|
|||||||
|
|
||||||
json_success(['submitted' => true, 'sumPoints' => $sumPoints]);
|
json_success(['submitted' => true, 'sumPoints' => $sumPoints]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($pdo) && $pdo->inTransaction()) {
|
qdb_handler_fail($e, 'Submit questionnaire', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
$pdo->rollBack();
|
|
||||||
}
|
|
||||||
if (isset($tmpDb, $lockFp)) {
|
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('app_questionnaires POST: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Submit'), 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ switch ($method) {
|
|||||||
|
|
||||||
case 'GET':
|
case 'GET':
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(false);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_read_or_fail();
|
||||||
|
|
||||||
if ($callerRole === 'admin') {
|
if ($callerRole === 'admin') {
|
||||||
$coaches = $pdo->query(
|
$coaches = $pdo->query(
|
||||||
@ -44,9 +44,7 @@ case 'GET':
|
|||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
json_success(['coaches' => $coaches, 'clients' => $clients]);
|
json_success(['coaches' => $coaches, 'clients' => $clients]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'assignments', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -61,7 +59,7 @@ case 'POST':
|
|||||||
if (!is_array($clientCodes)) $clientCodes = [$clientCodes];
|
if (!is_array($clientCodes)) $clientCodes = [$clientCodes];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
|
|
||||||
if ($callerRole === 'supervisor') {
|
if ($callerRole === 'supervisor') {
|
||||||
$chk = $pdo->prepare("SELECT coachID FROM coach WHERE coachID = :cid AND supervisorID = :sid");
|
$chk = $pdo->prepare("SELECT coachID FROM coach WHERE coachID = :cid AND supervisorID = :sid");
|
||||||
@ -92,10 +90,7 @@ case 'POST':
|
|||||||
|
|
||||||
json_success(['assigned' => $count]);
|
json_success(['assigned' => $count]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
|
qdb_handler_fail($e, 'assignments', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ case 'auth/login':
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(false);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_read_or_fail();
|
||||||
$stmt = $pdo->prepare(
|
$stmt = $pdo->prepare(
|
||||||
"SELECT userID, passwordHash, role, entityID, mustChangePassword
|
"SELECT userID, passwordHash, role, entityID, mustChangePassword
|
||||||
FROM users WHERE username = :u"
|
FROM users WHERE username = :u"
|
||||||
@ -80,9 +80,7 @@ case 'auth/login':
|
|||||||
}
|
}
|
||||||
json_success($loginData);
|
json_success($loginData);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'auth', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -117,7 +115,7 @@ case 'auth/change-password':
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
|
|
||||||
$stmt = $pdo->prepare(
|
$stmt = $pdo->prepare(
|
||||||
"SELECT userID, passwordHash, role, entityID FROM users WHERE username = :u"
|
"SELECT userID, passwordHash, role, entityID FROM users WHERE username = :u"
|
||||||
@ -165,9 +163,7 @@ case 'auth/change-password':
|
|||||||
'role' => $user['role'],
|
'role' => $user['role'],
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'auth', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ switch ($method) {
|
|||||||
|
|
||||||
case 'GET':
|
case 'GET':
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(false);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_read_or_fail();
|
||||||
|
|
||||||
$clientCode = trim((string)($_GET['clientCode'] ?? ''));
|
$clientCode = trim((string)($_GET['clientCode'] ?? ''));
|
||||||
if ($clientCode !== '' && !empty($_GET['detail'])) {
|
if ($clientCode !== '' && !empty($_GET['detail'])) {
|
||||||
@ -39,9 +39,7 @@ case 'GET':
|
|||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
json_success(['clients' => $clients]);
|
json_success(['clients' => $clients]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'clients', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -55,7 +53,7 @@ case 'POST':
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
|
|
||||||
// Validate coach exists and caller is allowed to assign to them
|
// Validate coach exists and caller is allowed to assign to them
|
||||||
if ($callerRole === 'supervisor') {
|
if ($callerRole === 'supervisor') {
|
||||||
@ -83,9 +81,7 @@ case 'POST':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success(['clientCode' => $clientCode, 'coachID' => $coachID]);
|
json_success(['clientCode' => $clientCode, 'coachID' => $coachID]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'clients', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -98,7 +94,7 @@ case 'DELETE':
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
|
|
||||||
// Verify the client exists and the caller can see it (RBAC)
|
// Verify the client exists and the caller can see it (RBAC)
|
||||||
[$clause, $params] = rbac_client_filter($tokenRec, 'cl');
|
[$clause, $params] = rbac_client_filter($tokenRec, 'cl');
|
||||||
@ -122,10 +118,7 @@ case 'DELETE':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success([]);
|
json_success([]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
|
qdb_handler_fail($e, 'clients', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -25,13 +25,5 @@ try {
|
|||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
json_success(['coaches' => $coaches]);
|
json_success(['coaches' => $coaches]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Load coaches', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('coaches GET: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Load coaches'), 500);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ $body = read_json_body();
|
|||||||
$action = trim((string)($body['action'] ?? 'import'));
|
$action = trim((string)($body['action'] ?? 'import'));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
|
|
||||||
if ($action === 'remove') {
|
if ($action === 'remove') {
|
||||||
$result = qdb_remove_dev_test_data($pdo, [
|
$result = qdb_remove_dev_test_data($pdo, [
|
||||||
@ -43,20 +43,11 @@ try {
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success($result);
|
json_success($result);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
$labels = [
|
$labels = [
|
||||||
'remove' => 'Remove',
|
'remove' => 'Remove dev data',
|
||||||
'wipeExceptAdmins' => 'Wipe',
|
'wipeExceptAdmins' => 'Wipe database',
|
||||||
'import' => 'Import',
|
'import' => 'Import dev fixture',
|
||||||
];
|
];
|
||||||
$label = $labels[$action ?? 'import'] ?? 'Operation';
|
$label = $labels[$action ?? 'import'] ?? 'Dev fixture';
|
||||||
error_log("dev fixture $label: " . $e->getMessage());
|
qdb_handler_fail($e, $label, $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
error_log("dev fixture $label: " . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, $label), 500);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,15 +22,7 @@ if (!empty($_GET['bundle'])) {
|
|||||||
echo json_encode($bundle, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
echo json_encode($bundle, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||||
exit;
|
exit;
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Export questionnaires bundle', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('export bundle: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Export'), 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,15 +45,7 @@ if (!empty($_GET['exportAll'])) {
|
|||||||
@unlink($zipPath);
|
@unlink($zipPath);
|
||||||
exit;
|
exit;
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Export responses ZIP', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('export exportAll: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Export'), 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,13 +96,5 @@ $filename = $safeName . '_v' . $questionnaire['version'] . '.csv';
|
|||||||
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
header('Content-Disposition: attachment; filename="' . $filename . '"');
|
||||||
echo qdb_export_rows_to_csv_string($rows, qdb_export_current_csv_fallback_header($resultColumns));
|
echo qdb_export_rows_to_csv_string($rows, qdb_export_current_csv_fallback_header($resultColumns));
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Export CSV', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('export csv: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Export'), 500);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,12 +12,7 @@ if (!$token) {
|
|||||||
try {
|
try {
|
||||||
token_revoke($token);
|
token_revoke($token);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
qdb_handler_fail($e, 'Logout');
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('logout: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Logout'), 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
json_success(['loggedOut' => true]);
|
json_success(['loggedOut' => true]);
|
||||||
|
|||||||
@ -40,15 +40,7 @@ case 'GET':
|
|||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
json_success(['questionnaires' => $rows, 'categoryKeys' => $categoryKeys]);
|
json_success(['questionnaires' => $rows, 'categoryKeys' => $categoryKeys]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Load questionnaires', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('questionnaires GET: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Load questionnaires'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -59,7 +51,7 @@ case 'POST':
|
|||||||
if (($body['action'] ?? '') === 'updateConditionMessageLabel') {
|
if (($body['action'] ?? '') === 'updateConditionMessageLabel') {
|
||||||
$messageKey = trim((string)($body['messageKey'] ?? ''));
|
$messageKey = trim((string)($body['messageKey'] ?? ''));
|
||||||
$germanLabel = (string)($body['germanLabel'] ?? '');
|
$germanLabel = (string)($body['germanLabel'] ?? '');
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
qdb_set_app_string_german_label($pdo, $messageKey, $germanLabel);
|
qdb_set_app_string_german_label($pdo, $messageKey, $germanLabel);
|
||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
@ -68,9 +60,7 @@ case 'POST':
|
|||||||
'germanLabel' => trim($germanLabel),
|
'germanLabel' => trim($germanLabel),
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Update condition message label', null, $tmpDb, $lockFp);
|
||||||
error_log('updateConditionMessageLabel: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -78,7 +68,7 @@ case 'POST':
|
|||||||
if (($body['action'] ?? '') === 'updateCategoryLabel') {
|
if (($body['action'] ?? '') === 'updateCategoryLabel') {
|
||||||
$categoryKey = trim((string)($body['categoryKey'] ?? ''));
|
$categoryKey = trim((string)($body['categoryKey'] ?? ''));
|
||||||
$germanLabel = (string)($body['germanLabel'] ?? '');
|
$germanLabel = (string)($body['germanLabel'] ?? '');
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$stringKey = qdb_set_category_german_label($pdo, $categoryKey, $germanLabel);
|
$stringKey = qdb_set_category_german_label($pdo, $categoryKey, $germanLabel);
|
||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
@ -88,9 +78,7 @@ case 'POST':
|
|||||||
'germanLabel' => trim($germanLabel),
|
'germanLabel' => trim($germanLabel),
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Update category label', null, $tmpDb, $lockFp);
|
||||||
error_log('updateCategoryLabel: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -100,15 +88,13 @@ case 'POST':
|
|||||||
if ($categoryKey === '') {
|
if ($categoryKey === '') {
|
||||||
json_error('INVALID_FIELD', 'categoryKey is required', 400);
|
json_error('INVALID_FIELD', 'categoryKey is required', 400);
|
||||||
}
|
}
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$cleared = qdb_delete_category_key($pdo, $categoryKey);
|
$cleared = qdb_delete_category_key($pdo, $categoryKey);
|
||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success(['deleted' => $categoryKey, 'questionnairesCleared' => $cleared]);
|
json_success(['deleted' => $categoryKey, 'questionnairesCleared' => $cleared]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Delete category key', null, $tmpDb, $lockFp);
|
||||||
error_log('deleteCategoryKey: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -119,7 +105,7 @@ case 'POST':
|
|||||||
json_error('MISSING_FIELDS', 'bundle object is required', 400);
|
json_error('MISSING_FIELDS', 'bundle object is required', 400);
|
||||||
}
|
}
|
||||||
$replaceIfExists = !empty($body['replaceIfExists']);
|
$replaceIfExists = !empty($body['replaceIfExists']);
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$pdo->exec('PRAGMA foreign_keys = OFF;');
|
$pdo->exec('PRAGMA foreign_keys = OFF;');
|
||||||
$result = qdb_import_questionnaires_bundle($pdo, $bundle, $replaceIfExists);
|
$result = qdb_import_questionnaires_bundle($pdo, $bundle, $replaceIfExists);
|
||||||
@ -127,14 +113,7 @@ case 'POST':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success($result);
|
json_success($result);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Import questionnaires', null, $tmpDb, $lockFp);
|
||||||
error_log('importBundle: ' . $e->getMessage());
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
error_log('importBundle: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Import'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -150,7 +129,7 @@ case 'POST':
|
|||||||
$showPts = (int)($body['showPoints'] ?? 0);
|
$showPts = (int)($body['showPoints'] ?? 0);
|
||||||
$condJson = $body['conditionJson'] ?? '{}';
|
$condJson = $body['conditionJson'] ?? '{}';
|
||||||
|
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
if ($order === 0) {
|
if ($order === 0) {
|
||||||
$order = (int)$pdo->query("SELECT COALESCE(MAX(orderIndex),0)+1 FROM questionnaire")->fetchColumn();
|
$order = (int)$pdo->query("SELECT COALESCE(MAX(orderIndex),0)+1 FROM questionnaire")->fetchColumn();
|
||||||
@ -170,9 +149,7 @@ case 'POST':
|
|||||||
'conditionJson' => $condJson, 'questionCount' => 0,
|
'conditionJson' => $condJson, 'questionCount' => 0,
|
||||||
]]);
|
]]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Create questionnaire', null, $tmpDb, $lockFp);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -184,7 +161,7 @@ case 'PUT':
|
|||||||
}
|
}
|
||||||
$id = $body['questionnaireID'];
|
$id = $body['questionnaireID'];
|
||||||
|
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$existing = $pdo->prepare('SELECT * FROM questionnaire WHERE questionnaireID = :id');
|
$existing = $pdo->prepare('SELECT * FROM questionnaire WHERE questionnaireID = :id');
|
||||||
$existing->execute([':id' => $id]);
|
$existing->execute([':id' => $id]);
|
||||||
@ -217,9 +194,7 @@ case 'PUT':
|
|||||||
'orderIndex' => $order, 'showPoints' => $showPts, 'conditionJson' => $condJson,
|
'orderIndex' => $order, 'showPoints' => $showPts, 'conditionJson' => $condJson,
|
||||||
]]);
|
]]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Update questionnaire', null, $tmpDb, $lockFp);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -231,7 +206,7 @@ case 'DELETE':
|
|||||||
}
|
}
|
||||||
$id = $body['questionnaireID'];
|
$id = $body['questionnaireID'];
|
||||||
|
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$pdo->exec('PRAGMA foreign_keys = OFF;');
|
$pdo->exec('PRAGMA foreign_keys = OFF;');
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
@ -260,12 +235,7 @@ case 'DELETE':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success([]);
|
json_success([]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if ($pdo->inTransaction()) {
|
qdb_handler_fail($e, 'Delete questionnaire', $pdo, $tmpDb, $lockFp);
|
||||||
$pdo->rollBack();
|
|
||||||
}
|
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -46,15 +46,7 @@ case 'GET':
|
|||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
json_success(['questions' => $questions]);
|
json_success(['questions' => $questions]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Load questions', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('questions GET: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Load questions'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -79,7 +71,7 @@ case 'POST':
|
|||||||
$body['noteAfter'] ?? ($cfg['noteAfter'] ?? null)
|
$body['noteAfter'] ?? ($cfg['noteAfter'] ?? null)
|
||||||
);
|
);
|
||||||
$configJson = json_encode($config, JSON_UNESCAPED_UNICODE);
|
$configJson = json_encode($config, JSON_UNESCAPED_UNICODE);
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$chk = $pdo->prepare("SELECT 1 FROM questionnaire WHERE questionnaireID = :id");
|
$chk = $pdo->prepare("SELECT 1 FROM questionnaire WHERE questionnaireID = :id");
|
||||||
$chk->execute([':id' => $qnID]);
|
$chk->execute([':id' => $qnID]);
|
||||||
@ -117,9 +109,7 @@ case 'POST':
|
|||||||
'configJson' => $configJson, 'answerOptions' => [], 'translations' => [],
|
'configJson' => $configJson, 'answerOptions' => [], 'translations' => [],
|
||||||
]]);
|
]]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -130,7 +120,7 @@ case 'PUT':
|
|||||||
json_error('BAD_REQUEST', 'questionID is required', 400);
|
json_error('BAD_REQUEST', 'questionID is required', 400);
|
||||||
}
|
}
|
||||||
$id = $body['questionID'];
|
$id = $body['questionID'];
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$existing = $pdo->prepare("SELECT * FROM question WHERE questionID = :id");
|
$existing = $pdo->prepare("SELECT * FROM question WHERE questionID = :id");
|
||||||
$existing->execute([':id' => $id]);
|
$existing->execute([':id' => $id]);
|
||||||
@ -171,9 +161,7 @@ case 'PUT':
|
|||||||
'configJson' => $configJson,
|
'configJson' => $configJson,
|
||||||
]]);
|
]]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -184,7 +172,7 @@ case 'DELETE':
|
|||||||
json_error('BAD_REQUEST', 'questionID is required', 400);
|
json_error('BAD_REQUEST', 'questionID is required', 400);
|
||||||
}
|
}
|
||||||
$id = $body['questionID'];
|
$id = $body['questionID'];
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$pdo->exec("PRAGMA foreign_keys = OFF;");
|
$pdo->exec("PRAGMA foreign_keys = OFF;");
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
@ -202,12 +190,7 @@ case 'DELETE':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success(['deleted' => true]);
|
json_success(['deleted' => true]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if ($pdo->inTransaction()) {
|
qdb_handler_fail($e, 'Delete question', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
$pdo->rollBack();
|
|
||||||
}
|
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -259,15 +242,7 @@ case 'PATCH':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success(['reordered' => true]);
|
json_success(['reordered' => true]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Reorder questions', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('questions PATCH: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Reorder questions'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -120,13 +120,5 @@ if (!empty($questionIDs) && !empty($clients)) {
|
|||||||
'clients' => $clients,
|
'clients' => $clients,
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Load results', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('results GET: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Load results'), 500);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,15 +34,7 @@ if (($rec['userID'] ?? '') !== '') {
|
|||||||
$username = $row !== false ? (string)$row : '';
|
$username = $row !== false ? (string)$row : '';
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Session check', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('session username lookup: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Session check'), 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,15 +15,7 @@ case 'GET':
|
|||||||
$bundle = qdb_export_translations_bundle($pdo);
|
$bundle = qdb_export_translations_bundle($pdo);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Export translations', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('translations exportBundle: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Export'), 500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$filename = 'translations_bundle_' . date('Y-m-d_His') . '.json';
|
$filename = 'translations_bundle_' . date('Y-m-d_His') . '.json';
|
||||||
@ -41,15 +33,7 @@ case 'GET':
|
|||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
json_success(['languages' => $rows]);
|
json_success(['languages' => $rows]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Load languages', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('translations languages: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Load languages'), 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,15 +86,7 @@ case 'GET':
|
|||||||
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
|
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Load translations', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('translations all: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Load translations'), 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,15 +122,7 @@ case 'GET':
|
|||||||
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
|
'sourceLanguage' => QDB_SOURCE_LANGUAGE,
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Load translations', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('translations questionnaire: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Load translations'), 500);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,15 +160,7 @@ case 'GET':
|
|||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_discard($tmpDb, $lockFp);
|
||||||
json_success(['translations' => $rows]);
|
json_success(['translations' => $rows]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Load translations', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('translations GET: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Load translations'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -212,20 +172,13 @@ case 'POST':
|
|||||||
if (!is_array($bundle)) {
|
if (!is_array($bundle)) {
|
||||||
json_error('MISSING_FIELDS', 'bundle object is required', 400);
|
json_error('MISSING_FIELDS', 'bundle object is required', 400);
|
||||||
}
|
}
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$result = qdb_import_translations_bundle($pdo, $bundle);
|
$result = qdb_import_translations_bundle($pdo, $bundle);
|
||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success($result);
|
json_success($result);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Import translations', null, $tmpDb, $lockFp);
|
||||||
error_log('translations importBundle: ' . $e->getMessage());
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
error_log('translations importBundle: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Import'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -243,7 +196,7 @@ case 'PUT':
|
|||||||
if (!$lang) {
|
if (!$lang) {
|
||||||
json_error('MISSING_FIELDS', 'languageCode required', 400);
|
json_error('MISSING_FIELDS', 'languageCode required', 400);
|
||||||
}
|
}
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
if ($lang === QDB_SOURCE_LANGUAGE) {
|
if ($lang === QDB_SOURCE_LANGUAGE) {
|
||||||
qdb_ensure_source_language($pdo);
|
qdb_ensure_source_language($pdo);
|
||||||
@ -255,9 +208,7 @@ case 'PUT':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success([]);
|
json_success([]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Save language', null, $tmpDb, $lockFp);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -280,15 +231,7 @@ case 'PUT':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success([]);
|
json_success([]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Save translation', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('translations PUT: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Save translation'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -305,7 +248,7 @@ case 'DELETE':
|
|||||||
if ($lang === QDB_SOURCE_LANGUAGE) {
|
if ($lang === QDB_SOURCE_LANGUAGE) {
|
||||||
json_error('BAD_REQUEST', 'German (de) cannot be removed', 400);
|
json_error('BAD_REQUEST', 'German (de) cannot be removed', 400);
|
||||||
}
|
}
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
try {
|
try {
|
||||||
$pdo->prepare("DELETE FROM language WHERE languageCode = :lc")->execute([':lc' => $lang]);
|
$pdo->prepare("DELETE FROM language WHERE languageCode = :lc")->execute([':lc' => $lang]);
|
||||||
$pdo->prepare("DELETE FROM question_translation WHERE languageCode = :lc")->execute([':lc' => $lang]);
|
$pdo->prepare("DELETE FROM question_translation WHERE languageCode = :lc")->execute([':lc' => $lang]);
|
||||||
@ -314,9 +257,7 @@ case 'DELETE':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success([]);
|
json_success([]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'Delete language', null, $tmpDb, $lockFp);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -349,15 +290,7 @@ case 'DELETE':
|
|||||||
qdb_save($tmpDb, $lockFp);
|
qdb_save($tmpDb, $lockFp);
|
||||||
json_success([]);
|
json_success([]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) {
|
qdb_handler_fail($e, 'Delete translation', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
qdb_discard($tmpDb, $lockFp);
|
|
||||||
}
|
|
||||||
if (function_exists('qdb_api_log_note_exception')) {
|
|
||||||
qdb_api_log_note_exception($e);
|
|
||||||
}
|
|
||||||
require_once __DIR__ . '/../lib/errors.php';
|
|
||||||
error_log('translations DELETE: ' . $e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', qdb_public_error_message($e, 'Delete translation'), 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@ switch ($method) {
|
|||||||
|
|
||||||
case 'GET':
|
case 'GET':
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(false);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_read_or_fail();
|
||||||
|
|
||||||
if ($callerRole === 'admin') {
|
if ($callerRole === 'admin') {
|
||||||
$users = $pdo->query(
|
$users = $pdo->query(
|
||||||
@ -53,9 +53,7 @@ case 'GET':
|
|||||||
'callerRole' => $callerRole,
|
'callerRole' => $callerRole,
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'users', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -88,7 +86,7 @@ case 'POST':
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
|
|
||||||
$chk = $pdo->prepare("SELECT userID FROM users WHERE username = :u");
|
$chk = $pdo->prepare("SELECT userID FROM users WHERE username = :u");
|
||||||
$chk->execute([':u' => $username]);
|
$chk->execute([':u' => $username]);
|
||||||
@ -142,7 +140,7 @@ case 'POST':
|
|||||||
|
|
||||||
$svUsername = null;
|
$svUsername = null;
|
||||||
if ($role === 'coach') {
|
if ($role === 'coach') {
|
||||||
[$pdo2, $tmp2, $lk2] = qdb_open(false);
|
[$pdo2, $tmp2, $lk2] = qdb_open_read_or_fail();
|
||||||
$svr = $pdo2->prepare("SELECT username FROM supervisor WHERE supervisorID = :sid");
|
$svr = $pdo2->prepare("SELECT username FROM supervisor WHERE supervisorID = :sid");
|
||||||
$svr->execute([':sid' => $supervisorID]);
|
$svr->execute([':sid' => $supervisorID]);
|
||||||
$svUsername = $svr->fetchColumn() ?: null;
|
$svUsername = $svr->fetchColumn() ?: null;
|
||||||
@ -161,10 +159,7 @@ case 'POST':
|
|||||||
'createdAt' => $now,
|
'createdAt' => $now,
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
|
qdb_handler_fail($e, 'users', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -187,7 +182,7 @@ case 'PATCH':
|
|||||||
$mustChange = isset($body['mustChangePassword']) ? (int)(bool)$body['mustChangePassword'] : 1;
|
$mustChange = isset($body['mustChangePassword']) ? (int)(bool)$body['mustChangePassword'] : 1;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
|
|
||||||
$row = $pdo->prepare(
|
$row = $pdo->prepare(
|
||||||
"SELECT u.userID, u.username, u.role, u.entityID, c.supervisorID
|
"SELECT u.userID, u.username, u.role, u.entityID, c.supervisorID
|
||||||
@ -242,9 +237,7 @@ case 'PATCH':
|
|||||||
'mustChangePassword' => $mustChange,
|
'mustChangePassword' => $mustChange,
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'users', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -260,7 +253,7 @@ case 'PATCH':
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
|
|
||||||
$row = $pdo->prepare(
|
$row = $pdo->prepare(
|
||||||
"SELECT u.userID, u.username, u.role, u.entityID, c.supervisorID
|
"SELECT u.userID, u.username, u.role, u.entityID, c.supervisorID
|
||||||
@ -308,9 +301,7 @@ case 'PATCH':
|
|||||||
'supervisorUsername' => $svUsername,
|
'supervisorUsername' => $svUsername,
|
||||||
]);
|
]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
qdb_handler_fail($e, 'users', null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -326,7 +317,7 @@ case 'DELETE':
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||||
|
|
||||||
$row = $pdo->prepare("SELECT role, entityID FROM users WHERE userID = :uid");
|
$row = $pdo->prepare("SELECT role, entityID FROM users WHERE userID = :uid");
|
||||||
$row->execute([':uid' => $targetUserID]);
|
$row->execute([':uid' => $targetUserID]);
|
||||||
@ -368,10 +359,7 @@ case 'DELETE':
|
|||||||
|
|
||||||
json_success([]);
|
json_success([]);
|
||||||
} catch (Throwable $e) {
|
} catch (Throwable $e) {
|
||||||
if (isset($pdo) && $pdo->inTransaction()) $pdo->rollBack();
|
qdb_handler_fail($e, 'users', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
|
||||||
if (isset($tmpDb, $lockFp)) qdb_discard($tmpDb, $lockFp);
|
|
||||||
error_log($e->getMessage());
|
|
||||||
json_error('SERVER_ERROR', 'Server error', 500);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared API error helpers for handlers and db_init.
|
||||||
|
*/
|
||||||
|
|
||||||
|
function qdb_is_lock_exception(Throwable $e): bool
|
||||||
|
{
|
||||||
|
$msg = $e->getMessage();
|
||||||
|
return str_contains($msg, 'Could not acquire lock')
|
||||||
|
|| str_contains($msg, 'Could not open lock file');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map internal exceptions to safe, identifiable API error messages (details go to error_log).
|
* Map internal exceptions to safe, identifiable API error messages (details go to error_log).
|
||||||
*/
|
*/
|
||||||
@ -7,8 +18,8 @@ function qdb_public_error_message(Throwable $e, string $contextLabel = 'Operatio
|
|||||||
{
|
{
|
||||||
$msg = $e->getMessage();
|
$msg = $e->getMessage();
|
||||||
|
|
||||||
if (str_contains($msg, 'Could not acquire lock') || str_contains($msg, 'Could not open lock')) {
|
if (qdb_is_lock_exception($e)) {
|
||||||
return 'Database is busy. Please try again in a moment.';
|
return 'Database is busy because another update is in progress. Please try again in a moment.';
|
||||||
}
|
}
|
||||||
if (str_contains($msg, 'decrypt') || str_contains($msg, 'QDB_MASTER_KEY')) {
|
if (str_contains($msg, 'decrypt') || str_contains($msg, 'QDB_MASTER_KEY')) {
|
||||||
return 'Database configuration error. Contact the server administrator.';
|
return 'Database configuration error. Contact the server administrator.';
|
||||||
@ -23,3 +34,50 @@ function qdb_public_error_message(Throwable $e, string $contextLabel = 'Operatio
|
|||||||
|
|
||||||
return $contextLabel . ' failed. If this persists, contact support.';
|
return $contextLabel . ' failed. If this persists, contact support.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{0: string, 1: string, 2: int} code, message, http status
|
||||||
|
*/
|
||||||
|
function qdb_api_error_for_exception(Throwable $e, string $contextLabel = 'Operation'): array
|
||||||
|
{
|
||||||
|
if (qdb_is_lock_exception($e)) {
|
||||||
|
return ['LOCKED', qdb_public_error_message($e, $contextLabel), 503];
|
||||||
|
}
|
||||||
|
return ['SERVER_ERROR', qdb_public_error_message($e, $contextLabel), 500];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard handler catch: rollback, discard DB temp file, log, JSON error (never returns).
|
||||||
|
*/
|
||||||
|
function qdb_handler_fail(
|
||||||
|
Throwable $e,
|
||||||
|
string $contextLabel,
|
||||||
|
?PDO $pdo = null,
|
||||||
|
?string $tmpDb = null,
|
||||||
|
$lockFp = null
|
||||||
|
): never {
|
||||||
|
if ($pdo !== null && $pdo->inTransaction()) {
|
||||||
|
try {
|
||||||
|
$pdo->rollBack();
|
||||||
|
} catch (Throwable) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($tmpDb !== null && $lockFp !== null) {
|
||||||
|
require_once __DIR__ . '/../db_init.php';
|
||||||
|
qdb_discard($tmpDb, $lockFp);
|
||||||
|
}
|
||||||
|
qdb_emit_api_error($e, $contextLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log exception and exit with unified JSON error (never returns).
|
||||||
|
*/
|
||||||
|
function qdb_emit_api_error(Throwable $e, string $contextLabel): never
|
||||||
|
{
|
||||||
|
if (function_exists('qdb_api_log_note_exception')) {
|
||||||
|
qdb_api_log_note_exception($e);
|
||||||
|
}
|
||||||
|
error_log($contextLabel . ': ' . $e->getMessage());
|
||||||
|
[$code, $message, $status] = qdb_api_error_for_exception($e, $contextLabel);
|
||||||
|
json_error($code, $message, $status);
|
||||||
|
}
|
||||||
|
|||||||
@ -172,12 +172,6 @@ export async function apiDelete(endpoint, body) {
|
|||||||
return unwrap(await res.json());
|
return unwrap(await res.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
export function apiDownloadUrl(endpoint) {
|
|
||||||
const token = getToken();
|
|
||||||
const cleanEndpoint = endpoint.replace(/\.php(\?|$)/, '$1');
|
|
||||||
return `${API_BASE}/${cleanEndpoint}${cleanEndpoint.includes('?') ? '&' : '?'}token=${encodeURIComponent(token)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Authenticated file/download fetch (marks request as website for activity logging). */
|
/** Authenticated file/download fetch (marks request as website for activity logging). */
|
||||||
export async function apiDownloadFetch(url, options = {}) {
|
export async function apiDownloadFetch(url, options = {}) {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
|
|||||||
Reference in New Issue
Block a user