Compare commits
16 Commits
2e99457e16
...
f2166b604e
| Author | SHA1 | Date |
|---|---|---|
|
|
f2166b604e | |
|
|
8d92d3f9fc | |
|
|
5ceed1f2f2 | |
|
|
450543fab7 | |
|
|
d543e69863 | |
|
|
7021b28163 | |
|
|
c55e9fa039 | |
|
|
bdf9f0478e | |
|
|
1053a2ab22 | |
|
|
6efbeb0fa2 | |
|
|
1ee9a0110b | |
|
|
cbdb47fb17 | |
|
|
e6c8d5a462 | |
|
|
7e272b2b2f | |
|
|
6670fbc8b8 | |
|
|
1e8d5e1eaf |
5
.env
5
.env
|
|
@ -62,3 +62,8 @@ MERCURE_PUBLIC_URL=https://example.com/.well-known/mercure
|
|||
# The secret used to sign the JWTs
|
||||
MERCURE_JWT_SECRET="!ChangeThisMercureHubJWTSecretKey!"
|
||||
###< symfony/mercure-bundle ###
|
||||
|
||||
###> aws/aws-sdk-php-symfony ###
|
||||
AWS_KEY=not-a-real-key
|
||||
AWS_SECRET=@@not-a-real-secret
|
||||
###< aws/aws-sdk-php-symfony ###
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
/.env.*.local
|
||||
/config/secrets/prod/prod.decrypt.private.php
|
||||
/public/bundles/
|
||||
/public/uploads/
|
||||
/var/
|
||||
/vendor/
|
||||
###< symfony/framework-bundle ###
|
||||
|
|
|
|||
|
|
@ -8,6 +8,16 @@
|
|||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mercure" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mercure-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/firebase/php-jwt" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/aws/aws-crt-php" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/aws/aws-sdk-php" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/aws/aws-sdk-php-symfony" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/guzzle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/promises" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/knplabs/knp-time-bundle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/mtdowling/jmespath.php" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/psr/http-client" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor/ralouphie/getallheaders" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@
|
|||
<path value="$PROJECT_DIR$/vendor/lcobucci/jwt" />
|
||||
<path value="$PROJECT_DIR$/vendor/lcobucci/clock" />
|
||||
<path value="$PROJECT_DIR$/vendor/twig/twig" />
|
||||
<path value="$PROJECT_DIR$/vendor/twig/extra-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/composer" />
|
||||
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
|
||||
|
|
@ -163,6 +162,17 @@
|
|||
<path value="$PROJECT_DIR$/vendor/symfony/mercure" />
|
||||
<path value="$PROJECT_DIR$/vendor/symfony/mercure-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/firebase/php-jwt" />
|
||||
<path value="$PROJECT_DIR$/vendor/doctrine/cache" />
|
||||
<path value="$PROJECT_DIR$/vendor/knplabs/knp-time-bundle" />
|
||||
<path value="$PROJECT_DIR$/vendor/aws/aws-sdk-php" />
|
||||
<path value="$PROJECT_DIR$/vendor/aws/aws-crt-php" />
|
||||
<path value="$PROJECT_DIR$/vendor/ralouphie/getallheaders" />
|
||||
<path value="$PROJECT_DIR$/vendor/aws/aws-sdk-php-symfony" />
|
||||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/psr7" />
|
||||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/promises" />
|
||||
<path value="$PROJECT_DIR$/vendor/guzzlehttp/guzzle" />
|
||||
<path value="$PROJECT_DIR$/vendor/mtdowling/jmespath.php" />
|
||||
<path value="$PROJECT_DIR$/vendor/psr/http-client" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="8.2" />
|
||||
|
|
|
|||
24
README.MD
24
README.MD
|
|
@ -6,20 +6,20 @@
|
|||
- Stimulus
|
||||
- Turbo
|
||||
- Bootstrap 5.3
|
||||
- Symfony UX toogle password (https://ux.symfony.com/toggle-password)
|
||||
- Les icones sont gérées via symfony UX (https://ux.symfony.com/icons)
|
||||
- Les icones sont prises en prioritées dans la bibliothèque bootstrap
|
||||
- Les icones n'éxistants pas dans cette bibliothèques seront prises en priorité dans fontawesome regular (pour une cohérence visuelle)
|
||||
- Sinon privilégier la bibliothèque ayant le visuel le plus proche
|
||||
|
||||
### Version 0.1 : (17/03/2025)
|
||||
- Contient la logique de login mot de passe avec une entité user (email et password seuelement)
|
||||
- Une base de template twig public est gérée pour les page n'ayant pas besoin de menu
|
||||
- La page de login est designé
|
||||
- Une base de template est gérée pour toutes les pages de l'application aya,t besoin de l'entête et du menu général
|
||||
- Une ébauche de page d'accueil est en cours
|
||||
|
||||
### Installation
|
||||
#### Database
|
||||
```bash
|
||||
php bin/console doctrine:database:create
|
||||
php bin/console doctrine:schema:update --force
|
||||
```
|
||||
#### SQL
|
||||
```bash
|
||||
insert into public.roles (id, name, created_at)
|
||||
values (3, 'USER', '2025-05-21 13:22:52'),
|
||||
(2, 'ADMIN', '2025-05-21 13:22:52'),
|
||||
(1, 'SUPER ADMIN', '2025-05-21 13:22:52');
|
||||
```
|
||||
#### Choices.js
|
||||
```bash
|
||||
php bin/console importmap:require choices.js
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
|
|
@ -6,6 +6,8 @@
|
|||
--delete : #E42E31;
|
||||
--disable : #A3A3A3;
|
||||
--check : #80F20E;
|
||||
--secondary : #cc664c;
|
||||
--secondary-dark : #a5543d;
|
||||
}
|
||||
|
||||
html {
|
||||
|
|
@ -113,4 +115,17 @@ body {
|
|||
}
|
||||
.color-primary-dark{
|
||||
color: var(--primary-blue-dark);
|
||||
}
|
||||
|
||||
.btn-secondary{
|
||||
background: var(--secondary);
|
||||
color : #FFFFFF;
|
||||
border: var(--secondary);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.btn-secondary:hover{
|
||||
background: var(--secondary-dark);
|
||||
color : #FFFFFF;
|
||||
border: var(--secondary);
|
||||
}
|
||||
|
|
@ -8,11 +8,13 @@
|
|||
"ext-ctype": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-openssl": "*",
|
||||
"aws/aws-sdk-php-symfony": "^2.8",
|
||||
"doctrine/dbal": "^3",
|
||||
"doctrine/doctrine-bundle": "^2.14",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.4",
|
||||
"doctrine/orm": "^3.3",
|
||||
"firebase/php-jwt": "^6.11",
|
||||
"knplabs/knp-time-bundle": "^2.4",
|
||||
"league/oauth2-server-bundle": "^0.11.0",
|
||||
"nelmio/cors-bundle": "^2.5",
|
||||
"phpdocumentor/reflection-docblock": "^5.6",
|
||||
|
|
@ -49,7 +51,6 @@
|
|||
"symfony/validator": "7.2.*",
|
||||
"symfony/web-link": "7.2.*",
|
||||
"symfony/yaml": "7.2.*",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
},
|
||||
"config": {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -9,7 +9,6 @@ return [
|
|||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
|
||||
Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
|
||||
|
|
@ -18,4 +17,6 @@ return [
|
|||
League\Bundle\OAuth2ServerBundle\LeagueOAuth2ServerBundle::class => ['all' => true],
|
||||
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
|
||||
Symfony\Bundle\MercureBundle\MercureBundle::class => ['all' => true],
|
||||
Knp\Bundle\TimeBundle\KnpTimeBundle::class => ['all' => true],
|
||||
Aws\Symfony\AwsBundle::class => ['all' => true],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
aws:
|
||||
version: latest
|
||||
region: "%env(AWS_REGION)%"
|
||||
credentials:
|
||||
key: "%env(AWS_KEY)%"
|
||||
secret: "%env(AWS_SECRET)%"
|
||||
S3:
|
||||
region: "%env(AWS_REGION)%"
|
||||
endpoint: "%env(AWS_ENDPOINT)%"
|
||||
use_path_style_endpoint: true
|
||||
signature_version: 'v4'
|
||||
|
|
@ -4,6 +4,9 @@
|
|||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
aws_url: '%env(AWS_ENDPOINT)%'
|
||||
aws_public_url: '%env(AWS_ENDPOINT)%'
|
||||
logos_directory: '%kernel.project_dir%/public/uploads/logos'
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
|
|
@ -22,6 +25,9 @@ services:
|
|||
App\EventSubscriber\:
|
||||
resource: '../src/EventSubscriber/'
|
||||
tags: ['kernel.event_subscriber']
|
||||
App\Service\AwsService:
|
||||
arguments:
|
||||
$awsPublicUrl: '%aws_public_url%'
|
||||
App\EventSubscriber\ScopeResolveListener:
|
||||
tags:
|
||||
- { name: kernel.event_listener, event: league.oauth2_server.event.scope_resolve, method: onScopeResolve }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250804084150 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE actions ADD organization_id INT DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE actions ADD CONSTRAINT FK_548F1EF32C8A3DE FOREIGN KEY (organization_id) REFERENCES organizations (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('CREATE INDEX IDX_548F1EF32C8A3DE ON actions (organization_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE actions DROP CONSTRAINT FK_548F1EF32C8A3DE');
|
||||
$this->addSql('DROP INDEX IDX_548F1EF32C8A3DE');
|
||||
$this->addSql('ALTER TABLE actions DROP organization_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250804085615 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP INDEX uniq_548f1ef67b3b43d');
|
||||
$this->addSql('CREATE INDEX IDX_548F1EF67B3B43D ON actions (users_id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('DROP INDEX IDX_548F1EF67B3B43D');
|
||||
$this->addSql('CREATE UNIQUE INDEX uniq_548f1ef67b3b43d ON actions (users_id)');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250804101742 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE actions ADD description VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE actions DROP description');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20250804121445 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE apps ADD description_small VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE SCHEMA public');
|
||||
$this->addSql('ALTER TABLE apps DROP description_small');
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
|
|
@ -5,34 +5,36 @@ namespace App\Controller;
|
|||
use App\Entity\Apps;
|
||||
use App\Entity\Roles;
|
||||
use App\Entity\UsersOrganizations;
|
||||
use App\Form\OrganizationForm;
|
||||
use App\Service\ActionService;
|
||||
use App\Service\OrganizationsService;
|
||||
use App\Service\UserOrganizationService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use App\Entity\Organizations;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Contracts\Service\Attribute\Required;
|
||||
|
||||
#[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)
|
||||
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
|
||||
#[Route('/', name: 'index', methods: ['GET'])]
|
||||
public function index(): Response
|
||||
{
|
||||
if($this->isGranted('ROLE_SUPER_ADMIN'))
|
||||
{
|
||||
if ($this->isGranted('ROLE_SUPER_ADMIN')) {
|
||||
$organizations = $this->entityManager->getRepository(Organizations::class)->findBy(['isActive' => true]);
|
||||
} else{
|
||||
} else {
|
||||
$user = $this->getUser();
|
||||
if (!$user) {
|
||||
return $this->redirectToRoute('app_login');
|
||||
|
|
@ -40,7 +42,7 @@ class OrganizationController extends AbstractController
|
|||
$userIdentifier = $user->getUserIdentifier();
|
||||
|
||||
$organizations = $this->entityManager->getRepository(UsersOrganizations::class)->findOrganizationsByUserEmailAndRoleName($userIdentifier, 'ADMIN');
|
||||
if(!$organizations) {
|
||||
if (!$organizations) {
|
||||
// if user is not admin in any organization, throw access denied
|
||||
throw $this->createNotFoundException(self::ACCESS_DENIED);
|
||||
}
|
||||
|
|
@ -51,23 +53,52 @@ class OrganizationController extends AbstractController
|
|||
]);
|
||||
}
|
||||
|
||||
#[Route(path: '/{id}', name: 'show', methods: ['GET'])]
|
||||
public function show(int $id): Response
|
||||
#[Route('/new', name: 'new', methods: ['GET', 'POST'])]
|
||||
public function new(Request $request): Response
|
||||
{
|
||||
if (!$this->isGranted('ROLE_SUPER_ADMIN')) {
|
||||
throw $this->createNotFoundException(self::ACCESS_DENIED);
|
||||
}
|
||||
$form = $this->createForm(OrganizationForm::class);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$organization = $form->getData();
|
||||
// dd($form);
|
||||
$logoFile = $form->get('logoUrl')->getData();
|
||||
|
||||
if ($logoFile) {
|
||||
$currentDate = (new \DateTime())->format('Y-m-d');
|
||||
$organizationName = preg_replace('/[^a-zA-Z0-9]/', '_', $organization->getName());
|
||||
$extension = $logoFile->guessExtension();
|
||||
$newFilename = $currentDate . '_' . $organizationName . $extension;
|
||||
// Move the file to the directory where logos are stored
|
||||
$logoFile->move(
|
||||
$this->getParameter('logos_directory'),
|
||||
$newFilename
|
||||
);
|
||||
|
||||
// Update the 'logoUrl' property to store the file name
|
||||
$organization->setLogoUrl($newFilename);
|
||||
}
|
||||
$this->entityManager->persist($organization);
|
||||
$this->entityManager->flush();
|
||||
$this->addFlash('success', 'Organization created successfully');
|
||||
return $this->redirectToRoute('organization_index');
|
||||
}
|
||||
return $this->render('organization/new.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/{id}', name: 'show', requirements: ['id' => '\d+'], methods: ['GET'])]
|
||||
public function show(int $id, ActionService $actionService): 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);
|
||||
|
|
@ -78,7 +109,21 @@ class OrganizationController extends AbstractController
|
|||
// get all applications
|
||||
$applications = $this->organizationsService->getApplicationsWithAccessStatus($organization);
|
||||
|
||||
}else{
|
||||
$actions = $organization->getActions()->toArray();
|
||||
usort($actions, static function ($a, $b) {
|
||||
return $b->getDate() <=> $a->getDate();
|
||||
});
|
||||
//get the last 10 activities
|
||||
$actions = array_slice($actions, 0, 10);
|
||||
$activities = array_map(static function ($activity) use ($actionService) {
|
||||
return [
|
||||
'date' => $activity->getDate(), // or however you access the date
|
||||
'actionType' => $activity->getActionType(),
|
||||
'users' => $activity->getUsers(),
|
||||
'color' => $actionService->getActivityColor($activity->getDate())
|
||||
];
|
||||
}, $actions);
|
||||
} else {
|
||||
throw $this->createNotFoundException(self::ACCESS_DENIED);
|
||||
}
|
||||
|
||||
|
|
@ -86,9 +131,52 @@ class OrganizationController extends AbstractController
|
|||
'organization' => $organization,
|
||||
'adminUsers' => $adminUsers,
|
||||
'newUsers' => $newUsers,
|
||||
'org' => $org[0],
|
||||
'org' => !empty($org) ? $org[0] : null,
|
||||
'applications' => $applications,
|
||||
'activities' => $activities
|
||||
]);
|
||||
}
|
||||
|
||||
#[Route('/edit/{id}', name: 'edit', requirements: ['id' => '\d+'], methods: ['GET', 'POST'])]
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$id = $request->attributes->get('id');
|
||||
if (!$this->isGranted('ROLE_SUPER_ADMIN')) {
|
||||
throw $this->createNotFoundException(self::ACCESS_DENIED);
|
||||
}
|
||||
$organization = $this->entityManager->getRepository(Organizations::class)->find($id);
|
||||
if (!$organization) {
|
||||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||||
}
|
||||
$form = $this->createForm(OrganizationForm::class, $organization);
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$logoFile = $form->get('logoUrl')->getData();
|
||||
|
||||
if ($logoFile) {
|
||||
$currentDate = (new \DateTime())->format('Y-m-d');
|
||||
$organizationName = preg_replace('/[^a-zA-Z0-9]/', '_', $organization->getName());
|
||||
$extension = $logoFile->guessExtension();
|
||||
$newFilename = $currentDate . '_' . $organizationName . '.' . $extension;
|
||||
// Move the file to the directory where logos are stored
|
||||
$logoFile->move(
|
||||
$this->getParameter('logos_directory'),
|
||||
$newFilename
|
||||
);
|
||||
|
||||
// Update the 'logoUrl' property to store the file name
|
||||
$organization->setLogoUrl($newFilename);
|
||||
}
|
||||
$this->entityManager->persist($organization);
|
||||
$this->entityManager->flush();
|
||||
$this->addFlash('success', 'Organization updated successfully');
|
||||
return $this->redirectToRoute('organization_index');
|
||||
}
|
||||
return $this->render('organization/edit.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'organization' => $organization,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Actions;
|
||||
use App\Entity\Apps;
|
||||
use App\Entity\Organizations;
|
||||
use App\Entity\Roles;
|
||||
use App\Entity\User;
|
||||
use App\Form\UserForm;
|
||||
|
|
@ -61,7 +63,7 @@ class UserController extends AbstractController
|
|||
* GET /user/{id} - Show specific user (show/member)
|
||||
*/
|
||||
#[Route('/{id}', name: 'show', requirements: ['id' => '\d+'], methods: ['GET'])]
|
||||
public function show(int $id, EntityManagerInterface $entityManager): Response
|
||||
public function show(int $id, EntityManagerInterface $entityManager, Request $request): Response
|
||||
{
|
||||
if (!$this->isGranted('ROLE_SUPER_ADMIN')) {
|
||||
throw $this->createAccessDeniedException(self::ACCESS_DENIED);
|
||||
|
|
@ -71,8 +73,11 @@ class UserController extends AbstractController
|
|||
if (!$user) {
|
||||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||||
}
|
||||
|
||||
$userOrganizations = $this->userOrganizationService->getUserOrganizations($user);
|
||||
if($request->query->has('organizationId')) {
|
||||
$userOrganizations = $this->userOrganizationService->getUserOrganizations($user, $request->query->get('organizationId'));
|
||||
}else{
|
||||
$userOrganizations = $this->userOrganizationService->getUserOrganizations($user);
|
||||
}
|
||||
|
||||
return $this->render('user/show.html.twig', [
|
||||
'user' => $user,
|
||||
|
|
@ -87,6 +92,7 @@ class UserController extends AbstractController
|
|||
public function new(Request $request): Response
|
||||
{
|
||||
$form = $this->createForm(UserForm::class);
|
||||
$organizationId = $request->query->get('organizationId');
|
||||
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
|
@ -95,12 +101,38 @@ class UserController extends AbstractController
|
|||
$data = $form->getData();
|
||||
// Handle user creation logic here
|
||||
|
||||
|
||||
//FOR DEV PURPOSES ONLY
|
||||
$data->setPictureUrl("");
|
||||
$data->setPassword($this->userService->generateRandomPassword());
|
||||
//FOR DEV PURPOSES ONLY
|
||||
$orgId = $request->get('organization_id');
|
||||
if ($orgId) {
|
||||
$organization = $this->entityManager->getRepository(Organizations::class)->find($orgId);
|
||||
$roleUser = $this->entityManager->getRepository(Roles::class)->findOneBy(['name' => 'USER']);
|
||||
if (!$organization || !$roleUser) {
|
||||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||||
}
|
||||
$uo = new UsersOrganizations();
|
||||
$uo->setOrganization($organization);
|
||||
$uo->setRole($roleUser);
|
||||
$uo->setUsers($data);
|
||||
|
||||
//log the action
|
||||
$action = new Actions();
|
||||
$action->setActionType('Création utilisateur dans une organisation');
|
||||
$action->setUsers($this->getUser());
|
||||
$action->setOrganization($organization);
|
||||
$this->entityManager->persist($uo);
|
||||
}else{
|
||||
$action = new Actions();
|
||||
$action->setActionType('Création utilisateur');
|
||||
$action->setUsers($this->getUser());
|
||||
}
|
||||
$this->entityManager->persist($data);
|
||||
$this->entityManager->persist($action);
|
||||
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
// Redirect to user index
|
||||
|
|
@ -109,13 +141,14 @@ class UserController extends AbstractController
|
|||
|
||||
return $this->render('user/new.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'organizationId' => $organizationId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /user/{id}/edit - Show form to edit user
|
||||
*/
|
||||
#[Route('/{id}/edit', name: 'edit', requirements: ['id' => '\d+'], methods: ['GET', 'PUT', 'POST'])]
|
||||
#[Route('/edit/{id}', name: 'edit', requirements: ['id' => '\d+'], methods: ['GET', 'PUT', 'POST'])]
|
||||
public function edit(int $id, EntityManagerInterface $entityManager, Request $request): Response
|
||||
{
|
||||
//Handle access control
|
||||
|
|
@ -137,6 +170,11 @@ class UserController extends AbstractController
|
|||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
//Persist changes to the user entity
|
||||
$entityManager->persist($user);
|
||||
//Log the action
|
||||
$action = new Actions();
|
||||
$action->setActionType('Modification utilisateur');
|
||||
$action->setUsers($this->getUser());
|
||||
$entityManager->persist($action);
|
||||
$entityManager->flush();
|
||||
|
||||
//Redirect to user profile after successful edit
|
||||
|
|
@ -171,6 +209,11 @@ class UserController extends AbstractController
|
|||
// Handle user deletion logic
|
||||
$user->setIsDeleted(true);
|
||||
$entityManager->persist($user);
|
||||
// Log the action
|
||||
$action = new Actions();
|
||||
$action->setActionType('Suppression utilisateur');
|
||||
$action->setUsers($this->getUser());
|
||||
$entityManager->persist($action);
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('user_index');
|
||||
|
|
@ -193,6 +236,11 @@ class UserController extends AbstractController
|
|||
|
||||
// Handle user deletion logic
|
||||
$entityManager->remove($user);
|
||||
// Log the action
|
||||
$action = new Actions();
|
||||
$action->setActionType('Suppression définitive utilisateur');
|
||||
$action->setUsers($this->getUser());
|
||||
$entityManager->persist($action);
|
||||
$entityManager->flush();
|
||||
|
||||
return $this->redirectToRoute('user_index');
|
||||
|
|
@ -215,6 +263,11 @@ class UserController extends AbstractController
|
|||
}
|
||||
$user->setIsActive(false);
|
||||
$entityManager->persist($user);
|
||||
// Log the action
|
||||
$action = new Actions();
|
||||
$action->setActionType('Désactivation utilisateur');
|
||||
$action->setUsers($this->getUser());
|
||||
$entityManager->persist($action);
|
||||
$entityManager->flush();
|
||||
return $this->redirectToRoute('user_index');
|
||||
}
|
||||
|
|
@ -260,7 +313,7 @@ class UserController extends AbstractController
|
|||
|
||||
// Fetch all roles and apps
|
||||
$roles = $entityManager->getRepository(Roles::class)->findAll();
|
||||
$apps = $entityManager->getRepository(Apps::class)->findAll();
|
||||
$apps = $organization->getApps() ?? throw $this->createNotFoundException(self::NOT_FOUND);
|
||||
if (!$roles) {
|
||||
throw $this->createNotFoundException(self::NOT_FOUND);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class Actions
|
|||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
|
||||
#[ORM\ManyToOne(cascade: ['persist', 'remove'])]
|
||||
private ?user $users = null;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
|
|
@ -22,6 +22,17 @@ class Actions
|
|||
#[ORM\Column(options: ['default' => 'CURRENT_TIMESTAMP'])]
|
||||
private ?\DateTimeImmutable $date = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'actions')]
|
||||
private ?Organizations $Organization = null;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $description = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->date = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
|
|
@ -62,4 +73,28 @@ class Actions
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOrganization(): ?Organizations
|
||||
{
|
||||
return $this->Organization;
|
||||
}
|
||||
|
||||
public function setOrganization(?Organizations $Organization): static
|
||||
{
|
||||
$this->Organization = $Organization;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): ?string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setDescription(?string $description): static
|
||||
{
|
||||
$this->description = $description;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,9 @@ class Apps
|
|||
#[ORM\ManyToMany(targetEntity: organizations::class, inversedBy: 'apps')]
|
||||
private Collection $organization;
|
||||
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $descriptionSmall = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->organization = new ArrayCollection();
|
||||
|
|
@ -144,4 +147,16 @@ class Apps
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescriptionSmall(): ?string
|
||||
{
|
||||
return $this->descriptionSmall;
|
||||
}
|
||||
|
||||
public function setDescriptionSmall(?string $descriptionSmall): static
|
||||
{
|
||||
$this->descriptionSmall = $descriptionSmall;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ class Organizations
|
|||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
#[ORM\Column(options: ['default' => false])]
|
||||
private ?bool $isDeleted = null;
|
||||
private ?bool $isDeleted = false;
|
||||
|
||||
#[ORM\Column(options: ['default' => true])]
|
||||
private ?bool $isActive = null;
|
||||
private ?bool $isActive = true;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Apps>
|
||||
|
|
@ -45,9 +45,17 @@ class Organizations
|
|||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $name = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, Actions>
|
||||
*/
|
||||
#[ORM\OneToMany(targetEntity: Actions::class, mappedBy: 'Organization')]
|
||||
private Collection $actions;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->apps = new ArrayCollection();
|
||||
$this->actions = new ArrayCollection();
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
|
|
@ -177,4 +185,34 @@ class Organizations
|
|||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, Actions>
|
||||
*/
|
||||
public function getActions(): Collection
|
||||
{
|
||||
return $this->actions;
|
||||
}
|
||||
|
||||
public function addAction(Actions $action): static
|
||||
{
|
||||
if (!$this->actions->contains($action)) {
|
||||
$this->actions->add($action);
|
||||
$action->setOrganization($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeAction(Actions $action): static
|
||||
{
|
||||
if ($this->actions->removeElement($action)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($action->getOrganization() === $this) {
|
||||
$action->setOrganization(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\Organizations;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class OrganizationForm extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('email', EmailType::class, ['required' => true, 'label' => 'Email*'])
|
||||
->add('name', TextType::class, ['required' => true, 'label' => 'Nom de l\'organisation*'])
|
||||
->add('address', TextType::class, ['required' => false, 'label' => 'Adresse'])
|
||||
->add('number', TextType::class, ['required' => false, 'label' => 'Numéro de téléphone'])
|
||||
->add('logoUrl', FileType::class, [
|
||||
'required' => false,
|
||||
'label' => 'Logo',
|
||||
'mapped' => false, // Important if the entity property is not directly mapped
|
||||
'attr' => ['accept' => 'image/*'],
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => Organizations::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
class ActionService
|
||||
{
|
||||
|
||||
public function getActivityColor(\DateTimeImmutable $activityTime): string
|
||||
{
|
||||
$now = new \DateTimeImmutable();
|
||||
$diffInSeconds = $now->getTimestamp() - $activityTime->getTimestamp();
|
||||
|
||||
if ($diffInSeconds < 15 * 60) { // less than 15 minutes
|
||||
return '#086572';
|
||||
}
|
||||
|
||||
if ($diffInSeconds < 60 * 60) { // less than 1 hour
|
||||
return '#247208';
|
||||
}
|
||||
return '#C76633';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
|
||||
class AwsService
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private S3Client $s3Client,
|
||||
private string $awsPublicUrl
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to generate UUID Version 4
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateUUIDv4():string {
|
||||
$uuid = uuid_create(4);
|
||||
|
||||
$isValid = uuid_is_valid($uuid);
|
||||
|
||||
if( $isValid == true){
|
||||
$retour = $uuid;
|
||||
}else{
|
||||
$retour = 'une erreur est survenue !';
|
||||
}
|
||||
|
||||
return $retour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public url for files download or visualisation
|
||||
*
|
||||
* @param string $bucket nom du conteneur S3
|
||||
* @return string
|
||||
*/
|
||||
public function getPublicUrl(string $bucket): string{
|
||||
$publicUrl = substr_replace($this->awsPublicUrl, $bucket.'.', 8, 0);
|
||||
$publicUrl .= '/';
|
||||
|
||||
return $publicUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE bucket S3 for new project
|
||||
*
|
||||
* @return string|array
|
||||
*/
|
||||
public function createBucket(): string|array{
|
||||
|
||||
$bucket = $this->generateUUIDv4();
|
||||
|
||||
$result = $this->s3Client->createBucket([
|
||||
'Bucket' => $bucket,
|
||||
'ObjectOwnership' => 'BucketOwnerPreferred'
|
||||
]);
|
||||
|
||||
if ( $result['@metadata']['statusCode'] == 200){
|
||||
return $bucket;
|
||||
}else{
|
||||
return $result['@metadata'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE bucket S3
|
||||
*
|
||||
* @param string $bucket nom du conteneur S3
|
||||
* @return string|array
|
||||
*/
|
||||
public function DeleteBucket(string $bucket): string|array{
|
||||
|
||||
$result = $this->s3Client->deleteBucket([
|
||||
'Bucket' => $bucket,
|
||||
|
||||
]);
|
||||
|
||||
if ( $result['@metadata']['statusCode'] == 200){
|
||||
return $bucket;
|
||||
}else{
|
||||
return $result['@metadata'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list files infos in the Bucket S3
|
||||
* If prefix NULL get ALL FILES else get FILES in this prefix
|
||||
*
|
||||
* @param string $bucket nom du conteneur S3
|
||||
* @param string|null $prefix arborescence dans le bucket
|
||||
* @return array|null
|
||||
*/
|
||||
public function getListObject(string $bucket, string|null $prefix = null):array|null{
|
||||
|
||||
$results = $this->s3Client->listObjectsV2([
|
||||
'Bucket' => $bucket,
|
||||
'Prefix' => $prefix
|
||||
]);
|
||||
|
||||
if( isset($results['Contents']) ){
|
||||
$return = $results['Contents'];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT file Object in bucket S3
|
||||
*
|
||||
* @param string $bucket nom du conteneur S3
|
||||
* @param object $file fichier à déposer dans le bucket
|
||||
* @param string $filename nom du fichier enregistré dans la bdd métier
|
||||
* @param string $mimeType type du fichier
|
||||
* @param string|null $prefix arborescence dans le bucket
|
||||
* @return bool
|
||||
*/
|
||||
public function PutDocObj(string $bucket, object $file, string $filename, $mimeType, string|null $prefix = null): int{
|
||||
|
||||
$body = fopen( $file, 'r');
|
||||
$hashRaw = hash_file('sha256', $file, true);
|
||||
$hash = base64_encode($hashRaw);
|
||||
|
||||
rewind($body);
|
||||
|
||||
$doc = $this->s3Client->putObject([
|
||||
'Bucket' => $bucket,
|
||||
'ChecksumAlgorithm' => 'SHA256',
|
||||
'ChecksumSHA256' => $hash,
|
||||
'Key' => $prefix.$filename,
|
||||
'Body' => $body,
|
||||
'ACL' => 'public-read',
|
||||
'ContentType' => $mimeType // pour rendre l'image publique si besoin
|
||||
]);
|
||||
|
||||
return $doc['@metadata']['statusCode'];
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE file Object in bucket S3
|
||||
*
|
||||
* @param string $bucket nom du conteneur S3
|
||||
* @param string $filename nom du fichier
|
||||
* @param string|null $prefix arborescence dans le bucket
|
||||
* @return bool
|
||||
*/
|
||||
public function DeleteDocObj(string $bucket, string $filename, string|null $prefix = null): int{
|
||||
|
||||
$doc = $this->s3Client->deleteObject([
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $prefix.$filename,
|
||||
]);
|
||||
|
||||
return $doc['@metadata']['statusCode'];
|
||||
}
|
||||
|
||||
/**
|
||||
* RENAME file Object in bucket S3
|
||||
*
|
||||
* @param string $bucket nom du conteneur S3
|
||||
* @param string $filename nom du fichier
|
||||
* @param string $newFilename
|
||||
* @param string|null $prefix arborescence dans le bucket
|
||||
* @return bool
|
||||
*/
|
||||
public function renameDocObj(string $bucket, string $filename, string $newFilename, string|null $prefix = null): int{
|
||||
|
||||
$doc = $this->s3Client->copyObject([
|
||||
'Bucket' => $bucket,
|
||||
'CopySource' => $prefix.$filename,
|
||||
'Key' => $prefix.$newFilename,
|
||||
]);
|
||||
|
||||
$this->DeleteDocObj($bucket, $filename, $prefix);
|
||||
|
||||
return $doc['@metadata']['statusCode'];
|
||||
}
|
||||
|
||||
/**
|
||||
* MOVE file Object in bucket S3
|
||||
*
|
||||
* @param string $bucket nom du conteneur S3
|
||||
* @param string $filename nom du fichier
|
||||
* @param string|null $prefix arborescence dans le bucket
|
||||
* @param string|null $newPrefix nouvel emplacement dans le bucket
|
||||
* @return bool
|
||||
*/
|
||||
public function moveDocObj(string $bucket, string $filename, string|null $prefix = null, string|null $newPrefix = null): int{
|
||||
|
||||
$doc = $this->s3Client->copyObject([
|
||||
'Bucket' => $bucket,
|
||||
'CopySource' => $prefix.$filename,
|
||||
'Key' => $newPrefix.$filename,
|
||||
]);
|
||||
|
||||
$this->DeleteDocObj($bucket, $filename, $prefix);
|
||||
|
||||
return $doc['@metadata']['statusCode'];
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\Actions;
|
||||
use App\Entity\Apps;
|
||||
use App\Entity\Organizations;
|
||||
use App\Entity\Roles;
|
||||
|
|
@ -333,6 +334,12 @@ readonly class UserOrganizationService
|
|||
]);
|
||||
foreach ($userOrganizations as $uo) {
|
||||
$uo->setIsActive(false);
|
||||
//Log action
|
||||
$action = new Actions();
|
||||
$action->setActionType("Désactivation role" );
|
||||
$action->setDescription("Désactivation du rôle " . $uo->getRole()->getName() . " pour l'utilisateur " . $user->getUserIdentifier() . " dans l'organisation " . $organization->getName());
|
||||
$action->setOrganization($organization);
|
||||
$action->setUsers($user);
|
||||
$this->entityManager->persist($uo);
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
|
|
@ -401,12 +408,12 @@ readonly class UserOrganizationService
|
|||
*/
|
||||
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();
|
||||
|
|
|
|||
18
symfony.lock
18
symfony.lock
|
|
@ -1,4 +1,16 @@
|
|||
{
|
||||
"aws/aws-sdk-php-symfony": {
|
||||
"version": "2.8",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes-contrib",
|
||||
"branch": "main",
|
||||
"version": "1.3",
|
||||
"ref": "d1753f9e2a669c464b2b0618af9b0123426b67b4"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/aws.yaml"
|
||||
]
|
||||
},
|
||||
"doctrine/deprecations": {
|
||||
"version": "1.1",
|
||||
"recipe": {
|
||||
|
|
@ -35,6 +47,9 @@
|
|||
"migrations/.gitignore"
|
||||
]
|
||||
},
|
||||
"knplabs/knp-time-bundle": {
|
||||
"version": "v2.4.0"
|
||||
},
|
||||
"league/oauth2-server-bundle": {
|
||||
"version": "0.11",
|
||||
"recipe": {
|
||||
|
|
@ -381,8 +396,5 @@
|
|||
"files": [
|
||||
"config/packages/messenger.yaml"
|
||||
]
|
||||
},
|
||||
"twig/extra-bundle": {
|
||||
"version": "v3.20.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
{% block body %}
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<h3><img width=10% src="{{ asset(application.application.logoUrl) }}" alt="Logo application">
|
||||
{{ application.application.name }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body d-flex flex-column align-items-center">
|
||||
<p class="card-text">{{ application.application.descriptionSmall }}</p>
|
||||
{% if application.has_access %}
|
||||
<div >
|
||||
<a href="http://{{ application.application.subDomain }}.solutions-easy.moi" class="btn btn-primary me-2">Y
|
||||
accéder</a>
|
||||
<a href="#" class="btn btn-secondary">Gérer l'application</a>
|
||||
</div>
|
||||
|
||||
{% else %}<a href="#" class="btn btn-primary">Demander l'accès</a>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -2,36 +2,24 @@
|
|||
|
||||
|
||||
<div class="card border-0">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div class="card-header d-flex justify-content-between align-items-center border-0">
|
||||
<h3>{{ title }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{# {% if activities|length == 0 %}#}
|
||||
{# <p>Aucune activité récente.</p>#}
|
||||
{# {% else %}#}
|
||||
{% if activities|length == 0 %}
|
||||
<p>Aucune activité récente.</p>
|
||||
{% else %}
|
||||
{% set sortedActivities = activities|sort((a, b) => a.date <=> b.date)|reverse %}
|
||||
<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 %}#}
|
||||
{% for activity in sortedActivities%}
|
||||
{% include 'user/organization/userActivity.html.twig' with {
|
||||
activityTime: activity.date,
|
||||
action: activity.actionType,
|
||||
userName: activity.users.name,
|
||||
color: activity.color
|
||||
} %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{# {% endif %}#}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<div class=" col-md-10 m-auto p-5">
|
||||
<div class="card">
|
||||
<div class="card-title shadow-sm p-3 d-flex justify-content-between align-items-center">
|
||||
<h2>Modifier l'organisation</h2>
|
||||
{% if is_granted("ROLE_SUPER_ADMIN") %}
|
||||
{# <a href="{{ path('organization_delete', {'id': organization.id}) }}" class="btn btn-danger">Supprimer</a>#}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
{{ form_start(form, {'action': path('organization_edit', {'id': organization.id}), 'method': 'PUT'}) }}
|
||||
{{ form_widget(form) }}
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -6,11 +6,16 @@
|
|||
<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>#}
|
||||
{% if is_granted("ROLE_SUPER_ADMIN") %}
|
||||
<a href="{{ path('organization_new') }}" class="btn btn-primary">Ajouter une organisation</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if organizations|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center">Aucune organisation trouvée.</td>
|
||||
<td colspan="4" class="text-center">
|
||||
<a href="{{ path('organization_new') }}" class="btn btn-primary">Créer une organisation</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<table class="table align-middle shadow">
|
||||
|
|
@ -27,7 +32,7 @@
|
|||
<tr>
|
||||
<td>
|
||||
{% if organization.logoUrl %}
|
||||
<img src="{{ asset(organization.logoUrl) }}" alt="Organization logo" class="rounded-circle" style="width:40px; height:40px;">
|
||||
<img src="{{ asset('uploads/logos/' ~ organization.logoUrl) }}" alt="Organization logo" class="rounded-circle" style="width:40px; height:40px;">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ organization.name }}</td>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Ajouter une organisation{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class=" col-md-10 m-auto p-5">
|
||||
<div class="card">
|
||||
<div class="card-title shadow-sm p-3 d-flex justify-content-between align-items-center">
|
||||
<h1>Ajouter une organisation</h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="{{ path('organization_new') }}" enctype="multipart/form-data">
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
{{ form_end(form) }}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -3,12 +3,18 @@
|
|||
{% 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> #}
|
||||
<h1 class="mb-4">
|
||||
{% if organization.logoUrl %}
|
||||
<img src="{{ asset('uploads/logos/' ~ organization.logoUrl) }}" alt="Organization logo"
|
||||
class="rounded-circle" style="width:40px; height:40px;">
|
||||
{% endif %}
|
||||
{{ organization.name|title }} - Dashboard</h1>
|
||||
{% if is_granted("ROLE_SUPER_ADMIN") %}
|
||||
<a href="{{ path('organization_edit', {'id': organization.id}) }}" class="btn btn-primary">Gérer mon
|
||||
organisation</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{# USER ROW#}
|
||||
{# USER ROW #}
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
<div class="row mb-4">
|
||||
|
|
@ -16,7 +22,8 @@
|
|||
{% include 'user/userListSmall.html.twig' with {
|
||||
title: 'Nouveaux utilisateurs',
|
||||
users: newUsers,
|
||||
empty_message: 'Aucun nouvel utilisateur trouvé.'
|
||||
empty_message: 'Aucun nouveaux utilisateurs trouvé.',
|
||||
organizationId: organization.id
|
||||
} %}
|
||||
</div>
|
||||
<div class="col mb-3 mb-sm-0">
|
||||
|
|
@ -30,8 +37,20 @@
|
|||
<div class="m-auto">
|
||||
{% include 'user/userList.html.twig' with {
|
||||
title: 'Mes utilisateurs',
|
||||
organizationId: organization.id,
|
||||
empty_message: 'Aucun utilisateurs trouvé.'
|
||||
} %}
|
||||
</div>
|
||||
{# APPLICATION ROW #}
|
||||
<div class="row ">
|
||||
{% for application in applications %}
|
||||
<div class="col-6 mb-3">
|
||||
{% include 'applications/appSmall.html.twig' with {
|
||||
application: application
|
||||
} %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-3 m-auto">
|
||||
|
|
@ -43,11 +62,9 @@
|
|||
|
||||
</div>
|
||||
|
||||
{# APPLICATION ROW#}
|
||||
<div class="row">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@
|
|||
<form method="post" action="{{ path('user_new') }}" enctype="multipart/form-data">
|
||||
{{ form_start(form) }}
|
||||
{{ form_widget(form) }}
|
||||
{% if organizationId is defined %}
|
||||
<div class="form-group">
|
||||
<input hidden type="text" value="{{ organizationId }}" name="organization_id">
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-primary">Enregistrer</button>
|
||||
{{ form_end(form) }}
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
{% block body %}
|
||||
|
||||
<div class="card border">
|
||||
<div class="card-header d-flex align-items-center border-0">
|
||||
<div class="row align-items-center">
|
||||
|
||||
<h4 class="mb-0">
|
||||
<span style="display:inline-block; width:16px; height:16px; border-radius:50%; background:{{ color }}; margin-right:10px;"></span>
|
||||
{{ activityTime|ago }}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p>{{ userName }} - {{ action }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -22,38 +22,50 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if org.users|length == 0 %}
|
||||
{% if org|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">Aucun utilisateur trouvé.</td>
|
||||
</tr>
|
||||
{% elseif org.users|length == 0 %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center">Aucun utilisateur trouvé.</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
{% 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>
|
||||
{% if organizationId is defined %}
|
||||
<a href="{{ path('user_show', {'id': user.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': user.users.id}) }}"
|
||||
class="p-3 align-middle color-primary">
|
||||
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% 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>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
<div class="card border-0">
|
||||
<div class="card-title p-3 d-flex justify-content-between align-items-center ">
|
||||
<h3>{{ title }}</h3>
|
||||
{% if organizationId is defined %}
|
||||
<a href="{{ path('user_new', {'organizationId': organizationId}) }}" class="btn btn-primary">Ajouter un utilisateur</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table align-middle table-borderless">
|
||||
|
|
@ -29,10 +32,18 @@
|
|||
</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td>
|
||||
<a href="{{ path('user_show', {'id': user.id}) }}"
|
||||
{% if organizationId is defined %}
|
||||
<a href="{{ path('user_show', {'id': user.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.id}) }}"
|
||||
class="p-3 align-middle color-primary">
|
||||
{{ ux_icon('fa6-regular:eye', {height: '30px', width: '30px'}) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
|||
Loading…
Reference in New Issue