Module Enseignant : Éditeur et Publication de SAE

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

Modèle de la table SAE

  • Métadonnées complexes : La table stocke la classe ciblée, la date d'échéance et les consignes détaillées du professeur.
  • Documents sérialisés : Les fichiers de consigne (PDF, ZIP) déposés par le professeur sont listés dans le champ documents sous forme de tableau JSON.
  • Flux de validation : À sa création, le statut de la SAE passe par défaut à "en_attente" si l'auteur est un enseignant. Il requiert l'intervention d'un administrateur pour passer en "validee".
/* Initialisation de la table des SAE */
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,         -- Tableau JSON des fichiers
    date_rendu TEXT,
    classe_cible TEXT,
    statut TEXT DEFAULT 'validee', 
    est_publique INTEGER DEFAULT 0,
    afficher_rendus INTEGER DEFAULT 0,
    FOREIGN KEY (auteur_id) REFERENCES Comptes(id) ON DELETE CASCADE
);
React / frontend/src/App.jsx

Construction du Payload (FormData)

  • Le défi du JSON : Il est impossible d'envoyer des fichiers binaires dans un objet JSON classique. Il faut utiliser une requête multipart/form-data.
  • Interface FormData : React compile toutes les variables d'état (titre, dates, options) dans un objet natif FormData.
  • Boucle d'itération : Les fichiers sélectionnés dans l'input (stockés dans un tableau fichiersSae) sont ajoutés un à un à la clé fichiers du FormData.
const handleCreateSae = async (e) => {
    e.preventDefault(); 
    setErreur(null);
    try {
      // 1. Initialisation de l'enveloppe
      const formData = new FormData();
      
      // 2. Ajout des métadonnées (Textes et Booléens)
      formData.append('nom', nomSae); 
      formData.append('description', descriptionSae); 
      formData.append('date_rendu', dateRenduSae); 
      formData.append('classe_cible', classeCible);
      formData.append('est_publique', estPublique ? '1' : '0');
      formData.append('afficher_rendus', afficherRendus ? '1' : '0');
      
      // 3. Ajout dynamique des fichiers binaires (Géré comme un tableau)
      fichiersSae.forEach(f => formData.append('fichiers', f));

      // 4. Envoi au service API
      await saeService.createSae(formData, token);
      
      // ... (Rafraîchissement de l'UI)
    } catch (err) { setErreur(err.message); }
};
Express / backend/server.js

Configuration de Multer

  • Stockage local (DiskStorage) : Le middleware multer intercepte la requête entrante avant même qu'elle n'atteigne la route API.
  • Anonymisation & Sécurité : Il renomme le fichier à la volée en ajoutant un horodatage (Date.now()) en préfixe et remplace les espaces par des tirets bas (_) pour éviter les failles de path traversal et les conflits de noms.
  • Sauvegarde physique : Le fichier est écrit dans le répertoire /uploads de la machine serveur.
const multer = require('multer');

// Configuration du moteur de stockage
const storage = multer.diskStorage({
    // Définition du dossier de destination
    destination: (req, file, cb) => cb(null, uploadDir),
    
    // Règle de nommage (Anti-collision)
    filename: (req, file, cb) => {
        // Résultat : 1712048500-Sujet_TP_Web.pdf
        const safeName = file.originalname.replace(/\s+/g, '_');
        cb(null, Date.now() + '-' + safeName); 
    }
});

// Initialisation du middleware
const upload = multer({ storage: storage });
Node.js / backend/server.js

Contrôleur et Insertion BDD

  • Double Middleware : La route utilise verifierToken pour authentifier l'utilisateur, puis upload.array('fichiers', 10) pour traiter jusqu'à 10 pièces jointes.
  • Traitement des noms : Les nouveaux noms sécurisés des fichiers (générés par Multer) sont récupérés dans req.files, convertis en JSON, et préparés pour la base de données.
  • Assignation du statut : Le statut de la SAE s'adapte au rôle de l'auteur. Si c'est un Admin, elle est "validee" d'office. Si c'est un Enseignant, elle est "en_attente" d'approbation.
app.post('/api/sae', verifierToken, upload.array('fichiers', 10), async (req, res) => {
    // 1. Contrôle des privilèges
    if (req.user.role !== 'enseignant' && req.user.role !== 'admin') {
        return res.status(403).json({ message: "Non autorisé." });
    }
    
    // 2. Extraction des textes
    const { nom, description, date_rendu, classe_cible } = req.body;
    
    // 3. Traitement des fichiers (Générés par Multer en amont)
    const fichiersNoms = req.files ? req.files.map(f => f.filename) : [];
    const documentsStr = JSON.stringify(fichiersNoms); 
    
    // 4. Logique métier de validation
    const statut = req.user.role === 'admin' ? 'validee' : 'en_attente';

    try {
        // 5. Insertion SQL
        await db.run(
            'INSERT INTO SAE (nom, auteur_id, description, documents, date_rendu, classe_cible, statut...) VALUES (...)', 
            [nom, req.user.id, description, documentsStr, date_rendu, classe_cible, statut...]
        );
        res.status(201).json({ message: "SAE créée !" });
    } catch (error) { /* ... */ }
});