172 lines
5.1 KiB
PHP
172 lines
5.1 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Services;
|
||
|
|
|
||
|
|
use App\Models\ApiLog;
|
||
|
|
use App\Models\JobSource;
|
||
|
|
use Illuminate\Http\Client\PendingRequest;
|
||
|
|
use Illuminate\Support\Facades\Http;
|
||
|
|
use Illuminate\Support\Facades\Log;
|
||
|
|
|
||
|
|
class ApiClientService
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Make an API request with logging and retry logic
|
||
|
|
*/
|
||
|
|
public function makeRequest(
|
||
|
|
JobSource $source,
|
||
|
|
string $endpoint,
|
||
|
|
string $method = 'GET',
|
||
|
|
array $params = [],
|
||
|
|
int $maxRetries = 3
|
||
|
|
): array {
|
||
|
|
$startTime = microtime(true);
|
||
|
|
$attempt = 0;
|
||
|
|
$lastException = null;
|
||
|
|
|
||
|
|
while ($attempt < $maxRetries) {
|
||
|
|
$attempt++;
|
||
|
|
|
||
|
|
try {
|
||
|
|
$response = $this->executeRequest($method, $endpoint, $params);
|
||
|
|
$responseTime = (int)((microtime(true) - $startTime) * 1000);
|
||
|
|
|
||
|
|
// Log successful request
|
||
|
|
$this->logApiRequest($source, $endpoint, $method, $params, $response, $responseTime, true);
|
||
|
|
|
||
|
|
return [
|
||
|
|
'success' => true,
|
||
|
|
'data' => $response->json(),
|
||
|
|
'status_code' => $response->status(),
|
||
|
|
'response_time' => $responseTime,
|
||
|
|
];
|
||
|
|
} catch (\Exception $e) {
|
||
|
|
$lastException = $e;
|
||
|
|
|
||
|
|
// Log failed attempt
|
||
|
|
Log::warning("API request failed (attempt {$attempt}/{$maxRetries})", [
|
||
|
|
'source' => $source->name,
|
||
|
|
'endpoint' => $endpoint,
|
||
|
|
'error' => $e->getMessage(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
if ($attempt < $maxRetries) {
|
||
|
|
// Exponential backoff: wait 2^attempt seconds
|
||
|
|
sleep(pow(2, $attempt));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$responseTime = (int)((microtime(true) - $startTime) * 1000);
|
||
|
|
|
||
|
|
// Log final failure
|
||
|
|
$this->logApiRequest(
|
||
|
|
$source,
|
||
|
|
$endpoint,
|
||
|
|
$method,
|
||
|
|
$params,
|
||
|
|
null,
|
||
|
|
$responseTime,
|
||
|
|
false,
|
||
|
|
$lastException->getMessage() ?? 'Unknown error'
|
||
|
|
);
|
||
|
|
|
||
|
|
return [
|
||
|
|
'success' => false,
|
||
|
|
'error' => $lastException->getMessage() ?? 'Unknown error',
|
||
|
|
'response_time' => $responseTime,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Execute the HTTP request
|
||
|
|
*/
|
||
|
|
private function executeRequest(string $method, string $endpoint, array $params): \Illuminate\Http\Client\Response
|
||
|
|
{
|
||
|
|
$http = Http::timeout(30)
|
||
|
|
->retry(1, 100)
|
||
|
|
->withHeaders([
|
||
|
|
'User-Agent' => 'JobifyGarden/1.0',
|
||
|
|
'Accept' => 'application/json',
|
||
|
|
]);
|
||
|
|
|
||
|
|
return match (strtoupper($method)) {
|
||
|
|
'GET' => $http->get($endpoint, $params),
|
||
|
|
'POST' => $http->post($endpoint, $params),
|
||
|
|
'PUT' => $http->put($endpoint, $params),
|
||
|
|
'DELETE' => $http->delete($endpoint, $params),
|
||
|
|
default => throw new \InvalidArgumentException("Unsupported HTTP method: {$method}"),
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Log API request to database
|
||
|
|
*/
|
||
|
|
private function logApiRequest(
|
||
|
|
JobSource $source,
|
||
|
|
string $endpoint,
|
||
|
|
string $method,
|
||
|
|
array $params,
|
||
|
|
$response,
|
||
|
|
int $responseTime,
|
||
|
|
bool $success,
|
||
|
|
?string $errorMessage = null
|
||
|
|
): void {
|
||
|
|
try {
|
||
|
|
ApiLog::create([
|
||
|
|
'job_source_id' => $source->id,
|
||
|
|
'endpoint' => $endpoint,
|
||
|
|
'method' => $method,
|
||
|
|
'status_code' => $response?->status(),
|
||
|
|
'response_time_ms' => $responseTime,
|
||
|
|
'success' => $success,
|
||
|
|
'error_message' => $errorMessage,
|
||
|
|
'request_params' => !empty($params) ? $params : null,
|
||
|
|
'response_data' => $success && $response ? [
|
||
|
|
'status' => $response->status(),
|
||
|
|
'size' => strlen($response->body()),
|
||
|
|
] : null,
|
||
|
|
]);
|
||
|
|
} catch (\Exception $e) {
|
||
|
|
Log::error('Failed to log API request', [
|
||
|
|
'error' => $e->getMessage(),
|
||
|
|
'endpoint' => $endpoint,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Check if source is rate limited
|
||
|
|
*/
|
||
|
|
public function isRateLimited(JobSource $source): bool
|
||
|
|
{
|
||
|
|
if (!$source->rate_limit) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check requests in the last hour
|
||
|
|
$recentRequests = ApiLog::where('job_source_id', $source->id)
|
||
|
|
->where('created_at', '>=', now()->subHour())
|
||
|
|
->count();
|
||
|
|
|
||
|
|
return $recentRequests >= $source->rate_limit;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get rate limit info for a source
|
||
|
|
*/
|
||
|
|
public function getRateLimitInfo(JobSource $source): array
|
||
|
|
{
|
||
|
|
$recentRequests = ApiLog::where('job_source_id', $source->id)
|
||
|
|
->where('created_at', '>=', now()->subHour())
|
||
|
|
->count();
|
||
|
|
|
||
|
|
return [
|
||
|
|
'limit' => $source->rate_limit,
|
||
|
|
'used' => $recentRequests,
|
||
|
|
'remaining' => max(0, $source->rate_limit - $recentRequests),
|
||
|
|
'reset_at' => now()->addHour()->startOfHour(),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|