From f832c56adbaea4b445b001025b64311043d04b34 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Thu, 21 Jul 2022 20:07:28 +0200 Subject: [PATCH 01/12] Moved Mercure and RabbitMq event listeners to their own subnamespaces --- .../Core/config/event_dispatcher.config.php | 20 +++++++++++------- .../EventDispatcher/Event/ShortUrlCreated.php | 21 +++++++++++++++++++ .../Mercure/NotifyNewShortUrlToMercure.php | 15 +++++++++++++ .../{ => Mercure}/NotifyVisitToMercure.php | 2 +- .../RabbitMq/NotifyNewShortUrlToRabbitMq.php | 15 +++++++++++++ .../{ => RabbitMq}/NotifyVisitToRabbitMq.php | 2 +- .../NotifyVisitToMercureTest.php | 4 ++-- .../NotifyVisitToRabbitMqTest.php | 6 +++--- 8 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 module/Core/src/EventDispatcher/Event/ShortUrlCreated.php create mode 100644 module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php rename module/Core/src/EventDispatcher/{ => Mercure}/NotifyVisitToMercure.php (97%) create mode 100644 module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php rename module/Core/src/EventDispatcher/{ => RabbitMq}/NotifyVisitToRabbitMq.php (98%) rename module/Core/test/EventDispatcher/{ => Mercure}/NotifyVisitToMercureTest.php (98%) rename module/Core/test/EventDispatcher/{ => RabbitMq}/NotifyVisitToRabbitMqTest.php (96%) diff --git a/module/Core/config/event_dispatcher.config.php b/module/Core/config/event_dispatcher.config.php index d47cc128..e1342ebf 100644 --- a/module/Core/config/event_dispatcher.config.php +++ b/module/Core/config/event_dispatcher.config.php @@ -22,11 +22,15 @@ return [ ], 'async' => [ EventDispatcher\Event\VisitLocated::class => [ - EventDispatcher\NotifyVisitToMercure::class, - EventDispatcher\NotifyVisitToRabbitMq::class, + EventDispatcher\Mercure\NotifyVisitToMercure::class, + EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class, EventDispatcher\NotifyVisitToWebHooks::class, EventDispatcher\UpdateGeoLiteDb::class, ], +// EventDispatcher\Event\ShortUrlCreated::class => [ +// EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class, +// EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class, +// ], ], ], @@ -34,16 +38,16 @@ return [ 'factories' => [ EventDispatcher\LocateVisit::class => ConfigAbstractFactory::class, EventDispatcher\NotifyVisitToWebHooks::class => ConfigAbstractFactory::class, - EventDispatcher\NotifyVisitToMercure::class => ConfigAbstractFactory::class, - EventDispatcher\NotifyVisitToRabbitMq::class => ConfigAbstractFactory::class, + EventDispatcher\Mercure\NotifyVisitToMercure::class => ConfigAbstractFactory::class, + EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => ConfigAbstractFactory::class, EventDispatcher\UpdateGeoLiteDb::class => ConfigAbstractFactory::class, ], 'delegators' => [ - EventDispatcher\NotifyVisitToMercure::class => [ + EventDispatcher\Mercure\NotifyVisitToMercure::class => [ EventDispatcher\CloseDbConnectionEventListenerDelegator::class, ], - EventDispatcher\NotifyVisitToRabbitMq::class => [ + EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => [ EventDispatcher\CloseDbConnectionEventListenerDelegator::class, ], EventDispatcher\NotifyVisitToWebHooks::class => [ @@ -68,13 +72,13 @@ return [ ShortUrl\Transformer\ShortUrlDataTransformer::class, Options\AppOptions::class, ], - EventDispatcher\NotifyVisitToMercure::class => [ + EventDispatcher\Mercure\NotifyVisitToMercure::class => [ Hub::class, Mercure\MercureUpdatesGenerator::class, 'em', 'Logger_Shlink', ], - EventDispatcher\NotifyVisitToRabbitMq::class => [ + EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => [ AMQPStreamConnection::class, 'em', 'Logger_Shlink', diff --git a/module/Core/src/EventDispatcher/Event/ShortUrlCreated.php b/module/Core/src/EventDispatcher/Event/ShortUrlCreated.php new file mode 100644 index 00000000..9786808f --- /dev/null +++ b/module/Core/src/EventDispatcher/Event/ShortUrlCreated.php @@ -0,0 +1,21 @@ + $this->shortUrlId, + ]; + } +} diff --git a/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php b/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php new file mode 100644 index 00000000..51fa3b58 --- /dev/null +++ b/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php @@ -0,0 +1,15 @@ +connection->reveal(), $this->em->reveal(), $this->logger->reveal(), From 67d91d5fc52c9b63a6e2118c10569d776074f174 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jul 2022 10:12:26 +0200 Subject: [PATCH 02/12] Migrated rabbit integration to RabbitMqPublishingHelper from shlink-common --- composer.json | 3 +- config/autoload/rabbit.global.php | 29 -------- .../Core/config/event_dispatcher.config.php | 12 +-- .../Mercure/NotifyNewShortUrlToMercure.php | 2 +- .../RabbitMq/NotifyNewShortUrlToRabbitMq.php | 73 ++++++++++++++++++- .../RabbitMq/NotifyVisitToRabbitMq.php | 44 +++-------- .../RabbitMq/NotifyVisitToRabbitMqTest.php | 52 ++++--------- 7 files changed, 104 insertions(+), 111 deletions(-) diff --git a/composer.json b/composer.json index 02a22264..f85c1a83 100644 --- a/composer.json +++ b/composer.json @@ -40,12 +40,11 @@ "mlocati/ip-lib": "^1.17", "ocramius/proxy-manager": "^2.11", "pagerfanta/core": "^3.5", - "php-amqplib/php-amqplib": "^3.1", "php-middleware/request-id": "^4.1", "predis/predis": "^1.1", "pugx/shortid-php": "^1.0", "ramsey/uuid": "^4.2", - "shlinkio/shlink-common": "dev-main#3244088 as 4.5", + "shlinkio/shlink-common": "dev-main#0396706 as 4.5", "shlinkio/shlink-config": "^1.6", "shlinkio/shlink-event-dispatcher": "^2.4", "shlinkio/shlink-importer": "^3.0", diff --git a/config/autoload/rabbit.global.php b/config/autoload/rabbit.global.php index a9764c8c..6f63eca6 100644 --- a/config/autoload/rabbit.global.php +++ b/config/autoload/rabbit.global.php @@ -2,9 +2,6 @@ declare(strict_types=1); -use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; -use Laminas\ServiceManager\Proxy\LazyServiceFactory; -use PhpAmqpLib\Connection\AMQPStreamConnection; use Shlinkio\Shlink\Core\Config\EnvVars; return [ @@ -18,30 +15,4 @@ return [ 'vhost' => EnvVars::RABBITMQ_VHOST->loadFromEnv('/'), ], - 'dependencies' => [ - 'factories' => [ - AMQPStreamConnection::class => ConfigAbstractFactory::class, - ], - 'delegators' => [ - AMQPStreamConnection::class => [ - LazyServiceFactory::class, - ], - ], - 'lazy_services' => [ - 'class_map' => [ - AMQPStreamConnection::class => AMQPStreamConnection::class, - ], - ], - ], - - ConfigAbstractFactory::class => [ - AMQPStreamConnection::class => [ - 'config.rabbitmq.host', - 'config.rabbitmq.port', - 'config.rabbitmq.user', - 'config.rabbitmq.password', - 'config.rabbitmq.vhost', - ], - ], - ]; diff --git a/module/Core/config/event_dispatcher.config.php b/module/Core/config/event_dispatcher.config.php index e1342ebf..be4872e2 100644 --- a/module/Core/config/event_dispatcher.config.php +++ b/module/Core/config/event_dispatcher.config.php @@ -5,9 +5,9 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core; use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory; -use PhpAmqpLib\Connection\AMQPStreamConnection; use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\CLI\Util\GeolocationDbUpdater; +use Shlinkio\Shlink\Common\RabbitMq\RabbitMqPublishingHelper; use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater; use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; use Symfony\Component\Mercure\Hub; @@ -27,10 +27,10 @@ return [ EventDispatcher\NotifyVisitToWebHooks::class, EventDispatcher\UpdateGeoLiteDb::class, ], -// EventDispatcher\Event\ShortUrlCreated::class => [ -// EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class, -// EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class, -// ], + EventDispatcher\Event\ShortUrlCreated::class => [ + EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class, + EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class, + ], ], ], @@ -79,7 +79,7 @@ return [ 'Logger_Shlink', ], EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => [ - AMQPStreamConnection::class, + RabbitMqPublishingHelper::class, 'em', 'Logger_Shlink', Visit\Transformer\OrphanVisitDataTransformer::class, diff --git a/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php b/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php index 51fa3b58..4ebb8536 100644 --- a/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php +++ b/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php @@ -8,7 +8,7 @@ use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated; class NotifyNewShortUrlToMercure { - public function __invoke(ShortUrlCreated $shortUrlCreated) + public function __invoke(ShortUrlCreated $shortUrlCreated): void { // TODO: Implement __invoke() method. } diff --git a/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php b/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php index dfce8419..ef38c356 100644 --- a/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php +++ b/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php @@ -4,12 +4,81 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\EventDispatcher\RabbitMq; +use Doctrine\ORM\EntityManagerInterface; +use PhpAmqpLib\Connection\AMQPStreamConnection; +use PhpAmqpLib\Exchange\AMQPExchangeType; +use PhpAmqpLib\Message\AMQPMessage; +use Psr\Log\LoggerInterface; +use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; +use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated; +use Throwable; + +use function Shlinkio\Shlink\Common\json_encode; class NotifyNewShortUrlToRabbitMq { - public function __invoke(ShortUrlCreated $shortUrlCreated) + private const NEW_SHORT_URL_QUEUE = 'https://shlink.io/new-short-url'; + + public function __construct( + private readonly AMQPStreamConnection $connection, + private readonly EntityManagerInterface $em, + private readonly LoggerInterface $logger, + private readonly DataTransformerInterface $shortUrlTransformer, + private readonly bool $isEnabled, + ) { + } + + public function __invoke(ShortUrlCreated $shortUrlCreated): void { - // TODO: Implement __invoke() method. + if (! $this->isEnabled) { + return; + } + + $shortUrlId = $shortUrlCreated->shortUrlId; + $shortUrl = $this->em->find(ShortUrl::class, $shortUrlId); + + if ($shortUrl === null) { + $this->logger->warning( + 'Tried to notify RabbitMQ for new short URL with id "{shortUrlId}", but it does not exist.', + ['shortUrlId' => $shortUrlId], + ); + return; + } + + if (! $this->connection->isConnected()) { + $this->connection->reconnect(); + } + + $queue = self::NEW_SHORT_URL_QUEUE; + $message = $this->shortUrlToMessage($shortUrl); + + try { + $channel = $this->connection->channel(); + + // Declare an exchange and a queue that will persist server restarts + $exchange = $queue; // We use the same name for the exchange and the queue + $channel->exchange_declare($exchange, AMQPExchangeType::DIRECT, false, true, false); + $channel->queue_declare($queue, false, true, false, false); + + // Bind the exchange and the queue together, and publish the message + $channel->queue_bind($queue, $exchange); + $channel->basic_publish($message, $exchange); + + $channel->close(); + } catch (Throwable $e) { + $this->logger->debug('Error while trying to notify RabbitMQ with new short URL. {e}', ['e' => $e]); + } finally { + $this->connection->close(); + } + } + + private function shortUrlToMessage(ShortUrl $shortUrl): AMQPMessage + { + $messageBody = json_encode($this->shortUrlTransformer->transform($shortUrl)); + return new AMQPMessage($messageBody, [ + 'content_type' => 'application/json', + 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT, + ]); } } diff --git a/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php b/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php index bec1ef94..929e37c0 100644 --- a/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php +++ b/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php @@ -5,16 +5,13 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\EventDispatcher\RabbitMq; use Doctrine\ORM\EntityManagerInterface; -use PhpAmqpLib\Connection\AMQPStreamConnection; -use PhpAmqpLib\Exchange\AMQPExchangeType; -use PhpAmqpLib\Message\AMQPMessage; use Psr\Log\LoggerInterface; +use Shlinkio\Shlink\Common\RabbitMq\RabbitMqPublishingHelperInterface; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated; use Throwable; -use function Shlinkio\Shlink\Common\json_encode; use function sprintf; class NotifyVisitToRabbitMq @@ -23,11 +20,11 @@ class NotifyVisitToRabbitMq private const NEW_ORPHAN_VISIT_QUEUE = 'https://shlink.io/new-orphan-visit'; public function __construct( - private AMQPStreamConnection $connection, - private EntityManagerInterface $em, - private LoggerInterface $logger, - private DataTransformerInterface $orphanVisitTransformer, - private bool $isEnabled, + private readonly RabbitMqPublishingHelperInterface $rabbitMqHelper, + private readonly EntityManagerInterface $em, + private readonly LoggerInterface $logger, + private readonly DataTransformerInterface $orphanVisitTransformer, + private readonly bool $isEnabled, ) { } @@ -47,32 +44,15 @@ class NotifyVisitToRabbitMq return; } - if (! $this->connection->isConnected()) { - $this->connection->reconnect(); - } - $queues = $this->determineQueuesToPublishTo($visit); - $message = $this->visitToMessage($visit); + $payload = $this->visitToPayload($visit); try { - $channel = $this->connection->channel(); - foreach ($queues as $queue) { - // Declare an exchange and a queue that will persist server restarts - $exchange = $queue; // We use the same name for the exchange and the queue - $channel->exchange_declare($exchange, AMQPExchangeType::DIRECT, false, true, false); - $channel->queue_declare($queue, false, true, false, false); - - // Bind the exchange and the queue together, and publish the message - $channel->queue_bind($queue, $exchange); - $channel->basic_publish($message, $exchange); + $this->rabbitMqHelper->publishPayloadInQueue($payload, $queue); } - - $channel->close(); } catch (Throwable $e) { $this->logger->debug('Error while trying to notify RabbitMQ with new visit. {e}', ['e' => $e]); - } finally { - $this->connection->close(); } } @@ -91,12 +71,8 @@ class NotifyVisitToRabbitMq ]; } - private function visitToMessage(Visit $visit): AMQPMessage + private function visitToPayload(Visit $visit): array { - $messageBody = json_encode(! $visit->isOrphan() ? $visit : $this->orphanVisitTransformer->transform($visit)); - return new AMQPMessage($messageBody, [ - 'content_type' => 'application/json', - 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT, - ]); + return ! $visit->isOrphan() ? $visit->jsonSerialize() : $this->orphanVisitTransformer->transform($visit); } } diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php index c39407f8..942a7bdc 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php @@ -7,14 +7,13 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher\RabbitMq; use Doctrine\ORM\EntityManagerInterface; use DomainException; use Exception; -use PhpAmqpLib\Channel\AMQPChannel; -use PhpAmqpLib\Connection\AMQPStreamConnection; use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; use Psr\Log\LoggerInterface; use RuntimeException; +use Shlinkio\Shlink\Common\RabbitMq\RabbitMqPublishingHelperInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated; @@ -32,25 +31,19 @@ class NotifyVisitToRabbitMqTest extends TestCase use ProphecyTrait; private NotifyVisitToRabbitMq $listener; - private ObjectProphecy $connection; + private ObjectProphecy $helper; private ObjectProphecy $em; private ObjectProphecy $logger; private ObjectProphecy $orphanVisitTransformer; - private ObjectProphecy $channel; protected function setUp(): void { - $this->channel = $this->prophesize(AMQPChannel::class); - - $this->connection = $this->prophesize(AMQPStreamConnection::class); - $this->connection->isConnected()->willReturn(false); - $this->connection->channel()->willReturn($this->channel->reveal()); - + $this->helper = $this->prophesize(RabbitMqPublishingHelperInterface::class); $this->em = $this->prophesize(EntityManagerInterface::class); $this->logger = $this->prophesize(LoggerInterface::class); $this->listener = new NotifyVisitToRabbitMq( - $this->connection->reveal(), + $this->helper->reveal(), $this->em->reveal(), $this->logger->reveal(), new OrphanVisitDataTransformer(), @@ -61,8 +54,8 @@ class NotifyVisitToRabbitMqTest extends TestCase /** @test */ public function doesNothingWhenTheFeatureIsNotEnabled(): void { - $listener = new \Shlinkio\Shlink\Core\EventDispatcher\RabbitMq\NotifyVisitToRabbitMq( - $this->connection->reveal(), + $listener = new NotifyVisitToRabbitMq( + $this->helper->reveal(), $this->em->reveal(), $this->logger->reveal(), new OrphanVisitDataTransformer(), @@ -74,8 +67,7 @@ class NotifyVisitToRabbitMqTest extends TestCase $this->em->find(Argument::cetera())->shouldNotHaveBeenCalled(); $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->connection->isConnected()->shouldNotHaveBeenCalled(); - $this->connection->close()->shouldNotHaveBeenCalled(); + $this->helper->publishPayloadInQueue(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @test */ @@ -93,8 +85,7 @@ class NotifyVisitToRabbitMqTest extends TestCase $findVisit->shouldHaveBeenCalledOnce(); $logWarning->shouldHaveBeenCalledOnce(); $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); - $this->connection->isConnected()->shouldNotHaveBeenCalled(); - $this->connection->close()->shouldNotHaveBeenCalled(); + $this->helper->publishPayloadInQueue(Argument::cetera())->shouldNotHaveBeenCalled(); } /** @@ -105,27 +96,17 @@ class NotifyVisitToRabbitMqTest extends TestCase { $visitId = '123'; $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit); - $argumentWithExpectedChannel = Argument::that(fn (string $channel) => contains($expectedChannels, $channel)); + $argumentWithExpectedChannels = Argument::that( + static fn (string $channel) => contains($expectedChannels, $channel), + ); ($this->listener)(new VisitLocated($visitId)); $findVisit->shouldHaveBeenCalledOnce(); - $this->channel->exchange_declare($argumentWithExpectedChannel, Argument::cetera())->shouldHaveBeenCalledTimes( - count($expectedChannels), - ); - $this->channel->queue_declare($argumentWithExpectedChannel, Argument::cetera())->shouldHaveBeenCalledTimes( - count($expectedChannels), - ); - $this->channel->queue_bind( - $argumentWithExpectedChannel, - $argumentWithExpectedChannel, + $this->helper->publishPayloadInQueue( + Argument::type('array'), + $argumentWithExpectedChannels, )->shouldHaveBeenCalledTimes(count($expectedChannels)); - $this->channel->basic_publish(Argument::any(), $argumentWithExpectedChannel)->shouldHaveBeenCalledTimes( - count($expectedChannels), - ); - $this->channel->close()->shouldHaveBeenCalledOnce(); - $this->connection->reconnect()->shouldHaveBeenCalledOnce(); - $this->connection->close()->shouldHaveBeenCalledOnce(); $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); } @@ -154,7 +135,7 @@ class NotifyVisitToRabbitMqTest extends TestCase { $visitId = '123'; $findVisit = $this->em->find(Visit::class, $visitId)->willReturn(Visit::forBasePath(Visitor::emptyInstance())); - $channel = $this->connection->channel()->willThrow($e); + $channel = $this->helper->publishPayloadInQueue(Argument::cetera())->willThrow($e); ($this->listener)(new VisitLocated($visitId)); @@ -162,11 +143,8 @@ class NotifyVisitToRabbitMqTest extends TestCase 'Error while trying to notify RabbitMQ with new visit. {e}', ['e' => $e], )->shouldHaveBeenCalledOnce(); - $this->connection->close()->shouldHaveBeenCalledOnce(); - $this->connection->reconnect()->shouldHaveBeenCalledOnce(); $findVisit->shouldHaveBeenCalledOnce(); $channel->shouldHaveBeenCalledOnce(); - $this->channel->close()->shouldNotHaveBeenCalled(); } public function provideExceptions(): iterable From 47bfa5fcc02439ce17334e36214cbd0767835176 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jul 2022 10:18:19 +0200 Subject: [PATCH 03/12] Simplified NotifyNewShortUrlToRabbitMq --- .../Core/config/event_dispatcher.config.php | 11 +++++ .../RabbitMq/NotifyNewShortUrlToRabbitMq.php | 42 +++---------------- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/module/Core/config/event_dispatcher.config.php b/module/Core/config/event_dispatcher.config.php index be4872e2..806d3104 100644 --- a/module/Core/config/event_dispatcher.config.php +++ b/module/Core/config/event_dispatcher.config.php @@ -40,6 +40,7 @@ return [ EventDispatcher\NotifyVisitToWebHooks::class => ConfigAbstractFactory::class, EventDispatcher\Mercure\NotifyVisitToMercure::class => ConfigAbstractFactory::class, EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => ConfigAbstractFactory::class, + EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class => ConfigAbstractFactory::class, EventDispatcher\UpdateGeoLiteDb::class => ConfigAbstractFactory::class, ], @@ -50,6 +51,9 @@ return [ EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => [ EventDispatcher\CloseDbConnectionEventListenerDelegator::class, ], + EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class => [ + EventDispatcher\CloseDbConnectionEventListenerDelegator::class, + ], EventDispatcher\NotifyVisitToWebHooks::class => [ EventDispatcher\CloseDbConnectionEventListenerDelegator::class, ], @@ -85,6 +89,13 @@ return [ Visit\Transformer\OrphanVisitDataTransformer::class, 'config.rabbitmq.enabled', ], + EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class => [ + RabbitMqPublishingHelper::class, + 'em', + 'Logger_Shlink', + ShortUrl\Transformer\ShortUrlDataTransformer::class, + 'config.rabbitmq.enabled', + ], EventDispatcher\UpdateGeoLiteDb::class => [GeolocationDbUpdater::class, 'Logger_Shlink'], ], diff --git a/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php b/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php index ef38c356..8e66dedb 100644 --- a/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php +++ b/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php @@ -5,23 +5,19 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\EventDispatcher\RabbitMq; use Doctrine\ORM\EntityManagerInterface; -use PhpAmqpLib\Connection\AMQPStreamConnection; -use PhpAmqpLib\Exchange\AMQPExchangeType; -use PhpAmqpLib\Message\AMQPMessage; use Psr\Log\LoggerInterface; +use Shlinkio\Shlink\Common\RabbitMq\RabbitMqPublishingHelperInterface; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated; use Throwable; -use function Shlinkio\Shlink\Common\json_encode; - class NotifyNewShortUrlToRabbitMq { private const NEW_SHORT_URL_QUEUE = 'https://shlink.io/new-short-url'; public function __construct( - private readonly AMQPStreamConnection $connection, + private readonly RabbitMqPublishingHelperInterface $rabbitMqHelper, private readonly EntityManagerInterface $em, private readonly LoggerInterface $logger, private readonly DataTransformerInterface $shortUrlTransformer, @@ -46,39 +42,13 @@ class NotifyNewShortUrlToRabbitMq return; } - if (! $this->connection->isConnected()) { - $this->connection->reconnect(); - } - - $queue = self::NEW_SHORT_URL_QUEUE; - $message = $this->shortUrlToMessage($shortUrl); - try { - $channel = $this->connection->channel(); - - // Declare an exchange and a queue that will persist server restarts - $exchange = $queue; // We use the same name for the exchange and the queue - $channel->exchange_declare($exchange, AMQPExchangeType::DIRECT, false, true, false); - $channel->queue_declare($queue, false, true, false, false); - - // Bind the exchange and the queue together, and publish the message - $channel->queue_bind($queue, $exchange); - $channel->basic_publish($message, $exchange); - - $channel->close(); + $this->rabbitMqHelper->publishPayloadInQueue( + $this->shortUrlTransformer->transform($shortUrl), + self::NEW_SHORT_URL_QUEUE, + ); } catch (Throwable $e) { $this->logger->debug('Error while trying to notify RabbitMQ with new short URL. {e}', ['e' => $e]); - } finally { - $this->connection->close(); } } - - private function shortUrlToMessage(ShortUrl $shortUrl): AMQPMessage - { - $messageBody = json_encode($this->shortUrlTransformer->transform($shortUrl)); - return new AMQPMessage($messageBody, [ - 'content_type' => 'application/json', - 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT, - ]); - } } From 405c6de59166a5921773f37dd35ecf5e1b0591e6 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jul 2022 10:53:42 +0200 Subject: [PATCH 04/12] Created NotifyNewShortUrlToRabbitMq test --- .../NotifyNewShortUrlToRabbitMqTest.php | 127 ++++++++++++++++++ .../RabbitMq/NotifyVisitToRabbitMqTest.php | 5 +- 2 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php new file mode 100644 index 00000000..e64fab2c --- /dev/null +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php @@ -0,0 +1,127 @@ +helper = $this->prophesize(RabbitMqPublishingHelperInterface::class); + $this->em = $this->prophesize(EntityManagerInterface::class); + $this->logger = $this->prophesize(LoggerInterface::class); + + $this->listener = new NotifyNewShortUrlToRabbitMq( + $this->helper->reveal(), + $this->em->reveal(), + $this->logger->reveal(), + new ShortUrlDataTransformer(new ShortUrlStringifier([])), + true, + ); + } + + /** @test */ + public function doesNothingWhenTheFeatureIsNotEnabled(): void + { + $listener = new NotifyNewShortUrlToRabbitMq( + $this->helper->reveal(), + $this->em->reveal(), + $this->logger->reveal(), + new ShortUrlDataTransformer(new ShortUrlStringifier([])), + false, + ); + + $listener(new ShortUrlCreated('123')); + + $this->em->find(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->helper->publishPayloadInQueue(Argument::cetera())->shouldNotHaveBeenCalled(); + } + + /** @test */ + public function notificationsAreNotSentWhenShortUrlCannotBeFound(): void + { + $shortUrlId = '123'; + $find = $this->em->find(ShortUrl::class, $shortUrlId)->willReturn(null); + $logWarning = $this->logger->warning( + 'Tried to notify RabbitMQ for new short URL with id "{shortUrlId}", but it does not exist.', + ['shortUrlId' => $shortUrlId], + ); + + ($this->listener)(new ShortUrlCreated($shortUrlId)); + + $find->shouldHaveBeenCalledOnce(); + $logWarning->shouldHaveBeenCalledOnce(); + $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->helper->publishPayloadInQueue(Argument::cetera())->shouldNotHaveBeenCalled(); + } + + /** @test */ + public function expectedChannelIsNotified(): void + { + $shortUrlId = '123'; + $find = $this->em->find(ShortUrl::class, $shortUrlId)->willReturn(ShortUrl::withLongUrl('')); + + ($this->listener)(new ShortUrlCreated($shortUrlId)); + + $find->shouldHaveBeenCalledOnce(); + $this->helper->publishPayloadInQueue( + Argument::type('array'), + 'https://shlink.io/new-short-url', + )->shouldHaveBeenCalledOnce(); + $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); + } + + /** + * @test + * @dataProvider provideExceptions + */ + public function printsDebugMessageInCaseOfError(Throwable $e): void + { + $shortUrlId = '123'; + $find = $this->em->find(ShortUrl::class, $shortUrlId)->willReturn(ShortUrl::withLongUrl('')); + $publish = $this->helper->publishPayloadInQueue(Argument::cetera())->willThrow($e); + + ($this->listener)(new ShortUrlCreated($shortUrlId)); + + $this->logger->debug( + 'Error while trying to notify RabbitMQ with new short URL. {e}', + ['e' => $e], + )->shouldHaveBeenCalledOnce(); + $find->shouldHaveBeenCalledOnce(); + $publish->shouldHaveBeenCalledOnce(); + } + + public function provideExceptions(): iterable + { + yield [new RuntimeException('RuntimeException Error')]; + yield [new Exception('Exception Error')]; + yield [new DomainException('DomainException Error')]; + } +} diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php index 942a7bdc..18a99425 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php @@ -34,7 +34,6 @@ class NotifyVisitToRabbitMqTest extends TestCase private ObjectProphecy $helper; private ObjectProphecy $em; private ObjectProphecy $logger; - private ObjectProphecy $orphanVisitTransformer; protected function setUp(): void { @@ -135,7 +134,7 @@ class NotifyVisitToRabbitMqTest extends TestCase { $visitId = '123'; $findVisit = $this->em->find(Visit::class, $visitId)->willReturn(Visit::forBasePath(Visitor::emptyInstance())); - $channel = $this->helper->publishPayloadInQueue(Argument::cetera())->willThrow($e); + $publish = $this->helper->publishPayloadInQueue(Argument::cetera())->willThrow($e); ($this->listener)(new VisitLocated($visitId)); @@ -144,7 +143,7 @@ class NotifyVisitToRabbitMqTest extends TestCase ['e' => $e], )->shouldHaveBeenCalledOnce(); $findVisit->shouldHaveBeenCalledOnce(); - $channel->shouldHaveBeenCalledOnce(); + $publish->shouldHaveBeenCalledOnce(); } public function provideExceptions(): iterable From fc6b4c12b2dc3eb7ab4fec8ccae7fbb7b156703a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jul 2022 11:07:20 +0200 Subject: [PATCH 05/12] Configured publishing of new short URL events in RabbitMQ --- module/Core/config/dependencies.config.php | 1 + module/Core/config/event_dispatcher.config.php | 2 +- module/Core/src/Service/UrlShortener.php | 18 +++++++++++++----- module/Core/test/Service/UrlShortenerTest.php | 7 ++++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 516ad8a1..4afd4805 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -98,6 +98,7 @@ return [ 'em', ShortUrl\Resolver\PersistenceShortUrlRelationResolver::class, Service\ShortUrl\ShortCodeUniquenessHelper::class, + EventDispatcherInterface::class, ], Visit\VisitsTracker::class => [ 'em', diff --git a/module/Core/config/event_dispatcher.config.php b/module/Core/config/event_dispatcher.config.php index 806d3104..f7ef63b3 100644 --- a/module/Core/config/event_dispatcher.config.php +++ b/module/Core/config/event_dispatcher.config.php @@ -28,7 +28,7 @@ return [ EventDispatcher\UpdateGeoLiteDb::class, ], EventDispatcher\Event\ShortUrlCreated::class => [ - EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class, +// EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class, EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class, ], ], diff --git a/module/Core/src/Service/UrlShortener.php b/module/Core/src/Service/UrlShortener.php index 8fa54493..21afb6b0 100644 --- a/module/Core/src/Service/UrlShortener.php +++ b/module/Core/src/Service/UrlShortener.php @@ -5,7 +5,9 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Service; use Doctrine\ORM\EntityManagerInterface; +use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; +use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated; use Shlinkio\Shlink\Core\Exception\InvalidUrlException; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; @@ -17,10 +19,11 @@ use Shlinkio\Shlink\Core\ShortUrl\Resolver\ShortUrlRelationResolverInterface; class UrlShortener implements UrlShortenerInterface { public function __construct( - private ShortUrlTitleResolutionHelperInterface $titleResolutionHelper, - private EntityManagerInterface $em, - private ShortUrlRelationResolverInterface $relationResolver, - private ShortCodeUniquenessHelperInterface $shortCodeHelper, + private readonly ShortUrlTitleResolutionHelperInterface $titleResolutionHelper, + private readonly EntityManagerInterface $em, + private readonly ShortUrlRelationResolverInterface $relationResolver, + private readonly ShortCodeUniquenessHelperInterface $shortCodeHelper, + private readonly EventDispatcherInterface $eventDispatcher, ) { } @@ -39,7 +42,8 @@ class UrlShortener implements UrlShortenerInterface /** @var ShortUrlMeta $meta */ $meta = $this->titleResolutionHelper->processTitleAndValidateUrl($meta); - return $this->em->transactional(function () use ($meta) { + /** @var ShortUrl $newShortUrl */ + $newShortUrl = $this->em->wrapInTransaction(function () use ($meta) { $shortUrl = ShortUrl::fromMeta($meta, $this->relationResolver); $this->verifyShortCodeUniqueness($meta, $shortUrl); @@ -47,6 +51,10 @@ class UrlShortener implements UrlShortenerInterface return $shortUrl; }); + + $this->eventDispatcher->dispatch(new ShortUrlCreated($newShortUrl->getId())); + + return $newShortUrl; } private function findExistingShortUrlIfExists(ShortUrlMeta $meta): ?ShortUrl diff --git a/module/Core/test/Service/UrlShortenerTest.php b/module/Core/test/Service/UrlShortenerTest.php index bdd508b4..fbe9b1c4 100644 --- a/module/Core/test/Service/UrlShortenerTest.php +++ b/module/Core/test/Service/UrlShortenerTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\TestCase; use Prophecy\Argument; use Prophecy\PhpUnit\ProphecyTrait; use Prophecy\Prophecy\ObjectProphecy; +use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; @@ -27,6 +28,7 @@ class UrlShortenerTest extends TestCase private ObjectProphecy $em; private ObjectProphecy $titleResolutionHelper; private ObjectProphecy $shortCodeHelper; + private ObjectProphecy $eventDispatcher; public function setUp(): void { @@ -39,7 +41,7 @@ class UrlShortenerTest extends TestCase [$shortUrl] = $arguments; $shortUrl->setId('10'); }); - $this->em->transactional(Argument::type('callable'))->will(function (array $args) { + $this->em->wrapInTransaction(Argument::type('callable'))->will(function (array $args) { /** @var callable $callback */ [$callback] = $args; @@ -51,11 +53,14 @@ class UrlShortenerTest extends TestCase $this->shortCodeHelper = $this->prophesize(ShortCodeUniquenessHelperInterface::class); $this->shortCodeHelper->ensureShortCodeUniqueness(Argument::cetera())->willReturn(true); + $this->eventDispatcher = $this->prophesize(EventDispatcherInterface::class); + $this->urlShortener = new UrlShortener( $this->titleResolutionHelper->reveal(), $this->em->reveal(), new SimpleShortUrlRelationResolver(), $this->shortCodeHelper->reveal(), + $this->eventDispatcher->reveal(), ); } From 4d1af867a4b716886a6c624536758c1a41b4de7d Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jul 2022 12:05:55 +0200 Subject: [PATCH 06/12] Extracted real-time update topic names to an enum --- .../RabbitMq/NotifyNewShortUrlToRabbitMq.php | 5 ++--- .../RabbitMq/NotifyVisitToRabbitMq.php | 12 ++++-------- module/Core/src/EventDispatcher/Topic.php | 19 +++++++++++++++++++ .../src/Mercure/MercureUpdatesGenerator.php | 11 ++++------- 4 files changed, 29 insertions(+), 18 deletions(-) create mode 100644 module/Core/src/EventDispatcher/Topic.php diff --git a/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php b/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php index 8e66dedb..0d5368a2 100644 --- a/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php +++ b/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php @@ -10,12 +10,11 @@ use Shlinkio\Shlink\Common\RabbitMq\RabbitMqPublishingHelperInterface; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated; +use Shlinkio\Shlink\Core\EventDispatcher\Topic; use Throwable; class NotifyNewShortUrlToRabbitMq { - private const NEW_SHORT_URL_QUEUE = 'https://shlink.io/new-short-url'; - public function __construct( private readonly RabbitMqPublishingHelperInterface $rabbitMqHelper, private readonly EntityManagerInterface $em, @@ -45,7 +44,7 @@ class NotifyNewShortUrlToRabbitMq try { $this->rabbitMqHelper->publishPayloadInQueue( $this->shortUrlTransformer->transform($shortUrl), - self::NEW_SHORT_URL_QUEUE, + Topic::NEW_SHORT_URL->value, ); } catch (Throwable $e) { $this->logger->debug('Error while trying to notify RabbitMQ with new short URL. {e}', ['e' => $e]); diff --git a/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php b/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php index 929e37c0..a10d8673 100644 --- a/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php +++ b/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php @@ -10,15 +10,11 @@ use Shlinkio\Shlink\Common\RabbitMq\RabbitMqPublishingHelperInterface; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated; +use Shlinkio\Shlink\Core\EventDispatcher\Topic; use Throwable; -use function sprintf; - class NotifyVisitToRabbitMq { - private const NEW_VISIT_QUEUE = 'https://shlink.io/new-visit'; - private const NEW_ORPHAN_VISIT_QUEUE = 'https://shlink.io/new-orphan-visit'; - public function __construct( private readonly RabbitMqPublishingHelperInterface $rabbitMqHelper, private readonly EntityManagerInterface $em, @@ -62,12 +58,12 @@ class NotifyVisitToRabbitMq private function determineQueuesToPublishTo(Visit $visit): array { if ($visit->isOrphan()) { - return [self::NEW_ORPHAN_VISIT_QUEUE]; + return [Topic::NEW_ORPHAN_VISIT->value]; } return [ - self::NEW_VISIT_QUEUE, - sprintf('%s/%s', self::NEW_VISIT_QUEUE, $visit->getShortUrl()?->getShortCode()), + Topic::NEW_VISIT->value, + Topic::newShortUrlVisit($visit->getShortUrl()?->getShortCode()), ]; } diff --git a/module/Core/src/EventDispatcher/Topic.php b/module/Core/src/EventDispatcher/Topic.php new file mode 100644 index 00000000..0cba5a09 --- /dev/null +++ b/module/Core/src/EventDispatcher/Topic.php @@ -0,0 +1,19 @@ +value, $shortCode ?? ''); + } +} diff --git a/module/Core/src/Mercure/MercureUpdatesGenerator.php b/module/Core/src/Mercure/MercureUpdatesGenerator.php index 74b85388..f84d296d 100644 --- a/module/Core/src/Mercure/MercureUpdatesGenerator.php +++ b/module/Core/src/Mercure/MercureUpdatesGenerator.php @@ -6,16 +6,13 @@ namespace Shlinkio\Shlink\Core\Mercure; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Entity\Visit; +use Shlinkio\Shlink\Core\EventDispatcher\Topic; use Symfony\Component\Mercure\Update; use function Shlinkio\Shlink\Common\json_encode; -use function sprintf; final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface { - private const NEW_VISIT_TOPIC = 'https://shlink.io/new-visit'; - private const NEW_ORPHAN_VISIT_TOPIC = 'https://shlink.io/new-orphan-visit'; - public function __construct( private DataTransformerInterface $shortUrlTransformer, private DataTransformerInterface $orphanVisitTransformer, @@ -24,7 +21,7 @@ final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface public function newVisitUpdate(Visit $visit): Update { - return new Update(self::NEW_VISIT_TOPIC, json_encode([ + return new Update(Topic::NEW_VISIT->value, json_encode([ 'shortUrl' => $this->shortUrlTransformer->transform($visit->getShortUrl()), 'visit' => $visit, ])); @@ -32,7 +29,7 @@ final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface public function newOrphanVisitUpdate(Visit $visit): Update { - return new Update(self::NEW_ORPHAN_VISIT_TOPIC, json_encode([ + return new Update(Topic::NEW_ORPHAN_VISIT->value, json_encode([ 'visit' => $this->orphanVisitTransformer->transform($visit), ])); } @@ -40,7 +37,7 @@ final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface public function newShortUrlVisitUpdate(Visit $visit): Update { $shortUrl = $visit->getShortUrl(); - $topic = sprintf('%s/%s', self::NEW_VISIT_TOPIC, $shortUrl?->getShortCode()); + $topic = Topic::newShortUrlVisit($shortUrl?->getShortCode()); return new Update($topic, json_encode([ 'shortUrl' => $this->shortUrlTransformer->transform($shortUrl), From 97d24d76d882a95d55a3510e5eeccda21719bec1 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jul 2022 12:37:57 +0200 Subject: [PATCH 07/12] Fixed new short URL event payload to RabbitMQ, and started to add logic for Mercure --- module/Core/config/event_dispatcher.config.php | 1 + .../Mercure/NotifyVisitToMercure.php | 8 ++++---- .../RabbitMq/NotifyNewShortUrlToRabbitMq.php | 2 +- .../RabbitMq/NotifyVisitToRabbitMq.php | 15 +++++++++++++++ .../Core/src/Mercure/MercureUpdatesGenerator.php | 12 ++++++++++-- .../Mercure/MercureUpdatesGeneratorInterface.php | 3 +++ .../RabbitMq/NotifyVisitToRabbitMqTest.php | 4 ++++ 7 files changed, 38 insertions(+), 7 deletions(-) diff --git a/module/Core/config/event_dispatcher.config.php b/module/Core/config/event_dispatcher.config.php index f7ef63b3..e710bd7d 100644 --- a/module/Core/config/event_dispatcher.config.php +++ b/module/Core/config/event_dispatcher.config.php @@ -87,6 +87,7 @@ return [ 'em', 'Logger_Shlink', Visit\Transformer\OrphanVisitDataTransformer::class, + ShortUrl\Transformer\ShortUrlDataTransformer::class, 'config.rabbitmq.enabled', ], EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class => [ diff --git a/module/Core/src/EventDispatcher/Mercure/NotifyVisitToMercure.php b/module/Core/src/EventDispatcher/Mercure/NotifyVisitToMercure.php index fa520f08..11d3a8e4 100644 --- a/module/Core/src/EventDispatcher/Mercure/NotifyVisitToMercure.php +++ b/module/Core/src/EventDispatcher/Mercure/NotifyVisitToMercure.php @@ -18,10 +18,10 @@ use function Functional\each; class NotifyVisitToMercure { public function __construct( - private HubInterface $hub, - private MercureUpdatesGeneratorInterface $updatesGenerator, - private EntityManagerInterface $em, - private LoggerInterface $logger, + private readonly HubInterface $hub, + private readonly MercureUpdatesGeneratorInterface $updatesGenerator, + private readonly EntityManagerInterface $em, + private readonly LoggerInterface $logger, ) { } diff --git a/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php b/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php index 0d5368a2..583f9420 100644 --- a/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php +++ b/module/Core/src/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMq.php @@ -43,7 +43,7 @@ class NotifyNewShortUrlToRabbitMq try { $this->rabbitMqHelper->publishPayloadInQueue( - $this->shortUrlTransformer->transform($shortUrl), + ['shortUrl' => $this->shortUrlTransformer->transform($shortUrl)], Topic::NEW_SHORT_URL->value, ); } catch (Throwable $e) { diff --git a/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php b/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php index a10d8673..897bff29 100644 --- a/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php +++ b/module/Core/src/EventDispatcher/RabbitMq/NotifyVisitToRabbitMq.php @@ -20,6 +20,7 @@ class NotifyVisitToRabbitMq private readonly EntityManagerInterface $em, private readonly LoggerInterface $logger, private readonly DataTransformerInterface $orphanVisitTransformer, + private readonly DataTransformerInterface $shortUrlTransformer, // @phpstan-ignore-line private readonly bool $isEnabled, ) { } @@ -69,6 +70,20 @@ class NotifyVisitToRabbitMq private function visitToPayload(Visit $visit): array { + // FIXME This was defined incorrectly. + // According to the spec, both the visit and the short URL it belongs to, should be published. + // The shape should be ['visit' => [...], 'shortUrl' => ?[...]] + // However, this would be a breaking change, so we need a flag that determines the shape of the payload. + return ! $visit->isOrphan() ? $visit->jsonSerialize() : $this->orphanVisitTransformer->transform($visit); + + if ($visit->isOrphan()) { // @phpstan-ignore-line + return ['visit' => $this->orphanVisitTransformer->transform($visit)]; + } + + return [ + 'visit' => $visit->jsonSerialize(), + 'shortUrl' => $this->shortUrlTransformer->transform($visit->getShortUrl()), + ]; } } diff --git a/module/Core/src/Mercure/MercureUpdatesGenerator.php b/module/Core/src/Mercure/MercureUpdatesGenerator.php index f84d296d..0f01faa2 100644 --- a/module/Core/src/Mercure/MercureUpdatesGenerator.php +++ b/module/Core/src/Mercure/MercureUpdatesGenerator.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Mercure; use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; +use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\EventDispatcher\Topic; use Symfony\Component\Mercure\Update; @@ -14,8 +15,8 @@ use function Shlinkio\Shlink\Common\json_encode; final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface { public function __construct( - private DataTransformerInterface $shortUrlTransformer, - private DataTransformerInterface $orphanVisitTransformer, + private readonly DataTransformerInterface $shortUrlTransformer, + private readonly DataTransformerInterface $orphanVisitTransformer, ) { } @@ -44,4 +45,11 @@ final class MercureUpdatesGenerator implements MercureUpdatesGeneratorInterface 'visit' => $visit, ])); } + + public function newShortUrlUpdate(ShortUrl $shortUrl): Update + { + return new Update(Topic::NEW_SHORT_URL->value, json_encode([ + 'shortUrl' => $this->shortUrlTransformer->transform($shortUrl), + ])); + } } diff --git a/module/Core/src/Mercure/MercureUpdatesGeneratorInterface.php b/module/Core/src/Mercure/MercureUpdatesGeneratorInterface.php index 951e805c..ee0cd593 100644 --- a/module/Core/src/Mercure/MercureUpdatesGeneratorInterface.php +++ b/module/Core/src/Mercure/MercureUpdatesGeneratorInterface.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\Mercure; +use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; use Symfony\Component\Mercure\Update; @@ -14,4 +15,6 @@ interface MercureUpdatesGeneratorInterface public function newOrphanVisitUpdate(Visit $visit): Update; public function newShortUrlVisitUpdate(Visit $visit): Update; + + public function newShortUrlUpdate(ShortUrl $shortUrl): Update; } diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php index 18a99425..2558dfc3 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyVisitToRabbitMqTest.php @@ -20,6 +20,8 @@ use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated; use Shlinkio\Shlink\Core\EventDispatcher\RabbitMq\NotifyVisitToRabbitMq; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\Visitor; +use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; +use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer; use Shlinkio\Shlink\Core\Visit\Transformer\OrphanVisitDataTransformer; use Throwable; @@ -46,6 +48,7 @@ class NotifyVisitToRabbitMqTest extends TestCase $this->em->reveal(), $this->logger->reveal(), new OrphanVisitDataTransformer(), + new ShortUrlDataTransformer(new ShortUrlStringifier([])), true, ); } @@ -58,6 +61,7 @@ class NotifyVisitToRabbitMqTest extends TestCase $this->em->reveal(), $this->logger->reveal(), new OrphanVisitDataTransformer(), + new ShortUrlDataTransformer(new ShortUrlStringifier([])), false, ); From 34e72b42dcc6c30466b6dcac92574101708de505 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 24 Jul 2022 19:00:48 +0200 Subject: [PATCH 08/12] Implemented listener to publish new short URL events in Mercure --- .../Core/config/event_dispatcher.config.php | 12 ++++++- .../Mercure/NotifyNewShortUrlToMercure.php | 33 ++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/module/Core/config/event_dispatcher.config.php b/module/Core/config/event_dispatcher.config.php index e710bd7d..9ae99e08 100644 --- a/module/Core/config/event_dispatcher.config.php +++ b/module/Core/config/event_dispatcher.config.php @@ -28,7 +28,7 @@ return [ EventDispatcher\UpdateGeoLiteDb::class, ], EventDispatcher\Event\ShortUrlCreated::class => [ -// EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class, + EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class, EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class, ], ], @@ -39,6 +39,7 @@ return [ EventDispatcher\LocateVisit::class => ConfigAbstractFactory::class, EventDispatcher\NotifyVisitToWebHooks::class => ConfigAbstractFactory::class, EventDispatcher\Mercure\NotifyVisitToMercure::class => ConfigAbstractFactory::class, + EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class => ConfigAbstractFactory::class, EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => ConfigAbstractFactory::class, EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class => ConfigAbstractFactory::class, EventDispatcher\UpdateGeoLiteDb::class => ConfigAbstractFactory::class, @@ -48,6 +49,9 @@ return [ EventDispatcher\Mercure\NotifyVisitToMercure::class => [ EventDispatcher\CloseDbConnectionEventListenerDelegator::class, ], + EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class => [ + EventDispatcher\CloseDbConnectionEventListenerDelegator::class, + ], EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => [ EventDispatcher\CloseDbConnectionEventListenerDelegator::class, ], @@ -82,6 +86,12 @@ return [ 'em', 'Logger_Shlink', ], + EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class => [ + Hub::class, + Mercure\MercureUpdatesGenerator::class, + 'em', + 'Logger_Shlink', + ], EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => [ RabbitMqPublishingHelper::class, 'em', diff --git a/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php b/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php index 4ebb8536..46b2f38b 100644 --- a/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php +++ b/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php @@ -4,12 +4,43 @@ declare(strict_types=1); namespace Shlinkio\Shlink\Core\EventDispatcher\Mercure; +use Doctrine\ORM\EntityManagerInterface; +use Psr\Log\LoggerInterface; +use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated; +use Shlinkio\Shlink\Core\Mercure\MercureUpdatesGeneratorInterface; +use Symfony\Component\Mercure\HubInterface; +use Throwable; class NotifyNewShortUrlToMercure { + public function __construct( + private readonly HubInterface $hub, + private readonly MercureUpdatesGeneratorInterface $updatesGenerator, + private readonly EntityManagerInterface $em, + private readonly LoggerInterface $logger, + ) { + } + public function __invoke(ShortUrlCreated $shortUrlCreated): void { - // TODO: Implement __invoke() method. + $shortUrlId = $shortUrlCreated->shortUrlId; + $shortUrl = $this->em->find(ShortUrl::class, $shortUrlId); + + if ($shortUrl === null) { + $this->logger->warning( + 'Tried to notify Mercure for new short URL with id "{shortUrlId}", but it does not exist.', + ['shortUrlId' => $shortUrlId], + ); + return; + } + + try { + $this->hub->publish($this->updatesGenerator->newShortUrlUpdate($shortUrl)); + } catch (Throwable $e) { + $this->logger->debug('Error while trying to notify mercure hub with new short URL. {e}', [ + 'e' => $e, + ]); + } } } From 074bfe3db261a000d731a0c4714cdc55b7375401 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 25 Jul 2022 09:02:05 +0200 Subject: [PATCH 09/12] Updated MercureUpdatesGeneratorTest --- docs/async-api/async-api.json | 4 +-- docs/swagger/swagger.json | 2 +- .../Mercure/MercureUpdatesGeneratorTest.php | 32 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/async-api/async-api.json b/docs/async-api/async-api.json index f2b964b9..e8c3e5d2 100644 --- a/docs/async-api/async-api.json +++ b/docs/async-api/async-api.json @@ -1,8 +1,8 @@ { - "asyncapi": "2.0.0", + "asyncapi": "2.4.0", "info": { "title": "Shlink", - "version": "2.0.0", + "version": "3.0.0", "description": "Shlink, the self-hosted URL shortener", "license": { "name": "MIT", diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 06f57c41..840ac84e 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -3,7 +3,7 @@ "info": { "title": "Shlink", "description": "Shlink, the self-hosted URL shortener", - "version": "1.0" + "version": "2.0" }, "externalDocs": { diff --git a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php index 779ec351..d3521f10 100644 --- a/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php +++ b/module/Core/test/Mercure/MercureUpdatesGeneratorTest.php @@ -7,6 +7,7 @@ namespace ShlinkioTest\Shlink\Core\Mercure; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\Visit; +use Shlinkio\Shlink\Core\EventDispatcher\Topic; use Shlinkio\Shlink\Core\Mercure\MercureUpdatesGenerator; use Shlinkio\Shlink\Core\Model\ShortUrlMeta; use Shlinkio\Shlink\Core\Model\Visitor; @@ -109,4 +110,35 @@ class MercureUpdatesGeneratorTest extends TestCase yield VisitType::INVALID_SHORT_URL->value => [Visit::forInvalidShortUrl($visitor)]; yield VisitType::BASE_URL->value => [Visit::forBasePath($visitor)]; } + + /** @test */ + public function shortUrlIsProperlySerializedIntoUpdate(): void + { + $shortUrl = ShortUrl::fromMeta(ShortUrlMeta::fromRawData([ + 'customSlug' => 'foo', + 'longUrl' => '', + 'title' => 'The title', + ])); + + $update = $this->generator->newShortUrlUpdate($shortUrl); + + self::assertEquals([Topic::NEW_SHORT_URL->value], $update->getTopics()); + self::assertEquals(['shortUrl' => [ + 'shortCode' => $shortUrl->getShortCode(), + 'shortUrl' => 'http:/' . $shortUrl->getShortCode(), + 'longUrl' => '', + 'dateCreated' => $shortUrl->getDateCreated()->toAtomString(), + 'visitsCount' => 0, + 'tags' => [], + 'meta' => [ + 'validSince' => null, + 'validUntil' => null, + 'maxVisits' => null, + ], + 'domain' => null, + 'title' => $shortUrl->title(), + 'crawlable' => false, + 'forwardQuery' => true, + ],], json_decode($update->getData())); + } } From be1ce06c00a34e3df859cbe5713359911353f6a3 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 25 Jul 2022 09:04:15 +0200 Subject: [PATCH 10/12] Updated asyn API spec --- docs/async-api/async-api.json | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/async-api/async-api.json b/docs/async-api/async-api.json index e8c3e5d2..3b59e8e5 100644 --- a/docs/async-api/async-api.json +++ b/docs/async-api/async-api.json @@ -75,6 +75,23 @@ } } } + }, + "https://shlink.io/new-short-url": { + "subscribe": { + "summary": "Receive information about any new short URL.", + "operationId": "newshortUrl", + "message": { + "payload": { + "type": "object", + "additionalProperties": false, + "properties": { + "shortUrl": { + "$ref": "#/components/schemas/ShortUrl" + } + } + } + } + } } }, "components": { From 019bd4dec8c745b51ded798729d6a71e548977ca Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 25 Jul 2022 09:30:25 +0200 Subject: [PATCH 11/12] Created NotifyNewShortUrlToMercureTest --- .../Mercure/NotifyNewShortUrlToMercure.php | 4 +- .../NotifyNewShortUrlToMercureTest.php | 104 ++++++++++++++++++ .../NotifyNewShortUrlToRabbitMqTest.php | 3 +- 3 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php diff --git a/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php b/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php index 46b2f38b..8e93d88b 100644 --- a/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php +++ b/module/Core/src/EventDispatcher/Mercure/NotifyNewShortUrlToMercure.php @@ -38,9 +38,7 @@ class NotifyNewShortUrlToMercure try { $this->hub->publish($this->updatesGenerator->newShortUrlUpdate($shortUrl)); } catch (Throwable $e) { - $this->logger->debug('Error while trying to notify mercure hub with new short URL. {e}', [ - 'e' => $e, - ]); + $this->logger->debug('Error while trying to notify mercure hub with new short URL. {e}', ['e' => $e]); } } } diff --git a/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php new file mode 100644 index 00000000..6bc2d527 --- /dev/null +++ b/module/Core/test/EventDispatcher/Mercure/NotifyNewShortUrlToMercureTest.php @@ -0,0 +1,104 @@ +hub = $this->prophesize(HubInterface::class); + $this->updatesGenerator = $this->prophesize(MercureUpdatesGeneratorInterface::class); + $this->em = $this->prophesize(EntityManagerInterface::class); + $this->logger = $this->prophesize(LoggerInterface::class); + + $this->listener = new NotifyNewShortUrlToMercure( + $this->hub->reveal(), + $this->updatesGenerator->reveal(), + $this->em->reveal(), + $this->logger->reveal(), + ); + } + + /** @test */ + public function messageIsLoggedWhenShortUrlIsNotFound(): void + { + $find = $this->em->find(ShortUrl::class, '123')->willReturn(null); + + ($this->listener)(new ShortUrlCreated('123')); + + $find->shouldHaveBeenCalledOnce(); + $this->logger->warning( + 'Tried to notify Mercure for new short URL with id "{shortUrlId}", but it does not exist.', + ['shortUrlId' => '123'], + )->shouldHaveBeenCalledOnce(); + $this->hub->publish(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->updatesGenerator->newShortUrlUpdate(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); + } + + /** @test */ + public function expectedNotificationIsPublished(): void + { + $shortUrl = ShortUrl::withLongUrl(''); + $update = new Update([]); + + $find = $this->em->find(ShortUrl::class, '123')->willReturn($shortUrl); + $newUpdate = $this->updatesGenerator->newShortUrlUpdate($shortUrl)->willReturn($update); + $publish = $this->hub->publish($update)->willReturn(''); + + ($this->listener)(new ShortUrlCreated('123')); + + $find->shouldHaveBeenCalledOnce(); + $newUpdate->shouldHaveBeenCalledOnce(); + $publish->shouldHaveBeenCalledOnce(); + $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); + } + + /** @test */ + public function messageIsPrintedIfPublishingFails(): void + { + $shortUrl = ShortUrl::withLongUrl(''); + $update = new Update([]); + $e = new Exception('Error'); + + $find = $this->em->find(ShortUrl::class, '123')->willReturn($shortUrl); + $newUpdate = $this->updatesGenerator->newShortUrlUpdate($shortUrl)->willReturn($update); + $publish = $this->hub->publish($update)->willThrow($e); + + ($this->listener)(new ShortUrlCreated('123')); + + $find->shouldHaveBeenCalledOnce(); + $newUpdate->shouldHaveBeenCalledOnce(); + $publish->shouldHaveBeenCalledOnce(); + $this->logger->warning(Argument::cetera())->shouldNotHaveBeenCalled(); + $this->logger->debug( + 'Error while trying to notify mercure hub with new short URL. {e}', + ['e' => $e], + )->shouldHaveBeenCalledOnce(); + } +} diff --git a/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php index e64fab2c..51b557af 100644 --- a/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php +++ b/module/Core/test/EventDispatcher/RabbitMq/NotifyNewShortUrlToRabbitMqTest.php @@ -17,6 +17,7 @@ use Shlinkio\Shlink\Common\RabbitMq\RabbitMqPublishingHelperInterface; use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated; use Shlinkio\Shlink\Core\EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq; +use Shlinkio\Shlink\Core\EventDispatcher\Topic; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use Shlinkio\Shlink\Core\ShortUrl\Transformer\ShortUrlDataTransformer; use Throwable; @@ -93,7 +94,7 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase $find->shouldHaveBeenCalledOnce(); $this->helper->publishPayloadInQueue( Argument::type('array'), - 'https://shlink.io/new-short-url', + Topic::NEW_SHORT_URL->value, )->shouldHaveBeenCalledOnce(); $this->logger->debug(Argument::cetera())->shouldNotHaveBeenCalled(); } From 9eb3fca726f6db6da9a9ccf3c0e4b8c96aeac28c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 25 Jul 2022 09:32:09 +0200 Subject: [PATCH 12/12] Updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e0e0625..de782f25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this Now you can run `tag:visits`, `domain:visits`, `visit:orphan` or `visit:non-orphan` to get the corresponding list of visits from the command line. +* [#962](https://github.com/shlinkio/shlink/issues/962) Added new real-time update for new short URLs. + + You can now subscribe to the `https://shlink.io/new-short-url` topic on any of the supported async updates technologies in order to get notified when a short URL is created. + ### Changed * [#1452](https://github.com/shlinkio/shlink/issues/1452) Updated to monolog 3