Bien commencer le projet

Ce document est un tutoriel pour montrer comment bien démarrer le projet QuizMaker. L’objectif est de comprendre le fonctionnement global du backend et les étapes nécessaires pour mettre en place la première route d’API : /api/user/login.

L’implémentation décrite peut varier selon les choix de chacun et évoluer au fil du développement.

Introduction

Ce projet est structuré en plusieurs modules Maven :

  • quizmaker-api : définit les objets de transfert (DTO) et les interfaces d’API.

  • quizmaker-domain : contient les entités métiers, les interfaces de service et les repositories.

  • quizmaker-spring : regroupe l’implémentation concrète, la configuration Spring Boot, Liquibase, et la génération des routes via OpenAPI.

Chaque module a un rôle précis et indépendant. Le but est d’obtenir un projet propre, structuré et facilement extensible.

Comprendre la logique du projet

Le flux complet d’une requête (par exemple un login) suit ce chemin :

  1. L’utilisateur appelle une route définie dans OpenAPI (openapi.yaml).

  2. OpenAPI génère automatiquement le contrat Java correspondant (UserApi, LoginRequest, etc.).

  3. Le contrôleur Spring (UserController) implémente cette interface et délègue la logique métier au UserService.

  4. Le service interroge les repositories (StudentRepository, TeacherRepository) pour vérifier les informations.

  5. Si un utilisateur est trouvé, il renvoie un User DTO au client.

  6. Liquibase s’assure que les tables nécessaires existent dans la base de données.

Chaque couche a donc un rôle spécifique, sans dépendre directement des autres.

Étape 1 — Créer le modèle de données (module quizmaker-api)

Dans le module quizmaker-api, on définit un objet de transfert appelé User. Cet objet représente les informations échangées entre le backend et le frontend :

  • un identifiant (id)

  • une adresse email (email)

  • un nom (name)

  • un prénom (firstname)

  • un rôle (role), qui peut être STUDENT ou TEACHER.

Remplacez les TODO de la classe User dans quizmaker-api pour implémenter votre code.

Indication de code
public record User(Long id, String email, String name, String firstname, String role) {
}

Étape 2 — Définir le service métier (module quizmaker-domain)

Dans le module quizmaker-domain, on crée une interface UserService. Cette interface décrit les opérations disponibles sur les utilisateurs, par exemple :

  • la connexion (login)

  • éventuellement l’inscription, la modification du profil, etc.

À ce stade, on ne fait qu’exposer une signature (pas d’implémentation). Cela permet de séparer la logique métier (implémentée plus tard) de la définition du contrat.

Remplacez les TODO pour ajouter la méthode login dans la classe UserService.

Indication de code
    /**
     * Authentifie un utilisateur à partir de son email et de son mot de passe.
     * @param email email de l'utilisateur
     * @param password mot de passe
     * @return l'utilisateur authentifié (ou lève une exception en cas d'échec)
     */
    User login(String email, String password);

Étape 3 — Créer les entités persistantes (module quizmaker-domain)

Toujours dans quizmaker-domain, on définit les entités JPA : PersistentStudent et PersistentTeacher. Elles correspondent aux tables qui existeront dans la base de données. Chaque entité contient :

  • un identifiant unique (id)

  • les champs name, firstname, email, et password.

Remplacez les TODO pour implémenter votre le PersistentStudent et PersistentTeacher.

Indication de code pour l’entité PersistentStudent
public class PersistentStudent {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(nullable = false)
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "name")
    private String name;

    @Column(name = "firstname")
    private String firstname;

    @Column(name = "email")
    private String email;

    @Column(name = "password")
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getFirstName(){
        return firstname;
    }

    public void setFirstName(String firstname){
        this.firstname = firstname;
    }

    public String getEmail(){
        return email;
    }

    public void setEmail(String email){
        this.email = email;
    }

    public String getPassword() { return password; }

    public void setPassword(String password) { this.password = password; }
}
Indication de code pour l’entité PersistentTeacher
public class PersistentTeacher {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(nullable = false)
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column(name = "name")
    private String name;

    @Column(name = "firstname")
    private String firstname;

    @Column(name = "email")
    private String email;

    @Column(name = "password")
    private String password;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getFirstName(){
        return firstname;
    }

    public void setFirstName(String firstname){
        this.firstname = firstname;
    }

    public String getEmail(){
        return email;
    }

    public void setEmail(String email){
        this.email = email;
    }

    public String getPassword() { return password; }

    public void setPassword(String password) { this.password = password; }
}

Étape 4 — Créer les repositories (module quizmaker-domain)

On ajoute deux repositories :

  • StudentRepository

  • TeacherRepository

Ces interfaces héritent de JpaRepository et permettent à Spring Data JPA de générer automatiquement toutes les opérations de base (findAll, save, deleteById, etc.).

Ici, vous n’avez pas à les implémenter, ils sont déjà implémenter dans StudentRepository et TeacherRepository.

Étape 5 — Implémenter la logique métier (module quizmaker-spring)

Dans le module quizmaker-spring, on implémente l’interface UserService dans une classe UserServiceImpl. Cette classe contient la logique de connexion :

  • vérifier si l’adresse email et le mot de passe correspondent à un étudiant ou un enseignant,

  • renvoyer le DTO User avec le bon rôle (STUDENT ou TEACHER),

  • lever une exception si les identifiants sont invalides.

