Skip to content

Commit

Permalink
Include visited short URL information with every visit
Browse files Browse the repository at this point in the history
  • Loading branch information
acelaya committed Nov 20, 2024
1 parent e3d9f96 commit 103f5a1
Show file tree
Hide file tree
Showing 10 changed files with 106 additions and 23 deletions.
5 changes: 3 additions & 2 deletions module/Core/config/dependencies.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
],

Expand Down Expand Up @@ -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',
Expand Down
16 changes: 12 additions & 4 deletions module/Core/src/Visit/Entity/Visit.php
Original file line number Diff line number Diff line change
Expand Up @@ -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),
];
}
}
25 changes: 25 additions & 0 deletions module/Core/src/Visit/Transformer/VisitDataTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Shlinkio\Shlink\Core\Visit\Transformer;

use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface;
use Shlinkio\Shlink\Core\Visit\Entity\Visit;

readonly class VisitDataTransformer implements VisitDataTransformerInterface
{
public function __construct(private ShortUrlStringifierInterface $stringifier)
{
}

public function transform(Visit $visit): array
{
return $visit->toArray(fn (ShortUrl $shortUrl) => [

Check failure on line 19 in module/Core/src/Visit/Transformer/VisitDataTransformer.php

View workflow job for this annotation

GitHub Actions / static-analysis (8.3, stan)

Parameter #1 $visitedShortUrlToArray of method Shlinkio\Shlink\Core\Visit\Entity\Visit::toArray() expects (callable(Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl): Shlinkio\Shlink\Core\Visit\Entity\VisitedShortUrl)|null, Closure(Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl): array{shortCode: string, domain: string|null, shortUrl: string} given.
'shortCode' => $shortUrl->getShortCode(),
'domain' => $shortUrl->getDomain()?->authority,
'shortUrl' => $this->stringifier->stringify($shortUrl),
]);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Shlinkio\Shlink\Core\Visit\Transformer;

use Shlinkio\Shlink\Core\Visit\Entity\Visit;

interface VisitDataTransformerInterface
{
public function transform(Visit $visit): array;
}
21 changes: 17 additions & 4 deletions module/Rest/config/dependencies.config.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,29 @@
ShortUrl\ShortUrlResolver::class,
ShortUrlDataTransformer::class,
],
Action\Visit\ShortUrlVisitsAction::class => [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,
Expand Down
6 changes: 5 additions & 1 deletion module/Rest/src/Action/Visit/DomainVisitsAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,6 +24,7 @@ class DomainVisitsAction extends AbstractRestAction
public function __construct(
private readonly VisitsStatsHelperInterface $visitsHelper,
private readonly UrlShortenerOptions $urlShortenerOptions,
private readonly VisitDataTransformerInterface $transformer,
) {
}

Expand All @@ -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
Expand Down
11 changes: 8 additions & 3 deletions module/Rest/src/Action/Visit/NonOrphanVisitsAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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(...)),
]);
}
}
11 changes: 8 additions & 3 deletions module/Rest/src/Action/Visit/OrphanVisitsAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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(...)),
]);
}
}
11 changes: 8 additions & 3 deletions module/Rest/src/Action/Visit/ShortUrlVisitsAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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(...)),
]);
}
}
11 changes: 8 additions & 3 deletions module/Rest/src/Action/Visit/TagVisitsAction.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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(...)),
]);
}
}

0 comments on commit 103f5a1

Please sign in to comment.