This commit is contained in:
Charles 2025-07-25 12:03:54 +02:00
parent 8eb5cf433d
commit d7677db885
2 changed files with 162 additions and 70 deletions

4
.gitignore vendored
View File

@ -23,3 +23,7 @@
/public/assets/ /public/assets/
/assets/vendor/ /assets/vendor/
###< symfony/asset-mapper ### ###< symfony/asset-mapper ###
###> IntelliJ IDEA ###
.idea/
*.iml

View File

@ -9,19 +9,28 @@ use App\Entity\User;
use App\Entity\UsersOrganizations; use App\Entity\UsersOrganizations;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
/**
* Service pour la gestion des organisations d'utilisateurs.
* Fournit des méthodes pour récupérer, modifier et désactiver les rôles et applications d'un utilisateur dans une organisation.
*/
readonly class UserOrganizationService readonly class UserOrganizationService
{ {
/**
* Constructeur du service UserOrganizationService.
*
* @param EntityManagerInterface $entityManager Le gestionnaire d'entités Doctrine
*/
public function __construct(private readonly EntityManagerInterface $entityManager) public function __construct(private readonly EntityManagerInterface $entityManager)
{ {
} }
/** /**
* Returns all organizations the given user belongs to, * Récupère toutes les organisations auxquelles appartient l'utilisateur donné,
* including the unique roles and apps the user has in each organization. * incluant les rôles et applications uniques de l'utilisateur dans chaque organisation.
* *
* @param User $user The user whose organizations are being fetched * @param User $user L'utilisateur concerné
* @param int|null $organizationsId Optional organization ID to filter by * @param int|null $organizationsId ID optionnel pour filtrer par organisation
* @return array<array{organization:object, roles:Roles[], apps:object[]}> * @return array<array{organization:object, roles:Roles[], apps:object[], uoId:int}>
*/ */
public function getUserOrganizations(User $user, int $organizationsId = null): array public function getUserOrganizations(User $user, int $organizationsId = null): array
{ {
@ -33,29 +42,60 @@ readonly class UserOrganizationService
foreach ($userOrganizations as $uo) { foreach ($userOrganizations as $uo) {
$orgId = $uo->getOrganization()->getId(); $orgId = $uo->getOrganization()->getId();
// If $organizationsId is provided, skip other organizations // Si $organizationsId est fourni, ignorer les autres organisations
if ($organizationsId !== null && $orgId !== $organizationsId) { if ($organizationsId !== null && $orgId !== $organizationsId) {
continue; continue;
} }
// Initialize the organization entry if it doesn't exist // Initialiser l'entrée de l'organisation si elle n'existe pas
$organizations[$orgId] = $organizations[$orgId] ?? $this->createEmptyOrganizationBucket($uo); $organizations[$orgId] = $organizations[$orgId] ?? $this->createEmptyOrganizationBucket($uo);
$organizations[$orgId]['uoId'] = $uo->getId(); $organizations[$orgId]['uoId'] = $uo->getId();
// Aggregate roles & apps // Agréger les rôles et applications
$this->addRole($organizations[$orgId]['roles'], $uo->getRole()); $this->addRole($organizations[$orgId]['roles'], $uo->getRole());
$this->addApps($organizations[$orgId]['apps'], $uo->getApps()); $this->addApps($organizations[$orgId]['apps'], $uo->getApps());
} }
// Ordonner les rôles : Super Admin, Admin, puis les autres
foreach ($organizations as &$org) {
$org['roles'] = $this->sortRoles($org['roles']);
}
unset($org);
$this->normalizeAppsIndexes($organizations); $this->normalizeAppsIndexes($organizations);
return array_values($organizations); return array_values($organizations);
} }
/** /**
* Build the initial array structure for a fresh organization entry. * Trie les rôles pour que Super Admin et Admin soient en premier, puis les autres.
* *
* @param UsersOrganizations $link * @param Roles[] $roles
* @return Roles[]
*/
private function sortRoles(array $roles): array
{
usort($roles, function ($a, $b) {
$priority = [
'SUPER_ADMIN' => 0,
'ADMIN' => 1
];
$aName = strtoupper($a->getName());
$bName = strtoupper($b->getName());
$aPriority = $priority[$aName] ?? 2;
$bPriority = $priority[$bName] ?? 2;
if ($aPriority === $bPriority) {
return strcmp($aName, $bName);
}
return $aPriority <=> $bPriority;
});
return $roles;
}
/**
* Initialise la structure de données pour une organisation.
*
* @param UsersOrganizations $link Lien utilisateur-organisation
* @return array{organization:object, roles:Roles[], apps:array<int,object>} * @return array{organization:object, roles:Roles[], apps:array<int,object>}
*/ */
private function createEmptyOrganizationBucket(UsersOrganizations $link): array private function createEmptyOrganizationBucket(UsersOrganizations $link): array
@ -68,10 +108,11 @@ readonly class UserOrganizationService
} }
/** /**
* Add a Role entity to the roles array only if it is not already present (by ID). * Ajoute un rôle à la liste si non déjà présent (par ID).
* *
* @param Roles[] &$roles * @param Roles[] &$roles Liste des rôles
* @param Roles|null $role * @param Roles|null $role Rôle à ajouter
* @return void
*/ */
private function addRole(array &$roles, ?Roles $role): void private function addRole(array &$roles, ?Roles $role): void
{ {
@ -87,10 +128,11 @@ readonly class UserOrganizationService
} }
/** /**
* Merge one or many apps into the apps map, keeping only one entry per id. * Fusionne une ou plusieurs applications dans le tableau associatif, une entrée par ID.
* *
* @param array<int,object> &$apps * @param array<int,object> &$apps Tableau des applications
* @param iterable $appsToAdd Collection returned by $userOrganizations->getApps() * @param iterable $appsToAdd Applications à ajouter
* @return void
*/ */
private function addApps(array &$apps, iterable $appsToAdd): void private function addApps(array &$apps, iterable $appsToAdd): void
{ {
@ -100,10 +142,10 @@ readonly class UserOrganizationService
} }
/** /**
* Convert apps from associative maps (keyed by id) to plain indexed arrays, * Normalise le tableau des applications pour le rendre indexé (JSON-friendly).
* so the final output is clean JSON-able.
* *
* @param array &$organizations * @param array &$organizations Tableau des organisations
* @return void
*/ */
private function normalizeAppsIndexes(array &$organizations): void private function normalizeAppsIndexes(array &$organizations): void
{ {
@ -113,57 +155,106 @@ readonly class UserOrganizationService
} }
/** /**
* Set user organizations with roles, ensuring USER role is always present * Définit les rôles d'un utilisateur dans une organisation, en s'assurant que le rôle USER est toujours présent.
* Désactive tous les rôles si USER n'est pas sélectionné.
* *
* @param User $user * @param User $user L'utilisateur
* @param Organizations $organization * @param Organizations $organization L'organisation
* @param array $selectedRoles Array of role IDs to set for the user in the organization * @param array $selectedRoles Tableau des IDs de rôles sélectionnés
* @return void
* @throws \RuntimeException Si le rôle USER n'est pas trouvé
*/ */
public function setUserOrganizations(User $user, Organizations $organization, array $selectedRoles): void public function setUserOrganizations(User $user, Organizations $organization, array $selectedRoles): void
{ {
$repo = $this->entityManager->getRepository(UsersOrganizations::class); $repo = $this->entityManager->getRepository(UsersOrganizations::class);
$roleRepo = $this->entityManager->getRepository(Roles::class); $roleRepo = $this->entityManager->getRepository(Roles::class);
// Get the USER role entity
$userRole = $roleRepo->findOneBy(['name' => 'USER']); $userRole = $roleRepo->findOneBy(['name' => 'USER']);
if (!$userRole) { if (!$userRole) {
throw new \RuntimeException('USER role not found'); throw new \RuntimeException('USER role not found');
} }
// If USER role is not selected, deactivate all roles and return
if (!in_array($userRole->getId(), $selectedRoles)) { if (!in_array($userRole->getId(), $selectedRoles)) {
$this->deactivateAllUserRoles($user, $organization); $this->deactivateAllUserRoles($user, $organization);
return; return;
} }
// 1. Get all current UsersOrganizations for this user/org
$currentUserOrgs = $repo->findBy([ $currentUserOrgs = $repo->findBy([
'users' => $user, 'users' => $user,
'organization' => $organization 'organization' => $organization
]); ]);
$currentRolesMap = $this->mapUserOrgRoles($currentUserOrgs);
// 2. Build a map: roleId => UsersOrganizations entity $selectedRoles = $this->ensureUserRolePresent($selectedRoles, $userRole->getId());
$currentRolesMap = []; $this->addOrUpdateRoles($selectedRoles, $currentRolesMap, $roleRepo, $user, $organization);
foreach ($currentUserOrgs as $uo) { $this->deactivateUnselectedRoles($currentRolesMap);
$currentRolesMap[$uo->getRole()->getId()] = $uo; $this->entityManager->flush();
} }
// 3. Check if we need to ensure USER role exists /**
* Met à jour les applications associées à l'utilisateur dans une organisation.
*
* @param User $user L'utilisateur
* @param Organizations $organization L'organisation
* @param array $selectedApps Tableau des IDs d'applications sélectionnées
* @return void
*/
public function setUserOrganizationsApps(User $user, Organizations $organization, array $selectedApps): void
{
$roleUser = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'USER']);
$uoEntity = $this->entityManager
->getRepository(UsersOrganizations::class)
->findOneBy(['users' => $user, 'organization' => $organization, 'role' => $roleUser]);
if (!$uoEntity) {
return;
}
$this->removeUnselectedApps($uoEntity, $selectedApps);
$this->addSelectedApps($uoEntity, $selectedApps);
$this->entityManager->persist($uoEntity);
$this->entityManager->flush();
}
/**
* Crée une map des rôles actuels de l'utilisateur dans l'organisation.
* @param array $currentUserOrgs
* @return array
*/
private function mapUserOrgRoles(array $currentUserOrgs): array
{
$map = [];
foreach ($currentUserOrgs as $uo) {
$map[$uo->getRole()->getId()] = $uo;
}
return $map;
}
/**
* S'assure que le rôle USER est présent dans la sélection si nécessaire.
* @param array $selectedRoles
* @param int $userRoleId
* @return array
*/
private function ensureUserRolePresent(array $selectedRoles, int $userRoleId): array
{
$hasNonUserRole = false; $hasNonUserRole = false;
foreach ($selectedRoles as $roleId) { foreach ($selectedRoles as $roleId) {
if ($roleId !== $userRole->getId()) { if ($roleId !== $userRoleId) {
$hasNonUserRole = true; $hasNonUserRole = true;
break; break;
} }
} }
if ($hasNonUserRole && !in_array($userRoleId, $selectedRoles)) {
// 4. If we have non-USER roles, ensure USER role is present $selectedRoles[] = $userRoleId;
if ($hasNonUserRole && !in_array($userRole->getId(), $selectedRoles)) { }
$selectedRoles[] = $userRole->getId(); return $selectedRoles;
} }
// 5. Add new roles that are selected but not present /**
* Ajoute ou réactive les rôles sélectionnés pour l'utilisateur dans l'organisation.
* @param array $selectedRoles
* @param array $currentRolesMap
* @param $roleRepo
* @param User $user
* @param Organizations $organization
*/
private function addOrUpdateRoles(array $selectedRoles, array &$currentRolesMap, $roleRepo, User $user, Organizations $organization): void
{
foreach ($selectedRoles as $roleId) { foreach ($selectedRoles as $roleId) {
if (!isset($currentRolesMap[$roleId])) { if (!isset($currentRolesMap[$roleId])) {
$roleEntity = $roleRepo->find($roleId); $roleEntity = $roleRepo->find($roleId);
@ -172,66 +263,63 @@ readonly class UserOrganizationService
$newUserOrganization->setUsers($user); $newUserOrganization->setUsers($user);
$newUserOrganization->setRole($roleEntity); $newUserOrganization->setRole($roleEntity);
$newUserOrganization->setOrganization($organization); $newUserOrganization->setOrganization($organization);
$newUserOrganization->setIsActive(true); // Ensure new roles are active $newUserOrganization->setIsActive(true);
$this->entityManager->persist($newUserOrganization); $this->entityManager->persist($newUserOrganization);
} }
} else { } else {
// If role exists but was inactive, reactivate it
$currentRolesMap[$roleId]->setIsActive(true); $currentRolesMap[$roleId]->setIsActive(true);
$this->entityManager->persist($currentRolesMap[$roleId]); $this->entityManager->persist($currentRolesMap[$roleId]);
} }
// Remove from map so we know which ones to deactivate later
unset($currentRolesMap[$roleId]); unset($currentRolesMap[$roleId]);
} }
}
// 6. Remove roles that are present but not selected (deactivate them) /**
* Désactive les rôles non sélectionnés pour l'utilisateur dans l'organisation.
* @param array $currentRolesMap
*/
private function deactivateUnselectedRoles(array $currentRolesMap): void
{
foreach ($currentRolesMap as $uo) { foreach ($currentRolesMap as $uo) {
$uo->setIsActive(false); $uo->setIsActive(false);
$this->entityManager->persist($uo); $this->entityManager->persist($uo);
} }
$this->entityManager->flush();
} }
public function setUserOrganizationsApps(User $user, Organizations $organization, array $selectedApps) /**
* Retire les applications non sélectionnées de l'utilisateur dans l'organisation.
* @param UsersOrganizations $uoEntity
* @param array $selectedApps
*/
private function removeUnselectedApps(UsersOrganizations $uoEntity, array $selectedApps): void
{ {
$apps = [];
$userOrganizations = $this->entityManager
->getRepository(UsersOrganizations::class)
->findAllDistinctOrganizationsByUserId($user->getId());
foreach ($userOrganizations as $uo) {
$this->addApps($apps, $uo->getApps());
}
$roleUser = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'USER']);
$uoEntity = $this->entityManager
->getRepository(UsersOrganizations::class)
->findOneBy(['users' => $user, 'organization' => $organization, 'role' => $roleUser]);
// dd($uoEntity->getApps()->toArray());
// 1. Remove apps that are no longer selected
foreach ($uoEntity->getApps()->toArray() as $existingApp) { foreach ($uoEntity->getApps()->toArray() as $existingApp) {
if (!in_array($existingApp->getId(), $selectedApps)) { if (!in_array($existingApp->getId(), $selectedApps)) {
$uoEntity->removeApp($existingApp); $uoEntity->removeApp($existingApp);
} }
} }
}
// 2. Add newly selected apps /**
* Ajoute les applications sélectionnées à l'utilisateur dans l'organisation.
* @param UsersOrganizations $uoEntity
* @param array $selectedApps
*/
private function addSelectedApps(UsersOrganizations $uoEntity, array $selectedApps): void
{
foreach ($selectedApps as $appId) { foreach ($selectedApps as $appId) {
$appEntity = $this->entityManager->getRepository(Apps::class)->find($appId); $appEntity = $this->entityManager->getRepository(Apps::class)->find($appId);
if ($appEntity && !$uoEntity->getApps()->contains($appEntity)) { if ($appEntity && !$uoEntity->getApps()->contains($appEntity)) {
$uoEntity->addApp($appEntity); $uoEntity->addApp($appEntity);
} }
} }
$this->entityManager->persist($uoEntity);
$this->entityManager->flush();
} }
/** /**
* Deactivate all roles if role USER is deactivated * Désactive tous les rôles d'un utilisateur dans une organisation.
* @param User $user *
* @param Organizations $organization * @param User $user L'utilisateur
* @param Organizations $organization L'organisation
* @return void * @return void
*/ */
public function deactivateAllUserRoles(User $user, Organizations $organization): void public function deactivateAllUserRoles(User $user, Organizations $organization): void