From d8087c25f787687bd4d9bf94bc4a432fd1960508 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Tue, 19 Nov 2024 20:16:33 +0100 Subject: [PATCH 1/2] Add Visit::toArray() method --- module/Core/src/Visit/Entity/Visit.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/module/Core/src/Visit/Entity/Visit.php b/module/Core/src/Visit/Entity/Visit.php index 70733593f..b0fa9ffc3 100644 --- a/module/Core/src/Visit/Entity/Visit.php +++ b/module/Core/src/Visit/Entity/Visit.php @@ -150,6 +150,15 @@ public function getType(): VisitType } public function jsonSerialize(): array + { + return $this->toArray(); + } + + /** + * @phpstan-type VisitedShortUrl array{shortCode: string, domain: string|null, shortUrl: string} + * @param (callable(ShortUrl $shortUrl): VisitedShortUrl)|null $visitedShortUrlToArray + */ + public function toArray(callable|null $visitedShortUrlToArray = null): array { $base = [ 'referer' => $this->referer, @@ -160,8 +169,11 @@ public function jsonSerialize(): array 'visitedUrl' => $this->visitedUrl, 'redirectUrl' => $this->redirectUrl, ]; - if (! $this->isOrphan()) { - return $base; + if ($this->shortUrl !== null) { + return $visitedShortUrlToArray === null ? $base : [ + ...$base, + 'visitedShortUrl' => $visitedShortUrlToArray($this->shortUrl), + ]; } return [ From 5d6abe59e9beae0ee2bb368c9c844c545adead78 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Wed, 20 Nov 2024 09:20:44 +0100 Subject: [PATCH 2/2] Include visited short URL information with every visit --- module/Core/config/dependencies.config.php | 5 ++-- module/Core/src/Visit/Entity/Visit.php | 16 +++++++++--- .../Transformer/VisitDataTransformer.php | 25 +++++++++++++++++++ .../VisitDataTransformerInterface.php | 12 +++++++++ module/Rest/config/dependencies.config.php | 21 +++++++++++++--- .../Action/Visit/AbstractListVisitsAction.php | 11 +++++--- .../src/Action/Visit/DomainVisitsAction.php | 4 ++- 7 files changed, 80 insertions(+), 14 deletions(-) create mode 100644 module/Core/src/Visit/Transformer/VisitDataTransformer.php create mode 100644 module/Core/src/Visit/Transformer/VisitDataTransformerInterface.php diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index b16a4c5cc..d16357513 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -10,7 +10,6 @@ use Shlinkio\Shlink\Common\Doctrine\EntityRepositoryFactory; use Shlinkio\Shlink\Core\Config\Options\NotFoundRedirectOptions; use Shlinkio\Shlink\Core\Geolocation\GeolocationDbUpdater; -use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater; use Shlinkio\Shlink\IpGeolocation\GeoLite2\GeoLite2ReaderFactory; @@ -94,6 +93,7 @@ ], Visit\Listener\ShortUrlVisitsCountTracker::class => InvokableFactory::class, Visit\Listener\OrphanVisitsCountTracker::class => InvokableFactory::class, + Visit\Transformer\VisitDataTransformer::class => ConfigAbstractFactory::class, Util\DoctrineBatchHelper::class => ConfigAbstractFactory::class, Util\RedirectResponseHelper::class => ConfigAbstractFactory::class, @@ -128,7 +128,7 @@ Matomo\MatomoTrackerBuilder::class => [Matomo\MatomoOptions::class], Matomo\MatomoVisitSender::class => [ Matomo\MatomoTrackerBuilder::class, - ShortUrlStringifier::class, + ShortUrl\Helper\ShortUrlStringifier::class, Visit\Repository\VisitIterationRepository::class, ], @@ -168,6 +168,7 @@ Visit\Geolocation\VisitLocator::class => ['em', Visit\Repository\VisitIterationRepository::class], Visit\Geolocation\VisitToLocationHelper::class => [IpLocationResolverInterface::class], Visit\VisitsStatsHelper::class => ['em'], + Visit\Transformer\VisitDataTransformer::class => [ShortUrl\Helper\ShortUrlStringifier::class], Tag\TagService::class => ['em', Tag\Repository\TagRepository::class], ShortUrl\DeleteShortUrlService::class => [ 'em', diff --git a/module/Core/src/Visit/Entity/Visit.php b/module/Core/src/Visit/Entity/Visit.php index b0fa9ffc3..eaef5f2f4 100644 --- a/module/Core/src/Visit/Entity/Visit.php +++ b/module/Core/src/Visit/Entity/Visit.php @@ -169,16 +169,24 @@ public function toArray(callable|null $visitedShortUrlToArray = null): array 'visitedUrl' => $this->visitedUrl, 'redirectUrl' => $this->redirectUrl, ]; - if ($this->shortUrl !== null) { - return $visitedShortUrlToArray === null ? $base : [ + + // Orphan visit + if ($this->shortUrl === null) { + return [ ...$base, - 'visitedShortUrl' => $visitedShortUrlToArray($this->shortUrl), + 'type' => $this->type->value, ]; + + } + + // Should not include visited short URL + if ($visitedShortUrlToArray === null) { + return $base; } return [ ...$base, - 'type' => $this->type->value, + 'visitedShortUrl' => $visitedShortUrlToArray($this->shortUrl), ]; } } diff --git a/module/Core/src/Visit/Transformer/VisitDataTransformer.php b/module/Core/src/Visit/Transformer/VisitDataTransformer.php new file mode 100644 index 000000000..c294c5191 --- /dev/null +++ b/module/Core/src/Visit/Transformer/VisitDataTransformer.php @@ -0,0 +1,25 @@ +toArray(fn (ShortUrl $shortUrl) => [ + 'shortCode' => $shortUrl->getShortCode(), + 'domain' => $shortUrl->getDomain()?->authority, + 'shortUrl' => $this->stringifier->stringify($shortUrl), + ]); + } +} diff --git a/module/Core/src/Visit/Transformer/VisitDataTransformerInterface.php b/module/Core/src/Visit/Transformer/VisitDataTransformerInterface.php new file mode 100644 index 000000000..9fd484adf --- /dev/null +++ b/module/Core/src/Visit/Transformer/VisitDataTransformerInterface.php @@ -0,0 +1,12 @@ + [Visit\VisitsStatsHelper::class], - Action\Visit\TagVisitsAction::class => [Visit\VisitsStatsHelper::class], + Action\Visit\ShortUrlVisitsAction::class => [ + Visit\VisitsStatsHelper::class, + Visit\Transformer\VisitDataTransformer::class, + ], + Action\Visit\TagVisitsAction::class => [ + Visit\VisitsStatsHelper::class, + Visit\Transformer\VisitDataTransformer::class, + ], Action\Visit\DomainVisitsAction::class => [ Visit\VisitsStatsHelper::class, Config\Options\UrlShortenerOptions::class, + Visit\Transformer\VisitDataTransformer::class, ], Action\Visit\GlobalVisitsAction::class => [Visit\VisitsStatsHelper::class], - Action\Visit\OrphanVisitsAction::class => [Visit\VisitsStatsHelper::class], + Action\Visit\OrphanVisitsAction::class => [ + Visit\VisitsStatsHelper::class, + Visit\Transformer\VisitDataTransformer::class, + ], Action\Visit\DeleteOrphanVisitsAction::class => [Visit\VisitsDeleter::class], - Action\Visit\NonOrphanVisitsAction::class => [Visit\VisitsStatsHelper::class], + Action\Visit\NonOrphanVisitsAction::class => [ + Visit\VisitsStatsHelper::class, + Visit\Transformer\VisitDataTransformer::class, + ], Action\ShortUrl\ListShortUrlsAction::class => [ ShortUrl\ShortUrlListService::class, ShortUrlDataTransformer::class, diff --git a/module/Rest/src/Action/Visit/AbstractListVisitsAction.php b/module/Rest/src/Action/Visit/AbstractListVisitsAction.php index b63133fa0..86577dd89 100644 --- a/module/Rest/src/Action/Visit/AbstractListVisitsAction.php +++ b/module/Rest/src/Action/Visit/AbstractListVisitsAction.php @@ -11,6 +11,7 @@ use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtils; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; +use Shlinkio\Shlink\Core\Visit\Transformer\VisitDataTransformerInterface; use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -20,8 +21,10 @@ abstract class AbstractListVisitsAction extends AbstractRestAction { protected const array ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - public function __construct(protected readonly VisitsStatsHelperInterface $visitsHelper) - { + public function __construct( + protected readonly VisitsStatsHelperInterface $visitsHelper, + private readonly VisitDataTransformerInterface $transformer, + ) { } public function handle(ServerRequestInterface $request): ResponseInterface @@ -30,7 +33,9 @@ public function handle(ServerRequestInterface $request): ResponseInterface $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); $visits = $this->getVisitsPaginator($request, $params, $apiKey); - return new JsonResponse(['visits' => PagerfantaUtils::serializePaginator($visits)]); + return new JsonResponse([ + 'visits' => PagerfantaUtils::serializePaginator($visits, $this->transformer->transform(...)), + ]); } /** diff --git a/module/Rest/src/Action/Visit/DomainVisitsAction.php b/module/Rest/src/Action/Visit/DomainVisitsAction.php index 0e0279559..f08849e06 100644 --- a/module/Rest/src/Action/Visit/DomainVisitsAction.php +++ b/module/Rest/src/Action/Visit/DomainVisitsAction.php @@ -9,6 +9,7 @@ use Shlinkio\Shlink\Core\Config\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\Domain\Entity\Domain; use Shlinkio\Shlink\Core\Visit\Model\VisitsParams; +use Shlinkio\Shlink\Core\Visit\Transformer\VisitDataTransformerInterface; use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; use Shlinkio\Shlink\Rest\Entity\ApiKey; @@ -19,8 +20,9 @@ class DomainVisitsAction extends AbstractListVisitsAction public function __construct( VisitsStatsHelperInterface $visitsHelper, private readonly UrlShortenerOptions $urlShortenerOptions, + VisitDataTransformerInterface $transformer, ) { - parent::__construct($visitsHelper); + parent::__construct($visitsHelper, $transformer); } protected function getVisitsPaginator(Request $request, VisitsParams $params, ApiKey $apiKey): Pagerfanta