feat: groups price calculator admin crud, booking/offer flow and api
This commit is contained in:
1 parent
eabed8295a
commit
9d2aa11fdb
258 files changed
+16231
-582
No files matched your search
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Groups\AccommodationBooking;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\UriSigner;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class AccommodationBookingLinkSigner
|
||||
{
|
||||
private const int LINK_TTL_DAYS = 90;
|
||||
private const string TIMESTAMP_PARAM = 't';
|
||||
|
||||
private readonly UriSigner $uriSigner;
|
||||
|
||||
public function __construct(
|
||||
private readonly UrlGeneratorInterface $urlGenerator,
|
||||
#[Autowire(env: 'ACCOMMODATION_OFFER_LINK_SECRET')]
|
||||
string $secret,
|
||||
) {
|
||||
$this->uriSigner = new UriSigner($secret);
|
||||
}
|
||||
|
||||
public function sign(AccommodationBooking $booking): string
|
||||
{
|
||||
$issuedAt = $booking->getAccessLinkIssuedAt();
|
||||
|
||||
if (null === $issuedAt) {
|
||||
throw new \LogicException('Cannot sign an access link before accessLinkIssuedAt is set.');
|
||||
}
|
||||
|
||||
$url = $this->urlGenerator->generate(
|
||||
'app_groups_booking_offer',
|
||||
['uuid' => $booking->getUuid(), self::TIMESTAMP_PARAM => $issuedAt->getTimestamp()],
|
||||
UrlGeneratorInterface::ABSOLUTE_URL,
|
||||
);
|
||||
|
||||
return $this->uriSigner->sign($url);
|
||||
}
|
||||
|
||||
public function expiresAt(AccommodationBooking $booking): ?\DateTimeImmutable
|
||||
{
|
||||
return $booking->getAccessLinkIssuedAt()?->modify(sprintf('+%d days', self::LINK_TTL_DAYS));
|
||||
}
|
||||
|
||||
public function isValidLinkRequest(Request $request, AccommodationBooking $booking): bool
|
||||
{
|
||||
$issuedAt = $booking->getAccessLinkIssuedAt();
|
||||
|
||||
if (null === $issuedAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->uriSigner->checkRequest($request)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$timestampParam = $request->query->get(self::TIMESTAMP_PARAM);
|
||||
|
||||
if (null === $timestampParam || (int) $timestampParam !== $issuedAt->getTimestamp()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$expiresAt = $this->expiresAt($booking);
|
||||
|
||||
return null !== $expiresAt && new \DateTimeImmutable() <= $expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the current session as authorized to view/act on this booking's offer.
|
||||
* Called once, after a successful {@see isValidLinkRequest()} check at the
|
||||
* signed-link entry point — every other route in this flow then trusts the
|
||||
* session instead of re-deriving cryptographic validity from its own URL
|
||||
* (which wouldn't work anyway, since the signature is bound to the exact
|
||||
* URI it was generated for).
|
||||
*/
|
||||
public function authorizeSession(Request $request, AccommodationBooking $booking): void
|
||||
{
|
||||
$request->getSession()->set($this->sessionKey($booking), $booking->getAccessLinkIssuedAt()?->getTimestamp());
|
||||
}
|
||||
|
||||
public function isSessionAuthorized(Request $request, AccommodationBooking $booking): bool
|
||||
{
|
||||
$issuedAt = $booking->getAccessLinkIssuedAt();
|
||||
|
||||
if (null === $issuedAt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($request->getSession()->get($this->sessionKey($booking)) !== $issuedAt->getTimestamp()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$expiresAt = $this->expiresAt($booking);
|
||||
|
||||
return null !== $expiresAt && new \DateTimeImmutable() <= $expiresAt;
|
||||
}
|
||||
|
||||
private function sessionKey(AccommodationBooking $booking): string
|
||||
{
|
||||
return 'accommodation_offer_access_'.$booking->getUuid();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user