mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
Allow short URLs list to be filtered by domain authority
This commit is contained in:
parent
525a306ec6
commit
bb270396b6
@ -125,6 +125,15 @@
|
|||||||
"false"
|
"false"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "domain",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Get short URLs for this particular domain only. Use **DEFAULT** keyword for default domain.",
|
||||||
|
"required": false,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -74,7 +74,7 @@ class ListShortUrlsCommandTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(
|
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(
|
||||||
ShortUrlsParams::emptyInstance(),
|
ShortUrlsParams::empty(),
|
||||||
)->willReturn(new Paginator(new ArrayAdapter($data)));
|
)->willReturn(new Paginator(new ArrayAdapter($data)));
|
||||||
|
|
||||||
$this->commandTester->setInputs(['n']);
|
$this->commandTester->setInputs(['n']);
|
||||||
@ -110,7 +110,7 @@ class ListShortUrlsCommandTest extends TestCase
|
|||||||
ApiKey $apiKey,
|
ApiKey $apiKey,
|
||||||
): void {
|
): void {
|
||||||
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(
|
$this->shortUrlService->expects($this->once())->method('listShortUrls')->with(
|
||||||
ShortUrlsParams::emptyInstance(),
|
ShortUrlsParams::empty(),
|
||||||
)->willReturn(new Paginator(new ArrayAdapter([
|
)->willReturn(new Paginator(new ArrayAdapter([
|
||||||
ShortUrlWithVisitsSummary::fromShortUrl(
|
ShortUrlWithVisitsSummary::fromShortUrl(
|
||||||
ShortUrl::create(ShortUrlCreation::fromRawData([
|
ShortUrl::create(ShortUrlCreation::fromRawData([
|
||||||
|
@ -19,17 +19,18 @@ final class ShortUrlsParams
|
|||||||
private function __construct(
|
private function __construct(
|
||||||
public readonly int $page,
|
public readonly int $page,
|
||||||
public readonly int $itemsPerPage,
|
public readonly int $itemsPerPage,
|
||||||
public readonly ?string $searchTerm,
|
public readonly string|null $searchTerm,
|
||||||
public readonly array $tags,
|
public readonly array $tags,
|
||||||
public readonly Ordering $orderBy,
|
public readonly Ordering $orderBy,
|
||||||
public readonly ?DateRange $dateRange,
|
public readonly ?DateRange $dateRange,
|
||||||
public readonly bool $excludeMaxVisitsReached,
|
public readonly bool $excludeMaxVisitsReached,
|
||||||
public readonly bool $excludePastValidUntil,
|
public readonly bool $excludePastValidUntil,
|
||||||
public readonly TagsMode $tagsMode = TagsMode::ANY,
|
public readonly TagsMode $tagsMode = TagsMode::ANY,
|
||||||
|
public readonly string|null $domain = null,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function emptyInstance(): self
|
public static function empty(): self
|
||||||
{
|
{
|
||||||
return self::fromRawData([]);
|
return self::fromRawData([]);
|
||||||
}
|
}
|
||||||
@ -59,6 +60,7 @@ final class ShortUrlsParams
|
|||||||
excludeMaxVisitsReached: $inputFilter->getValue(ShortUrlsParamsInputFilter::EXCLUDE_MAX_VISITS_REACHED),
|
excludeMaxVisitsReached: $inputFilter->getValue(ShortUrlsParamsInputFilter::EXCLUDE_MAX_VISITS_REACHED),
|
||||||
excludePastValidUntil: $inputFilter->getValue(ShortUrlsParamsInputFilter::EXCLUDE_PAST_VALID_UNTIL),
|
excludePastValidUntil: $inputFilter->getValue(ShortUrlsParamsInputFilter::EXCLUDE_PAST_VALID_UNTIL),
|
||||||
tagsMode: self::resolveTagsMode($inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS_MODE)),
|
tagsMode: self::resolveTagsMode($inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS_MODE)),
|
||||||
|
domain: $inputFilter->getValue(ShortUrlsParamsInputFilter::DOMAIN),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,6 +26,7 @@ class ShortUrlsParamsInputFilter extends InputFilter
|
|||||||
public const ORDER_BY = 'orderBy';
|
public const ORDER_BY = 'orderBy';
|
||||||
public const EXCLUDE_MAX_VISITS_REACHED = 'excludeMaxVisitsReached';
|
public const EXCLUDE_MAX_VISITS_REACHED = 'excludeMaxVisitsReached';
|
||||||
public const EXCLUDE_PAST_VALID_UNTIL = 'excludePastValidUntil';
|
public const EXCLUDE_PAST_VALID_UNTIL = 'excludePastValidUntil';
|
||||||
|
public const DOMAIN = 'domain';
|
||||||
|
|
||||||
public function __construct(array $data)
|
public function __construct(array $data)
|
||||||
{
|
{
|
||||||
@ -56,5 +57,7 @@ class ShortUrlsParamsInputFilter extends InputFilter
|
|||||||
|
|
||||||
$this->add(InputFactory::boolean(self::EXCLUDE_MAX_VISITS_REACHED));
|
$this->add(InputFactory::boolean(self::EXCLUDE_MAX_VISITS_REACHED));
|
||||||
$this->add(InputFactory::boolean(self::EXCLUDE_PAST_VALID_UNTIL));
|
$this->add(InputFactory::boolean(self::EXCLUDE_PAST_VALID_UNTIL));
|
||||||
|
|
||||||
|
$this->add(InputFactory::basic(self::DOMAIN));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ class ShortUrlsCountFiltering
|
|||||||
$params->excludePastValidUntil,
|
$params->excludePastValidUntil,
|
||||||
$apiKey,
|
$apiKey,
|
||||||
$defaultDomain,
|
$defaultDomain,
|
||||||
|
$params->domain,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,9 @@ class ShortUrlsListFiltering extends ShortUrlsCountFiltering
|
|||||||
bool $excludeMaxVisitsReached = false,
|
bool $excludeMaxVisitsReached = false,
|
||||||
bool $excludePastValidUntil = false,
|
bool $excludePastValidUntil = false,
|
||||||
?ApiKey $apiKey = null,
|
?ApiKey $apiKey = null,
|
||||||
|
// Used only to determine if search term includes default domain
|
||||||
?string $defaultDomain = null,
|
?string $defaultDomain = null,
|
||||||
|
?string $domain = null,
|
||||||
) {
|
) {
|
||||||
parent::__construct(
|
parent::__construct(
|
||||||
$searchTerm,
|
$searchTerm,
|
||||||
@ -34,6 +36,7 @@ class ShortUrlsListFiltering extends ShortUrlsCountFiltering
|
|||||||
$excludePastValidUntil,
|
$excludePastValidUntil,
|
||||||
$apiKey,
|
$apiKey,
|
||||||
$defaultDomain,
|
$defaultDomain,
|
||||||
|
$domain,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,6 +59,7 @@ class ShortUrlsListFiltering extends ShortUrlsCountFiltering
|
|||||||
$params->excludePastValidUntil,
|
$params->excludePastValidUntil,
|
||||||
$apiKey,
|
$apiKey,
|
||||||
$defaultDomain,
|
$defaultDomain,
|
||||||
|
$params->domain,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ use Doctrine\ORM\Query\Expr\Join;
|
|||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
use Happyr\DoctrineSpecification\Repository\EntitySpecificationRepository;
|
||||||
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
use Shlinkio\Shlink\Common\Doctrine\Type\ChronosDateTimeType;
|
||||||
|
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\OrderableField;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\OrderableField;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlWithVisitsSummary;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlWithVisitsSummary;
|
||||||
@ -118,8 +119,8 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
|||||||
$qb->expr()->like('d.authority', ':searchPattern'),
|
$qb->expr()->like('d.authority', ':searchPattern'),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Include default domain in search if provided
|
// Include default domain in search if included, and a domain was not explicitly provided
|
||||||
if ($filtering->searchIncludesDefaultDomain) {
|
if ($filtering->searchIncludesDefaultDomain && $filtering->domain === null) {
|
||||||
$conditions[] = $qb->expr()->isNull('s.domain');
|
$conditions[] = $qb->expr()->isNull('s.domain');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,6 +143,12 @@ class ShortUrlListRepository extends EntitySpecificationRepository implements Sh
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($filtering->domain !== null) {
|
if ($filtering->domain !== null) {
|
||||||
|
if ($filtering->domain === Domain::DEFAULT_AUTHORITY) {
|
||||||
|
$qb->andWhere($qb->expr()->isNull('s.domain'));
|
||||||
|
} else {
|
||||||
|
$qb->andWhere($qb->expr()->eq('d.authority', ':domain'))
|
||||||
|
->setParameter('domain', $filtering->domain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($filtering->excludeMaxVisitsReached) {
|
if ($filtering->excludeMaxVisitsReached) {
|
||||||
|
@ -9,6 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection;
|
|||||||
use PHPUnit\Framework\Attributes\Test;
|
use PHPUnit\Framework\Attributes\Test;
|
||||||
use ReflectionObject;
|
use ReflectionObject;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
|
use Shlinkio\Shlink\Core\Domain\Entity\Domain;
|
||||||
use Shlinkio\Shlink\Core\Model\Ordering;
|
use Shlinkio\Shlink\Core\Model\Ordering;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\ShortUrl\Model\OrderableField;
|
use Shlinkio\Shlink\Core\ShortUrl\Model\OrderableField;
|
||||||
@ -261,16 +262,23 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
|
|||||||
|
|
||||||
$this->getEntityManager()->flush();
|
$this->getEntityManager()->flush();
|
||||||
|
|
||||||
$buildFiltering = static fn (string $searchTerm) => new ShortUrlsListFiltering(
|
$buildFiltering = static fn (string $searchTerm = '', string|null $domain = null) => new ShortUrlsListFiltering(
|
||||||
searchTerm: $searchTerm,
|
searchTerm: $searchTerm,
|
||||||
defaultDomain: 'deFaulT-domain.com',
|
defaultDomain: 'deFaulT-domain.com',
|
||||||
|
domain: $domain,
|
||||||
);
|
);
|
||||||
|
|
||||||
self::assertCount(2, $this->repo->findList($buildFiltering('default-dom')));
|
self::assertCount(2, $this->repo->findList($buildFiltering(searchTerm: 'default-dom')));
|
||||||
self::assertCount(2, $this->repo->findList($buildFiltering('DOM')));
|
self::assertCount(2, $this->repo->findList($buildFiltering(searchTerm: 'DOM')));
|
||||||
self::assertCount(1, $this->repo->findList($buildFiltering('another')));
|
self::assertCount(1, $this->repo->findList($buildFiltering(searchTerm: 'another')));
|
||||||
self::assertCount(3, $this->repo->findList($buildFiltering('foo')));
|
self::assertCount(3, $this->repo->findList($buildFiltering(searchTerm: 'foo')));
|
||||||
self::assertCount(0, $this->repo->findList($buildFiltering('no results')));
|
self::assertCount(0, $this->repo->findList($buildFiltering(searchTerm: 'no results')));
|
||||||
|
self::assertCount(1, $this->repo->findList($buildFiltering(domain: 'another.com')));
|
||||||
|
self::assertCount(0, $this->repo->findList($buildFiltering(
|
||||||
|
searchTerm: 'default-domain.com',
|
||||||
|
domain: 'another.com',
|
||||||
|
)));
|
||||||
|
self::assertCount(2, $this->repo->findList($buildFiltering(domain: Domain::DEFAULT_AUTHORITY)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
@ -303,18 +311,42 @@ class ShortUrlListRepositoryTest extends DatabaseTestCase
|
|||||||
$this->getEntityManager()->flush();
|
$this->getEntityManager()->flush();
|
||||||
|
|
||||||
$filtering = static fn (bool $excludeMaxVisitsReached, bool $excludePastValidUntil) =>
|
$filtering = static fn (bool $excludeMaxVisitsReached, bool $excludePastValidUntil) =>
|
||||||
new ShortUrlsListFiltering(
|
new ShortUrlsListFiltering(
|
||||||
excludeMaxVisitsReached: $excludeMaxVisitsReached,
|
excludeMaxVisitsReached: $excludeMaxVisitsReached,
|
||||||
excludePastValidUntil: $excludePastValidUntil,
|
excludePastValidUntil: $excludePastValidUntil,
|
||||||
);
|
);
|
||||||
|
|
||||||
self::assertCount(4, $this->repo->findList($filtering(false, false)));
|
self::assertCount(4, $this->repo->findList($filtering(
|
||||||
self::assertEquals(4, $this->repo->countList($filtering(false, false)));
|
excludeMaxVisitsReached: false,
|
||||||
self::assertCount(3, $this->repo->findList($filtering(true, false)));
|
excludePastValidUntil: false,
|
||||||
self::assertEquals(3, $this->repo->countList($filtering(true, false)));
|
)));
|
||||||
self::assertCount(3, $this->repo->findList($filtering(false, true)));
|
self::assertEquals(4, $this->repo->countList($filtering(
|
||||||
self::assertEquals(3, $this->repo->countList($filtering(false, true)));
|
excludeMaxVisitsReached: false,
|
||||||
self::assertCount(2, $this->repo->findList($filtering(true, true)));
|
excludePastValidUntil: false,
|
||||||
self::assertEquals(2, $this->repo->countList($filtering(true, true)));
|
)));
|
||||||
|
self::assertCount(3, $this->repo->findList($filtering(
|
||||||
|
excludeMaxVisitsReached: true,
|
||||||
|
excludePastValidUntil: false,
|
||||||
|
)));
|
||||||
|
self::assertEquals(3, $this->repo->countList($filtering(
|
||||||
|
excludeMaxVisitsReached: true,
|
||||||
|
excludePastValidUntil: false,
|
||||||
|
)));
|
||||||
|
self::assertCount(3, $this->repo->findList($filtering(
|
||||||
|
excludeMaxVisitsReached: false,
|
||||||
|
excludePastValidUntil: true,
|
||||||
|
)));
|
||||||
|
self::assertEquals(3, $this->repo->countList($filtering(
|
||||||
|
excludeMaxVisitsReached: false,
|
||||||
|
excludePastValidUntil: true,
|
||||||
|
)));
|
||||||
|
self::assertCount(2, $this->repo->findList($filtering(
|
||||||
|
excludeMaxVisitsReached: true,
|
||||||
|
excludePastValidUntil: true,
|
||||||
|
)));
|
||||||
|
self::assertEquals(2, $this->repo->countList($filtering(
|
||||||
|
excludeMaxVisitsReached: true,
|
||||||
|
excludePastValidUntil: true,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class ShortUrlListServiceTest extends TestCase
|
|||||||
$this->repo->expects($this->once())->method('findList')->willReturn($list);
|
$this->repo->expects($this->once())->method('findList')->willReturn($list);
|
||||||
$this->repo->expects($this->once())->method('countList')->willReturn(count($list));
|
$this->repo->expects($this->once())->method('countList')->willReturn(count($list));
|
||||||
|
|
||||||
$paginator = $this->service->listShortUrls(ShortUrlsParams::emptyInstance(), $apiKey);
|
$paginator = $this->service->listShortUrls(ShortUrlsParams::empty(), $apiKey);
|
||||||
|
|
||||||
self::assertCount(4, $paginator);
|
self::assertCount(4, $paginator);
|
||||||
self::assertCount(4, $paginator->getCurrentPageResults());
|
self::assertCount(4, $paginator->getCurrentPageResults());
|
||||||
|
Loading…
Reference in New Issue
Block a user