Skip to content

julien-gournay/CultureQuiz_API

Repository files navigation

CultureQuiz API 🧠

Une API REST pour l'application CultureQuiz, un système de quiz interactif permettant de tester vos connaissances culturelles par catégorie. Cette API complète gère les catégories, questions, réponses, comptes utilisateurs et l'historique des quiz.

Version: 1.0.0
Stack: Node.js + Express + PostgreSQL
Authentification: JWT (JSON Web Tokens)


📋 Table des matières

  1. Installation et démarrage
  2. Configuration
  3. Architecture et endpoints
  4. Guide d'authentification
  5. Intégration avec React
  6. Exemples d'utilisation
  7. Docker
  8. Troubleshooting

🚀 Installation et démarrage

Prérequis

  • Node.js (v14+)
  • PostgreSQL (v13+) ou Docker
  • npm ou yarn

Étapes d'installation

# 1. Cloner le repository
git clone <votre-repo>
cd CultureQuiz_API

# 2. Installer les dépendances
npm install

# 3. Créer un fichier .env
cp .env.example .env

# 4. Configurer les variables (voir section Configuration)

# 5. Initialiser la base de données
# Option A : Avec Docker (recommandé)
docker-compose up -d

# Option B : PostgreSQL local
psql -U postgres -d template1 -a -f db/db.sql

# 6. Démarrer l'API
npm start           # Production
npm run dev         # Développement (avec nodemon)

L'API démarre sur http://localhost:3333


⚙️ Configuration

Variables d'environnement

Créez un fichier .env à la racine du projet :

# Serveur
PORT=3333
NODE_ENV=development

# Base de données PostgreSQL
DB_HOST=localhost
DB_PORT=5432
DB_USER=culturequiz_user
DB_PASSWORD=culturequiz_password
DB_NAME=culturequiz

# Authentification JWT
JWT_SECRET=votre_cle_secrete_tres_longue_et_aleatoire
JWT_EXPIRATION=24h

# CORS
CORS_ORIGIN=http://localhost:3000

Variables Docker (docker-compose.yml)

Le service PostgreSQL inclus utilise :

  • Utilisateur: culturequiz_user
  • Mot de passe: culturequiz_password
  • Base de données: culturequiz
  • Port: 5432

📐 Architecture et endpoints

Structure de base

/api
├── /categories      (Catégories de quiz)
├── /questions       (Questions avec différents niveaux)
├── /reponses        (Réponses aux questions)
├── /comptes         (Gestion des utilisateurs)
└── /historique      (Historique des réponses)

🏠 Routes système

Vérifier l'état de l'API

GET /health

Réponse (200):

{
  "status": "OK",
  "message": "CultureQuiz API is running"
}

Accueil de l'API

GET /

Réponse (200):

{
  "message": "Bienvenue sur CultureQuiz API",
  "version": "1.0.0",
  "endpoints": {
    "health": "/health",
    "api": "/api",
    "categories": "/api/categories",
    "questions": "/api/questions",
    "reponses": "/api/reponses",
    "comptes": "/api/comptes",
    "historique": "/api/historique"
  }
}

📚 Endpoints détaillés

1. Catégories (/api/categories)

Gère les catégories de quiz disponibles.

Récupérer toutes les catégories

GET /api/categories

Réponse (200):

{
  "success": true,
  "data": [
    {
      "idCat": 1,
      "libelleCategorie": "Histoire",
      "diminutif": "HIS"
    },
    {
      "idCat": 2,
      "libelleCategorie": "Jeux Vidéo",
      "diminutif": "JV"
    }
  ]
}

Récupérer une catégorie par ID

GET /api/categories/:id

Exemple: GET /api/categories/1

Réponse (200):

{
  "success": true,
  "data": {
    "idCat": 1,
    "libelleCategorie": "Histoire",
    "diminutif": "HIS"
  }
}

Créer une catégorie 🔒

