Updated short URL deletion so that it accepts the domain

This commit is contained in:
Alejandro Celaya
2020-02-01 18:06:50 +01:00
parent 5f00d8b732
commit 732bb06c62
6 changed files with 37 additions and 23 deletions

View File

@@ -6,6 +6,7 @@ namespace Shlinkio\Shlink\CLI\Command\ShortUrl;
use Shlinkio\Shlink\CLI\Util\ExitCodes; use Shlinkio\Shlink\CLI\Util\ExitCodes;
use Shlinkio\Shlink\Core\Exception; use Shlinkio\Shlink\Core\Exception;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputArgument;
@@ -40,33 +41,39 @@ class DeleteShortUrlCommand extends Command
InputOption::VALUE_NONE, InputOption::VALUE_NONE,
'Ignores the safety visits threshold check, which could make short URLs with many visits to be ' 'Ignores the safety visits threshold check, which could make short URLs with many visits to be '
. 'accidentally deleted', . 'accidentally deleted',
)
->addOption(
'domain',
'd',
InputOption::VALUE_REQUIRED,
'The domain if the short code does not belong to the default one',
); );
} }
protected function execute(InputInterface $input, OutputInterface $output): ?int protected function execute(InputInterface $input, OutputInterface $output): ?int
{ {
$io = new SymfonyStyle($input, $output); $io = new SymfonyStyle($input, $output);
$shortCode = $input->getArgument('shortCode'); $identifier = ShortUrlIdentifier::fromCli($input);
$ignoreThreshold = $input->getOption('ignore-threshold'); $ignoreThreshold = $input->getOption('ignore-threshold');
try { try {
$this->runDelete($io, $shortCode, $ignoreThreshold); $this->runDelete($io, $identifier, $ignoreThreshold);
return ExitCodes::EXIT_SUCCESS; return ExitCodes::EXIT_SUCCESS;
} catch (Exception\ShortUrlNotFoundException $e) { } catch (Exception\ShortUrlNotFoundException $e) {
$io->error($e->getMessage()); $io->error($e->getMessage());
return ExitCodes::EXIT_FAILURE; return ExitCodes::EXIT_FAILURE;
} catch (Exception\DeleteShortUrlException $e) { } catch (Exception\DeleteShortUrlException $e) {
return $this->retry($io, $shortCode, $e->getMessage()); return $this->retry($io, $identifier, $e->getMessage());
} }
} }
private function retry(SymfonyStyle $io, string $shortCode, string $warningMsg): int private function retry(SymfonyStyle $io, ShortUrlIdentifier $identifier, string $warningMsg): int
{ {
$io->writeln(sprintf('<bg=yellow>%s</>', $warningMsg)); $io->writeln(sprintf('<bg=yellow>%s</>', $warningMsg));
$forceDelete = $io->confirm('Do you want to delete it anyway?', false); $forceDelete = $io->confirm('Do you want to delete it anyway?', false);
if ($forceDelete) { if ($forceDelete) {
$this->runDelete($io, $shortCode, true); $this->runDelete($io, $identifier, true);
} else { } else {
$io->warning('Short URL was not deleted.'); $io->warning('Short URL was not deleted.');
} }
@@ -74,9 +81,9 @@ class DeleteShortUrlCommand extends Command
return $forceDelete ? ExitCodes::EXIT_SUCCESS : ExitCodes::EXIT_WARNING; return $forceDelete ? ExitCodes::EXIT_SUCCESS : ExitCodes::EXIT_WARNING;
} }
private function runDelete(SymfonyStyle $io, string $shortCode, bool $ignoreThreshold): void private function runDelete(SymfonyStyle $io, ShortUrlIdentifier $identifier, bool $ignoreThreshold): void
{ {
$this->deleteShortUrlService->deleteByShortCode($shortCode, $ignoreThreshold); $this->deleteShortUrlService->deleteByShortCode($identifier, $ignoreThreshold);
$io->success(sprintf('Short URL with short code "%s" successfully deleted.', $shortCode)); $io->success(sprintf('Short URL with short code "%s" successfully deleted.', $identifier->shortCode()));
} }
} }

View File

