Aller au contenu

API — Parcours pédagogiques

Cette page documente les endpoints du parcours pédagogique de ChaosStudentController, montés sous /api/chaos/student/pedagogique. Ces endpoints gèrent un scénario d'apprentissage guidé où le formateur active un niveau BAC et les étudiants progressent à travers une série d'énigmes.

Contrôleur couvert

ChaosStudentController — partie parcours pédagogiques (activate, deactivate, hints, join, validate, sessions, last-summary, succes/{token}, level, logique/questions, logique/check, finale/validate)

La partie self-service du même contrôleur est documentée dans chaos-student.md.


Vue d'ensemble du modèle pédagogique

Un parcours pédagogique (ou « parcours BAC ») est une progression guidée en 5 niveaux de difficulté :

Niveau Surnom Étoiles max Format du code agent
bac1 Junior 1 ⭐ 4 chiffres décimaux (ex: 3847)
bac2 Confirmé 2 ⭐ 4 chiffres décimaux
bac3 Expert 3 ⭐ 4 caractères hexadécimaux majuscules (ex: A3F7)
bac4 Master 4 ⭐ 4 caractères hexadécimaux majuscules
bac5 Maestro 5 ⭐ 4 caractères hexadécimaux majuscules

Chaque niveau possède son propre catalogue d'énigmes dans PedagogiqueEnigme.forLevel(level). Tous les étudiants d'un même niveau voient exactement les mêmes énigmes dans le même ordre — le parcours n'est pas tiré aléatoirement. La personnalisation se fait sur deux axes :

  1. Le code agent — chaque étudiant reçoit son propre code dérivé de son UUID, qui sert à calculer la réponse à certaines énigmes « dynamiques ».
  2. Le tirage logique — pour le thème Logique & Mathématiques (clé finale du parcours), 5 questions sont tirées dans un pool de 25 via un LCG seedé par le token étudiant.

Flux général

sequenceDiagram
    autonumber
    participant F as Formateur
    participant S as Étudiant
    participant B as Backend

    F->>B: POST /pedagogique/activate (level, memoryCache)
    B-->>F: 200 — parcours actif
    S->>B: POST /pedagogique/join
    B-->>S: 200 — token, alias, totalSteps, timer
    loop Pour chaque énigme
        S->>B: POST /pedagogique/validate<br/>{step, answerHash}
        B-->>S: 200 — {valid, nextEnigme|completed}
    end
    Note over S: Dernière étape validée
    S->>B: GET /pedagogique/logique/questions
    B-->>S: 5 questions logique tirées
    S->>B: POST /pedagogique/logique/check<br/>{answerHashes[5]}
    B-->>S: 200 — results[5]
    S->>B: POST /pedagogique/finale/validate<br/>{theme, answerHash}
    B-->>S: 200 — {valid, gameUrl?}

Authentification des endpoints

Les endpoints de ce contrôleur utilisent trois mécanismes d'authentification selon le rôle :

Rôle Mécanisme Endpoints concernés
Formateur X-Admin-Token ou session admin /activate, /deactivate, /hints, /sessions, /last-summary
Étudiant header X-Student-Token (UUID unique par étudiant) /validate, /logique/questions, /logique/check, /finale/validate
Anonyme Aucune /join, /succes/{token}, /level

Le X-Student-Token est généré par POST /pedagogique/join et doit être stocké côté client (typiquement dans localStorage). Chaque requête étudiante transporte ce header pour identifier sa session individuelle.


Liste des endpoints

# Méthode Endpoint Rôle
1 POST /api/chaos/student/pedagogique/activate Formateur — démarre un parcours
2 POST /api/chaos/student/pedagogique/deactivate Formateur — arrête le parcours
3 POST /api/chaos/student/pedagogique/hints Formateur — toggle les indices
4 GET /api/chaos/student/pedagogique/sessions Formateur — liste toutes les sessions étudiantes
5 GET /api/chaos/student/pedagogique/last-summary Formateur — snapshot du dernier parcours terminé
6 POST /api/chaos/student/pedagogique/join Étudiant — rejoint le parcours
7 POST /api/chaos/student/pedagogique/validate Étudiant — valide une énigme
8 POST /api/chaos/student/pedagogique/level Étudiant — change son niveau (self-service)
9 GET /api/chaos/student/pedagogique/succes/{token} Public — page de succès partageable
10 GET /api/chaos/student/pedagogique/logique/questions Étudiant — récupère ses 5 questions logique
11 POST /api/chaos/student/pedagogique/logique/check Étudiant — vérifie individuellement les 5 réponses
12 POST /api/chaos/student/pedagogique/finale/validate Étudiant — validation finale d'un thème

