feat: bpn as single source of truth for role and hotel code assignments
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controller\Admin\System\User;
|
||||
|
||||
use App\BusProNet\UserDataHandler;
|
||||
use App\Entity\User;
|
||||
use App\Htmx\HxRedirectResponse;
|
||||
use App\Security\Voter\UserVoter;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* Granting a privilege is its own act, deliberately not a checkbox on the user edit form:
|
||||
* it is confirmed on its own, logged on its own, and cannot happen as a side effect of
|
||||
* saving an unrelated setting. It is also the only way a role is ever granted at all -
|
||||
* everything else about roles is synced from BusPro (see docs/user-roles.md).
|
||||
*/
|
||||
class ApproveRoleController extends AbstractController
|
||||
{
|
||||
public function __construct(private readonly UserDataHandler $userDataHandler)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route('/admin/system/user/approve-role/{id}/{role}', name: 'app_admin_system_user_approve_role')]
|
||||
#[IsGranted(UserVoter::EDIT, subject: 'user')]
|
||||
public function index(User $user, string $role, Request $request): Response
|
||||
{
|
||||
// nothing but a role this user is actually nominated for, so a hand-crafted URL
|
||||
// cannot grant one BusPro never claimed
|
||||
$pendingRole = User::PENDING_ROLES[$role] ?? null;
|
||||
if (null === $pendingRole || false === in_array($pendingRole, $user->getPendingRoles(), true)) {
|
||||
throw $this->createNotFoundException('Für diese Rolle liegt keine Freischaltung vor');
|
||||
}
|
||||
|
||||
if (true === $request->isMethod(Request::METHOD_POST)) {
|
||||
// checked again by the handler, which is what catches a sync revoking the claim
|
||||
// between opening the dialog and confirming it
|
||||
if (false === $this->userDataHandler->approveRole($user, $role)) {
|
||||
throw $this->createNotFoundException('Für diese Rolle liegt keine Freischaltung vor');
|
||||
}
|
||||
|
||||
$this->addFlash('success', sprintf('Die Rolle %s wurde freigeschaltet', User::ROLES[$role]));
|
||||
|
||||
return new HxRedirectResponse($this->generateUrl('app_admin_system_user_edit', ['id' => $user->getId()]));
|
||||
}
|
||||
|
||||
return $this->render('admin/system/user/modal_approve_role.html.twig', [
|
||||
'user' => $user,
|
||||
'roleLabel' => User::ROLES[$role],
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Controller\Admin\System\User;
|
||||
|
||||
use App\Config\HouseCatalog;
|
||||
use App\Entity\User;
|
||||
use App\Form\UserType;
|
||||
use App\Security\Voter\UserVoter;
|
||||
@@ -18,6 +19,7 @@ class EditController extends AbstractController
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly HouseCatalog $houseCatalog,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -44,9 +46,21 @@ class EditController extends AbstractController
|
||||
return $this->redirectToRoute('app_admin_system_user_index');
|
||||
}
|
||||
|
||||
// a code with no house behind it stays visible under its own name rather than
|
||||
// vanishing from the page - it is what the user is actually restricted to
|
||||
$houses = [];
|
||||
foreach ($user->getHotelCodes() as $code) {
|
||||
$houses[$code] = $this->houseCatalog->getName($code) ?? $code;
|
||||
}
|
||||
|
||||
return $this->render('admin/system/user/edit.html.twig', [
|
||||
'form' => $form,
|
||||
'user' => $user,
|
||||
'grantedRoles' => array_map(
|
||||
static fn (string $role): string => User::ROLES[$role],
|
||||
$user->getAssignedRoles(),
|
||||
),
|
||||
'houses' => $houses,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user