diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index 4844e6d5c..781075b51 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -9,7 +9,6 @@ use Psr\EventDispatcher\EventDispatcherInterface; use Shlinkio\Shlink\Common\Doctrine\EntityRepositoryFactory; use Shlinkio\Shlink\Core\Config\Options\NotFoundRedirectOptions; -use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifier; use Shlinkio\Shlink\Importer\ImportedLinksProcessorInterface; use Shlinkio\Shlink\IpGeolocation\GeoLite2\DbUpdater; use Shlinkio\Shlink\IpGeolocation\Resolver\IpLocationResolverInterface; @@ -90,6 +89,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, @@ -123,7 +123,7 @@ Matomo\MatomoTrackerBuilder::class => [Matomo\MatomoOptions::class], Matomo\MatomoVisitSender::class => [ Matomo\MatomoTrackerBuilder::class, - ShortUrlStringifier::class, + ShortUrl\Helper\ShortUrlStringifier::class, Visit\Repository\VisitIterationRepository::class, ], @@ -163,6 +163,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 2879ef66e..43ae3b58c 100644 --- a/module/Core/src/Visit/Entity/Visit.php +++ b/module/Core/src/Visit/Entity/Visit.php @@ -166,16 +166,24 @@ public function toArray(callable|null $visitedShortUrlToArray = null): array 'potentialBot' => $this->potentialBot, 'visitedUrl' => $this->visitedUrl, ]; - 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/DomainVisitsAction.php b/module/Rest/src/Action/Visit/DomainVisitsAction.php index fc9cf20c5..e3baf9b29 100644 --- a/module/Rest/src/Action/Visit/DomainVisitsAction.php +++ b/module/Rest/src/Action/Visit/DomainVisitsAction.php @@ -11,6 +11,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\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware; @@ -23,6 +24,7 @@ class DomainVisitsAction extends AbstractRestAction public function __construct( private readonly VisitsStatsHelperInterface $visitsHelper, private readonly UrlShortenerOptions $urlShortenerOptions, + private readonly VisitDataTransformerInterface $transformer, ) { } @@ -33,7 +35,9 @@ public function handle(Request $request): Response $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); $visits = $this->visitsHelper->visitsForDomain($domain, $params, $apiKey); - return new JsonResponse(['visits' => PagerfantaUtils::serializePaginator($visits)]); + return new JsonResponse([ + 'visits' => PagerfantaUtils::serializePaginator($visits, $this->transformer->transform(...)), + ]); } private function resolveDomainParam(Request $request): string diff --git a/module/Rest/src/Action/Visit/NonOrphanVisitsAction.php b/module/Rest/src/Action/Visit/NonOrphanVisitsAction.php index 1fffdb8b5..7de5e63e1 100644 --- a/module/Rest/src/Action/Visit/NonOrphanVisitsAction.php +++ b/module/Rest/src/Action/Visit/NonOrphanVisitsAction.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtils; 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\Middleware\AuthenticationMiddleware; @@ -18,8 +19,10 @@ class NonOrphanVisitsAction extends AbstractRestAction protected const ROUTE_PATH = '/visits/non-orphan'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper) - { + public function __construct( + private readonly VisitsStatsHelperInterface $visitsHelper, + private readonly VisitDataTransformerInterface $transformer, + ) { } public function handle(ServerRequestInterface $request): ResponseInterface @@ -28,6 +31,8 @@ public function handle(ServerRequestInterface $request): ResponseInterface $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); $visits = $this->visitsHelper->nonOrphanVisits($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/OrphanVisitsAction.php b/module/Rest/src/Action/Visit/OrphanVisitsAction.php index 7906fdaec..6a2c3d9d1 100644 --- a/module/Rest/src/Action/Visit/OrphanVisitsAction.php +++ b/module/Rest/src/Action/Visit/OrphanVisitsAction.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtils; use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams; +use Shlinkio\Shlink\Core\Visit\Transformer\VisitDataTransformerInterface; use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Middleware\AuthenticationMiddleware; @@ -18,8 +19,10 @@ class OrphanVisitsAction extends AbstractRestAction protected const ROUTE_PATH = '/visits/orphan'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper) - { + public function __construct( + private readonly VisitsStatsHelperInterface $visitsHelper, + private readonly VisitDataTransformerInterface $transformer, + ) { } public function handle(ServerRequestInterface $request): ResponseInterface @@ -28,6 +31,8 @@ public function handle(ServerRequestInterface $request): ResponseInterface $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); $visits = $this->visitsHelper->orphanVisits($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/ShortUrlVisitsAction.php b/module/Rest/src/Action/Visit/ShortUrlVisitsAction.php index fe5099a2e..40fb1c731 100644 --- a/module/Rest/src/Action/Visit/ShortUrlVisitsAction.php +++ b/module/Rest/src/Action/Visit/ShortUrlVisitsAction.php @@ -10,6 +10,7 @@ use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtils; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; 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\Middleware\AuthenticationMiddleware; @@ -19,8 +20,10 @@ class ShortUrlVisitsAction extends AbstractRestAction protected const ROUTE_PATH = '/short-urls/{shortCode}/visits'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper) - { + public function __construct( + private readonly VisitsStatsHelperInterface $visitsHelper, + private readonly VisitDataTransformerInterface $transformer, + ) { } public function handle(Request $request): Response @@ -30,6 +33,8 @@ public function handle(Request $request): Response $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); $visits = $this->visitsHelper->visitsForShortUrl($identifier, $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/TagVisitsAction.php b/module/Rest/src/Action/Visit/TagVisitsAction.php index 1739264fc..f18c7461f 100644 --- a/module/Rest/src/Action/Visit/TagVisitsAction.php +++ b/module/Rest/src/Action/Visit/TagVisitsAction.php @@ -9,6 +9,7 @@ use Psr\Http\Message\ServerRequestInterface as Request; use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtils; 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\Middleware\AuthenticationMiddleware; @@ -18,8 +19,10 @@ class TagVisitsAction extends AbstractRestAction protected const ROUTE_PATH = '/tags/{tag}/visits'; protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; - public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper) - { + public function __construct( + private readonly VisitsStatsHelperInterface $visitsHelper, + private readonly VisitDataTransformerInterface $transformer, + ) { } public function handle(Request $request): Response @@ -29,6 +32,8 @@ public function handle(Request $request): Response $apiKey = AuthenticationMiddleware::apiKeyFromRequest($request); $visits = $this->visitsHelper->visitsForTag($tag, $params, $apiKey); - return new JsonResponse(['visits' => PagerfantaUtils::serializePaginator($visits)]); + return new JsonResponse([ + 'visits' => PagerfantaUtils::serializePaginator($visits, $this->transformer->transform(...)), + ]); } }