feat: accept form submissions from the websites over an API endpoint
This commit is contained in:
1 parent
f48e53fdd0
commit
30ad5e8488
17 files changed
+1350
-4
No files matched your search
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Api;
|
||||
|
||||
use App\Entity\ApplicationFormSubmission;
|
||||
use App\Model\ApplicationFormSubmissionDto;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* Receives a form submitted on one of the public websites, posted by the TYPO3
|
||||
* ApplicationApiFinisher. Authenticated by the API key of the stateless "api" firewall.
|
||||
*
|
||||
* The visitor waits for this response inside their own form submission and TYPO3 gives up after
|
||||
* a few seconds without retrying, so this stores the row and answers - notification mail and any
|
||||
* matching against known people belong in a worker, not here
|
||||
* (.ddev/plans/MYEP-APPLICATION-ENDPOINT.md §6).
|
||||
*/
|
||||
class ApplicationFormController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A failed mapping or validation answers 400 rather than the resolver's default 422: the
|
||||
* contract names 400 for an unusable payload, and the TYPO3 side logs the response body.
|
||||
* Errors are English - the only caller is a machine.
|
||||
*/
|
||||
#[Route('/api/application-forms', name: 'app_api_application_form_submit', methods: ['POST'])]
|
||||
#[IsGranted('ROLE_API')]
|
||||
public function submit(
|
||||
Request $request,
|
||||
#[MapRequestPayload(validationFailedStatusCode: Response::HTTP_BAD_REQUEST)]
|
||||
ApplicationFormSubmissionDto $dto,
|
||||
): JsonResponse {
|
||||
$submission = $this->store($dto, $request);
|
||||
|
||||
$this->logger->info('Application form submission received', [
|
||||
'uuid' => $submission->getUuid(),
|
||||
'form' => $submission->getForm(),
|
||||
'formLabel' => $submission->getFormLabel(),
|
||||
'pageUid' => $submission->getPageUid(),
|
||||
'submittedAt' => $submission->getSubmittedAt()->format(\DateTimeInterface::ATOM),
|
||||
// the labels and the answers are personal data (§7), their number is not
|
||||
'detailCount' => count($submission->getDetails()),
|
||||
]);
|
||||
|
||||
return new JsonResponse(
|
||||
['status' => 'accepted', 'uuid' => $submission->getUuid()],
|
||||
Response::HTTP_ACCEPTED
|
||||
);
|
||||
}
|
||||
|
||||
private function store(ApplicationFormSubmissionDto $dto, Request $request): ApplicationFormSubmission
|
||||
{
|
||||
$submission = ApplicationFormSubmission::fromDto($dto, $this->decodePayload($request));
|
||||
|
||||
$this->entityManager->persist($submission);
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $submission;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function decodePayload(Request $request): array
|
||||
{
|
||||
$payload = json_decode($request->getContent(), true);
|
||||
|
||||
return is_array($payload) ? $payload : [];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user