KutsumKutsum
Accueil
Utilisation de l'appli
Création de questions
Installation
Détails techniques
Accueil
Utilisation de l'appli
Création de questions
Installation
Détails techniques
  • Détails techniques (utilisateurs avancés seulement)

    • Détails techniques (utilisateurs avancés seulement)
    • Architecture générale
    • 🖼️ Gestion des images
    • Base de données
    • Système de scoring
    • Services backend
    • API REST
    • Configuration et environnement
    • Tests et qualité
    • Tests E2E (Playwright) — runbook
    • Tests backend (Jest)
    • Tests frontend (Jest)
    • Déploiement et DevOps
    • Security Documentation
    • Performance & Monitoring
    • Troubleshooting Guide
    • Éditeur de Questions pour Enseignants
    • Landing Page Variants (App)

API REST

Vue d'ensemble

MathQuest expose une API REST complète pour toutes les opérations. L'API utilise JSON pour les requêtes et réponses, avec validation Zod pour la sécurité des données.

Base URL : /api/v1

Authentification : JWT via cookies (teacherToken, authToken)

Import externe MathALÉA

Kutsum expose un flux d'import externe public pour les brouillons MathALÉA. Le flux est en deux temps :

  1. dépôt temporaire du brouillon dans Redis
  2. matérialisation explicite en session enseignant ou en session d'entraînement

Le brouillon temporaire n'est pas une ressource métier durable. Les objets canoniques restent GameTemplate, GameInstance et Question.

POST /api/v1/external-drafts

Dépose un brouillon MathALÉA dans Redis et renvoie un draftId consommable ensuite par Kutsum.

Authentification : Non requise

Allowed origins par défaut :

  • https://coopmaths.fr
  • https://www.coopmaths.fr
  • http://localhost:3000
  • http://localhost:3001
  • http://localhost:3008

Ces origines peuvent être surchargées via EXTERNAL_DRAFT_ALLOWED_ORIGINS.

Rate limit : 10 requêtes POST par minute et par clé de rate limiting.

Taille maximale du payload stocké : 100 KiB UTF-8 sérialisés.

Durée de vie du brouillon temporaire : 15 minutes en Redis.

Contrat d'entrée :

  • le backend n'accepte qu'un seul format de brouillon externe
  • les champs de niveau (gradeLevel, gradeLevels) sont tolérés en entrée pour compatibilité, puis supprimés avant stockage et matérialisation

Exemple de payload canonique :

{
  "source": "mathalea",
  "title": "Fractions CM1",
  "discipline": "Mathematiques",
  "themes": ["fractions", "calcul"],
  "exercises": [
    {
      "id": "ex-1",
      "title": "Fractions simples",
      "questions": [
        {
          "questionType": "multipleChoice",
          "text": "Quelle fraction vaut un demi ?",
          "answerOptions": ["1/2", "2/3"],
          "correctAnswers": [true, false],
          "explanation": "1/2 représente un demi."
        },
        {
          "questionType": "singleChoice",
          "text": "Quelle fraction est irreductible ?",
          "answerOptions": ["2/4", "1/2", "3/6"],
          "correctAnswers": [false, true, false],
          "explanation": "1/2 est deja irreductible."
        },
        {
          "questionType": "numeric",
          "text": "Combien vaut 3 + 4 ?",
          "correctAnswer": 7,
          "tolerance": 0,
          "unit": "",
          "explanation": "3 + 4 = 7."
        },
        {
          "questionType": "math",
          "text": "Ecrire un demi sous forme de fraction irreductible.",
          "targetLatex": "\\frac{1}{2}",
          "validationConfig": {
            "kind": "EXPRESSION",
            "responseFormat": "SINGLE",
            "constraints": [
              { "type": "IS_FRACTION" },
              { "type": "IS_IRREDUCIBLE" }
            ]
          },
          "explanation": "La forme attendue est 1/2."
        }
      ]
    }
  ]
}

Question types actuellement supportés :

  • multipleChoice → question Kutsum multiple-choice
  • singleChoice → question Kutsum single-choice
  • numeric → question Kutsum numeric
  • math → question Kutsum math

Pourquoi pas tous les types Kutsum ?

Le flux d'import MathALÉA est volontairement basé sur un contrat d'entrée explicite. Kutsum n'essaie pas de deviner comment transformer un type externe ambigu en type canonique interne.

Concrètement :

  • multipleChoice, singleChoice, numeric et math ont maintenant un mapping stable et non ambigu
  • les autres types Kutsum restent refusés tant qu'un payload externe partagé n'est pas défini entre MathALÉA et Kutsum
  • cela évite de matérialiser des questions partiellement interprétées ou incompatibles avec la validation canonique