POST /pedagogique/activate — Formateur

Active un parcours pédagogique au niveau BAC demandé. Remplace le parcours en cours et vide toutes les sessions étudiantes précédentes.

Auth : admin (session ou X-Admin-Token) Licence : requiert une licence valide (402 LICENSE_REQUIRED sinon)

Requête

{
  "level": "bac3",
  "timerSeconds": 2400,
  "memoryCache": true
}
Champ Type Requis Description
level string oui Identifiant du niveau : bac1, bac2, bac3, bac4 ou bac5
timerSeconds integer non Durée du parcours en secondes. Défaut = PedagogiqueEnigme.defaultTimerSeconds(level)
memoryCache boolean non Active le cache mémoire write-through. Défaut : false

Réponse — 200 OK

{
  "success": true,
  "level": "bac3",
  "totalSteps": 8,
  "timer": 2400,
  "memoryCacheEnabled": true
}
Champ Description
level Niveau activé, tel qu'envoyé dans la requête
totalSteps Nombre d'énigmes du niveau — déterminé par PedagogiqueEnigme.forLevel(level).size()
timer Durée en secondes effectivement retenue
memoryCacheEnabled État du cache mémoire après activation

Codes d'erreur

Code Cause
400 level hors de [bac1, bac2, bac3, bac4, bac5] (clé student.pedagogique.error.level_unknown)
401 Non authentifié admin
402 Aucune licence valide (LICENSE_REQUIRED)

Cache mémoire write-through

Le paramètre memoryCache active une couche de cache en ConcurrentHashMap par-dessus la persistance DB. Quand activé :

  • Chaque session créée est stockée à la fois en base (toujours) et en heap (cache)
  • Les lectures consultent d'abord le cache (O(1) par session)
  • Les objets MutableSession apparaissent dans les heap dumps générés par le Chaos Mémoire

La démonstration pédagogique associée consiste à activer memoryCache: true puis à générer un heap dump via /actuator/heapdump pour y retrouver les tokens, alias et progressions des étudiants — illustration concrète d'une fuite de données via fuite mémoire. En désactivé (défaut), les sessions ne survivent en heap que le temps de la requête.


POST /pedagogique/deactivate — Formateur

Arrête le parcours en cours et vide toutes les sessions étudiantes. Un snapshot est pris avant effacement et reste disponible via /last-summary.

Auth : admin

Réponse — 200 OK

{
  "success": true,
  "message": "Parcours pédagogique désactivé"
}

Message localisé via student.pedagogique.deactivated.

Effets de bord

  1. Si des sessions existent, buildSessionSnapshot() est appelé et remplace le contenu de lastSessionSummary
  2. pedagogiqueActive = false, niveau remis à null, timer remis à 0
  3. hintsEnabled remis à true (valeur par défaut pour le prochain parcours)
  4. memoryCache remis à false (le prochain parcours devra le réactiver explicitement si besoin)
  5. pedagogiqueSessionService.clearAll() — toutes les lignes de la table pedagogique_sessions sont supprimées

POST /pedagogique/hints — Formateur

Active ou désactive l'affichage du bouton 💡 indice dans l'interface étudiante, en temps réel pendant un parcours actif. L'état est propagé via le polling /status des étudiants (délai maximal ~15 secondes).

Auth : admin

Requête

{ "enabled": false }
Champ Type Requis Description
enabled boolean oui true = indices visibles, false = bouton masqué

Réponse — 200 OK

{
  "success": true,
  "hintsEnabled": false
}

Codes d'erreur

Code Cause
400 Champ enabled manquant ou non-booléen (clé student.pedagogique.error.hints_enabled_required)
401 Non authentifié admin

GET /pedagogique/sessions — Formateur

Retourne la liste complète des sessions étudiantes du parcours actif, avec leur progression, leurs étoiles et leur durée. Endpoint admin — utilisé par le dashboard formateur.

