diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c58d8e6..8157c47f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com), and this ## [Unreleased] ### Added -* *Nothing* +* [#1330](https://github.com/shlinkio/shlink/issues/1330) All visit-related endpoints now expose the `visitedUrl` prop for any visit. + + Previously, this was exposed only for orphan visits, since this can be an arbitrary value for those. ### Changed * [#2034](https://github.com/shlinkio/shlink/issues/2034) Modernize entities, using constructor property promotion and readonly wherever possible. diff --git a/docs/async-api/async-api.json b/docs/async-api/async-api.json index 7cd838a8..83c424ea 100644 --- a/docs/async-api/async-api.json +++ b/docs/async-api/async-api.json @@ -232,6 +232,11 @@ "potentialBot": { "type": "boolean", "description": "Tells if Shlink thinks this visit comes potentially from a bot or crawler" + }, + "visitedUrl": { + "type": "string", + "nullable": true, + "description": "The originally visited URL that triggered the tracking of this visit" } }, "example": { @@ -247,7 +252,8 @@ "regionName": "California", "timezone": "America/Los_Angeles" }, - "potentialBot": false + "potentialBot": false, + "visitedUrl": "https://s.test" } }, "OrphanVisit": { @@ -256,11 +262,6 @@ { "type": "object", "properties": { - "visitedUrl": { - "type": "string", - "nullable": true, - "description": "The originally visited URL that triggered the tracking of this visit" - }, "type": { "type": "string", "enum": [ diff --git a/docs/swagger/definitions/OrphanVisit.json b/docs/swagger/definitions/OrphanVisit.json index a8b4954a..897c6049 100644 --- a/docs/swagger/definitions/OrphanVisit.json +++ b/docs/swagger/definitions/OrphanVisit.json @@ -1,14 +1,10 @@ { "type": "object", - "required": ["visitedUrl", "type"], + "required": ["type"], "allOf": [{ "$ref": "./Visit.json" }], "properties": { - "visitedUrl": { - "type": ["string", "null"], - "description": "The originally visited URL that triggered the tracking of this visit" - }, "type": { "type": "string", "enum": [ diff --git a/docs/swagger/definitions/Visit.json b/docs/swagger/definitions/Visit.json index ecb6b9f9..c4589bb1 100644 --- a/docs/swagger/definitions/Visit.json +++ b/docs/swagger/definitions/Visit.json @@ -1,6 +1,6 @@ { "type": "object", - "required": ["referer", "date", "userAgent", "visitLocation"], + "required": ["referer", "date", "userAgent", "visitLocation", "potentialBot", "visitedUrl"], "properties": { "referer": { "type": "string", @@ -21,6 +21,10 @@ "potentialBot": { "type": "boolean", "description": "Tells if Shlink thinks this visit comes potentially from a bot or crawler" + }, + "visitedUrl": { + "type": ["string", "null"], + "description": "The originally visited URL that triggered the tracking of this visit" } } } diff --git a/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json b/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json index 71e70148..f3799f13 100644 --- a/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json +++ b/docs/swagger/paths/v1_short-urls_{shortCode}_visits.json @@ -100,7 +100,8 @@ "date": "2015-08-20T05:05:03+04:00", "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0", "visitLocation": null, - "potentialBot": false + "potentialBot": false, + "visitedUrl": "https://s.test" }, { "referer": "https://t.co", @@ -115,14 +116,16 @@ "regionName": "California", "timezone": "America/Los_Angeles" }, - "potentialBot": false + "potentialBot": false, + "visitedUrl": "https://s.test" }, { "referer": null, "date": "2015-08-20T05:05:03+04:00", "userAgent": "some_web_crawler/1.4", "visitLocation": null, - "potentialBot": true + "potentialBot": true, + "visitedUrl": "https://s.test" } ], "pagination": { diff --git a/docs/swagger/paths/v2_domains_{domain}_visits.json b/docs/swagger/paths/v2_domains_{domain}_visits.json index d3acf60e..a477cb8e 100644 --- a/docs/swagger/paths/v2_domains_{domain}_visits.json +++ b/docs/swagger/paths/v2_domains_{domain}_visits.json @@ -103,7 +103,8 @@ "date": "2015-08-20T05:05:03+04:00", "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0", "visitLocation": null, - "potentialBot": false + "potentialBot": false, + "visitedUrl": "https://s.test" }, { "referer": "https://t.co", @@ -118,14 +119,16 @@ "regionName": "California", "timezone": "America/Los_Angeles" }, - "potentialBot": false + "potentialBot": false, + "visitedUrl": "https://s.test" }, { "referer": null, "date": "2015-08-20T05:05:03+04:00", "userAgent": "some_web_crawler/1.4", "visitLocation": null, - "potentialBot": true + "potentialBot": true, + "visitedUrl": "https://s.test" } ], "pagination": { diff --git a/docs/swagger/paths/v2_tags_{tag}_visits.json b/docs/swagger/paths/v2_tags_{tag}_visits.json index 2a0148ec..1f3dabf2 100644 --- a/docs/swagger/paths/v2_tags_{tag}_visits.json +++ b/docs/swagger/paths/v2_tags_{tag}_visits.json @@ -103,7 +103,8 @@ "date": "2015-08-20T05:05:03+04:00", "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0", "visitLocation": null, - "potentialBot": false + "potentialBot": false, + "visitedUrl": "https://s.test" }, { "referer": "https://t.co", @@ -118,14 +119,16 @@ "regionName": "California", "timezone": "America/Los_Angeles" }, - "potentialBot": false + "potentialBot": false, + "visitedUrl": "https://s.test" }, { "referer": null, "date": "2015-08-20T05:05:03+04:00", "userAgent": "some_web_crawler/1.4", "visitLocation": null, - "potentialBot": true + "potentialBot": true, + "visitedUrl": "https://s.test" } ], "pagination": { diff --git a/docs/swagger/paths/v2_visits_non-orphan.json b/docs/swagger/paths/v2_visits_non-orphan.json index da0bdd14..65b11252 100644 --- a/docs/swagger/paths/v2_visits_non-orphan.json +++ b/docs/swagger/paths/v2_visits_non-orphan.json @@ -94,7 +94,8 @@ "date": "2015-08-20T05:05:03+04:00", "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0 Mozilla/5.0 (Macintosh; Intel Mac OS X x.y; rv:42.0) Gecko/20100101 Firefox/42.0", "visitLocation": null, - "potentialBot": false + "potentialBot": false, + "visitedUrl": "https://s.test" }, { "referer": "https://t.co", @@ -109,14 +110,16 @@ "regionName": "California", "timezone": "America/Los_Angeles" }, - "potentialBot": false + "potentialBot": false, + "visitedUrl": "https://s.test" }, { "referer": null, "date": "2015-08-20T05:05:03+04:00", "userAgent": "some_web_crawler/1.4", "visitLocation": null, - "potentialBot": true + "potentialBot": true, + "visitedUrl": "https://s.test" } ], "pagination": { diff --git a/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php b/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php index a999760e..a3e8a43c 100644 --- a/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php +++ b/module/CLI/src/Command/Visit/AbstractVisitsListCommand.php @@ -54,7 +54,10 @@ abstract class AbstractVisitsListCommand extends Command $extraKeys = array_keys($extraFields); $rowData = [ - ...$visit->jsonSerialize(), + 'referer' => $visit->referer, + 'date' => $visit->getDate()->toAtomString(), + 'userAgent' => $visit->userAgent, + 'potentialBot' => $visit->potentialBot, 'country' => $visit->getVisitLocation()?->countryName ?? 'Unknown', 'city' => $visit->getVisitLocation()?->cityName ?? 'Unknown', ...$extraFields, diff --git a/module/Core/config/dependencies.config.php b/module/Core/config/dependencies.config.php index ed64a30e..d75c6bb8 100644 --- a/module/Core/config/dependencies.config.php +++ b/module/Core/config/dependencies.config.php @@ -68,7 +68,6 @@ return [ Visit\Geolocation\VisitLocator::class => ConfigAbstractFactory::class, Visit\Geolocation\VisitToLocationHelper::class => ConfigAbstractFactory::class, Visit\VisitsStatsHelper::class => ConfigAbstractFactory::class, - Visit\Transformer\OrphanVisitDataTransformer::class => InvokableFactory::class, Visit\Repository\VisitLocationRepository::class => [ EntityRepositoryFactory::class, Visit\Entity\Visit::class, @@ -199,10 +198,7 @@ return [ ], ShortUrl\Middleware\TrimTrailingSlashMiddleware::class => [Options\UrlShortenerOptions::class], - EventDispatcher\PublishingUpdatesGenerator::class => [ - ShortUrl\Transformer\ShortUrlDataTransformer::class, - Visit\Transformer\OrphanVisitDataTransformer::class, - ], + EventDispatcher\PublishingUpdatesGenerator::class => [ShortUrl\Transformer\ShortUrlDataTransformer::class], Importer\ImportedLinksProcessor::class => [ 'em', diff --git a/module/Core/src/EventDispatcher/PublishingUpdatesGenerator.php b/module/Core/src/EventDispatcher/PublishingUpdatesGenerator.php index 06d06c84..82ada6e1 100644 --- a/module/Core/src/EventDispatcher/PublishingUpdatesGenerator.php +++ b/module/Core/src/EventDispatcher/PublishingUpdatesGenerator.php @@ -9,12 +9,10 @@ use Shlinkio\Shlink\Common\UpdatePublishing\Update; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\Visit\Entity\Visit; -final class PublishingUpdatesGenerator implements PublishingUpdatesGeneratorInterface +final readonly class PublishingUpdatesGenerator implements PublishingUpdatesGeneratorInterface { - public function __construct( - private readonly DataTransformerInterface $shortUrlTransformer, - private readonly DataTransformerInterface $orphanVisitTransformer, - ) { + public function __construct(private DataTransformerInterface $shortUrlTransformer) + { } public function newVisitUpdate(Visit $visit): Update @@ -28,7 +26,7 @@ final class PublishingUpdatesGenerator implements PublishingUpdatesGeneratorInte public function newOrphanVisitUpdate(Visit $visit): Update { return Update::forTopicAndPayload(Topic::NEW_ORPHAN_VISIT->value, [ - 'visit' => $this->orphanVisitTransformer->transform($visit), + 'visit' => $visit->jsonSerialize(), ]); } diff --git a/module/Core/src/Visit/Entity/Visit.php b/module/Core/src/Visit/Entity/Visit.php index 0302f898..86854945 100644 --- a/module/Core/src/Visit/Entity/Visit.php +++ b/module/Core/src/Visit/Entity/Visit.php @@ -25,17 +25,60 @@ class Visit extends AbstractEntity implements JsonSerializable public readonly VisitType $type, public readonly string $userAgent, public readonly string $referer, - private readonly bool $potentialBot, + public readonly bool $potentialBot, public readonly ?string $remoteAddr = null, public readonly ?string $visitedUrl = null, private ?VisitLocation $visitLocation = null, + // TODO Make public readonly once VisitRepositoryTest does not try to set it private Chronos $date = new Chronos(), ) { } public static function forValidShortUrl(ShortUrl $shortUrl, Visitor $visitor, bool $anonymize = true): self { - return self::hydrateFromVisitor($shortUrl, VisitType::VALID_SHORT_URL, $visitor, $anonymize); + return self::fromVisitor($shortUrl, VisitType::VALID_SHORT_URL, $visitor, $anonymize); + } + + public static function forBasePath(Visitor $visitor, bool $anonymize = true): self + { + return self::fromVisitor(null, VisitType::BASE_URL, $visitor, $anonymize); + } + + public static function forInvalidShortUrl(Visitor $visitor, bool $anonymize = true): self + { + return self::fromVisitor(null, VisitType::INVALID_SHORT_URL, $visitor, $anonymize); + } + + public static function forRegularNotFound(Visitor $visitor, bool $anonymize = true): self + { + return self::fromVisitor(null, VisitType::REGULAR_404, $visitor, $anonymize); + } + + private static function fromVisitor(?ShortUrl $shortUrl, VisitType $type, Visitor $visitor, bool $anonymize): self + { + return new self( + shortUrl: $shortUrl, + type: $type, + userAgent: $visitor->userAgent, + referer: $visitor->referer, + potentialBot: $visitor->isPotentialBot(), + remoteAddr: self::processAddress($visitor->remoteAddress, $anonymize), + visitedUrl: $visitor->visitedUrl, + ); + } + + private static function processAddress(?string $address, bool $anonymize): ?string + { + // Localhost address does not need to be anonymized + if (! $anonymize || $address === null || $address === IpAddress::LOCALHOST) { + return $address; + } + + try { + return IpAddress::fromString($address)->getAnonymizedCopy()->__toString(); + } catch (InvalidArgumentException) { + return null; + } } public static function fromImport(ShortUrl $shortUrl, ImportedShlinkVisit $importedVisit): self @@ -69,52 +112,6 @@ class Visit extends AbstractEntity implements JsonSerializable ); } - public static function forBasePath(Visitor $visitor, bool $anonymize = true): self - { - return self::hydrateFromVisitor(null, VisitType::BASE_URL, $visitor, $anonymize); - } - - public static function forInvalidShortUrl(Visitor $visitor, bool $anonymize = true): self - { - return self::hydrateFromVisitor(null, VisitType::INVALID_SHORT_URL, $visitor, $anonymize); - } - - public static function forRegularNotFound(Visitor $visitor, bool $anonymize = true): self - { - return self::hydrateFromVisitor(null, VisitType::REGULAR_404, $visitor, $anonymize); - } - - private static function hydrateFromVisitor( - ?ShortUrl $shortUrl, - VisitType $type, - Visitor $visitor, - bool $anonymize, - ): self { - return new self( - shortUrl: $shortUrl, - type: $type, - userAgent: $visitor->userAgent, - referer: $visitor->referer, - potentialBot: $visitor->isPotentialBot(), - remoteAddr: self::processAddress($anonymize, $visitor->remoteAddress), - visitedUrl: $visitor->visitedUrl, - ); - } - - private static function processAddress(bool $anonymize, ?string $address): ?string - { - // Localhost addresses do not need to be anonymized - if (! $anonymize || $address === null || $address === IpAddress::LOCALHOST) { - return $address; - } - - try { - return IpAddress::fromString($address)->getAnonymizedCopy()->__toString(); - } catch (InvalidArgumentException) { - return null; - } - } - public function hasRemoteAddr(): bool { return ! empty($this->remoteAddr); @@ -160,12 +157,21 @@ class Visit extends AbstractEntity implements JsonSerializable public function jsonSerialize(): array { - return [ + $base = [ 'referer' => $this->referer, 'date' => $this->date->toAtomString(), 'userAgent' => $this->userAgent, 'visitLocation' => $this->visitLocation, 'potentialBot' => $this->potentialBot, + 'visitedUrl' => $this->visitedUrl, + ]; + if (! $this->isOrphan()) { + return $base; + } + + return [ + ...$base, + 'type' => $this->type->value, ]; } } diff --git a/module/Core/src/Visit/Transformer/OrphanVisitDataTransformer.php b/module/Core/src/Visit/Transformer/OrphanVisitDataTransformer.php deleted file mode 100644 index c4dd6253..00000000 --- a/module/Core/src/Visit/Transformer/OrphanVisitDataTransformer.php +++ /dev/null @@ -1,23 +0,0 @@ -jsonSerialize(); - $serializedVisit['visitedUrl'] = $visit->visitedUrl; - $serializedVisit['type'] = $visit->type->value; - - return $serializedVisit; - } -} diff --git a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php index 94802cae..032c3263 100644 --- a/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php +++ b/module/Core/test/EventDispatcher/PublishingUpdatesGeneratorTest.php @@ -18,7 +18,6 @@ use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Model\Visitor; use Shlinkio\Shlink\Core\Visit\Model\VisitsSummary; use Shlinkio\Shlink\Core\Visit\Model\VisitType; -use Shlinkio\Shlink\Core\Visit\Transformer\OrphanVisitDataTransformer; class PublishingUpdatesGeneratorTest extends TestCase { @@ -28,7 +27,6 @@ class PublishingUpdatesGeneratorTest extends TestCase { $this->generator = new PublishingUpdatesGenerator( new ShortUrlDataTransformer(new ShortUrlStringifier([])), - new OrphanVisitDataTransformer(), ); } @@ -70,6 +68,7 @@ class PublishingUpdatesGeneratorTest extends TestCase 'visitLocation' => null, 'date' => $visit->getDate()->toAtomString(), 'potentialBot' => false, + 'visitedUrl' => '', ], ], $update->payload); } diff --git a/module/Core/test/Visit/Entity/VisitTest.php b/module/Core/test/Visit/Entity/VisitTest.php index 3fea2882..d9c50af6 100644 --- a/module/Core/test/Visit/Entity/VisitTest.php +++ b/module/Core/test/Visit/Entity/VisitTest.php @@ -4,13 +4,18 @@ declare(strict_types=1); namespace ShlinkioTest\Shlink\Core\Visit\Entity; +use Laminas\Diactoros\ServerRequestFactory; +use Laminas\Diactoros\Uri; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Common\Util\IpAddress; use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; use Shlinkio\Shlink\Core\Visit\Entity\Visit; +use Shlinkio\Shlink\Core\Visit\Entity\VisitLocation; use Shlinkio\Shlink\Core\Visit\Model\Visitor; +use Shlinkio\Shlink\Core\Visit\Model\VisitType; +use Shlinkio\Shlink\IpGeolocation\Model\Location; class VisitTest extends TestCase { @@ -25,6 +30,7 @@ class VisitTest extends TestCase 'userAgent' => $userAgent, 'visitLocation' => null, 'potentialBot' => $expectedToBePotentialBot, + 'visitedUrl' => $visit->visitedUrl, ], $visit->jsonSerialize()); } @@ -40,6 +46,62 @@ class VisitTest extends TestCase yield 'Guzzle' => ['guzzlehttp', true]; } + #[Test, DataProvider('provideOrphanVisits')] + public function isProperlyJsonSerializedWhenOrphan(Visit $visit, array $expectedResult): void + { + self::assertEquals($expectedResult, $visit->jsonSerialize()); + } + + public static function provideOrphanVisits(): iterable + { + yield 'base path visit' => [ + $visit = Visit::forBasePath(Visitor::emptyInstance()), + [ + 'referer' => '', + 'date' => $visit->getDate()->toAtomString(), + 'userAgent' => '', + 'visitLocation' => null, + 'potentialBot' => false, + 'visitedUrl' => '', + 'type' => VisitType::BASE_URL->value, + ], + ]; + yield 'invalid short url visit' => [ + $visit = Visit::forInvalidShortUrl(Visitor::fromRequest( + ServerRequestFactory::fromGlobals()->withHeader('User-Agent', 'foo') + ->withHeader('Referer', 'bar') + ->withUri(new Uri('https://example.com/foo')), + )), + [ + 'referer' => 'bar', + 'date' => $visit->getDate()->toAtomString(), + 'userAgent' => 'foo', + 'visitLocation' => null, + 'potentialBot' => false, + 'visitedUrl' => 'https://example.com/foo', + 'type' => VisitType::INVALID_SHORT_URL->value, + ], + ]; + yield 'regular 404 visit' => [ + $visit = Visit::forRegularNotFound( + Visitor::fromRequest( + ServerRequestFactory::fromGlobals()->withHeader('User-Agent', 'user-agent') + ->withHeader('Referer', 'referer') + ->withUri(new Uri('https://s.test/foo/bar')), + ), + )->locate($location = VisitLocation::fromGeolocation(Location::emptyInstance())), + [ + 'referer' => 'referer', + 'date' => $visit->getDate()->toAtomString(), + 'userAgent' => 'user-agent', + 'visitLocation' => $location, + 'potentialBot' => false, + 'visitedUrl' => 'https://s.test/foo/bar', + 'type' => VisitType::REGULAR_404->value, + ], + ]; + } + #[Test, DataProvider('provideAddresses')] public function addressIsAnonymizedWhenRequested(bool $anonymize, ?string $address, ?string $expectedAddress): void { diff --git a/module/Core/test/Visit/Transformer/OrphanVisitDataTransformerTest.php b/module/Core/test/Visit/Transformer/OrphanVisitDataTransformerTest.php deleted file mode 100644 index 527f4fc9..00000000 --- a/module/Core/test/Visit/Transformer/OrphanVisitDataTransformerTest.php +++ /dev/null @@ -1,85 +0,0 @@ -transformer = new OrphanVisitDataTransformer(); - } - - #[Test, DataProvider('provideVisits')] - public function visitsAreParsedAsExpected(Visit $visit, array $expectedResult): void - { - $result = $this->transformer->transform($visit); - - self::assertEquals($expectedResult, $result); - } - - public static function provideVisits(): iterable - { - yield 'base path visit' => [ - $visit = Visit::forBasePath(Visitor::emptyInstance()), - [ - 'referer' => '', - 'date' => $visit->getDate()->toAtomString(), - 'userAgent' => '', - 'visitLocation' => null, - 'potentialBot' => false, - 'visitedUrl' => '', - 'type' => VisitType::BASE_URL->value, - ], - ]; - yield 'invalid short url visit' => [ - $visit = Visit::forInvalidShortUrl(Visitor::fromRequest( - ServerRequestFactory::fromGlobals()->withHeader('User-Agent', 'foo') - ->withHeader('Referer', 'bar') - ->withUri(new Uri('https://example.com/foo')), - )), - [ - 'referer' => 'bar', - 'date' => $visit->getDate()->toAtomString(), - 'userAgent' => 'foo', - 'visitLocation' => null, - 'potentialBot' => false, - 'visitedUrl' => 'https://example.com/foo', - 'type' => VisitType::INVALID_SHORT_URL->value, - ], - ]; - yield 'regular 404 visit' => [ - $visit = Visit::forRegularNotFound( - Visitor::fromRequest( - ServerRequestFactory::fromGlobals()->withHeader('User-Agent', 'user-agent') - ->withHeader('Referer', 'referer') - ->withUri(new Uri('https://s.test/foo/bar')), - ), - )->locate($location = VisitLocation::fromGeolocation(Location::emptyInstance())), - [ - 'referer' => 'referer', - 'date' => $visit->getDate()->toAtomString(), - 'userAgent' => 'user-agent', - 'visitLocation' => $location, - 'potentialBot' => false, - 'visitedUrl' => 'https://s.test/foo/bar', - 'type' => VisitType::REGULAR_404->value, - ], - ]; - } -} diff --git a/module/Rest/config/dependencies.config.php b/module/Rest/config/dependencies.config.php index 9396dd38..d334d5b0 100644 --- a/module/Rest/config/dependencies.config.php +++ b/module/Rest/config/dependencies.config.php @@ -89,10 +89,7 @@ return [ 'config.url_shortener.domain.hostname', ], Action\Visit\GlobalVisitsAction::class => [Visit\VisitsStatsHelper::class], - Action\Visit\OrphanVisitsAction::class => [ - Visit\VisitsStatsHelper::class, - Visit\Transformer\OrphanVisitDataTransformer::class, - ], + Action\Visit\OrphanVisitsAction::class => [Visit\VisitsStatsHelper::class], Action\Visit\DeleteOrphanVisitsAction::class => [Visit\VisitsDeleter::class], Action\Visit\NonOrphanVisitsAction::class => [Visit\VisitsStatsHelper::class], Action\ShortUrl\ListShortUrlsAction::class => [ diff --git a/module/Rest/src/Action/Visit/OrphanVisitsAction.php b/module/Rest/src/Action/Visit/OrphanVisitsAction.php index 57244197..0224022d 100644 --- a/module/Rest/src/Action/Visit/OrphanVisitsAction.php +++ b/module/Rest/src/Action/Visit/OrphanVisitsAction.php @@ -8,7 +8,6 @@ use Laminas\Diactoros\Response\JsonResponse; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Shlinkio\Shlink\Common\Paginator\Util\PagerfantaUtilsTrait; -use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams; use Shlinkio\Shlink\Core\Visit\VisitsStatsHelperInterface; use Shlinkio\Shlink\Rest\Action\AbstractRestAction; @@ -21,10 +20,8 @@ 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, - private readonly DataTransformerInterface $orphanVisitTransformer, - ) { + public function __construct(private readonly VisitsStatsHelperInterface $visitsHelper) + { } public function handle(ServerRequestInterface $request): ResponseInterface @@ -34,7 +31,7 @@ class OrphanVisitsAction extends AbstractRestAction $visits = $this->visitsHelper->orphanVisits($params, $apiKey); return new JsonResponse([ - 'visits' => $this->serializePaginator($visits, $this->orphanVisitTransformer), + 'visits' => $this->serializePaginator($visits), ]); } } diff --git a/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php b/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php index efa14caa..d5bdfef9 100644 --- a/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php +++ b/module/Rest/test/Action/Visit/OrphanVisitsActionTest.php @@ -11,7 +11,6 @@ use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Shlinkio\Shlink\Common\Paginator\Paginator; -use Shlinkio\Shlink\Common\Rest\DataTransformerInterface; use Shlinkio\Shlink\Core\Exception\ValidationException; use Shlinkio\Shlink\Core\Visit\Entity\Visit; use Shlinkio\Shlink\Core\Visit\Model\OrphanVisitsParams; @@ -26,14 +25,11 @@ class OrphanVisitsActionTest extends TestCase { private OrphanVisitsAction $action; private MockObject & VisitsStatsHelperInterface $visitsHelper; - private MockObject & DataTransformerInterface $orphanVisitTransformer; protected function setUp(): void { $this->visitsHelper = $this->createMock(VisitsStatsHelperInterface::class); - $this->orphanVisitTransformer = $this->createMock(DataTransformerInterface::class); - - $this->action = new OrphanVisitsAction($this->visitsHelper, $this->orphanVisitTransformer); + $this->action = new OrphanVisitsAction($this->visitsHelper); } #[Test] @@ -45,9 +41,6 @@ class OrphanVisitsActionTest extends TestCase $this->isInstanceOf(OrphanVisitsParams::class), )->willReturn(new Paginator(new ArrayAdapter($visits))); $visitsAmount = count($visits); - $this->orphanVisitTransformer->expects($this->exactly($visitsAmount))->method('transform')->with( - $this->isInstanceOf(Visit::class), - )->willReturn([]); /** @var JsonResponse $response */ $response = $this->action->handle(