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 9, 2021
1 parent e125bb1 commit fd6e817
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 0 deletions.
15 changes: 15 additions & 0 deletions features/product/viewing_products/viewing_product_via_slug.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@viewing_products
Feature: Viewing a product details using slug
In order to see products detailed information
As a Visitor
I want to be able to view a single product using slug

Background:
Given the store operates on a single channel in "United States"
And the store has a product "T-shirt banana"
And the description of product "T-shirt banana" is "You must have this beautiful T-shirt"

@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
23 changes: 23 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,29 @@ 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
{
/** @var ProductVariantInterface $productVariant */
$productVariant = $product->getVariants()->first();

$this->client->showByIri('/api/v2/shop/products/slug/'.$product->getSlug());

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

/**
* @Then I should be redirected to :product product
*/
public function iShouldBeRedirectedToProduct(ProductInterface $product) {
$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
63 changes: 63 additions & 0 deletions src/Sylius/Bundle/ApiBundle/Controller/GetProductBySlugAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?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;
}
}

0 comments on commit fd6e817

Please sign in to comment.