* SPDX-License-Identifier: AGPL-3.0-or-later */ namespace KTXM\ProviderImapMail\Console; use KTXM\ProviderImapMail\Providers\Provider; use KTXM\ProviderImapMail\Providers\Service; use KTXC\SessionTenant; 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\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; /** * Remove a stored IMAP service connection. * * Looks up the service by its ID (or prompts for one), confirms with the * operator, then permanently deletes the service document from the store. * * Usage: * bin/console provider_imap_mail:service:disconnect --tenant=t1 --user=u1 */ #[AsCommand( name: 'provider_imap_mail:service:disconnect', description: 'Remove a stored IMAP service connection', )] class ServiceDisconnectCommand extends Command { public function __construct( private readonly Provider $provider, private readonly SessionTenant $sessionTenant, ) { parent::__construct(); } protected function configure(): void { $this ->addArgument('service-id', InputArgument::OPTIONAL, 'Service ID to remove') ->addOption('tenant', 't', InputOption::VALUE_REQUIRED, 'Tenant ID') ->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'User ID') ->addOption('force', 'f', InputOption::VALUE_NONE, 'Skip confirmation prompt') ->setHelp(<<<'HELP' The provider_imap_mail:service:disconnect command permanently removes a stored IMAP service configuration from the store. Examples: Interactive (lists services and prompts for ID): bin/console provider_imap_mail:service:disconnect --tenant=t1 --user=u1 Direct, with confirmation: bin/console provider_imap_mail:service:disconnect abc123 --tenant=t1 --user=u1 Skip confirmation: bin/console provider_imap_mail:service:disconnect abc123 --tenant=t1 --user=u1 --force HELP); } protected function interact(InputInterface $input, OutputInterface $output): void { $io = new SymfonyStyle($input, $output); if (!$input->getOption('tenant')) { $tenant = $io->ask('Tenant ID'); if ($tenant) $input->setOption('tenant', $tenant); } if (!$input->getOption('user')) { $user = $io->ask('User ID'); if ($user) $input->setOption('user', $user); } // If no service ID given, list available services and let the operator pick if (!$input->getArgument('service-id')) { $tenantId = (string) ($input->getOption('tenant') ?? ''); $userId = (string) ($input->getOption('user') ?? ''); if ($tenantId !== '' && $userId !== '') { $this->sessionTenant->configureById($tenantId); $services = $this->provider->serviceList($tenantId, $userId); if (empty($services)) { // nothing to select — let execute() handle the error return; } $choices = []; foreach ($services as $id => $service) { $label = $service instanceof Service ? ($service->getLabel() ?? $id) : $id; $choices[$id] = "{$label} [{$id}]"; } $chosen = $io->choice('Select service to disconnect', array_values($choices)); // resolve the chosen label back to its key $serviceId = (string) array_search($chosen, $choices, true); if ($serviceId !== '') { $input->setArgument('service-id', $serviceId); } } } } protected function execute(InputInterface $input, OutputInterface $output): int { $io = new SymfonyStyle($input, $output); $tenantId = (string) ($input->getOption('tenant') ?? ''); $userId = (string) ($input->getOption('user') ?? ''); $serviceId = (string) ($input->getArgument('service-id') ?? ''); $force = (bool) $input->getOption('force'); $errors = []; if ($tenantId === '') $errors[] = 'Tenant ID is required (--tenant).'; if ($userId === '') $errors[] = 'User ID is required (--user).'; if ($serviceId === '') $errors[] = 'Service ID is required.'; if (!empty($errors)) { $io->error($errors); return Command::FAILURE; } // ── Fetch service for display ──────────────────────────────────────── $this->sessionTenant->configureById($tenantId); $service = $this->provider->serviceFetch($tenantId, $userId, $serviceId); if ($service === null) { $io->error("Service '{$serviceId}' not found."); return Command::FAILURE; } $label = $service->getLabel() ?? $serviceId; $host = $service->getLocation()?->getHost() ?? 'unknown'; $io->title('Disconnect IMAP Service'); $io->definitionList( ['ID' => $serviceId], ['Label' => $label], ['Host' => $host], ); // ── Confirmation ───────────────────────────────────────────────────── if (!$force) { $confirm = $io->confirm( "Permanently remove service {$label} ({$serviceId})?", false ); if (!$confirm) { $io->note('Aborted.'); return Command::SUCCESS; } } // ── Destroy ────────────────────────────────────────────────────────── $deleted = $this->provider->serviceDestroy($tenantId, $userId, $service); if (!$deleted) { $io->error("Failed to remove service '{$serviceId}'."); return Command::FAILURE; } $io->success("Service '{$label}' ({$serviceId}) has been removed."); return Command::SUCCESS; } }