feat: improved contingents api performance with db backed snapshots

This commit is contained in:
Björn Fromme committed 2026-08-20 11:46:03 +02:00
1 parent d5f9f4ef10
commit 351fbab498
20 files changed
+1521 -109

No files matched your search

+17 -22
View File
@@ -4,8 +4,6 @@ declare(strict_types=1);
namespace App\Controller\Api;
use App\BpnConnect\ContingentsClient;
use App\BpnConnect\Exception\BpnConnectException;
use App\BpnConnect\Model\ContingentStatus;
use App\Entity\Groups\AccommodationPrice;
use App\Enum\Groups\PriceType;
@@ -14,9 +12,9 @@ use App\Model\ContingentPricesQuery;
use App\Repository\Groups\AccommodationPriceRepository;
use App\Repository\Groups\AccommodationRepository;
use App\Service\AccommodationPriceCoverage;
use App\Service\ContingentSnapshotReader;
use App\Service\PriceTimelineBuilder;
use Carbon\CarbonImmutable;
use Psr\Cache\InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -24,18 +22,15 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
#[Route('/api')]
#[IsGranted('ROLE_OAUTH2_API')]
class ContingentController extends AbstractController
{
public function __construct(
private readonly ContingentsClient $contingentsClient,
private readonly AccommodationRepository $accommodationRepository,
private readonly AccommodationPriceRepository $priceRepository,
private readonly CacheInterface $cache,
private readonly ContingentSnapshotReader $snapshotReader,
private readonly PriceTimelineBuilder $priceTimelineBuilder,
private readonly AccommodationPriceCoverage $priceCoverage,
private readonly LoggerInterface $logger,
@@ -88,18 +83,12 @@ class ContingentController extends AbstractController
return $this->json(['error' => 'Hotel not found for hotelCode.'], Response::HTTP_BAD_REQUEST);
}
try {
$cacheKey = sprintf('contingents_calendar_%s_%s_%s', $query->hotelCode, $query->dateFrom, $query->dateTo);
$calendar = $this->cache->get($cacheKey, function (ItemInterface $item) use ($query) {
$item->expiresAfter(3600);
return $this->contingentsClient->getContingentCalendar($query->hotelCode, $query->dateFrom, $query->dateTo);
});
} catch (BpnConnectException|InvalidArgumentException $e) {
$this->logger->error('Failed to fetch contingent data', [
'error' => $e->getMessage(),
]);
// An empty or stale snapshot must not be served as though every day were blocked: that
// is a plausible-looking 200 nobody can distinguish from real data. Fail the way this
// endpoint always failed instead, so existing consumers need no change.
$statuses = $this->snapshotReader->statusesFor($accommodation, $dateFrom, $dateTo);
if (null === $statuses) {
return $this->json(['error' => 'Failed to fetch contingent data.'], Response::HTTP_BAD_GATEWAY);
}
@@ -109,10 +98,16 @@ class ContingentController extends AbstractController
$covered = $this->priceCoverage->coveredDatesFor($prices, $dateFrom, $dateTo);
$data = array_map(
fn ($entry) => $this->enrichEntry($entry->date, $entry->status->value, $prices, $covered, $currency),
$calendar->data,
);
$data = [];
for ($day = $dateFrom; $day <= $dateTo; $day = $day->modify('+1 day')) {
$date = $day->format('Y-m-d');
// Days the snapshot does not know about are not sold, same rule as days without a price.
$status = $statuses[$date] ?? ContingentStatus::Blocked;
$data[] = $this->enrichEntry($date, $status->value, $prices, $covered, $currency);
}
return $this->json($data);
}