Allow filtering by domain in VisitRepository::findNonOrphanVisits

This commit is contained in:
Alejandro Celaya
2025-10-30 10:08:46 +01:00
parent 94426c7bf4
commit a236f19dc4
3 changed files with 43 additions and 55 deletions

View File

@@ -20,7 +20,6 @@ use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsListFiltering;
use Shlinkio\Shlink\Core\Visit\Persistence\WithDomainVisitsCountFiltering;
use Shlinkio\Shlink\Core\Visit\Persistence\WithDomainVisitsListFiltering;
use Shlinkio\Shlink\Core\Visit\Spec\CountOfNonOrphanVisits;
use Shlinkio\Shlink\Rest\ApiKey\Role;
use Shlinkio\Shlink\Rest\Entity\ApiKey;
@@ -203,22 +202,40 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
*/
public function findNonOrphanVisits(WithDomainVisitsListFiltering $filtering): array
{
$qb = $this->createNonOrphanVisitsQueryBuilder($filtering);
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
}
public function countNonOrphanVisits(WithDomainVisitsCountFiltering $filtering): int
{
$qb = $this->createNonOrphanVisitsQueryBuilder($filtering);
$qb->select('COUNT(v.id)');
return (int) $qb->getQuery()->getSingleScalarResult();
}
private function createNonOrphanVisitsQueryBuilder(WithDomainVisitsCountFiltering $filtering): QueryBuilder
{
$conn = $this->getEntityManager()->getConnection();
$qb = $this->createAllVisitsQueryBuilder($filtering);
$qb->andWhere($qb->expr()->isNotNull('v.shortUrl'));
$apiKey = $filtering->apiKey;
if (ApiKey::isShortUrlRestricted($apiKey)) {
$domain = $filtering->domain;
if (ApiKey::isShortUrlRestricted($apiKey) || $domain !== null) {
$qb->join('v.shortUrl', 's');
}
if ($domain === Domain::DEFAULT_AUTHORITY) {
$qb->andWhere($qb->expr()->isNull('s.domain'));
} elseif ($domain !== null) {
$qb->join('s.domain', 'd')
->andWhere($qb->expr()->eq('d.authority', $conn->quote($domain)));
}
$this->applySpecification($qb, $apiKey?->inlinedSpec(), 'v');
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
}
public function countNonOrphanVisits(VisitsCountFiltering $filtering): int
{
return (int) $this->matchSingleScalarResult(new CountOfNonOrphanVisits($filtering));
return $qb;
}
private function createAllVisitsQueryBuilder(VisitsCountFiltering $filtering): QueryBuilder

View File

@@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Visit\Spec;
use Happyr\DoctrineSpecification\Spec;
use Happyr\DoctrineSpecification\Specification\BaseSpecification;
use Happyr\DoctrineSpecification\Specification\Specification;
use Shlinkio\Shlink\Core\Spec\InDateRange;
use Shlinkio\Shlink\Core\Visit\Persistence\VisitsCountFiltering;
use Shlinkio\Shlink\Rest\ApiKey\Spec\WithApiKeySpecsEnsuringJoin;
class CountOfNonOrphanVisits extends BaseSpecification
{
public function __construct(private readonly VisitsCountFiltering $filtering)
{
parent::__construct();
}
protected function getSpec(): Specification
{
$conditions = [
Spec::isNotNull('shortUrl'),
new InDateRange($this->filtering->dateRange),
];
if ($this->filtering->excludeBots) {
$conditions[] = Spec::eq('potentialBot', false);
}
$apiKey = $this->filtering->apiKey;
if ($apiKey !== null) {
$conditions[] = new WithApiKeySpecsEnsuringJoin($apiKey, 'shortUrl');
}
return Spec::countOf(Spec::andX(...$conditions));
}
}

View File

@@ -339,13 +339,17 @@ class VisitRepositoryTest extends DatabaseTestCase
$this->getEntityManager()->flush();
self::assertEquals(4 + 5 + 7, $this->repo->countNonOrphanVisits(new VisitsCountFiltering()));
self::assertEquals(4 + 5 + 7, $this->repo->countNonOrphanVisits(new WithDomainVisitsCountFiltering()));
self::assertEquals(4 + 5 + 7, $this->countRepo->countNonOrphanVisits(new VisitsCountFiltering()));
self::assertEquals(4, $this->repo->countNonOrphanVisits(new VisitsCountFiltering(apiKey: $apiKey1)));
self::assertEquals(4, $this->repo->countNonOrphanVisits(new WithDomainVisitsCountFiltering(apiKey: $apiKey1)));
self::assertEquals(4, $this->countRepo->countNonOrphanVisits(new VisitsCountFiltering(apiKey: $apiKey1)));
self::assertEquals(5 + 7, $this->repo->countNonOrphanVisits(new VisitsCountFiltering(apiKey: $apiKey2)));
self::assertEquals(5 + 7, $this->repo->countNonOrphanVisits(new WithDomainVisitsCountFiltering(
apiKey: $apiKey2,
)));
self::assertEquals(5 + 7, $this->countRepo->countNonOrphanVisits(new VisitsCountFiltering(apiKey: $apiKey2)));
self::assertEquals(4 + 7, $this->repo->countNonOrphanVisits(new VisitsCountFiltering(apiKey: $domainApiKey)));
self::assertEquals(4 + 7, $this->repo->countNonOrphanVisits(new WithDomainVisitsCountFiltering(
apiKey: $domainApiKey,
)));
self::assertEquals(4 + 7, $this->countRepo->countNonOrphanVisits(new VisitsCountFiltering(
apiKey: $domainApiKey,
)));
@@ -355,21 +359,27 @@ class VisitRepositoryTest extends DatabaseTestCase
self::assertEquals(0, $this->orphanCountRepo->countOrphanVisits(new OrphanVisitsCountFiltering(
apiKey: $noOrphanVisitsApiKey,
)));
self::assertEquals(4, $this->repo->countNonOrphanVisits(new VisitsCountFiltering(DateRange::since(
self::assertEquals(4, $this->repo->countNonOrphanVisits(new WithDomainVisitsCountFiltering(DateRange::since(
Chronos::parse('2016-01-05')->startOfDay(),
))));
self::assertEquals(2, $this->repo->countNonOrphanVisits(new VisitsCountFiltering(DateRange::since(
self::assertEquals(2, $this->repo->countNonOrphanVisits(new WithDomainVisitsCountFiltering(DateRange::since(
Chronos::parse('2016-01-03')->startOfDay(),
), false, $apiKey1)));
self::assertEquals(1, $this->repo->countNonOrphanVisits(new VisitsCountFiltering(DateRange::since(
self::assertEquals(1, $this->repo->countNonOrphanVisits(new WithDomainVisitsCountFiltering(DateRange::since(
Chronos::parse('2016-01-07')->startOfDay(),
), false, $apiKey2)));
self::assertEquals(3 + 5, $this->repo->countNonOrphanVisits(
new VisitsCountFiltering(excludeBots: true, apiKey: $apiKey2),
new WithDomainVisitsCountFiltering(excludeBots: true, apiKey: $apiKey2),
));
self::assertEquals(3 + 5, $this->countRepo->countNonOrphanVisits(
new VisitsCountFiltering(excludeBots: true, apiKey: $apiKey2),
));
self::assertEquals(4 + 7, $this->repo->countNonOrphanVisits(
new WithDomainVisitsCountFiltering(domain: $domain->authority),
));
self::assertEquals(5, $this->repo->countNonOrphanVisits(
new WithDomainVisitsCountFiltering(domain: Domain::DEFAULT_AUTHORITY),
));
self::assertEquals(4, $this->repo->countOrphanVisits(new OrphanVisitsCountFiltering()));
self::assertEquals(4, $this->orphanCountRepo->countOrphanVisits(new OrphanVisitsCountFiltering()));
self::assertEquals(3, $this->repo->countOrphanVisits(new OrphanVisitsCountFiltering(excludeBots: true)));