mirror of
https://github.com/shlinkio/shlink.git
synced 2025-02-25 18:45:27 -06:00
Created DTOs with implicit validation to wrap short URLs lists params
This commit is contained in:
parent
240d2588f9
commit
452bfea088
@ -4,16 +4,17 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
|
namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
|
||||||
|
|
||||||
use Cake\Chronos\Chronos;
|
|
||||||
use Laminas\Paginator\Paginator;
|
use Laminas\Paginator\Paginator;
|
||||||
use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand;
|
use Shlinkio\Shlink\CLI\Command\Util\AbstractWithDateRangeCommand;
|
||||||
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
use Shlinkio\Shlink\CLI\Util\ExitCodes;
|
||||||
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||||
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
|
||||||
|
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||||
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
|
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
|
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
|
||||||
|
use Shlinkio\Shlink\Core\Validation\ShortUrlsParamsInputFilter;
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Input\InputOption;
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
@ -108,7 +109,14 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
|
|||||||
$orderBy = $this->processOrderBy($input);
|
$orderBy = $this->processOrderBy($input);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
$result = $this->renderPage($output, $page, $searchTerm, $tags, $showTags, $startDate, $endDate, $orderBy);
|
$result = $this->renderPage($output, $showTags, ShortUrlsParams::fromRawData([
|
||||||
|
ShortUrlsParamsInputFilter::PAGE => $page,
|
||||||
|
ShortUrlsParamsInputFilter::SEARCH_TERM => $searchTerm,
|
||||||
|
ShortUrlsParamsInputFilter::TAGS => $tags,
|
||||||
|
ShortUrlsOrdering::ORDER_BY => $orderBy,
|
||||||
|
ShortUrlsParamsInputFilter::START_DATE => $startDate !== null ? $startDate->toAtomString() : null,
|
||||||
|
ShortUrlsParamsInputFilter::END_DATE => $endDate !== null ? $endDate->toAtomString() : null,
|
||||||
|
]));
|
||||||
$page++;
|
$page++;
|
||||||
|
|
||||||
$continue = $this->isLastPage($result)
|
$continue = $this->isLastPage($result)
|
||||||
@ -122,26 +130,9 @@ class ListShortUrlsCommand extends AbstractWithDateRangeCommand
|
|||||||
return ExitCodes::EXIT_SUCCESS;
|
return ExitCodes::EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function renderPage(OutputInterface $output, bool $showTags, ShortUrlsParams $params): Paginator
|
||||||
* @param string|array|null $orderBy
|
{
|
||||||
*/
|
$result = $this->shortUrlService->listShortUrls($params);
|
||||||
private function renderPage(
|
|
||||||
OutputInterface $output,
|
|
||||||
int $page,
|
|
||||||
?string $searchTerm,
|
|
||||||
array $tags,
|
|
||||||
bool $showTags,
|
|
||||||
?Chronos $startDate,
|
|
||||||
?Chronos $endDate,
|
|
||||||
$orderBy
|
|
||||||
): Paginator {
|
|
||||||
$result = $this->shortUrlService->listShortUrls(
|
|
||||||
$page,
|
|
||||||
$searchTerm,
|
|
||||||
$tags,
|
|
||||||
$orderBy,
|
|
||||||
new DateRange($startDate, $endDate),
|
|
||||||
);
|
|
||||||
|
|
||||||
$headers = ['Short code', 'Short URL', 'Long URL', 'Date created', 'Visits count'];
|
$headers = ['Short code', 'Short URL', 'Long URL', 'Date created', 'Visits count'];
|
||||||
if ($showTags) {
|
if ($showTags) {
|
||||||
|
@ -11,8 +11,8 @@ use PHPUnit\Framework\TestCase;
|
|||||||
use Prophecy\Argument;
|
use Prophecy\Argument;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\CLI\Command\ShortUrl\ListShortUrlsCommand;
|
use Shlinkio\Shlink\CLI\Command\ShortUrl\ListShortUrlsCommand;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
|
||||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
|
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
use Symfony\Component\Console\Application;
|
use Symfony\Component\Console\Application;
|
||||||
use Symfony\Component\Console\Tester\CommandTester;
|
use Symfony\Component\Console\Tester\CommandTester;
|
||||||
@ -64,7 +64,7 @@ class ListShortUrlsCommandTest extends TestCase
|
|||||||
$data[] = new ShortUrl('url_' . $i);
|
$data[] = new ShortUrl('url_' . $i);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->shortUrlService->listShortUrls(1, null, [], null, new DateRange())
|
$this->shortUrlService->listShortUrls(ShortUrlsParams::emptyInstance())
|
||||||
->willReturn(new Paginator(new ArrayAdapter($data)))
|
->willReturn(new Paginator(new ArrayAdapter($data)))
|
||||||
->shouldBeCalledOnce();
|
->shouldBeCalledOnce();
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ class ListShortUrlsCommandTest extends TestCase
|
|||||||
public function passingPageWillMakeListStartOnThatPage(): void
|
public function passingPageWillMakeListStartOnThatPage(): void
|
||||||
{
|
{
|
||||||
$page = 5;
|
$page = 5;
|
||||||
$this->shortUrlService->listShortUrls($page, null, [], null, new DateRange())
|
$this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData(['page' => $page]))
|
||||||
->willReturn(new Paginator(new ArrayAdapter()))
|
->willReturn(new Paginator(new ArrayAdapter()))
|
||||||
->shouldBeCalledOnce();
|
->shouldBeCalledOnce();
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ class ListShortUrlsCommandTest extends TestCase
|
|||||||
/** @test */
|
/** @test */
|
||||||
public function ifTagsFlagIsProvidedTagsColumnIsIncluded(): void
|
public function ifTagsFlagIsProvidedTagsColumnIsIncluded(): void
|
||||||
{
|
{
|
||||||
$this->shortUrlService->listShortUrls(1, null, [], null, new DateRange())
|
$this->shortUrlService->listShortUrls(ShortUrlsParams::emptyInstance())
|
||||||
->willReturn(new Paginator(new ArrayAdapter()))
|
->willReturn(new Paginator(new ArrayAdapter()))
|
||||||
->shouldBeCalledOnce();
|
->shouldBeCalledOnce();
|
||||||
|
|
||||||
@ -115,10 +115,16 @@ class ListShortUrlsCommandTest extends TestCase
|
|||||||
?int $page,
|
?int $page,
|
||||||
?string $searchTerm,
|
?string $searchTerm,
|
||||||
array $tags,
|
array $tags,
|
||||||
?DateRange $dateRange
|
?string $startDate = null,
|
||||||
|
?string $endDate = null
|
||||||
): void {
|
): void {
|
||||||
$listShortUrls = $this->shortUrlService->listShortUrls($page, $searchTerm, $tags, null, $dateRange)
|
$listShortUrls = $this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData([
|
||||||
->willReturn(new Paginator(new ArrayAdapter()));
|
'page' => $page,
|
||||||
|
'searchTerm' => $searchTerm,
|
||||||
|
'tags' => $tags,
|
||||||
|
'startDate' => $startDate !== null ? Chronos::parse($startDate)->toAtomString() : null,
|
||||||
|
'endDate' => $endDate !== null ? Chronos::parse($endDate)->toAtomString() : null,
|
||||||
|
]))->willReturn(new Paginator(new ArrayAdapter()));
|
||||||
|
|
||||||
$this->commandTester->setInputs(['n']);
|
$this->commandTester->setInputs(['n']);
|
||||||
$this->commandTester->execute($commandArgs);
|
$this->commandTester->execute($commandArgs);
|
||||||
@ -128,36 +134,37 @@ class ListShortUrlsCommandTest extends TestCase
|
|||||||
|
|
||||||
public function provideArgs(): iterable
|
public function provideArgs(): iterable
|
||||||
{
|
{
|
||||||
yield [[], 1, null, [], new DateRange()];
|
yield [[], 1, null, []];
|
||||||
yield [['--page' => $page = 3], $page, null, [], new DateRange()];
|
yield [['--page' => $page = 3], $page, null, []];
|
||||||
yield [['--searchTerm' => $searchTerm = 'search this'], 1, $searchTerm, [], new DateRange()];
|
yield [['--searchTerm' => $searchTerm = 'search this'], 1, $searchTerm, []];
|
||||||
yield [
|
yield [
|
||||||
['--page' => $page = 3, '--searchTerm' => $searchTerm = 'search this', '--tags' => $tags = 'foo,bar'],
|
['--page' => $page = 3, '--searchTerm' => $searchTerm = 'search this', '--tags' => $tags = 'foo,bar'],
|
||||||
$page,
|
$page,
|
||||||
$searchTerm,
|
$searchTerm,
|
||||||
explode(',', $tags),
|
explode(',', $tags),
|
||||||
new DateRange(),
|
|
||||||
];
|
];
|
||||||
yield [
|
yield [
|
||||||
['--startDate' => $startDate = '2019-01-01'],
|
['--startDate' => $startDate = '2019-01-01'],
|
||||||
1,
|
1,
|
||||||
null,
|
null,
|
||||||
[],
|
[],
|
||||||
new DateRange(Chronos::parse($startDate)),
|
$startDate,
|
||||||
];
|
];
|
||||||
yield [
|
yield [
|
||||||
['--endDate' => $endDate = '2020-05-23'],
|
['--endDate' => $endDate = '2020-05-23'],
|
||||||
1,
|
1,
|
||||||
null,
|
null,
|
||||||
[],
|
[],
|
||||||
new DateRange(null, Chronos::parse($endDate)),
|
null,
|
||||||
|
$endDate,
|
||||||
];
|
];
|
||||||
yield [
|
yield [
|
||||||
['--startDate' => $startDate = '2019-01-01', '--endDate' => $endDate = '2020-05-23'],
|
['--startDate' => $startDate = '2019-01-01', '--endDate' => $endDate = '2020-05-23'],
|
||||||
1,
|
1,
|
||||||
null,
|
null,
|
||||||
[],
|
[],
|
||||||
new DateRange(Chronos::parse($startDate), Chronos::parse($endDate)),
|
$startDate,
|
||||||
|
$endDate,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,8 +175,9 @@ class ListShortUrlsCommandTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function orderByIsProperlyComputed(array $commandArgs, $expectedOrderBy): void
|
public function orderByIsProperlyComputed(array $commandArgs, $expectedOrderBy): void
|
||||||
{
|
{
|
||||||
$listShortUrls = $this->shortUrlService->listShortUrls(1, null, [], $expectedOrderBy, new DateRange())
|
$listShortUrls = $this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData([
|
||||||
->willReturn(new Paginator(new ArrayAdapter()));
|
'orderBy' => $expectedOrderBy,
|
||||||
|
]))->willReturn(new Paginator(new ArrayAdapter()));
|
||||||
|
|
||||||
$this->commandTester->setInputs(['n']);
|
$this->commandTester->setInputs(['n']);
|
||||||
$this->commandTester->execute($commandArgs);
|
$this->commandTester->execute($commandArgs);
|
||||||
|
63
module/Core/src/Model/ShortUrlsOrdering.php
Normal file
63
module/Core/src/Model/ShortUrlsOrdering.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\Model;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||||
|
|
||||||
|
use function is_array;
|
||||||
|
use function is_string;
|
||||||
|
use function key;
|
||||||
|
|
||||||
|
final class ShortUrlsOrdering
|
||||||
|
{
|
||||||
|
public const ORDER_BY = 'orderBy';
|
||||||
|
private const DEFAULT_ORDER_DIRECTION = 'ASC';
|
||||||
|
|
||||||
|
private ?string $orderField = null;
|
||||||
|
private string $orderDirection = self::DEFAULT_ORDER_DIRECTION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
public static function fromRawData(array $query): self
|
||||||
|
{
|
||||||
|
$instance = new self();
|
||||||
|
$instance->validateAndInit($query);
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
private function validateAndInit(array $data): void
|
||||||
|
{
|
||||||
|
/** @var string|array|null $orderBy */
|
||||||
|
$orderBy = $data[self::ORDER_BY] ?? null;
|
||||||
|
if ($orderBy === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isArray = is_array($orderBy);
|
||||||
|
if (! $isArray && $orderBy !== null && ! is_string($orderBy)) {
|
||||||
|
throw ValidationException::fromArray([
|
||||||
|
'orderBy' => '"Order by" must be an array, string or null',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->orderField = $isArray ? key($orderBy) : $orderBy;
|
||||||
|
$this->orderDirection = $isArray ? $orderBy[$this->orderField] : self::DEFAULT_ORDER_DIRECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orderField(): ?string
|
||||||
|
{
|
||||||
|
return $this->orderField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orderDirection(): string
|
||||||
|
{
|
||||||
|
return $this->orderDirection;
|
||||||
|
}
|
||||||
|
}
|
85
module/Core/src/Model/ShortUrlsParams.php
Normal file
85
module/Core/src/Model/ShortUrlsParams.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\Model;
|
||||||
|
|
||||||
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
|
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||||
|
use Shlinkio\Shlink\Core\Validation\ShortUrlsParamsInputFilter;
|
||||||
|
|
||||||
|
use function Shlinkio\Shlink\Core\parseDateField;
|
||||||
|
|
||||||
|
final class ShortUrlsParams
|
||||||
|
{
|
||||||
|
private int $page;
|
||||||
|
private ?string $searchTerm;
|
||||||
|
private array $tags;
|
||||||
|
private ShortUrlsOrdering $orderBy;
|
||||||
|
private ?DateRange $dateRange;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function emptyInstance(): self
|
||||||
|
{
|
||||||
|
return self::fromRawData([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
public static function fromRawData(array $query): self
|
||||||
|
{
|
||||||
|
$instance = new self();
|
||||||
|
$instance->validateAndInit($query);
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ValidationException
|
||||||
|
*/
|
||||||
|
private function validateAndInit(array $query): void
|
||||||
|
{
|
||||||
|
$inputFilter = new ShortUrlsParamsInputFilter($query);
|
||||||
|
if (! $inputFilter->isValid()) {
|
||||||
|
throw ValidationException::fromInputFilter($inputFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->page = (int) ($inputFilter->getValue(ShortUrlsParamsInputFilter::PAGE) ?? 1);
|
||||||
|
$this->searchTerm = $inputFilter->getValue(ShortUrlsParamsInputFilter::SEARCH_TERM);
|
||||||
|
$this->tags = (array) $inputFilter->getValue(ShortUrlsParamsInputFilter::TAGS);
|
||||||
|
$this->dateRange = new DateRange(
|
||||||
|
parseDateField($inputFilter->getValue(ShortUrlsParamsInputFilter::START_DATE)),
|
||||||
|
parseDateField($inputFilter->getValue(ShortUrlsParamsInputFilter::END_DATE)),
|
||||||
|
);
|
||||||
|
$this->orderBy = ShortUrlsOrdering::fromRawData($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function page(): int
|
||||||
|
{
|
||||||
|
return $this->page;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function searchTerm(): ?string
|
||||||
|
{
|
||||||
|
return $this->searchTerm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tags(): array
|
||||||
|
{
|
||||||
|
return $this->tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orderBy(): ShortUrlsOrdering
|
||||||
|
{
|
||||||
|
return $this->orderBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dateRange(): ?DateRange
|
||||||
|
{
|
||||||
|
return $this->dateRange;
|
||||||
|
}
|
||||||
|
}
|
@ -5,38 +5,20 @@ declare(strict_types=1);
|
|||||||
namespace Shlinkio\Shlink\Core\Paginator\Adapter;
|
namespace Shlinkio\Shlink\Core\Paginator\Adapter;
|
||||||
|
|
||||||
use Laminas\Paginator\Adapter\AdapterInterface;
|
use Laminas\Paginator\Adapter\AdapterInterface;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||||
|
|
||||||
use function strip_tags;
|
|
||||||
use function trim;
|
|
||||||
|
|
||||||
class ShortUrlRepositoryAdapter implements AdapterInterface
|
class ShortUrlRepositoryAdapter implements AdapterInterface
|
||||||
{
|
{
|
||||||
public const ITEMS_PER_PAGE = 10;
|
public const ITEMS_PER_PAGE = 10;
|
||||||
|
|
||||||
private ShortUrlRepositoryInterface $repository;
|
private ShortUrlRepositoryInterface $repository;
|
||||||
private ?string $searchTerm;
|
private ShortUrlsParams $params;
|
||||||
/** @var null|array|string */
|
|
||||||
private $orderBy;
|
|
||||||
private array $tags;
|
|
||||||
private ?DateRange $dateRange;
|
|
||||||
|
|
||||||
/**
|
public function __construct(ShortUrlRepositoryInterface $repository, ShortUrlsParams $params)
|
||||||
* @param string|array|null $orderBy
|
{
|
||||||
*/
|
|
||||||
public function __construct(
|
|
||||||
ShortUrlRepositoryInterface $repository,
|
|
||||||
?string $searchTerm = null,
|
|
||||||
array $tags = [],
|
|
||||||
$orderBy = null,
|
|
||||||
?DateRange $dateRange = null
|
|
||||||
) {
|
|
||||||
$this->repository = $repository;
|
$this->repository = $repository;
|
||||||
$this->searchTerm = $searchTerm !== null ? trim(strip_tags($searchTerm)) : null;
|
$this->params = $params;
|
||||||
$this->orderBy = $orderBy;
|
|
||||||
$this->tags = $tags;
|
|
||||||
$this->dateRange = $dateRange;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -50,10 +32,10 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
|
|||||||
return $this->repository->findList(
|
return $this->repository->findList(
|
||||||
$itemCountPerPage,
|
$itemCountPerPage,
|
||||||
$offset,
|
$offset,
|
||||||
$this->searchTerm,
|
$this->params->searchTerm(),
|
||||||
$this->tags,
|
$this->params->tags(),
|
||||||
$this->orderBy,
|
$this->params->orderBy(),
|
||||||
$this->dateRange,
|
$this->params->dateRange(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,6 +50,10 @@ class ShortUrlRepositoryAdapter implements AdapterInterface
|
|||||||
*/
|
*/
|
||||||
public function count(): int
|
public function count(): int
|
||||||
{
|
{
|
||||||
return $this->repository->countList($this->searchTerm, $this->tags, $this->dateRange);
|
return $this->repository->countList(
|
||||||
|
$this->params->searchTerm(),
|
||||||
|
$this->params->tags(),
|
||||||
|
$this->params->dateRange(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,18 +8,16 @@ use Doctrine\ORM\EntityRepository;
|
|||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
|
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
|
||||||
|
|
||||||
use function array_column;
|
use function array_column;
|
||||||
use function array_key_exists;
|
use function array_key_exists;
|
||||||
use function Functional\contains;
|
use function Functional\contains;
|
||||||
use function is_array;
|
|
||||||
use function key;
|
|
||||||
|
|
||||||
class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface
|
class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param string[] $tags
|
* @param string[] $tags
|
||||||
* @param string|array|null $orderBy
|
|
||||||
* @return ShortUrl[]
|
* @return ShortUrl[]
|
||||||
*/
|
*/
|
||||||
public function findList(
|
public function findList(
|
||||||
@ -27,7 +25,7 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
|||||||
?int $offset = null,
|
?int $offset = null,
|
||||||
?string $searchTerm = null,
|
?string $searchTerm = null,
|
||||||
array $tags = [],
|
array $tags = [],
|
||||||
$orderBy = null,
|
?ShortUrlsOrdering $orderBy = null,
|
||||||
?DateRange $dateRange = null
|
?DateRange $dateRange = null
|
||||||
): array {
|
): array {
|
||||||
$qb = $this->createListQueryBuilder($searchTerm, $tags, $dateRange);
|
$qb = $this->createListQueryBuilder($searchTerm, $tags, $dateRange);
|
||||||
@ -51,14 +49,10 @@ class ShortUrlRepository extends EntityRepository implements ShortUrlRepositoryI
|
|||||||
return $qb->getQuery()->getResult();
|
return $qb->getQuery()->getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private function processOrderByForList(QueryBuilder $qb, ShortUrlsOrdering $orderBy): array
|
||||||
* @param string|array|null $orderBy
|
|
||||||
*/
|
|
||||||
private function processOrderByForList(QueryBuilder $qb, $orderBy): array
|
|
||||||
{
|
{
|
||||||
$isArray = is_array($orderBy);
|
$fieldName = $orderBy->orderField();
|
||||||
$fieldName = $isArray ? key($orderBy) : $orderBy;
|
$order = $orderBy->orderDirection();
|
||||||
$order = $isArray ? $orderBy[$fieldName] : 'ASC';
|
|
||||||
|
|
||||||
if (contains(['visits', 'visitsCount', 'visitCount'], $fieldName)) {
|
if (contains(['visits', 'visitsCount', 'visitCount'], $fieldName)) {
|
||||||
$qb->addSelect('COUNT(DISTINCT v) AS totalVisits')
|
$qb->addSelect('COUNT(DISTINCT v) AS totalVisits')
|
||||||
|
@ -7,18 +7,16 @@ namespace Shlinkio\Shlink\Core\Repository;
|
|||||||
use Doctrine\Persistence\ObjectRepository;
|
use Doctrine\Persistence\ObjectRepository;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Common\Util\DateRange;
|
||||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
|
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
|
||||||
|
|
||||||
interface ShortUrlRepositoryInterface extends ObjectRepository
|
interface ShortUrlRepositoryInterface extends ObjectRepository
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @param string|array|null $orderBy
|
|
||||||
*/
|
|
||||||
public function findList(
|
public function findList(
|
||||||
?int $limit = null,
|
?int $limit = null,
|
||||||
?int $offset = null,
|
?int $offset = null,
|
||||||
?string $searchTerm = null,
|
?string $searchTerm = null,
|
||||||
array $tags = [],
|
array $tags = [],
|
||||||
$orderBy = null,
|
?ShortUrlsOrdering $orderBy = null,
|
||||||
?DateRange $dateRange = null
|
?DateRange $dateRange = null
|
||||||
): array;
|
): array;
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@ namespace Shlinkio\Shlink\Core\Service;
|
|||||||
|
|
||||||
use Doctrine\ORM;
|
use Doctrine\ORM;
|
||||||
use Laminas\Paginator\Paginator;
|
use Laminas\Paginator\Paginator;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
|
||||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||||
|
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||||
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
|
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
|
||||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrl\FindShortCodeTrait;
|
use Shlinkio\Shlink\Core\Service\ShortUrl\FindShortCodeTrait;
|
||||||
@ -28,23 +28,15 @@ class ShortUrlService implements ShortUrlServiceInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $tags
|
|
||||||
* @param array|string|null $orderBy
|
|
||||||
*
|
|
||||||
* @return ShortUrl[]|Paginator
|
* @return ShortUrl[]|Paginator
|
||||||
*/
|
*/
|
||||||
public function listShortUrls(
|
public function listShortUrls(ShortUrlsParams $params): Paginator
|
||||||
int $page = 1,
|
{
|
||||||
?string $searchQuery = null,
|
|
||||||
array $tags = [],
|
|
||||||
$orderBy = null,
|
|
||||||
?DateRange $dateRange = null
|
|
||||||
) {
|
|
||||||
/** @var ShortUrlRepository $repo */
|
/** @var ShortUrlRepository $repo */
|
||||||
$repo = $this->em->getRepository(ShortUrl::class);
|
$repo = $this->em->getRepository(ShortUrl::class);
|
||||||
$paginator = new Paginator(new ShortUrlRepositoryAdapter($repo, $searchQuery, $tags, $orderBy, $dateRange));
|
$paginator = new Paginator(new ShortUrlRepositoryAdapter($repo, $params));
|
||||||
$paginator->setItemCountPerPage(ShortUrlRepositoryAdapter::ITEMS_PER_PAGE)
|
$paginator->setItemCountPerPage(ShortUrlRepositoryAdapter::ITEMS_PER_PAGE)
|
||||||
->setCurrentPageNumber($page);
|
->setCurrentPageNumber($params->page());
|
||||||
|
|
||||||
return $paginator;
|
return $paginator;
|
||||||
}
|
}
|
||||||
|
@ -5,26 +5,17 @@ declare(strict_types=1);
|
|||||||
namespace Shlinkio\Shlink\Core\Service;
|
namespace Shlinkio\Shlink\Core\Service;
|
||||||
|
|
||||||
use Laminas\Paginator\Paginator;
|
use Laminas\Paginator\Paginator;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
|
||||||
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
||||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||||
|
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||||
|
|
||||||
interface ShortUrlServiceInterface
|
interface ShortUrlServiceInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @param string[] $tags
|
|
||||||
* @param array|string|null $orderBy
|
|
||||||
*
|
|
||||||
* @return ShortUrl[]|Paginator
|
* @return ShortUrl[]|Paginator
|
||||||
*/
|
*/
|
||||||
public function listShortUrls(
|
public function listShortUrls(ShortUrlsParams $params): Paginator;
|
||||||
int $page = 1,
|
|
||||||
?string $searchQuery = null,
|
|
||||||
array $tags = [],
|
|
||||||
$orderBy = null,
|
|
||||||
?DateRange $dateRange = null
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string[] $tags
|
* @param string[] $tags
|
||||||
|
55
module/Core/src/Validation/ShortUrlsParamsInputFilter.php
Normal file
55
module/Core/src/Validation/ShortUrlsParamsInputFilter.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Shlinkio\Shlink\Core\Validation;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Laminas\Filter;
|
||||||
|
use Laminas\InputFilter\ArrayInput;
|
||||||
|
use Laminas\InputFilter\InputFilter;
|
||||||
|
use Laminas\Validator;
|
||||||
|
use Shlinkio\Shlink\Common\Validation;
|
||||||
|
|
||||||
|
class ShortUrlsParamsInputFilter extends InputFilter
|
||||||
|
{
|
||||||
|
use Validation\InputFactoryTrait;
|
||||||
|
|
||||||
|
public const PAGE = 'page';
|
||||||
|
public const SEARCH_TERM = 'searchTerm';
|
||||||
|
public const TAGS = 'tags';
|
||||||
|
public const START_DATE = 'startDate';
|
||||||
|
public const END_DATE = 'endDate';
|
||||||
|
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
$this->initialize();
|
||||||
|
$this->setData($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initialize(): void
|
||||||
|
{
|
||||||
|
$startDate = $this->createInput(self::START_DATE, false);
|
||||||
|
$startDate->getValidatorChain()->attach(new Validator\Date(['format' => DateTime::ATOM]));
|
||||||
|
$this->add($startDate);
|
||||||
|
|
||||||
|
$endDate = $this->createInput(self::END_DATE, false);
|
||||||
|
$endDate->getValidatorChain()->attach(new Validator\Date(['format' => DateTime::ATOM]));
|
||||||
|
$this->add($endDate);
|
||||||
|
|
||||||
|
$this->add($this->createInput(self::SEARCH_TERM, false));
|
||||||
|
|
||||||
|
$page = $this->createInput(self::PAGE, false);
|
||||||
|
$page->getValidatorChain()->attach(new Validator\Digits())
|
||||||
|
->attach(new Validator\GreaterThan(['min' => 1, 'inclusive' => true]));
|
||||||
|
$this->add($page);
|
||||||
|
|
||||||
|
$tags = new ArrayInput(self::TAGS);
|
||||||
|
$tags->setRequired(false)
|
||||||
|
->getFilterChain()->attach(new Filter\StripTags())
|
||||||
|
->attach(new Filter\StringTrim())
|
||||||
|
->attach(new Filter\StringToLower())
|
||||||
|
->attach(new Validation\SluggerFilter());
|
||||||
|
$this->add($tags);
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
|||||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||||
use Shlinkio\Shlink\Core\Entity\Visit;
|
use Shlinkio\Shlink\Core\Entity\Visit;
|
||||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||||
|
use Shlinkio\Shlink\Core\Model\ShortUrlsOrdering;
|
||||||
use Shlinkio\Shlink\Core\Model\Visitor;
|
use Shlinkio\Shlink\Core\Model\Visitor;
|
||||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||||
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
|
use Shlinkio\Shlink\TestUtils\DbTest\DatabaseTestCase;
|
||||||
@ -122,7 +123,9 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||||||
|
|
||||||
$this->assertCount(1, $this->repo->findList(2, 2));
|
$this->assertCount(1, $this->repo->findList(2, 2));
|
||||||
|
|
||||||
$result = $this->repo->findList(null, null, null, [], ['visits' => 'DESC']);
|
$result = $this->repo->findList(null, null, null, [], ShortUrlsOrdering::fromRawData([
|
||||||
|
'orderBy' => ['visits' => 'DESC'],
|
||||||
|
]));
|
||||||
$this->assertCount(3, $result);
|
$this->assertCount(3, $result);
|
||||||
$this->assertSame($bar, $result[0]);
|
$this->assertSame($bar, $result[0]);
|
||||||
|
|
||||||
@ -148,7 +151,9 @@ class ShortUrlRepositoryTest extends DatabaseTestCase
|
|||||||
|
|
||||||
$this->getEntityManager()->flush();
|
$this->getEntityManager()->flush();
|
||||||
|
|
||||||
$result = $this->repo->findList(null, null, null, [], ['longUrl' => 'ASC']);
|
$result = $this->repo->findList(null, null, null, [], ShortUrlsOrdering::fromRawData([
|
||||||
|
'orderBy' => ['longUrl' => 'ASC'],
|
||||||
|
]));
|
||||||
|
|
||||||
$this->assertCount(count($urls), $result);
|
$this->assertCount(count($urls), $result);
|
||||||
$this->assertEquals('a', $result[0]->getLongUrl());
|
$this->assertEquals('a', $result[0]->getLongUrl());
|
||||||
|
@ -7,7 +7,7 @@ namespace ShlinkioTest\Shlink\Core\Paginator\Adapter;
|
|||||||
use Cake\Chronos\Chronos;
|
use Cake\Chronos\Chronos;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||||
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
|
use Shlinkio\Shlink\Core\Paginator\Adapter\ShortUrlRepositoryAdapter;
|
||||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
|
||||||
|
|
||||||
@ -21,17 +21,26 @@ class ShortUrlRepositoryAdapterTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string|array|null $orderBy
|
|
||||||
* @test
|
* @test
|
||||||
* @dataProvider provideFilteringArgs
|
* @dataProvider provideFilteringArgs
|
||||||
*/
|
*/
|
||||||
public function getItemsFallsBackToFindList(
|
public function getItemsFallsBackToFindList(
|
||||||
?string $searchTerm = null,
|
?string $searchTerm = null,
|
||||||
array $tags = [],
|
array $tags = [],
|
||||||
?DateRange $dateRange = null,
|
?string $startDate = null,
|
||||||
$orderBy = null
|
?string $endDate = null,
|
||||||
|
?string $orderBy = null
|
||||||
): void {
|
): void {
|
||||||
$adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $searchTerm, $tags, $orderBy, $dateRange);
|
$params = ShortUrlsParams::fromRawData([
|
||||||
|
'searchTerm' => $searchTerm,
|
||||||
|
'tags' => $tags,
|
||||||
|
'startDate' => $startDate,
|
||||||
|
'endDate' => $endDate,
|
||||||
|
'orderBy' => $orderBy,
|
||||||
|
]);
|
||||||
|
$adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $params);
|
||||||
|
$orderBy = $params->orderBy();
|
||||||
|
$dateRange = $params->dateRange();
|
||||||
|
|
||||||
$this->repo->findList(10, 5, $searchTerm, $tags, $orderBy, $dateRange)->shouldBeCalledOnce();
|
$this->repo->findList(10, 5, $searchTerm, $tags, $orderBy, $dateRange)->shouldBeCalledOnce();
|
||||||
$adapter->getItems(5, 10);
|
$adapter->getItems(5, 10);
|
||||||
@ -44,9 +53,17 @@ class ShortUrlRepositoryAdapterTest extends TestCase
|
|||||||
public function countFallsBackToCountList(
|
public function countFallsBackToCountList(
|
||||||
?string $searchTerm = null,
|
?string $searchTerm = null,
|
||||||
array $tags = [],
|
array $tags = [],
|
||||||
?DateRange $dateRange = null
|
?string $startDate = null,
|
||||||
|
?string $endDate = null
|
||||||
): void {
|
): void {
|
||||||
$adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $searchTerm, $tags, null, $dateRange);
|
$params = ShortUrlsParams::fromRawData([
|
||||||
|
'searchTerm' => $searchTerm,
|
||||||
|
'tags' => $tags,
|
||||||
|
'startDate' => $startDate,
|
||||||
|
'endDate' => $endDate,
|
||||||
|
]);
|
||||||
|
$adapter = new ShortUrlRepositoryAdapter($this->repo->reveal(), $params);
|
||||||
|
$dateRange = $params->dateRange();
|
||||||
|
|
||||||
$this->repo->countList($searchTerm, $tags, $dateRange)->shouldBeCalledOnce();
|
$this->repo->countList($searchTerm, $tags, $dateRange)->shouldBeCalledOnce();
|
||||||
$adapter->count();
|
$adapter->count();
|
||||||
@ -58,12 +75,12 @@ class ShortUrlRepositoryAdapterTest extends TestCase
|
|||||||
yield ['search'];
|
yield ['search'];
|
||||||
yield ['search', []];
|
yield ['search', []];
|
||||||
yield ['search', ['foo', 'bar']];
|
yield ['search', ['foo', 'bar']];
|
||||||
yield ['search', ['foo', 'bar'], null, 'order'];
|
yield ['search', ['foo', 'bar'], null, null, 'order'];
|
||||||
yield ['search', ['foo', 'bar'], new DateRange(), 'order'];
|
yield ['search', ['foo', 'bar'], Chronos::now()->toAtomString(), null, 'order'];
|
||||||
yield ['search', ['foo', 'bar'], new DateRange(Chronos::now()), 'order'];
|
yield ['search', ['foo', 'bar'], null, Chronos::now()->toAtomString(), 'order'];
|
||||||
yield ['search', ['foo', 'bar'], new DateRange(null, Chronos::now()), 'order'];
|
yield ['search', ['foo', 'bar'], Chronos::now()->toAtomString(), Chronos::now()->toAtomString(), 'order'];
|
||||||
yield ['search', ['foo', 'bar'], new DateRange(Chronos::now(), Chronos::now()), 'order'];
|
yield [null, ['foo', 'bar'], Chronos::now()->toAtomString(), null, 'order'];
|
||||||
yield ['search', ['foo', 'bar'], new DateRange(Chronos::now())];
|
yield [null, ['foo', 'bar'], Chronos::now()->toAtomString()];
|
||||||
yield [null, ['foo', 'bar'], new DateRange(Chronos::now(), Chronos::now())];
|
yield [null, ['foo', 'bar'], Chronos::now()->toAtomString(), Chronos::now()->toAtomString()];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ use Shlinkio\Shlink\Core\Entity\ShortUrl;
|
|||||||
use Shlinkio\Shlink\Core\Entity\Tag;
|
use Shlinkio\Shlink\Core\Entity\Tag;
|
||||||
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException;
|
||||||
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
use Shlinkio\Shlink\Core\Model\ShortUrlMeta;
|
||||||
|
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||||
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
use Shlinkio\Shlink\Core\Repository\ShortUrlRepository;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ class ShortUrlServiceTest extends TestCase
|
|||||||
$repo->countList(Argument::cetera())->willReturn(count($list))->shouldBeCalledOnce();
|
$repo->countList(Argument::cetera())->willReturn(count($list))->shouldBeCalledOnce();
|
||||||
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
$this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||||
|
|
||||||
$list = $this->service->listShortUrls();
|
$list = $this->service->listShortUrls(ShortUrlsParams::emptyInstance());
|
||||||
$this->assertEquals(4, $list->getCurrentItemCount());
|
$this->assertEquals(4, $list->getCurrentItemCount());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,13 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
|
namespace Shlinkio\Shlink\Rest\Action\ShortUrl;
|
||||||
|
|
||||||
use Cake\Chronos\Chronos;
|
|
||||||
use Laminas\Diactoros\Response\JsonResponse;
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
use Shlinkio\Shlink\Common\Paginator\Util\PaginatorUtilsTrait;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
use Shlinkio\Shlink\Core\Service\ShortUrlServiceInterface;
|
||||||
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
|
use Shlinkio\Shlink\Core\Transformer\ShortUrlDataTransformer;
|
||||||
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
|
||||||
@ -37,29 +36,9 @@ class ListShortUrlsAction extends AbstractRestAction
|
|||||||
|
|
||||||
public function handle(Request $request): Response
|
public function handle(Request $request): Response
|
||||||
{
|
{
|
||||||
$params = $this->queryToListParams($request->getQueryParams());
|
$shortUrls = $this->shortUrlService->listShortUrls(ShortUrlsParams::fromRawData($request->getQueryParams()));
|
||||||
$shortUrls = $this->shortUrlService->listShortUrls(...$params);
|
|
||||||
return new JsonResponse(['shortUrls' => $this->serializePaginator($shortUrls, new ShortUrlDataTransformer(
|
return new JsonResponse(['shortUrls' => $this->serializePaginator($shortUrls, new ShortUrlDataTransformer(
|
||||||
$this->domainConfig,
|
$this->domainConfig,
|
||||||
))]);
|
))]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function queryToListParams(array $query): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
(int) ($query['page'] ?? 1),
|
|
||||||
$query['searchTerm'] ?? null,
|
|
||||||
$query['tags'] ?? [],
|
|
||||||
$query['orderBy'] ?? null,
|
|
||||||
$this->determineDateRangeFromQuery($query),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
private function determineDateRangeFromQuery(array $query): DateRange
|
|
||||||
{
|
|
||||||
return new DateRange(
|
|
||||||
isset($query['startDate']) ? Chronos::parse($query['startDate']) : null,
|
|
||||||
isset($query['endDate']) ? Chronos::parse($query['endDate']) : null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ use Laminas\Paginator\Paginator;
|
|||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Prophecy\Prophecy\ObjectProphecy;
|
use Prophecy\Prophecy\ObjectProphecy;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Shlinkio\Shlink\Common\Util\DateRange;
|
use Shlinkio\Shlink\Core\Model\ShortUrlsParams;
|
||||||
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
use Shlinkio\Shlink\Core\Service\ShortUrlService;
|
||||||
use Shlinkio\Shlink\Rest\Action\ShortUrl\ListShortUrlsAction;
|
use Shlinkio\Shlink\Rest\Action\ShortUrl\ListShortUrlsAction;
|
||||||
|
|
||||||
@ -43,15 +43,17 @@ class ListShortUrlsActionTest extends TestCase
|
|||||||
?string $expectedSearchTerm,
|
?string $expectedSearchTerm,
|
||||||
array $expectedTags,
|
array $expectedTags,
|
||||||
?string $expectedOrderBy,
|
?string $expectedOrderBy,
|
||||||
DateRange $expectedDateRange
|
?string $startDate = null,
|
||||||
|
?string $endDate = null
|
||||||
): void {
|
): void {
|
||||||
$listShortUrls = $this->service->listShortUrls(
|
$listShortUrls = $this->service->listShortUrls(ShortUrlsParams::fromRawData([
|
||||||
$expectedPage,
|
'page' => $expectedPage,
|
||||||
$expectedSearchTerm,
|
'searchTerm' => $expectedSearchTerm,
|
||||||
$expectedTags,
|
'tags' => $expectedTags,
|
||||||
$expectedOrderBy,
|
'orderBy' => $expectedOrderBy,
|
||||||
$expectedDateRange,
|
'startDate' => $startDate,
|
||||||
)->willReturn(new Paginator(new ArrayAdapter()));
|
'endDate' => $endDate,
|
||||||
|
]))->willReturn(new Paginator(new ArrayAdapter()));
|
||||||
|
|
||||||
/** @var JsonResponse $response */
|
/** @var JsonResponse $response */
|
||||||
$response = $this->action->handle((new ServerRequest())->withQueryParams($query));
|
$response = $this->action->handle((new ServerRequest())->withQueryParams($query));
|
||||||
@ -66,25 +68,25 @@ class ListShortUrlsActionTest extends TestCase
|
|||||||
|
|
||||||
public function provideFilteringData(): iterable
|
public function provideFilteringData(): iterable
|
||||||
{
|
{
|
||||||
yield [[], 1, null, [], null, new DateRange()];
|
yield [[], 1, null, [], null];
|
||||||
yield [['page' => 10], 10, null, [], null, new DateRange()];
|
yield [['page' => 10], 10, null, [], null];
|
||||||
yield [['page' => null], 1, null, [], null, new DateRange()];
|
yield [['page' => null], 1, null, [], null];
|
||||||
yield [['page' => '8'], 8, null, [], null, new DateRange()];
|
yield [['page' => '8'], 8, null, [], null];
|
||||||
yield [['searchTerm' => $searchTerm = 'foo'], 1, $searchTerm, [], null, new DateRange()];
|
yield [['searchTerm' => $searchTerm = 'foo'], 1, $searchTerm, [], null];
|
||||||
yield [['tags' => $tags = ['foo','bar']], 1, null, $tags, null, new DateRange()];
|
yield [['tags' => $tags = ['foo','bar']], 1, null, $tags, null];
|
||||||
yield [['orderBy' => $orderBy = 'something'], 1, null, [], $orderBy, new DateRange()];
|
yield [['orderBy' => $orderBy = 'something'], 1, null, [], $orderBy];
|
||||||
yield [[
|
yield [[
|
||||||
'page' => '2',
|
'page' => '2',
|
||||||
'orderBy' => $orderBy = 'something',
|
'orderBy' => $orderBy = 'something',
|
||||||
'tags' => $tags = ['one', 'two'],
|
'tags' => $tags = ['one', 'two'],
|
||||||
], 2, null, $tags, $orderBy, new DateRange()];
|
], 2, null, $tags, $orderBy];
|
||||||
yield [
|
yield [
|
||||||
['startDate' => $date = Chronos::now()->toAtomString()],
|
['startDate' => $date = Chronos::now()->toAtomString()],
|
||||||
1,
|
1,
|
||||||
null,
|
null,
|
||||||
[],
|
[],
|
||||||
null,
|
null,
|
||||||
new DateRange(Chronos::parse($date)),
|
$date,
|
||||||
];
|
];
|
||||||
yield [
|
yield [
|
||||||
['endDate' => $date = Chronos::now()->toAtomString()],
|
['endDate' => $date = Chronos::now()->toAtomString()],
|
||||||
@ -92,7 +94,8 @@ class ListShortUrlsActionTest extends TestCase
|
|||||||
null,
|
null,
|
||||||
[],
|
[],
|
||||||
null,
|
null,
|
||||||
new DateRange(null, Chronos::parse($date)),
|
null,
|
||||||
|
$date,
|
||||||
];
|
];
|
||||||
yield [
|
yield [
|
||||||
[
|
[
|
||||||
@ -103,7 +106,8 @@ class ListShortUrlsActionTest extends TestCase
|
|||||||
null,
|
null,
|
||||||
[],
|
[],
|
||||||
null,
|
null,
|
||||||
new DateRange(Chronos::parse($startDate), Chronos::parse($endDate)),
|
$startDate,
|
||||||
|
$endDate,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user