Module d'Exposition des Travaux Étudiants

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

Structure de la table "Rendus"

  • Clés étrangères (Foreign Keys) : La table est liée relationnellement à la table SAE et à la table Comptes (l'étudiant). L'option ON DELETE CASCADE garantit la propreté de la base si un compte est supprimé.
  • Stockage JSON : Le champ documents est de type TEXT. Il stocke un tableau sérialisé au format JSON (ex: ["fichier1.pdf", "fichier2.zip"]) pour permettre le multi-upload.
/* Initialisation de la table des rendus */
CREATE TABLE IF NOT EXISTS Rendus (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    sae_id INTEGER NOT NULL,
    etudiant_id INTEGER NOT NULL,
    date_soumission TEXT NOT NULL,
    documents TEXT,
    FOREIGN KEY (sae_id) REFERENCES SAE(id) ON DELETE CASCADE,
    FOREIGN KEY (etudiant_id) REFERENCES Comptes(id) ON DELETE CASCADE
);
Node.js / backend/server.js

Requêtes imbriquées (Sub-fetching)

  • Filtrage de base : L'API récupère d'abord toutes les SAEs validées et publiques.
  • Condition métier : Une boucle itère sur les résultats. Si l'enseignant a défini afficher_rendus = 1, une requête supplémentaire est déclenchée.
  • Jointure SQL (JOIN) : Cette seconde requête lie les tables Rendus et Comptes pour récupérer les fichiers soumis associés au prénom de l'auteur.
app.get('/api/public/sae', async (req, res) => {
    try {
        // 1. Récupération des SAEs vitrines
        const saes = await db.all(`SELECT * FROM SAE WHERE statut = 'validee' AND est_publique = 1`);
        
        // 2. Traitement conditionnel des rendus
        for (let sae of saes) {
            if (sae.afficher_rendus === 1) {
                // Jointure pour obtenir le prénom de l'étudiant et ses fichiers
                sae.rendus_publics = await db.all(`
                    SELECT Rendus.documents, Comptes.prenom 
                    FROM Rendus 
                    JOIN Comptes ON Rendus.etudiant_id = Comptes.id 
                    WHERE Rendus.sae_id = ?
                `, [sae.id]);
            } else {
                sae.rendus_publics = [];
            }
        }
        res.json(saes);
    } catch (error) { /* Gestion erreur */ }
});
React / frontend/src/App.jsx

Rendu dynamique de l'interface

  • Évaluation stricte : Le composant React utilise des opérateurs logiques (&&) pour s'assurer que l'utilisateur n'est pas connecté (!token) et que des rendus existent avant d'afficher la section.
  • Parsing JSON : La chaîne de caractères stockée en base de données est désérialisée avec JSON.parse() pour recréer le tableau itérable de fichiers.
  • Double Mapping : Une boucle map() parcourt chaque étudiant, et une boucle interne parcourt les fichiers de cet étudiant.
{/* Rendu conditionnel dans la vue publique */}
{!token && sae.afficher_rendus === 1 && sae.rendus_publics?.length > 0 && (
  

Travaux des étudiants

{/* Itération sur les étudiants */} {sae.rendus_publics.map((rendu, i) => { // Désérialisation du tableau de fichiers const docs = JSON.parse(rendu.documents); // Itération sur les fichiers de l'étudiant return docs.map((doc, j) => ( {rendu.prenom} - {doc.split('-').slice(1).join('-')} )); })}
)}
Node.js / backend/server.js

Politique de confidentialité

  • Minimisation des données (RGPD) : Sur la route publique, la requête SQL est volontairement bridée. Au lieu d'utiliser SELECT *, elle demande explicitement SELECT Comptes.prenom.
  • Résultat : Le nom de famille et l'adresse e-mail de l'étudiant ne quittent jamais le serveur lors d'un appel public, empêchant toute collecte de données automatisée (scraping).
  • Traitement d'affichage : Côté frontend, le Timestamp unique généré par Multer lors de l'upload est nettoyé via .split('-').slice(1) pour redonner au visiteur le nom de fichier original.
/* Mauvaise pratique (Fuite de données) : */
SELECT Rendus.documents, Comptes.* FROM Rendus ...

/* Bonne pratique implémentée (Minimisation) : */
SELECT Rendus.documents, Rendus.date_soumission, Comptes.prenom 
FROM Rendus 
JOIN Comptes ON Rendus.etudiant_id = Comptes.id 
WHERE Rendus.sae_id = ?
/* Côté React : Nettoyage du nom de fichier généré par le serveur */
// Avant : "1712045600-Mon_Fichier.pdf"
// Après : "Mon_Fichier.pdf"
const cleanFileName = doc.split('-').slice(1).join('-');
Express / backend/server.js

Distribution des Fichiers Statiques

  • Exposition du dossier : Le framework Express.js ne permet pas d'accéder aux fichiers du serveur par défaut.
  • Middleware Static : La fonction express.static() est utilisée pour créer une route virtuelle (/uploads) pointant vers le dossier physique du disque dur contenant les PDF des élèves.
  • Sécurité d'accès : Le module fs (File System) vérifie l'existence du dossier au démarrage et le crée s'il est manquant pour éviter les erreurs (Crash du serveur).
const fs = require('fs');
const path = require('path');

// Chemin absolu vers le dossier physique
const uploadDir = path.join(__dirname, 'uploads');

// Création automatique si inexistant
if (!fs.existsSync(uploadDir)) {
    fs.mkdirSync(uploadDir); 
}

// Configuration du serveur web pour servir ces fichiers
// Exemple d'URL générée : http://serveur:8000/uploads/fichier.pdf
app.use('/uploads', express.static(uploadDir));