Skip to content

Commit

Permalink
feat(Users): Added users:inactive console command to list and purge…
Browse files Browse the repository at this point in the history
… never logging-in users with Role filtering
  • Loading branch information
roadiz-ci committed Sep 23, 2024
1 parent c0bf179 commit d384ddc
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
138 changes: 138 additions & 0 deletions src/Console/UsersInactiveCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?php

declare(strict_types=1);

namespace RZ\Roadiz\CoreBundle\Console;

use Doctrine\Persistence\ManagerRegistry;
use RZ\Roadiz\CoreBundle\Entity\User;
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;

final class UsersInactiveCommand extends Command
{
public function __construct(private readonly ManagerRegistry $managerRegistry, string $name = null)
{
parent::__construct($name);
}

protected function configure(): void
{
$this
->setName('users:inactive')
->setDescription('List users that did not logged-in for <info>30</info> days.')
->addOption(
'days',
'd',
InputOption::VALUE_REQUIRED,
'Number of days since last login.',
30
)
->addOption(
'role',
'r',
InputOption::VALUE_REQUIRED,
'List users <info>with</info> a specific role.',
null
)
->addOption(
'missing-role',
'm',
InputOption::VALUE_REQUIRED,
'List users <info>without</info> a specific role. Can be combined with --role.',
null
)
->addOption(
'purge',
null,
InputOption::VALUE_NONE,
'Purge and delete inactive users <info>with a specific role</info>, <error>destructive action</error>.'
)
;
}

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

$daysCount = $input->getOption('days');
if (!\is_numeric($daysCount) || $daysCount < 1) {
$io->error('Days option must be a positive number.');
return 1;
}

$sinceDate = new \DateTimeImmutable("-$daysCount days");

$inactiveUsers = $this->managerRegistry
->getRepository(User::class)
->findAllInactiveSinceDays(
(int) $daysCount
)
;

$filteringRole = $input->getOption('role');
if (\is_string($filteringRole) && !empty(trim($filteringRole))) {
$inactiveUsers = array_filter($inactiveUsers, function (User $user) use ($filteringRole) {
return \in_array($filteringRole, $user->getRoles(), true);
});
}

$missingRole = $input->getOption('missing-role');
if (\is_string($missingRole) && !empty(trim($missingRole))) {
$inactiveUsers = array_filter($inactiveUsers, function (User $user) use ($missingRole) {
return !\in_array($missingRole, $user->getRoles(), true);
});
}

$io->success(sprintf(
'%d inactive users since %s.',
count($inactiveUsers),
$sinceDate->format('Y-m-d')
));

if ($output->isVerbose() && count($inactiveUsers) > 0) {
$io->table(
['ID', 'Username', 'Last login', 'Created at'],
array_map(function (User $user) {
return [
$user->getId(),
$user->getUsername(),
$user->getLastLogin()?->format('Y-m-d H:i:s') ?? 'Never',
$user->getCreatedAt()?->format('Y-m-d H:i:s') ?? 'Never',
];
}, $inactiveUsers)
);
}

$purge = $input->getOption('purge');
if (!$purge || count($inactiveUsers) === 0) {
return 0;
}

if (!\is_string($filteringRole) || empty(trim($filteringRole))) {
$io->error(sprintf(
'You cannot purge inactive users since %s without filtering them by a ROLE name.',
$sinceDate->format('Y-m-d')
));
return 1;
}

if ($input->isInteractive() && !$io->confirm('Do you want to delete these users?')) {
$io->comment('No user has been deleted.');
return 0;
}

foreach ($inactiveUsers as $user) {
$this->managerRegistry->getManager()->remove($user);
}
$this->managerRegistry->getManager()->flush();
$io->success(sprintf(
'%d inactive users have been deleted.',
count($inactiveUsers)
));
return 0;
}
}
23 changes: 23 additions & 0 deletions src/Repository/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,27 @@ public function emailExists(string $email): bool

return (bool) $qb->getQuery()->getSingleScalarResult();
}

/**
* Find all users that did not logged-in since a number of days, including users that never logged-in using
* their creation date.
*
* @param int $days
* @return User[]
* @throws \Exception
*/
public function findAllInactiveSinceDays(int $days): array
{
$qb = $this->createQueryBuilder('u');
$qb->andWhere($qb->expr()->orX(
// If user never logged in, we compare with creation date
$qb->expr()->andX(
$qb->expr()->isNull('u.lastLogin'),
$qb->expr()->lt('u.createdAt', ':lastLogin')
),
$qb->expr()->lt('u.lastLogin', ':lastLogin'),
))->setParameter('lastLogin', new \DateTimeImmutable('-' . $days . ' days'));

return $qb->getQuery()->getResult();
}
}

0 comments on commit d384ddc

Please sign in to comment.