Auth : admin

Réponse — 200 OK

{
  "sessions": [
    {
      "token": "a7f1c2e8",
      "alias": "Alice",
      "step": 5,
      "totalSteps": 8,
      "joinedAt": 1712586600000,
      "elapsedSeconds": 420,
      "completed": false
    },
    {
      "token": "b3d4e5f6",
      "alias": "Agent-B3D4",
      "step": 8,
      "totalSteps": 8,
      "joinedAt": 1712586640000,
      "elapsedSeconds": 1680,
      "completed": true,
      "stars": 3,
      "maxStars": 3
    }
  ],
  "count": 2,
  "level": "bac3",
  "timerRemaining": 720
}
Champ Description
sessions[].token 8 premiers caractères du token étudiant (troncature pour l'affichage)
sessions[].alias Alias de l'étudiant, ou Agent-XXXX si non fourni (dérivé du token)
sessions[].step Numéro de l'étape courante (0-indexé)
sessions[].elapsedSeconds Temps écoulé depuis le /join de l'étudiant
sessions[].completed true si step >= totalSteps
sessions[].stars / maxStars Présents uniquement si completed: true — voir Règle des étoiles
count Nombre total de sessions dans la liste
level Niveau actif, ou chaîne vide si aucun parcours actif
timerRemaining Secondes restantes sur le timer global du parcours, ou 0

Les sessions sont triées par joinedAt croissant (ordre d'arrivée).

Codes d'erreur

Code Cause
401 Non authentifié admin

GET /pedagogique/last-summary — Formateur

Retourne le snapshot archivé du dernier parcours terminé. Contrairement à /sessions qui interroge les sessions vivantes, /last-summary expose l'état figé pris au moment de deactivate (ou d'un nouvel activate qui écrase le parcours en cours).

Auth : admin

Réponse — 200 OK

{
  "sessions": [
    {
      "alias": "Alice",
      "step": 8,
      "totalSteps": 8,
      "completed": true,
      "joinedAt": 1712586640000,
      "durationSeconds": 1680,
      "stars": 3,
      "maxStars": 3
    }
  ],
  "count": 1
}

Le snapshot est une copie immuable — il n'évolue pas jusqu'au prochain deactivate.

Codes d'erreur

Code Cause
401 Non authentifié admin

Snapshot vide au démarrage

Après un redémarrage du backend, lastSessionSummary est initialisé à List.of() (liste vide). GET /last-summary retourne alors {sessions: [], count: 0} sans erreur.


POST /pedagogique/join — Étudiant

Point d'entrée anonyme pour un étudiant qui rejoint le parcours en cours. Génère un studentToken UUID unique et calcule le agentCode ainsi que les indices de questions logique.

Auth : aucune Licence : requiert une licence valide (402 LICENSE_REQUIRED sinon)

Requête

Corps optionnel :

{ "alias": "Alice" }
Champ Type Requis Description
alias string non Nom d'affichage de l'étudiant. Si absent, génère Agent-XXXX à partir du token

Un corps vide ou absent est accepté — l'étudiant sera identifié par Agent-<4 premiers chars>.

Réponse — 200 OK

{
  "token": "a7f1c2e8-4b9d-4f11-9c8e-1a2b3c4d5e6f",
  "alias": "Alice",
  "level": "bac3",
  "totalSteps": 8,
  "timerRemaining": 2280,
  "timerTotal": 2400
}
Champ Description
token UUID à stocker côté client et à envoyer en X-Student-Token sur les appels suivants
alias Alias retenu (éventuellement généré si non fourni)
level Niveau actif du parcours (ex: bac3)
totalSteps Nombre total d'énigmes du niveau
timerRemaining Secondes restantes par rapport au timer global du parcours
timerTotal Durée totale du parcours en secondes

Le token est la seule clé de session

Le token UUID est la seule identité de l'étudiant — il n'y a pas de cookie, pas de session HTTP. Si le client le perd, la progression est inaccessible depuis l'étudiant (mais pas supprimée — le formateur peut toujours la voir dans /sessions).

Codes d'erreur

Code Cause
402 Aucune licence valide (LICENSE_REQUIRED)
409 Aucun parcours actif (clé student.pedagogique.error.no_active)
429 Limite de 500 sessions simultanées atteinte (clé student.pedagogique.error.too_many)

Limite 500 sessions simultanées

PerfShop accepte jusqu'à 500 sessions actives en parallèle sur un même parcours. Cette limite protège contre un afflux accidentel qui saturerait la mémoire en mode cache. Un parcours normal de formation ne dépasse pas 60 étudiants. Au-delà de 500, le formateur doit désactiver puis réactiver le parcours pour libérer les sessions.

Ce qui est calculé au join

Au moment du /join, le service fait 4 calculs déterministes basés sur le token UUID :

  1. Code agent — 4 chiffres décimaux (bac1/bac2) ou 4 caractères hex majuscules (bac3/bac4/bac5) extraits du token
  2. Réponse dynamique — calculée par niveau (voir Code agent dynamique ci-dessous) puis hachée en SHA-256 (extractionAnswerHash)
  3. Indices logique — 5 indices distincts tirés dans [0..24] via un LCG seedé par le token (identique à l'implémentation frontend)
  4. Hash logique attendu — SHA-256 de la concaténation des 5 réponses correspondant aux indices

Ces 4 valeurs sont persistées dans la session (DB et cache) et utilisées par les endpoints /validate, /logique/* et /finale/validate sans jamais recalculer à la volée.


POST /pedagogique/validate — Étudiant

Valide la réponse à l'énigme courante. L'étudiant envoie un hash SHA-256 pré-calculé côté client, jamais la réponse en clair.

Auth : header X-Student-Token

Requête

{
  "step": 3,
  "answerHash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
Champ Type Requis Description
step integer oui Numéro de l'étape 1-indexé (1 pour la première énigme)
answerHash string oui SHA-256 en hexadécimal de la réponse normalisée (trim + lowercase + remove whitespace)

Normalisation côté client

La normalisation de la réponse avant hash doit suivre exactement l'algorithme du backend :

function hashAnswer(answer) {
  const normalized = answer.trim().toLowerCase().replace(/\s+/g, '');
  // Puis SHA-256 hex du résultat
}

C'est la même normalisation que PedagogiqueSessionService.sha256() côté Java.

Réponse — 200 OK (bonne réponse, étape intermédiaire)

{
  "valid": true,
  "attempts": 1,
  "completed": false,
  "inTime": true,
  "culturalNote": "Note culturelle optionnelle affichée au succès",
  "nextEnigme": {
    "id": "bac3-04",
    "step": 4,
    "text": "Texte de la prochaine énigme...",
    "position": "bottom-right",
    "hintAvailable": true,
    "hint": "Indice contextuel",
    "action": "observer"
  }
}
Champ Description
valid Toujours true pour cette variante
attempts Nombre total de tentatives effectuées sur cette étape (incluant les échecs précédents)
completed false s'il reste des étapes
inTime true si le temps écoulé depuis pedagogiqueStartTime est ≤ timerTotal
culturalNote Note culturelle optionnelle associée à l'énigme (présent uniquement si non null dans le catalogue)
nextEnigme.step Numéro 1-indexé de l'étape suivante
nextEnigme.hint Texte de l'indice si hintAvailable: true, sinon null

Réponse — 200 OK (bonne réponse, parcours terminé)

{
  "valid": true,
  "attempts": 2,
  "completed": true,
  "inTime": true,
  "stars": 3,
  "maxStars": 3
}

Lorsque completed: true, session.completedAt est persisté en base et le nombre d'étoiles est calculé via computeStars(level, inTime).

Réponse — 200 OK (mauvaise réponse)

{
  "valid": false,
  "attempts": 2,
  "message": "Réponse incorrecte — essaie encore !"
}

Message localisé via student.pedagogique.validate.wrong. La tentative est persistée en base (le compteur attempts survit au redémarrage), mais le step n'avance pas.

Codes d'erreur

Code Cause
400 step hors de [1..totalSteps] (clé student.pedagogique.error.step_invalid)
401 Header X-Student-Token absent (clé student.pedagogique.error.token_missing)
401 Token inconnu ou session expirée (clé student.pedagogique.error.token_invalid)
409 Aucun parcours actif
409 step différent de session.step + 1 — tentative de saut ou de retour (clé student.pedagogique.error.step_wrong) — retourne {error, expected, submitted}

Le hash de la réponse n'est jamais exposé

PedagogiqueEnigme ne renvoie jamais les answerHash au client. Pour les énigmes à réponse fixe, le hash est codé en dur dans le catalogue côté serveur. Pour les énigmes « DYNAMIC » (réponse dépendante du code agent), le hash est calculé au /join et stocké dans session.extractionAnswerHash — jamais retourné dans une réponse HTTP. L'étudiant doit donc réellement trouver la réponse, pas la deviner depuis le trafic réseau.


POST /pedagogique/level — Étudiant (self-service)

Permet à l'étudiant de choisir lui-même son niveau de parcours, sans passer par l'admin. Utile quand le formateur laisse la main au mode libre.

Auth : aucune (mais studentModeEnabled doit être actif) Licence : requiert une licence valide

Requête

{ "level": 3 }
Champ Type Requis Description
level integer oui Niveau numérique 0–5. 0 = sortir du parcours, 1..5 = activer le niveau correspondant

Réponse — 200 OK (sortie du parcours)

Lorsque level: 0, le parcours est désactivé si actif, un snapshot est pris, toutes les sessions sont effacées :

{
  "success": true,
  "level": 0,
  "active": false
}

Réponse — 200 OK (niveau déjà actif, même niveau)

Si le parcours demandé est exactement celui en cours, l'état courant est retourné sans réinitialiser :

{
  "success": true,
  "level": 3,
  "levelId": "bac3",
  "active": true,
  "alreadyActive": true,
  "totalSteps": 8,
  "timerRemaining": 1800,
  "timerTotal": 2400
}

Réponse — 200 OK (changement de niveau)

Si le niveau change (ou qu'aucun parcours n'était actif), le parcours est réinitialisé avec le timer par défaut du niveau :

{
  "success": true,
  "level": 3,
  "levelId": "bac3",
  "active": true,
  "totalSteps": 8,
  "timerRemaining": 2400,
  "timerTotal": 2400
}

Codes d'erreur

Code Cause
400 level null ou hors de [0..5] (clé student.pedagogique.error.level_range)
400 Niveau non reconnu côté PedagogiqueEnigme (clé student.pedagogique.error.level_unknown)
402 Aucune licence valide
403 Mode étudiant bloqué par le formateur (clé student.error.blocked)

GET /pedagogique/succes/{token} — Public

Page de succès partageable — affiche le résultat d'un étudiant ayant terminé son parcours. Utilise le studentToken de l'étudiant comme identifiant public.

Auth : aucune

Paramètres

Paramètre Emplacement Description
token path studentToken de l'étudiant (le même que celui utilisé en X-Student-Token)

Réponse — 200 OK

{
  "alias": "Alice",
  "level": "bac3",
  "stars": 3,
  "maxStars": 3,
  "durationSeconds": 1680,
  "completedAt": 1712588400000,
  "totalSteps": 8
}
Champ Description
alias Alias de l'étudiant (ou Agent-XXXX)
level Niveau du parcours terminé
stars / maxStars Étoiles obtenues vs étoiles max du niveau
durationSeconds Durée totale entre joinedAt et completedAt
completedAt Timestamp epoch millis de la validation de la dernière étape
totalSteps Nombre d'étapes du parcours

Codes d'erreur

Code Cause
404 Token inconnu (clé student.pedagogique.error.session_not_found)
404 Session non terminée (step < totalSteps ou completedAt == 0) (clé student.pedagogique.error.not_completed)

L'URL est publique mais opaque

L'endpoint n'est protégé par aucune authentification — le token dans l'URL fait office de secret partageable. C'est voulu : l'étudiant peut partager le lien /succes/{token} avec son formateur ou ses collègues. La sécurité repose sur l'imprévisibilité du UUID (128 bits aléatoires), pas sur un contrôle d'accès.


Thème Logique & Mathématiques (BAC5 final)

Le thème Logique est spécifique au parcours BAC5 final. Il consiste à répondre correctement à 5 questions de calcul mental, tirées dans un pool de 25, pour déverrouiller la dernière validation du parcours.

Deux endpoints sont dédiés à ce thème :

  • GET /pedagogique/logique/questions — récupère les 5 questions tirées pour l'étudiant
  • POST /pedagogique/logique/check — vérifie les réponses individuellement sans débloquer le parcours

GET /pedagogique/logique/questions — Étudiant

Retourne les 5 questions logique tirées au /join pour cet étudiant. Les indices sont calculés au join puis persistés — l'endpoint ne fait que les relire et matérialiser les textes depuis le pool i18n.

Auth : header X-Student-Token

Réponse — 200 OK

{
  "questions": [
    { "index": 15, "text": "Combien de jours dans 7 mois de 31 jours ?", "hint": "31 × 7" },
    { "index": 10, "text": "Racine carrée de 144 ?",                     "hint": "12²" },
    { "index": 18, "text": "Minutes dans une journée ?",                 "hint": "24 × 60" },
    { "index": 19, "text": "Nombre de minutes dans une demi-heure ?",    "hint": "60 ÷ 2" },
    { "index": 14, "text": "Combien font 3 × 13 ?",                      "hint": "Trois fois treize" }
  ]
}
Champ Description
questions[].index Index de la question dans le pool de 25 (valeur 0..24)
questions[].text Énoncé de la question dans la langue active (PERFSHOP_LANG)
questions[].hint Indice d'aide au calcul

Aucune réponse n'est jamais exposée par cet endpoint.

Codes d'erreur

Code Cause
401 Header X-Student-Token absent (clé student.pedagogique.token_missing)
401 Token inconnu ou invalide
500 Erreur interne — indices non calculés ou pool i18n introuvable (clé student.pedagogique.error.logique_indices)

Source des questions

Le pool est chargé une seule fois au premier appel depuis i18n/logique/logique_<lang>.json (où <lang> est la valeur de la variable d'environnement PERFSHOP_LANG, défaut fr). Le fichier contient 25 paires {text, hint}. Les réponses associées sont stockées uniquement côté backend dans DefaultPedagogiqueSessionService.LOGIQUE_ANSWERS — un tableau Java en clair qui n'est jamais exposé par l'API.

Tirage déterministe par LCG

Le choix des 5 indices utilise un LCG (Linear Congruential Generator) seedé par le token UUID :

int seed = 0;
for (int i = 0; i < token.length(); i++) {
    seed = (seed << 5) - seed + token.charAt(i);
}
// Puis pour chaque tirage :
seed = (seed * 1103515245 + 12345) & 0x7fffffff;
int index = seed % 25;

Le tirage continue jusqu'à obtenir 5 indices distincts. L'algorithme est identique à celui du frontend React (ThemeLogique.jsx) — c'est ce qui garantit la reproductibilité d'un même étudiant entre deux connexions.


POST /pedagogique/logique/check — Étudiant

Vérifie individuellement les 5 réponses hachées sans débloquer la validation finale. Permet à l'étudiant de savoir lesquelles de ses réponses sont fausses avant de tenter finale/validate.

Auth : header X-Student-Token

Requête

{
  "answerHashes": [
    "hash_sha256_réponse_1",
    "hash_sha256_réponse_2",
    "hash_sha256_réponse_3",
    "hash_sha256_réponse_4",
    "hash_sha256_réponse_5"
  ]
}
Champ Type Requis Description
answerHashes array[string] oui Exactement 5 hashes SHA-256 hex, dans l'ordre des questions retournées par /logique/questions

Réponse — 200 OK

{
  "results": [true, false, true, true, false]
}

Le tableau results contient 5 booléens alignés sur l'ordre des answerHashes envoyés. true indique une réponse correcte, false une réponse erronée.

Codes d'erreur

Code Cause
400 Tableau answerHashes absent ou de taille ≠ 5 (clé student.pedagogique.error.logique_check_count)
401 Header X-Student-Token absent ou invalide
500 Indices logique non calculés (session pré-V38)

Sécurité

L'endpoint est conçu pour ne jamais révéler les bonnes réponses :

  • Les réponses stockées côté backend restent en Java (LOGIQUE_ANSWERS)
  • Le serveur hache chaque bonne réponse et compare hash↔hash
  • Le résultat est un simple booléen — pas de fuite de la valeur attendue
  • L'endpoint est en lecture seule : il ne mute jamais la session

Le bénéfice pédagogique est purement ergonomique : éviter à l'étudiant de recommencer les 5 questions à cause d'une seule erreur, sans lui apprendre quoi que ce soit qu'il n'aurait pu deviner en tentant /finale/validate directement.


POST /pedagogique/finale/validate — Étudiant

Valide la réponse maîtresse d'un thème du parcours BAC5 final. L'étudiant appelle cet endpoint une fois par thème (5 thèmes au total). Chaque validation réussie retourne une URL de redirection.

Auth : header X-Student-Token

Requête

{
  "theme": "performance",
  "answerHash": "hash_sha256_de_la_réponse"
}
Champ Type Requis Description
theme string oui Identifiant du thème : performance, fonctionnel, metier, securite ou logique
answerHash string oui SHA-256 hex de la réponse normalisée

Les 5 thèmes

Thème Origine de la réponse attendue
performance Constante côté serveur — identique pour tous les étudiants
fonctionnel Constante côté serveur
metier Constante côté serveur
securite Constante côté serveur
logique Dynamique par étudiant — hash pré-calculé au /join et stocké dans session.logiqueExpectedHash

Les 4 premiers thèmes ont des réponses fixes — elles sont les mêmes pour tous les étudiants quel que soit leur token. Seul logique est personnalisé : la réponse est la concaténation des 5 bonnes réponses aux questions tirées pour cet étudiant, dans l'ordre du tirage.

Réponse — 200 OK (succès)

{
  "valid": true,
  "gameUrl": "https://..."
}

Le champ gameUrl est une URL de redirection vers un hub de mini-jeux pédagogiques débloqué par la validation. La destination exacte dépend du thème validé : le thème logique pointe vers le hub principal, les autres thèmes pointent vers un jeu spécifique directement accessible.

À propos du hub de mini-jeux

Le hub est un espace distinct du backend PerfShop principal, configurable côté serveur via la propriété Spring PUBLIC_GAMES_URL. Son URL n'est volontairement pas documentée ici — elle apparaît uniquement dans la réponse finale/validate, comme une récompense de fin de parcours. Les formateurs qui déploient PerfShop en interne peuvent pointer cette propriété vers leur propre hub de jeux ou vers l'hébergement officiel selon leur contexte.

Réponse — 200 OK (échec)

{
  "valid": false
}

Aucun message, aucune indication de la raison de l'échec. L'étudiant doit simplement retenter.

Codes d'erreur

Code Cause
400 Thème inconnu (clé student.pedagogique.error.theme_unknown)
401 Header X-Student-Token absent ou invalide

Le code agent dynamique

Chaque étudiant reçoit un code agent unique calculé au /join à partir de son token UUID. Ce code sert à deux choses :

  1. Calcul de la réponse dynamique pour les énigmes marquées DYNAMIC dans le catalogue
  2. Identification narrative dans les énigmes du parcours BAC5 (agents secrets)

Format du code selon le niveau

Niveau Format Extraction depuis le token
bac1, bac2 4 chiffres décimaux Tous les chiffres du token UUID, 4 premiers retenus
bac3, bac4, bac5 4 caractères hex majuscules 4 premiers caractères du token après retrait des tirets, en majuscules

Le choix du format est pédagogique : les étudiants de niveau BAC1/BAC2 ne maîtrisent pas nécessairement l'hexadécimal, donc leur code reste décimal. Dès BAC3, la manipulation hex fait partie des compétences attendues.

Calcul de la réponse dynamique par niveau

À partir du code agent, la réponse à une énigme DYNAMIC est calculée par DefaultPedagogiqueSessionService.computeDynamicAnswer() selon l'algorithme du niveau :

Niveau Algorithme Exemple (code → réponse)
bac1 Somme des 4 chiffres × 10 3847(3+8+4+7) × 10 = 220
bac2 Code décimal ÷ somme des 4 chiffres (division entière) 38473847 / 22 = 174
bac3 Somme des valeurs décimales des 4 digits hex A3F710 + 3 + 15 + 7 = 35
bac4 Valeur décimale du code hex, mod 256 A3F741975 mod 256 = 55
bac5 XOR de l'octet haut et de l'octet bas A3F70xA3 ^ 0xF7 = 84

La réponse en clair est ensuite hachée en SHA-256 et stockée dans session.extractionAnswerHash. C'est ce hash qui est comparé au answerHash reçu lors de POST /validate pour les énigmes dynamiques.


Règle des étoiles

Le nombre d'étoiles gagnées en terminant le parcours dépend du niveau et du respect du temps imparti :

stars = niveau (1 à 5)
si temps écoulé > timer → stars -= 1
stars minimum = 1
Niveau Étoiles dans les temps Étoiles hors délai
bac1 1 ⭐ 1 ⭐ (minimum garanti)
bac2 2 ⭐ 1 ⭐
bac3 3 ⭐ 2 ⭐
bac4 4 ⭐ 3 ⭐
bac5 5 ⭐ 4 ⭐

L'évaluation inTime compare (completedAt - joinedAt) en secondes au timerTotal. Un étudiant qui termine à la seconde près est considéré « dans les temps ».


Exemple curl — parcours complet BAC3

# === Formateur ===
TOKEN_ADMIN=$(curl -s -H "Content-Type: application/json" \
  -d '{"email":"admin@perfshop.fr","password":"admin"}' \
  http://localhost:9080/api/admin/login | jq -r '.adminToken')

# 1. Active le parcours BAC3 avec cache mémoire (pour démo heap dump)
curl -H "X-Admin-Token: $TOKEN_ADMIN" -H "Content-Type: application/json" \
     -d '{"level":"bac3","memoryCache":true}' \
     http://localhost:9080/api/chaos/student/pedagogique/activate

# === Étudiant ===

# 2. Rejoint le parcours — récupère son token
RESPONSE=$(curl -s -H "Content-Type: application/json" \
  -d '{"alias":"Alice"}' \
  http://localhost:9080/api/chaos/student/pedagogique/join)
STUDENT=$(echo $RESPONSE | jq -r '.token')
echo "Mon token : $STUDENT"

# 3. Calculer le hash côté client puis valider l'étape 1
#    Exemple bash avec openssl — normalisation trim+lower+nospace
ANSWER="ma_reponse"
NORMALIZED=$(echo -n "$ANSWER" | tr '[:upper:]' '[:lower:]' | tr -d ' \t\n')
HASH=$(echo -n "$NORMALIZED" | openssl dgst -sha256 | awk '{print $2}')

curl -H "X-Student-Token: $STUDENT" \
     -H "Content-Type: application/json" \
     -d "{\"step\":1,\"answerHash\":\"$HASH\"}" \
     http://localhost:9080/api/chaos/student/pedagogique/validate

# 4. (… valider les autres étapes une par une …)

# 5. Consulter sa page de succès (lien partageable)
curl "http://localhost:9080/api/chaos/student/pedagogique/succes/$STUDENT"

# === Formateur : consulter les sessions vivantes ===
curl -H "X-Admin-Token: $TOKEN_ADMIN" \
     http://localhost:9080/api/chaos/student/pedagogique/sessions

# === Formateur : arrêt ===
curl -H "X-Admin-Token: $TOKEN_ADMIN" -X POST \
     http://localhost:9080/api/chaos/student/pedagogique/deactivate

Points d'attention

Persistance DB vs cache mémoire

En mode memoryCache: true, les sessions sont écrites à la fois en cache et en DB. En cas de crash du backend, le cache est perdu mais les sessions sont reconstructibles depuis la table pedagogique_sessions. Un redémarrage n'invalide pas les tokens étudiants — c'est voulu pour éviter les pertes de progression.

Rejouabilité des énigmes

Les énigmes du catalogue sont fixes par niveau — tous les étudiants voient les mêmes questions dans le même ordre. Ce qui varie par étudiant, c'est :

  1. Le code agent et la réponse dynamique des énigmes DYNAMIC
  2. Le tirage des 5 questions logique sur 25 (BAC5 uniquement)

Cette personnalisation ciblée empêche le partage direct de réponses entre étudiants voisins tout en gardant un parcours identique pour la progression et l'évaluation.

Protection anti-triche

Trois mesures empêchent la triche :

  1. Les réponses ne sont jamais exposées — ni en clair, ni en hash, par aucun endpoint
  2. Les hash sont calculés côté client — l'étudiant n'apprend rien en écoutant le trafic réseau
  3. Le code agent est unique — même si un étudiant partage sa réponse dynamique, elle n'est valide que pour son propre code

Liens associés