<?php
namespace CodersLab\Lms\Modules\Materials\Infrastructure\SurveyMonkeyProxy;
use CodersLab\Lms\Modules\Materials\Application\Service\Quiz\QuizException;
use CodersLab\Lms\Modules\Materials\Application\Service\Quiz\QuizResponse;
use CodersLab\Lms\Modules\Materials\Application\Service\SurveyMonkeyProxy\IProxyErrorsQuery;
use CodersLab\Lms\Modules\Materials\Application\Service\SurveyMonkeyProxy\ProxyClient;
use CodersLab\Lms\SharedKernel\Application\CredentialSettings;
use CodersLab\Lms\SharedKernel\Application\DateTime\DateTime;
use CodersLab\Lms\SharedKernel\Application\Enum\CredentialSettingsType;
use CodersLab\Lms\SharedKernel\Application\Query\ICredentialSettingsQuery;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\RequestException;
use Symfony\Component\HttpFoundation\Response;
final class SurveyMonkeyProxyClient implements ProxyClient
{
private const PROXY_ERROR_CODES = [
Response::HTTP_TOO_MANY_REQUESTS,
Response::HTTP_UNAUTHORIZED,
Response::HTTP_SERVICE_UNAVAILABLE,
Response::HTTP_GATEWAY_TIMEOUT,
];
private static int $retries = 0;
private Client $client;
private ?CredentialSettings $settings;
private int $retriesLimit;
public function __construct(
private ICredentialSettingsQuery $credentialQuery,
private IProxyErrorsQuery $proxyErrorsQuery,
) {
$this->initClient();
$this->retriesLimit = $this->credentialQuery->countWithNotExceededLimit(
CredentialSettingsType::SURVEYMONKEY_PROXY()
) * 2;
}
public function getResponse(string $quizId, string $responseId): QuizResponse
{
$url = sprintf('/response?quiz_id=%s&response_id=%s', $quizId, $responseId);
try {
$response = $this->client->get($url);
} catch (ClientException $e) {
if (in_array($e->getResponse()->getStatusCode(), self::PROXY_ERROR_CODES, true)) {
return $this->repeatRequest($quizId, $responseId, $e);
}
$this->proxyErrorsQuery->storeError($quizId, $responseId);
throw new QuizException($e->getMessage(), $e->getCode());
} catch (RequestException $e) {
return $this->repeatRequest($quizId, $responseId, $e);
}
$body = json_decode($response->getBody(), true);
if (empty($body['data']['custom_variables'])) {
$this->throwMissingPropertyException('custom_variables', $quizId, $responseId);
}
if (empty($body['data']['custom_variables']['student_id'])) {
$this->throwMissingPropertyException('custom_variables.student_id', $quizId, $responseId);
}
if (empty($body['data']['custom_variables']['course_id'])) {
$this->throwMissingPropertyException('custom_variables.course_id', $quizId, $responseId);
}
if (empty($body['data']['custom_variables']['course_instance_id'])) {
$this->throwMissingPropertyException('custom_variables.course_instance_id', $quizId, $responseId);
}
return new QuizResponse(
$responseId,
$quizId,
(float)$body['data']['quiz_results']['score'],
$body['data']['custom_variables']['course_id'],
(int)$body['data']['custom_variables']['course_instance_id'],
(int)$body['data']['custom_variables']['student_id']
);
}
public function repeatRequest(string $quizId, string $responseId, RequestException $e): QuizResponse
{
if (self::$retries > $this->retriesLimit) {
$this->proxyErrorsQuery->storeError($quizId, $responseId);
throw new QuizException(
sprintf(
'Maximum retries reached! Origin exception: code: %s, msg: %s',
$e->getCode(),
$e->getMessage()
)
);
}
$limitReached = $e->getResponse()->getStatusCode() === Response::HTTP_TOO_MANY_REQUESTS;
if ($limitReached) {
$limitRefreshInterval = new \DateInterval('P1D');
$limitRefreshDate = (new \DateTimeImmutable())->add($limitRefreshInterval);
$limitRefreshDate = DateTime::createFromPHPDateTime(
\DateTime::createFromImmutable($limitRefreshDate)
);
$this->credentialQuery->setLimitRenewalDate($this->settings->getId(), $limitRefreshDate);
}
$this->initClient();
self::$retries++;
return $this->getResponse($quizId, $responseId);
}
private function initClient(): void
{
$this->settings = $this->credentialQuery->getOneWithNotExceededLimit(
CredentialSettingsType::SURVEYMONKEY_PROXY()
);
if (!$this->settings) {
throw new QuizException('No credentials with free quota found');
}
$this->client = new Client([
'base_uri' => $this->settings->getUrl(),
'headers' => ['auth-token' => $this->settings->getAuthToken()],
]);
}
private function throwMissingPropertyException(string $name, string $quizId, string $responseId): void
{
$this->proxyErrorsQuery->storeError($quizId, $responseId);
throw QuizException::missingProperty($name);
}
}