mirror of
https://github.com/shlinkio/shlink.git
synced 2026-02-28 04:03:12 +08:00
Merge pull request #2452 from acelaya-forks/feature/invokable-command-poc
Use invokable commands approach on some API console commands
This commit is contained in:
@@ -54,11 +54,11 @@
|
||||
"spiral/roadrunner-cli": "^2.7",
|
||||
"spiral/roadrunner-http": "^3.5",
|
||||
"spiral/roadrunner-jobs": "^4.6",
|
||||
"symfony/console": "^7.2",
|
||||
"symfony/filesystem": "^7.2",
|
||||
"symfony/console": "^7.3",
|
||||
"symfony/filesystem": "^7.3",
|
||||
"symfony/lock": "7.1.6",
|
||||
"symfony/process": "^7.2",
|
||||
"symfony/string": "^7.2"
|
||||
"symfony/process": "^7.3",
|
||||
"symfony/string": "^7.3"
|
||||
},
|
||||
"require-dev": {
|
||||
"devizzent/cebe-php-openapi": "^1.1.2",
|
||||
@@ -73,7 +73,7 @@
|
||||
"roave/security-advisories": "dev-master",
|
||||
"shlinkio/php-coding-standard": "~2.4.2",
|
||||
"shlinkio/shlink-test-utils": "^4.3.1",
|
||||
"symfony/var-dumper": "^7.2",
|
||||
"symfony/var-dumper": "^7.3",
|
||||
"veewee/composer-run-parallel": "^1.4"
|
||||
},
|
||||
"conflict": {
|
||||
|
||||
@@ -7,16 +7,40 @@ namespace Shlinkio\Shlink\CLI\Command\Api;
|
||||
use Shlinkio\Shlink\Common\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
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\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\map;
|
||||
use function sprintf;
|
||||
|
||||
#[AsCommand(
|
||||
name: DisableKeyCommand::NAME,
|
||||
description: 'Disables an API key by name or plain-text key (providing a plain-text key is DEPRECATED)',
|
||||
help: <<<HELP
|
||||
The <info>%command.name%</info> command allows you to disable an existing API key, via its name or the
|
||||
plain-text key.
|
||||
|
||||
If no arguments are provided, you will be prompted to select one of the existing non-disabled API keys.
|
||||
|
||||
<info>%command.full_name%</info>
|
||||
|
||||
You can optionally pass the API key name to be disabled. In that case <comment>--by-name</comment> is also
|
||||
required, to indicate the first argument is the API key name and not the plain-text key:
|
||||
|
||||
<info>%command.full_name% the_key_name --by-name</info>
|
||||
|
||||
You can pass the plain-text key to be disabled, but that is <options=bold>DEPRECATED</>. In next major version,
|
||||
the argument will always be assumed to be the name:
|
||||
|
||||
<info>%command.full_name% d6b6c60e-edcd-4e43-96ad-fa6b7014c143</info>
|
||||
|
||||
HELP,
|
||||
)]
|
||||
class DisableKeyCommand extends Command
|
||||
{
|
||||
public const string NAME = 'api-key:disable';
|
||||
@@ -26,47 +50,9 @@ class DisableKeyCommand extends Command
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$help = <<<HELP
|
||||
The <info>%command.name%</info> command allows you to disable an existing API key, via its name or the
|
||||
plain-text key.
|
||||
|
||||
If no arguments are provided, you will be prompted to select one of the existing non-disabled API keys.
|
||||
|
||||
<info>%command.full_name%</info>
|
||||
|
||||
You can optionally pass the API key name to be disabled. In that case <comment>--by-name</comment> is also
|
||||
required, to indicate the first argument is the API key name and not the plain-text key:
|
||||
|
||||
<info>%command.full_name% the_key_name --by-name</info>
|
||||
|
||||
You can pass the plain-text key to be disabled, but that is <options=bold>DEPRECATED</>. In next major version,
|
||||
the argument will always be assumed to be the name:
|
||||
|
||||
<info>%command.full_name% d6b6c60e-edcd-4e43-96ad-fa6b7014c143</info>
|
||||
|
||||
HELP;
|
||||
|
||||
$this
|
||||
->setName(self::NAME)
|
||||
->setDescription('Disables an API key by name or plain-text key (providing a plain-text key is DEPRECATED)')
|
||||
->addArgument(
|
||||
'keyOrName',
|
||||
InputArgument::OPTIONAL,
|
||||
'The API key to disable. Pass `--by-name` to indicate this value is the name and not the key.',
|
||||
)
|
||||
->addOption(
|
||||
'by-name',
|
||||
mode: InputOption::VALUE_NONE,
|
||||
description: 'Indicates the first argument is the API key name, not the plain-text key.',
|
||||
)
|
||||
->setHelp($help);
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$keyOrName = $input->getArgument('keyOrName');
|
||||
$keyOrName = $input->getArgument('key-or-name');
|
||||
|
||||
if ($keyOrName === null) {
|
||||
$apiKeys = $this->apiKeyService->listKeys(enabledOnly: true);
|
||||
@@ -75,18 +61,21 @@ class DisableKeyCommand extends Command
|
||||
map($apiKeys, static fn (ApiKey $apiKey) => $apiKey->name),
|
||||
);
|
||||
|
||||
$input->setArgument('keyOrName', $name);
|
||||
$input->setArgument('key-or-name', $name);
|
||||
$input->setOption('by-name', true);
|
||||
}
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$keyOrName = $input->getArgument('keyOrName');
|
||||
$byName = $input->getOption('by-name');
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
if (! $keyOrName) {
|
||||
public function __invoke(
|
||||
SymfonyStyle $io,
|
||||
#[Argument(
|
||||
description: 'The API key to disable. Pass `--by-name` to indicate this value is the name and not the key.',
|
||||
)]
|
||||
string|null $keyOrName = null,
|
||||
#[Option(description: 'Indicates the first argument is the API key name, not the plain-text key.')]
|
||||
bool $byName = false,
|
||||
): int {
|
||||
if ($keyOrName === null) {
|
||||
$io->warning('An API key name was not provided.');
|
||||
return Command::INVALID;
|
||||
}
|
||||
|
||||
@@ -8,16 +8,20 @@ use Shlinkio\Shlink\CLI\Util\ShlinkTable;
|
||||
use Shlinkio\Shlink\Rest\ApiKey\Role;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Attribute\Option;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function implode;
|
||||
use function sprintf;
|
||||
|
||||
#[AsCommand(
|
||||
name: ListKeysCommand::NAME,
|
||||
description: 'Lists all the available API keys.',
|
||||
)]
|
||||
class ListKeysCommand extends Command
|
||||
{
|
||||
private const string ERROR_STRING_PATTERN = '<fg=red>%s</>';
|
||||
@@ -31,23 +35,14 @@ class ListKeysCommand extends Command
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName(self::NAME)
|
||||
->setDescription('Lists all the available API keys.')
|
||||
->addOption(
|
||||
'enabled-only',
|
||||
'e',
|
||||
InputOption::VALUE_NONE,
|
||||
'Tells if only enabled API keys should be returned.',
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$enabledOnly = $input->getOption('enabled-only');
|
||||
|
||||
public function __invoke(
|
||||
SymfonyStyle $io,
|
||||
#[Option(
|
||||
description: 'Tells if only enabled API keys should be returned.',
|
||||
shortcut: 'e',
|
||||
)]
|
||||
bool $enabledOnly = false,
|
||||
): int {
|
||||
$rows = array_map(function (ApiKey $apiKey) use ($enabledOnly) {
|
||||
$expiration = $apiKey->expirationDate;
|
||||
$messagePattern = $this->determineMessagePattern($apiKey);
|
||||
@@ -65,7 +60,7 @@ class ListKeysCommand extends Command
|
||||
return $rowData;
|
||||
}, $this->apiKeyService->listKeys($enabledOnly));
|
||||
|
||||
ShlinkTable::withRowSeparators($output)->render(array_filter([
|
||||
ShlinkTable::withRowSeparators($io)->render(array_filter([
|
||||
'Name',
|
||||
! $enabledOnly ? 'Is enabled' : null,
|
||||
'Expiration date',
|
||||
|
||||
@@ -8,14 +8,19 @@ use Shlinkio\Shlink\Core\Exception\InvalidArgumentException;
|
||||
use Shlinkio\Shlink\Core\Model\Renaming;
|
||||
use Shlinkio\Shlink\Rest\Entity\ApiKey;
|
||||
use Shlinkio\Shlink\Rest\Service\ApiKeyServiceInterface;
|
||||
use Symfony\Component\Console\Attribute\Argument;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\map;
|
||||
|
||||
#[AsCommand(
|
||||
name: RenameApiKeyCommand::NAME,
|
||||
description: 'Renames an API key by name',
|
||||
)]
|
||||
class RenameApiKeyCommand extends Command
|
||||
{
|
||||
public const string NAME = 'api-key:rename';
|
||||
@@ -25,20 +30,11 @@ class RenameApiKeyCommand extends Command
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName(self::NAME)
|
||||
->setDescription('Renames an API key by name')
|
||||
->addArgument('oldName', InputArgument::REQUIRED, 'Current name of the API key to rename')
|
||||
->addArgument('newName', InputArgument::REQUIRED, 'New name to set to the API key');
|
||||
}
|
||||
|
||||
protected function interact(InputInterface $input, OutputInterface $output): void
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$oldName = $input->getArgument('oldName');
|
||||
$newName = $input->getArgument('newName');
|
||||
$oldName = $input->getArgument('old-name');
|
||||
$newName = $input->getArgument('new-name');
|
||||
|
||||
if ($oldName === null) {
|
||||
$apiKeys = $this->apiKeyService->listKeys();
|
||||
@@ -47,7 +43,7 @@ class RenameApiKeyCommand extends Command
|
||||
map($apiKeys, static fn (ApiKey $apiKey) => $apiKey->name),
|
||||
);
|
||||
|
||||
$input->setArgument('oldName', $requestedOldName);
|
||||
$input->setArgument('old-name', $requestedOldName);
|
||||
}
|
||||
|
||||
if ($newName === null) {
|
||||
@@ -58,16 +54,15 @@ class RenameApiKeyCommand extends Command
|
||||
: throw new InvalidArgumentException('The new name cannot be empty'),
|
||||
);
|
||||
|
||||
$input->setArgument('newName', $requestedNewName);
|
||||
$input->setArgument('new-name', $requestedNewName);
|
||||
}
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$oldName = $input->getArgument('oldName');
|
||||
$newName = $input->getArgument('newName');
|
||||
|
||||
public function __invoke(
|
||||
SymfonyStyle $io,
|
||||
#[Argument(description: 'Current name of the API key to rename')] string $oldName,
|
||||
#[Argument(description: 'New name to set to the API key')] string $newName,
|
||||
): int {
|
||||
$this->apiKeyService->renameApiKey(Renaming::fromNames($oldName, $newName));
|
||||
$io->success('API key properly renamed');
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class DisableKeyCommandTest extends TestCase
|
||||
$this->apiKeyService->expects($this->never())->method('disableByName');
|
||||
|
||||
$exitCode = $this->commandTester->execute([
|
||||
'keyOrName' => $apiKey,
|
||||
'key-or-name' => $apiKey,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
@@ -51,7 +51,7 @@ class DisableKeyCommandTest extends TestCase
|
||||
$this->apiKeyService->expects($this->never())->method('disableByKey');
|
||||
|
||||
$exitCode = $this->commandTester->execute([
|
||||
'keyOrName' => $name,
|
||||
'key-or-name' => $name,
|
||||
'--by-name' => true,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
@@ -71,7 +71,7 @@ class DisableKeyCommandTest extends TestCase
|
||||
$this->apiKeyService->expects($this->never())->method('disableByName');
|
||||
|
||||
$exitCode = $this->commandTester->execute([
|
||||
'keyOrName' => $apiKey,
|
||||
'key-or-name' => $apiKey,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
@@ -90,7 +90,7 @@ class DisableKeyCommandTest extends TestCase
|
||||
$this->apiKeyService->expects($this->never())->method('disableByKey');
|
||||
|
||||
$exitCode = $this->commandTester->execute([
|
||||
'keyOrName' => $name,
|
||||
'key-or-name' => $name,
|
||||
'--by-name' => true,
|
||||
]);
|
||||
$output = $this->commandTester->getDisplay();
|
||||
|
||||
@@ -43,7 +43,7 @@ class RenameApiKeyCommandTest extends TestCase
|
||||
|
||||
$this->commandTester->setInputs([$oldName]);
|
||||
$this->commandTester->execute([
|
||||
'newName' => $newName,
|
||||
'new-name' => $newName,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class RenameApiKeyCommandTest extends TestCase
|
||||
|
||||
$this->commandTester->setInputs([$newName]);
|
||||
$this->commandTester->execute([
|
||||
'oldName' => $oldName,
|
||||
'old-name' => $oldName,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -76,8 +76,8 @@ class RenameApiKeyCommandTest extends TestCase
|
||||
);
|
||||
|
||||
$this->commandTester->execute([
|
||||
'oldName' => $oldName,
|
||||
'newName' => $newName,
|
||||
'old-name' => $oldName,
|
||||
'new-name' => $newName,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user