Files
kupshop/bundles/KupShop/GraphQLBundle/Controller/AdminController.php
2025-08-02 16:30:27 +02:00

183 lines
6.2 KiB
PHP

<?php
namespace KupShop\GraphQLBundle\Controller;
use GraphQL\Executor\ExecutionResult;
use GraphQL\GraphQL;
use KupShop\AdminBundle\Util\LegacyAdminCredentials;
use KupShop\GraphQLBundle\Util\SchemaFactory;
use KupShop\KupShopBundle\Context\PosContext;
use KupShop\KupShopBundle\Routing\AdminRoute;
use KupShop\KupShopBundle\Util\Logging\SentryLogger;
use KupShop\KupShopBundle\Util\RequestUtil;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use TheCodingMachine\GraphQLite\Context\Context;
class AdminController
{
public function __construct(
private SchemaFactory $schemaFactory,
private SentryLogger $sentryLogger,
private LoggerInterface $logger,
public PosContext $posContext,
) {
}
/**
* @AdminRoute("/graphql{slash}", requirements={"slash": "\/$"}, defaults={"slash": "/"})
*/
public function graphQLAdmin(Request $request, LegacyAdminCredentials $legacyAdminCredentials): Response
{
if (!findModule(\Modules::GRAPHQL, \Modules::SUB_GRAPHQL_ADMIN)) {
throw new NotFoundHttpException();
}
increaseMaxExecutionTime(120);
increaseMemoryLimit(512);
// CORS check - return 200
if ($request->isMethod('OPTIONS')) {
return $this->getAPIResponse([]);
}
$token = $request->headers->get('X-Access-Token');
if (!$legacyAdminCredentials->isLogged() && (!$token || !$legacyAdminCredentials->loginByHash($token))) {
$this->log('Unauthorized access', [
'token' => $token,
'uri' => $request->getUri(),
'headers' => $request->headers->all(),
]);
return $this->getAPIResponse(['errors' => ['message' => 'Unauthorized access']], 401);
}
$legacyAdminCredentials->setAdminGlobalVars();
$admin = LegacyAdminCredentials::getAdminById($legacyAdminCredentials->getAdminID());
if (!findRight('API')) {
return $this->getAPIResponse(['errors' => ['message' => 'Used access token does not have access to the API']], 403);
}
$schema = $this->schemaFactory->createAdminSchema();
$input = json_decode($request->getContent(), true);
if (($query = ($input['query'] ?? null)) === null) {
return $this->getAPIResponse(['errors' => ['message' => 'Invalid request! Missing "query" param']], 400);
}
$variableValues = $input['variables'] ?? null;
$result = GraphQL::executeQuery($schema, $query, null, new Context(), $variableValues);
$output = $result->toArray();
$this->captureGraphQLRequest($input, $admin);
$this->captureGraphQLErrors($result);
return $this->getAPIResponse($output);
}
private function captureGraphQLRequest(array $input, ?array $admin = null): void
{
$this->log('API Request', [
'input' => $input,
'admin' => ['id' => $admin['id'], 'email' => $admin['email'], 'login' => $admin['login'], 'name' => $admin['name'] ?? ''],
]);
}
private function captureGraphQLErrors(ExecutionResult $result): void
{
foreach ($result->errors ?? [] as $error) {
// pokud je chyba client safe, tak ji neloguju do sentry
if ($error->isClientSafe()) {
continue;
}
if (isLocalDevelopment()) {
throw $error;
}
$this->sentryLogger->captureException($error);
}
}
private function log(string $message, array $data): void
{
if (isLocalDevelopment()) {
return;
}
$this->logger->notice("[GraphQL] {$message}", $data);
}
private function getAPIResponse(array $data, int $code = 200): JsonResponse
{
return new JsonResponse(
$data,
$code,
[
'Content-Type' => 'application/json',
'Access-Control-Allow-Origin' => '*',
'Access-Control-Allow-Headers' => '*',
]
);
}
/**
* @AdminRoute("/pos/graphql{slash}", requirements={"slash": "\/$"}, defaults={"slash": "/"})
*/
public function graphQLPos(Request $request, LegacyAdminCredentials $legacyAdminCredentials, RequestUtil $requestUtil): Response
{
if (!findModule(\Modules::NEW_POS)) {
throw new NotFoundHttpException();
}
// Checks if the request has a pos ID in the header and activates context
if ($idPos = PosContext::getIdFromRequest($request)) {
$this->posContext->activate($idPos);
}
// Checks the API versions, and if they differ, returns 426 Upgrade Required.
if ($this->posContext->getApiVersion() !== PosContext::getVersionFromRequest($request)) {
return $this->getAPIResponse([], 426);
}
// CORS check - return 200
if ($request->isMethod('OPTIONS')) {
return $this->getAPIResponse([]);
}
$token = $request->headers->get('X-Access-Token');
if (!$legacyAdminCredentials->isLogged() && (!$token || !$legacyAdminCredentials->loginByHash($token))) {
return $this->getAPIResponse(['errors' => ['message' => 'Unauthorized access']], 401);
}
$legacyAdminCredentials->setAdminGlobalVars();
if (!findRight('POS_API')) {
return $this->getAPIResponse(['errors' => ['message' => 'User does not have access']], 403);
}
$schema = $this->schemaFactory->createPosSchema();
$input = json_decode($request->getContent(), true);
if (!$input) {
return $this->getAPIResponse(['errors' => ['message' => 'Unprocessable Entity - empty input']], 422);
}
if (preg_match('/^\s*(query|mutation)\s*(\w+)/i', $input['query'], $matches)) {
$requestUtil->addTransactionInfo($request, ['query' => $matches[2]]);
}
$result = GraphQL::executeQuery($schema, $input['query'], null, new Context(), $input['variables'] ?? null);
$output = $result->toArray();
return $this->getAPIResponse($output);
}
}