$v !== '')); } function sendJSON(mixed $data, int $code = 200): void { http_response_code($code); echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); exit; } function sendSuccess(mixed $data = null, string $message = 'OK', int $code = 200): void { sendJSON(['status' => 'success', 'message' => $message, 'data' => $data], $code); } function sendError(string $message, int $code = 400): void { sendJSON(['status' => 'error', 'message' => $message], $code); } function requireWriteAuth(string $method): void { if (!in_array($method, ['POST', 'PUT', 'DELETE'], true)) { return; } $expected = (string)(getenv('API_WRITE_TOKEN') ?: ''); if ($expected === '') { return; } $provided = (string)($_SERVER['HTTP_X_API_TOKEN'] ?? ''); if ($provided === '') { $auth = (string)($_SERVER['HTTP_AUTHORIZATION'] ?? ''); if (preg_match('/Bearer\s+(.+)/i', $auth, $m)) { $provided = trim($m[1]); } } if ($provided === '' || !hash_equals($expected, $provided)) { sendError('Unauthorized', 401); } } function executeOrFail(mysqli_stmt $stmt, string $defaultMessage = 'Operasi database gagal'): void { if ($stmt->execute()) { return; } $errno = $stmt->errno; $error = $stmt->error; error_log('DB stmt error [' . $errno . ']: ' . $error); if ($errno === 1062) { sendError('Data duplikat terdeteksi', 409); } if ($errno === 1451 || $errno === 1452) { sendError('Relasi data tidak valid', 422); } sendError($defaultMessage, 500); } function getBody(): array { $raw = file_get_contents('php://input'); $data = json_decode($raw, true); return is_array($data) ? $data : []; } function getMethod(): string { return strtoupper($_SERVER['REQUEST_METHOD']); }