Skip to content

Commit

Permalink
Get product by slug api endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
paullla committed Nov 11, 2021
1 parent e125bb1 commit 840f618
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ Feature: Viewing a product details using permalink

Background:
Given the store operates on a single channel in "United States"
And the store has a product "T-shirt banana"

@ui
@ui @no-api
Scenario: Accessing a detailed product page using permalink
Given the store has a product "T-shirt banana"
When I open page "en_US/products/t-shirt-banana"
Then I should be on "T-shirt banana" product detailed page
And I should see the product name "T-shirt banana"

@api
Scenario: Viewing a detailed page with product's slug
When I view product "T-shirt banana" using slug
Then I should be redirected to "T-shirt banana" product
20 changes: 20 additions & 0 deletions src/Sylius/Behat/Context/Api/Shop/ProductContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,26 @@ public function iOpenProductPage(ProductInterface $product): void
$this->sharedStorage->set('product_variant', $productVariant);
}

/**
* @When I view product :product using slug
*/
public function iViewProductUsingSlug(ProductInterface $product): void
{
$this->client->showByIri('/api/v2/shop/products/slug/'.$product->getSlug());

$this->sharedStorage->set('product', $product);
}

/**
* @Then I should be redirected to :product product
*/
public function iShouldBeRedirectedToProduct(ProductInterface $product): void
{
$response = $this->client->getLastResponse();

Assert::eq($response->headers->get('Location'), '/api/v2/shop/products/'.$product->getCode());
}

/**
* @When I browse products from taxon :taxon
*/
Expand Down
69 changes: 69 additions & 0 deletions src/Sylius/Bundle/ApiBundle/Controller/GetProductBySlugAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ApiBundle\Controller;

use ApiPlatform\Core\Api\IriConverterInterface;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Core\Repository\ProductRepositoryInterface;
use Sylius\Component\Locale\Context\LocaleContextInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

final class GetProductBySlugAction
{
private ChannelContextInterface $channelContext;
private LocaleContextInterface $localeContext;
private ProductRepositoryInterface $productRepository;
private IriConverterInterface $iriConverter;
private RequestStack $requestStack;

public function __construct(
ChannelContextInterface $channelContext,
LocaleContextInterface $localeContext,
ProductRepositoryInterface $productRepository,
IriConverterInterface $iriConverter,
RequestStack $requestStack
) {
$this->channelContext = $channelContext;
$this->localeContext = $localeContext;
$this->productRepository = $productRepository;
$this->iriConverter = $iriConverter;
$this->requestStack = $requestStack;
}

public function __invoke(string $slug): RedirectResponse
{
$channel = $this->channelContext->getChannel();
$locale = $this->localeContext->getLocaleCode();

$product = $this->productRepository->findOneByChannelAndSlug($channel, $locale, $slug);

if (null === $product) {
throw new NotFoundHttpException('Not Found');
}

$iri = $this->iriConverter->getIriFromItem($product);

$request = $this->requestStack->getCurrentRequest();

$requestQuery = $request->getQueryString();
if (null !== $requestQuery) {
$iri .= sprintf('?%s', $requestQuery);
}

return new RedirectResponse($iri, Response::HTTP_MOVED_PERMANENTLY);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,29 @@
</attribute>
</itemOperation>

<itemOperation name="shop_get_by_slug">
<attribute name="method">GET</attribute>
<attribute name="path">/shop/products/slug/{slug}</attribute>
<attribute name="controller">Sylius\Bundle\ApiBundle\Controller\GetProductBySlugAction</attribute>
<attribute name="read">false</attribute>
<attribute name="openapi_context">
<attribute name="summary">Use slug to retrieve a product resource.</attribute>
<attribute name="parameters">
<attribute>
<attribute name="name">slug</attribute>
<attribute name="in">path</attribute>
<attribute name="required">true</attribute>
<attribute name="schema">
<attribute name="type">string</attribute>
</attribute>
</attribute>
</attribute>
</attribute>
<attribute name="normalization_context">
<attribute name="groups">shop:product:read</attribute>
</attribute>
</itemOperation>

<itemOperation name="admin_put">
<attribute name="method">PUT</attribute>
<attribute name="path">/admin/products/{code}</attribute>
Expand Down
8 changes: 8 additions & 0 deletions src/Sylius/Bundle/ApiBundle/Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
<argument type="service" id="api_platform.iri_converter" />
</service>

<service id="Sylius\Bundle\ApiBundle\Controller\GetProductBySlugAction" public="true">
<argument type="service" id="sylius.context.channel" />
<argument type="service" id="sylius.context.locale" />
<argument type="service" id="sylius.repository.product" />
<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="request_stack" />
</service>

<service id="api_platform.swagger.action.ui" class="Sylius\Bundle\ApiBundle\ApiPlatform\Bridge\Symfony\Bundle\Action\SwaggerUiAction" public="true">
<argument type="service" id="api_platform.metadata.resource.name_collection_factory" />
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@
<argument type="service" id="Sylius\Bundle\ApiBundle\Provider\ProductImageFilterProviderInterface" />
</service>

<service
id="Sylius\Bundle\ApiBundle\Swagger\ProductSlugDocumentationNormalizer"
decorates="api_platform.swagger.normalizer.documentation"
autoconfigure="false"
decoration-priority="20"
>
<argument type="service" id="Sylius\Bundle\ApiBundle\Swagger\ProductSlugDocumentationNormalizer.inner" />
</service>

<service
id="Sylius\Bundle\ApiBundle\Swagger\ProductVariantDocumentationNormalizer"
decorates="api_platform.swagger.normalizer.documentation"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Bundle\ApiBundle\Swagger;

use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/** @experimental */
final class ProductSlugDocumentationNormalizer implements NormalizerInterface
{
private const PRODUCT_SLUG_PATH = '/api/v2/shop/products/slug/{slug}';

private NormalizerInterface $decoratedNormalizer;

public function __construct(NormalizerInterface $decoratedNormalizer)
{
$this->decoratedNormalizer = $decoratedNormalizer;
}

public function supportsNormalization($data, $format = null)
{
return $this->decoratedNormalizer->supportsNormalization($data, $format);
}

public function normalize($object, $format = null, array $context = [])
{
$docs = $this->decoratedNormalizer->normalize($object, $format, $context);

$params = $docs['paths'][self::PRODUCT_SLUG_PATH]['get']['parameters'];

foreach ($params as $index => $param) {
if ($param['name'] === 'code') {
unset($docs['paths'][self::PRODUCT_SLUG_PATH]['get']['parameters'][$index]);
}
}

return $docs;
}
}
32 changes: 32 additions & 0 deletions tests/Api/Shop/ProductsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the Sylius package.
*
* (c) Paweł Jędrzejewski
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Sylius\Tests\Api\Shop;

use Sylius\Tests\Api\JsonApiTestCase;
use Symfony\Component\HttpFoundation\Response;

class ProductsTest extends JsonApiTestCase
{
/** @test */
public function it_preserves_query_param_when_redirecting_from_product_slug_to_product_code(): void
{
$this->loadFixturesFromFile('product_variant_with_original_price.yaml');

$this->client->request('GET', '/api/v2/shop/products/slug/mug?paramName=paramValue', [], [], self::CONTENT_TYPE_HEADER);
$response = $this->client->getResponse();

$this->assertEquals('/api/v2/shop/products/MUG?paramName=paramValue', $response->headers->get(('Location')));
$this->assertResponseCode($response, Response::HTTP_MOVED_PERMANENTLY);
}
}

0 comments on commit 840f618

Please sign in to comment.