Contraintes de mapping :

  • les questions multipleChoice exigent answerOptions.length === correctAnswers.length, au moins 2 réponses possibles, et au moins 1 réponse correcte
  • les questions singleChoice exigent answerOptions.length === correctAnswers.length, au moins 2 réponses possibles, et exactement 1 réponse correcte
  • les questions numeric exigent un correctAnswer numérique fini
  • les questions math exigent un targetLatex non vide et acceptent un validationConfig Kutsum canonique
  • les structures historiques avec type, statement et choices: [{ text, isCorrect }] sont refusées
  • gradeLevel et gradeLevels sont ignorés s'ils sont fournis
  • themes accepte au plus 50 entrées
  • chaque exercice doit contenir au moins 1 question
  • les structures inconnues sont rejetées à la validation Zod

Pour comprendre comment targetLatex et validationConfig sont structurés et interprétés, voir le guide des questions mathématiques. L'endpoint POST /api/v1/questions/math/preview sert ensuite à tester concrètement une configuration côté enseignant.

Exemple curl complet avec 4 questions (multipleChoice, singleChoice, numeric, math) :

DRAFT_ID=$(curl -sS \
  -X POST 'https://app.kutsum.org/api/v1/external-drafts' \
  -H 'Content-Type: application/json' \
  --data-raw '{
    "source": "mathalea",
    "title": "Démo import MathALÉA",
    "discipline": "Mathematiques",
    "themes": ["couleurs", "alimentation", "calcul", "fractions"],
    "exercises": [
      {
        "id": "demo-1",
        "title": "Questions variées",
        "questions": [
          {
            "questionType": "multipleChoice",
            "text": "Parmi ces couleurs, lesquelles sont primaires ?",
            "answerOptions": ["Rouge", "Bleu", "Vert", "Jaune"],
            "correctAnswers": [true, true, false, true],
            "explanation": "Les couleurs primaires sont le rouge, le bleu et le jaune."
          },
          {
            "questionType": "singleChoice",
            "text": "Lequel de ces aliments n\u0027est pas un fruit ?",
            "answerOptions": ["Pomme", "Banane", "Carotte", "Poire"],
            "correctAnswers": [false, false, true, false],
            "explanation": "La carotte est un légume, pas un fruit."
          },
          {
            "questionType": "numeric",
            "text": "Quel est le carré de 25 ?",
            "correctAnswer": 625,
            "tolerance": 0,
            "unit": "",
            "explanation": "25 × 25 = 625."
          },
          {
            "questionType": "math",
            "text": "Écrire 1/4+1/4 sous forme d\u0027une fraction irréductible.",
            "targetLatex": "\\frac{1}{2}",
            "validationConfig": {
              "kind": "EXPRESSION",
              "responseFormat": "SINGLE",
              "constraints": [
                { "type": "IS_FRACTION" },
                { "type": "IS_IRREDUCIBLE" }
              ]
            },
            "explanation": "1/4 + 1/4 = 2/4, soit 1/2 après simplification."
          }
        ]
      }
    ]
  }' | jq -r '.draftId')

echo "\n"
echo "Draft ID généré : $DRAFT_ID"
echo "URL Kutsum : https://app.kutsum.org/import?draftId=$DRAFT_ID"

Note shell : cet exemple utilise \u0027 à la place des apostrophes littérales dans deux chaînes JSON afin de rester copiable tel quel dans un shell avec --data-raw '...'.

Étape suivante recommandée : ouvrir la page d'import Kutsum avec ce brouillon :

echo "https://app.kutsum.org/import?draftId=$DRAFT_ID"

Cette page frontend est le point d'entrée normal du flux utilisateur :

  • elle lit draftId dans l'URL
  • elle propose le choix entre accès enseignant et entraînement
  • elle matérialise ensuite le brouillon une seule fois, puis redirige automatiquement

En pratique, après le premier curl, l'étape suivante attendue est simplement d'ouvrir /import?draftId=... dans le frontend. Les endpoints de matérialisation documentés plus bas restent des endpoints backend de référence, mais ils ne constituent pas le parcours utilisateur recommandé.

Important : un draftId est à usage unique. Dès qu'une matérialisation réussit, le brouillon Redis est supprimé. Il faut donc soit :

  • passer par la page /import?draftId=...
  • appeler materialize-practice
  • appeler materialize-teacher

mais pas enchaîner plusieurs de ces options avec le même draftId.

Erreurs possibles :

{
  "error": "Origin not allowed for external draft ingestion",
  "code": "EXTERNAL_DRAFT_ORIGIN_NOT_ALLOWED"
}
{
  "error": "Draft payload too large",
  "code": "EXTERNAL_DRAFT_PAYLOAD_TOO_LARGE"
}
{
  "error": "Failed to create external draft",
  "code": "EXTERNAL_DRAFT_CREATE_FAILED"
}

POST /api/v1/external-drafts/:draftId/materialize-teacher

Matérialise le brouillon en activité enseignant Kutsum.

Authentification : Requise, rôle enseignant

Paramètre de chemin : draftId

Réponse de succès :

