From 5bccdded8ab40f2a5a65a5e352b8ab076e33bd40 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Fri, 26 Jul 2024 09:20:03 +0200 Subject: [PATCH] Create command to edit existing short URLs --- module/CLI/config/dependencies.config.php | 2 +- .../ShortUrl/CreateShortUrlCommand.php | 34 ++----- .../Command/ShortUrl/EditShortUrlCommand.php | 23 +++-- module/CLI/src/Input/ShortUrlDataInput.php | 96 +++++++++---------- 4 files changed, 73 insertions(+), 82 deletions(-) diff --git a/module/CLI/config/dependencies.config.php b/module/CLI/config/dependencies.config.php index f9bb9654..3853fd1d 100644 --- a/module/CLI/config/dependencies.config.php +++ b/module/CLI/config/dependencies.config.php @@ -93,7 +93,7 @@ return [ ShortUrlStringifier::class, UrlShortenerOptions::class, ], - Command\ShortUrl\EditShortUrlCommand::class => [ShortUrl\ShortUrlService::class], + Command\ShortUrl\EditShortUrlCommand::class => [ShortUrl\ShortUrlService::class, ShortUrlStringifier::class], Command\ShortUrl\ResolveUrlCommand::class => [ShortUrl\ShortUrlResolver::class], Command\ShortUrl\ListShortUrlsCommand::class => [ ShortUrl\ShortUrlListService::class, diff --git a/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php index d47c30b9..0273da71 100644 --- a/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php @@ -9,8 +9,6 @@ use Shlinkio\Shlink\CLI\Util\ExitCode; use Shlinkio\Shlink\Core\Exception\NonUniqueSlugException; use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface; -use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; -use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter; use Shlinkio\Shlink\Core\ShortUrl\UrlShortenerInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -95,29 +93,17 @@ class CreateShortUrlCommand extends Command protected function execute(InputInterface $input, OutputInterface $output): int { $io = $this->getIO($input, $output); - $longUrl = $this->shortUrlDataInput->longUrl($input); - if (empty($longUrl)) { - $io->error('A URL was not provided!'); - return ExitCode::EXIT_FAILURE; - } - - $shortCodeLength = $input->getOption('short-code-length') ?? $this->options->defaultShortCodesLength; try { - $result = $this->urlShortener->shorten(ShortUrlCreation::fromRawData([ - ShortUrlInputFilter::LONG_URL => $longUrl, - ShortUrlInputFilter::VALID_SINCE => $this->shortUrlDataInput->validSince($input), - ShortUrlInputFilter::VALID_UNTIL => $this->shortUrlDataInput->validUntil($input), - ShortUrlInputFilter::MAX_VISITS => $this->shortUrlDataInput->maxVisits($input), - ShortUrlInputFilter::CUSTOM_SLUG => $input->getOption('custom-slug'), - ShortUrlInputFilter::PATH_PREFIX => $input->getOption('path-prefix'), - ShortUrlInputFilter::FIND_IF_EXISTS => $input->getOption('find-if-exists'), - ShortUrlInputFilter::DOMAIN => $input->getOption('domain'), - ShortUrlInputFilter::SHORT_CODE_LENGTH => $shortCodeLength, - ShortUrlInputFilter::TAGS => $this->shortUrlDataInput->tags($input), - ShortUrlInputFilter::CRAWLABLE => $this->shortUrlDataInput->crawlable($input), - ShortUrlInputFilter::FORWARD_QUERY => !$this->shortUrlDataInput->noForwardQuery($input), - ], $this->options)); + $result = $this->urlShortener->shorten($this->shortUrlDataInput->toShortUrlCreation( + $input, + $this->options, + customSlugField: 'custom-slug', + shortCodeLengthField: 'short-code-length', + pathPrefixField: 'path-prefix', + findIfExistsField: 'find-if-exists', + domainField: 'domain', + )); $result->onEventDispatchingError(static fn () => $io->isVerbose() && $io->warning( 'Short URL properly created, but the real-time updates cannot be notified when generating the ' @@ -125,7 +111,7 @@ class CreateShortUrlCommand extends Command )); $io->writeln([ - sprintf('Processed long URL: %s', $longUrl), + sprintf('Processed long URL: %s', $result->shortUrl->getLongUrl()), sprintf('Generated short URL: %s', $this->stringifier->stringify($result->shortUrl)), ]); return ExitCode::EXIT_SUCCESS; diff --git a/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php index b3fd0bd4..048b3934 100644 --- a/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php +++ b/module/CLI/src/Command/ShortUrl/EditShortUrlCommand.php @@ -8,12 +8,15 @@ use Shlinkio\Shlink\CLI\Input\ShortUrlDataInput; use Shlinkio\Shlink\CLI\Input\ShortUrlIdentifierInput; use Shlinkio\Shlink\CLI\Util\ExitCode; use Shlinkio\Shlink\Core\Exception\ShortUrlNotFoundException; +use Shlinkio\Shlink\Core\ShortUrl\Helper\ShortUrlStringifierInterface; use Shlinkio\Shlink\Core\ShortUrl\ShortUrlServiceInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +use function sprintf; + class EditShortUrlCommand extends Command { public const NAME = 'short-url:edit'; @@ -21,8 +24,10 @@ class EditShortUrlCommand extends Command private readonly ShortUrlDataInput $shortUrlDataInput; private readonly ShortUrlIdentifierInput $shortUrlIdentifierInput; - public function __construct(private readonly ShortUrlServiceInterface $shortUrlService) - { + public function __construct( + private readonly ShortUrlServiceInterface $shortUrlService, + private readonly ShortUrlStringifierInterface $stringifier, + ) { parent::__construct(); $this->shortUrlDataInput = new ShortUrlDataInput($this, longUrlAsOption: true); @@ -43,17 +48,23 @@ class EditShortUrlCommand extends Command protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); + $identifier = $this->shortUrlIdentifierInput->toShortUrlIdentifier($input); try { $shortUrl = $this->shortUrlService->updateShortUrl( - $this->shortUrlIdentifierInput->toShortUrlIdentifier($input), + $identifier, $this->shortUrlDataInput->toShortUrlEdition($input), ); - // TODO Print success + $io->success(sprintf('Short URL "%s" properly edited', $this->stringifier->stringify($shortUrl))); return ExitCode::EXIT_SUCCESS; - } catch (ShortUrlNotFoundException) { - // TODO Print error + } catch (ShortUrlNotFoundException $e) { + $io->error(sprintf('Short URL not found for "%s"', $identifier->__toString())); + + if ($io->isVerbose()) { + $this->getApplication()?->renderThrowable($e, $io); + } + return ExitCode::EXIT_FAILURE; } } diff --git a/module/CLI/src/Input/ShortUrlDataInput.php b/module/CLI/src/Input/ShortUrlDataInput.php index 46c5b1ce..30121547 100644 --- a/module/CLI/src/Input/ShortUrlDataInput.php +++ b/module/CLI/src/Input/ShortUrlDataInput.php @@ -4,6 +4,8 @@ declare(strict_types=1); namespace Shlinkio\Shlink\CLI\Input; +use Shlinkio\Shlink\Core\Options\UrlShortenerOptions; +use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation; use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlEdition; use Shlinkio\Shlink\Core\ShortUrl\Model\Validation\ShortUrlInputFilter; use Symfony\Component\Console\Command\Command; @@ -53,6 +55,11 @@ readonly final class ShortUrlDataInput InputOption::VALUE_REQUIRED, 'This will limit the number of visits for this short URL.', ) + ->addOption( + 'title', + mode: InputOption::VALUE_REQUIRED, + description: 'A descriptive title for the short URL.', + ) ->addOption( 'crawlable', 'r', @@ -67,59 +74,46 @@ readonly final class ShortUrlDataInput ); } - public function longUrl(InputInterface $input): ?string - { - return $this->longUrlAsOption ? $input->getOption('long-url') : $input->getArgument('longUrl'); - } - - /** - * @return string[] - */ - public function tags(InputInterface $input): array - { - return array_unique(flatten(array_map(splitByComma(...), $input->getOption('tags')))); - } - - public function validSince(InputInterface $input): ?string - { - return $input->getOption('valid-since'); - } - - public function validUntil(InputInterface $input): ?string - { - return $input->getOption('valid-until'); - } - - public function maxVisits(InputInterface $input): ?int - { - $maxVisits = $input->getOption('max-visits'); - return $maxVisits !== null ? (int) $maxVisits : null; - } - - public function crawlable(InputInterface $input): bool - { - return $input->getOption('crawlable'); - } - - public function noForwardQuery(InputInterface $input): bool - { - return $input->getOption('no-forward-query'); - } - public function toShortUrlEdition(InputInterface $input): ShortUrlEdition { - return ShortUrlEdition::fromRawData([ - ShortUrlInputFilter::LONG_URL => $this->longUrl($input), - ShortUrlInputFilter::VALID_SINCE => $this->validSince($input), - ShortUrlInputFilter::VALID_UNTIL => $this->validUntil($input), - ShortUrlInputFilter::MAX_VISITS => $this->maxVisits($input), - ShortUrlInputFilter::TAGS => $this->tags($input), - ShortUrlInputFilter::CRAWLABLE => $this->crawlable($input), - ShortUrlInputFilter::FORWARD_QUERY => !$this->noForwardQuery($input), -// ShortUrlInputFilter::TITLE => TODO, - ]); + return ShortUrlEdition::fromRawData($this->getCommonData($input)); } - // TODO - // public function toShortUrlCreation(InputInterface $input) + public function toShortUrlCreation( + InputInterface $input, + UrlShortenerOptions $options, + string $customSlugField, + string $shortCodeLengthField, + string $pathPrefixField, + string $findIfExistsField, + string $domainField, + ): ShortUrlCreation { + $shortCodeLength = $input->getOption($shortCodeLengthField) ?? $options->defaultShortCodesLength; + return ShortUrlCreation::fromRawData([ + ...$this->getCommonData($input), + ShortUrlInputFilter::CUSTOM_SLUG => $input->getOption($customSlugField), + ShortUrlInputFilter::SHORT_CODE_LENGTH => $shortCodeLength, + ShortUrlInputFilter::PATH_PREFIX => $input->getOption($pathPrefixField), + ShortUrlInputFilter::FIND_IF_EXISTS => $input->getOption($findIfExistsField), + ShortUrlInputFilter::DOMAIN => $input->getOption($domainField), + ], $options); + } + + private function getCommonData(InputInterface $input): array + { + $longUrl = $this->longUrlAsOption ? $input->getOption('long-url') : $input->getArgument('longUrl'); + $tags = array_unique(flatten(array_map(splitByComma(...), $input->getOption('tags')))); + $maxVisits = $input->getOption('max-visits'); + + return [ + ShortUrlInputFilter::LONG_URL => $longUrl, + ShortUrlInputFilter::VALID_SINCE => $input->getOption('valid-since'), + ShortUrlInputFilter::VALID_UNTIL => $input->getOption('valid-until'), + ShortUrlInputFilter::MAX_VISITS => $maxVisits !== null ? (int) $maxVisits : null, + ShortUrlInputFilter::TAGS => $tags, + ShortUrlInputFilter::TITLE => $input->getOption('title'), + ShortUrlInputFilter::CRAWLABLE => $input->getOption('crawlable'), + ShortUrlInputFilter::FORWARD_QUERY => !$input->getOption('no-forward-query'), + ]; + } }