Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions docs/installation/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,19 @@ This app brings custom commands:
```text
app:admin:password Set password of admin user
app:alias:delete Delete an alias
app:api-token:create Create a new API token with specified name and scopes
app:api-token:delete Delete an API token by its plain token
app:domain:delete Delete a domain and all associated data (users, aliases, vouchers)
app:metrics Global Metrics for Userli
app:openpgp:delete-key Delete OpenPGP key for email
app:openpgp:import-key Import OpenPGP key for email
app:openpgp:show-key Show OpenPGP key of email
app:report:weekly Send weekly report to all admins
app:users:delete Delete a user
app:users:list List users
app:users:mailcrypt Get MailCrypt values for user
app:users:quota Get quota of user if set
app:users:registration:mail Send a registration mail to a user
app:users:remove Removes all mailboxes from deleted users
app:users:reset Reset a user
app:users:restore Reset a user
app:users:restore Restore a user
app:voucher:count Get count of vouchers for a specific user
app:voucher:create Create voucher for a specific user
app:voucher:unlink Remove connection between vouchers and accounts after 3 months
```

Get more information about each command by running:
Expand Down
84 changes: 0 additions & 84 deletions src/Command/AbstractUsersCommand.php

This file was deleted.

62 changes: 31 additions & 31 deletions src/Command/AdminPasswordCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,49 @@

namespace App\Command;

use App\Exception\PasswordMismatchException;
use App\Exception\PasswordPolicyException;
use App\Helper\AdminPasswordUpdater;
use Override;
use App\Service\ConsolePasswordHelper;
use Symfony\Component\Console\Attribute\Argument;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;

#[AsCommand(name: 'app:admin:password', description: 'Set password of admin user', help: <<<'TXT'
Set password of admin user. Create primary user and domain if not created before.
TXT)]
final class AdminPasswordCommand extends Command
#[AsCommand(
name: 'app:admin:password',
description: 'Set password of admin user',
help: 'Set password of admin user. Create primary user and domain if do not exist.'
)]
final readonly class AdminPasswordCommand
{
/**
* AdminPasswordCommand constructor.
*/
public function __construct(private readonly AdminPasswordUpdater $updater)
{
parent::__construct();
public function __construct(
private AdminPasswordUpdater $updater,
private ConsolePasswordHelper $consolePasswordHelper,
) {
}

#[Override]
protected function configure(): void
{
$this
->addArgument('password', InputArgument::OPTIONAL, 'Admin password');
}

#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$password = $input->getArgument('password');
if (null === $password) {
$helper = $this->getHelper('question');
assert($helper instanceof QuestionHelper);
$question = new Question('Please enter new admin password:');
$password = $helper->ask($input, $output, $question);
public function __invoke(
#[Argument(description: 'Admin password (omit for interactive prompt)')]
?string $password = null,
?InputInterface $input = null,
?OutputInterface $output = null,
): int {
try {
if (null !== $password) {
$this->consolePasswordHelper->validatePassword($password);
} else {
$password = $this->consolePasswordHelper->askForPassword($input, $output);
}
} catch (PasswordPolicyException|PasswordMismatchException $e) {
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));

return Command::FAILURE;
}

$this->updater->updateAdminPassword($password);

return 0;
return Command::SUCCESS;
}
}
53 changes: 23 additions & 30 deletions src/Command/AliasDeleteCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,61 +4,54 @@

namespace App\Command;

use App\Entity\Alias;
use App\Entity\User;
use App\Handler\DeleteHandler;
use Doctrine\ORM\EntityManagerInterface;
use Override;
use App\Repository\AliasRepository;
use App\Repository\UserRepository;
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;