Remplacez les TODO pour implémenter la fonction login.

Indication de code pour UserServiceImpl
 public User login(String email, String password) {
        PersistentStudent s = studentRepository.findAll().stream()
                .filter(u -> email != null && u.getEmail() != null && u.getEmail().equalsIgnoreCase(email))
                .filter(u -> password != null && u.getPassword() != null && u.getPassword().equals(password))
                .findFirst()
                .orElse(null);

        if (s != null) {
            return new User(
                    s.getId(),
                    s.getEmail(),
                    s.getName(),
                    s.getFirstName(),
                    ROLE_STUDENT
            );
        }

        PersistentTeacher t = teacherRepository.findAll().stream()
                .filter(u -> email != null && u.getEmail() != null && u.getEmail().equalsIgnoreCase(email))
                .filter(u -> password != null && u.getPassword() != null && u.getPassword().equals(password))
                .findFirst()
                .orElse(null);

        if (t != null) {
            return new User(
                    t.getId(),
                    t.getEmail(),
                    t.getName(),
                    t.getFirstName(),
                    ROLE_TEACHER
            );
        }

        throw new IllegalArgumentException("Invalid email or password.");
    }

Étape 6 — Définir la route API avec OpenAPI

OpenAPI permet de décrire les routes HTTP de manière standardisée. Dans quizmaker-spring/src/main/resources/openapi/openapi.yaml, on définit les endpoints de notre API.

Pour la route /api/user/login :

  • on décrit qu’elle accepte une requête POST avec un LoginRequest contenant email et password.

  • on précise les réponses possibles (200 si succès, 401 si identifiants invalides).

  • on relie la réponse au modèle User.

Lors de la compilation du projet, le plugin OpenAPI Generator :

  • lit ce fichier YAML,

  • génère automatiquement les classes Java correspondantes (interface UserApi, modèles User et LoginRequest),

  • place ces fichiers dans target/generated-sources/openapi/.

La route ici est déjà défini, nous vous laissons lire openapi.yaml.

Étape 7 — Lier le contrôleur au service

Une fois la génération OpenAPI terminée, on crée un contrôleur UserController qui implémente UserApi.

Ce contrôleur :

  • reçoit la requête HTTP,

  • extrait les informations du LoginRequest,

  • appelle userService.login(email, password),

  • renvoie la réponse (User) au client.

Remplacez les TODO pour implémenter la logique du controller.

Indication de code pour UserController
public ResponseEntity<User> apiUserLoginPost(LoginRequest loginRequest) {
        try {
            var user = userService.login(loginRequest.getEmail(), loginRequest.getPassword());

            User response = new User()
                    .id(user.id())
                    .email(user.email())
                    .name(user.name())
                    .firstname(user.firstname())
                    .role(user.role());

            return ResponseEntity.ok(response);

        } catch (IllegalArgumentException e) {
            return ResponseEntity.status(401).build();
        } catch (Exception e) {
            return ResponseEntity.badRequest().build();
        }
    }

Étape 8 — Gérer la base de données avec Liquibase

Liquibase est utilisé pour créer et versionner les tables en base. Dans src/main/resources/db/changelog/db.changelog-master.xml, on définit les changements à appliquer (appelés changeSet).

Exemple :

  • création de la table student avec ses colonnes,

  • création de la table teacher.

Lors du démarrage de Spring Boot :

  • Liquibase détecte le changelog,

  • applique les modifications manquantes,

  • et maintient un historique des versions dans une table interne (DATABASECHANGELOG).

Ici, nous vous invitons à lire le contenu de master.xml pour voir les deux tables présentes.

Étape 9 — Comprendre le fonctionnement complet du login

Voici le flux complet d’une requête de connexion :

  1. Le client envoie une requête POST vers /api/user/login avec l’email et le mot de passe.

  2. Spring Boot redirige cette requête vers la méthode correspondante du contrôleur généré via OpenAPI.

  3. Le contrôleur appelle le service UserService.

  4. Le service vérifie dans les repositories si un étudiant ou un enseignant correspond à l’email et au mot de passe.

  5. Si un utilisateur est trouvé :

    • un User DTO est créé et renvoyé au contrôleur,

    • qui le renvoie en réponse JSON au client avec un code HTTP 200.

  6. Sinon, une exception est levée, et la réponse HTTP est 401 Unauthorized.

Chaque couche a donc une responsabilité claire :

  • OpenAPI → définit la structure de l’API.

  • Contrôleur → reçoit et renvoie les données.

  • Service → applique la logique métier.

  • Repository → interagit avec la base.

  • Liquibase → gère la structure de la base.

Étape 10 — Étendre le projet

Une fois la route /api/user/login fonctionnelle, tu peux facilement ajouter d’autres fonctionnalités :

  • créer de nouvelles routes OpenAPI (/api/user/register, /api/quiz/create, etc.),

  • ajouter de nouveaux changeSet Liquibase pour les tables nécessaires,

  • créer les DTO correspondants dans quizmaker-api,

  • implémenter les nouveaux services dans quizmaker-spring.

Cette approche garantit que chaque ajout reste cohérent et documenté, sans duplication ni conflit entre modules.