diff --git a/composer.json b/composer.json index b47d0dfc..c9d9757d 100644 --- a/composer.json +++ b/composer.json @@ -65,7 +65,7 @@ "require-dev": { "cebe/php-openapi": "^1.7", "devster/ubench": "^2.1", - "infection/infection": "^0.26.19", + "infection/infection": "^0.27", "openswoole/ide-helper": "~22.0.0", "phpstan/phpstan": "^1.9", "phpstan/phpstan-doctrine": "^1.3", diff --git a/module/Core/src/Visit/Repository/VisitDeleterRepository.php b/module/Core/src/Visit/Repository/VisitDeleterRepository.php index 602ba576..19f79dc7 100644 --- a/module/Core/src/Visit/Repository/VisitDeleterRepository.php +++ b/module/Core/src/Visit/Repository/VisitDeleterRepository.php @@ -19,4 +19,13 @@ class VisitDeleterRepository extends EntitySpecificationRepository implements Vi return $qb->getQuery()->execute(); } + + public function deleteOrphanVisits(): int + { + $qb = $this->getEntityManager()->createQueryBuilder(); + $qb->delete(Visit::class, 'v') + ->where($qb->expr()->isNull('v.shortUrl')); + + return $qb->getQuery()->execute(); + } } diff --git a/module/Core/src/Visit/Repository/VisitDeleterRepositoryInterface.php b/module/Core/src/Visit/Repository/VisitDeleterRepositoryInterface.php index 61a8af9b..a1516225 100644 --- a/module/Core/src/Visit/Repository/VisitDeleterRepositoryInterface.php +++ b/module/Core/src/Visit/Repository/VisitDeleterRepositoryInterface.php @@ -9,4 +9,6 @@ use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl; interface VisitDeleterRepositoryInterface { public function deleteShortUrlVisits(ShortUrl $shortUrl): int; + + public function deleteOrphanVisits(): int; } diff --git a/module/Core/test-db/Visit/Repository/VisitDeleterRepositoryTest.php b/module/Core/test-db/Visit/Repository/VisitDeleterRepositoryTest.php index 53b1585f..62aa89e7 100644 --- a/module/Core/test-db/Visit/Repository/VisitDeleterRepositoryTest.php +++ b/module/Core/test-db/Visit/Repository/VisitDeleterRepositoryTest.php @@ -25,7 +25,7 @@ class VisitDeleterRepositoryTest extends DatabaseTestCase } #[Test] - public function deletesExpectedVisits(): void + public function deletesExpectedShortUrlVisits(): void { $shortUrl1 = ShortUrl::withLongUrl('https://foo.com'); $this->getEntityManager()->persist($shortUrl1); @@ -59,4 +59,21 @@ class VisitDeleterRepositoryTest extends DatabaseTestCase self::assertEquals(1, $this->repo->deleteShortUrlVisits($shortUrl3)); self::assertEquals(0, $this->repo->deleteShortUrlVisits($shortUrl3)); } + + #[Test] + public function deletesExpectedOrphanVisits(): void + { + $visitor = Visitor::emptyInstance(); + $this->getEntityManager()->persist(Visit::forBasePath($visitor)); + $this->getEntityManager()->persist(Visit::forInvalidShortUrl($visitor)); + $this->getEntityManager()->persist(Visit::forRegularNotFound($visitor)); + $this->getEntityManager()->persist(Visit::forBasePath($visitor)); + $this->getEntityManager()->persist(Visit::forInvalidShortUrl($visitor)); + $this->getEntityManager()->persist(Visit::forRegularNotFound($visitor)); + + $this->getEntityManager()->flush(); + + self::assertEquals(6, $this->repo->deleteOrphanVisits()); + self::assertEquals(0, $this->repo->deleteOrphanVisits()); + } } diff --git a/module/Rest/src/Action/Visit/OrphanVisitsAction.php b/module/Rest/src/Action/Visit/OrphanVisitsAction.php index e53c2a6f..af5292a2 100644 --- a/module/Rest/src/Action/Visit/OrphanVisitsAction.php +++ b/module/Rest/src/Action/Visit/OrphanVisitsAction.php @@ -21,8 +21,8 @@ class OrphanVisitsAction extends AbstractRestAction protected const ROUTE_ALLOWED_METHODS = [self::METHOD_GET]; public function __construct( - private VisitsStatsHelperInterface $visitsHelper, - private DataTransformerInterface $orphanVisitTransformer, + private readonly VisitsStatsHelperInterface $visitsHelper, + private readonly DataTransformerInterface $orphanVisitTransformer, ) { }