Skip to content

Commit

Permalink
Import product created or updated via webhook (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
lruozzi9 committed Nov 24, 2023
1 parent 254c9f3 commit ad4ce0f
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 1 deletion.
10 changes: 10 additions & 0 deletions config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@
</call>
</service>

<service id="webgriffe_sylius_akeneo.controller.webhook" class="Webgriffe\SyliusAkeneoPlugin\Controller\WebhookController" >
<tag name="controller.service_arguments"/>
<argument type="service" id="monolog.logger.webgriffe_sylius_akeneo_plugin" />
<argument type="service" id="webgriffe_sylius_akeneo.command_bus" />
<argument type="string">%webgriffe_sylius_akeneo.webhook_secret%</argument>
<call method="setContainer">
<argument type="service" id="service_container" />
</call>
</service>

<service id="webgriffe_sylius_akeneo.temporary_file_manager" class="Webgriffe\SyliusAkeneoPlugin\TemporaryFilesManager">
<argument type="service" id="filesystem" />
<argument type="service">
Expand Down
4 changes: 4 additions & 0 deletions config/webhook_routing.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
webgriffe_sylius_akeneo_webhook:
path: /akeneo/webhook
methods: [POST]
controller: webgriffe_sylius_akeneo.controller.webhook::postAction
125 changes: 125 additions & 0 deletions src/Controller/WebhookController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

declare(strict_types=1);

namespace Webgriffe\SyliusAkeneoPlugin\Controller;

use Psr\Log\LoggerInterface;
use RuntimeException;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Webgriffe\SyliusAkeneoPlugin\Message\ItemImport;
use Webgriffe\SyliusAkeneoPlugin\Product\Importer as ProductImporter;
use Webgriffe\SyliusAkeneoPlugin\ProductAssociations\Importer as ProductAssociationsImporter;

/**
* @psalm-type AkeneoEventProduct = array{
* uuid: string,
* identifier: string,
* enabled: bool,
* family: string,
* categories: string[],
* groups: string[],
* parent: ?string,
* values: array<string, array>,
* created: string,
* updated: string,
* associations: array<string, array>,
* quantified_associations: array<string, array>,
* }
* @psalm-type AkeneoEventProductModel = array{
* code: string,
* family: string,
* family_variant: string,
* parent: ?string,
* categories: string[],
* values: array<string, array>,
* created: string,
* updated: string,
* associations: array<string, array>,
* quantified_associations: array<string, array>,
* }
* @psalm-type AkeneoEvent = array{
* action: string,
* event_id: string,
* event_datetime: string,
* author: string,
* author_type: string,
* pim_source: string,
* data: array{
* resource: AkeneoEventProduct|AkeneoEventProductModel
* },
* }
* @psalm-type AkeneoEvents = array{
* events: AkeneoEvent[],
* }
*/
final class WebhookController extends AbstractController
{
public function __construct(
private LoggerInterface $logger,
private MessageBusInterface $messageBus,
private string $secret,
) {
}

/**
* As guideline see the documentation here: https://api.akeneo.com/getting-started/quick-start-my-first-webhook-5x/step-2.html
*
* @throws RuntimeException
* @throws \JsonException
*/
public function postAction(Request $request): Response
{
$timestamp = $request->headers->get('x-akeneo-request-timestamp');
$signature = $request->headers->get('x-akeneo-request-signature');
if (null === $timestamp || null === $signature) {
return new Response('', Response::HTTP_UNAUTHORIZED);
}

$body = $request->getContent();
$expectedSignature = hash_hmac('sha256', $timestamp . '.' . $body, $this->secret);
if (false === hash_equals($signature, $expectedSignature)) {
return new Response('', Response::HTTP_UNAUTHORIZED);
}
if (time() - (int) $timestamp > 300) {
throw new RuntimeException('Request is too old (> 5min)');
}

$this->logger->debug($body);

Check failure on line 91 in src/Controller/WebhookController.php

View workflow job for this annotation

GitHub Actions / Sylius ^1.12, PHP 8.0, Symfony ^5.4, MySQL 8.0

Parameter #1 $message of method Psr\Log\LoggerInterface::debug() expects string|Stringable, resource|string given.

/**
* @TODO Could this be improved by using serializer? Is it necessary or overwork?
*
* @var AkeneoEvents $akeneoEvents
*/
$akeneoEvents = json_decode($body, true, 512, \JSON_THROW_ON_ERROR);

Check failure on line 98 in src/Controller/WebhookController.php

View workflow job for this annotation

GitHub Actions / Sylius ^1.12, PHP 8.0, Symfony ^5.4, MySQL 8.0

Parameter #1 $json of function json_decode expects string, resource|string given.

foreach ($akeneoEvents['events'] as $akeneoEvent) {
$resource = $akeneoEvent['data']['resource'];
if (array_key_exists('identifier', $resource)) {
$productCode = $resource['identifier'];
$this->logger->debug(sprintf(
'Dispatching product import message for %s',
$productCode,
));
$this->messageBus->dispatch(new ItemImport(
ProductImporter::AKENEO_ENTITY,
$productCode,
));
$this->logger->debug(sprintf(
'Dispatching product associations import message for %s',
$productCode,
));
$this->messageBus->dispatch(new ItemImport(
ProductAssociationsImporter::AKENEO_ENTITY,
$productCode,
));
}
}

return new Response();
}
}
3 changes: 3 additions & 0 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()

->scalarNode('webhook_secret')
->end()

->arrayNode('value_handlers')
->children()
->arrayNode('product')
Expand Down
6 changes: 6 additions & 0 deletions src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public function load(array $configs, ContainerBuilder $container): void
$this->registerResources('webgriffe_sylius_akeneo', 'doctrine/orm', $config['resources'], $container);

$this->registerApiClientParameters($config['api_client'], $container);
$this->registerWebhookParameters($config['webhook_secret'], $container);

Check failure on line 125 in src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php

View workflow job for this annotation

GitHub Actions / Sylius ^1.12, PHP 8.1, Symfony ^5.4, MySQL 8.0

MixedArgument

src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php:125:42: MixedArgument: Argument 1 of Webgriffe\SyliusAkeneoPlugin\DependencyInjection\WebgriffeSyliusAkeneoExtension::registerWebhookParameters cannot be mixed, expecting string (see https://psalm.dev/030)

Check failure on line 125 in src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php

View workflow job for this annotation

GitHub Actions / Sylius ^1.12, PHP 8.1, Symfony ^6.3, MySQL 8.0

MixedArgument

src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php:125:42: MixedArgument: Argument 1 of Webgriffe\SyliusAkeneoPlugin\DependencyInjection\WebgriffeSyliusAkeneoExtension::registerWebhookParameters cannot be mixed, expecting string (see https://psalm.dev/030)

Check failure on line 125 in src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php

View workflow job for this annotation

GitHub Actions / Sylius ^1.12, PHP 8.2, Symfony ^5.4, MySQL 8.0

MixedArgument

src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php:125:42: MixedArgument: Argument 1 of Webgriffe\SyliusAkeneoPlugin\DependencyInjection\WebgriffeSyliusAkeneoExtension::registerWebhookParameters cannot be mixed, expecting string (see https://psalm.dev/030)

Check failure on line 125 in src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php

View workflow job for this annotation

GitHub Actions / Sylius ^1.12, PHP 8.2, Symfony ^6.3, MySQL 8.0

MixedArgument

src/DependencyInjection/WebgriffeSyliusAkeneoExtension.php:125:42: MixedArgument: Argument 1 of Webgriffe\SyliusAkeneoPlugin\DependencyInjection\WebgriffeSyliusAkeneoExtension::registerWebhookParameters cannot be mixed, expecting string (see https://psalm.dev/030)

$loader->load('services.xml');

Expand Down Expand Up @@ -252,4 +253,9 @@ private function registerTemporaryDirectoryParameter(ContainerBuilder $container
}
$container->setParameter($parameterKey, sys_get_temp_dir());
}

private function registerWebhookParameters(string $webhookSecret, ContainerBuilder $container): void
{
$container->setParameter('webgriffe_sylius_akeneo.webhook_secret', $webhookSecret);
}
}
2 changes: 1 addition & 1 deletion src/ProductAssociations/Importer.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

final class Importer implements ImporterInterface
{
private const AKENEO_ENTITY = 'ProductAssociations';
public const AKENEO_ENTITY = 'ProductAssociations';

/**
* @param RepositoryInterface<ProductAssociationInterface> $productAssociationRepository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ webgriffe_sylius_akeneo:
password: '%env(WEBGRIFFE_SYLIUS_AKENEO_PLUGIN_PASSWORD)%'
client_id: '%env(WEBGRIFFE_SYLIUS_AKENEO_PLUGIN_CLIENT_ID)%'
secret: '%env(WEBGRIFFE_SYLIUS_AKENEO_PLUGIN_SECRET)%'

webhook_secret: '%env(WEBGRIFFE_SYLIUS_AKENEO_PLUGIN_WEBHOOK_SECRET)%'

value_handlers:
product:
Expand Down
3 changes: 3 additions & 0 deletions tests/Application/config/routes.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
webgriffe_sylius_akeneo_plugin_admin:
resource: "@WebgriffeSyliusAkeneoPlugin/config/admin_routing.yaml"
prefix: '/%sylius_admin.path_name%'

webgriffe_sylius_akeneo_plugin_webhook:
resource: "@WebgriffeSyliusAkeneoPlugin/config/webhook_routing.yaml"

0 comments on commit ad4ce0f

Please sign in to comment.