Compare commits
17 Commits
00e3003257
...
2e99457e16
| Author | SHA1 | Date |
|---|---|---|
|
|
2e99457e16 | |
|
|
cde6c529a9 | |
|
|
a3f993b858 | |
|
|
d2c20b9423 | |
|
|
89ed7049b9 | |
|
|
16dd919a5d | |
|
|
301f7bb445 | |
|
|
05d8ca0499 | |
|
|
e17e8e0eb2 | |
|
|
e6391279fe | |
|
|
cf16ec09a1 | |
|
|
943752a002 | |
|
|
a7e7298310 | |
|
|
3337b8c001 | |
|
|
6446eb2ce1 | |
|
|
a10b499522 | |
|
|
e77e92d39f |
|
|
@ -25,6 +25,10 @@
|
|||
php bin/console importmap:require choices.js
|
||||
php bin/console importmap:require choices.js/public/assets/styles/choices.min.css
|
||||
```
|
||||
|
||||
|
||||
### Notes
|
||||
- certaines abbreviations sont utilisées afin de simplifier le code et d'éviter les répétitions ou noms trop longs :
|
||||
- `uo` pour `User Organization`
|
||||
- `uoId` pour `User Organization Id`
|
||||
- `oa` pour `Organization Application`
|
||||
- `at` pour `Access Token`
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
},
|
||||
"@symfony/ux-turbo": {
|
||||
"turbo-core": {
|
||||
"enabled": true,
|
||||
"enabled": false,
|
||||
"fetch": "eager"
|
||||
},
|
||||
"mercure-turbo-stream": {
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 16 16"><g fill="currentColor"><path d="M4 2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5zm3 0a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5zm3.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zM4 5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5zM7.5 5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm2.5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5zM4.5 8a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm2.5.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5zm3.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5z"/><path d="M2 1a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1zm11 0H3v14h3v-2.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5V15h3z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 965 B |
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="https://www.w3.org/2000/svg" viewBox="0 0 16 16"><g fill="currentColor"><path d="M14.763.075A.5.5 0 0 1 15 .5v15a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5V14h-1v1.5a.5.5 0 0 1-.5.5h-9a.5.5 0 0 1-.5-.5V10a.5.5 0 0 1 .342-.474L6 7.64V4.5a.5.5 0 0 1 .276-.447l8-4a.5.5 0 0 1 .487.022M6 8.694L1 10.36V15h5zM7 15h2v-1.5a.5.5 0 0 1 .5-.5h2a.5.5 0 0 1 .5.5V15h2V1.309l-7 3.5z"/><path d="M2 11h1v1H2zm2 0h1v1H4zm-2 2h1v1H2zm2 0h1v1H4zm4-4h1v1H8zm2 0h1v1h-1zm-2 2h1v1H8zm2 0h1v1h-1zm2-2h1v1h-1zm0 2h1v1h-1zM8 7h1v1H8zm2 0h1v1h-1zm2 0h1v1h-1zM8 5h1v1H8zm2 0h1v1h-1zm2 0h1v1h-1zm0-2h1v1h-1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 598 B |
|
|
@ -69,7 +69,7 @@ body {
|
|||
}
|
||||
|
||||
.content-wrapper {
|
||||
background: #F5F7FF;
|
||||
background: #EEF0FD;
|
||||
width: 100%;
|
||||
-webkit-flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
|
|
|
|||
|
|
@ -2,14 +2,93 @@
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Apps;
|
||||
use App\Entity\Roles;
|
||||
use App\Entity\UsersOrganizations;
|
||||
use App\Service\OrganizationsService;
|
||||
use App\Service\UserOrganizationService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use App\Entity\Organizations;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
#[Route(path: '/organization', name: 'organization_')]
|
||||
|
||||
class OrganizationController extends AbstractController
|
||||
{
|
||||
private const NOT_FOUND = 'Entity not found';
|
||||
private const ACCESS_DENIED = 'Access denied';
|
||||
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager,
|
||||
private readonly OrganizationsService $organizationsService,
|
||||
private readonly UserOrganizationService $usersOrganizationService)
|
||||
{
|
||||
}
|
||||
|
||||
#[Route(path: '/' , name: 'index', methods: ['GET'])]
|
||||
public function index():Response
|
||||
{
|
||||
if($this->isGranted('ROLE_SUPER_ADMIN'))
|
||||
{
|
||||
$organizations = $this->entityManager->getRepository(Organizations::class)->findBy(['isActive' => true]);
|
||||
} else{
|
||||
$user = $this->getUser();
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_login');
|
||||
}
|
||||
$userIdentifier = $user->getUserIdentifier();
|
||||
|
||||
}
|
||||
$organizations = $this->entityManager->getRepository(UsersOrganizations::class)->findOrganizationsByUserEmailAndRoleName($userIdentifier, 'ADMIN');
|
||||
if(!$organizations) {
|
||||
// if user is not admin in any organization, throw access denied
|
||||
throw $this->createNotFoundException(self::ACCESS_DENIED);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('organization/index.html.twig', [
|
||||
'organizations' => $organizations,
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}', name: 'show', methods: ['GET'])]
|
||||
public function show(int $id): Response
|
||||
{
|
||||
if ($this->isGranted('ROLE_ADMIN')) {
|
||||
$user = $this->getUser();
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_login');
|
||||
}
|
||||
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']);
|
||||
$uo = $this->entityManager->getRepository(UsersOrganizations::class)->findOneBy([
|
||||
'users' => $user,
|
||||
'organization' => $id,
|
||||
'role' => $roleAdmin
|
||||
]);
|
||||
if (!$uo) {
|
||||
throw $this->createNotFoundException(self::ACCESS_DENIED);
|
||||
}
|
||||
//Don't care about the null pointer because if no UO found, it won't pass the previous check
|
||||
$organization = $this->entityManager->getRepository(Organizations::class)->find($id);
|
||||
$newUsers = $this->entityManager->getRepository(UsersOrganizations::class)->getLastNewActiveUsersByOrganization($organization);
|
||||
$adminUsers = $this->entityManager->getRepository(UsersOrganizations::class)->getAdminUsersByOrganization($organization);
|
||||
// reusing the method to avoid code duplication even though it returns an array of UsersOrganizations
|
||||
$org = $this->usersOrganizationService->findActiveUsersByOrganizations([$organization]);
|
||||
|
||||
// get all applications
|
||||
$applications = $this->organizationsService->getApplicationsWithAccessStatus($organization);
|
||||
|
||||
}else{
|
||||
throw $this->createNotFoundException(self::ACCESS_DENIED);
|
||||
}
|
||||
|
||||
return $this->render('organization/show.html.twig', [
|
||||
'organization' => $organization,
|
||||
'adminUsers' => $adminUsers,
|
||||
'newUsers' => $newUsers,
|
||||
'org' => $org[0],
|
||||
'applications' => $applications,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,13 +36,20 @@ class UserController extends AbstractController
|
|||
public function index(EntityManagerInterface $entityManager): Response
|
||||
{
|
||||
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
|
||||
$usersByOrganization = $entityManager->getRepository(UsersOrganizations::class)->getActiveUsersGroupedByOrganization();
|
||||
|
||||
} else {
|
||||
$userIdentifier = $this->getUser()->getUserIdentifier();
|
||||
$usersByOrganization = $this->userOrganizationService->getActiveUsersGroupedByOrganization();
|
||||
// dd($usersByOrganization);
|
||||
} else{
|
||||
$user = $this->getUser();
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_login');
|
||||
}
|
||||
$userIdentifier = $user->getUserIdentifier();
|
||||
$organizations = $this->entityManager->getRepository(UsersOrganizations::class)->findOrganizationsByUserEmailAndRoleName($userIdentifier, 'ADMIN');
|
||||
$usersByOrganization = $this->entityManager->getRepository(UsersOrganizations::class)
|
||||
->findActiveUsersByOrganizations($organizations);
|
||||
if(!$organizations) {
|
||||
// if user is not admin in any organization, throw access denied
|
||||
throw $this->createNotFoundException(self::ACCESS_DENIED);
|
||||
}
|
||||
$usersByOrganization = $this->userOrganizationService->findActiveUsersByOrganizations($organizations);
|
||||
}
|
||||
return $this->render('user/index.html.twig', [
|
||||
'usersByOrganization' => $usersByOrganization,
|
||||
|
|
@ -67,7 +74,7 @@ class UserController extends AbstractController
|
|||
|
||||
$userOrganizations = $this->userOrganizationService->getUserOrganizations($user);
|
||||
|
||||
return $this->render('user/profile.html.twig', [
|
||||
return $this->render('user/show.html.twig', [
|
||||
'user' => $user,
|
||||
'userOrganizations' => $userOrganizations,
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ namespace App\EventSubscriber;
|
|||
|
||||
use App\Entity\User;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use League\Bundle\OAuth2ServerBundle\Model\AccessToken;
|
||||
use League\Bundle\OAuth2ServerBundle\Model\Client;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
|
||||
|
||||
|
|
@ -30,8 +32,20 @@ class LoginSubscriber implements EventSubscriberInterface
|
|||
if($user) {
|
||||
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email' => $user->getUserIdentifier()]);
|
||||
$user->setLastConnection(new \DateTime('now', new \DateTimeZone('Europe/Paris')));
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$easySolution = $this->entityManager->getRepository(Client::class)->findOneBy(['name' => 'EasySolution']);
|
||||
if($easySolution) {
|
||||
$accessToken = new AccessToken(
|
||||
identifier: bin2hex(random_bytes(40)), // Generate unique identifier
|
||||
expiry: new \DateTimeImmutable('+1 hour', new \DateTimeZone('Europe/Paris')),
|
||||
client: $easySolution,
|
||||
userIdentifier: $user->getUserIdentifier(),
|
||||
scopes: ['email profile openid apps:easySolutions'] // Empty array if no specific scopes needed
|
||||
);
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->persist($accessToken);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\UserOrganizationRoles;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<UserOrganizationRoles>
|
||||
*/
|
||||
class UserOrganizationRolesRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, UserOrganizationRoles::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return UserOrganizationRoles[] Returns an array of UserOrganizationRoles objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('u')
|
||||
// ->andWhere('u.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('u.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?UserOrganizationRoles
|
||||
// {
|
||||
// return $this->createQueryBuilder('u')
|
||||
// ->andWhere('u.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
|
|
@ -55,99 +55,18 @@ class UsersOrganizationsRepository extends ServiceEntityRepository
|
|||
->where('u.email = :email')
|
||||
->andWhere('r.name = :roleName')
|
||||
->andWhere('uo.isActive = :isActive')
|
||||
->andWhere('o.isActive = :orgIsActive') // Check if organization is active
|
||||
->setParameter('email', $userEmail)
|
||||
->setParameter('roleName', $roleName)
|
||||
->setParameter('isActive', true)
|
||||
->setParameter('orgIsActive', true) // Parameter for organization active status
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return array_map(fn($uo) => $uo->getOrganization(), $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active users grouped by organization.
|
||||
* Users with no organization are grouped under 'autre'.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getActiveUsersGroupedByOrganization(): array
|
||||
{
|
||||
$users = $this->getEntityManager()->getRepository(User::class)->getAllActiveUsers();
|
||||
$userOrgs = $this->getAllActiveUserOrganizationLinks();
|
||||
|
||||
$userToOrgs = $this->mapUserToOrganizations($userOrgs);
|
||||
|
||||
$orgs = [];
|
||||
foreach ($users as $user) {
|
||||
$userId = $user['id'];
|
||||
if (isset($userToOrgs[$userId])) {
|
||||
foreach ($userToOrgs[$userId] as $orgInfo) {
|
||||
$orgId = $orgInfo['organization_id'];
|
||||
if (!isset($orgs[$orgId])) {
|
||||
$orgs[$orgId] = [
|
||||
'organization_id' => $orgId,
|
||||
'organization_name' => $orgInfo['organization_name'],
|
||||
'users' => [],
|
||||
];
|
||||
}
|
||||
$orgs[$orgId]['users'][$userId] = $user;
|
||||
}
|
||||
} else {
|
||||
if (!isset($orgs['autre'])) {
|
||||
$orgs['autre'] = [
|
||||
'organization_id' => null,
|
||||
'organization_name' => 'autre',
|
||||
'users' => [],
|
||||
];
|
||||
}
|
||||
$orgs['autre']['users'][$userId] = $user;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert users arrays to indexed arrays
|
||||
foreach ($orgs as &$org) {
|
||||
$org['users'] = array_values($org['users']);
|
||||
}
|
||||
|
||||
return array_values($orgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active users for each organization in the given array.
|
||||
*
|
||||
* @param Organizations[] $organizations
|
||||
* @return array
|
||||
*/
|
||||
public function findActiveUsersByOrganizations(array $organizations): array
|
||||
{
|
||||
if (empty($organizations)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$userOrgs = $this->getAllActiveUserOrganizationLinks($organizations);
|
||||
|
||||
$usersByOrg = [];
|
||||
foreach ($userOrgs as $uo) {
|
||||
$org = $uo->getOrganization();
|
||||
$orgId = $org->getId();
|
||||
if (!isset($usersByOrg[$orgId])) {
|
||||
$usersByOrg[$orgId] = [
|
||||
'organization_id' => $orgId,
|
||||
'organization_name' => $org->getName(),
|
||||
'users' => [],
|
||||
];
|
||||
}
|
||||
$userId = $uo->getUsers()->getId();
|
||||
$usersByOrg[$orgId]['users'][$userId] = $uo->getUsers();
|
||||
}
|
||||
|
||||
// Convert users arrays to indexed arrays
|
||||
foreach ($usersByOrg as &$org) {
|
||||
$org['users'] = array_values($org['users']);
|
||||
}
|
||||
|
||||
return array_values($usersByOrg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Get all active UsersOrganizations links, optionally filtered by organizations.
|
||||
|
|
@ -155,7 +74,7 @@ class UsersOrganizationsRepository extends ServiceEntityRepository
|
|||
* @param Organizations[]|null $organizations
|
||||
* @return UsersOrganizations[]
|
||||
*/
|
||||
private function getAllActiveUserOrganizationLinks(array $organizations = null): array
|
||||
public function getAllActiveUserOrganizationLinks(array $organizations = null): array
|
||||
{
|
||||
$qb = $this->createQueryBuilder('uo')
|
||||
->innerJoin('uo.organization', 'o')
|
||||
|
|
@ -171,25 +90,82 @@ class UsersOrganizationsRepository extends ServiceEntityRepository
|
|||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper: Map userId to their organizations (id and name), avoiding duplicates.
|
||||
* Get the last 10 new active users for a specific organization.
|
||||
* Users are ordered by creation date (most recent first).
|
||||
*
|
||||
* @param UsersOrganizations[] $userOrgs
|
||||
* @param Organizations $organization
|
||||
* @return array
|
||||
*/
|
||||
private function mapUserToOrganizations(array $userOrgs): array
|
||||
public function getLastNewActiveUsersByOrganization(Organizations $organization): array
|
||||
{
|
||||
$userToOrgs = [];
|
||||
foreach ($userOrgs as $uo) {
|
||||
$userId = $uo->getUsers()->getId();
|
||||
$org = $uo->getOrganization();
|
||||
$orgId = $org->getId();
|
||||
$orgName = $org->getName();
|
||||
$userToOrgs[$userId][$orgId] = [
|
||||
'organization_id' => $orgId,
|
||||
'organization_name' => $orgName,
|
||||
];
|
||||
$results = $this->createQueryBuilder('uo')
|
||||
->select('u.id', 'u.surname', 'u.name', 'u.email', 'u.pictureUrl', 'u.isActive', 'uo.createdAt')
|
||||
->innerJoin('uo.users', 'u')
|
||||
->innerJoin('uo.organization', 'o')
|
||||
->where('uo.isActive = :isActive')
|
||||
->andWhere('u.isActive = :userIsActive')
|
||||
->andWhere('o.isActive = :orgIsActive')
|
||||
->andWhere('uo.organization = :organization')
|
||||
->setParameter('isActive', true)
|
||||
->setParameter('userIsActive', true)
|
||||
->setParameter('orgIsActive', true)
|
||||
->setParameter('organization', $organization)
|
||||
->orderBy('uo.createdAt', 'DESC')
|
||||
->setMaxResults(10)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// Remove duplicates by user ID (in case user has multiple roles)
|
||||
$uniqueUsers = [];
|
||||
foreach ($results as $result) {
|
||||
$userId = $result['id'];
|
||||
if (!isset($uniqueUsers[$userId])) {
|
||||
$uniqueUsers[$userId] = $result;
|
||||
}
|
||||
}
|
||||
return $userToOrgs;
|
||||
|
||||
return array_values($uniqueUsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active admin users for a specific organization.
|
||||
* Returns users who have the 'ADMIN' role in the given organization.
|
||||
*
|
||||
* @param Organizations $organization
|
||||
* @return array
|
||||
*/
|
||||
public function getAdminUsersByOrganization(Organizations $organization): array
|
||||
{
|
||||
$results = $this->createQueryBuilder('uo')
|
||||
->select('u.id', 'u.surname', 'u.name', 'u.email', 'u.pictureUrl', 'u.isActive')
|
||||
->innerJoin('uo.users', 'u')
|
||||
->innerJoin('uo.organization', 'o')
|
||||
->innerJoin('uo.role', 'r')
|
||||
->where('uo.isActive = :isActive')
|
||||
->andWhere('u.isActive = :userIsActive')
|
||||
->andWhere('o.isActive = :orgIsActive')
|
||||
->andWhere('uo.organization = :organization')
|
||||
->andWhere('r.name = :roleName')
|
||||
->setParameter('isActive', true)
|
||||
->setParameter('userIsActive', true)
|
||||
->setParameter('orgIsActive', true)
|
||||
->setParameter('organization', $organization)
|
||||
->setParameter('roleName', 'ADMIN')
|
||||
->orderBy('u.surname', 'ASC')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// Remove duplicates by user ID (in case user has multiple admin-related roles)
|
||||
$uniqueUsers = [];
|
||||
foreach ($results as $result) {
|
||||
$userId = $result['id'];
|
||||
if (!isset($uniqueUsers[$userId])) {
|
||||
$uniqueUsers[$userId] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($uniqueUsers);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Apps;
|
||||
use App\Entity\Organizations;
|
||||
use App\Entity\Roles;
|
||||
use App\Entity\UsersOrganizations;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class OrganizationsService
|
||||
{
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager)
|
||||
{
|
||||
}
|
||||
|
||||
public function getAdminUsers(Organizations $organization): array{
|
||||
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findBy(['name' => 'ADMIN']);
|
||||
return $this->entityManager->getRepository(UsersOrganizations::class)->findBy(['organization'=>$organization,
|
||||
'role' => $roleAdmin,
|
||||
'isActive' => true]);
|
||||
}
|
||||
|
||||
public function getNewUsers(Organizations $organization): array{
|
||||
return $this->entityManager->getRepository(UsersOrganizations::class)->findBy(['organization'=>$organization,
|
||||
'isActive' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all applications with organization access status
|
||||
*
|
||||
* @param Organizations $organization
|
||||
* @return array
|
||||
*/
|
||||
public function getApplicationsWithAccessStatus(Organizations $organization): array
|
||||
{
|
||||
// Get all applications
|
||||
$allApplications = $this->entityManager->getRepository(Apps::class)->findAll();
|
||||
|
||||
// Get applications the organization has access to
|
||||
$organizationApps = $organization->getApps();
|
||||
|
||||
// Create a lookup array for faster checking
|
||||
$orgAppIds = [];
|
||||
foreach ($organizationApps as $app) {
|
||||
$orgAppIds[$app->getId()] = true;
|
||||
}
|
||||
|
||||
// Build result array
|
||||
$result = [];
|
||||
foreach ($allApplications as $app) {
|
||||
$result[] = [
|
||||
'application' => $app,
|
||||
'has_access' => isset($orgAppIds[$app->getId()])
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ use App\Entity\Apps;
|
|||
use App\Entity\Organizations;
|
||||
use App\Entity\Roles;
|
||||
use App\Entity\User;
|
||||
use App\Service\UserService;
|
||||
use App\Entity\UsersOrganizations;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
|
|
@ -20,7 +21,8 @@ readonly class UserOrganizationService
|
|||
*
|
||||
* @param EntityManagerInterface $entityManager Le gestionnaire d'entités Doctrine
|
||||
*/
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager)
|
||||
public function __construct(private readonly EntityManagerInterface $entityManager,
|
||||
private readonly UserService $userService)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -335,4 +337,128 @@ readonly class UserOrganizationService
|
|||
}
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active users grouped by organization.
|
||||
* Users with no organization are grouped under 'autre'.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getActiveUsersGroupedByOrganization(): array
|
||||
{
|
||||
$users = $this->entityManager->getRepository(User::class)->getAllActiveUsers();
|
||||
$userOrgs = $this->entityManager->getRepository(UsersOrganizations::class)->getAllActiveUserOrganizationLinks();
|
||||
|
||||
$userToOrgs = $this->mapUserToOrganizations($userOrgs);
|
||||
|
||||
$orgs = [];
|
||||
foreach ($users as $user) {
|
||||
$userId = $user['id'];
|
||||
if (isset($userToOrgs[$userId])) {
|
||||
foreach ($userToOrgs[$userId] as $orgInfo) {
|
||||
$orgId = $orgInfo['organization_id'];
|
||||
if (!isset($orgs[$orgId])) {
|
||||
$orgs[$orgId] = [
|
||||
'organization_id' => $orgId,
|
||||
'organization_name' => $orgInfo['organization_name'],
|
||||
'users' => [],
|
||||
];
|
||||
}
|
||||
// $orgs[$orgId]['users'][$userId] = $user;
|
||||
$orgs[$orgId]['users'][$userId] = [
|
||||
'users' => $user,
|
||||
'is_connected' => $this->userService->isUserConnected($user['email'])
|
||||
];
|
||||
}
|
||||
} else {
|
||||
if (!isset($orgs['autre'])) {
|
||||
$orgs['autre'] = [
|
||||
'organization_id' => null,
|
||||
'organization_name' => 'autre',
|
||||
'users' => [],
|
||||
];
|
||||
}
|
||||
$orgs['autre']['users'][$userId] = [
|
||||
'users' => $user,
|
||||
'is_connected' => $this->userService->isUserConnected($user['email'])
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Convert users arrays to indexed arrays
|
||||
foreach ($orgs as &$org) {
|
||||
$org['users'] = array_values($org['users']);
|
||||
}
|
||||
|
||||
return array_values($orgs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active users for each organization in the given array.
|
||||
*
|
||||
* @param Organizations[] $organizations
|
||||
* @return array
|
||||
*/
|
||||
public function findActiveUsersByOrganizations(array $organizations): array
|
||||
{
|
||||
if (empty($organizations)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$userOrgs = $this->entityManager->getRepository(UsersOrganizations::class)->getAllActiveUserOrganizationLinks($organizations);
|
||||
|
||||
$usersByOrg = [];
|
||||
foreach ($userOrgs as $uo) {
|
||||
$org = $uo->getOrganization();
|
||||
$orgId = $org->getId();
|
||||
if (!isset($usersByOrg[$orgId])) {
|
||||
$usersByOrg[$orgId] = [
|
||||
'organization_id' => $orgId,
|
||||
'organization_name' => $org->getName(),
|
||||
'users' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$user = $uo->getUsers();
|
||||
$userId = $user->getId();
|
||||
|
||||
// Add connection status to user data
|
||||
$usersByOrg[$orgId]['users'][$userId] = [
|
||||
'users' => $user,
|
||||
'is_connected' => $this->userService->isUserConnected($user->getUserIdentifier())
|
||||
];
|
||||
}
|
||||
|
||||
// Convert users arrays to indexed arrays
|
||||
foreach ($usersByOrg as &$org) {
|
||||
$org['users'] = array_values($org['users']);
|
||||
}
|
||||
|
||||
return array_values($usersByOrg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Map userId to their organizations (id and name), avoiding duplicates.
|
||||
*
|
||||
* @param UsersOrganizations[] $userOrgs
|
||||
* @return array
|
||||
*/
|
||||
private function mapUserToOrganizations(array $userOrgs): array
|
||||
{
|
||||
$userToOrgs = [];
|
||||
foreach ($userOrgs as $uo) {
|
||||
$userId = $uo->getUsers()->getId();
|
||||
$org = $uo->getOrganization();
|
||||
$orgId = $org->getId();
|
||||
$orgName = $org->getName();
|
||||
$userToOrgs[$userId][$orgId] = [
|
||||
'organization_id' => $orgId,
|
||||
'organization_name' => $orgName,
|
||||
];
|
||||
}
|
||||
return $userToOrgs;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use App\Entity\Roles;
|
|||
use App\Entity\User;
|
||||
use App\Entity\UsersOrganizations;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use League\Bundle\OAuth2ServerBundle\Model\AccessToken;
|
||||
|
||||
class UserService
|
||||
{
|
||||
|
|
@ -59,4 +60,29 @@ class UserService
|
|||
'roleId' => $roleAdmin[0]->getId()]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user is currently connected.
|
||||
* This method check if the user is currently connected to one of the applications.
|
||||
*
|
||||
* @param String $userIdentifier
|
||||
* @return bool
|
||||
*/
|
||||
public function isUserConnected(string $userIdentifier): bool
|
||||
{
|
||||
$now = new \DateTimeImmutable('now', new \DateTimeZone('Europe/Paris'));
|
||||
$tokens = $this->entityManager->getRepository(AccessToken::class)->findBy([
|
||||
'userIdentifier' => $userIdentifier,
|
||||
'revoked' => false
|
||||
]);
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
// Assuming $token->getExpiry() returns a DateTimeInterface
|
||||
if ($token->getExpiry() > $now) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,27 @@
|
|||
{% if is_granted('ROLE_ADMIN') %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ path('user_index') }}">
|
||||
<i class="icon-grid menu-icon">{{ ux_icon('bi:menu-up', {height: '15px', width: '15px'}) }}</i>
|
||||
<i class="icon-grid menu-icon">{{ ux_icon('fa6-regular:circle-user', {height: '15px', width: '15px'}) }}</i>
|
||||
<span class="menu-title">Users</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ path('organization_index') }}">
|
||||
<i class="icon-grid menu-icon">
|
||||
{% if is_granted('ROLE_SUPER_ADMIN')%}
|
||||
{{ ux_icon('bi:buildings', {height: '15px', width: '15px'}) }}
|
||||
{% else %}
|
||||
{{ ux_icon('bi:building', {height: '15px', width: '15px'}) }}
|
||||
{% endif %}
|
||||
</i>
|
||||
<span class="menu-title">
|
||||
{% if is_granted('ROLE_SUPER_ADMIN') %}
|
||||
Organizations
|
||||
{% else %}
|
||||
Organization
|
||||
{% endif %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{% block body %}
|
||||
|
||||
|
||||
<div class="card border-0">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h3>{{ title }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{# {% if activities|length == 0 %}#}
|
||||
{# <p>Aucune activité récente.</p>#}
|
||||
{# {% else %}#}
|
||||
<ul class="list-group">
|
||||
{# {% for activity in activities %}#}
|
||||
<li class="list-group-item">
|
||||
<p> 5 mins ago</p>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<p> 5 mins ago</p>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<p> 5 mins ago</p>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<p> 5 mins ago</p>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<p> 5 mins ago</p>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<p> 5 mins ago</p>
|
||||
</li>
|
||||
|
||||
{# {% endfor %}#}
|
||||
</ul>
|
||||
{# {% endif %}#}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %} Gestion des organisations {% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="w-100 h-100 p-5 m-auto" data-controller="organization">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1>Gestion des organisations</h1>
|
||||
{# <a href="{{ path('organization_new') }}" class="btn btn-primary">Ajouter une organisation</a>#}
|
||||
</div>
|
||||
{% if organizations|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">Aucune organisation trouvée.</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<table class="table align-middle shadow">
|
||||
<thead class="table-light shadow-sm">
|
||||
<tr>
|
||||
<th>Logo</th>
|
||||
<th>Nom</th>
|
||||
<th>Email</th>
|
||||
<th>Visualiser</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for organization in organizations %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if organization.logoUrl %}
|
||||
<img src="{{ asset(organization.logoUrl) }}" alt="Organization logo" class="rounded-circle" style="width:40px; height:40px;">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ organization.name }}</td>
|
||||
<td>{{ organization.email }}</td>
|
||||
<td>
|
||||
<a href="{{ path('organization_show', {'id': organization.id}) }}" class="p-3 align-middle color-primary">
|
||||
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<div class="col-md-12 m-auto p-5">
|
||||
<div class="col d-flex justify-content-between align-items-center ">
|
||||
<h1 class="mb-4">{{ organization.name|title }} - Dashboard</h1>
|
||||
{% if is_granted("ROLE_SUER_ADMIN") %}
|
||||
{# <a href="{{ path('user_deactivate', {'id': user.id}) }}" class="btn btn-danger">Désactiver</a> #}
|
||||
{% endif %}
|
||||
</div>
|
||||
{# USER ROW#}
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<div class="row mb-4">
|
||||
<div class="col mb-3 mb-sm-0">
|
||||
{% include 'user/userListSmall.html.twig' with {
|
||||
title: 'Nouveaux utilisateurs',
|
||||
users: newUsers,
|
||||
empty_message: 'Aucun nouvel utilisateur trouvé.'
|
||||
} %}
|
||||
</div>
|
||||
<div class="col mb-3 mb-sm-0">
|
||||
{% include 'user/userListSmall.html.twig' with {
|
||||
title: 'Administrateurs',
|
||||
users: adminUsers,
|
||||
empty_message: 'Aucun administrateur trouvé.'
|
||||
} %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-auto">
|
||||
{% include 'user/userList.html.twig' with {
|
||||
title: 'Mes utilisateurs',
|
||||
} %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-3 m-auto">
|
||||
{% include 'organization/activity.html.twig' with {
|
||||
title: 'Activités récentes',
|
||||
empty_message: 'Aucune activité récente.'
|
||||
} %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{# APPLICATION ROW#}
|
||||
<div class="row">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
@ -10,43 +10,9 @@
|
|||
</div>
|
||||
|
||||
{% for org in usersByOrganization %}
|
||||
<h2 class="mt-5 mb-3">{{ org.organization_name|title }}</h2>
|
||||
<table class="table align-middle shadow">
|
||||
<thead class="table-light shadow-sm">
|
||||
<tr>
|
||||
<th>Picture</th>
|
||||
<th>Surname</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Visualiser</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in org.users %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if user.pictureUrl %}
|
||||
<img src="{{ asset(user.pictureUrl) }}" alt="User profile pic" class="rounded-circle" style="width:40px; height:40px;">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.surname }}</td>
|
||||
<td>{{ user.name }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<a href="{{ path('user_show', {'id': user.id}) }}" class="p-3 align-middle">
|
||||
<i class="icon-grid menu-icon color-primary">
|
||||
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if org.users|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">Aucun utilisateur trouvé.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% include 'user/userList.html.twig' with {
|
||||
title: org.organization_name|capitalize
|
||||
} %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -3,11 +3,13 @@
|
|||
{% block body %}
|
||||
|
||||
<div class="col-md-10 m-auto p-5">
|
||||
<div class="col d-flex justify-content-between align-items-center ">
|
||||
<h1 class="mb-4">Gestion Utilisateur</h1>
|
||||
<a href="{{ path('user_deactivate', {'id': user.id}) }}" class="btn btn-danger">Désactiver</a>
|
||||
</div>
|
||||
{% include 'elements/userInformation.html.twig' %}
|
||||
{% if is_granted("ROLE_ADMIN") %}
|
||||
<div class="col d-flex justify-content-between align-items-center ">
|
||||
<h1 class="mb-4">Gestion Utilisateur</h1>
|
||||
<a href="{{ path('user_deactivate', {'id': user.id}) }}" class="btn btn-danger">Désactiver</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include 'user/userInformation.html.twig' %}
|
||||
|
||||
<h1 class="mt-5 mb-4">Organisations</h1>
|
||||
<div class="row">
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{% block body %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card border-0">
|
||||
<div class="card-title shadow-sm p-3 d-flex justify-content-between align-items-center">
|
||||
<h2>{{ user.surname|capitalize }} {{ user.name|capitalize }}</h2>
|
||||
<a href="{{ path('user_edit', {'id': user.id}) }}" class="btn btn-primary">Modifier</a>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
{% block body %}
|
||||
|
||||
<div class="card border-0 p-3 mb-4">
|
||||
|
||||
{% if title is defined %}
|
||||
<div class="card-title d-flex justify-content-between align-items-center ">
|
||||
<h3>{{ title }}</h3>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
|
||||
<table class="table align-middle ">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Picture</th>
|
||||
<th>Surname</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Statut</th>
|
||||
<th>Visualiser</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if org.users|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">Aucun utilisateur trouvé.</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% for user in org.users %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if user.users.pictureUrl %}
|
||||
<img src="{{ asset(user.users.pictureUrl) }}" alt="User profile pic"
|
||||
class="rounded-circle"
|
||||
style="width:40px; height:40px;">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.users.surname }}</td>
|
||||
<td>{{ user.users.name }}</td>
|
||||
<td>{{ user.users.email }}</td>
|
||||
<td>
|
||||
{% if user.is_connected %}
|
||||
<span class="badge bg-success">Actif</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Inactif</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ path('user_show', {'id': user.users.id}) }}" class="p-3 align-middle">
|
||||
<i class="icon-grid menu-icon color-primary">
|
||||
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
{% block body %}
|
||||
|
||||
<div class="card border-0">
|
||||
<div class="card-title p-3 d-flex justify-content-between align-items-center ">
|
||||
<h3>{{ title }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table align-middle table-borderless">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Picture</th>
|
||||
<th>Email</th>
|
||||
<th>Visualiser</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if users|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="3" class="text-center">{{ empty_message }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if user.pictureUrl %}
|
||||
<img src="{{ asset(user.pictureUrl) }}" alt="User profile pic"
|
||||
class="rounded-circle" style="width:40px; height:40px;">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<a href="{{ path('user_show', {'id': user.id}) }}"
|
||||
class="p-3 align-middle color-primary">
|
||||
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
Loading…
Reference in New Issue