display users for super admin

This commit is contained in:
Charles 2025-08-22 11:45:29 +02:00
parent 8a19b01893
commit 3ca1446b91
6 changed files with 176 additions and 145 deletions

View File

@ -2,7 +2,9 @@
namespace App\Controller;
use App\Entity\Organizations;
use App\Entity\User;
use App\Entity\UsersOrganizations;
use App\Form\UserForm;
use App\Service\ActionService;
use App\Service\UserOrganizationService;
@ -25,7 +27,8 @@ class UserController extends AbstractController
private readonly EntityManagerInterface $entityManager,
private readonly UserService $userService,
private readonly ActionService $actionService,
) {
)
{
}
#[Route('/', name: 'index', methods: ['GET'])]
@ -33,14 +36,20 @@ class UserController extends AbstractController
{
$this->denyAccessUnlessGranted('ROLE_USER');
$user = $this->getUser();
$user = $this->userService->getUserByIdentifier($this->getUser()->getUserIdentifier());
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$usersByOrganization = $this->userService->getUsersGroupedForIndex();
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->findUsersWithOrganization();
$noOrgUsers = $this->userService->formatNoOrgUsersAsAssoc(
$this->entityManager->getRepository(User::class)->findActiveUsersWithoutOrganization());
$usersByOrganization = $this->userService->groupByOrganization($uo);
$usersByOrganization += $noOrgUsers;
//Log action
$this->actionService->createAction("View all users", $user, null, "All" );
} elseif ($this->isGranted('ROLE_ADMIN')) {
$usersByOrganization = $this->userService->getUsersGroupedForAdmin($user);
dd("dsaf");
} else {
$usersByOrganization = [];
}

View File

@ -8,6 +8,7 @@ use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use App\Entity\UsersOrganizations;
/**
* @extends ServiceEntityRepository<User>
@ -33,16 +34,6 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
$this->getEntityManager()->flush();
}
// Rewrote this function to return less data
public function getAllActiveUsers(): array{
$queryBuilder = $this->createQueryBuilder('u')
->select('u.surname', 'u.email', 'u.id', 'u.isActive', 'u.name', 'u.pictureUrl')
->where('u.isActive = :isActive')
->orderBy('u.surname', 'ASC');
$queryBuilder->setParameter('isActive', true);
return $queryBuilder->getQuery()->getResult();
}
/**
* Returns active users that are NOT in any UsersOrganizations mapping.
* Returns User entities.
@ -53,7 +44,7 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
{
$qb = $this->createQueryBuilder('u')
->select('u')
->leftJoin('App\\Entity\\UsersOrganizations', 'uo', 'WITH', 'uo.users = u')
->leftJoin(UsersOrganizations::class, 'uo', 'WITH', 'uo.users = u')
->where('u.isActive = :uActive')
->andWhere('u.isDeleted = :uDeleted')
->andWhere('uo.id IS NULL')
@ -63,32 +54,4 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
return $qb->getQuery()->getResult();
}
/**
* Get all active users with their organization relationships
* @return array
*/
public function getAllActiveUsersWithOrganizations(): array
{
$queryBuilder = $this->createQueryBuilder('u')
->select('u', 'uo', 'o')
->leftJoin('App\Entity\UsersOrganizations', 'uo', 'WITH', 'u.id = uo.users')
->leftJoin('App\Entity\Organizations', 'o', 'WITH', 'uo.organization = o.id')
->where('u.isActive = :isActive')
->andWhere('u.isDeleted = :isDeleted')
->andWhere('(uo.isActive = :uoActive OR uo.isActive IS NULL)')
->andWhere('(o.isActive = :oActive OR o.isActive IS NULL)')
->andWhere('(o.isDeleted = :oDeleted OR o.isDeleted IS NULL)')
->orderBy('o.name', 'ASC')
->addOrderBy('u.surname', 'ASC');
$queryBuilder->setParameter('isActive', true);
$queryBuilder->setParameter('isDeleted', false);
$queryBuilder->setParameter('uoActive', true);
$queryBuilder->setParameter('oActive', true);
$queryBuilder->setParameter('oDeleted', false);
return $queryBuilder->getQuery()->getResult();
}
}

View File

