mirror of
https://github.com/shlinkio/shlink.git
synced 2025-03-14 04:00:57 +03:00
Updated to readonly public props on as many models as possible
This commit is contained in:
parent
e79391907a
commit
bca3e62ced
74 changed files with 249 additions and 494 deletions
|
@ -53,7 +53,7 @@ class DomainRedirectsCommand extends Command
|
|||
|
||||
/** @var string[] $availableDomains */
|
||||
$availableDomains = invoke(
|
||||
filter($this->domainService->listDomains(), static fn (DomainItem $item) => ! $item->isDefault()),
|
||||
filter($this->domainService->listDomains(), static fn (DomainItem $item) => ! $item->isDefault),
|
||||
'toString',
|
||||
);
|
||||
if (empty($availableDomains)) {
|
||||
|
|
|
@ -48,12 +48,12 @@ class ListDomainsCommand extends Command
|
|||
$table->render(
|
||||
$showRedirects ? [...$commonFields, '"Not found" redirects'] : $commonFields,
|
||||
map($domains, function (DomainItem $domain) use ($showRedirects) {
|
||||
$commonValues = [$domain->toString(), $domain->isDefault() ? 'Yes' : 'No'];
|
||||
$commonValues = [$domain->toString(), $domain->isDefault ? 'Yes' : 'No'];
|
||||
|
||||
return $showRedirects
|
||||
? [
|
||||
...$commonValues,
|
||||
$this->notFoundRedirectsToString($domain->notFoundRedirectConfig()),
|
||||
$this->notFoundRedirectsToString($domain->notFoundRedirectConfig),
|
||||
]
|
||||
: $commonValues;
|
||||
}),
|
||||
|
|
|
@ -81,6 +81,6 @@ class DeleteShortUrlCommand extends Command
|
|||
private function runDelete(SymfonyStyle $io, ShortUrlIdentifier $identifier, bool $ignoreThreshold): void
|
||||
{
|
||||
$this->deleteShortUrlService->deleteByShortCode($identifier, $ignoreThreshold);
|
||||
$io->success(sprintf('Short URL with short code "%s" successfully deleted.', $identifier->shortCode()));
|
||||
$io->success(sprintf('Short URL with short code "%s" successfully deleted.', $identifier->shortCode));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class ListTagsCommand extends Command
|
|||
|
||||
return map(
|
||||
$tags,
|
||||
static fn (TagInfo $tagInfo) => [$tagInfo->tag(), $tagInfo->shortUrlsCount(), $tagInfo->visitsCount()],
|
||||
static fn (TagInfo $tagInfo) => [$tagInfo->tag, $tagInfo->shortUrlsCount, $tagInfo->visitsCount],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,11 +22,11 @@ abstract class AbstractLockedCommand extends Command
|
|||
final protected function execute(InputInterface $input, OutputInterface $output): ?int
|
||||
{
|
||||
$lockConfig = $this->getLockConfig();
|
||||
$lock = $this->locker->createLock($lockConfig->lockName(), $lockConfig->ttl(), $lockConfig->isBlocking());
|
||||
$lock = $this->locker->createLock($lockConfig->lockName, $lockConfig->ttl, $lockConfig->isBlocking);
|
||||
|
||||
if (! $lock->acquire($lockConfig->isBlocking())) {
|
||||
if (! $lock->acquire($lockConfig->isBlocking)) {
|
||||
$output->writeln(
|
||||
sprintf('<comment>Command "%s" is already in progress. Skipping.</comment>', $lockConfig->lockName()),
|
||||
sprintf('<comment>Command "%s" is already in progress. Skipping.</comment>', $lockConfig->lockName),
|
||||
);
|
||||
return ExitCodes::EXIT_WARNING;
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ final class LockedCommandConfig
|
|||
public const DEFAULT_TTL = 600.0; // 10 minutes
|
||||
|
||||
private function __construct(
|
||||
private string $lockName,
|
||||
private bool $isBlocking,
|
||||
private float $ttl = self::DEFAULT_TTL,
|
||||
public readonly string $lockName,
|
||||
public readonly bool $isBlocking,
|
||||
public readonly float $ttl = self::DEFAULT_TTL,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -24,19 +24,4 @@ final class LockedCommandConfig
|
|||
{
|
||||
return new self($lockName, false);
|
||||
}
|
||||
|
||||
public function lockName(): string
|
||||
{
|
||||
return $this->lockName;
|
||||
}
|
||||
|
||||
public function isBlocking(): bool
|
||||
{
|
||||
return $this->isBlocking;
|
||||
}
|
||||
|
||||
public function ttl(): float
|
||||
{
|
||||
return $this->ttl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ final class ShlinkTable
|
|||
private const DEFAULT_STYLE_NAME = 'default';
|
||||
private const TABLE_TITLE_STYLE = '<options=bold> %s </>';
|
||||
|
||||
private function __construct(private Table $baseTable, private bool $withRowSeparators)
|
||||
private function __construct(private readonly Table $baseTable, private readonly bool $withRowSeparators)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,11 @@ class DeleteShortUrlCommandTest extends TestCase
|
|||
public function successMessageIsPrintedIfUrlIsProperlyDeleted(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$deleteByShortCode = $this->service->deleteByShortCode(new ShortUrlIdentifier($shortCode), false)->will(
|
||||
function (): void {
|
||||
},
|
||||
);
|
||||
$deleteByShortCode = $this->service->deleteByShortCode(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
false,
|
||||
)->will(function (): void {
|
||||
});
|
||||
|
||||
$this->commandTester->execute(['shortCode' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
@ -55,7 +56,7 @@ class DeleteShortUrlCommandTest extends TestCase
|
|||
public function invalidShortCodePrintsMessage(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$identifier = new ShortUrlIdentifier($shortCode);
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode);
|
||||
$deleteByShortCode = $this->service->deleteByShortCode($identifier, false)->willThrow(
|
||||
Exception\ShortUrlNotFoundException::fromNotFound($identifier),
|
||||
);
|
||||
|
@ -77,7 +78,7 @@ class DeleteShortUrlCommandTest extends TestCase
|
|||
string $expectedMessage,
|
||||
): void {
|
||||
$shortCode = 'abc123';
|
||||
$identifier = new ShortUrlIdentifier($shortCode);
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode);
|
||||
$deleteByShortCode = $this->service->deleteByShortCode($identifier, Argument::type('bool'))->will(
|
||||
function (array $args) use ($shortCode): void {
|
||||
$ignoreThreshold = array_pop($args);
|
||||
|
@ -114,12 +115,13 @@ class DeleteShortUrlCommandTest extends TestCase
|
|||
public function deleteIsNotRetriedWhenThresholdIsReachedAndQuestionIsDeclined(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$deleteByShortCode = $this->service->deleteByShortCode(new ShortUrlIdentifier($shortCode), false)->willThrow(
|
||||
Exception\DeleteShortUrlException::fromVisitsThreshold(
|
||||
10,
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
),
|
||||
);
|
||||
$deleteByShortCode = $this->service->deleteByShortCode(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
false,
|
||||
)->willThrow(Exception\DeleteShortUrlException::fromVisitsThreshold(
|
||||
10,
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
));
|
||||
$this->commandTester->setInputs(['no']);
|
||||
|
||||
$this->commandTester->execute(['shortCode' => $shortCode]);
|
||||
|
|
|
@ -44,7 +44,7 @@ class GetVisitsCommandTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(DateRange::emptyInstance()),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([])))
|
||||
|
@ -60,7 +60,7 @@ class GetVisitsCommandTest extends TestCase
|
|||
$startDate = '2016-01-01';
|
||||
$endDate = '2016-02-01';
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(DateRange::withStartAndEndDate(Chronos::parse($startDate), Chronos::parse($endDate))),
|
||||
)
|
||||
->willReturn(new Paginator(new ArrayAdapter([])))
|
||||
|
@ -79,7 +79,7 @@ class GetVisitsCommandTest extends TestCase
|
|||
$shortCode = 'abc123';
|
||||
$startDate = 'foo';
|
||||
$info = $this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
new VisitsParams(DateRange::emptyInstance()),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([])));
|
||||
|
||||
|
@ -100,7 +100,10 @@ class GetVisitsCommandTest extends TestCase
|
|||
public function outputIsProperlyGenerated(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsHelper->visitsForShortUrl(new ShortUrlIdentifier($shortCode), Argument::any())->willReturn(
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
Argument::any(),
|
||||
)->willReturn(
|
||||
new Paginator(new ArrayAdapter([
|
||||
Visit::forValidShortUrl(ShortUrl::createEmpty(), new Visitor('bar', 'foo', '', ''))->locate(
|
||||
VisitLocation::fromGeolocation(new Location('', 'Spain', '', '', 0, 0, '')),
|
||||
|
|
|
@ -37,8 +37,9 @@ class ResolveUrlCommandTest extends TestCase
|
|||
$shortCode = 'abc123';
|
||||
$expectedUrl = 'http://domain.com/foo/bar';
|
||||
$shortUrl = ShortUrl::withLongUrl($expectedUrl);
|
||||
$this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode))->willReturn($shortUrl)
|
||||
->shouldBeCalledOnce();
|
||||
$this->urlResolver->resolveShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode))->willReturn(
|
||||
$shortUrl,
|
||||
)->shouldBeCalledOnce();
|
||||
|
||||
$this->commandTester->execute(['shortCode' => $shortCode]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
@ -48,8 +49,8 @@ class ResolveUrlCommandTest extends TestCase
|
|||
/** @test */
|
||||
public function incorrectShortCodeOutputsErrorMessage(): void
|
||||
{
|
||||
$identifier = new ShortUrlIdentifier('abc123');
|
||||
$shortCode = $identifier->shortCode();
|
||||
$identifier = ShortUrlIdentifier::fromShortCodeAndDomain('abc123');
|
||||
$shortCode = $identifier->shortCode;
|
||||
|
||||
$this->urlResolver->resolveShortUrl($identifier)
|
||||
->willThrow(ShortUrlNotFoundException::fromNotFound($identifier))
|
||||
|
|
|
@ -29,11 +29,11 @@ final class QrCodeParams
|
|||
private const SUPPORTED_FORMATS = ['png', 'svg'];
|
||||
|
||||
private function __construct(
|
||||
private int $size,
|
||||
private int $margin,
|
||||
private WriterInterface $writer,
|
||||
private ErrorCorrectionLevelInterface $errorCorrectionLevel,
|
||||
private RoundBlockSizeModeInterface $roundBlockSizeMode,
|
||||
public readonly int $size,
|
||||
public readonly int $margin,
|
||||
public readonly WriterInterface $writer,
|
||||
public readonly ErrorCorrectionLevelInterface $errorCorrectionLevel,
|
||||
public readonly RoundBlockSizeModeInterface $roundBlockSizeMode,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -105,29 +105,4 @@ final class QrCodeParams
|
|||
{
|
||||
return strtolower(trim($param));
|
||||
}
|
||||
|
||||
public function size(): int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
public function margin(): int
|
||||
{
|
||||
return $this->margin;
|
||||
}
|
||||
|
||||
public function writer(): WriterInterface
|
||||
{
|
||||
return $this->writer;
|
||||
}
|
||||
|
||||
public function errorCorrectionLevel(): ErrorCorrectionLevelInterface
|
||||
{
|
||||
return $this->errorCorrectionLevel;
|
||||
}
|
||||
|
||||
public function roundBlockSizeMode(): RoundBlockSizeModeInterface
|
||||
{
|
||||
return $this->roundBlockSizeMode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,11 +42,11 @@ class QrCodeAction implements MiddlewareInterface
|
|||
$params = QrCodeParams::fromRequest($request, $this->defaultOptions);
|
||||
$qrCodeBuilder = Builder::create()
|
||||
->data($this->stringifier->stringify($shortUrl))
|
||||
->size($params->size())
|
||||
->margin($params->margin())
|
||||
->writer($params->writer())
|
||||
->errorCorrectionLevel($params->errorCorrectionLevel())
|
||||
->roundBlockSizeMode($params->roundBlockSizeMode());
|
||||
->size($params->size)
|
||||
->margin($params->margin)
|
||||
->writer($params->writer)
|
||||
->errorCorrectionLevel($params->errorCorrectionLevel)
|
||||
->roundBlockSizeMode($params->roundBlockSizeMode);
|
||||
|
||||
return new QrCodeResponse($qrCodeBuilder->build());
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@ use JsonSerializable;
|
|||
final class NotFoundRedirects implements JsonSerializable
|
||||
{
|
||||
private function __construct(
|
||||
private ?string $baseUrlRedirect,
|
||||
private ?string $regular404Redirect,
|
||||
private ?string $invalidShortUrlRedirect,
|
||||
public readonly ?string $baseUrlRedirect,
|
||||
public readonly ?string $regular404Redirect,
|
||||
public readonly ?string $invalidShortUrlRedirect,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -33,21 +33,6 @@ final class NotFoundRedirects implements JsonSerializable
|
|||
return new self($config->baseUrlRedirect(), $config->regular404Redirect(), $config->invalidShortUrlRedirect());
|
||||
}
|
||||
|
||||
public function baseUrlRedirect(): ?string
|
||||
{
|
||||
return $this->baseUrlRedirect;
|
||||
}
|
||||
|
||||
public function regular404Redirect(): ?string
|
||||
{
|
||||
return $this->regular404Redirect;
|
||||
}
|
||||
|
||||
public function invalidShortUrlRedirect(): ?string
|
||||
{
|
||||
return $this->invalidShortUrlRedirect;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
|
|
|
@ -12,9 +12,9 @@ use Shlinkio\Shlink\Core\Entity\Domain;
|
|||
final class DomainItem implements JsonSerializable
|
||||
{
|
||||
private function __construct(
|
||||
private string $authority,
|
||||
private NotFoundRedirectConfigInterface $notFoundRedirectConfig,
|
||||
private bool $isDefault,
|
||||
private readonly string $authority,
|
||||
public readonly NotFoundRedirectConfigInterface $notFoundRedirectConfig,
|
||||
public readonly bool $isDefault,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -23,9 +23,9 @@ final class DomainItem implements JsonSerializable
|
|||
return new self($domain->getAuthority(), $domain, false);
|
||||
}
|
||||
|
||||
public static function forDefaultDomain(string $authority, NotFoundRedirectConfigInterface $config): self
|
||||
public static function forDefaultDomain(string $defaultDomain, NotFoundRedirectConfigInterface $config): self
|
||||
{
|
||||
return new self($authority, $config, true);
|
||||
return new self($defaultDomain, $config, true);
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
|
@ -41,14 +41,4 @@ final class DomainItem implements JsonSerializable
|
|||
{
|
||||
return $this->authority;
|
||||
}
|
||||
|
||||
public function isDefault(): bool
|
||||
{
|
||||
return $this->isDefault;
|
||||
}
|
||||
|
||||
public function notFoundRedirectConfig(): NotFoundRedirectConfigInterface
|
||||
{
|
||||
return $this->notFoundRedirectConfig;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,8 +66,8 @@ class Domain extends AbstractEntity implements JsonSerializable, NotFoundRedirec
|
|||
|
||||
public function configureNotFoundRedirects(NotFoundRedirects $redirects): void
|
||||
{
|
||||
$this->baseUrlRedirect = $redirects->baseUrlRedirect();
|
||||
$this->regular404Redirect = $redirects->regular404Redirect();
|
||||
$this->invalidShortUrlRedirect = $redirects->invalidShortUrlRedirect();
|
||||
$this->baseUrlRedirect = $redirects->baseUrlRedirect;
|
||||
$this->regular404Redirect = $redirects->regular404Redirect;
|
||||
$this->invalidShortUrlRedirect = $redirects->invalidShortUrlRedirect;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,10 +89,10 @@ class Visit extends AbstractEntity implements JsonSerializable
|
|||
|
||||
private function hydrateFromVisitor(Visitor $visitor, bool $anonymize = true): void
|
||||
{
|
||||
$this->userAgent = $visitor->getUserAgent();
|
||||
$this->referer = $visitor->getReferer();
|
||||
$this->remoteAddr = $this->processAddress($anonymize, $visitor->getRemoteAddress());
|
||||
$this->visitedUrl = $visitor->getVisitedUrl();
|
||||
$this->userAgent = $visitor->userAgent;
|
||||
$this->referer = $visitor->referer;
|
||||
$this->remoteAddr = $this->processAddress($anonymize, $visitor->remoteAddress);
|
||||
$this->visitedUrl = $visitor->visitedUrl;
|
||||
$this->potentialBot = $visitor->isPotentialBot();
|
||||
}
|
||||
|
||||
|
|
|
@ -8,15 +8,10 @@ use JsonSerializable;
|
|||
|
||||
abstract class AbstractVisitEvent implements JsonSerializable
|
||||
{
|
||||
public function __construct(protected string $visitId)
|
||||
public function __construct(public readonly string $visitId)
|
||||
{
|
||||
}
|
||||
|
||||
public function visitId(): string
|
||||
{
|
||||
return $this->visitId;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return ['visitId' => $this->visitId];
|
||||
|
|
|
@ -6,13 +6,8 @@ namespace Shlinkio\Shlink\Core\EventDispatcher\Event;
|
|||
|
||||
final class UrlVisited extends AbstractVisitEvent
|
||||
{
|
||||
public function __construct(string $visitId, private ?string $originalIpAddress = null)
|
||||
public function __construct(string $visitId, public readonly ?string $originalIpAddress = null)
|
||||
{
|
||||
parent::__construct($visitId);
|
||||
}
|
||||
|
||||
public function originalIpAddress(): ?string
|
||||
{
|
||||
return $this->originalIpAddress;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ class LocateVisit
|
|||
|
||||
public function __invoke(UrlVisited $shortUrlVisited): void
|
||||
{
|
||||
$visitId = $shortUrlVisited->visitId();
|
||||
$visitId = $shortUrlVisited->visitId;
|
||||
|
||||
/** @var Visit|null $visit */
|
||||
$visit = $this->em->find(Visit::class, $visitId);
|
||||
|
@ -41,7 +41,7 @@ class LocateVisit
|
|||
return;
|
||||
}
|
||||
|
||||
$this->locateVisit($visitId, $shortUrlVisited->originalIpAddress(), $visit);
|
||||
$this->locateVisit($visitId, $shortUrlVisited->originalIpAddress, $visit);
|
||||
$this->eventDispatcher->dispatch(new VisitLocated($visitId));
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class NotifyVisitToMercure
|
|||
|
||||
public function __invoke(VisitLocated $shortUrlLocated): void
|
||||
{
|
||||
$visitId = $shortUrlLocated->visitId();
|
||||
$visitId = $shortUrlLocated->visitId;
|
||||
|
||||
/** @var Visit|null $visit */
|
||||
$visit = $this->em->find(Visit::class, $visitId);
|
||||
|
|
|
@ -37,7 +37,7 @@ class NotifyVisitToRabbitMq
|
|||
return;
|
||||
}
|
||||
|
||||
$visitId = $shortUrlLocated->visitId();
|
||||
$visitId = $shortUrlLocated->visitId;
|
||||
$visit = $this->em->find(Visit::class, $visitId);
|
||||
|
||||
if ($visit === null) {
|
||||
|
|
|
@ -40,7 +40,7 @@ class NotifyVisitToWebHooks
|
|||
return;
|
||||
}
|
||||
|
||||
$visitId = $shortUrlLocated->visitId();
|
||||
$visitId = $shortUrlLocated->visitId;
|
||||
|
||||
/** @var Visit|null $visit */
|
||||
$visit = $this->em->find(Visit::class, $visitId);
|
||||
|
|
|
@ -20,8 +20,8 @@ class DeleteShortUrlException extends DomainException implements ProblemDetailsE
|
|||
|
||||
public static function fromVisitsThreshold(int $threshold, ShortUrlIdentifier $identifier): self
|
||||
{
|
||||
$shortCode = $identifier->shortCode();
|
||||
$domain = $identifier->domain();
|
||||
$shortCode = $identifier->shortCode;
|
||||
$domain = $identifier->domain;
|
||||
$suffix = $domain === null ? '' : sprintf(' for domain "%s"', $domain);
|
||||
$e = new self(sprintf(
|
||||
'Impossible to delete short URL with short code "%s"%s, since it has more than "%s" visits.',
|
||||
|
|
|
@ -20,8 +20,8 @@ class ShortUrlNotFoundException extends DomainException implements ProblemDetail
|
|||
|
||||
public static function fromNotFound(ShortUrlIdentifier $identifier): self
|
||||
{
|
||||
$shortCode = $identifier->shortCode();
|
||||
$domain = $identifier->domain();
|
||||
$shortCode = $identifier->shortCode;
|
||||
$domain = $identifier->domain;
|
||||
$suffix = $domain === null ? '' : sprintf(' for domain "%s"', $domain);
|
||||
$e = new self(sprintf('No URL found with short code "%s"%s', $shortCode, $suffix));
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ use function sprintf;
|
|||
|
||||
final class ShortUrlImporting
|
||||
{
|
||||
private function __construct(private ShortUrl $shortUrl, private bool $isNew)
|
||||
private function __construct(private readonly ShortUrl $shortUrl, private readonly bool $isNew)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ abstract class AbstractInfinitePaginableListParams
|
|||
{
|
||||
private const FIRST_PAGE = 1;
|
||||
|
||||
private int $page;
|
||||
private int $itemsPerPage;
|
||||
public readonly int $page;
|
||||
public readonly int $itemsPerPage;
|
||||
|
||||
protected function __construct(?int $page, ?int $itemsPerPage)
|
||||
{
|
||||
|
@ -28,14 +28,4 @@ abstract class AbstractInfinitePaginableListParams
|
|||
{
|
||||
return $itemsPerPage === null || $itemsPerPage < 0 ? Paginator::ALL_ITEMS : $itemsPerPage;
|
||||
}
|
||||
|
||||
public function getPage(): int
|
||||
{
|
||||
return $this->page;
|
||||
}
|
||||
|
||||
public function getItemsPerPage(): int
|
||||
{
|
||||
return $this->itemsPerPage;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ final class Ordering
|
|||
{
|
||||
private const DEFAULT_DIR = 'ASC';
|
||||
|
||||
private function __construct(private ?string $field, private string $dir)
|
||||
private function __construct(public readonly ?string $field, public readonly string $direction)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -26,16 +26,6 @@ final class Ordering
|
|||
return self::fromTuple([null, null]);
|
||||
}
|
||||
|
||||
public function orderField(): ?string
|
||||
{
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
public function orderDirection(): string
|
||||
{
|
||||
return $this->dir;
|
||||
}
|
||||
|
||||
public function hasOrderField(): bool
|
||||
{
|
||||
return $this->field !== null;
|
||||
|
|
|
@ -10,7 +10,7 @@ use Symfony\Component\Console\Input\InputInterface;
|
|||
|
||||
final class ShortUrlIdentifier
|
||||
{
|
||||
public function __construct(private string $shortCode, private ?string $domain = null)
|
||||
public function __construct(public readonly string $shortCode, public readonly ?string $domain = null)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -54,14 +54,4 @@ final class ShortUrlIdentifier
|
|||
{
|
||||
return new self($shortCode, $domain);
|
||||
}
|
||||
|
||||
public function shortCode(): string
|
||||
{
|
||||
return $this->shortCode;
|
||||
}
|
||||
|
||||
public function domain(): ?string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,10 @@ final class Visitor
|
|||
public const REMOTE_ADDRESS_MAX_LENGTH = 256;
|
||||
public const VISITED_URL_MAX_LENGTH = 2048;
|
||||
|
||||
private string $userAgent;
|
||||
private string $referer;
|
||||
private string $visitedUrl;
|
||||
private ?string $remoteAddress;
|
||||
public readonly string $userAgent;
|
||||
public readonly string $referer;
|
||||
public readonly string $visitedUrl;
|
||||
public readonly ?string $remoteAddress;
|
||||
private bool $potentialBot;
|
||||
|
||||
public function __construct(string $userAgent, string $referer, ?string $remoteAddress, string $visitedUrl)
|
||||
|
@ -61,26 +61,6 @@ final class Visitor
|
|||
return new self('cf-facebook', '', null, '');
|
||||
}
|
||||
|
||||
public function getUserAgent(): string
|
||||
{
|
||||
return $this->userAgent;
|
||||
}
|
||||
|
||||
public function getReferer(): string
|
||||
{
|
||||
return $this->referer;
|
||||
}
|
||||
|
||||
public function getRemoteAddress(): ?string
|
||||
{
|
||||
return $this->remoteAddress;
|
||||
}
|
||||
|
||||
public function getVisitedUrl(): string
|
||||
{
|
||||
return $this->visitedUrl;
|
||||
}
|
||||
|
||||
public function isPotentialBot(): bool
|
||||
{
|
||||
return $this->potentialBot;
|
||||
|
|
|
@ -10,13 +10,13 @@ use function Shlinkio\Shlink\Core\parseDateRangeFromQuery;
|
|||
|
||||
final class VisitsParams extends AbstractInfinitePaginableListParams
|
||||
{
|
||||
private DateRange $dateRange;
|
||||
public readonly DateRange $dateRange;
|
||||
|
||||
public function __construct(
|
||||
?DateRange $dateRange = null,
|
||||
?int $page = null,
|
||||
?int $itemsPerPage = null,
|
||||
private bool $excludeBots = false,
|
||||
public readonly bool $excludeBots = false,
|
||||
) {
|
||||
parent::__construct($page, $itemsPerPage);
|
||||
$this->dateRange = $dateRange ?? DateRange::emptyInstance();
|
||||
|
@ -31,14 +31,4 @@ final class VisitsParams extends AbstractInfinitePaginableListParams
|
|||
isset($query['excludeBots']),
|
||||
);
|
||||
}
|
||||
|
||||
public function getDateRange(): DateRange
|
||||
{
|
||||
return $this->dateRange;
|
||||
}
|
||||
|
||||
public function excludeBots(): bool
|
||||
{
|
||||
return $this->excludeBots;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
|||
|
||||
private function processOrderByForList(QueryBuilder $qb, Ordering $orderBy): array
|
||||
{
|
||||
$fieldName = $orderBy->orderField();
|
||||
$order = $orderBy->orderDirection();
|
||||
$fieldName = $orderBy->field;
|
||||
$order = $orderBy->direction;
|
||||
|
||||
if ($fieldName === 'visits') {
|
||||
// FIXME This query is inefficient.
|
||||
|
@ -146,8 +146,8 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
|||
$query = $this->getEntityManager()->createQuery($dql);
|
||||
$query->setMaxResults(1)
|
||||
->setParameters([
|
||||
'shortCode' => $identifier->shortCode(),
|
||||
'domain' => $identifier->domain(),
|
||||
'shortCode' => $identifier->shortCode,
|
||||
'domain' => $identifier->domain,
|
||||
]);
|
||||
|
||||
// Since we ordered by domain, we will have first the URL matching provided domain, followed by the one
|
||||
|
@ -198,10 +198,10 @@ class ShortUrlRepository extends EntitySpecificationRepository implements ShortU
|
|||
$qb->from(ShortUrl::class, 's')
|
||||
->where($qb->expr()->isNotNull('s.shortCode'))
|
||||
->andWhere($qb->expr()->eq('s.shortCode', ':slug'))
|
||||
->setParameter('slug', $identifier->shortCode())
|
||||
->setParameter('slug', $identifier->shortCode)
|
||||
->setMaxResults(1);
|
||||
|
||||
$this->whereDomainIs($qb, $identifier->domain());
|
||||
$this->whereDomainIs($qb, $identifier->domain);
|
||||
|
||||
$this->applySpecification($qb, $spec, 's');
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
|||
*/
|
||||
public function findTagsWithInfo(?TagsListFiltering $filtering = null): array
|
||||
{
|
||||
$orderField = $filtering?->orderBy()?->orderField();
|
||||
$orderDir = $filtering?->orderBy()?->orderDirection();
|
||||
$orderField = $filtering?->orderBy?->field;
|
||||
$orderDir = $filtering?->orderBy?->direction;
|
||||
$orderMainQuery = contains(['shortUrlsCount', 'visitsCount'], $orderField);
|
||||
|
||||
$conn = $this->getEntityManager()->getConnection();
|
||||
|
@ -51,16 +51,16 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
|||
|
||||
if (! $orderMainQuery) {
|
||||
$subQb->orderBy('t.name', $orderDir ?? 'ASC')
|
||||
->setMaxResults($filtering?->limit() ?? PHP_INT_MAX)
|
||||
->setFirstResult($filtering?->offset() ?? 0);
|
||||
->setMaxResults($filtering?->limit ?? PHP_INT_MAX)
|
||||
->setFirstResult($filtering?->offset ?? 0);
|
||||
}
|
||||
|
||||
$searchTerm = $filtering?->searchTerm();
|
||||
$searchTerm = $filtering?->searchTerm;
|
||||
if ($searchTerm !== null) {
|
||||
$subQb->andWhere($subQb->expr()->like('t.name', $conn->quote('%' . $searchTerm . '%')));
|
||||
}
|
||||
|
||||
$apiKey = $filtering?->apiKey();
|
||||
$apiKey = $filtering?->apiKey;
|
||||
$this->applySpecification($subQb, new WithInlinedApiKeySpecsEnsuringJoin($apiKey), 't');
|
||||
|
||||
// A native query builder needs to be used here, because DQL and ORM query builders do not support
|
||||
|
@ -97,8 +97,8 @@ class TagRepository extends EntitySpecificationRepository implements TagReposito
|
|||
$orderField === 'shortUrlsCount' ? 'short_urls_count' : 'visits_count',
|
||||
$orderDir ?? 'ASC',
|
||||
)
|
||||
->setMaxResults($filtering?->limit() ?? PHP_INT_MAX)
|
||||
->setFirstResult($filtering?->offset() ?? 0);
|
||||
->setMaxResults($filtering?->limit ?? PHP_INT_MAX)
|
||||
->setFirstResult($filtering?->offset ?? 0);
|
||||
}
|
||||
|
||||
// Add ordering by tag name, as a fallback in case of same amount, or as default ordering
|
||||
|
|
|
@ -86,7 +86,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
public function findVisitsByShortCode(ShortUrlIdentifier $identifier, VisitsListFiltering $filtering): array
|
||||
{
|
||||
$qb = $this->createVisitsByShortCodeQueryBuilder($identifier, $filtering);
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countVisitsByShortCode(ShortUrlIdentifier $identifier, VisitsCountFiltering $filtering): int
|
||||
|
@ -103,7 +103,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
): QueryBuilder {
|
||||
/** @var ShortUrlRepositoryInterface $shortUrlRepo */
|
||||
$shortUrlRepo = $this->getEntityManager()->getRepository(ShortUrl::class);
|
||||
$shortUrlId = $shortUrlRepo->findOne($identifier, $filtering->apiKey()?->spec())?->getId() ?? '-1';
|
||||
$shortUrlId = $shortUrlRepo->findOne($identifier, $filtering->apiKey?->spec())?->getId() ?? '-1';
|
||||
|
||||
// Parameters in this query need to be part of the query itself, as we need to use it as sub-query later
|
||||
// Since they are not provided by the caller, it's reasonably safe
|
||||
|
@ -111,12 +111,12 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
$qb->from(Visit::class, 'v')
|
||||
->where($qb->expr()->eq('v.shortUrl', $shortUrlId));
|
||||
|
||||
if ($filtering->excludeBots()) {
|
||||
if ($filtering->excludeBots) {
|
||||
$qb->andWhere($qb->expr()->eq('v.potentialBot', 'false'));
|
||||
}
|
||||
|
||||
// Apply date range filtering
|
||||
$this->applyDatesInline($qb, $filtering->dateRange());
|
||||
$this->applyDatesInline($qb, $filtering->dateRange);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
@ -124,7 +124,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
public function findVisitsByTag(string $tag, VisitsListFiltering $filtering): array
|
||||
{
|
||||
$qb = $this->createVisitsByTagQueryBuilder($tag, $filtering);
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countVisitsByTag(string $tag, VisitsCountFiltering $filtering): int
|
||||
|
@ -144,12 +144,12 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
->join('s.tags', 't')
|
||||
->where($qb->expr()->eq('t.name', $this->getEntityManager()->getConnection()->quote($tag)));
|
||||
|
||||
if ($filtering->excludeBots()) {
|
||||
if ($filtering->excludeBots) {
|
||||
$qb->andWhere($qb->expr()->eq('v.potentialBot', 'false'));
|
||||
}
|
||||
|
||||
$this->applyDatesInline($qb, $filtering->dateRange());
|
||||
$this->applySpecification($qb, $filtering->apiKey()?->inlinedSpec(), 'v');
|
||||
$this->applyDatesInline($qb, $filtering->dateRange);
|
||||
$this->applySpecification($qb, $filtering->apiKey?->inlinedSpec(), 'v');
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
public function findVisitsByDomain(string $domain, VisitsListFiltering $filtering): array
|
||||
{
|
||||
$qb = $this->createVisitsByDomainQueryBuilder($domain, $filtering);
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countVisitsByDomain(string $domain, VisitsCountFiltering $filtering): int
|
||||
|
@ -185,12 +185,12 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
->where($qb->expr()->eq('d.authority', $this->getEntityManager()->getConnection()->quote($domain)));
|
||||
}
|
||||
|
||||
if ($filtering->excludeBots()) {
|
||||
if ($filtering->excludeBots) {
|
||||
$qb->andWhere($qb->expr()->eq('v.potentialBot', 'false'));
|
||||
}
|
||||
|
||||
$this->applyDatesInline($qb, $filtering->dateRange());
|
||||
$this->applySpecification($qb, $filtering->apiKey()?->inlinedSpec(), 'v');
|
||||
$this->applyDatesInline($qb, $filtering->dateRange);
|
||||
$this->applySpecification($qb, $filtering->apiKey?->inlinedSpec(), 'v');
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
@ -199,7 +199,7 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
{
|
||||
$qb = $this->createAllVisitsQueryBuilder($filtering);
|
||||
$qb->andWhere($qb->expr()->isNull('v.shortUrl'));
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countOrphanVisits(VisitsCountFiltering $filtering): int
|
||||
|
@ -215,9 +215,9 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
$qb = $this->createAllVisitsQueryBuilder($filtering);
|
||||
$qb->andWhere($qb->expr()->isNotNull('v.shortUrl'));
|
||||
|
||||
$this->applySpecification($qb, $filtering->apiKey()?->inlinedSpec());
|
||||
$this->applySpecification($qb, $filtering->apiKey?->inlinedSpec());
|
||||
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit(), $filtering->offset());
|
||||
return $this->resolveVisitsWithNativeQuery($qb, $filtering->limit, $filtering->offset);
|
||||
}
|
||||
|
||||
public function countNonOrphanVisits(VisitsCountFiltering $filtering): int
|
||||
|
@ -232,11 +232,11 @@ class VisitRepository extends EntitySpecificationRepository implements VisitRepo
|
|||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb->from(Visit::class, 'v');
|
||||
|
||||
if ($filtering->excludeBots()) {
|
||||
if ($filtering->excludeBots) {
|
||||
$qb->andWhere($qb->expr()->eq('v.potentialBot', 'false'));
|
||||
}
|
||||
|
||||
$this->applyDatesInline($qb, $filtering->dateRange());
|
||||
$this->applyDatesInline($qb, $filtering->dateRange);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
|
|
@ -8,23 +8,11 @@ use JsonSerializable;
|
|||
|
||||
final class TagInfo implements JsonSerializable
|
||||
{
|
||||
public function __construct(private string $tag, private int $shortUrlsCount, private int $visitsCount)
|
||||
{
|
||||
}
|
||||
|
||||
public function tag(): string
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
public function shortUrlsCount(): int
|
||||
{
|
||||
return $this->shortUrlsCount;
|
||||
}
|
||||
|
||||
public function visitsCount(): int
|
||||
{
|
||||
return $this->visitsCount;
|
||||
public function __construct(
|
||||
public readonly string $tag,
|
||||
public readonly int $shortUrlsCount,
|
||||
public readonly int $visitsCount,
|
||||
) {
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
|
|
|
@ -10,7 +10,7 @@ use function sprintf;
|
|||
|
||||
final class TagRenaming
|
||||
{
|
||||
private function __construct(private string $oldName, private string $newName)
|
||||
private function __construct(public readonly string $oldName, public readonly string $newName)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -31,16 +31,6 @@ final class TagRenaming
|
|||
return self::fromNames($payload['oldName'], $payload['newName']);
|
||||
}
|
||||
|
||||
public function oldName(): string
|
||||
{
|
||||
return $this->oldName;
|
||||
}
|
||||
|
||||
public function newName(): string
|
||||
{
|
||||
return $this->newName;
|
||||
}
|
||||
|
||||
public function nameChanged(): bool
|
||||
{
|
||||
return $this->oldName !== $this->newName;
|
||||
|
|
|
@ -10,41 +10,16 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
|||
final class TagsListFiltering
|
||||
{
|
||||
public function __construct(
|
||||
private ?int $limit = null,
|
||||
private ?int $offset = null,
|
||||
private ?string $searchTerm = null,
|
||||
private ?Ordering $orderBy = null,
|
||||
private ?ApiKey $apiKey = null,
|
||||
public readonly ?int $limit = null,
|
||||
public readonly ?int $offset = null,
|
||||
public readonly ?string $searchTerm = null,
|
||||
public readonly ?Ordering $orderBy = null,
|
||||
public readonly ?ApiKey $apiKey = null,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function fromRangeAndParams(int $limit, int $offset, TagsParams $params, ?ApiKey $apiKey): self
|
||||
{
|
||||
return new self($limit, $offset, $params->searchTerm(), $params->orderBy(), $apiKey);
|
||||
}
|
||||
|
||||
public function limit(): ?int
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function offset(): ?int
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
public function searchTerm(): ?string
|
||||
{
|
||||
return $this->searchTerm;
|
||||
}
|
||||
|
||||
public function orderBy(): ?Ordering
|
||||
{
|
||||
return $this->orderBy;
|
||||
}
|
||||
|
||||
public function apiKey(): ?ApiKey
|
||||
{
|
||||
return $this->apiKey;
|
||||
return new self($limit, $offset, $params->searchTerm, $params->orderBy, $apiKey);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ use function Shlinkio\Shlink\Common\parseOrderBy;
|
|||
final class TagsParams extends AbstractInfinitePaginableListParams
|
||||
{
|
||||
private function __construct(
|
||||
private ?string $searchTerm,
|
||||
private Ordering $orderBy,
|
||||
private bool $withStats,
|
||||
public readonly ?string $searchTerm,
|
||||
public readonly Ordering $orderBy,
|
||||
public readonly bool $withStats,
|
||||
?int $page,
|
||||
?int $itemsPerPage,
|
||||
) {
|
||||
|
@ -31,19 +31,4 @@ final class TagsParams extends AbstractInfinitePaginableListParams
|
|||
isset($query['itemsPerPage']) ? (int) $query['itemsPerPage'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
public function searchTerm(): ?string
|
||||
{
|
||||
return $this->searchTerm;
|
||||
}
|
||||
|
||||
public function orderBy(): Ordering
|
||||
{
|
||||
return $this->orderBy;
|
||||
}
|
||||
|
||||
public function withStats(): bool
|
||||
{
|
||||
return $this->withStats;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ abstract class AbstractTagsPaginatorAdapter implements AdapterInterface
|
|||
new WithApiKeySpecsEnsuringJoin($this->apiKey),
|
||||
];
|
||||
|
||||
$searchTerm = $this->params->searchTerm();
|
||||
$searchTerm = $this->params->searchTerm;
|
||||
if ($searchTerm !== null) {
|
||||
$conditions[] = Spec::like('name', $searchTerm);
|
||||
}
|
||||
|
|
|
@ -15,13 +15,13 @@ class TagsPaginatorAdapter extends AbstractTagsPaginatorAdapter
|
|||
new WithApiKeySpecsEnsuringJoin($this->apiKey),
|
||||
Spec::orderBy(
|
||||
'name', // Ordering by other fields makes no sense here
|
||||
$this->params->orderBy()->orderDirection(),
|
||||
$this->params->orderBy->direction,
|
||||
),
|
||||
Spec::limit($length),
|
||||
Spec::offset($offset),
|
||||
];
|
||||
|
||||
$searchTerm = $this->params->searchTerm();
|
||||
$searchTerm = $this->params->searchTerm;
|
||||
if ($searchTerm !== null) {
|
||||
$conditions[] = Spec::like('name', $searchTerm);
|
||||
}
|
||||
|
|
|
@ -49,8 +49,8 @@ class TagService implements TagServiceInterface
|
|||
private function createPaginator(AdapterInterface $adapter, TagsParams $params): Paginator
|
||||
{
|
||||
return (new Paginator($adapter))
|
||||
->setMaxPerPage($params->getItemsPerPage())
|
||||
->setCurrentPage($params->getPage());
|
||||
->setMaxPerPage($params->itemsPerPage)
|
||||
->setCurrentPage($params->page);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,17 +83,17 @@ class TagService implements TagServiceInterface
|
|||
$repo = $this->em->getRepository(Tag::class);
|
||||
|
||||
/** @var Tag|null $tag */
|
||||
$tag = $repo->findOneBy(['name' => $renaming->oldName()]);
|
||||
$tag = $repo->findOneBy(['name' => $renaming->oldName]);
|
||||
if ($tag === null) {
|
||||
throw TagNotFoundException::fromTag($renaming->oldName());
|
||||
throw TagNotFoundException::fromTag($renaming->oldName);
|
||||
}
|
||||
|
||||
$newNameExists = $renaming->nameChanged() && $repo->count(['name' => $renaming->newName()]) > 0;
|
||||
$newNameExists = $renaming->nameChanged() && $repo->count(['name' => $renaming->newName]) > 0;
|
||||
if ($newNameExists) {
|
||||
throw TagConflictException::forExistingTag($renaming);
|
||||
}
|
||||
|
||||
$tag->rename($renaming->newName());
|
||||
$tag->rename($renaming->newName);
|
||||
$this->em->flush();
|
||||
|
||||
return $tag;
|
||||
|
|
|
@ -26,8 +26,8 @@ class DomainVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
|||
return $this->visitRepository->countVisitsByDomain(
|
||||
$this->domain,
|
||||
new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
),
|
||||
);
|
||||
|
@ -38,8 +38,8 @@ class DomainVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
|||
return $this->visitRepository->findVisitsByDomain(
|
||||
$this->domain,
|
||||
new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
$length,
|
||||
$offset,
|
||||
|
|
|
@ -23,8 +23,8 @@ class NonOrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAda
|
|||
protected function doCount(): int
|
||||
{
|
||||
return $this->repo->countNonOrphanVisits(new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
));
|
||||
}
|
||||
|
@ -32,8 +32,8 @@ class NonOrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAda
|
|||
public function getSlice(int $offset, int $length): iterable
|
||||
{
|
||||
return $this->repo->findNonOrphanVisits(new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
$length,
|
||||
$offset,
|
||||
|
|
|
@ -19,16 +19,16 @@ class OrphanVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapte
|
|||
protected function doCount(): int
|
||||
{
|
||||
return $this->repo->countOrphanVisits(new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
));
|
||||
}
|
||||
|
||||
public function getSlice(int $offset, int $length): iterable
|
||||
{
|
||||
return $this->repo->findOrphanVisits(new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
null,
|
||||
$length,
|
||||
$offset,
|
||||
|
|
|
@ -27,8 +27,8 @@ class ShortUrlVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdap
|
|||
return $this->visitRepository->findVisitsByShortCode(
|
||||
$this->identifier,
|
||||
new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
$length,
|
||||
$offset,
|
||||
|
@ -41,8 +41,8 @@ class ShortUrlVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdap
|
|||
return $this->visitRepository->countVisitsByShortCode(
|
||||
$this->identifier,
|
||||
new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -26,8 +26,8 @@ class TagVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapter
|
|||
return $this->visitRepository->findVisitsByTag(
|
||||
$this->tag,
|
||||
new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
$length,
|
||||
$offset,
|
||||
|
@ -40,8 +40,8 @@ class TagVisitsPaginatorAdapter extends AbstractCacheableCountPaginatorAdapter
|
|||
return $this->visitRepository->countVisitsByTag(
|
||||
$this->tag,
|
||||
new VisitsCountFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -10,9 +10,9 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
|||
class VisitsCountFiltering
|
||||
{
|
||||
public function __construct(
|
||||
private ?DateRange $dateRange = null,
|
||||
private bool $excludeBots = false,
|
||||
private ?ApiKey $apiKey = null,
|
||||
public readonly ?DateRange $dateRange = null,
|
||||
public readonly bool $excludeBots = false,
|
||||
public readonly ?ApiKey $apiKey = null,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -20,19 +20,4 @@ class VisitsCountFiltering
|
|||
{
|
||||
return new self(null, false, $apiKey);
|
||||
}
|
||||
|
||||
public function dateRange(): ?DateRange
|
||||
{
|
||||
return $this->dateRange;
|
||||
}
|
||||
|
||||
public function excludeBots(): bool
|
||||
{
|
||||
return $this->excludeBots;
|
||||
}
|
||||
|
||||
public function apiKey(): ?ApiKey
|
||||
{
|
||||
return $this->apiKey;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,19 +13,9 @@ final class VisitsListFiltering extends VisitsCountFiltering
|
|||
?DateRange $dateRange = null,
|
||||
bool $excludeBots = false,
|
||||
?ApiKey $apiKey = null,
|
||||
private ?int $limit = null,
|
||||
private ?int $offset = null,
|
||||
public readonly ?int $limit = null,
|
||||
public readonly ?int $offset = null,
|
||||
) {
|
||||
parent::__construct($dateRange, $excludeBots, $apiKey);
|
||||
}
|
||||
|
||||
public function limit(): ?int
|
||||
{
|
||||
return $this->limit;
|
||||
}
|
||||
|
||||
public function offset(): ?int
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,14 +22,14 @@ class CountOfNonOrphanVisits extends BaseSpecification
|
|||
{
|
||||
$conditions = [
|
||||
Spec::isNotNull('shortUrl'),
|
||||
new InDateRange($this->filtering->dateRange()),
|
||||
new InDateRange($this->filtering->dateRange),
|
||||
];
|
||||
|
||||
if ($this->filtering->excludeBots()) {
|
||||
if ($this->filtering->excludeBots) {
|
||||
$conditions[] = Spec::eq('potentialBot', false);
|
||||
}
|
||||
|
||||
$apiKey = $this->filtering->apiKey();
|
||||
$apiKey = $this->filtering->apiKey;
|
||||
if ($apiKey !== null) {
|
||||
$conditions[] = new WithApiKeySpecsEnsuringJoin($apiKey, 'shortUrl');
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ class CountOfOrphanVisits extends BaseSpecification
|
|||
{
|
||||
$conditions = [
|
||||
Spec::isNull('shortUrl'),
|
||||
new InDateRange($this->filtering->dateRange()),
|
||||
new InDateRange($this->filtering->dateRange),
|
||||
];
|
||||
|
||||
if ($this->filtering->excludeBots()) {
|
||||
if ($this->filtering->excludeBots) {
|
||||
$conditions[] = Spec::eq('potentialBot', false);
|
||||
}
|
||||
|
||||
|
|
|
@ -129,8 +129,8 @@ class VisitsStatsHelper implements VisitsStatsHelperInterface
|
|||
private function createPaginator(AdapterInterface $adapter, VisitsParams $params): Paginator
|
||||
{
|
||||
$paginator = new Paginator($adapter);
|
||||
$paginator->setMaxPerPage($params->getItemsPerPage())
|
||||
->setCurrentPage($params->getPage());
|
||||
$paginator->setMaxPerPage($params->itemsPerPage)
|
||||
->setCurrentPage($params->page);
|
||||
|
||||
return $paginator;
|
||||
}
|
||||
|
|
|
@ -72,6 +72,6 @@ class VisitsTracker implements VisitsTrackerInterface
|
|||
$this->em->persist($visit);
|
||||
$this->em->flush();
|
||||
|
||||
$this->eventDispatcher->dispatch(new UrlVisited($visit->getId(), $visitor->getRemoteAddress()));
|
||||
$this->eventDispatcher->dispatch(new UrlVisited($visit->getId(), $visitor->remoteAddress));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ class TagRepositoryTest extends DatabaseTestCase
|
|||
$this->getEntityManager()->persist(new Tag($name));
|
||||
}
|
||||
|
||||
$apiKey = $filtering?->apiKey();
|
||||
$apiKey = $filtering?->apiKey;
|
||||
if ($apiKey !== null) {
|
||||
$this->getEntityManager()->persist($apiKey);
|
||||
}
|
||||
|
@ -101,9 +101,9 @@ class TagRepositoryTest extends DatabaseTestCase
|
|||
|
||||
self::assertCount(count($expectedList), $result);
|
||||
foreach ($expectedList as $index => [$tag, $shortUrlsCount, $visitsCount]) {
|
||||
self::assertEquals($shortUrlsCount, $result[$index]->shortUrlsCount());
|
||||
self::assertEquals($visitsCount, $result[$index]->visitsCount());
|
||||
self::assertEquals($tag, $result[$index]->tag());
|
||||
self::assertEquals($shortUrlsCount, $result[$index]->shortUrlsCount);
|
||||
self::assertEquals($visitsCount, $result[$index]->visitsCount);
|
||||
self::assertEquals($tag, $result[$index]->tag);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,9 +37,10 @@ class PixelActionTest extends TestCase
|
|||
public function imageIsReturned(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))->willReturn(
|
||||
ShortUrl::withLongUrl('http://domain.com/foo/bar'),
|
||||
)->shouldBeCalledOnce();
|
||||
$this->urlResolver->resolveEnabledShortUrl(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''),
|
||||
)->willReturn(ShortUrl::withLongUrl('http://domain.com/foo/bar'))
|
||||
->shouldBeCalledOnce();
|
||||
$this->requestTracker->trackIfApplicable(Argument::cetera())->shouldBeCalledOnce();
|
||||
|
||||
$request = (new ServerRequest())->withAttribute('shortCode', $shortCode);
|
||||
|
|
|
@ -59,7 +59,7 @@ class QrCodeActionTest extends TestCase
|
|||
public function aNotFoundShortCodeWillDelegateIntoNextMiddleware(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''))
|
||||
->willThrow(ShortUrlNotFoundException::class)
|
||||
->shouldBeCalledOnce();
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
@ -74,7 +74,7 @@ class QrCodeActionTest extends TestCase
|
|||
public function aCorrectRequestReturnsTheQrCodeResponse(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''))
|
||||
->willReturn(ShortUrl::createEmpty())
|
||||
->shouldBeCalledOnce();
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
@ -100,7 +100,7 @@ class QrCodeActionTest extends TestCase
|
|||
): void {
|
||||
$this->options->setFromArray(['format' => $defaultFormat]);
|
||||
$code = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($code, ''))->willReturn(
|
||||
ShortUrl::createEmpty(),
|
||||
);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
@ -134,7 +134,7 @@ class QrCodeActionTest extends TestCase
|
|||
): void {
|
||||
$this->options->setFromArray($defaults);
|
||||
$code = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($code, ''))->willReturn(
|
||||
ShortUrl::createEmpty(),
|
||||
);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
@ -214,7 +214,7 @@ class QrCodeActionTest extends TestCase
|
|||
->withQueryParams(['size' => 250, 'roundBlockSize' => $roundBlockSize])
|
||||
->withAttribute('shortCode', $code);
|
||||
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($code, ''))->willReturn(
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($code, ''))->willReturn(
|
||||
ShortUrl::withLongUrl('https://shlink.io'),
|
||||
);
|
||||
$delegate = $this->prophesize(RequestHandlerInterface::class);
|
||||
|
|
|
@ -54,7 +54,7 @@ class RedirectActionTest extends TestCase
|
|||
$shortCode = 'abc123';
|
||||
$shortUrl = ShortUrl::withLongUrl(self::LONG_URL);
|
||||
$shortCodeToUrl = $this->urlResolver->resolveEnabledShortUrl(
|
||||
new ShortUrlIdentifier($shortCode, ''),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''),
|
||||
)->willReturn($shortUrl);
|
||||
$track = $this->requestTracker->trackIfApplicable(Argument::cetera())->will(function (): void {
|
||||
});
|
||||
|
@ -74,7 +74,7 @@ class RedirectActionTest extends TestCase
|
|||
public function nextMiddlewareIsInvokedIfLongUrlIsNotFound(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode, ''))
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, ''))
|
||||
->willThrow(ShortUrlNotFoundException::class)
|
||||
->shouldBeCalledOnce();
|
||||
$this->requestTracker->trackIfApplicable(Argument::cetera())->shouldNotBeCalled();
|
||||
|
|
|
@ -24,7 +24,7 @@ class ShortUrlNotFoundExceptionTest extends TestCase
|
|||
$expectedAdditional['domain'] = $domain;
|
||||
}
|
||||
|
||||
$e = ShortUrlNotFoundException::fromNotFound(new ShortUrlIdentifier($shortCode, $domain));
|
||||
$e = ShortUrlNotFoundException::fromNotFound(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain));
|
||||
|
||||
self::assertEquals($expectedMessage, $e->getMessage());
|
||||
self::assertEquals($expectedMessage, $e->getDetail());
|
||||
|
|
|
@ -24,9 +24,9 @@ class VisitorTest extends TestCase
|
|||
$visitor = new Visitor(...$params);
|
||||
['userAgent' => $userAgent, 'referer' => $referer, 'remoteAddress' => $remoteAddress] = $expected;
|
||||
|
||||
self::assertEquals($userAgent, $visitor->getUserAgent());
|
||||
self::assertEquals($referer, $visitor->getReferer());
|
||||
self::assertEquals($remoteAddress, $visitor->getRemoteAddress());
|
||||
self::assertEquals($userAgent, $visitor->userAgent);
|
||||
self::assertEquals($referer, $visitor->referer);
|
||||
self::assertEquals($remoteAddress, $visitor->remoteAddress);
|
||||
}
|
||||
|
||||
public function provideParams(): iterable
|
||||
|
@ -89,11 +89,11 @@ class VisitorTest extends TestCase
|
|||
]));
|
||||
|
||||
self::assertNotSame($visitor, $normalizedVisitor);
|
||||
self::assertEmpty($normalizedVisitor->getUserAgent());
|
||||
self::assertNotEmpty($visitor->getUserAgent());
|
||||
self::assertEmpty($normalizedVisitor->getReferer());
|
||||
self::assertNotEmpty($visitor->getReferer());
|
||||
self::assertNull($normalizedVisitor->getRemoteAddress());
|
||||
self::assertNotNull($visitor->getRemoteAddress());
|
||||
self::assertEmpty($normalizedVisitor->userAgent);
|
||||
self::assertNotEmpty($visitor->userAgent);
|
||||
self::assertEmpty($normalizedVisitor->referer);
|
||||
self::assertNotEmpty($visitor->referer);
|
||||
self::assertNull($normalizedVisitor->remoteAddress);
|
||||
self::assertNotNull($visitor->remoteAddress);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ class DeleteShortUrlServiceTest extends TestCase
|
|||
$this->shortCode,
|
||||
));
|
||||
|
||||
$service->deleteByShortCode(new ShortUrlIdentifier($this->shortCode));
|
||||
$service->deleteByShortCode(ShortUrlIdentifier::fromShortCodeAndDomain($this->shortCode));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
@ -66,7 +66,7 @@ class DeleteShortUrlServiceTest extends TestCase
|
|||
$remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null);
|
||||
$flush = $this->em->flush()->willReturn(null);
|
||||
|
||||
$service->deleteByShortCode(new ShortUrlIdentifier($this->shortCode), true);
|
||||
$service->deleteByShortCode(ShortUrlIdentifier::fromShortCodeAndDomain($this->shortCode), true);
|
||||
|
||||
$remove->shouldHaveBeenCalledOnce();
|
||||
$flush->shouldHaveBeenCalledOnce();
|
||||
|
@ -80,7 +80,7 @@ class DeleteShortUrlServiceTest extends TestCase
|
|||
$remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null);
|
||||
$flush = $this->em->flush()->willReturn(null);
|
||||
|
||||
$service->deleteByShortCode(new ShortUrlIdentifier($this->shortCode));
|
||||
$service->deleteByShortCode(ShortUrlIdentifier::fromShortCodeAndDomain($this->shortCode));
|
||||
|
||||
$remove->shouldHaveBeenCalledOnce();
|
||||
$flush->shouldHaveBeenCalledOnce();
|
||||
|
@ -94,7 +94,7 @@ class DeleteShortUrlServiceTest extends TestCase
|
|||
$remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null);
|
||||
$flush = $this->em->flush()->willReturn(null);
|
||||
|
||||
$service->deleteByShortCode(new ShortUrlIdentifier($this->shortCode));
|
||||
$service->deleteByShortCode(ShortUrlIdentifier::fromShortCodeAndDomain($this->shortCode));
|
||||
|
||||
$remove->shouldHaveBeenCalledOnce();
|
||||
$flush->shouldHaveBeenCalledOnce();
|
||||
|
|
|
@ -91,7 +91,7 @@ class ShortUrlResolverTest extends TestCase
|
|||
)->willReturn($shortUrl);
|
||||
$getRepo = $this->em->getRepository(ShortUrl::class)->willReturn($repo->reveal());
|
||||
|
||||
$result = $this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode));
|
||||
$result = $this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode));
|
||||
|
||||
self::assertSame($shortUrl, $result);
|
||||
$findOneByShortCode->shouldHaveBeenCalledOnce();
|
||||
|
@ -116,7 +116,7 @@ class ShortUrlResolverTest extends TestCase
|
|||
$findOneByShortCode->shouldBeCalledOnce();
|
||||
$getRepo->shouldBeCalledOnce();
|
||||
|
||||
$this->urlResolver->resolveEnabledShortUrl(new ShortUrlIdentifier($shortCode));
|
||||
$this->urlResolver->resolveEnabledShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode));
|
||||
}
|
||||
|
||||
public function provideDisabledShortUrls(): iterable
|
||||
|
|
|
@ -88,7 +88,7 @@ class ShortUrlServiceTest extends TestCase
|
|||
$shortUrl = ShortUrl::withLongUrl($originalLongUrl);
|
||||
|
||||
$findShortUrl = $this->urlResolver->resolveShortUrl(
|
||||
new ShortUrlIdentifier('abc123'),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('abc123'),
|
||||
$apiKey,
|
||||
)->willReturn($shortUrl);
|
||||
$flush = $this->em->flush()->willReturn(null);
|
||||
|
@ -97,7 +97,11 @@ class ShortUrlServiceTest extends TestCase
|
|||
$shortUrlEdit,
|
||||
);
|
||||
|
||||
$result = $this->service->updateShortUrl(new ShortUrlIdentifier('abc123'), $shortUrlEdit, $apiKey);
|
||||
$result = $this->service->updateShortUrl(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain('abc123'),
|
||||
$shortUrlEdit,
|
||||
$apiKey,
|
||||
);
|
||||
|
||||
self::assertSame($shortUrl, $result);
|
||||
self::assertEquals($shortUrlEdit->validSince(), $shortUrl->getValidSince());
|
||||
|
|
|
@ -39,7 +39,7 @@ class NonOrphanVisitsPaginatorAdapterTest extends TestCase
|
|||
{
|
||||
$expectedCount = 5;
|
||||
$repoCount = $this->repo->countNonOrphanVisits(
|
||||
new VisitsCountFiltering($this->params->getDateRange(), $this->params->excludeBots(), $this->apiKey),
|
||||
new VisitsCountFiltering($this->params->dateRange, $this->params->excludeBots, $this->apiKey),
|
||||
)->willReturn($expectedCount);
|
||||
|
||||
$result = $this->adapter->getNbResults();
|
||||
|
@ -57,8 +57,8 @@ class NonOrphanVisitsPaginatorAdapterTest extends TestCase
|
|||
$visitor = Visitor::emptyInstance();
|
||||
$list = [Visit::forRegularNotFound($visitor), Visit::forInvalidShortUrl($visitor)];
|
||||
$repoFind = $this->repo->findNonOrphanVisits(new VisitsListFiltering(
|
||||
$this->params->getDateRange(),
|
||||
$this->params->excludeBots(),
|
||||
$this->params->dateRange,
|
||||
$this->params->excludeBots,
|
||||
$this->apiKey,
|
||||
$limit,
|
||||
$offset,
|
||||
|
|
|
@ -35,7 +35,7 @@ class OrphanVisitsPaginatorAdapterTest extends TestCase
|
|||
{
|
||||
$expectedCount = 5;
|
||||
$repoCount = $this->repo->countOrphanVisits(
|
||||
new VisitsCountFiltering($this->params->getDateRange()),
|
||||
new VisitsCountFiltering($this->params->dateRange),
|
||||
)->willReturn($expectedCount);
|
||||
|
||||
$result = $this->adapter->getNbResults();
|
||||
|
@ -53,7 +53,7 @@ class OrphanVisitsPaginatorAdapterTest extends TestCase
|
|||
$visitor = Visitor::emptyInstance();
|
||||
$list = [Visit::forRegularNotFound($visitor), Visit::forInvalidShortUrl($visitor)];
|
||||
$repoFind = $this->repo->findOrphanVisits(
|
||||
new VisitsListFiltering($this->params->getDateRange(), $this->params->excludeBots(), null, $limit, $offset),
|
||||
new VisitsListFiltering($this->params->dateRange, $this->params->excludeBots, null, $limit, $offset),
|
||||
)->willReturn($list);
|
||||
|
||||
$result = $this->adapter->getSlice($offset, $limit);
|
||||
|
|
|
@ -68,7 +68,7 @@ class ShortUrlVisitsPaginatorAdapterTest extends TestCase
|
|||
{
|
||||
return new ShortUrlVisitsPaginatorAdapter(
|
||||
$this->repo->reveal(),
|
||||
new ShortUrlIdentifier(''),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain(''),
|
||||
VisitsParams::fromRawData([]),
|
||||
$apiKey,
|
||||
);
|
||||
|
|
|
@ -32,7 +32,7 @@ class ListTagsAction extends AbstractRestAction
|
|||
$params = TagsParams::fromRawData($request->getQueryParams());
|
||||
$apiKey = AuthenticationMiddleware::apiKeyFromRequest($request);
|
||||
|
||||
if (! $params->withStats()) {
|
||||
if (! $params->withStats) {
|
||||
return new JsonResponse([
|
||||
'tags' => $this->serializePaginator($this->tagService->listTags($params, $apiKey)),
|
||||
]);
|
||||
|
@ -41,7 +41,7 @@ class ListTagsAction extends AbstractRestAction
|
|||
// This part is deprecated. To get tags with stats, the /tags/stats endpoint should be used instead
|
||||
$tagsInfo = $this->tagService->tagsInfo($params, $apiKey);
|
||||
$rawTags = $this->serializePaginator($tagsInfo, null, 'stats');
|
||||
$rawTags['data'] = map($tagsInfo, static fn (TagInfo $info) => $info->tag());
|
||||
$rawTags['data'] = map($tagsInfo, static fn (TagInfo $info) => $info->tag);
|
||||
|
||||
return new JsonResponse(['tags' => $rawTags]);
|
||||
}
|
||||
|
|
|
@ -8,11 +8,13 @@ use Cake\Chronos\Chronos;
|
|||
|
||||
final class ApiKeyMeta
|
||||
{
|
||||
/**
|
||||
* @param RoleDefinition[] $roleDefinitions
|
||||
*/
|
||||
private function __construct(
|
||||
private ?string $name,
|
||||
private ?Chronos $expirationDate,
|
||||
/** @var RoleDefinition[] */
|
||||
private array $roleDefinitions,
|
||||
public readonly ?string $name,
|
||||
public readonly ?Chronos $expirationDate,
|
||||
public readonly array $roleDefinitions,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -35,22 +37,4 @@ final class ApiKeyMeta
|
|||
{
|
||||
return new self(null, null, $roleDefinitions);
|
||||
}
|
||||
|
||||
public function name(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function expirationDate(): ?Chronos
|
||||
{
|
||||
return $this->expirationDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return RoleDefinition[]
|
||||
*/
|
||||
public function roleDefinitions(): array
|
||||
{
|
||||
return $this->roleDefinitions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ use Shlinkio\Shlink\Rest\ApiKey\Role;
|
|||
|
||||
final class RoleDefinition
|
||||
{
|
||||
private function __construct(private string $roleName, private array $meta)
|
||||
private function __construct(public readonly string $roleName, public readonly array $meta)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -25,14 +25,4 @@ final class RoleDefinition
|
|||
['domain_id' => $domain->getId(), 'authority' => $domain->getAuthority()],
|
||||
);
|
||||
}
|
||||
|
||||
public function roleName(): string
|
||||
{
|
||||
return $this->roleName;
|
||||
}
|
||||
|
||||
public function meta(): array
|
||||
{
|
||||
return $this->meta;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ class ApiKey extends AbstractEntity
|
|||
|
||||
public static function fromMeta(ApiKeyMeta $meta): self
|
||||
{
|
||||
$apiKey = new self($meta->name(), $meta->expirationDate());
|
||||
foreach ($meta->roleDefinitions() as $roleDefinition) {
|
||||
$apiKey = new self($meta->name, $meta->expirationDate);
|
||||
foreach ($meta->roleDefinitions as $roleDefinition) {
|
||||
$apiKey->registerRole($roleDefinition);
|
||||
}
|
||||
|
||||
|
@ -137,21 +137,16 @@ class ApiKey extends AbstractEntity
|
|||
|
||||
public function registerRole(RoleDefinition $roleDefinition): void
|
||||
{
|
||||
$roleName = $roleDefinition->roleName();
|
||||
$meta = $roleDefinition->meta();
|
||||
$roleName = $roleDefinition->roleName;
|
||||
$meta = $roleDefinition->meta;
|
||||
|
||||
if ($this->hasRole($roleName)) {
|
||||
/** @var ApiKeyRole $role */
|
||||
$role = $this->roles->get($roleName);
|
||||
$role->updateMeta($meta);
|
||||
} else {
|
||||
$role = new ApiKeyRole($roleDefinition->roleName(), $roleDefinition->meta(), $this);
|
||||
$role = new ApiKeyRole($roleDefinition->roleName, $roleDefinition->meta, $this);
|
||||
$this->roles[$roleName] = $role;
|
||||
}
|
||||
}
|
||||
|
||||
public function removeRole(string $roleName): void
|
||||
{
|
||||
$this->roles->remove($roleName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ class AuthenticationMiddleware implements MiddlewareInterface, StatusCodeInterfa
|
|||
throw VerifyAuthenticationException::forInvalidApiKey();
|
||||
}
|
||||
|
||||
return $handler->handle($request->withAttribute(ApiKey::class, $result->apiKey()));
|
||||
return $handler->handle($request->withAttribute(ApiKey::class, $result->apiKey));
|
||||
}
|
||||
|
||||
public static function apiKeyFromRequest(Request $request): ApiKey
|
||||
|
|
|
@ -8,7 +8,7 @@ use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
|||
|
||||
final class ApiKeyCheckResult
|
||||
{
|
||||
public function __construct(private ?ApiKey $apiKey = null)
|
||||
public function __construct(public readonly ?ApiKey $apiKey = null)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -16,9 +16,4 @@ final class ApiKeyCheckResult
|
|||
{
|
||||
return $this->apiKey !== null && $this->apiKey->isValid();
|
||||
}
|
||||
|
||||
public function apiKey(): ?ApiKey
|
||||
{
|
||||
return $this->apiKey;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,9 +44,9 @@ class DomainRedirectsRequestTest extends TestCase
|
|||
$notFound = $request->toNotFoundRedirects($defaults);
|
||||
|
||||
self::assertEquals($expectedAuthority, $request->authority());
|
||||
self::assertEquals($expectedBaseUrlRedirect, $notFound->baseUrlRedirect());
|
||||
self::assertEquals($expectedRegular404Redirect, $notFound->regular404Redirect());
|
||||
self::assertEquals($expectedInvalidShortUrlRedirect, $notFound->invalidShortUrlRedirect());
|
||||
self::assertEquals($expectedBaseUrlRedirect, $notFound->baseUrlRedirect);
|
||||
self::assertEquals($expectedRegular404Redirect, $notFound->regular404Redirect);
|
||||
self::assertEquals($expectedInvalidShortUrlRedirect, $notFound->invalidShortUrlRedirect);
|
||||
}
|
||||
|
||||
public function provideValidData(): iterable
|
||||
|
|
|
@ -36,9 +36,11 @@ class ResolveShortUrlActionTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$apiKey = ApiKey::create();
|
||||
$this->urlResolver->resolveShortUrl(new ShortUrlIdentifier($shortCode), $apiKey)->willReturn(
|
||||
ShortUrl::withLongUrl('http://domain.com/foo/bar'),
|
||||
)->shouldBeCalledOnce();
|
||||
$this->urlResolver->resolveShortUrl(
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
$apiKey,
|
||||
)->willReturn(ShortUrl::withLongUrl('http://domain.com/foo/bar'))
|
||||
->shouldBeCalledOnce();
|
||||
|
||||
$request = (new ServerRequest())->withAttribute('shortCode', $shortCode)->withAttribute(ApiKey::class, $apiKey);
|
||||
$response = $this->action->handle($request);
|
||||
|
|
|
@ -38,7 +38,7 @@ class ShortUrlVisitsActionTest extends TestCase
|
|||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsHelper->visitsForShortUrl(
|
||||
new ShortUrlIdentifier($shortCode),
|
||||
ShortUrlIdentifier::fromShortCodeAndDomain($shortCode),
|
||||
Argument::type(VisitsParams::class),
|
||||
Argument::type(ApiKey::class),
|
||||
)->willReturn(new Paginator(new ArrayAdapter([])))
|
||||
|
@ -52,7 +52,7 @@ class ShortUrlVisitsActionTest extends TestCase
|
|||
public function paramsAreReadFromQuery(): void
|
||||
{
|
||||
$shortCode = 'abc123';
|
||||
$this->visitsHelper->visitsForShortUrl(new ShortUrlIdentifier($shortCode), new VisitsParams(
|
||||
$this->visitsHelper->visitsForShortUrl(ShortUrlIdentifier::fromShortCodeAndDomain($shortCode), new VisitsParams(
|
||||
DateRange::withEndDate(Chronos::parse('2016-01-01 00:00:00')),
|
||||
3,
|
||||
10,
|
||||
|
|
|
@ -16,8 +16,8 @@ class RoleDefinitionTest extends TestCase
|
|||
{
|
||||
$definition = RoleDefinition::forAuthoredShortUrls();
|
||||
|
||||
self::assertEquals(Role::AUTHORED_SHORT_URLS, $definition->roleName());
|
||||
self::assertEquals([], $definition->meta());
|
||||
self::assertEquals(Role::AUTHORED_SHORT_URLS, $definition->roleName);
|
||||
self::assertEquals([], $definition->meta);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
@ -26,7 +26,7 @@ class RoleDefinitionTest extends TestCase
|
|||
$domain = Domain::withAuthority('foo.com')->setId('123');
|
||||
$definition = RoleDefinition::forDomain($domain);
|
||||
|
||||
self::assertEquals(Role::DOMAIN_SPECIFIC, $definition->roleName());
|
||||
self::assertEquals(['domain_id' => '123', 'authority' => 'foo.com'], $definition->meta());
|
||||
self::assertEquals(Role::DOMAIN_SPECIFIC, $definition->roleName);
|
||||
self::assertEquals(['domain_id' => '123', 'authority' => 'foo.com'], $definition->meta);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class ApiKeyServiceTest extends TestCase
|
|||
self::assertEquals($date, $key->getExpirationDate());
|
||||
self::assertEquals($name, $key->name());
|
||||
foreach ($roles as $roleDefinition) {
|
||||
self::assertTrue($key->hasRole($roleDefinition->roleName()));
|
||||
self::assertTrue($key->hasRole($roleDefinition->roleName));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ class ApiKeyServiceTest extends TestCase
|
|||
$result = $this->service->check('12345');
|
||||
|
||||
self::assertFalse($result->isValid());
|
||||
self::assertSame($invalidKey, $result->apiKey());
|
||||
self::assertSame($invalidKey, $result->apiKey);
|
||||
}
|
||||
|
||||
public function provideInvalidApiKeys(): iterable
|
||||
|
@ -100,7 +100,7 @@ class ApiKeyServiceTest extends TestCase
|
|||
$result = $this->service->check('12345');
|
||||
|
||||
self::assertTrue($result->isValid());
|
||||
self::assertSame($apiKey, $result->apiKey());
|
||||
self::assertSame($apiKey, $result->apiKey);
|
||||
}
|
||||
|
||||
/** @test */
|
||||
|
|
Loading…
Add table
Reference in a new issue