Skip to content

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 dans compose.yaml — pas besoin de docker compose up manuel.
  • Les identifiants de la base (.env) sont injectés comme variables d'environnement dans application.yml via un bean PropertySourcesPlaceholderConfigurer (AppConfig.java) qui charge le fichier .env comme source de propriétés.
  • ddl-auto: update : le schéma (table user) 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)

  1. UserController.register reçoit un RegisterDTO (validé via @Valid : tous les champs sont @NotBlank).
  2. UserDtoMapper convertit le DTO en entité User.
  3. UserService.register :
  4. rejette si le login existe déjà (IllegalArgumentException → HTTP 400 via RestExceptionHandler) ;
  5. encode le mot de passe avec BCryptPasswordEncoder ;
  6. sauvegarde via UserRepository.
  7. 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) :

  1. UserService.login compare le mot de passe fourni à lui-même :
if (user.isPresent() && passwordEncoder.matches(password, password)) {

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.

  1. JwtService.generateToken n'est pas implémenté :
public String generateToken(UserDetails userDetails) {
    return null; // TODO
}

L'API retourne donc toujours null en cas de succès.

  1. UserController.login ne porte pas l'annotation @RequestBody sur le paramètre LoginRequestDTO (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.
@PostMapping("/api/login")
public ResponseEntity<?> login(LoginRequestDTO loginRequestDTO) {

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).
  • CustomUserDetailService charge un User par login via UserRepository ; l'entité User implémente déjà UserDetails directement (pas de classe UserPrincipal séparée).

Base de données

  • MySQL (image mysql:latest) démarrée via compose.yaml, volume nommé db_data pour la persistance.
  • Une seule table pour l'instant : user (id, firstName, lastName, login unique, password, created_at, updated_at).
  • Credentials (.env) : base etudiant_db, utilisateur etudiant_db.

Front-end (front-end/)

Stack et démarrage

  • Angular 19 (standalone components, pas de NgModule applicatif — seul MaterialModule regroupe 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é dans angular.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é avec FormBuilder : firstName, lastName, login, password, tous Validators.required.
  • Affichage des erreurs de validation par champ (is-invalid + message conditionnel) uniquement après tentative de soumission (submitted).
  • onSubmit() appelle UserService.register(...), désabonnement propre via takeUntilDestroyed.
  • En cas de succès : alert('SUCCESS!! :-)') — un // TODO indique 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/état error dans le flux subscribe) — à 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 :

  1. POST /api/register en direct sur localhost:8080201 Created.
  2. POST /api/register via le proxy Angular (localhost:4200localhost:8080, celui qu'utilise réellement le formulaire /register) → 201 Created.
  3. Contrôle de la table user dans le conteneur back-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 Docker db_data persiste 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émenter JwtService.generateToken, et ajouter @RequestBody sur le paramètre de UserController.login.
  • Étape 3 (front — écran login) : créer route /login, composant, méthode login() dans UserService, 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ègle anyRequest().authenticated() de SpringSecurityConfig s'appliquera automatiquement dès qu'elles seront ajoutées.
  • Étape 5 (front — écrans CRUD) : prévoir un Guard Angular (inexistant actuellement) pour protéger les futures routes étudiants.