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));