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) { /* ... */ }
});