diff --git a/.env b/.env index 45d04f9..8867c0c 100644 --- a/.env +++ b/.env @@ -1,10 +1,11 @@ -APP_ENV=dev -APP_SECRET=je_te_remplis_parce_que_tu_me_mets_des_messages_d_erreur -APP_SHARE_DIR=var/share -APP_VERSION=0.0.1 -DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" -MESSENGER_TRANSPORT_DSN="doctrine://default" -MAILER_DSN="smtp://localhost:1025" -DEFAULT_URI="http://localhost:8000" +APP_ENV= +APP_SECRET= +APP_SHARE_DIR= +APP_VERSION= +DATABASE_URL= +MESSENGER_TRANSPORT_DSN= +MAILER_DSN= +DEFAULT_URI= +KAZ_API_BASE_URL= KAZ_API_USER= KAZ_API_PASSWORD= diff --git a/assets/styles/app.css b/assets/styles/app.css index e6b1010..d760871 100644 --- a/assets/styles/app.css +++ b/assets/styles/app.css @@ -30,6 +30,6 @@ --color-gris-fonce: #4B5563; /* Polices */ - --font-sora: "Sora", system-ui, sans-serif; + --font-sora: "Sora", sans-serif; --font-caveat: "Caveat", cursive; } diff --git a/composer.lock b/composer.lock index 7d7666c..c4512b7 100644 --- a/composer.lock +++ b/composer.lock @@ -3686,16 +3686,16 @@ }, { "name": "symfony/http-client", - "version": "v8.0.5", + "version": "v8.0.8", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "f9fdd372473e66469c6d32a4ed12efcffdea38c4" + "reference": "356e43d6994ae9d7761fd404d40f78691deabe0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/f9fdd372473e66469c6d32a4ed12efcffdea38c4", - "reference": "f9fdd372473e66469c6d32a4ed12efcffdea38c4", + "url": "https://api.github.com/repos/symfony/http-client/zipball/356e43d6994ae9d7761fd404d40f78691deabe0e", + "reference": "356e43d6994ae9d7761fd404d40f78691deabe0e", "shasum": "" }, "require": { @@ -3758,7 +3758,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v8.0.5" + "source": "https://github.com/symfony/http-client/tree/v8.0.8" }, "funding": [ { @@ -3778,7 +3778,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T16:18:07+00:00" + "time": "2026-03-30T15:14:47+00:00" }, { "name": "symfony/http-client-contracts", diff --git a/config/services.yaml b/config/services.yaml index b37d953..421e277 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -16,7 +16,7 @@ services: $apiUser: '%env(KAZ_API_USER)%' $apiPassword: '%env(KAZ_API_PASSWORD)%' - # Gestion de l'enregistrement de la photo de profil + # Gestion de l'enregistrement de l'image de profil App\Service\FileUploader: arguments: $targetDirectory: '%images_directory%' diff --git a/migrations/Version20260313151403.php b/migrations/Version20260313151403.php deleted file mode 100644 index de8ff23..0000000 --- a/migrations/Version20260313151403.php +++ /dev/null @@ -1,35 +0,0 @@ -addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, lastname VARCHAR(255) NOT NULL, firstname VARCHAR(255) NOT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); - $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE "user"'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/migrations/Version20260316103235.php b/migrations/Version20260316103235.php deleted file mode 100644 index 4094fd5..0000000 --- a/migrations/Version20260316103235.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ALTER alternate_email DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER alternate_email SET NOT NULL'); - } -} diff --git a/migrations/Version20260316104254.php b/migrations/Version20260316104254.php deleted file mode 100644 index baf28d0..0000000 --- a/migrations/Version20260316104254.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ALTER identifiant_kaz DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER identifiant_kaz SET NOT NULL'); - } -} diff --git a/migrations/Version20260316104335.php b/migrations/Version20260316104335.php deleted file mode 100644 index ebcb3db..0000000 --- a/migrations/Version20260316104335.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ALTER quota DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER quota SET NOT NULL'); - } -} diff --git a/migrations/Version20260316104505.php b/migrations/Version20260316104505.php deleted file mode 100644 index c68c5fd..0000000 --- a/migrations/Version20260316104505.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ALTER has_agora_access DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER has_agora_access SET NOT NULL'); - } -} diff --git a/migrations/Version20260316104557.php b/migrations/Version20260316104557.php deleted file mode 100644 index bcd88dc..0000000 --- a/migrations/Version20260316104557.php +++ /dev/null @@ -1,33 +0,0 @@ -addSql('ALTER TABLE "user" ALTER has_nextcloud_access DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_mobilizon DROP NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_mobilizon SET NOT NULL'); - } -} diff --git a/migrations/Version20260316114715.php b/migrations/Version20260316114715.php deleted file mode 100644 index d0ff32d..0000000 --- a/migrations/Version20260316114715.php +++ /dev/null @@ -1,49 +0,0 @@ -addSql('ALTER TABLE "user" ADD last_name VARCHAR(255) NOT NULL'); - $this->addSql('ALTER TABLE "user" ADD first_name VARCHAR(255) NOT NULL'); - $this->addSql('ALTER TABLE "user" DROP lastname'); - $this->addSql('ALTER TABLE "user" DROP firstname'); - $this->addSql('ALTER TABLE "user" ALTER alternate_email SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER identifiant_kaz SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER quota SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_mobilizon SET NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_agora_access SET NOT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" ADD lastname VARCHAR(255) NOT NULL'); - $this->addSql('ALTER TABLE "user" ADD firstname VARCHAR(255) NOT NULL'); - $this->addSql('ALTER TABLE "user" DROP last_name'); - $this->addSql('ALTER TABLE "user" DROP first_name'); - $this->addSql('ALTER TABLE "user" ALTER alternate_email DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER identifiant_kaz DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER quota DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_mobilizon DROP NOT NULL'); - $this->addSql('ALTER TABLE "user" ALTER has_agora_access DROP NOT NULL'); - } -} diff --git a/migrations/Version20260326214353.php b/migrations/Version20260326214353.php deleted file mode 100644 index a2eddd8..0000000 --- a/migrations/Version20260326214353.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ADD photo VARCHAR(255) DEFAULT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" DROP photo'); - } -} diff --git a/migrations/Version20260326231417.php b/migrations/Version20260326231417.php deleted file mode 100644 index d7e8987..0000000 --- a/migrations/Version20260326231417.php +++ /dev/null @@ -1,31 +0,0 @@ -addSql('ALTER TABLE "user" ADD telephone VARCHAR(20) DEFAULT NULL'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('ALTER TABLE "user" DROP telephone'); - } -} diff --git a/migrations/Version20260328101039.php b/migrations/Version20260328101039.php deleted file mode 100644 index 3c11da4..0000000 --- a/migrations/Version20260328101039.php +++ /dev/null @@ -1,35 +0,0 @@ -addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, photo VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); - $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE "user"'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/migrations/Version20260328101220.php b/migrations/Version20260328101220.php deleted file mode 100644 index d51320f..0000000 --- a/migrations/Version20260328101220.php +++ /dev/null @@ -1,35 +0,0 @@ -addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, photo VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); - $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE "user"'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/migrations/Version20260329084928.php b/migrations/Version20260329084928.php deleted file mode 100644 index aa29b31..0000000 --- a/migrations/Version20260329084928.php +++ /dev/null @@ -1,35 +0,0 @@ -addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, photo VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)'); - $this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))'); - $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE "user"'); - $this->addSql('DROP TABLE messenger_messages'); - } -} diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 972912f..d51e365 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -7,10 +7,11 @@ use App\Form\UserProfileType; use App\Service\FileUploader; use App\Service\KazApiService; use Doctrine\ORM\EntityManagerInterface; -use Symfony\Component\HttpFoundation\File\UploadedFile; -use Symfony\Component\HttpFoundation\Request; +use Exception; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormError; +use Symfony\Component\HttpFoundation\File\UploadedFile; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; use Symfony\Component\Routing\Attribute\Route; @@ -37,16 +38,6 @@ class UserController extends AbstractController * @throws TransportExceptionInterface */ - #[Route('/user/{email}', name: 'app_user_by_mail', methods: ['GET'])] - public function index(string $email, KazApiService $apiKazService): Response - { - $user = $apiKazService->getUserData($email); - return $this->render('user/profil_infos.html.twig', [ - 'user' => $user, - ]); - } - - /* TODO : Param l'API avec un Serializer pour la lecture du fichier JSON ? */ #[Route('/mon-profil', name: 'app_user', methods: ['GET', 'POST'])] #[IsGranted('ROLE_USER')] public function showProfile( @@ -54,66 +45,63 @@ class UserController extends AbstractController EntityManagerInterface $entityManager, FileUploader $fileUploader, KazApiService $apiKazService - ): Response { - # Récupération de l'utilisateur actuellement connecté + ): Response + { + // Récupération de l'utilisateur actuellement connecté $user = $this->getUser(); + // Vérification si l'URL est en mode édition + $isEditMode = $request->query->getBoolean('edit', false); - $kazUser = $apiKazService->getUserData($user->getEmail()); + try { + // Récupération des données de l'utilisateur sur l'API grâce à son email + $kazUser = $apiKazService->getUserData($user->getEmail()); + // Initialisation de la variable $userData + $user = $user->updateFromKazUser($kazUser); + } catch (Exception $e) { + $this->addFlash('error', 'Impossible de charger vos données.' . $e->getMessage()); + } - $user = $user->updateFromKazUser($kazUser); - - //TODO: modifier pour que ça communique avec l'API */ - - # Création du formulaire lié à l'utilisateur connecté + // Création du formulaire lié à l'utilisateur connecté $form = $this->createForm(UserProfileType::class, $user); $form->handleRequest($request); - # Traitement si l'utilisateur clique sur "Valider" + // Affichage du formulaire si les données sont valides if ($form->isSubmitted() && $form->isValid()) { - - /** @var UploadedFile $imageFile */ + /** @var UploadedFile|null $imageFile */ $imageFile = $form->get('image')->getData(); + // --- Gestion de l'image de profil --- if ($imageFile) { - # Suppression de l'ancienne photo du serveur - $fileUploader->delete($user->getPhoto()); - - # Dépot de la nouvelle photo + // Suppression de l'ancienne image via le service + if ($user->getImage()) { + $fileUploader->delete($user->getImage()); + } + // Dépôt de la nouvelle image et mise à jour de son nom dans l'entité $newFilename = $fileUploader->upload($imageFile); - - # Mise à jour de l'utilisateur avec le nouveau nom - $user->setPhoto($newFilename); + $user->setImage($newFilename); } + // --- Fin gestion de l'image de profil --- - $alternateEmail = $form->get('alternateEmail')->getData(); - $regexEmail = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/'; - if(isset($alternateEmail) && preg_match($regexEmail, $alternateEmail)) { - $user->setAlternateEmail($form->get('alternateEmail')->getData()); - } else { - $alternateEmail->addError(new FormError('L\'adresse e-mail n\'est pas valide.')); + // Synchronisation des données avec l'API + $kazUser = $user->convertToKazUser(); + + try { + $apiKazService->updateUserData($user->getEmail(), $kazUser); + } catch (Exception $e) { + $this->addFlash('error', 'Impossible de mettre à jour votre profil' . $e->getMessage()); } - - $telephone = $form->get('telephone')->getData(); - $regexTelephone = '/^[0-9\+\s\.\-\(\)]+$/'; - if(isset($telephone) && preg_match($regexTelephone, $telephone)) { - $user->setTelephone($telephone); - } else { - $telephone->addError(new FormError('Le numéro de téléphone n\'est pas valide.')); - } - - # Sauvegarde en base de données + // Sauvegarde en base de données $entityManager->flush(); - - # Message de confirmation et rechargement de la page + // Message de confirmation et rechargement de la page $this->addFlash('success', 'Votre profil a été mis à jour avec succès !'); - + // Redirection de l'utilisateur return $this->redirectToRoute('app_user'); } - - # Affichage de la page + // Affichage de la page return $this->render('user/index.html.twig', [ 'form' => $form->createView(), - 'userData' => $user, # TODO : Mettre $userData quand connexion avec API OK + 'userData' => $user, + 'isEditMode' => $isEditMode, ]); } @@ -124,31 +112,30 @@ class UserController extends AbstractController EntityManagerInterface $entityManager ): Response { - # Création du formulaire + // Création du formulaire $form = $this->createForm(ChangePasswordType::class); - # Liaison du formulaire à la requête HTTP + // Liaison du formulaire à la requête HTTP $form->handleRequest($request); - # Vérification du formulaire, s'il est bien soumis et valide + // Vérification du formulaire, s'il est bien soumis et valide if ($form->isSubmitted() && $form->isValid()) { - # Récupération des données du formulaire + // Récupération des données du formulaire $user = $this->getUser(); $plainOldPassword = $form->get('oldPassword')->getData(); $newPassword = $form->get('newPassword')->getData(); - # Vérification de l'ancien mot de passe + // Vérification de l'ancien mot de passe if (!$hasher->isPasswordValid($user, $plainOldPassword)) { $form->get('oldPassword')->addError(new FormError('L\'ancien mot de passe est incorrect.')); } else { - # Si tout est OK : Hachage du mot de passe + // Si tout est OK : Hachage du mot de passe $hashedPassword = $hasher->hashPassword($user, $newPassword); $user->setPassword($hashedPassword); - # Sauvegarde en BDD + // Sauvegarde en BDD $entityManager->flush(); - - # Message de succès pour l'utilisateur + // Message de succès pour l'utilisateur $this->addFlash('success', 'Votre mot de passe a bien été mis à jour !'); return $this->redirectToRoute('app_user_edit_password'); diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index cf35c5b..cec78c9 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -10,35 +10,36 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; class AppFixtures extends Fixture { - # Initialisation de l'outil de hachage de Symfony + // Initialisation de l'outil de hachage de Symfony private UserPasswordHasherInterface $hasher; - # Injection de dépendance pour récupérer le service de sécurité + // Injection de dépendance pour récupérer le service de sécurité public function __construct(UserPasswordHasherInterface $hasher) { $this->hasher = $hasher; } - # Méthode principale qui génère les données en base + // Méthode principale qui génère des données de test en BDD locale public function load(ObjectManager $manager): void { - # Initialisation de Faker en français + // --- Création de 10 utilisateurs avec Faker --- // + // Initialisation de Faker en français $faker = Factory::create('fr_FR'); - # Boucle pour créer 10 utilisateurs + // Boucle pour créer 10 utilisateurs for ($i = 0; $i < 10; $i++) { - # Instanciation d'un nouvel utilisateur (Adhérent) + // Instanciation d'un nouvel utilisateur (Adhérent) $user = new User(); - # Attribution d'un email aléatoire et unique + // Attribution d'un email aléatoire et unique $user->setEmail($faker->unique()->safeEmail()); - # Définition des droits d'accès de l'utilisateur + // Définition des droits d'accès de l'utilisateur $user->setRoles(['ROLE_USER', 'ROLE_ORGANISATION']); - # Hachage sécurisé du mot de passe "password" + // Hachage sécurisé du mot de passe "password" $user->setPassword($this->hasher->hashPassword($user, 'password')); - # Définition d'un NOM et Prénom + // Définition d'un NOM et Prénom $user->setFirstname($faker->firstName()); $user->setLastname($faker->lastName()); - # autres fixtures à modifier plus tard + // autres fixtures à modifier plus tard $user->setNextcloudQuota($faker->numberBetween(1, 20) . 'G'); $user->setQuota($faker->numberBetween(1, 10) . 'G'); $user->setEmailQuota('1G'); @@ -47,7 +48,7 @@ class AppFixtures extends Fixture $user->setHasMobilizon($faker->boolean(50)); $user->setHasNextcloudAccess($faker->boolean(90)); $user->setIdentifiantKaz($faker->uuid()); - # Préparation de l'enregistrement de l'objet en base de données + // Préparation de l'enregistrement de l'objet en base de données $manager->persist($user); } @@ -56,10 +57,9 @@ class AppFixtures extends Fixture $admin->setEmail('admin@kaz.bzh'); $admin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']); $admin->setPassword($this->hasher->hashPassword($admin, 'password')); + // Remplissage des champs obligatoires restants pour éviter les erreurs SQL $admin->setFirstName('Admin'); $admin->setLastName('KAZ'); - - // Remplissage des champs obligatoires restants pour éviter les erreurs SQL $admin->setAlternateEmail('secours@kaz.bzh'); $admin->setIdentifiantKaz('ADMIN-KAZ-001'); $admin->setQuota('5G'); @@ -71,26 +71,24 @@ class AppFixtures extends Fixture $manager->persist($admin); - // Création d'un compte de test fixe - $melvin = new User(); - $melvin->setEmail('melvin.leveque@kazkouil.fr'); - $melvin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']); - $melvin->setPassword($this->hasher->hashPassword($melvin, 'password')); - $melvin->setFirstName(''); - $melvin->setLastName(''); + // Création d'un compte de test fixe présent dans le LDAP pour ma présentation + $toto = new User(); + $toto->setEmail('toto@kazkouil.fr'); + $toto->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']); + $toto->setPassword($this->hasher->hashPassword($toto, 'password')); + $toto->setFirstName(''); + $toto->setLastName(''); + $toto->setAlternateEmail(''); + $toto->setIdentifiantKaz(''); + $toto->setQuota('5G'); + $toto->setEmailQuota('1G'); + $toto->setNextcloudQuota('10G'); + $toto->setHasNextcloudAccess(true); + $toto->setHasMobilizon(true); + $toto->setHasAgoraAccess(true); + $manager->persist($toto); - $melvin->setAlternateEmail(''); - $melvin->setIdentifiantKaz('MELVIN-KAZ-001'); - $melvin->setQuota('5G'); - $melvin->setEmailQuota('1G'); - $melvin->setNextcloudQuota('10G'); - $melvin->setHasNextcloudAccess(true); - $melvin->setHasMobilizon(true); - $melvin->setHasAgoraAccess(true); - - $manager->persist($melvin); - - # Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie + // Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie $manager->flush(); } } diff --git a/src/Entity/User.php b/src/Entity/User.php index 88e6bf1..d52b38d 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -3,7 +3,6 @@ namespace App\Entity; use App\Repository\UserRepository; -use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; @@ -69,13 +68,18 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(length: 255, name: 'first_name')] private ?string $firstName = null; - // TODO: Modifier "photo" par "image" - #[ORM\Column(length: 255, nullable: true, name: 'photo')] - private ?string $photo = null; + #[ORM\Column(length: 255, nullable: true, name: 'image')] + private ?string $image = null; #[ORM\Column(length: 20, nullable: true, name: 'telephone')] private ?string $telephone = null; + private ?string $numeroMembre = null; + + private ?bool $mailEnabled = null; + + private ?string $mailAlias = null; + public function __construct() { $this->emailQuota = self::EMAIL_QUOTA_DEFAULT; } @@ -290,14 +294,14 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } - public function getPhoto(): ?string + public function getImage(): ?string { - return $this->photo; + return $this->image; } - public function setPhoto(?string $photo): static + public function setImage(?string $image): static { - $this->photo = $photo; + $this->image = $image; return $this; } @@ -314,18 +318,80 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + public function getNumeroMembre(): ?string + { + return $this->numeroMembre; + } + + public function setNumeroMembre(?string $numeroMembre): static + { + $this->numeroMembre = $numeroMembre; + + return $this; + } + + public function isMailEnabled(): ?bool + { + return $this->mailEnabled; + } + + public function setMailEnabled(?bool $mailEnabled): static + { + $this->mailEnabled = $mailEnabled; + + return $this; + } + + public function getMailAlias(): ?string + { + return $this->mailAlias; + } + + public function setMailAlias(?string $mailAlias): static + { + $this->mailAlias = $mailAlias; + + return $this; + } + + // Fonction qui permet d'afficher les données de l'API sur la page de profil public function updateFromKazUser($kazUser) : User { + // Récupération et conversion des données de l'API pour les afficher $this->setEmail($kazUser['mail']); - // Création du firstname et lastname + // Création du firstname et lastname (une seule donnée sur l'API) $name = explode(' ', $kazUser['sn']); $this->setFirstName($name[0]); // Récupération des valeurs du tableau moins la première $aLastname = array_slice($name, 1); $this->setLastName(implode(' ', $aLastname)); - - //TODO: Ajouter les champs manquants de l'objet User dans l'api kaz. - + // Récupération du mail de secours + $this->setAlternateEmail($kazUser['mailDeSecours']); + $this->setEmailQuota($kazUser['mailQuota']); + $this->setHasAgoraAccess($kazUser['agoraEnabled']); + $this->setHasMobilizon($kazUser['mobilizonEnabled']); + $this->setHasNextcloudAccess($kazUser['nextcloudEnabled']); + $this->setNextcloudQuota($kazUser['nextcloudQuota']); + $this->setQuota($kazUser['quota']); + $this->setIdentifiantKaz($kazUser['identifiantKaz']); return $this; } + + // Fonction qui permet de convertir les données de l'API vers $kazUser + public function convertToKazUser() : array + { + $data = [ + 'numeroMembre' => $this->getNumeroMembre(), + 'mailDeSecours' => $this->getAlternateEmail(), + 'mailEnabled' => $this->isMailEnabled(), + 'nextcloudEnabled' => $this->hasNextcloudAccess(), + 'mobilizonEnabled' => $this->hasMobilizon(), + 'agoraEnabled' => $this->hasAgoraAccess(), + 'identifiantKaz' => $this->getIdentifiantKaz(), + 'mailAlias' => $this->getMailAlias(), + 'quota' => $this->getQuota(), + ]; + + return array_filter($data, fn($value) => $value !== null); + } } diff --git a/src/Form/UserProfileType.php b/src/Form/UserProfileType.php index 566ca32..10f2aa0 100644 --- a/src/Form/UserProfileType.php +++ b/src/Form/UserProfileType.php @@ -3,11 +3,12 @@ namespace App\Form; use App\Entity\User; -use Symfony\Component\Form\Extension\Core\Type\TelType; -use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\FileType; +use Symfony\Component\Form\Extension\Core\Type\TelType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Validator\Constraints\Image; @@ -27,22 +28,53 @@ class UserProfileType extends AbstractType 'label' => 'Nom', 'disabled' => true, ]) + ->add('identifiantKaz', TextType::class, [ + 'label' => 'Identifiant KAZ : ', + 'disabled' => true, + ]) ->add('email', EmailType::class, [ 'label' => 'E-mail', 'disabled' => true, ]) - ->add('alternateEmail', EmailType::class, ['label' => 'E-mail de secours']) + ->add('alternateEmail', EmailType::class, [ + 'label' => 'E-mail de secours', + 'constraints' => [ + new Regex( + pattern: '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/', + message: 'L\'adresse e-mail n\'est pas valide.' + ) + ] + ]) + ->add('emailQuota', TextType::class, [ + 'label' => 'Espace de stockage de votre boîte mail : ', + 'disabled' => true, + ]) + ->add('hasNextcloudAccess', CheckboxType::class, [ + 'label' => 'Accès au Nextcloud : ', + 'disabled' => true, + ]) + ->add('nextcloudQuota', TextType::class, [ + 'label' => 'Espace de stockage de votre Nextcloud : ', + 'disabled' => true, + ]) + ->add('hasMobilizon', CheckboxType::class, [ + 'label' => 'Accès à Mobilizon : ', + 'disabled' => true, + ]) + ->add('hasAgoraAccess', CheckboxType::class, [ + 'label' => 'Accès à l\'Agora : ', + 'disabled' => true, + ]) ->add('telephone', TelType::class, [ 'label'=>'Téléphone', 'required' => false, 'attr' => [ 'placeholder'=>'06 00 00 00 00', - 'class'=> 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton transition-shadow' ], 'constraints' => [ new Regex( pattern: '/^[0-9\+\s\.\-\(\)]+$/', - message: 'Le numéro de téléphone contient des caractères non valides' + message: 'Le numéro de téléphone n\'est pas valide.' ), new Length( max: 20, @@ -51,14 +83,14 @@ class UserProfileType extends AbstractType ], ]) ->add('image', FileType::class, [ - 'label' => 'Ma photo de profil', + 'label' => 'Mon image de profil', 'mapped' => false, 'required' => false, 'constraints' => [ new Image( - maxSize: '2M', - extensions: ['jpg', 'jpeg', 'png'], - extensionsMessage: 'Veuillez déposer une image JPG, JPEG ou PNG valide',) + maxSize: '8M', + extensions: ['jpg', 'jpeg', 'png', 'gif'], + extensionsMessage: 'Veuillez déposer une image JPG, JPEG, GIF ou PNG valide') ], ]) ; diff --git a/src/Service/FileUploader.php b/src/Service/FileUploader.php index 627af1d..144216f 100644 --- a/src/Service/FileUploader.php +++ b/src/Service/FileUploader.php @@ -2,45 +2,76 @@ namespace App\Service; +use RuntimeException; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\String\Slugger\SluggerInterface; +/** + * Service de gestion des envois et suppressions de fichiers. + */ class FileUploader { - // On utilise la promotion de constructeur (PHP 8) : ultra moderne et concis + /** + * @param string $targetDirectory Le chemin absolu vers le dossier de dépôt. + * @param SluggerInterface $slugger Le service de nettoyage des chaînes de caractères. + */ public function __construct( - private string $targetDirectory, - private SluggerInterface $slugger, - ) { + #[Autowire('%kernel.project_dir%/public/uploads/img')] + private readonly string $targetDirectory, + private readonly SluggerInterface $slugger, + ) + { } + /** + * Traite, sécurise et déplace un fichier déposé. + * + * @param UploadedFile $file Le fichier physique à déposer. + * @return string Le nom final sécurisé et unique du fichier. + * @throws RuntimeException En cas d'échec de l'écriture sur le disque. + */ public function upload(UploadedFile $file): string { $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); $safeFilename = $this->slugger->slug($originalFilename); - $fileName = $safeFilename . '-' . uniqid() . '.' . $file->guessExtension(); + + // Utilisation de uniqid('', true) pour garantir une unicité absolue en production + $fileName = sprintf('%s-%s.%s', $safeFilename, uniqid('', true), $file->guessExtension()); try { $file->move($this->getTargetDirectory(), $fileName); } catch (FileException $e) { - // Ici tu peux logguer l'erreur si besoin - throw new \Exception('Erreur lors du transfert de l\'image : ' . $e->getMessage()); + throw new RuntimeException('Erreur lors du transfert de l\'image : ' . $e->getMessage(), 0, $e); } return $fileName; } + /** + * Supprime physiquement un fichier du serveur. + * + * @param string|null $fileName Le nom du fichier à supprimer. + */ public function delete(?string $fileName): void { - if ($fileName) { - $filePath = $this->getTargetDirectory() . 'FileUploader.php/' . $fileName; - if (file_exists($filePath)) { - unlink($filePath); - } + if (null === $fileName) { + return; + } + + $filePath = rtrim($this->getTargetDirectory(), '/') . '/' . $fileName; + + if (file_exists($filePath)) { + unlink($filePath); } } + /** + * Retourne le chemin du répertoire de dépôt. + * + * @return string + */ public function getTargetDirectory(): string { return $this->targetDirectory; diff --git a/src/Service/KazApiService.php b/src/Service/KazApiService.php index e754325..10a80c9 100644 --- a/src/Service/KazApiService.php +++ b/src/Service/KazApiService.php @@ -4,6 +4,8 @@ namespace App\Service; use Exception; +use Symfony\Component\Mime\Part\DataPart; +use Symfony\Component\Mime\Part\Multipart\FormDataPart; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface; use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; @@ -50,7 +52,7 @@ class KazApiService } $data = $response->toArray(); - $this->token = $data['access_token']; // Ajustez la clé selon le format de votre API + $this->token = $data['access_token']; return $this->token; } @@ -80,4 +82,26 @@ class KazApiService return $response->toArray(); } + + /** + * Envoie les nouvelles données saisies par l'utilisateur vers l'API + * + * @throws ClientExceptionInterface + * @throws DecodingExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + * @throws Exception + */ + public function updateUserData(string $email, array $kazUser): void + { + $options['headers']['Authorization'] = 'Bearer ' . $this->getToken(); + $options['headers']['Content-Type'] = 'application/json'; + $options['json'] = $kazUser; + $response = $this->kazApiClient->request('PATCH', "/ldap/user/update/$email", $options); + + if ($response->getStatusCode() !== 200) { + throw new Exception('Erreur lors de l\'appel API : ' . $response->getStatusCode()); + } + } } diff --git a/templates/base.html.twig b/templates/base.html.twig index 50788c4..5b315f1 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -12,11 +12,11 @@ {% endblock %} {% block javascripts %} - {% block importmap %}{{ importmap('app') }}{% endblock %} + {{ importmap('app') }} {% endblock %} -
+ {% block navbar %} {{ include('_navbar.html.twig') }} @@ -28,67 +28,58 @@