POST /api/categories
Authorization: Bearer <token_jwt>
Content-Type: application/json

{
  "libelleCategorie": "Littérature",
  "diminutif": "LIT"
}

Réponse (201):

{
  "success": true,
  "data": {
    "idCat": 3,
    "libelleCategorie": "Littérature",
    "diminutif": "LIT"
  }
}

Modifier une catégorie 🔒

PUT /api/categories/:id
Authorization: Bearer <token_jwt>
Content-Type: application/json

{
  "libelleCategorie": "Littérature française",
  "diminutif": "LFR"
}

Supprimer une catégorie 🔒

DELETE /api/categories/:id
Authorization: Bearer <token_jwt>

2. Questions (/api/questions)

Gère les questions du quiz avec différents niveaux de difficulté.

Récupérer toutes les questions

GET /api/questions

Réponse (200):

{
  "success": true,
  "data": [
    {
      "idQue": 1,
      "idCat": 1,
      "libelleQue": "Qui a été le premier empereur romain?",
      "niveau": 1,
      "libelleCategorie": "Histoire"
    }
  ]
}

Récupérer une question par ID

GET /api/questions/:id

Générer un quiz (10 questions aléatoires)

GET /api/questions/quiz/:idCat

Exemple: GET /api/questions/quiz/1

Réponse (200):

{
  "success": true,
  "data": {
    "questions": [
      {
        "question": {
          "idQue": 1,
          "idCat": 1,
          "libelleQue": "Qui a été le premier empereur romain?",
          "niveau": 1,
          "libelleCategorie": "Histoire"
        },
        "reponses": [
          {
            "idRep": 1,
            "libelleRep": "Auguste",
            "isBonneRep": true
          },
          {
            "idRep": 2,
            "libelleRep": "César",
            "isBonneRep": false
          },
          {
            "idRep": 3,
            "libelleRep": "Néron",
            "isBonneRep": false
          },
          {
            "idRep": 4,
            "libelleRep": "Julien",
            "isBonneRep": false
          }
        ]
      }
    ],
    "tempsLimite": 300,
    "tempsParQuestion": 30,
    "heureDebut": "2024-01-20T14:30:00Z"
  }
}

Récupérer une question aléatoire

GET /api/questions/random

Récupérer une question aléatoire par catégorie

GET /api/questions/random/:idCat

Exemple: GET /api/questions/random/1

Récupérer une question aléatoire par catégorie et niveau

GET /api/questions/random/:idCat/:niveau

Exemple: GET /api/questions/random/1/2 (Catégorie 1, Niveau 2)

Vérifier si une réponse est correcte

POST /api/questions/check-answer
Content-Type: application/json

{
  "idRep": 12
}

Réponses possibles (200):

  • Réponse correcte :
{
  "success": true,
  "data": {
    "isCorrect": true,
    "selectedAnswer": {
      "idRep": 12,
      "libelleRep": "Auguste"
    }
  }
}
  • Réponse incorrecte (avec la bonne réponse retournée) :
{
  "success": true,
  "data": {
    "isCorrect": false,
    "selectedAnswer": {
      "idRep": 13,
      "libelleRep": "César"
    },
    "bonneReponse": {
      "idRep": 12,
      "libelleRep": "Auguste"
    }
  }
}

Erreurs possibles :

  • 400 si idRep est manquant.
  • 404 si aucune réponse ne correspond à idRep.

Créer une question 🔒

POST /api/questions
Authorization: Bearer <token_jwt>
Content-Type: application/json

{
  "idCat": 1,
  "libelleQue": "En quelle année la Révolution française a-t-elle commencé?",
  "niveau": 2
}

Réponse (201):

{
  "success": true,
  "data": {
    "idQue": 42,
    "idCat": 1,
    "libelleQue": "En quelle année la Révolution française a-t-elle commencé?",
    "niveau": 2
  }
}

Modifier une question 🔒

