Module Vitrine des SAEs (Mode Public)

Retour à l'organigramme
SQLite / backend/server.js

Structure de la table "SAE"

  • Statut de validation : Le champ statut agit comme un verrou. Seules les SAEs marquées comme "validee" (par un administrateur) sont techniquement publiables.
  • Contrôle enseignant : Le champ booléen est_publique (0 ou 1) donne le contrôle final à l'auteur pour décider si le sujet doit apparaître sur la vitrine publique du site.
/* Structure de la table pour les Situations d'Apprentissage */
CREATE TABLE IF NOT EXISTS SAE (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nom TEXT NOT NULL,
    auteur_id INTEGER NOT NULL,
    description TEXT,
    date_creation TEXT,
    documents TEXT,
    date_rendu TEXT,
    classe_cible TEXT,
    /* Verrou administratif */
    statut TEXT DEFAULT 'validee', 
    /* Verrou enseignant (Vitrine) */
    est_publique INTEGER DEFAULT 0,
    afficher_rendus INTEGER DEFAULT 0,
    FOREIGN KEY (auteur_id) REFERENCES Comptes(id) ON DELETE CASCADE
);
Node.js / backend/server.js

Route Publique Restreinte

  • Séparation des routes : Contrairement à la route protégée /api/sae, la route publique /api/public/sae ne requiert aucun Token JWT.
  • Restriction stricte (WHERE) : La requête SQL impose les conditions statut = 'validee' ET est_publique = 1 pour prévenir toute fuite de sujets en cours de rédaction.
  • Jointure (JOIN) : Le nom et prénom de l'auteur sont récupérés dynamiquement depuis la table Comptes pour enrichir l'affichage visuel sans exposer d'autres données sensibles du professeur.
/* Route non protégée (Pas de middleware verifierToken) */
app.get('/api/public/sae', async (req, res) => {
    try {
        // Extraction filtrée des SAEs autorisées
        const saes = await db.all(`
            SELECT SAE.*, Comptes.nom AS auteur_nom, Comptes.prenom AS auteur_prenom 
            FROM SAE 
            JOIN Comptes ON SAE.auteur_id = Comptes.id 
            WHERE SAE.statut = 'validee' AND SAE.est_publique = 1
        `);
        
        // ... (Logique d'attachement des rendus étudiants)
        
        res.json(saes);
    } catch (error) { 
        res.status(500).json({ message: "Erreur serveur" }); 
    }
});
React / frontend/src/services/saeServices.js

Service de communication HTTP

  • Encapsulation : L'appel réseau (Fetch) est isolé dans un service dédié pour garder les composants React propres.
  • Absence d'en-tête (Header) : Aucune autorisation n'est envoyée dans la requête, confirmant la nature totalement ouverte de cet appel.
  • Gestion d'erreur : Si le serveur est injoignable ou renvoie une erreur, une exception est levée et interceptée par le frontend pour afficher un message à l'utilisateur.
export const saeService = {
  // ... autres méthodes

  // Appel de la route publique sans injection de Token
  getPublicListeSae: async () => {
    const response = await fetch(`${API_BASE_URL}/public/sae`);
    
    // Interception des erreurs HTTP (ex: 500 Internal Server Error)
    if (!response.ok) {
        throw new Error("Erreur de chargement des ressources publiques.");
    }
    
    // Renvoi des données JSON prêtes à l'emploi
    return await response.json();
  }
};
React / frontend/src/App.jsx

Cycle de vie & Rendu

  • Initialisation (useEffect) : Au montage du composant, React vérifie si un token existe. Si l'utilisateur n'est pas connecté, la fonction getPublicListeSae() est déclenchée.
  • Algorithme de Tri : Avant l'affichage, les SAEs passent par une fonction getSaesTriees() qui permet au visiteur de les classer par date (proche ou lointaine).
  • Composant interactif : Chaque SAE génère une "carte" (Card) affichant les badges, le titre, et un extrait de la description calculé dynamiquement (100 premiers caractères).
/* 1. Déclenchement au chargement de l'application */
useEffect(() => {
    setLoading(true);
    if (!token) {
        // Mode Visiteur : On charge uniquement la vitrine
        saeService.getPublicListeSae()
            .then(setSaes)
            .finally(() => setLoading(false));
    }
}, [token]);

/* 2. Rendu de la grille (JSX) */
{getSaesTriees(saes).map(sae => (
{sae.classe_cible}

{sae.nom}

{/* Troncation automatique de la description longue */}

{sae.description.substring(0, 100)}...

Date limite : {formatDateTime(sae.date_rendu)}
))}