Create command to edit existing short URLs

This commit is contained in:
Alejandro Celaya 2024-07-26 09:20:03 +02:00
parent 8917ed5c2e
commit 5bccdded8a
4 changed files with 73 additions and 82 deletions

View file

@ -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,

View file

@ -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: <info>%s</info>', $longUrl),
sprintf('Processed long URL: <info>%s</info>', $result->shortUrl->getLongUrl()),
sprintf('Generated short URL: <info>%s</info>', $this->stringifier->stringify($result->shortUrl)),
]);
return ExitCode::EXIT_SUCCESS;

View file

@ -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;
}
}

View file

@ -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'),
];
}
}