$error === null, 'data' => $data, 'error' => $error, // ['code' => ..., 'message' => ...] or null ]); exit; } /** * Returns true when the caller has EXCEEDED $max hits on $key within $window seconds. * Reuses the api_requests table (no new table needed). */ protected function throttle(string $key, int $max, int $window): bool { $ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'; // Trusted server-local / whitelisted callers are never throttled: the static-site build // fetches the read API from the box itself thousands of times per build. The throttle is // for public abuse (real client IPs), not our own build. See Functions::isTrustedIp. if (\Functions::isTrustedIp($ip)) { return false; } $count = (int) \Db::getValue( "SELECT COUNT(*) FROM `api_requests` WHERE `requesting_ip` = ? AND `request` = ? AND `created_date` > (NOW() - INTERVAL ? SECOND)", [$ip, $key, $window] ); \Db::insert('api_requests', [ 'requesting_ip' => $ip, 'request' => $key, 'service' => 'foundation', 'domainURI' => $_SERVER['HTTP_HOST'] ?? '', 'created_date' => date('Y-m-d H:i:s'), ]); return $count >= $max; } }