{
  "accessCode": "AB7K9Q2",
  "gameTemplateId": "uuid-du-template",
  "gameInstanceId": "uuid-de-la-session"
}

Comportement métier :

  • crée des Question canoniques Kutsum
  • crée un GameTemplate avec source: "mathalea"
  • crée un GameInstance quiz avec settings.importSource = "mathalea"
  • supprime le brouillon Redis après matérialisation réussie

Erreurs possibles :

{
  "error": "Draft not found or already consumed",
  "code": "EXTERNAL_DRAFT_NOT_FOUND"
}
{
  "error": "Failed to materialize teacher draft",
  "code": "EXTERNAL_DRAFT_MATERIALIZE_TEACHER_FAILED"
}

POST /api/v1/external-drafts/:draftId/materialize-practice

Matérialise le brouillon en session d'entraînement Kutsum.

Authentification : Optionnelle

Paramètre de chemin : draftId

Réponse de succès :

{
  "accessCode": "CD5M8R4"
}

Comportement métier :

  • si aucun utilisateur authentifié n'est présent, Kutsum crée un propriétaire invité temporaire
  • crée un GameTemplate avec source: "mathalea"
  • crée un GameInstance practice avec settings.importSource = "mathalea"
  • supprime le brouillon Redis après matérialisation réussie

Erreurs possibles :

{
  "error": "Draft not found or already consumed",
  "code": "EXTERNAL_DRAFT_NOT_FOUND"
}
{
  "error": "Failed to materialize practice draft",
  "code": "EXTERNAL_DRAFT_MATERIALIZE_PRACTICE_FAILED"
}

Format des codes d'accès

Les sessions issues de MathALÉA utilisent le même format canonique que les autres sessions Kutsum. Il n'existe pas de format spécial MathALÉA.

  • longueur fixe : 7 caractères
  • alphabet sans ambiguïté : ABCDEFGHJKLMNPQRSTUVWXYZ23456789
  • code généré aléatoirement puis vérifié en base pour l'unicité

Authentification

POST /api/v1/auth

Point d'entrée générique pour l'authentification avec différents action.

Actions disponibles

Connexion enseignant :

{
  "action": "teacher_login",
  "email": "teacher@school.com",
  "password": "password123"
}

Inscription enseignant :

{
  "action": "teacher_register",
  "username": "prof_math",
  "email": "teacher@school.com",
  "password": "password123",
  "name": "Dupont",
  "prenom": "Jean"
}

Connexion élève :

{
  "action": "login",
  "email": "student@school.com",
  "password": "password123"
}

Réponse de succès :

{
  "success": true,
  "user": {
    "id": "uuid",
    "username": "prof_math",
    "email": "teacher@school.com",
    "role": "TEACHER",
    "avatarEmoji": "🎓"
  },
  "token": "jwt_token_here"
}

POST /api/v1/auth/logout

Déconnexion de l'utilisateur.

Réponse :

{
  "success": true,
  "message": "Logged out successfully"
}

POST /api/v1/auth/upgrade

Mise à niveau d'un compte invité vers un compte permanent.

{
  "cookieId": "guest_session_id",
  "email": "student@school.com",
  "password": "new_password",
  "targetRole": "STUDENT"
}

POST /api/v1/auth/reset-password

Demande de réinitialisation de mot de passe.

{
  "email": "user@domain.com"
}

POST /api/v1/auth/reset-password/confirm

Confirmation de réinitialisation avec nouveau mot de passe.

{
  "token": "reset_token_from_email",
  "newPassword": "new_secure_password"
}

POST /api/v1/auth/send-email-verification

Envoi d'un email de vérification.

{
  "email": "user@domain.com"
}

POST /api/v1/auth/verify-email

Vérification de l'email avec token.

{
  "token": "verification_token_from_email"
}

POST /api/v1/auth/resend-email-verification

Renvoi de l'email de vérification.

{
  "email": "user@domain.com"
}

GET /api/v1/auth/status

Vérification du statut d'authentification.

Réponse :

{
  "authenticated": true,
  "user": {
    "id": "uuid",
    "username": "prof_math",
    "role": "TEACHER"
  }
}

POST /api/v1/auth/register

Création d'un nouveau compte utilisateur.

Authentification : Non requise

{
  "username": "eleve123",
  "email": "student@school.com",
  "password": "password123",
  "role": "STUDENT",
  "avatar": "🐼"
}

POST /api/v1/auth/login

Connexion utilisateur.

Authentification : Non requise

{
  "email": "student@school.com",
  "password": "password123"
}

PUT /api/v1/auth/profile

Mise à jour du profil utilisateur.

Authentification : Requise

{
  "username": "nouveau_nom",
  "avatar": "🎭"
}

POST /api/v1/auth/upgrade-to-teacher

Changement de role entre élève et enseignant.

Authentification : Requise (élève ou enseignant)

{
  "targetRole": "TEACHER"
}

Gestion des parties

POST /api/v1/games

