<?php
namespace CodersLab\Lms\UserInterface\WebApp\Controller;
use CodersLab\Lms\Modules\Learning\Application\Command\AddMaterialResultAsync;
use CodersLab\Lms\Modules\Materials\Application\Query\QuizQuery;
use CodersLab\Lms\Modules\Materials\Application\Service\SurveyMonkeyProxy\ProxyClient;
use CodersLab\Lms\Modules\Materials\Domain\Materials;
use CodersLab\Lms\Modules\Materials\Domain\Quiz\State;
use CodersLab\Lms\Modules\Materials\Domain\Quizzes;
use CodersLab\Lms\SharedKernel\Common\Assert;
use CodersLab\Lms\SharedKernel\Common\Exception\InvalidArgumentException;
use CodersLab\Lms\SharedKernel\Domain\Bus\CommandBus;
use CodersLab\Lms\SharedKernel\Domain\Bus\IEventRecorder;
use CodersLab\Lms\SharedKernel\Domain\Identity\Uuid;
use Exception;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\Routing\Annotation\Route;
final class QuizWebhookController
{
public function __construct(
private Quizzes $quizzes,
private CommandBus $commandBus,
private IEventRecorder $eventRecorder,
private ProxyClient $proxyClient,
private Materials $materials,
private QuizQuery $quizQuery
) {
}
/**
* @Route("/webhooks/quiz/update/{quizProviderId}", name="webhook_quiz_update", methods={"GET", "POST"})
*/
public function quizUpdate(Request $request): Response
{
if ($request->getMethod() === Request::METHOD_HEAD) {
return new Response();
}
$content = json_decode((string)$request->getContent(), true);
try {
Assert::isArray($content);
Assert::keyExists($content, 'object_type');
Assert::keyExists($content, 'event_type');
Assert::keyExists($content, 'object_id');
Assert::keyExists($content, 'resources');
Assert::eq($content['object_type'], 'survey');
Assert::eq($content['event_type'], 'survey_updated');
Assert::notEmpty($content['object_id']);
Assert::string($content['object_id']);
Assert::isArray($content['resources']);
Assert::keyExists($content['resources'], 'survey_id');
Assert::string($content['resources']['survey_id']);
$quiz = $this->quizzes->findByProviderId($content['resources']['survey_id']);
Assert::notNull($quiz);
} catch (InvalidArgumentException $exception) {
throw new BadRequestHttpException($exception->getMessage(), $exception);
}
if ($quiz->state()->hasDetails()) {
$quiz->subtractState(State::DETAILS());
}
$this->eventRecorder->record(...$quiz->getEventRecorder()->pullEvents());
return new Response();
}
/**
* @Route("/webhooks/quiz/result/{quizProviderId}", name="webhook_quiz_response", methods={"GET", "POST"})
*/
public function quizResponse(Request $request): Response
{
if ($request->getMethod() === Request::METHOD_HEAD) {
return new Response();
}
$content = json_decode((string)$request->getContent(), true);
if ($content === null) {
throw new Exception((string)$request->getContent());
}
try {
Assert::isArray($content);
Assert::keyExists($content, 'object_type');
Assert::keyExists($content, 'event_type');
Assert::keyExists($content, 'object_id');
Assert::keyExists($content, 'resources');
Assert::eq($content['object_type'], 'response');
Assert::eq($content['event_type'], 'response_completed');
Assert::notEmpty($content['object_id']);
Assert::string($content['object_id']);
Assert::isArray($content['resources']);
Assert::keyExists($content['resources'], 'survey_id');
Assert::string($content['resources']['survey_id']);
$quiz = $this->quizzes->findByProviderId($content['resources']['survey_id']);
Assert::notNull($quiz);
} catch (InvalidArgumentException $exception) {
throw new BadRequestHttpException($exception->getMessage(), $exception);
}
$response = $this->proxyClient->getResponse(
$content['resources']['survey_id'],
$content['object_id']
);
$materials = $this->materials->findByQuizAndCourse($quiz->id(), new Uuid($response->getCourseId()));
foreach ($materials as $material) {
$command = new AddMaterialResultAsync(
$response->getCourseId(),
$response->getCourseInstanceId(),
$response->getStudentId(),
$this->quizQuery->getMaterialCriterionIdForQuiz($material->getUuid()),
$response->getStudentId(),
'student',
$response->getScore()
);
$this->commandBus->dispatch($command);
}
return new Response();
}
}