@ -22,20 +22,18 @@ class UsersOrganizationsRepository extends ServiceEntityRepository
*
* @return UsersOrganizations[]
*/
public function findActiveWithUserAndOrganization(): array
public function findUsersWithOrganization(): array
{
$qb = $this->createQueryBuilder('uo')
->addSelect('u', 'o')
->leftJoin('uo.users', 'u')
->leftJoin('uo.organization', 'o')
->where('uo.isActive = :uoActive')
->andWhere('u.isActive = :uActive')
->andWhere('u.isDeleted = :uDeleted')
->andWhere('o.isActive = :oActive')
->andWhere('o.isDeleted = :oDeleted')
->orderBy('o.name', 'ASC')
->addOrderBy('u.surname', 'ASC')
->setParameter('uoActive', true)
->setParameter('uActive', true)
->setParameter('uDeleted', false)
->setParameter('oActive', true)

View File

@ -3,17 +3,26 @@
namespace App\Service;
use App\Entity\Roles;
use App\Entity\User;
use App\Entity\UserOrganizatonApp;
use App\Entity\UsersOrganizations;
use DateTimeImmutable;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityNotFoundException;
use Exception;
use League\Bundle\OAuth2ServerBundle\Model\AccessToken;
use Random\RandomException;
use Symfony\Bundle\SecurityBundle\Security;
class UserService
{
public function __construct(private readonly EntityManagerInterface $entityManager)
public const NOT_FOUND = 'Entity not found';
public function __construct(private readonly EntityManagerInterface $entityManager,
private readonly Security $security,
)
{
// Constructor logic if needed
}
@ -45,7 +54,7 @@ class UserService
*/
public function isUserConnected(string $userIdentifier): bool
{
$now = new \DateTimeImmutable('now', new \DateTimeZone('Europe/Paris'));
$now = new DateTimeImmutable();
$tokens = $this->entityManager->getRepository(AccessToken::class)->findBy([
'userIdentifier' => $userIdentifier,
'revoked' => false
@ -62,28 +71,103 @@ class UserService
}
/**
* Returns UsersOrganizations rows joined with User and Organization, grouped by organization name.
* @return array<string, array{ organization: ?object, users: array<int, array{ users: User, userOrganization: UsersOrganizations, is_connected: bool }> }>
* Check if the user have the rights to access the page
*
* @param User $user
* @return bool
* @throws Exception
*/
private function groupUserOrganizationsByOrganizationFromRows(array $rows): array
public function hasAccessTo(User $user): bool{
if($user->getUserIdentifier() === $this->security->getUser()->getUserIdentifier()){
return true;
}
$userOrganization = $this->entityManager->getRepository(UserOrganizatonApp::class)->findBy(['user' => $user]);
if($userOrganization) {
foreach ($userOrganization as $uo) {
if ($this->isAdminOfOrganization($uo)) {
return true;
}
}
}
if($this->security->isGranted('ROLE_SUPER_ADMIN')){
return true;
}
return false;
}
/**
* Check if the user is an admin of the organization
* A user is considered an admin of an organization if they have the 'ROLE_ADMIN' AND have the link to the
* entity role 'ROLE_ADMIN' in the UsersOrganizationsApp entity
* (if he is admin for any application of the organization).
*
* @param UsersOrganizations $usersOrganizations
* @return bool
* @throws Exception
*/
public function isAdminOfOrganization(UsersOrganizations $usersOrganizations): bool{
$actingUser = $this->getUserByIdentifier($this->security->getUser()->getUserIdentifier());
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->findOneBy(['user' => $actingUser]);
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['role' => 'ADMIN']);
if ($uo){
$uoa = $this->entityManager->getRepository(UserOrganizatonApp::class)->findOneBy(['userOrganization' => $uo,
'role'=> $roleAdmin]);
if ($uoa && $this->security->isGranted('ROLE_ADMIN')) {
return true;
}
}
return false;
}
/**
* Get the user by their identifier.
*
* @param string $userIdentifier
* @return User|null
* @throws Exception
*/
public function getUserByIdentifier(string $userIdentifier): ?User
{
$user = $this->entityManager->getRepository(User::class)->findOneBy(['userIdentifier' => $userIdentifier]);
if (!$user) {
throw new EntityNotFoundException(self::NOT_FOUND);
}
return $user;
}
/**
* Get users grouped by their organizations for the index page.
* This method should return an array of users grouped by their organizations.
*
* @return array
*/
public function groupByOrganization(array $usersOrganizations): array
{
$grouped = [];
foreach ($rows as $userOrg) {
$organization = $userOrg->getOrganization();
$user = $userOrg->getUsers();
$orgName = $organization?->getName() ?? 'No Organization';
if (!isset($grouped[$orgName])) {
$grouped[$orgName] = [
'organization' => $organization,
foreach ($usersOrganizations as $userOrg) {
$org = $userOrg->getOrganization();
if (!$org) {
continue;
}
$orgId = $org->getId();
$orgName = $org->getName();
if (!isset($grouped[$orgId])) {
$grouped[$orgId] = [
'id' => $orgId,
'name' => $orgName,
'users' => [],
];
}
$grouped[$orgName]['users'][] = [
'users' => $user,
'userOrganization' => $userOrg,
'is_connected' => $this->isUserConnected($user->getEmail()),
$user = $userOrg->getUsers();
$grouped[$orgId]['users'][] = [
'entity' => $user,
'connected' => $this->isUserConnected($user->getUserIdentifier()),
'isActive' => (bool) $userOrg->isActive()
];
}
@ -91,64 +175,27 @@ class UserService
}
/**
* Returns users that have no UsersOrganizations mapping.
* @return array<int, array{ users: User, userOrganization: null, is_connected: bool }>
* Format users without organization for admin view.
*
* @param array $users
* @return array
*/
private function buildUsersWithoutOrganization(): array
public function formatNoOrgUsersAsAssoc(array $noOrgUsers): array
{
$userRepository = $this->entityManager->getRepository(User::class);
$users = $userRepository->findActiveUsersWithoutOrganization();
$group = [
'id' => null,
'name' => 'Utilisateurs',
'users' => [],
];
$list = [];
foreach ($users as $user) {
$list[] = [
'users' => $user,
'userOrganization' => null,
'is_connected' => $this->isUserConnected($user->getEmail()),
foreach ($noOrgUsers as $user) {
$group['users'][] = [
'entity' => $user,
'connected' => $this->isUserConnected($user->getUserIdentifier()),
];
}
return $list;
}
/**
* Public API: build the final structure for the index view.
* Groups users by organization and adds a "No Organization" group for users without mapping.
* @return array<string, array{ organization: ?object, users: array<int, array{ users: User, userOrganization: UsersOrganizations|null, is_connected: bool }> }>
*/
public function getUsersGroupedForIndex(): array
{
$usersOrganizationsRepo = $this->entityManager->getRepository(UsersOrganizations::class);
$rows = $usersOrganizationsRepo->findActiveWithUserAndOrganization();
$grouped = $this->groupUserOrganizationsByOrganizationFromRows($rows);
$noOrgUsers = $this->buildUsersWithoutOrganization();
if (!empty($noOrgUsers)) {
$grouped['No Organization'] = [
'organization' => null,
'users' => $noOrgUsers,
];
}
ksort($grouped);
return $grouped;
}
/**
* For admins: return users grouped only for organizations where the given user is ADMIN.
* @return array<string, array{ organization: ?object, users: array<int, array{ users: User, userOrganization: UsersOrganizations, is_connected: bool }> }>
*/
public function getUsersGroupedForAdmin(User $adminUser): array
{
$uoaRepo = $this->entityManager->getRepository(UserOrganizatonApp::class);
$orgIds = $uoaRepo->findAdminOrganizationIdsForUser($adminUser);
$uoRepo = $this->entityManager->getRepository(UsersOrganizations::class);
$rows = $uoRepo->findActiveWithUserAndOrganizationByOrganizationIds($orgIds);
$grouped = $this->groupUserOrganizationsByOrganizationFromRows($rows);
ksort($grouped);
return $grouped;
// Use a fixed key (e.g., 0 or 'none') to avoid collisions with real org IDs
return ['none' => $group];
}
}

View File

@ -7,7 +7,7 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Gestion Utilisateurs</h1>
{% if is_granted('ROLE_SUPER_ADMIN') %}
<a href="{{ path('user_new') }}" class="btn btn-primary">Ajouter un utilisateur</a>
{# <a href="{{ path('user_new') }}" class="btn btn-primary">Ajouter un utilisateur</a>#}
{% endif %}
</div>
@ -15,14 +15,13 @@
{% if usersByOrganization|length == 0 %}
<div class="alert alert-info">
<h4>Aucun utilisateur trouvé</h4>
<p>Il n'y a actuellement aucun utilisateur correspondant à votre périmètre.</p>
</div>
{% else %}
{% for orgName, orgData in usersByOrganization %}
{% for org in usersByOrganization %}
{% include 'user/userList.html.twig' with {
title: orgName,
org: orgData,
organizationId: orgData.organization ? orgData.organization.id : null
title: org.name,
organizationId: org.id|default(null),
users: org.users
} %}
{% endfor %}
{% endif %}

View File

@ -6,7 +6,7 @@
<div class="card-title d-flex justify-content-between align-items-center ">
<h3>{{ title }}</h3>
{% if organizationId %}
{# <span class="badge bg-primary">ID: {{ organizationId }}</span>#}
{% endif %}
</div>
{% endif %}
@ -24,47 +24,62 @@
</tr>
</thead>
<tbody>
{% if org.users|length == 0 %}
{% if users|length == 0 %}
<tr>
<td colspan="6" class="text-center">Aucun utilisateur trouvé dans cette organisation.</td>
</tr>
{% else %}
{% for userData in org.users %}
{% for user in users %}
<tr>
<td>
{% if userData.users.pictureUrl %}
<img src="{{ asset(userData.users.pictureUrl) }}" alt="User profile pic"
{% if user.entity.pictureUrl %}
<img src="{{ asset(user.entity.pictureUrl) }}" alt="User profile pic"
class="rounded-circle"
style="width:40px; height:40px;">
{% else %}
<div class="rounded-circle bg-secondary d-flex align-items-center justify-content-center"
style="width:40px; height:40px;">
<span class="text-white">{{ userData.users.name|first|upper }}{{ userData.users.surname|first|upper }}</span>
<span class="text-white">{{ user.entity.name|first|upper }}{{ user.entity.surname|first|upper }}</span>
</div>
{% endif %}
</td>
<td>{{ userData.users.surname }}</td>
<td>{{ userData.users.name }}</td>
<td>{{ userData.users.email }}</td>
<td>{{ user.entity.surname }}</td>
<td>{{ user.entity.name }}</td>
<td>{{ user.entity.email }}</td>
{# Logic for status #}
<td>
{% if userData.is_connected %}
<span class="badge bg-success">Actif</span>
{# check if the user is active in the organization link #}
{% if user.isActive is defined %}
{% if user.isActive %}
{% if user.connected %}
<span class="badge bg-success">Actif</span>
{% else %}
<span class="badge bg-secondary">Inactif</span>
{% endif %}
{% else %}
<span class="badge bg-danger">Désactivé</span>
{% endif %}
{# if no organization link #}
{% else %}
<span class="badge bg-secondary">Inactif</span>
{% if user.connected %}
<span class="badge bg-success">Actif</span>
{% else %}
<span class="badge bg-secondary">Inactif</span>
{% endif %}
{% endif %}
</td>
<td>
{# {% if organizationId is defined and organizationId %}#}
{# <a href="{{ path('user_show', {'id': userData.users.id, 'organizationId': organizationId}) }}"#}
{# class="p-3 align-middle color-primary">#}
{# {{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}#}
{# </a>#}
{# {% else %}#}
{# <a href="{{ path('user_show', {'id': userData.users.id}) }}"#}
{# class="p-3 align-middle color-primary">#}
{# {{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}#}
{# </a>#}
{# {% endif %}#}
{# {% if organizationId is defined and organizationId %} #}
{# <a href="{{ path('user_show', {'id': user.entity.id, 'organizationId': organizationId}) }}" #}
{# class="p-3 align-middle color-primary"> #}
{# {{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }} #}
{# </a> #}
{# {% else %} #}
{# <a href="{{ path('user_show', {'id': user.entity.id}) }}" #}
{# class="p-3 align-middle color-primary"> #}
{# {{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }} #}
{# </a> #}
{# {% endif %} #}
</td>
</tr>
{% endfor %}