feat: configurable recipient list for notifications of role nominations

This commit is contained in:
2026-09-16 09:21:04 +02:00
parent 6a8328a564
commit 5d7ffd7d58
5 changed files with 22 additions and 50 deletions
+6 -12
View File
@@ -14,7 +14,7 @@ use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
/**
* Tells the administrators that an account is waiting for a role to be approved.
* Tells the configured recipients that an account is waiting for a role to be approved.
*
* Runs off the request: mail is routed sync in this application, so sending it inline would put
* SMTP latency and SMTP failures into the login path.
@@ -22,11 +22,15 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
#[AsMessageHandler]
final class RoleNominationHandler
{
/**
* @param string[] $notificationRecipients
*/
public function __construct(
private readonly UserRepository $userRepository,
private readonly Mailer $mailer,
private readonly RoleApprovalUrlGenerator $approvalUrlGenerator,
private readonly LoggerInterface $authLogger,
private readonly array $notificationRecipients,
) {
}
@@ -43,19 +47,9 @@ final class RoleNominationHandler
return;
}
$recipients = array_values(array_filter(array_map(
static fn ($admin): ?string => $admin->getEmail(),
$this->userRepository->findAdministrators(),
)));
$recipients = $this->notificationRecipients;
if ([] === $recipients) {
// Worth a warning rather than a silent return: nobody can approve the nomination, and
// without this line nobody would find out that the notification goes nowhere.
$this->authLogger->warning('No administrator to notify about a role nomination', [
'userId' => $message->userId,
'roles' => $message->roles,
]);
return;
}
-26
View File
@@ -71,32 +71,6 @@ class UserRepository extends ServiceEntityRepository
->getResult();
}
/**
* The administrators who can approve a role nomination.
*
* The quote-anchored needle is load-bearing. ROLE_ADMIN_PENDING lives in the same JSON column,
* and an unanchored '%ROLE_ADMIN%' would match it — which would mail the very people whose own
* nomination is unapproved about other people's nominations. The result is filtered through
* Role::effectiveOnly() as well, so the guarantee does not rest on the LIKE alone.
*
* @return User[]
*/
public function findAdministrators(): array
{
/** @var User[] $candidates */
$candidates = $this->createQueryBuilder('u')
->andWhere('u.roles LIKE :admin')
->setParameter('admin', '%"'.Role::ADMIN.'"%')
->orderBy('u.email', 'ASC')
->getQuery()
->getResult();
return array_values(array_filter(
$candidates,
static fn (User $user): bool => \in_array(Role::ADMIN, Role::effectiveOnly($user->getRoles()), true),
));
}
/**
* Everyone worth filtering an accommodation booking by: current groups staff, plus whoever
* a booking is still assigned to even after losing the role — otherwise a booking assigned