#[AsCommand(name: 'app:alias:delete', description: 'Delete an alias')]
final class AliasDeleteCommand extends Command
final readonly class AliasDeleteCommand
{
public function __construct(private readonly EntityManagerInterface $manager, private readonly DeleteHandler $deleteHandler)
{
parent::__construct();
public function __construct(
private UserRepository $userRepository,
private AliasRepository $aliasRepository,
private DeleteHandler $deleteHandler,
) {
}

#[Override]
protected function configure(): void
{
$this
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'User who owns the alias (optional)')
->addOption('alias', 'a', InputOption::VALUE_REQUIRED, 'Alias address to delete')
->addOption('dry-run', null, InputOption::VALUE_NONE);
}

#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$email = $input->getOption('user');
$source = $input->getOption('alias');

public function __invoke(
#[Option(name: 'alias', description: 'Alias address to delete', shortcut: 'a')]
?string $source = null,
#[Option(name: 'user', description: 'User who owns the alias (optional)', shortcut: 'u')]
?string $email = null,
#[Option(name: 'dry-run', description: 'Show what would be deleted without actually deleting')]
bool $dryRun = false,
?OutputInterface $output = null,
): int {
$user = null;
if ($email && null === $user = $this->manager->getRepository(User::class)->findByEmail($email)) {
if ($email && null === $user = $this->userRepository->findByEmail($email)) {
$output->writeln(sprintf("<error>User with email '%s' not found!</error>", $email));

return Command::FAILURE;
}

if (empty($source) || null === $alias = $this->manager->getRepository(Alias::class)->findOneBySource($source)) {
if (empty($source) || null === $alias = $this->aliasRepository->findOneBySource($source)) {
$output->writeln(sprintf("<error>Alias with address '%s' not found!</error>", $source));

return Command::FAILURE;
}

if ($input->getOption('dry-run')) {
if ($user) {
if ($dryRun) {
if ($user !== null) {
$output->write(sprintf("Would delete alias %s of user %s\n", $source, $email));
} else {
$output->write(sprintf("Would delete alias %s\n", $source));
}
} else {
if ($user) {
if ($user !== null) {
$output->write(sprintf("Deleting alias %s of user %s\n", $source, $email));
$this->deleteHandler->deleteAlias($alias, $user);
} else {
Expand Down
41 changes: 11 additions & 30 deletions src/Command/ApiTokenCreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,50 +8,31 @@
use App\Form\Model\ApiToken as ApiTokenModel;
use App\Service\ApiTokenManager;
use Exception;
use Override;
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 Symfony\Component\Validator\Validator\ValidatorInterface;

#[AsCommand(
name: 'app:api-token:create',
description: 'Create a new API token with specified name and scopes'
)]
final class ApiTokenCreateCommand extends Command
final readonly class ApiTokenCreateCommand
{
public function __construct(
private readonly ApiTokenManager $apiTokenManager,
private readonly ValidatorInterface $validator,
private ApiTokenManager $apiTokenManager,
private ValidatorInterface $validator,
) {
parent::__construct();
}

#[Override]
protected function configure(): void
{
$this
->addOption('name', 't', InputOption::VALUE_REQUIRED, 'Name for the API token')
->addOption(
'scopes',
's',
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Scopes for the API token (available: '.implode(', ', ApiScope::all()).')',
[]
);
}

#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

$name = (string) $input->getOption('name');
$scopes = (array) $input->getOption('scopes');

public function __invoke(
#[Option(description: 'Name for the API token', shortcut: 't')]
string $name = '',
#[Option(description: 'Scopes for the API token (available: '.ApiScope::ALL_SCOPES_DESCRIPTION.')', shortcut: 's')]
array $scopes = [],
?SymfonyStyle $io = null,
): int {
$model = new ApiTokenModel();
$model->setName($name);
$model->setScopes($scopes);
Expand Down
29 changes: 9 additions & 20 deletions src/Command/ApiTokenDeleteCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,29 @@

use App\Service\ApiTokenManager;
use Exception;
use Override;
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;

#[AsCommand(
name: 'app:api-token:delete',
description: 'Delete an API token by its plain token'
)]
final class ApiTokenDeleteCommand extends Command
final readonly class ApiTokenDeleteCommand
{
public function __construct(
private readonly ApiTokenManager $apiTokenManager,
private ApiTokenManager $apiTokenManager,
) {
parent::__construct();
}

#[Override]
protected function configure(): void
{
$this
->addOption('token', 't', InputOption::VALUE_REQUIRED, 'The plain API token to delete');
}

#[Override]
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

public function __invoke(
#[Option(description: 'The plain API token to delete', shortcut: 't')]
?string $token = null,
?SymfonyStyle $io = null,
): int {
try {
$plainToken = (string) $input->getOption('token');
$plainToken = (string) $token;
$apiToken = $this->apiTokenManager->findOne($plainToken);

if ($apiToken === null) {
Expand Down
Loading
Loading