Easy_solution/src/Controller/UserController.php

779 lines
36 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Controller;
use App\Entity\Apps;
use App\Entity\Roles;
use App\Entity\User;
use App\Entity\UserOrganizatonApp;
use App\Entity\UsersOrganizations;
use App\Form\UserForm;
use App\Repository\AppsRepository;
use App\Repository\OrganizationsRepository;
use App\Repository\RolesRepository;
use App\Repository\UserRepository;
use App\Repository\UsersOrganizationsRepository;
use App\Service\AccessTokenService;
use App\Service\ActionService;
use App\Service\AwsService;
use App\Service\EmailService;
use App\Service\LoggerService;
use App\Service\OrganizationsService;
use App\Service\UserOrganizationAppService;
use App\Service\UserOrganizationService;
use App\Service\UserService;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
#[Route(path: '/user', name: 'user_')]
class UserController extends AbstractController
{
private const NOT_FOUND = 'Entity not found';
private const ACCESS_DENIED = 'Access denied';
public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly UserService $userService,
private readonly ActionService $actionService,
private readonly UserOrganizationAppService $userOrganizationAppService,
private readonly UserOrganizationService $userOrganizationService,
private readonly UserRepository $userRepository,
private readonly UsersOrganizationsRepository $uoRepository,
private readonly OrganizationsRepository $organizationRepository,
private readonly LoggerInterface $userManagementLogger,
private readonly LoggerInterface $organizationManagementLogger,
private readonly LoggerInterface $errorLogger,
private readonly LoggerInterface $securityLogger,
private readonly LoggerService $loggerService,
private readonly EmailService $emailService,
private readonly AwsService $awsService,
private readonly OrganizationsService $organizationsService,
private readonly AppsRepository $appsRepository,
private readonly RolesRepository $rolesRepository, private readonly AccessTokenService $accessTokenService,
)
{
}
//TODO: Move mailing/notification logic to event listeners/subscribers for better separation of concerns and to avoid bloating the controller with non-controller logic. Keep in mind the potential for circular dependencies and design accordingly (e.g. using interfaces or decoupled events).
#[Route(path: '/', name: 'index', methods: ['GET'])]
public function index(): Response
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$totalUsers = $this->userRepository->count(['isDeleted' => false, 'isActive' => true]);
return $this->render('user/index.html.twig', [
'users' => $totalUsers
]);
}
#[Route('/view/{id}', name: 'show', methods: ['GET'])]
public function view(int $id, Request $request): Response
{
// Accès : uniquement utilisateur authentifié
$this->denyAccessUnlessGranted('ROLE_USER');
// Utilisateur courant (acting user) via UserService
$actingUser = $this->getUser();
// Chargement de l'utilisateur cible à afficher
$user = $this->userRepository->find($id);
if (!$user) {
$this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getUserIdentifier());
$this->addFlash('danger', "L'utilisateur demandé n'existe pas.");
throw $this->createNotFoundException(self::NOT_FOUND);
}
//if hasAccessTo is false, turn to true and deny access
if (!$this->userService->isAdminOfUser($user) && !$this->isGranted('ROLE_ADMIN')) {
$this->loggerService->logAccessDenied($actingUser->getUserIdentifier());
$this->addFlash('danger', "Vous n'avez pas accès à cette information.");
throw new AccessDeniedHttpException (self::ACCESS_DENIED);
}
try {
// Paramètre optionnel de contexte organisationnel
$orgId = $request->query->get('organizationId');
if ($orgId) {
// TODO: afficher les projets de l'organisation
} else {
// Afficher tous les projets de l'utilisateur
}
} catch (\Exception $e) {
$this->loggerService->logError('error while loading user information', [
'target_user_id' => $id,
'acting_user_id' => $actingUser->getUserIdentifier(),
'error' => $e->getMessage(),
]);
$this->addFlash('danger', 'Une erreur est survenue lors du chargement des informations utilisateur.');
$referer = $request->headers->get('referer');
return $this->redirect($referer ?? $this->generateUrl('app_index'));
}
return $this->render('user/show.html.twig', [
'user' => $user,
'organizationId' => $orgId ?? null,
]);
}
#[Route('/edit/{id}', name: 'edit', methods: ['GET', 'POST'])]
public function edit(int $id, Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_USER');
$actingUser = $this->getUser();
$user = $this->userRepository->find($id);
if (!$user) {
$this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getUserIdentifier());
$this->addFlash('danger', "L'utilisateur demandé n'existe pas.");
throw $this->createNotFoundException(self::NOT_FOUND);
}
try {
if ($this->userService->isAdminOfUser($user)) {
$form = $this->createForm(UserForm::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// Handle user edit
$picture = $form->get('pictureUrl')->getData();;
$this->userService->formatUserData($user, $picture);
$user->setModifiedAt(new \DateTimeImmutable('now'));
$this->entityManager->persist($user);
$this->entityManager->flush();
//log and action
$this->loggerService->logUserAction($user->getId(), $actingUser->getUserIdentifier(), 'User information edited');
$orgId = $request->get('organizationId');
if ($orgId) {
$org = $this->organizationRepository->find($orgId);
if ($org) {
$this->actionService->createAction("Edit user information", $actingUser, $org, $user->getUserIdentifier());
$this->loggerService->logUserAction($user->getId(), $actingUser->getUserIdentifier(), 'User information edited');
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin(
$user->getId(),
$actingUser->getUserIdentifier(),
"Super Admin accessed user edit page",
);
}
$this->addFlash('success', 'Information modifié avec success.');
return $this->redirectToRoute('user_show', ['id' => $user->getId(), 'organizationId' => $orgId]);
}
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getUserIdentifier());
$this->addFlash('danger', "L'organisation n'existe pas.");
throw $this->createNotFoundException(self::NOT_FOUND);
}
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin(
$user->getId(),
$actingUser->getUserIdentifier(),
"Super Admin accessed user edit page",
);
}
$this->addFlash('success', 'Information modifié avec success.');
$this->actionService->createAction("Edit user information", $actingUser, null, $user->getUserIdentifier());
return $this->redirectToRoute('user_show', ['id' => $user->getId()]);
}
return $this->render('user/edit.html.twig', [
'user' => $user,
'form' => $form->createView(),
'organizationId' => $request->get('organizationId')
]);
}
$this->loggerService->logAccessDenied($actingUser->getUserIdentifier());
$this->addFlash('danger', "Accès non autorisé.");
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
} catch (\Exception $e) {
$this->addFlash('danger', 'Une erreur est survenue lors de la modification des informations utilisateur.');
$this->errorLogger->critical($e->getMessage());
}
// Default deny access. shouldn't reach here normally.
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
}
#[Route('/new', name: 'new', methods: ['GET', 'POST'])]
public function new(Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_USER');
try {
$actingUser =$this->getUser();
$user = new User();
$form = $this->createForm(UserForm::class, $user);
$form->handleRequest($request);
$orgId = $request->query->get('organizationId') ?? $request->request->get('organizationId');
if ($orgId) {
$org = $this->organizationRepository->find($orgId);
if (!$org) {
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getUserIdentifier());
$this->addFlash('danger', "L'organisation n'existe pas.");
throw $this->createNotFoundException(self::NOT_FOUND);
}
if (!$this->isGranted('ROLE_ADMIN') && !$this->userService->isAdminOfOrganization($org)) {
$this->loggerService->logAccessDenied($actingUser->getUserIdentifier());
$this->addFlash('danger', "Accès non autorisé.");
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
}
} else{
$this->loggerService->logAccessDenied($actingUser->getUserIdentifier());
$this->addFlash('danger', "Accès non autorisé.");
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
}
if ($form->isSubmitted() && $form->isValid()) {
$existingUser = $this->userRepository->findOneBy(['email' => $user->getEmail()]);
// Case : User exists -> link him to given organization if not already linked, else error message
if ($existingUser && $org) {
$this->userService->addExistingUserToOrganization(
$existingUser,
$org,
);
if ($this->isGranted('ROLE_ADMIN')) {
$this->loggerService->logSuperAdmin(
$existingUser->getId(),
$actingUser->getUserIdentifier(),
"Super Admin linked user to organization",
$org->getId(),
);
}
$this->addFlash('success', 'Utilisateur ajouté avec succès à l\'organisation. ');
return $this->redirectToRoute('organization_show', ['id' => $orgId]);
}
// Case : user doesn't already exist
$picture = $form->get('pictureUrl')->getData();
$this->userService->createNewUser($user, $actingUser, $picture);
$this->userService->linkUserToOrganization(
$user,
$org,
);
$this->addFlash('success', 'Nouvel utilisateur créé et ajouté à l\'organisation avec succès. ');
return $this->redirectToRoute('organization_show', ['id' => $orgId]);
}
return $this->render('user/new.html.twig', [
'user' => $user,
'form' => $form->createView(),
'organizationId' => $orgId,
]);
} catch (\Exception $e) {
$this->errorLogger->critical($e->getMessage());
if ($orgId) {
$this->addFlash('danger', 'Une erreur est survenue lors de la création de l\'utilisateur pour l\'organisation .');
return $this->redirectToRoute('organization_show', ['id' => $orgId]);
}
$this->addFlash('danger', 'Une erreur est survenue lors de la création de l\'utilisateur.');
return $this->redirectToRoute('user_index');
}
}
/**
* Endpoint to activate/deactivate a user (soft delete)
* If deactivating, also deactivate all org links and revoke tokens
*/
#[Route('/activeStatus/{id}', name: 'active_status', methods: ['POST'])]
public function activeStatus(int $id, Request $request): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$actingUser =$this->getUser();
$status = $request->request->get('status');
try {
$user = $this->userRepository->find($id);
if (!$user) {
$this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
if ($status === 'deactivate') {
$user->setIsActive(false);
$this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, $user);
if ($this->userService->isUserConnected($user->getUserIdentifier())) {
$this->accessTokenService->revokeUserTokens($user->getUserIdentifier());
}
$user->setModifiedAt(new \DateTimeImmutable('now'));
$this->entityManager->persist($user);
$this->entityManager->flush();
$this->loggerService->logUserAction($user->getId(), $actingUser->getUserIdentifier(), 'User deactivated');
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin(
$user->getId(),
$actingUser->getUserIdentifier(),
'Super admin deactivated user'
);
}
$this->actionService->createAction('Deactivate user', $actingUser, null, $user->getUserIdentifier());
return new JsonResponse(['status' => 'deactivated'], Response::HTTP_OK);
}
// Activate
if ($status === 'activate') {
$user->setIsActive(true);
$user->setModifiedAt(new \DateTimeImmutable('now'));
$this->entityManager->persist($user);
$this->entityManager->flush();
$this->loggerService->logUserAction($user->getId(), $actingUser->getUserIdentifier(), 'User activated');
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin(
$user->getId(),
$actingUser->getUserIdentifier(),
'Super admin activated user'
);
}
$this->actionService->createAction('Activate user', $actingUser, null, $user->getUserIdentifier());
return new JsonResponse(['status' => 'activated'], Response::HTTP_OK);
}
// Invalid status
$this->loggerService->logError('Invalid status provided for activeStatus', [
'requested_status' => $status,
'target_user_id' => $id,
]);
return new JsonResponse(['error' => 'Status invalide'], Response::HTTP_BAD_REQUEST);
} catch (\Throwable $e) {
// Application-level error logging → error.log (via error channel)
$this->errorLogger->critical($e->getMessage());
// Preserve 403/404 semantics, 500 for everything else
if ($e instanceof NotFoundHttpException || $e instanceof AccessDeniedException) {
throw $e;
}
return new JsonResponse(['error' => 'Une erreur est survenue'], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
#[Route('/organization/activateStatus/{id}', name: 'activate_organization', methods: ['POST'])]
public function activateStatusOrganization(int $id, Request $request): JsonResponse
{
$this->denyAccessUnlessGranted('ROLE_USER');
$actingUser = $this->getUser();
try {
$user = $this->userRepository->find($id);
if (!$user) {
$this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
if ($this->userService->isAdminOfUser($user)) {
$orgId = $request->request->get('organizationId');
$org = $this->organizationRepository->find($orgId);
if (!$org) {
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
$uo = $this->uoRepository->findOneBy(['users' => $user,
'organization' => $org]);
if (!$uo) {
$this->loggerService->logEntityNotFound('UsersOrganization', ['user_id' => $user->getId(),
'organization_id' => $org->getId()], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
$status = $request->request->get('status');
if ($status === 'deactivate') {
$uo->setIsActive(false);
$this->userOrganizationAppService->deactivateAllUserOrganizationsAppLinks($uo);
$this->entityManager->persist($uo);
$this->entityManager->flush();
$data = ['user' => $user,
'organization' => $org];
$this->organizationsService->notifyOrganizationAdmins($data, "USER_DEACTIVATED");
$this->loggerService->logOrganizationInformation($org->getId(), $actingUser->getUserIdentifier(), "UO link deactivated with uo id : {$uo->getId()}");
$this->actionService->createAction("Deactivate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier());
return new JsonResponse(['status' => 'deactivated'], Response::HTTP_OK);
}
if ($status === "activate") {
$uo->setIsActive(true);
$this->entityManager->persist($uo);
$this->entityManager->flush();
$this->loggerService->logOrganizationInformation($orgId, $actingUser->getUserIdentifier(), "UO link activated with uo id : {$uo->getId()}");
$this->actionService->createAction("Activate user in organization", $actingUser, $org, $org->getName() . " for user " . $user->getUserIdentifier());
$data = ['user' => $user,
'organization' => $org];
$this->organizationsService->notifyOrganizationAdmins($data, "USER_ACTIVATED");
return new JsonResponse(['status' => 'activated'], Response::HTTP_OK);
}
//invalid status
$this->loggerService->logError('Invalid status provided for activateStatusOrganization', [
'requested_status' => $status,
'target_user_id' => $id,
'organization_id' => $orgId,
]);
throw $this->createNotFoundException(self::NOT_FOUND);
}
} catch (\Exception $exception) {
$this->loggerService->logCritical($exception->getMessage());
}
throw $this->createNotFoundException(self::NOT_FOUND);
}
#[Route('/delete/{id}', name: 'delete', methods: ['GET', 'POST'])]
public function delete(int $id, Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_SUPER_ADMIN');
$actingUser = $this->getUser();
try {
$user = $this->userRepository->find($id);
if (!$user) {
// Security/audit log for missing user
$this->loggerService->logEntityNotFound('User', ['id' => $id], $actingUser->getUserIdentifier());
$this->addFlash('danger', "L'utilisateur demandé n'existe pas.");
throw $this->createNotFoundException(self::NOT_FOUND);
}
// Soft delete the user
$user->setIsActive(false);
$user->setIsDeleted(true);
$this->userService->deleteProfilePicture($user);
$user->setModifiedAt(new \DateTimeImmutable('now'));
// Deactivate all org links
$this->userOrganizationService->deactivateAllUserOrganizationLinks($actingUser, $user);
$this->loggerService->logOrganizationInformation($user->getId(), $actingUser->getUserIdentifier(), 'All user organization links deactivated');
// Revoke tokens if connected
if ($this->userService->isUserConnected($user->getUserIdentifier())) {
$this->accessTokenService->revokeUserTokens($user->getUserIdentifier());
}
$this->entityManager->flush();
// User management log
$this->loggerService->logUserAction($user->getId(), $actingUser->getUserIdentifier(), 'User deleted');
// Super admin log (standardized style)
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
$this->loggerService->logSuperAdmin(
$user->getId(),
$actingUser->getUserIdentifier(),
'Super admin deleted user'
);
}
$this->actionService->createAction('Delete user', $actingUser, null, $user->getUserIdentifier());
// Notify organization admins (user may belong to multiple organizations)
try {
$data = [
'user' => $user,
'organization' => null,
];
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_DELETED');
} catch (\Throwable $e) {
$this->loggerService->logCritical($e->getMessage(), [
'target_user_id' => $id,
'acting_user_id' => $actingUser?->getId(),
]);
}
$this->addFlash('success', 'Utilisateur supprimé avec succès.');
return $this->redirectToRoute('user_index');
} catch (\Exception $e) {
// Route-level error logging → error.log
$this->loggerService->logCritical('error while deleting user', [
'target_user_id' => $id,
'acting_user_id' => $actingUser?->getId(),
'error' => $e->getMessage(),
]);
if ($e instanceof NotFoundHttpException) {
throw $e; // keep 404 semantics
}
$this->addFlash('danger', 'Erreur lors de la suppression de l\'utilisateur\.');
return $this->redirectToRoute('user_index');
}
}
/*
* AJAX endpoint for user listing with pagination
* Get all the users that aren´t deleted and are active
*/
#[Route(path: '/data', name: 'data', methods: ['GET'])]
public function data(Request $request): JsonResponse
{
$this->denyAccessUnlessGranted("ROLE_ADMIN");
$page = max(1, $request->query->getInt('page', 1));
$size = max(1, $request->query->getInt('size', 10));
$filters = $request->query->all('filter', []);
// Call the repository
$paginator = $this->userRepository->findActiveUsersForTabulator($page, $size, $filters);
$total = count($paginator);
$data = array_map(function (User $user) {
return [
'id' => $user->getId(),
'pictureUrl' => $user->getPictureUrl(),
'name' => $user->getSurname(),
'prenom' => $user->getName(),
'email' => $user->getEmail(),
'isConnected' => $this->userService->isUserConnected($user->getUserIdentifier()),
'showUrl' => $this->generateUrl('user_show', ['id' => $user->getId()]),
'statut' => $user->isActive(),
];
}, iterator_to_array($paginator));
return $this->json([
'data' => $data,
'last_page' => (int)ceil($total / $size),
'total' => $total,
]);
}
/*
* AJAX endpoint for new users listing
* Get the 5 most recently created users for an organization
*/
#[Route(path: '/data/new', name: 'dataNew', methods: ['GET'])]
public function dataNew(Request $request): JsonResponse
{
$actingUser = $this->getUser();
$orgId = $request->query->get('orgId');
$org = $this->organizationRepository->find($orgId);
if (!$org) {
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
if ($this->userService->isAdminOfOrganization($org) || $this->isGranted("ROLE_ADMIN")) {
$uos = $this->uoRepository->findBy(['organization' => $org, 'statut' => ["ACCEPTED", "INVITED"]],
orderBy: ['createdAt' => 'DESC'], limit: 5);
// Map to array (keep isConnected)
$data = array_map(function (UsersOrganizations $uo) {
$user = $uo->getUsers();
$initials = $user->getName()[0] . $user->getSurname()[0];
return [
'pictureUrl' => $user->getPictureUrl(),
'email' => $user->getEmail(),
'isConnected' => $this->userService->isUserConnected($user->getUserIdentifier()),
'showUrl' => $this->generateUrl('user_show', ['id' => $user->getId()]),
'initials' => strtoupper($initials),
];
}, $uos);
return $this->json([
'data' => $data,
]);
}
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
}
/*
* AJAX endpoint for admin users listing
* Get all admin users for an organization
*/
#[Route(path: '/data/admin', name: 'dataAdmin', methods: ['GET'])]
public function dataAdmin(Request $request): JsonResponse
{
$actingUser = $this->getUser();
$orgId = $request->query->get('orgId');
$org = $this->organizationRepository->find($orgId);
if (!$org) {
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
if ($this->userService->isAdminOfOrganization($org) || $this->isGranted("ROLE_ADMIN")) {
$uos = $this->uoRepository->findBy(['organization' => $org]);
$roleAdmin = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'ADMIN']);
$users = [];
foreach ($uos as $uo) {
if ($this->entityManager->getRepository(UserOrganizatonApp::class)->findOneBy(['userOrganization' => $uo, 'role' => $roleAdmin])) {
$users[] = $uo;
}
}
// Map to array (keep isConnected)
$data = array_map(function (UsersOrganizations $uo) {
$user = $uo->getUsers();
$initials = $user->getName()[0] . $user->getSurname()[0];
return [
'pictureUrl' => $user->getPictureUrl(),
'email' => $user->getEmail(),
'isConnected' => $this->userService->isUserConnected($user->getUserIdentifier()),
'showUrl' => $this->generateUrl('user_show', ['id' => $user->getId()]),
'initials' => strtoupper($initials),
];
}, $users);
return $this->json([
'data' => $data,
]);
}
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
}
/*
* AJAX endpoint for All users in an organization
*/
#[Route(path: '/data/organization', name: 'dataUserOrganization', methods: ['GET'])]
public function dataUserOrganization(Request $request): JsonResponse
{
$actingUser = $this->getUser();
$orgId = $request->query->get('orgId');
$org = $this->organizationRepository->find($orgId);
if (!$org) {
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
// Security Check
if (!$this->isGranted("ROLE_ADMIN") && !$this->userService->isAdminOfOrganization($org)) {
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
}
// Params extraction
$page = max(1, $request->query->getInt('page', 1));
$size = max(1, $request->query->getInt('size', 10));
$filters = $request->query->all('filter') ?? [];
// Get paginated results from Repository
$paginator = $this->uoRepository->findByOrganizationWithFilters($org, $page, $size, $filters);
$total = count($paginator);
// Format the data using your existing service method
$data = $this->userService->formatStatutForOrganizations(iterator_to_array($paginator));
return $this->json([
'data' => $data,
'last_page' => (int)ceil($total / $size),
'total' => $total,
]);
}
#[Route(path: '/organization/resend-invitation/{userId}', name: 'resend_invitation', methods: ['POST'])]
public function resendInvitation(int $userId, Request $request): JsonResponse
{
$this->denyAccessUnlessGranted("ROLE_ADMIN");
$actingUser = $this->getUser();
if ($this->userService->hasAccessTo($actingUser, true)) {
$orgId = $request->get('organizationId');
$org = $this->organizationRepository->find($orgId);
if (!$org) {
$this->loggerService->logEntityNotFound('Organization', ['id' => $orgId], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
$user = $this->userRepository->find($userId);
if (!$user) {
$this->loggerService->logEntityNotFound('User', ['id' => $user->getId()], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
$token = $this->userService->generatePasswordToken($user, $org->getId());
if ($user->getLastConnection() !== null) {
$this->userService->sendExistingUserNotifications($user, $org, $actingUser);
} else {
$uo = $this->uoRepository->findOneBy(['users' => $user,
'organization' => $org,
'statut' => "INVITED"]);
if (!$uo) {
$this->loggerService->logEntityNotFound('UsersOrganization', [
'user_id' => $user->getId(),
'organization_id' => $orgId], $actingUser->getUserIdentifier());
throw $this->createNotFoundException(self::NOT_FOUND);
}
$uo->setModifiedAt(new \DateTimeImmutable());
try {
$data = ['user' => $uo->getUsers(), 'organization' => $uo->getOrganization()];
$this->emailService->sendPasswordSetupEmail($user, $token);
$this->loggerService->logEmailSent($userId, $org->getId(), 'Invitation Resent');
$this->organizationsService->notifyOrganizationAdmins($data, 'USER_INVITED');
return $this->json(['message' => 'Invitation envoyée avec success.'], Response::HTTP_OK);
} catch (\Exception $e) {
$this->loggerService->logCritical('Error while resending invitation', [
'target_user_id' => $user->getId(),
'organization_id' => $orgId,
'acting_user_id' => $actingUser->getUserIdentifier(),
'error' => $e->getMessage(),
]);
return $this->json(['message' => 'Erreur lors de l\'envoie du mail.'], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
}
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
}
#[Route(path: '/accept-invitation', name: 'accept', methods: ['GET'])]
public function acceptInvitation(Request $request): Response
{
$token = $request->get('token');
$userId = $request->get('id');
if (!$token || !$userId) {
$this->loggerService->logEntityNotFound('Token or UserId missing in accept invitation', [
'token' => $token,
'user_id' => $userId
],
null);
throw $this->createNotFoundException('Invalid invitation link.');
}
$user = $this->userRepository->find($userId);
if (!$user) {
$this->loggerService->logEntityNotFound('User not found in accept invitation', [
'user_id' => $userId
], null);
throw $this->createNotFoundException(self::NOT_FOUND);
}
if (!$this->userService->isPasswordTokenValid($user, $token)) {
$this->loggerService->logError('Token or UserId mismatch in accept invitation', [
'token' => $token,
'user_id' => $userId
]);
throw $this->createNotFoundException('Invalid or expired invitation token.');
}
$orgId = $this->userService->getOrgFromToken($token);
if ($orgId) {
$uo = $this->uoRepository->findOneBy(['users' => $user, 'organization' => $orgId]);
if (!$uo || $uo->getStatut() !== 'INVITED') {
$this->loggerService->logEntityNotFound('UsersOrganization not found or not in INVITED status in accept invitation', [
'user_id' => $user->getId(),
'organization_id' => $orgId
], null);
throw $this->createNotFoundException('No pending invitation found for this user and organization.');
}
$uo->setModifiedAt(new \DateTimeImmutable());
$uo->setStatut("ACCEPTED");
$uo->setIsActive(true);
$this->entityManager->persist($uo);
$this->entityManager->flush();
$this->loggerService->logUserAction($user->getId(), $user->getId(), "User accepted invitation for organization id : {$orgId}");
$this->loggerService->logOrganizationInformation($orgId, $user->getId(), "User accepted invitation with uo id : {$uo->getId()}");
}
return $this->render('security/login.html.twig', ['error'=> null, 'last_username' => $user->getEmail()]);
}
}