PUT /api/questions/:id
Authorization: Bearer <token_jwt>
Content-Type: application/json

{
  "libelleQue": "En quelle année exactement la Révolution française a-t-elle commencé?",
  "niveau": 3
}

Supprimer une question 🔒

DELETE /api/questions/:id
Authorization: Bearer <token_jwt>

3. Réponses (/api/reponses)

Gère les réponses aux questions (une question a 4 réponses possibles, une seule correcte).

Récupérer toutes les réponses

GET /api/reponses

Récupérer les réponses d'une question

GET /api/reponses/question/:idQue

Exemple: GET /api/reponses/question/1

Réponse (200):

{
  "success": true,
  "count": 4,
  "data": [
    {
      "idRep": 1,
      "idQue": 1,
      "libelleRep": "Auguste",
      "isBonneRep": true
    },
    {
      "idRep": 2,
      "idQue": 1,
      "libelleRep": "César",
      "isBonneRep": false
    },
    {
      "idRep": 3,
      "idQue": 1,
      "libelleRep": "Néron",
      "isBonneRep": false
    },
    {
      "idRep": 4,
      "idQue": 1,
      "libelleRep": "Julien",
      "isBonneRep": false
    }
  ]
}

Créer une réponse 🔒

POST /api/reponses
Authorization: Bearer <token_jwt>
Content-Type: application/json

{
  "idQue": 1,
  "libelleRep": "Auguste",
  "isBonneRep": true
}

Modifier une réponse 🔒

PUT /api/reponses/:id
Authorization: Bearer <token_jwt>
Content-Type: application/json

{
  "libelleRep": "Octave Auguste",
  "isBonneRep": true
}

Supprimer une réponse 🔒

DELETE /api/reponses/:id
Authorization: Bearer <token_jwt>

4. Comptes (/api/comptes)

Gère la création de comptes utilisateurs et l'authentification.

S'inscrire

POST /api/comptes/register
Content-Type: application/json

{
  "pseudo": "john_doe",
  "password": "SecurePassword123!"
}

Réponse (201):