@@ -39,8 +39,10 @@ class DeleteShortUrlCommandTest extends TestCase
public function successMessageIsPrintedIfUrlIsProperlyDeleted(): void public function successMessageIsPrintedIfUrlIsProperlyDeleted(): void
{ {
$shortCode = 'abc123'; $shortCode = 'abc123';
$deleteByShortCode = $this->service->deleteByShortCode($shortCode, false)->will(function (): void { $deleteByShortCode = $this->service->deleteByShortCode(new ShortUrlIdentifier($shortCode), false)->will(
}); function (): void {
},
);
$this->commandTester->execute(['shortCode' => $shortCode]); $this->commandTester->execute(['shortCode' => $shortCode]);
$output = $this->commandTester->getDisplay(); $output = $this->commandTester->getDisplay();
@@ -56,8 +58,9 @@ class DeleteShortUrlCommandTest extends TestCase
public function invalidShortCodePrintsMessage(): void public function invalidShortCodePrintsMessage(): void
{ {
$shortCode = 'abc123'; $shortCode = 'abc123';
$deleteByShortCode = $this->service->deleteByShortCode($shortCode, false)->willThrow( $identifier = new ShortUrlIdentifier($shortCode);
Exception\ShortUrlNotFoundException::fromNotFound(new ShortUrlIdentifier($shortCode)), $deleteByShortCode = $this->service->deleteByShortCode($identifier, false)->willThrow(
Exception\ShortUrlNotFoundException::fromNotFound($identifier),
); );
$this->commandTester->execute(['shortCode' => $shortCode]); $this->commandTester->execute(['shortCode' => $shortCode]);
@@ -77,7 +80,8 @@ class DeleteShortUrlCommandTest extends TestCase
string $expectedMessage string $expectedMessage
): void { ): void {
$shortCode = 'abc123'; $shortCode = 'abc123';
$deleteByShortCode = $this->service->deleteByShortCode($shortCode, Argument::type('bool'))->will( $identifier = new ShortUrlIdentifier($shortCode);
$deleteByShortCode = $this->service->deleteByShortCode($identifier, Argument::type('bool'))->will(
function (array $args) use ($shortCode): void { function (array $args) use ($shortCode): void {
$ignoreThreshold = array_pop($args); $ignoreThreshold = array_pop($args);
@@ -110,7 +114,7 @@ class DeleteShortUrlCommandTest extends TestCase
public function deleteIsNotRetriedWhenThresholdIsReachedAndQuestionIsDeclined(): void public function deleteIsNotRetriedWhenThresholdIsReachedAndQuestionIsDeclined(): void
{ {
$shortCode = 'abc123'; $shortCode = 'abc123';
$deleteByShortCode = $this->service->deleteByShortCode($shortCode, false)->willThrow( $deleteByShortCode = $this->service->deleteByShortCode(new ShortUrlIdentifier($shortCode), false)->willThrow(
Exception\DeleteShortUrlException::fromVisitsThreshold(10, $shortCode), Exception\DeleteShortUrlException::fromVisitsThreshold(10, $shortCode),
); );
$this->commandTester->setInputs(['no']); $this->commandTester->setInputs(['no']);

View File

@@ -27,9 +27,9 @@ class DeleteShortUrlService implements DeleteShortUrlServiceInterface
* @throws Exception\ShortUrlNotFoundException * @throws Exception\ShortUrlNotFoundException
* @throws Exception\DeleteShortUrlException * @throws Exception\DeleteShortUrlException
*/ */
public function deleteByShortCode(string $shortCode, bool $ignoreThreshold = false): void public function deleteByShortCode(ShortUrlIdentifier $identifier, bool $ignoreThreshold = false): void
{ {
$shortUrl = $this->findByShortCode($this->em, new ShortUrlIdentifier($shortCode)); $shortUrl = $this->findByShortCode($this->em, $identifier);
if (! $ignoreThreshold && $this->isThresholdReached($shortUrl)) { if (! $ignoreThreshold && $this->isThresholdReached($shortUrl)) {
throw Exception\DeleteShortUrlException::fromVisitsThreshold( throw Exception\DeleteShortUrlException::fromVisitsThreshold(
$this->deleteShortUrlsOptions->getVisitsThreshold(), $this->deleteShortUrlsOptions->getVisitsThreshold(),

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace Shlinkio\Shlink\Core\Service\ShortUrl; namespace Shlinkio\Shlink\Core\Service\ShortUrl;
use Shlinkio\Shlink\Core\Exception; use Shlinkio\Shlink\Core\Exception;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
interface DeleteShortUrlServiceInterface interface DeleteShortUrlServiceInterface
{ {
@@ -12,5 +13,5 @@ interface DeleteShortUrlServiceInterface
* @throws Exception\ShortUrlNotFoundException * @throws Exception\ShortUrlNotFoundException
* @throws Exception\DeleteShortUrlException * @throws Exception\DeleteShortUrlException
*/ */
public function deleteByShortCode(string $shortCode, bool $ignoreThreshold = false): void; public function deleteByShortCode(ShortUrlIdentifier $identifier, bool $ignoreThreshold = false): void;
} }

View File

@@ -12,6 +12,7 @@ use Prophecy\Prophecy\ObjectProphecy;
use Shlinkio\Shlink\Core\Entity\ShortUrl; use Shlinkio\Shlink\Core\Entity\ShortUrl;
use Shlinkio\Shlink\Core\Entity\Visit; use Shlinkio\Shlink\Core\Entity\Visit;
use Shlinkio\Shlink\Core\Exception\DeleteShortUrlException; use Shlinkio\Shlink\Core\Exception\DeleteShortUrlException;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Model\Visitor; use Shlinkio\Shlink\Core\Model\Visitor;
use Shlinkio\Shlink\Core\Options\DeleteShortUrlsOptions; use Shlinkio\Shlink\Core\Options\DeleteShortUrlsOptions;
use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface; use Shlinkio\Shlink\Core\Repository\ShortUrlRepositoryInterface;
@@ -51,7 +52,7 @@ class DeleteShortUrlServiceTest extends TestCase
$this->shortCode, $this->shortCode,
)); ));
$service->deleteByShortCode($this->shortCode); $service->deleteByShortCode(new ShortUrlIdentifier($this->shortCode));
} }
/** @test */ /** @test */
@@ -62,7 +63,7 @@ class DeleteShortUrlServiceTest extends TestCase
$remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null); $remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null);
$flush = $this->em->flush()->willReturn(null); $flush = $this->em->flush()->willReturn(null);
$service->deleteByShortCode($this->shortCode, true); $service->deleteByShortCode(new ShortUrlIdentifier($this->shortCode), true);
$remove->shouldHaveBeenCalledOnce(); $remove->shouldHaveBeenCalledOnce();
$flush->shouldHaveBeenCalledOnce(); $flush->shouldHaveBeenCalledOnce();
@@ -76,7 +77,7 @@ class DeleteShortUrlServiceTest extends TestCase
$remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null); $remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null);
$flush = $this->em->flush()->willReturn(null); $flush = $this->em->flush()->willReturn(null);
$service->deleteByShortCode($this->shortCode); $service->deleteByShortCode(new ShortUrlIdentifier($this->shortCode));
$remove->shouldHaveBeenCalledOnce(); $remove->shouldHaveBeenCalledOnce();
$flush->shouldHaveBeenCalledOnce(); $flush->shouldHaveBeenCalledOnce();
@@ -90,7 +91,7 @@ class DeleteShortUrlServiceTest extends TestCase
$remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null); $remove = $this->em->remove(Argument::type(ShortUrl::class))->willReturn(null);
$flush = $this->em->flush()->willReturn(null); $flush = $this->em->flush()->willReturn(null);
$service->deleteByShortCode($this->shortCode); $service->deleteByShortCode(new ShortUrlIdentifier($this->shortCode));
$remove->shouldHaveBeenCalledOnce(); $remove->shouldHaveBeenCalledOnce();
$flush->shouldHaveBeenCalledOnce(); $flush->shouldHaveBeenCalledOnce();

View File

@@ -8,6 +8,7 @@ use Laminas\Diactoros\Response\EmptyResponse;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Shlinkio\Shlink\Core\Model\ShortUrlIdentifier;
use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface; use Shlinkio\Shlink\Core\Service\ShortUrl\DeleteShortUrlServiceInterface;
use Shlinkio\Shlink\Rest\Action\AbstractRestAction; use Shlinkio\Shlink\Rest\Action\AbstractRestAction;
@@ -26,8 +27,8 @@ class DeleteShortUrlAction extends AbstractRestAction
public function handle(ServerRequestInterface $request): ResponseInterface public function handle(ServerRequestInterface $request): ResponseInterface
{ {
$shortCode = $request->getAttribute('shortCode', ''); $identifier = ShortUrlIdentifier::fromApiRequest($request);
$this->deleteShortUrlService->deleteByShortCode($shortCode); $this->deleteShortUrlService->deleteByShortCode($identifier);
return new EmptyResponse(); return new EmptyResponse();
} }
} }