ldc-arcadia-api/src/Presentation/Console/Commands/ToggleClientStatusCommand.php

210 lines
7.2 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
namespace Presentation\Console\Commands;
use Application\Security\Exceptions\ClientNotFoundException;
use Application\Security\Models\Entities\Client;
use Application\Security\Models\ValueObjects\ClientIdentifier;
use Application\Security\Ports\Repositories\ClientRepository;
use Application\Security\Usecases\FindClient\FindClient;
use Application\Security\Usecases\FindClient\Input as FindClientInput;
use Application\Security\Usecases\ToggleClientStatus\Input;
use Application\Security\Usecases\ToggleClientStatus\SetClientStatus;
use Illuminate\Console\Command;
use Presentation\Console\Presenters\Clients\FindClientPresenter;
use Presentation\Console\Presenters\Clients\FindClientView;
use Presentation\Console\Presenters\Clients\FoundClientItem;
use Presentation\Console\Presenters\Clients\SetClientStatusPresenter;
use Shared\DDD\Usecase\Builder\UsecaseBuilder;
use Shared\DDD\Usecase\Builder\UsecaseExecutor;
use Shared\Pagination;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Completion\Suggestion;
final class ToggleClientStatusCommand extends Command
{
/**
* @var string
*/
protected $signature = 'client:toggle-status
{identifier? : The identifier of the client}
{status? : The desired status of the client (activate|deactivate)}';
/**
* @var string
*/
protected $description = 'Toggle the status of an API client';
public function __construct(
private readonly ClientRepository $clientRepository,
) {
parent::__construct();
}
#[\Override]
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
parent::complete($input, $suggestions);
if ($input->mustSuggestArgumentValuesFor('identifier')) {
$clients = $this->clientRepository->findAll(Pagination::ofOffsetAndLimit(0, 100));
/** @var list<Suggestion> $items */
$items = array_map(
static fn (Client $client): Suggestion => new Suggestion(
value: $client->identifier->value,
description: sprintf('%s (%s)', $client->name->value, $client->isActive ? 'Active' : 'Inactive'),
),
$clients->getItems(),
);
$suggestions->suggestValues($items);
}
if ($input->mustSuggestArgumentValuesFor('status')) {
$suggestions->suggestValues([
new Suggestion('activate', 'Activate the client'),
new Suggestion('deactivate', 'Deactivate the client'),
]);
}
}
public function handle(
SetClientStatus $usecase,
FindClient $findClient,
UsecaseExecutor $executor,
): int {
$identifierString = $this->resolveIdentifier($findClient, $executor);
if ($identifierString === null) {
return self::FAILURE;
}
try {
$clientIdentifier = $this->parseClientIdentifier($identifierString, $findClient, $executor);
$desiredStatus = $this->resolveDesiredStatus($clientIdentifier);
$input = new Input($clientIdentifier, $desiredStatus);
$view = UsecaseBuilder::using($executor)
->do($usecase)
->from($input)
->with(new SetClientStatusPresenter())
->run();
$this->info($view->message);
return self::SUCCESS;
} catch (ClientNotFoundException | \InvalidArgumentException $e) {
$this->error($e->getMessage());
return self::FAILURE;
}
}
private function resolveIdentifier(FindClient $findClient, UsecaseExecutor $executor): ?string
{
$raw = $this->argument('identifier');
if ($raw !== null) {
return (string) $raw;
}
/** @var string|null $answer */
$answer = $this->anticipate(
'Search client (by name, ID, identifier, or slug):',
function (string $input) use ($findClient, $executor): array {
$trimmed = trim($input);
if ($trimmed === '') {
return [];
}
try {
/** @var FindClientView $view */
$view = UsecaseBuilder::using($executor)
->do($findClient)
->from(new FindClientInput($trimmed))
->with(new FindClientPresenter())
->run();
return array_map(
static fn (FoundClientItem $item): string => $item->identifier,
$view->clients,
);
} catch (\Throwable) {
return [];
}
}
);
if ($answer === null || trim($answer) === '') {
$this->error('No client specified.');
return null;
}
return trim($answer);
}
/**
* @throws \InvalidArgumentException
*/
private function parseClientIdentifier(
string $value,
FindClient $findClient,
UsecaseExecutor $executor,
): ClientIdentifier {
try {
return new ClientIdentifier($value);
} catch (\InvalidArgumentException) {
try {
/** @var FindClientView $view */
$view = UsecaseBuilder::using($executor)
->do($findClient)
->from(new FindClientInput($value))
->with(new FindClientPresenter())
->run();
if (count($view->clients) === 1) {
return new ClientIdentifier($view->clients[0]->identifier);
}
if (count($view->clients) > 1) {
foreach ($view->clients as $found) {
if (
strcasecmp($found->name, $value) === 0 || strcasecmp($found->id, $value) === 0
) {
return new ClientIdentifier($found->identifier);
}
}
}
} catch (\Throwable) {
}
throw new \InvalidArgumentException("Invalid client identifier '{$value}'. Expected a valid client identifier.");
}
}
private function resolveDesiredStatus(ClientIdentifier $clientIdentifier): bool
{
$status = $this->argument('status') !== null ? (string) $this->argument('status') : null;
return match ($status) {
'activate' => true,
'deactivate' => false,
null => $this->resolveToggledStatus($clientIdentifier),
default => throw new \InvalidArgumentException("Invalid status '{$status}'. Expected 'activate' or 'deactivate'."),
};
}
private function resolveToggledStatus(ClientIdentifier $clientIdentifier): bool
{
$client = $this->clientRepository->findByIdentifier($clientIdentifier)
?? throw ClientNotFoundException::forClient($clientIdentifier->value);
return ! $client->isActive;
}
}