{
  "success": true,
  "data": {
    "idCompte": 1,
    "pseudo": "john_doe",
    "dateCreation": "2024-01-20T10:30:00Z"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Se connecter

POST /api/comptes/login
Content-Type: application/json

{
  "pseudo": "john_doe",
  "password": "SecurePassword123!"
}

Réponse (200):

{
  "success": true,
  "data": {
    "idCompte": 1,
    "pseudo": "john_doe",
    "dateCreation": "2024-01-20T10:30:00Z",
    "dateModif": "2024-01-20T10:30:00Z"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Récupérer tous les comptes 🔒

GET /api/comptes
Authorization: Bearer <token_jwt>

Récupérer un compte par ID 🔒

GET /api/comptes/:id
Authorization: Bearer <token_jwt>

Modifier un compte 🔒

PUT /api/comptes/:id
Authorization: Bearer <token_jwt>
Content-Type: application/json

{
  "pseudo": "john_doe_updated",
  "password": "NewPassword123!"
}

Supprimer un compte 🔒

DELETE /api/comptes/:id
Authorization: Bearer <token_jwt>

5. Historique (/api/historique)

Enregistre l'historique des résultats et des statistiques par partie et catégorie.

Récupérer tout l'historique 🔒

GET /api/historique
Authorization: Bearer <token_jwt>

Réponse (200):

{
  "success": true,
  "count": 5,
  "data": [
    {
      "idHistorique": 1,
      "idCompte": 1,
      "idCat": 1,
      "score": 8,
      "tempsJeu": 420,
      "datePartie": "2024-01-20T14:30:00Z",
      "libelleCategorie": "Histoire",
      "pseudo": "john_doe"
    }
  ]
}

Récupérer l'historique d'un compte 🔒

GET /api/historique/compte/:idCompte
Authorization: Bearer <token_jwt>

Exemple: GET /api/historique/compte/1

Réponse (200):

{
  "success": true,
  "count": 5,
  "totalQuestionsRepondues": 50,
  "data": [
    {
      "idhistorique": 1,
      "idcompte": 1,
      "idcat": 1,
      "score": 8,
      "nbquestions": 10,
      "tempsjeu": 120,
      "datepartie": "2024-01-20T14:07:00Z",
      "libellecategorie": "Histoire",
      "pseudo": "john_doe"
    }
  ]
}

Récupérer l'historique d'un compte par catégorie 🔒

GET /api/historique/compte/:idCompte/categorie/:idCat
Authorization: Bearer <token_jwt>

Exemple: GET /api/historique/compte/1/categorie/1

Réponse (200):

{
  "success": true,
  "count": 3,
  "totalQuestionsRepondues": 30,
  "data": [
    {
      "idhistorique": 1,
      "idcompte": 1,
      "idcat": 1,
      "score": 9,
      "nbquestions": 10,
      "tempsjeu": 120,
      "datepartie": "2024-01-20T14:07:00Z",
      "libellecategorie": "Histoire",
      "pseudo": "john_doe"
    }
  ]
}

Récupérer les statistiques d'un compte 🔒

GET /api/historique/stats/:idCompte
Authorization: Bearer <token_jwt>

Réponse (200):

{
  "success": true,
  "data": {
    "parCategorie": [
      {
        "idCat": 1,
        "libelleCategorie": "Histoire",
        "nombreParties": 10,
        "totalQuestionsRepondues": 100,
        "scoreMoyen": 7.5,
        "meilleurScore": 10,
        "pireScore": 4,
        "tempsMoyen": 450,
        "meilleurTemps": 300,
        "tempsMaximum": 600
      }
    ],
    "statistiquesGlobales": {
      "totalParties": 25,
      "totalQuestionsRepondues": 250,
      "scoreMoyenGlobal": 7.2,
      "tempsMoyenGlobal": 420,
      "tempsTotal": 10500
    }
  }
}

Enregistrer une partie 🔒

POST /api/historique
Authorization: Bearer <token_jwt>
Content-Type: application/json

{
  "idCompte": 1,
  "idCat": 1,
  "score": 8,
  "nbQuestions": 10,
  "heureDebut": "2024-01-20T14:00:00Z",
  "heureFin": "2024-01-20T14:07:00Z"
}

Réponse (201):

{
  "success": true,
  "data": {
    "idHistorique": 42,
    "idCompte": 1,
    "idCat": 1,
    "score": 8,
    "nbQuestions": 10,
    "tempsJeu": 420,
    "datePartie": "2024-01-20T14:07:00Z"
  }
}

🔐 Guide d'authentification

Système JWT

L'API utilise JWT (JSON Web Tokens) pour sécuriser certains endpoints.

1. Obtenir un token

Étape 1: S'inscrire

curl -X POST http://localhost:3333/api/comptes/register \
  -H "Content-Type: application/json" \
  -d '{
    "pseudo": "john_doe",
    "password": "SecurePassword123!"
  }'

Réponse:

{
  "success": true,
  "data": {
    "idCompte": 1,
    "pseudo": "john_doe",
    "dateCreation": "2024-01-20T10:30:00Z"
  },
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZENvbXB0ZSI6MSwicHNldWRvIjoiam9obl9kb2UiLCJpYXQiOjE3MDU3NTMwMDB9.xyz..."
}

Étape 2: Ou se connecter

curl -X POST http://localhost:3333/api/comptes/login \
  -H "Content-Type: application/json" \
  -d '{
    "pseudo": "john_doe",
    "password": "SecurePassword123!"
  }'

2. Utiliser le token

Pour accéder à un endpoint protégé, ajoutez le header Authorization :

curl -X GET http://localhost:3333/api/historique \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

3. Format du token

Le token JWT decode se présente comme :

{
  "idCompte": 1,
  "pseudo": "john_doe",
  "iat": 1705753000  // issued at timestamp
}

4. Durée de validité

  • Par défaut: 24 heures
  • Configurable via JWT_EXPIRATION en .env

5. Erreurs d'authentification

Code Message Signification
401 Token d'authentification manquant Header Authorization absent
401 Token invalide Token corrompu ou incorrect
401 Token expiré Token expiré (au-delà de 24h)
403 Accès refusé Credentials invalides

⚛️ Intégration avec React

1. Installation des dépendances

npm install axios react-router-dom

2. Configuration de l'API

Créez un fichier src/api/config.js :

import axios from 'axios';

const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:3333';

// Instance axios
const api = axios.create({
  baseURL: API_URL,
  headers: {
    'Content-Type': 'application/json',
  },
});

// Intercepteur pour ajouter le token JWT
api.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// Intercepteur pour gérer les erreurs
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Token expiré ou invalide
      localStorage.removeItem('token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export default api;

3. Service d'authentification

Créez src/api/authService.js :

import api from './config';

const authService = {
  register: async (pseudo, password) => {
    const response = await api.post('/api/comptes/register', {
      pseudo,
      password,
    });
    if (response.data.token) {
      localStorage.setItem('token', response.data.token);
      localStorage.setItem('userId', response.data.data.idCompte);
      localStorage.setItem('pseudo', response.data.data.pseudo);
    }
    return response.data.data;
  },

  login: async (pseudo, password) => {
    const response = await api.post('/api/comptes/login', {
      pseudo,
      password,
    });
    if (response.data.token) {
      localStorage.setItem('token', response.data.token);
      localStorage.setItem('userId', response.data.data.idCompte);
      localStorage.setItem('pseudo', response.data.data.pseudo);
    }
    return response.data.data;
  },

  logout: () => {
    localStorage.removeItem('token');
    localStorage.removeItem('userId');
    localStorage.removeItem('pseudo');
  },

  isAuthenticated: () => {
    return !!localStorage.getItem('token');
  },

  getCurrentUser: () => {
    return {
      id: localStorage.getItem('userId'),
      pseudo: localStorage.getItem('pseudo'),
    };
  },
};

export default authService;

4. Services pour les endpoints

Créez src/api/quizService.js :

import api from './config';

const quizService = {
  // Categories
  getCategories: async () => {
    const response = await api.get('/api/categories');
    return response.data.data;
  },

  getCategoryById: async (id) => {
    const response = await api.get(`/api/categories/${id}`);
    return response.data.data;
  },

  // Questions
  getQuiz: async (categoryId) => {
    const response = await api.get(`/api/questions/quiz/${categoryId}`);
    return response.data.data;
  },

  getRandomQuestion: async (categoryId = null, level = null) => {
    let url = '/api/questions/random';
    if (categoryId) {
      url += `/${categoryId}`;
      if (level) {
        url += `/${level}`;
      }
    }
    const response = await api.get(url);
    return response.data.data;
  },

  // Historique
  getHistory: async (userId) => {
    const response = await api.get(`/api/historique/compte/${userId}`);
    return response.data.data;
  },

  getStats: async (userId) => {
    const response = await api.get(`/api/historique/stats/${userId}`);
    return response.data.data;
  },

  recordGame: async (userId, categoryId, score, nbQuestions, heureDebut, heureFin) => {
    const response = await api.post('/api/historique', {
      idCompte: userId,
      idCat: categoryId,
      score: score,
      nbQuestions: nbQuestions,
      heureDebut: heureDebut,
      heureFin: heureFin,
    });
    return response.data.data;
  },
};

export default quizService;

5. Hook personnalisé pour l'authentification

Créez src/hooks/useAuth.js :

import { useState, useContext, createContext } from 'react';
import authService from '../api/authService';

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(authService.getCurrentUser());
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  const login = async (pseudo, password) => {
    setLoading(true);
    setError(null);
    try {
      const userData = await authService.login(pseudo, password);
      setUser(userData);
      return userData;
    } catch (err) {
      setError(err.response?.data?.message || 'Erreur de connexion');
      throw err;
    } finally {
      setLoading(false);
    }
  };

  const register = async (pseudo, password) => {
    setLoading(true);
    setError(null);
    try {
      const userData = await authService.register(pseudo, password);
      setUser(userData);
      return userData;
    } catch (err) {
      setError(err.response?.data?.message || 'Erreur d\'inscription');
      throw err;
    } finally {
      setLoading(false);
    }
  };

  const logout = () => {
    authService.logout();
    setUser(null);
  };

  return (
    <AuthContext.Provider
      value={{ user, loading, error, login, register, logout }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth doit être utilisé avec AuthProvider');
  }
  return context;
};

6. Composant React d'exemple - Quiz

import { useState, useEffect } from 'react';
import quizService from '../api/quizService';
import { useAuth } from '../hooks/useAuth';

export default function Quiz({ categoryId }) {
  const { user } = useAuth();
  const [questions, setQuestions] = useState([]);
  const [currentIndex, setCurrentIndex] = useState(0);
  const [score, setScore] = useState(0);
  const [loading, setLoading] = useState(true);
  const [startTime] = useState(new Date());

  useEffect(() => {
    const loadQuiz = async () => {
      try {
        const data = await quizService.getQuiz(categoryId);
        setQuestions(data);
        setLoading(false);
      } catch (error) {
        console.error('Erreur lors du chargement du quiz:', error);
        setLoading(false);
      }
    };

    loadQuiz();
  }, [categoryId]);

  const handleAnswerClick = async (answerId, isCorrect) => {
    // Mettre à jour le score
    if (isCorrect) {
      setScore(score + 1);
    }

    // Passer à la question suivante
    if (currentIndex < questions.length - 1) {
      setCurrentIndex(currentIndex + 1);
    } else {
      // Quiz terminé - enregistrer la partie
      const finalScore = score + (isCorrect ? 1 : 0);
      if (user) {
        await quizService.recordGame(
          user.id,
          categoryId,
          finalScore,
          questions.length,
          startTime.toISOString(),
          new Date().toISOString()
        );
      }
      alert(`Quiz terminé! Score: ${finalScore}/${questions.length}`);
    }
  };

  if (loading) return <div>Chargement du quiz...</div>;
  if (questions.length === 0) return <div>Aucune question</div>;

  const currentQuestion = questions[currentIndex];

  return (
    <div className="quiz-container">
      <h2>{currentQuestion.question}</h2>
      <p>Question {currentIndex + 1}/{questions.length}</p>
      <p>Score: {score}</p>

      <div className="answers">
        {currentQuestion.reponses.map((answer) => (
          <button
            key={answer.idRep}
            onClick={() => handleAnswerClick(answer.idRep, answer.isBonneRep)}
            className="answer-btn"
          >
            {answer.libelleRep}
          </button>
        ))}
      </div>
    </div>
  );
}

7. Composant d'authentification

import { useState } from 'react';
import { useAuth } from '../hooks/useAuth';
import { useNavigate } from 'react-router-dom';

export default function LoginForm() {
  const { login, loading, error } = useAuth();
  const navigate = useNavigate();
  const [pseudo, setPseudo] = useState('');
  const [password, setPassword] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    try {
      await login(pseudo, password);
      navigate('/quiz');
    } catch (err) {
      console.error('Erreur de connexion:', err);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        placeholder="Pseudo"
        value={pseudo}
        onChange={(e) => setPseudo(e.target.value)}
        required
      />
      <input
        type="password"
        placeholder="Mot de passe"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        required
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Connexion...' : 'Se connecter'}
      </button>
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </form>
  );
}

8. Configuration .env React

Créez .env à la racine du projet React :

REACT_APP_API_URL=http://localhost:3333

9. App.js avec routing

import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './hooks/useAuth';
import LoginForm from './components/LoginForm';
import Quiz from './components/Quiz';

function ProtectedRoute({ children }) {
  const { user } = useAuth();
  return user ? children : <Navigate to="/login" />;
}

export default function App() {
  return (
    <BrowserRouter>
      <AuthProvider>
        <Routes>
          <Route path="/login" element={<LoginForm />} />
          <Route
            path="/quiz"
            element={
              <ProtectedRoute>
                <Quiz categoryId={1} />
              </ProtectedRoute>
            }
          />
          <Route path="/" element={<Navigate to="/quiz" />} />
        </Routes>
      </AuthProvider>
    </BrowserRouter>
  );
}

📝 Exemples d'utilisation

Workflow complet en cURL

1. S'inscrire

curl -X POST http://localhost:3333/api/comptes/register \
  -H "Content-Type: application/json" \
  -d '{
    "pseudo": "john_doe",
    "password": "MyPassword123!"
  }'

# Réponse:
# {
#   "success": true,
#   "data": {
#     "id": 1,
#     "pseudo": "john_doe",
#     "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
#   }
# }

# Sauvegarder le token
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

2. Récupérer les catégories

curl -X GET http://localhost:3333/api/categories

3. Générer un quiz

curl -X GET http://localhost:3333/api/questions/quiz/1

4. Enregistrer une partie

curl -X POST http://localhost:3333/api/historique \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "idCompte": 1,
    "idCat": 1,
    "score": 8,
    "nbQuestions": 10,
    "heureDebut": "2024-01-20T14:00:00Z",
    "heureFin": "2024-01-20T14:07:00Z"
  }'

5. Récupérer les statistiques

curl -X GET http://localhost:3333/api/historique/stats/1 \
  -H "Authorization: Bearer $TOKEN"

🐳 Docker

Démarrer avec Docker Compose

# Démarrer les services (PostgreSQL + API)
docker-compose up -d

# Arrêter les services
docker-compose down

# Voir les logs
docker-compose logs -f api

# Reconstruire après des changements de code
docker-compose up -d --build

# Accéder à la DB en CLI
docker exec -it culturequiz_db psql -U culturequiz_user -d culturequiz

Architecture Docker

┌─────────────────────────────────────────┐
│         docker-compose.yml              │
├─────────────────────────────────────────┤
│                                          │
│  ┌──────────────┐   ┌──────────────┐   │
│  │     API      │───│  PostgreSQL  │   │
│  │ :3333        │   │  :5432       │   │
│  └──────────────┘   └──────────────┘   │
│                                          │
│  Network: culturequiz_network           │
└─────────────────────────────────────────┘

Vérifier l'état des services

# Status des conteneurs
docker-compose ps

# Logs du service API
docker-compose logs api

# Logs du service DB
docker-compose logs postgres

Backups automatiques de la base de données

L'API inclut un système de backup automatique hebdomadaire pour PostgreSQL.

Configuration

  • Fréquence: Chaque dimanche à 2h du matin
  • Rétention: 30 jours
  • Format: SQL compressé (.sql.gz)
  • Stockage: Volume Docker backup_data

Commandes utiles

# Forcer un backup manuel
docker exec culturequiz_backup /usr/local/bin/backup.sh

# Voir les logs de backup
docker exec culturequiz_backup cat /var/log/backup.log

# Lister les backups disponibles
docker exec culturequiz_backup ls -lh /backups/

# Restaurer un backup
docker exec culturequiz_backup sh -c "gunzip -c /backups/culturequiz_backup_YYYYMMDD_HHMMSS.sql.gz" | docker exec -i culturequiz_db psql -U culturequiz_user -d culturequiz

# Exporter un backup vers votre machine
docker cp culturequiz_backup:/backups/culturequiz_backup_YYYYMMDD_HHMMSS.sql.gz ./

📖 Documentation complète: Consultez backup/README.md


🛠️ Troubleshooting

Problème: "Connection refused" port 3333

Solution:

# Vérifier que le serveur est lancé
npm run dev

# Ou avec Docker
docker-compose up -d api

Problème: "Erreur de connexion à la base de données"

Vérifier les variables d'environnement:

# Vérifier le fichier .env
cat .env

# Vérifier la connectivité DB
psql -h localhost -U culturequiz_user -d culturequiz

Problème: "Token invalide" (401)

Solutions:

  • Le token a expiré (> 24h) → Se reconnecter
  • Le format du header n'est pas correct → Utiliser Authorization: Bearer <token>
  • La clé JWT_SECRET ne correspond pas → Vérifier .env

Problème: "CORS error" depuis React

Solution dans .env:

CORS_ORIGIN=http://localhost:3000

Puis redémarrer l'API:

npm run dev

Problème: Port déjà utilisé

# Trouver le processus utilisant le port
lsof -i :3333  # macOS/Linux
netstat -ano | findstr :3333  # Windows

# Tuer le processus (Windows)
taskkill /PID <PID> /F

# Ou changer le PORT dans .env
PORT=3334

Problème: "Authentification impossible" sur React

Vérifier:

  1. Le domaine CORS dans le fichier .env de l'API
  2. Le stockage localStorage n'est pas bloqué
  3. L'intercepteur axios utilise bien localStorage.getItem('token')

📚 Structure des données

Modèle de données

┌──────────────┐        ┌──────────────────────┐
│   comptes    │        │  historiqueScore     │
├──────────────┤        ├──────────────────────┤
│ id (PK)      │◄───────│ idHistorique (PK)    │
│ pseudo       │        │ idCompte (FK) ────┐  │
│ password     │        │ idCat (FK)       │  │
│ dateCreation │        │ score            │  │
│ dateModif    │        │ tempsJeu         │  │
└──────────────┘        │ datePartie       │  │
                        └──────────────────┘  │
                              │               │
                    ┌─────────┴───────────────┘
                    │
         ┌──────────▼──────────┐
         │  categories        │
         ├────────────────────┤
         │ idCat (PK)         │
         │ libelleCategorie   │
        │ diminutif          │
         └────────────────────┘
                    │
         ┌──────────▼──────────┐
         │   questions        │
         ├────────────────────┤
         │ idQue (PK)         │
         │ libelleQue         │
         │ niveau             │
         │ idCat (FK) ────────┘
         └────────────────────┘
                    │
         ┌──────────▼──────────┐
         │    reponses        │
         ├────────────────────┤
         │ idRep (PK)         │
         │ libelleRep         │
         │ isBonneRep         │
         │ idQue (FK) ────────┘
         └────────────────────┘

🚀 Déploiement

Prérequis

  • Compte sur une plateforme cloud (Heroku, Railway, AWS, Azure, etc.)
  • Base de données PostgreSQL en cloud
  • Variables d'environnement configurées

Déploiement sur Railway

# 1. Installer Railway CLI
npm install -g @railway/cli

# 2. Authentification
railway login

# 3. Initialiser le projet
railway init

# 4. Créer une base de données PostgreSQL
railway add postgres

# 5. Déployer
railway up

# 6. Vérifier les logs
railway logs

Variables d'environnement en production

NODE_ENV=production
PORT=3333
JWT_SECRET=une_cle_secrete_vraiment_très_longue_et_aléatoire
DB_HOST=<host_db_cloud>
DB_PORT=5432
DB_USER=<user_db>
DB_PASSWORD=<password_db>
DB_NAME=culturequiz
CORS_ORIGIN=https://votredomaine.com

📞 Support et ressources


📄 Licence

Ce projet est sous licence ISC.


Dernière mise à jour: Janvier 2026
Version API: 1.0.0

About

Projet de cours en B3 Dev - API REST pour l'application CultureQuiz, un système de quiz interactif permettant de tester vos connaissances culturelles par catégorie.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors