feat: improved api endpoint for product and hotel data using middleware

This commit is contained in:
Björn Fromme
2025-11-20 09:57:02 +01:00
parent ba3ebe0bb3
commit 63ccda5b8a
4 changed files with 169 additions and 203 deletions
+7
View File
@@ -0,0 +1,7 @@
### GET product data for myep
GET https://ep-reisen.ddev.site/api/product
?product=SBW
&hotel=SBWOU2
&key=abcabc123
###
@@ -1,203 +0,0 @@
<?php
declare(strict_types=1);
namespace EP\EpProducts\Controller;
/***************************************************************
*
* Copyright notice
*
* (c) 2025 Björn Fromme <[email protected]>, dreipunktnull
*
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
use EP\EpProducts\Service\HotelImageService;
use EP\EpProducts\Service\ProductImageService;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
class ApiController extends ActionController
{
/**
* @var string
*/
protected $defaultViewObjectName = JsonView::class;
/**
* @var ProductImageService
*/
private $productImageService;
/**
* @var HotelImageService
*/
private $hotelImageService;
/**
* @var ConnectionPool
*/
private $connectionPool;
public function __construct(
ProductImageService $productImageService,
HotelImageService $hotelImageService,
ConnectionPool $connectionPool
) {
$this->productImageService = $productImageService;
$this->hotelImageService = $hotelImageService;
$this->connectionPool = $connectionPool;
}
/**
* Returns product details in JSON format for API consumption.
*/
public function productAction(string $code): void
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_product');
$product = $queryBuilder
->select(
'uid',
'name',
'code',
'headline',
'teaser',
'teaser_long',
'concept_description',
'board_description',
'programme_description',
'included_services',
'additional_services',
'ski_pass_alt_title',
'ski_pass_description'
)
->from('tx_epproducts_domain_model_product')
->where(
$queryBuilder->expr()->eq(
'code',
$queryBuilder->createNamedParameter($code)
)
)
->execute()
->fetchAssociative()
;
if (false === $product) {
$this->view->assign('value', ['error' => 'Product not found']);
$this->response->setStatus(404);
return;
}
$images = $this->productImageService->getImages((int) $product['uid']);
$response = [
'name' => $product['name'],
'code' => $product['code'],
'headline' => $product['headline'],
'teaser' => $product['teaser'],
'teaserLong' => $product['teaser_long'],
'conceptDescription' => $product['concept_description'],
'boardDescription' => $product['board_description'],
'programmeDescription' => $product['programme_description'],
'includedServices' => $product['included_services'],
'additionalServices' => $product['additional_services'],
'skiPassAltTitle' => $product['ski_pass_alt_title'],
'skiPassDescription' => $product['ski_pass_description'],
'images' => $images['resized'] ?? [],
];
$this->view->assign('value', $response);
}
/**
* Returns hotel details in JSON format for API consumption.
*/
public function hotelAction(string $code): void
{
$queryBuilder = $this->connectionPool->getQueryBuilderForTable('tx_epproducts_domain_model_hotel');
$hotel = $queryBuilder
->select(
'uid',
'name',
'short_name',
'code',
'headline',
'teaser',
'header_title',
'header_subtitle',
'description',
'features',
'room_types',
'additional_information',
'address',
'latitude',
'longitude',
'category',
'type',
'external_link'
)
->from('tx_epproducts_domain_model_hotel')
->where(
$queryBuilder->expr()->eq(
'code',
$queryBuilder->createNamedParameter($code)
)
)
->execute()
->fetchAssociative()
;
if (false === $hotel) {
$this->view->assign('value', ['error' => 'Hotel not found']);
$this->response->setStatus(404);
return;
}
$images = $this->hotelImageService->getImages((int) $hotel['uid']);
$response = [
'name' => $hotel['name'],
'shortName' => $hotel['short_name'],
'code' => $hotel['code'],
'headline' => $hotel['headline'],
'teaser' => $hotel['teaser'],
'headerTitle' => $hotel['header_title'],
'headerSubtitle' => $hotel['header_subtitle'],
'description' => $hotel['description'],
'features' => $hotel['features'],
'roomTypes' => $hotel['room_types'],
'additionalInformation' => $hotel['additional_information'],
'address' => $hotel['address'],
'latitude' => $hotel['latitude'],
'longitude' => $hotel['longitude'],
'category' => $hotel['category'],
'type' => $hotel['type'],
'externalLink' => $hotel['external_link'],
'images' => $images['resized'] ?? [],
];
$this->view->assign('value', $response);
}
}
@@ -0,0 +1,153 @@
<?php
namespace EP\EpProducts\Middleware;
use EP\EpProducts\Service\HotelImageService;
use EP\EpProducts\Service\ProductImageService;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Http\JsonResponse;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class ProductApiMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ('/api/product' !== $request->getUri()->getPath()) {
return $handler->handle($request);
}
// This is currently fake, we only check for existence. Since we are communicating
// on the backchannel only it's not much of a problem.
$apiKey = $request->getQueryParams()['key'] ?? null;
if (null === $apiKey) {
return (new JsonResponse(['message' => 'No api key provided'], 403));
}
$productCode = $request->getQueryParams()['product'] ?? null;
$hotelCode = $request->getQueryParams()['hotel'] ?? null;
if (null === $productCode) {
return (new JsonResponse(['message' => 'No product code provided'], 400));
}
$data = $this->fetchProductData($productCode);
if (null === $data) {
return (new JsonResponse(['message' => 'Product not found'], 404));
}
if (null !== $hotelCode) {
$data['hotel'] = $this->fetchHotelData($hotelCode);
}
return new JsonResponse($data);
}
private function fetchProductData(string $productCode): ?array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_epproducts_domain_model_product');
$product = $queryBuilder
->select(
'uid',
'name',
'code',
'headline',
'teaser',
'teaser_long',
'concept_description',
'board_description',
'programme_description',
'included_services',
'additional_services',
'ski_pass_alt_title',
'ski_pass_description'
)
->from('tx_epproducts_domain_model_product')
->where(
$queryBuilder->expr()->eq(
'code',
$queryBuilder->createNamedParameter($productCode)
)
)
->setMaxResults(1)
->execute()
->fetchAssociative()
;
if (false === $product) {
return null;
}
$images = GeneralUtility::makeInstance(ProductImageService::class)
->getImages((int) $product['uid']);
return [
'name' => $product['name'],
'code' => $product['code'],
'headline' => $product['headline'],
'teaser' => $product['teaser'],
'teaserLong' => $product['teaser_long'],
'conceptDescription' => $product['concept_description'],
'boardDescription' => $product['board_description'],
'programmeDescription' => $product['programme_description'],
'includedServices' => $product['included_services'],
'additionalServices' => $product['additional_services'],
'skiPassAltTitle' => $product['ski_pass_alt_title'],
'skiPassDescription' => $product['ski_pass_description'],
'images' => $images,
];
}
private function fetchHotelData(string $hotelCode): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_epproducts_domain_model_hotel');
$hotel = $queryBuilder
->select(
'hotel.uid',
'hotel.name',
'hotel.code',
'hotel.description',
'hotel.features',
'hotel.room_types',
'hotel.additional_information',
)
->from('tx_epproducts_domain_model_hotel', 'hotel')
->leftJoin('hotel', 'tx_epproducts_domain_model_matchcode', 'alias', 'hotel.uid = alias.uid AND alias.entity_type="hotel"')
->where(
$queryBuilder->expr()->orX(
$queryBuilder->expr()->eq('hotel.code', $queryBuilder->createNamedParameter($hotelCode)),
$queryBuilder->expr()->eq('alias.code', $queryBuilder->createNamedParameter($hotelCode)),
)
)
->setMaxResults(1)
->execute()
->fetchAssociative()
;
if (false === $hotel) {
return [];
}
$images = GeneralUtility::makeInstance(HotelImageService::class)
->getImages((int) $hotel['uid']);
return [
'name' => $hotel['name'],
'code' => $hotel['code'],
'description' => $hotel['description'],
'features' => $hotel['features'],
'roomTypes' => $hotel['room_types'],
'additionalInformation' => $hotel['additional_information'],
'images' => $images,
];
}
}
@@ -11,5 +11,14 @@ return [
'typo3/cms-frontend/static-route-resolver', 'typo3/cms-frontend/static-route-resolver',
], ],
], ],
'ep/productapi' => [
'target' => \EP\EpProducts\Middleware\ProductApiMiddleware::class,
'before' => [
'typo3/cms-frontend/page-resolver',
],
'after' => [
'typo3/cms-frontend/site',
],
],
], ],
]; ];