feat: mise à jour du profil utilisateur et intégration API Kaz (synchro des données, gestion des erreurs et succès) + divers correctifs (liens externes, simplifications de commentaires)

This commit is contained in:
2026-04-04 12:53:48 +02:00
parent 7a25779c9c
commit 7400d0d418
8 changed files with 100 additions and 30 deletions

12
composer.lock generated
View File

@@ -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",

View File

@@ -58,7 +58,7 @@ class UserController extends AbstractController
// Initialisation de la variable $userData
$user = $user->updateFromKazUser($kazUser);
} catch (Exception $e) {
$this->addFlash('error', 'Impossible de charger vos données.');
$this->addFlash('error', 'Impossible de charger vos données.' . $e->getMessage());
}
// Création du formulaire lié à l'utilisateur connecté
@@ -80,6 +80,21 @@ class UserController extends AbstractController
$newFilename = $fileUploader->upload($imageFile);
$user->setImage($newFilename);
}
// --- Fin gestion de l'image de profil ---
// Synchronisation des données avec l'API
$kazUser = [
'telephone' => $form->get('telephone')->getData(),
'alternateEmail' => $form->get('alternateEmail')->getData(),
];
try {
$apiKazService->updateUserData($user->getEmail(), $kazUser);
$this->addFlash('success', 'Votre profil a été mis à jour avec succès !');
} catch (Exception $e) {
$this->addFlash('error', 'Impossible de mettre à jour votre profil' . $e->getMessage());
}
// Sauvegarde en base de données
$entityManager->flush();

View File

@@ -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,14 +71,13 @@ class AppFixtures extends Fixture
$manager->persist($admin);
// Création d'un compte de test fixe
// Création d'un compte de test fixe présent dans le LDAP
$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('');
$melvin->setAlternateEmail('');
$melvin->setIdentifiantKaz('MELVIN-KAZ-001');
$melvin->setQuota('5G');
@@ -90,7 +89,24 @@ class AppFixtures extends Fixture
$manager->persist($melvin);
# Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie
// 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);
// Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie
$manager->flush();
}
}

View File

@@ -336,4 +336,27 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this;
}
// Fonction qui permet de convertir les données de l'API vers $kazUser
public function convertToKazUser() : array
{
$fullName = implode(' ', array_filter([
$this->getFirstName(),
$this->getLastName()
]));
return [
'mail' => $this->getEmail(),
'sn' => $fullName,
'mailDeSecours' => $this->getAlternateEmail(),
'mailQuota' => $this->getEmailQuota(),
'agoraEnabled' => $this->hasAgoraAccess(),
'mobilizonEnabled' => $this->hasMobilizon(),
'nextcloudEnabled' => $this->hasNextcloudAccess(),
'nextcloudQuota' => $this->getNextcloudQuota(),
'quota' => $this->getQuota(),
'identifiantKaz' => $this->getIdentifiantKaz(),
'telephone' => $this->getTelephone(),
];
}
}

View File

@@ -90,7 +90,7 @@ class UserProfileType extends AbstractType
new Image(
maxSize: '8M',
extensions: ['jpg', 'jpeg', 'png', 'gif'],
extensionsMessage: 'Veuillez déposer une image JPG, JPEG, GIF ou PNG valide',)
extensionsMessage: 'Veuillez déposer une image JPG, JPEG, GIF ou PNG valide')
],
])
;

View File

@@ -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;
@@ -80,4 +82,18 @@ class KazApiService
return $response->toArray();
}
/**
* Envoie les nouvelles données saisies par l'utilisateur vers l'API
*/
public function updateUserData(string $email, array $kazUser): void
{
$options['headers']['Authorization'] = 'Bearer ' . $this->getToken();
$options['body'] = json_encode($kazUser);
// $response = $this->kazApiClient->request('PUT', "/ldap/user/add/$email", $options);
if ($response->getStatusCode() !== 200) {
throw new Exception('Erreur lors de l\'appel API : ' . $response->getStatusCode());
}
}
}

View File

@@ -80,13 +80,13 @@
<ul class="flex flex-wrap justify-center gap-4 sm:gap-6 text-sm text-gris-fonce">
<li>
<a href="https://kaz.bzh/mentions-legales/" class="hover:text-bouton transition-colors">Mentions légales et statuts</a>
<a href="https://kaz.bzh/mentions-legales/" target="_blank" class="hover:text-bouton transition-colors">Mentions légales et statuts</a>
</li>
<li>
<a href="https://status.kaz.bzh/status/kaz" class="hover:text-bouton transition-colors">Santé des services Kaz</a>
<a href="https://status.kaz.bzh/status/kaz" target="_blank" class="hover:text-bouton transition-colors">Santé des services Kaz</a>
</li>
<li>
<a href="https://kaz.bzh/contact/" class="hover:text-bouton transition-colors">Contact</a>
<a href="https://kaz.bzh/contact/" target="_blank" class="hover:text-bouton transition-colors">Contact</a>
</li>
</ul>
</div>

View File

@@ -16,7 +16,7 @@
Bienvenue sur ton espace kaznaute <span class="text-bouton">{{ app.user ? app.user.userIdentifier : 'visiteur' }}</span> !
</h2>
{# Zone réservée pour les futures données de l'API et Pahéko
{# Zone réservée pour les futures données de Pahéko
TODO : Gérer les données avec Pahéko, mise en service en cours par un des développeurs de l'association. Cela sera vu à posteriori du stage #}
<div class="bg-bouton/10 border border-bouton/30 rounded-lg p-5">
<h3 class="font-semibold text-title mb-3 flex items-center gap-2">
@@ -91,7 +91,7 @@
</div>
</a>
{% else %}
<a href="https://kaz.bzh/contact/" class="group flex items-center p-5 bg-white border border-gris-clair rounded-xl shadow-sm hover:shadow-md hover:border-bouton transition-all duration-200">
<a href="https://kaz.bzh/contact/" target="_blank" class="group flex items-center p-5 bg-white border border-gris-clair rounded-xl shadow-sm hover:shadow-md hover:border-bouton transition-all duration-200">
<div class="flex-shrink-0 bg-purple-50 text-purple-600 rounded-lg p-3 group-hover:bg-purple-600 group-hover:text-white transition-colors">
<span class="text-2xl block">✉️</span>
</div>