From d481c06f09096f494961fa63b7c34b60bfee8752 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 15 Dec 2025 10:04:43 +0100 Subject: [PATCH] Convert DeleteShortUrlVisitsCommand into invokable command --- .../ShortUrl/DeleteShortUrlCommand.php | 2 +- .../ShortUrl/DeleteShortUrlVisitsCommand.php | 46 +++++++++---------- module/CLI/src/Command/Util/CommandUtils.php | 28 +++++++++++ .../DeleteShortUrlVisitsCommandTest.php | 8 ++-- 4 files changed, 55 insertions(+), 29 deletions(-) create mode 100644 module/CLI/src/Command/Util/CommandUtils.php diff --git a/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php index 9d578729..c3b4ba0c 100644 --- a/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/DeleteShortUrlCommand.php @@ -28,7 +28,7 @@ class DeleteShortUrlCommand extends Command public function __invoke( SymfonyStyle $io, #[Argument('The short code for the short URL to be deleted')] string $shortCode, - #[Option('TThe domain if the short code does not belong to the default one', shortcut: 'd')] + #[Option('The domain if the short code does not belong to the default one', shortcut: 'd')] string|null $domain = null, #[Option( 'Ignores the safety visits threshold check, which could make short URLs with many visits to be ' diff --git a/module/CLI/src/Command/ShortUrl/DeleteShortUrlVisitsCommand.php b/module/CLI/src/Command/ShortUrl/DeleteShortUrlVisitsCommand.php index 4c238f31..16667eb3 100644 --- a/module/CLI/src/Command/ShortUrl/DeleteShortUrlVisitsCommand.php +++ b/module/CLI/src/Command/ShortUrl/DeleteShortUrlVisitsCommand.php @@ -4,41 +4,44 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Command\ShortUrl; -use Shlinkio\Shlink\CLI\Command\Visit\AbstractDeleteVisitsCommand; -use Shlinkio\Shlink\CLI\Input\ShortUrlIdentifierInput; +use Shlinkio\Shlink\CLI\Command\Util\CommandUtils; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; +use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlIdentifier; use Shlinkio\Shlink\Core\ShortUrl\ShortUrlVisitsDeleterInterface; -use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Attribute\Argument; +use Symfony\Component\Console\Attribute\AsCommand; +use Symfony\Component\Console\Attribute\Option; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Style\SymfonyStyle; use function sprintf; -class DeleteShortUrlVisitsCommand extends AbstractDeleteVisitsCommand +#[AsCommand(DeleteShortUrlVisitsCommand::NAME, 'Deletes visits from a short URL')] +class DeleteShortUrlVisitsCommand extends Command { public const string NAME = 'short-url:visits-delete'; - private readonly ShortUrlIdentifierInput $shortUrlIdentifierInput; - public function __construct(private readonly ShortUrlVisitsDeleterInterface $deleter) { parent::__construct(); - $this->shortUrlIdentifierInput = new ShortUrlIdentifierInput( - $this, - shortCodeDesc: 'The short code for the short URL which visits will be deleted', - domainDesc: 'The domain if the short code does not belong to the default one', + } + + public function __invoke( + SymfonyStyle $io, + #[Argument('The short code for the short URL which visits will be deleted')] string $shortCode, + #[Option('The domain if the short code does not belong to the default one', shortcut: 'd')] + string|null $domain = null, + ): int { + $identifier = ShortUrlIdentifier::fromShortCodeAndDomain($shortCode, $domain); + return CommandUtils::executeWithWarning( + 'You are about to delete all visits for a short URL. This operation cannot be undone', + $io, + fn () => $this->deleteVisits($io, $identifier), ); } - protected function configure(): void + private function deleteVisits(SymfonyStyle $io, ShortUrlIdentifier $identifier): int { - $this - ->setName(self::NAME) - ->setDescription('Deletes visits from a short URL'); - } - - protected function doExecute(InputInterface $input, SymfonyStyle $io): int - { - $identifier = $this->shortUrlIdentifierInput->toShortUrlIdentifier($input); try { $result = $this->deleter->deleteShortUrlVisits($identifier); $io->success(sprintf('Successfully deleted %s visits', $result->affectedItems)); @@ -49,9 +52,4 @@ class DeleteShortUrlVisitsCommand extends AbstractDeleteVisitsCommand return self::INVALID; } } - - protected function getWarningMessage(): string - { - return 'You are about to delete all visits for a short URL. This operation cannot be undone.'; - } } diff --git a/module/CLI/src/Command/Util/CommandUtils.php b/module/CLI/src/Command/Util/CommandUtils.php new file mode 100644 index 00000000..76085f1a --- /dev/null +++ b/module/CLI/src/Command/Util/CommandUtils.php @@ -0,0 +1,28 @@ +warning($warning); + if (! $io->confirm('Do you want to proceed?', default: false)) { + $io->info('Operation aborted'); + return Command::SUCCESS; + } + + return $callback(); + } +} diff --git a/module/CLI/test/Command/ShortUrl/DeleteShortUrlVisitsCommandTest.php b/module/CLI/test/Command/ShortUrl/DeleteShortUrlVisitsCommandTest.php index 3efa94b5..6f066d0f 100644 --- a/module/CLI/test/Command/ShortUrl/DeleteShortUrlVisitsCommandTest.php +++ b/module/CLI/test/Command/ShortUrl/DeleteShortUrlVisitsCommandTest.php @@ -36,7 +36,7 @@ class DeleteShortUrlVisitsCommandTest extends TestCase $this->deleter->expects($this->never())->method('deleteShortUrlVisits'); $this->commandTester->setInputs($input); - $exitCode = $this->commandTester->execute(['shortCode' => 'foo']); + $exitCode = $this->commandTester->execute(['short-code' => 'foo']); $output = $this->commandTester->getDisplay(); self::assertEquals(Command::SUCCESS, $exitCode); @@ -67,8 +67,8 @@ class DeleteShortUrlVisitsCommandTest extends TestCase public static function provideErrorArgs(): iterable { - yield 'domain' => [['shortCode' => 'foo'], 'Short URL not found for "foo"']; - yield 'no domain' => [['shortCode' => 'foo', '--domain' => 's.test'], 'Short URL not found for "s.test/foo"']; + yield 'domain' => [['short-code' => 'foo'], 'Short URL not found for "foo"']; + yield 'no domain' => [['short-code' => 'foo', '--domain' => 's.test'], 'Short URL not found for "s.test/foo"']; } #[Test] @@ -77,7 +77,7 @@ class DeleteShortUrlVisitsCommandTest extends TestCase $this->deleter->expects($this->once())->method('deleteShortUrlVisits')->willReturn(new BulkDeleteResult(5)); $this->commandTester->setInputs(['yes']); - $exitCode = $this->commandTester->execute(['shortCode' => 'foo']); + $exitCode = $this->commandTester->execute(['short-code' => 'foo']); $output = $this->commandTester->getDisplay(); self::assertEquals(Command::SUCCESS, $exitCode);