Création d'une nouvelle instance de jeu.

Authentification : Optionnelle (enseignant ou élève)

{
  "name": "Quiz Mathématiques CM2",
  "gameTemplateId": "uuid-du-template",
  "playMode": "quiz",
  "settings": {
    "allowLateJoin": true,
    "showCorrectAnswers": false
  },
  "differedAvailableFrom": "2025-09-20T08:00:00Z",
  "differedAvailableTo": "2025-09-20T18:00:00Z"
}

Modes de jeu :

  • quiz : Quiz en temps réel
  • tournament : Tournoi avec système de score avancé
  • practice : Mode entraînement
  • class : Mode classe

Réponse :

{
  "success": true,
  "gameInstance": {
    "id": "uuid",
    "name": "Quiz Mathématiques CM2",
    "accessCode": "ABC123",
    "status": "waiting",
    "playMode": "quiz",
    "createdAt": "2025-09-19T10:00:00Z"
  }
}

POST /api/v1/games/:accessCode/join

Rejoindre une partie existante (canonical, path-based).

Paramètre de chemin : accessCode (code d'accès de la partie)

Corps de la requête :

{
  "userId": "uuid-player-id"
}

Réponse (exemple) :

{
  "success": true,
  "participant": {
    "id": "uuid",
    "userId": "uuid-player-id",
    "status": "ACTIVE"
  },
  "gameInstance": {
    "accessCode": "ABC123",
    "status": "active"
  }
}
```  "userId": "uuid-utilisateur",
  "username": "Alice",
  "avatar": "🎨"
}

Réponse :

{
  "success": true,
  "participant": {
    "id": "uuid",
    "userId": "uuid-utilisateur",
    "username": "Alice",
    "avatar": "🎨",
    "liveScore": 0,
    "joinedAt": "2025-09-19T10:05:00Z"
  },
  "gameInstance": {
    "id": "uuid",
    "name": "Quiz Mathématiques CM2",
    "status": "active"
  }
}

GET /api/v1/games/:gameId

Récupération des détails d'une partie.

Authentification : Requise (participant ou enseignant)

Réponse :

{
  "id": "uuid",
  "name": "Quiz Mathématiques CM2",
  "accessCode": "ABC123",
  "status": "active",
  "playMode": "quiz",
  "currentQuestionIndex": 2,
  "settings": {},
  "participants": [
    {
      "id": "uuid",
      "username": "Alice",
      "avatar": "🎨",
      "liveScore": 850,
      "status": "ACTIVE"
    }
  ],
  "leaderboard": [
    { "username": "Alice", "score": 850 },
    { "username": "Bob", "score": 720 }
  ]
}

GET /api/v1/games/:gameId/state

Récupération de l'état complet de la partie (avec questions).

Authentification : Requise (enseignant uniquement)

PUT /api/v1/games/:gameId/status

Mise à jour du statut d'une partie.

Authentification : Requise (enseignant)

{
  "status": "active"
}

Statuts possibles :

  • waiting : En attente de participants
  • active : Partie en cours
  • completed : Partie terminée
  • cancelled : Partie annulée

PUT /api/v1/games/:gameId/rename

Renommage d'une partie.

{
  "name": "Nouveau nom du quiz"
}

GET /api/v1/games/active

Liste des parties actives de l'enseignant.

Authentification : Requise (enseignant)

Réponse :

{
  "games": [
    {
      "id": "uuid",
      "name": "Quiz Mathématiques",
      "accessCode": "ABC123",
      "participantCount": 15,
      "status": "active",
      "createdAt": "2025-09-19T10:00:00Z"
    }
  ]
}

Contrôle des parties (Enseignant)

Les endpoints de contrôle des parties sont basés sur le accessCode de la partie (paramètre de chemin). Toutes les opérations ci-dessous nécessitent une authentification professeur (teacher).

GET /api/v1/game-control/:accessCode

Récupère l'état complet de contrôle pour une partie donnée (state utilisé par le dashboard enseignant).

Authentification : Requise (enseignant)

Exemple : GET /api/v1/game-control/ABC123

Réponse (extrait) :

{ "gameState": { /* état complet affiché au professeur */ } }

POST /api/v1/game-control/:accessCode/question

Sélectionne / fixe la question courante (index de la liste de questions du quiz).

Authentification : Requise (enseignant)

Corps de la requête :

{ "questionIndex": 0 }

Réponse : nouvel état de la partie / données de la question diffusée aux élèves.

POST /api/v1/game-control/:accessCode/end-question

Met fin à la question courante et déclenche le traitement des réponses (scoring, leaderboard).

Authentification : Requise (enseignant)

Corps : aucun (POST sans body)

POST /api/v1/game-control/:accessCode/end-game

Clôt la partie et notifie les participants.

Authentification : Requise (enseignant)

Corps : aucun (POST sans body)

Note : la documentation précédente listait des endpoints body-based (/game-control/start, /next-question, /end) acceptant { gameId }. Les endpoints canoniques actuellement utilisés par le backend et le frontend sont basés sur :accessCode comme illustré ci-dessus.

Modèles de jeu

GET /api/v1/game-templates

Liste des modèles de jeu disponibles.

Authentification : Requise (enseignant)

Paramètres de requête :

  • gradeLevel : Filtre historique encore accepté pendant la transition vers gradeLevels
  • discipline : Discipline (mathématiques, français, etc.)
  • themes : Thèmes spécifiques

Réponse :

{
  "templates": [
    {
      "id": "uuid",
      "name": "Mathématiques - Géométrie",
      "gradeLevels": ["CM2"],
      "discipline": "mathématiques",
      "themes": ["géométrie", "aires"],
      "description": "Quiz sur les figures géométriques",
      "questionCount": 15,
      "createdAt": "2025-09-01T09:00:00Z"
    }
  ]
}

POST /api/v1/game-templates

Création d'un nouveau modèle de jeu.

Authentification : Requise (enseignant)

{
  "name": "Mon Quiz Personnalisé",
  "gradeLevels": ["CM1"],
  "discipline": "mathématiques",
  "themes": ["addition", "soustraction"],
  "description": "Quiz personnalisé pour CM1",
  "questionIds": ["uuid-q1", "uuid-q2", "uuid-q3"]
}

GET /api/v1/game-templates/:templateId

Détails d'un modèle spécifique.

PUT /api/v1/game-templates/:templateId

Modification d'un modèle.

DELETE /api/v1/game-templates/:templateId

Suppression d'un modèle.


Quiz Templates (teacher-only endpoints)

Les opérations CRUD et de gestion des questions pour les templates de quiz.

  • POST /api/v1/quiz-templates

    Création d'un nouveau quiz template.

    Authentification : Requise (enseignant)

    Corps (exemple) :

    {
      "name": "Mon Quiz Personnalisé",
      "gradeLevels": ["CM1"],
      "discipline": "mathématiques",
      "themes": ["addition", "soustraction"],
      "description": "Quiz personnalisé pour CM1",
      "questionUids": ["uuid-q1","uuid-q2"]
    }
    
  • GET /api/v1/quiz-templates/:id

    Récupération d'un template (option ?includeQuestions=true).

  • GET /api/v1/quiz-templates

    Liste / filtrage des templates (discipline, themes, gradeLevel, page, pageSize). Le filtre gradeLevel reste un paramètre historique ; les payloads documentés utilisent gradeLevels.

  • PUT /api/v1/quiz-templates/:id

    Mise à jour d'un template.

  • DELETE /api/v1/quiz-templates/:id

    Suppression d'un template.

  • POST /api/v1/quiz-templates/:id/questions

    Ajout d'une question au template.

    Corps (exemple) :

    { "questionUid": "uuid-q3", "sequence": 4 }
    
  • DELETE /api/v1/quiz-templates/:id/questions/:questionUid

    Suppression d'une question du template.

  • PUT /api/v1/quiz-templates/:id/questions-sequence

    Mise à jour de l'ordre des questions dans le template.

    Corps (exemple) :

    { "updates": [{ "questionUid": "uuid-q1", "sequence": 0 }, { "questionUid": "uuid-q2", "sequence": 1 }] }
    

Questions

GET /api/v1/questions

Recherche et filtrage de questions.

Authentification : Non requise (questions publiques)

Paramètres de requête :

  • gradeLevel : Filtre historique encore accepté pendant la transition vers gradeLevels
  • discipline : Discipline
  • themes : Liste de thèmes
  • difficulty : Difficulté (1-5)
  • limit : Nombre maximum de résultats
  • offset : Décalage pour pagination

Réponse :

{
  "questions": [
    {
      "uid": "uuid",
      "title": "Calcul mental",
      "text": "Combien font 15 + 27 ?",
      "questionType": "numeric",
      "discipline": "mathématiques",
      "gradeLevels": ["CE2"],
      "difficulty": 2,
      "timeLimit": 30,
      "numericQuestion": {
        "correctAnswer": 42,
        "tolerance": 0,
        "unit": null
      }
    }
  ],
  "total": 150,
  "limit": 20,
  "offset": 0
}

GET /api/v1/questions/:uid

Détails d'une question spécifique.

Remarque : Le backend identifie les questions par leur uid (UUID). Utilisez :uid dans les exemples d'URL.

POST /api/v1/questions

Création d'une nouvelle question.

Authentification : Requise (enseignant)

Question à choix multiples :

{
  "title": "Capitale de la France",
  "text": "Quelle est la capitale de la France ?",
  "questionType": "multiple-choice",
  "discipline": "géographie",
  "gradeLevels": ["CM1"],
  "difficulty": 1,
  "timeLimit": 20,
  "multipleChoiceQuestion": {
    "answerOptions": ["Paris", "Lyon", "Marseille", "Toulouse"],
    "correctAnswers": [true, false, false, false]
  }
}

Question numérique :

{
  "title": "Calcul",
  "text": "Combien font 12 × 8 ?",
  "questionType": "numeric",
  "discipline": "mathématiques",
  "gradeLevels": ["CE2"],
  "difficulty": 2,
  "timeLimit": 30,
  "numericQuestion": {
    "correctAnswer": 96,
    "tolerance": 0,
    "unit": null
  }
}

GET /api/v1/questions/personal

Récupération des questions personnelles de l'enseignant.

Authentification : Requise (enseignant)

Réponse :

{
  "questions": [
    {
      "uid": "uuid",
      "title": "Ma question personnalisée",
      "text": "Quelle est la réponse ?",
      "questionType": "multiple-choice",
      "discipline": "Personnel",
      "gradeLevels": ["CM1"],
      "difficulty": 3,
      "timeLimit": 45,
      "multipleChoiceQuestion": {
        "answerOptions": ["A", "B", "C", "D"],
        "correctAnswers": [true, false, false, false]
      },
      "createdAt": "2025-12-19T10:00:00Z",
      "updatedAt": "2025-12-19T10:00:00Z"
    }
  ]
}

POST /api/v1/questions/bulk-upsert

Import en masse de questions personnelles.

Authentification : Requise (enseignant)

Description : Permet de créer ou mettre à jour plusieurs questions personnelles en une seule requête. Les questions sont automatiquement marquées comme "Personnel" pour la discipline.

Payload :

{
  "questions": [
    {
      "editorUid": "temp-id-1",
      "text": "Quelle est la capitale de la France ?",
      "questionType": "singleChoice",
      "title": "Géographie",
      "answerOptions": ["Paris", "Lyon", "Marseille", "Toulouse"],
      "correctAnswers": [true, false, false, false],
      "difficulty": 2,
      "gradeLevels": ["CM1"],
      "themes": ["géographie", "capitale"],
      "durationMs": 30000
    },
    {
      "uid": "existing-question-uuid",
      "text": "Combien font 15 + 27 ?",
      "questionType": "numeric",
      "correctAnswer": 42,
      "tolerance": 0,
      "difficulty": 1,
      "gradeLevels": ["CE2"]
    }
  ]
}

Réponse :

{
  "success": true,
  "created": 1,
  "updated": 1,
  "mapping": {
    "temp-id-1": "generated-uuid-123"
  }
}

POST /api/v1/questions/math/preview

Prévisualisation enseignant de la validation math backend (matrice d'exemples).

Authentification : Requise (enseignant)

Description :

  • Prend une réponse cible LaTeX et une configuration de validation math.
  • Retourne une liste d'exemples (10 à 15) montrant comment le backend parse et évalue des saisies proches de la réponse attendue.
  • Permet de tester des cas limites (incorrect, format invalide, ambigu, etc.) avant publication d'une question.

Payload :

{
  "targetLatex": "2x",
  "validationConfig": {
    "kind": "EXPRESSION",
    "responseFormat": "SINGLE",
    "valueCheck": { "method": "EXACT" }
  },
  "sampleCount": 12,
  "extraInputs": ["2", "2x+1"]
}

extraInputs est optionnel et permet d'évaluer explicitement une ou plusieurs saisies fournies par l'enseignant (elles sont priorisées dans la réponse).

Réponse :

{
  "targetLatex": "2x",
  "validationConfig": {
    "kind": "EXPRESSION",
    "responseFormat": "SINGLE",
    "valueCheck": { "method": "EXACT" },
    "constraints": []
  },
  "examples": [
    {
      "inputLatex": "2x",
      "status": "CORRECT",
      "parsedRawLatex": "2x",
      "parsedCanonicalLatex": "2x",
      "parseOkRaw": true,
      "parseOkCanonical": true,
      "valueMethod": "EXACT"
    },
    {
      "inputLatex": "2",
      "status": "INCORRECT",
      "parsedRawLatex": "2",
      "parsedCanonicalLatex": "2",
      "parseOkRaw": true,
      "parseOkCanonical": true,
      "valueMethod": "EXACT"
    },
    {
      "inputLatex": "notlatex",
      "status": "INVALID_INPUT",
      "parseOkRaw": false,
      "parseOkCanonical": false,
      "valueMethod": "EXACT",
      "invalidReason": "INVALID_LATEX"
    }
  ]
}

DELETE /api/v1/questions/personal

Suppression de toutes les questions personnelles de l'enseignant.

Authentification : Requise (enseignant)

Réponse :

{
  "success": true,
  "deleted": 15
}

Images partagées

GET /api/v1/shared-images/filters

Retourne les valeurs disponibles pour filtrer la bibliothèque d'images (disciplines, niveaux, types).

Authentification : Non requise

Réponse (exemple) :

{
  "disciplines": ["mathématiques","français"],
  "levels": ["CP","CE1","CM2"],
  "types": ["illustration","photo","diagramme"]
}

GET /api/v1/shared-images

Liste des images partagées avec pagination et filtres.

Paramètres de requête : page, limit, search, discipline, level, type

Réponse (exemple) :

{
  "images": [ { "id": "uuid", "title": "Triangle", "variants": [/* ... */] } ],
  "total": 120,
  "page": 1,
  "totalPages": 3,
  "hasMore": true
}

Utilisateurs

GET /api/v1/users/profile

Récupération du profil utilisateur.

Authentification : Requise

PUT /api/v1/users/profile

Mise à jour du profil utilisateur.

{
  "username": "nouveau_nom",
  "avatar": "🎭"
}

GET /api/v1/users/my-tournaments

Liste des tournois de l'utilisateur.

Authentification : Requise

Validation d'accès aux pages

POST /api/v1/validatePageAccess

Validation de l'accès à une page spécifique.

Authentification : Requise (enseignant)

{
  "pageType": "dashboard",
  "accessCode": "ABC123"
}

Types de page :

  • dashboard : Tableau de bord de projection
  • projection : Interface de projection
  • practice : Mode entraînement
  • tournament : Interface tournoi

Réponse :

{
  "valid": true,
  "gameInstance": {
    "id": "uuid",
    "name": "Quiz Mathématiques",
    "status": "active"
  }
}

Gestion des enseignants

GET /api/v1/teachers/dashboard

Données du tableau de bord enseignant.

Authentification : Requise (enseignant)

GET /api/v1/teachers/students

Liste des élèves de l'enseignant.

Gestion des élèves

GET /api/v1/student/dashboard

Données du tableau de bord élève.

Authentification : Requise (élève)

GET /api/v1/student/games

Liste des parties de l'élève.

Sessions d'entraînement

GET /api/v1/practice/sessions

Liste des sessions d'entraînement.

POST /api/v1/practice/sessions

Création d'une session d'entraînement.

{
  "name": "Entraînement Mathématiques",
  "gradeLevel": "CM1",
  "discipline": "mathématiques",
  "themes": ["addition", "soustraction"],
  "questionCount": 10
}

Monitoring et Santé

GET /api/v1/health

Vérification basique de la santé du système.

Authentification : Non requise

Réponse :

{
  "status": "healthy",
  "timestamp": "2025-12-19T12:00:00.000Z",
  "uptime": 3600.5,
  "environment": "development"
}

GET /api/v1/health/resources

Informations sur l'utilisation des ressources système (mémoire, CPU).

Authentification : Non requise

Réponse :

{
  "timestamp": 1734609600000,
  "memoryUsageMB": 45.67,
  "heapUsedMB": 32.45,
  "heapTotalMB": 40.12,
  "externalMB": 1.23,
  "cpuUsagePercent": "2.34",
  "uptime": 3600.5,
  "raw": {
    "rss": 47841280,
    "heapUsed": 33947648,
    "heapTotal": 42065920,
    "external": 1289744,
    "arrayBuffers": 1289744,
    "cpuUser": 2345678,
    "cpuSystem": 1234567
  }
}

GET /api/v1/health/detailed

Informations détaillées sur le système et le processus Node.js.

Authentification : Non requise

Réponse :

{
  "timestamp": 1734609600000,
  "uptime": 3600.5,
  "memory": {
    "rss": 47841280,
    "heapUsed": 33947648,
    "heapTotal": 42065920,
    "external": 1289744,
    "arrayBuffers": 1289744,
    "rssMB": "45.67",
    "heapUsedMB": "32.45",
    "heapTotalMB": "40.12"
  },
  "cpu": {
    "user": 2345678,
    "system": 1234567,
    "userSeconds": "2.35",
    "systemSeconds": "1.23"
  },
  "process": {
    "pid": 12345,
    "version": "v18.17.0",
    "platform": "linux",
    "arch": "x64",
    "nodeVersion": "18.17.0",
    "v8Version": "10.2.154.26"
  },
  "handles": {
    "active": 12,
    "requests": 3
  }
}

Métriques

GET /api/v1/metrics

Snapshot actuel des métriques système.

Authentification : Non requise

Note : Activé uniquement si ENABLE_METRICS=true

Réponse :

{
  "enabled": true,
  "timestamp": 1734609600000,
  "currentMinute": 1734609600,
  "alerts": [],
  "metrics": {
    "games": {
      "active": 5,
      "totalCreated": 1234,
      "avgDuration": 1800
    },
    "users": {
      "online": 45,
      "total": 1234
    }
  }
}

GET /api/v1/metrics/history

Historique des métriques sur les N dernières minutes.

Authentification : Non requise

Paramètres :

  • minutes (optionnel, défaut: 10, max: 60)

Réponse :

{
  "enabled": true,
  "minutes": 10,
  "buckets": [
    {
      "timestamp": 1734609540000,
      "games": { "active": 3 },
      "users": { "online": 42 }
    }
  ],
  "current": 1734609600,
  "timestamp": 1734609600000
}

Mes Tournois

GET /api/v1/my-tournaments

Liste des parties créées et rejointes par l'utilisateur.

Authentification : Optionnelle

Paramètres :

  • mode (optionnel: 'tournament', 'quiz', 'practice', défaut: 'tournament')
  • cookie_id (optionnel, pour utilisateurs invités)

Réponse :

{
  "pending": [],
  "active": [
    {
      "id": "uuid",
      "name": "Quiz Mathématiques",
      "accessCode": "ABC123",
      "status": "active",
      "participantCount": 15,
      "createdAt": "2025-12-19T10:00:00Z",
      "role": "teacher"
    }
  ],
  "ended": []
}

Joueurs

GET /api/v1/players/cookie/:cookieId

Récupération d'un utilisateur par son cookie ID.

Authentification : Non requise

Réponse :

{
  "user": {
    "id": "uuid",
    "username": "eleve123",
    "email": "student@school.com",
    "role": "STUDENT",
    "avatarEmoji": "🐼"
  }
}

Taxonomie

GET /api/v1/questions/taxonomy

Récupération de toutes les métadonnées de taxonomie (liste des niveaux, disciplines, thèmes).

Authentification : Non requise

Authentification : Non requise

Réponse :

{
  "gradeLevels": ["CP", "CE1", "CE2"],
  "metadata": {
    "CP": {
      "disciplines": ["mathématiques", "français"],
      "themes": {
        "mathématiques": ["nombres", "addition"]
      }
    }
  }
}

GET /api/v1/questions/taxonomy/:level

Récupération des métadonnées de taxonomie pour un niveau spécifique (ex: CP, CE1).

Authentification : Non requise

Authentification : Non requise

Paramètres :

  • level: Niveau scolaire (ex: "CP", "CE1")

Réponse :

{
  "gradeLevel": "CP",
  "content": {
    "disciplines": ["mathématiques", "français"],
    "themes": {
      "mathématiques": ["nombres", "addition"]
    }
  }
}

Debug (Développement)

GET /api/v1/debug/sockets/:accessCode

Inspection des rooms Socket.IO pour une partie (développement uniquement).

Authentification : Non requise

Note : Indisponible en production

Réponse :

{
  "accessCode": "ABC123",
  "rooms": {
    "game": { "name": "game_ABC123", "socketIds": ["socket1", "socket2"] },
    "lobby": { "name": "lobby_ABC123", "socketIds": ["socket3"] },
    "dashboard": { "name": "dashboard_ABC123", "socketIds": ["socket4"] },
    "projection": { "name": "projection_ABC123", "socketIds": [] }
  },
  "allRooms": ["game_ABC123", "lobby_ABC123"],
  "ts": 1734609600000
}

POST /api/v1/debug/seed-question

Création d'une question déterministe pour les tests E2E (développement uniquement).

Authentification : Non requise

Note : Indisponible en production

{
  "questionType": "multiple-choice",
  "title": "Question de test",
  "text": "Quelle est la réponse ?",
  "answerOptions": ["A", "B", "C", "D"],
  "correctAnswers": [true, false, false, false],
  "durationMs": 30000,
  "discipline": "mathématiques",
  "difficulty": 1,
  "gradeLevel": "CM1",
  "tags": ["test"]
}

Réponse :

{
  "success": true,
  "question": { /* question object */ },
  "uid": "question_uid"
}

Codes d'erreur

Erreurs communes

400 Bad Request :

{
  "error": "Invalid request data",
  "details": "Validation failed for field 'email'"
}

401 Unauthorized :

{
  "error": "Authentication required"
}

403 Forbidden :

{
  "error": "Insufficient permissions"
}

404 Not Found :

{
  "error": "Resource not found"
}

409 Conflict :

{
  "error": "Resource already exists"
}

429 Too Many Requests :

{
  "error": "Rate limit exceeded"
}

500 Internal Server Error :

{
  "error": "Internal server error"
}

Limites et quotas

  • Requêtes par minute : 1000 par IP
  • Taille maximale du payload : 10MB
  • Timeout des requêtes : 30 secondes
  • Taille maximale des fichiers : 5MB (pour les images/avatar)

Webhooks et callbacks

MathQuest ne propose pas actuellement de webhooks externes, mais toutes les opérations importantes sont tracées dans les logs pour audit.

Versionnage de l'API

L'API est versionnée avec /v1/. Les changements non rétrocompatibles feront l'objet d'une nouvelle version majeure.

SDK et bibliothèques

Actuellement, aucun SDK officiel n'est fourni. L'API peut être utilisée directement avec n'importe quelle bibliothèque HTTP (axios, fetch, etc.).

Dernière mise à jour: 23/03/2026 14:30
Contributors: alexisflesch
Prev
Services backend
Next
Configuration et environnement