Exercice 1 - Étape 1
Back-end (back-end/)¶
Stack et démarrage¶
- Spring Boot 3.5.5 / Java 21 / Maven, port 8080.
spring-boot-docker-compose: au lancement (mvn spring-boot:run), Spring Boot démarre automatiquement le conteneur MySQL défini danscompose.yaml— pas besoin dedocker compose upmanuel.- Les identifiants de la base (
.env) sont injectés comme variables d'environnement dansapplication.ymlvia un beanPropertySourcesPlaceholderConfigurer(AppConfig.java) qui charge le fichier.envcomme source de propriétés. ddl-auto: update: le schéma (tableuser) est créé/mis à jour automatiquement par Hibernate à partir de l'entité JPA.
Organisation des packages¶
| Package | Rôle |
|---|---|
controller |
Endpoints REST (UserController) |
service |
Logique métier (UserService, JwtService) |
repository |
Accès données Spring Data JPA (UserRepository) |
entities |
Entités JPA (User) |
dto |
Objets d'échange API (RegisterDTO, LoginRequestDTO) |
mapper |
Conversion DTO ↔ entité via MapStruct (UserDtoMapper) |
configuration.security |
Spring Security (SpringSecurityConfig, CustomUserDetailService) |
configuration.logging |
Filtre de log des requêtes HTTP |
handler |
Gestion centralisée des erreurs (RestExceptionHandler) |
Le découpage controller → service → repository est bien respecté, et aucune entité JPA n'est exposée directement dans le controller (passage systématique par des DTO + mapper), conformément aux conventions attendues.
Flux d'enregistrement — POST /api/register (fonctionnel)¶
UserController.registerreçoit unRegisterDTO(validé via@Valid: tous les champs sont@NotBlank).UserDtoMapperconvertit le DTO en entitéUser.UserService.register:- rejette si le
loginexiste déjà (IllegalArgumentException→ HTTP 400 viaRestExceptionHandler) ; - encode le mot de passe avec
BCryptPasswordEncoder; - sauvegarde via
UserRepository. - Réponse 201 Created.
Ce flux est couvert par des tests (UserControllerTest, tests
d'intégration avec Testcontainers + MySQL réel ; UserServiceTest, tests
unitaires avec Mockito).
Flux d'authentification — POST /api/login (non fonctionnel, à corriger en étape 2)¶
Trois anomalies identifiées dans le code existant (aucune n'a été corrigée à ce stade — conformément à la consigne « ne pas modifier le code » de l'étape 1) :
UserService.logincompare le mot de passe fourni à lui-même :
au lieu de le comparer au mot de passe haché stocké
(user.get().getPassword()). Résultat : n'importe quel mot de passe est
accepté pour un login existant.
JwtService.generateTokenn'est pas implémenté :
L'API retourne donc toujours null en cas de succès.
UserController.loginne porte pas l'annotation@RequestBodysur le paramètreLoginRequestDTO(contrairement àregister) : Spring va tenter de le lier comme un model attribute (paramètres de requête / formulaire) plutôt que de désérialiser un JSON envoyé dans le corps de la requête.
Ces trois points correspondent exactement aux bugs mentionnés dans l'énoncé
de l'étape 2 (exercices1.md) : ils seront corrigés à cette étape-là, pas
maintenant.
Sécurité (SpringSecurityConfig)¶
- Session stateless (pas de session HTTP côté serveur — cohérent avec une auth par JWT à venir).
- CORS et CSRF désactivés (API consommée par un front connu, en local via proxy).
- Routes publiques :
/actuator/**,/api/register,/api/login. - Toute autre route (
anyRequest()) exige une authentification — c'est cette règle qui protégera les futures APIs CRUD étudiants (étape 4). CustomUserDetailServicecharge unUserpar login viaUserRepository; l'entitéUserimplémente déjàUserDetailsdirectement (pas de classeUserPrincipalséparée).
Base de données¶
- MySQL (image
mysql:latest) démarrée viacompose.yaml, volume nommédb_datapour la persistance. - Une seule table pour l'instant :
user(id, firstName, lastName, login unique, password, created_at, updated_at). - Credentials (
.env) : baseetudiant_db, utilisateuretudiant_db.
Front-end (front-end/)¶
Stack et démarrage¶
- Angular 19 (standalone components, pas de
NgModuleapplicatif — seulMaterialModuleregroupe les imports Angular Material/CDK). - Angular Material + Bootstrap CSS (
netdna.bootstrapcdn.com) pour le style des formulaires. - Tests unitaires : Jest. Tests E2E : Cypress (à mettre en place, aucun test E2E présent actuellement).
- Port 4200, proxy
/api → http://localhost:8080(proxy.conf.json, déjà branché dansangular.json).
Organisation¶
| Dossier | Contenu |
|---|---|
core/models |
Interfaces TypeScript des DTO (Register) |
core/service |
Services HTTP (UserService) + un UserMockService inutilisé (probablement prévu pour les tests) |
pages/register |
Écran d'enregistrement (composant + template + tests) |
shared |
MaterialModule (regroupe les modules Angular Material) |
Routing (app.routes.ts)¶
export const routes: Routes = [
{ path: '', component: AppComponent },
{ path: 'register', component: RegisterComponent },
];
Seule la route /register existe pour l'instant. Aucune route /login
(à créer en étape 3), aucun guard (à créer en étape 5).
RegisterComponent¶
ReactiveFormsModule, formulaire typé avecFormBuilder:firstName,lastName,login,password, tousValidators.required.- Affichage des erreurs de validation par champ (
is-invalid+ message conditionnel) uniquement après tentative de soumission (submitted). onSubmit()appelleUserService.register(...), désabonnement propre viatakeUntilDestroyed.- En cas de succès :
alert('SUCCESS!! :-)')— un// TODOindique qu'il faudra rediriger vers l'écran de login une fois celui-ci créé (étape 3). - Aucune gestion d'erreur HTTP pour l'instant (pas de
catchError/étaterrordans le fluxsubscribe) — à prévoir aux étapes suivantes (points de vigilance de l'énoncé : « assurez-vous que les erreurs serveur s'affichent »).
UserService¶
register(user: Register): Observable<Object> {
return this.httpClient.post('/api/register', user);
}
Simple, un seul point d'entrée pour l'instant. Un login() similaire sera
à ajouter en étape 3, consommant /api/login.
Vérifications réalisées (résultat attendu de l'étape 1)¶
| Vérification | Statut |
|---|---|
| Lecture et compréhension du starter code back-end | ✅ |
| Lecture et compréhension du starter code front-end | ✅ |
npm install (front-end) |
✅ (1087 paquets installés) |
Lancement du front-end (npm run start) |
✅ — http://localhost:4200/register répond 200, formulaire affiché |
Lancement du back-end (mvn spring-boot:run) |
✅ — démarré en 6,4 s, conteneur back-end-mysql-1 Healthy |
Création d'un agent via /register + vérification en base |
✅ — voir ci-dessous |
Résolution du blocage Docker Desktop / WSL¶
Le blocage initial (CLI docker en erreur d'E/S, socket du daemon
inaccessible) était bien un souci d'intégration WSL ↔ Docker Desktop côté
Windows : après redémarrage de Docker Desktop / de la distribution WSL par
l'utilisateur, docker version répond correctement (client 29.6.1,
serveur Docker Desktop 4.82.0, Compose v5.3.0).
JDK 21 (openjdk-21-jdk) et Maven 3.9.9 ont ensuite été installés dans la
distribution WSL (sudo apt-get install openjdk-21-jdk maven), ce qui
manquait pour exécuter mvn spring-boot:run localement.
Test complet d'enregistrement d'un agent¶
Avec le back-end démarré, vérifications effectuées :
POST /api/registeren direct surlocalhost:8080→ 201 Created.POST /api/registervia le proxy Angular (localhost:4200→localhost:8080, celui qu'utilise réellement le formulaire/register) → 201 Created.- Contrôle de la table
userdans le conteneurback-end-mysql-1(docker exec back-end-mysql-1 mysql -u etudiant_db -p ...) : les nouveaux comptes de test apparaissent bien, aux côtés de comptes déjà créés lors de précédents essais (le volume Dockerdb_datapersiste les données entre redémarrages).
Point relevé en base : Hibernate stocke les colonnes en
first_name/last_name (snake_case) alors que l'entité User déclare
explicitement @Column(name = "firstName") / "lastName"
(camelCase). La stratégie de nommage physique par défaut de Spring Boot
(CamelCaseToUnderscoresNamingStrategy) convertit les noms de colonnes en
snake_case même quand un nom explicite est fourni — comportement normal,
mais à garder en tête si une requête SQL manuelle est écrite plus tard
(ex. dans un test d'intégration à l'étape 2 de l'exercice 2).
Les deux applications (front :4200, back :8080) tournent et
communiquent normalement. L'étape 1 est validée de bout en bout.
Points de vigilance pour les étapes suivantes¶
- Étape 2 (back — auth JWT) : corriger dans l'ordre
UserService.login(comparaison de mot de passe), implémenterJwtService.generateToken, et ajouter@RequestBodysur le paramètre deUserController.login. - Étape 3 (front — écran login) : créer route
/login, composant, méthodelogin()dansUserService, gestion des états chargement/erreur/succès (absente aujourd'hui même sur/register). - Étape 4 (back — CRUD étudiants) : suivre le même découpage en couches
et la même approche DTO que pour
User/RegisterDTO. Les routes devront être protégées — la règleanyRequest().authenticated()deSpringSecurityConfigs'appliquera automatiquement dès qu'elles seront ajoutées. - Étape 5 (front — écrans CRUD) : prévoir un
GuardAngular (inexistant actuellement) pour protéger les futures routes étudiants.