classe_cible permet de définir si l'annonce est destinée à une classe spécifique (ex: MMI-A1) ou à tout le monde ("Toutes").auteur_id) et potentiellement à une Situation d'Apprentissage (sae_id).ON DELETE CASCADE nettoiera automatiquement les annonces obsolètes./* Initialisation SQLite (server.js) */
CREATE TABLE IF NOT EXISTS Annonces (
id INTEGER PRIMARY KEY AUTOINCREMENT,
auteur_id INTEGER NOT NULL,
message TEXT NOT NULL,
classe_cible TEXT NOT NULL,
sae_id INTEGER,
date_creation TEXT NOT NULL,
/* Relations fortes */
FOREIGN KEY (auteur_id) REFERENCES Comptes(id) ON DELETE CASCADE,
FOREIGN KEY (sae_id) REFERENCES SAE(id) ON DELETE CASCADE
);
classe_cible correspond à celle de l'étudiant, ou celles marquées "Toutes".LEFT JOIN sur la SAE empêche la requête d'échouer si l'annonce n'a pas de SAE liée.app.get('/api/annonces', verifierToken, async (req, res) => {
try {
// ... (Logique admin / enseignant)
// Logique Étudiant : Récupération de sa classe
const userDb = await db.get('SELECT classe FROM Comptes WHERE id = ?', [req.user.id]);
// Requête complexe avec jointures et filtre de classe
const rows = await db.all(`
SELECT Annonces.*, Comptes.nom, Comptes.prenom, SAE.nom AS sae_nom
FROM Annonces
JOIN Comptes ON Annonces.auteur_id = Comptes.id
LEFT JOIN SAE ON Annonces.sae_id = SAE.id
WHERE Annonces.classe_cible = ? OR Annonces.classe_cible = 'Toutes'
ORDER BY Annonces.id DESC
`, [userDb.classe]);
return res.json(rows);
} catch (error) { /* ... */ }
});
annonces contient des éléments (annonces.length > 0).ann.sae_id est défini), un bouton "Voir SAE" est généré dynamiquement.openSaeDetails() qui bascule l'interface directement sur la SAE sans recharger la page.{/* Extrait de App.jsx - Affichage Conditionnel */}
{token && role !== 'admin' && vueActuelle === 'dashboard' && annonces.length > 0 && (
Annonces récentes
{annonces.map(ann => (
{ann.prenom} {ann.nom} {ann.classe_cible}
{formatDateTime(ann.date_creation)}
{ann.message}
{/* Bouton conditionnel si une SAE est liée */}
{ann.sae_id && (
)}
))}
)}