const fs = require('fs'); const mqtt = require('mqtt'); const path = require('path'); const express = require('express'); const multer = require('multer'); const cors = require('cors'); // Lecture du fichier de configuration const configPath = path.join(__dirname, '../config/configuration.json'); const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); // Extraction des informations de config const { mqttHost, services: { session: { MQTTconfig: { mqttSessionRequestTopic, mqttSessionGetTopic, mqttSessionUpdateTopic, mqttSessionListTopic, mqttSessionListResponseTopic, mqttSessionCreateTopic, mqttSessionDeleteTopic }, httpPort } } } = config; const PORT = httpPort || 3000; const quizzDir = path.join(__dirname, 'quizz'); // Configurer Express const app = express(); app.use(cors()); // Configuration de Multer pour l'upload const storage = multer.diskStorage({ destination: function (req, file, cb) { const sessionId = req.params.sessionId; let subfolder = 'assets'; // Organiser par type (envoyé par le frontend dans req.body.type) if (req.body.type === 'video') subfolder = 'assets/videos'; else if (req.body.type === 'audio') subfolder = 'assets/audios'; else if (req.body.type === 'picture') subfolder = 'assets/pictures'; const uploadPath = path.join(quizzDir, sessionId, subfolder); // Créer le dossier s'il n'existe pas fs.mkdirSync(uploadPath, { recursive: true }); cb(null, uploadPath); }, filename: function (req, file, cb) { // Utiliser l'ID de la question comme nom de fichier si disponible const ext = path.extname(file.originalname); let filename = 'file-' + Date.now() + '-' + Math.round(Math.random() * 1E9); if (req.body.questionId) { filename = req.body.questionId; } cb(null, filename + ext); } }); const upload = multer({ storage: storage }); // Route d'upload app.post('/upload/:sessionId', upload.single('file'), (req, res) => { if (!req.file) { return res.status(400).send('No file uploaded.'); } // Calculer le chemin relatif pour l'URL // req.file.path est le chemin absolu sur le disque // On veut le chemin relatif à partir du dossier de session const sessionId = req.params.sessionId; const sessionDir = path.join(quizzDir, sessionId); // path.relative(from, to) -> donne le chemin relatif let relativeId = path.relative(sessionDir, req.file.path); // Remplacer les backslashes par des slashs pour les URLs web relativeId = relativeId.replace(/\\/g, '/'); // Ajouter le slash initial const relativePath = `/${relativeId}`; res.json({ path: relativePath, fullPath: req.file.path }); }); // App.use pour servir les fichiers statiques app.use('/quizz', express.static(quizzDir)); app.listen(PORT, () => { console.log(`[HTTP] Serveur d'upload démarré sur le port ${PORT}`); }); // Connexion au broker MQTT const client = mqtt.connect(mqttHost); console.log("------------------------------------------------------------------------------"); console.log("[CONFIG] Session Manager chargé (Multi-Session + Upload)"); console.log("[CONFIG] Hôte MQTT :", mqttHost); console.log("[CONFIG] Port HTTP :", PORT); console.log("[CONFIG] Dossier Quizz :", quizzDir); console.log("------------------------------------------------------------------------------"); client.on('connect', () => { console.log(`[INFO] Connecté au broker MQTT à ${mqttHost}`); client.subscribe(mqttSessionRequestTopic); client.subscribe(mqttSessionUpdateTopic); client.subscribe(mqttSessionListTopic); client.subscribe(mqttSessionCreateTopic); client.subscribe(mqttSessionDeleteTopic); console.log(`[INFO] Abonné aux topics session`); }); client.on('message', (topic, message) => { if (topic === mqttSessionListTopic) { console.log(`[INFO] Demande de liste de sessions`); sendSessionList(); } else if (topic === mqttSessionRequestTopic) { try { const payload = JSON.parse(message.toString()); console.log(`[INFO] Demande de configuration pour session: ${payload.SessionId}`); if (payload.SessionId) { sendSessionConfiguration(payload.SessionId); } } catch (e) { console.error("Erreur payload request", e); } } else if (topic === mqttSessionUpdateTopic) { try { const payload = JSON.parse(message.toString()); console.log(`[INFO] Mise à jour configuration pour session: ${payload.SessionId}`); if (payload.SessionId && payload.Config) { saveSessionConfiguration(payload.SessionId, payload.Config); } } catch (e) { console.error('[ERREUR] Impossible de parser la mise à jour', e); } } else if (topic === mqttSessionCreateTopic) { try { const payload = JSON.parse(message.toString()); console.log(`[INFO] Demande de création de session: ${payload.SessionName}`); if (payload.SessionName) { createSession(payload.SessionName); } } catch (e) { console.error('[ERREUR] Impossible de parser la demande de création', e); } } else if (topic === mqttSessionDeleteTopic) { try { const payload = JSON.parse(message.toString()); console.log(`[INFO] Demande de suppression de session: ${payload.SessionId}`); if (payload.SessionId) { deleteSession(payload.SessionId); } } catch (e) { console.error('[ERREUR] Impossible de parser la demande de suppression', e); } } }); function sendSessionList() { fs.readdir(quizzDir, { withFileTypes: true }, (err, entries) => { if (err) { console.error('[ERREUR] Lecture dossier quizz', err); return; } const sessions = []; entries.forEach(entry => { if (entry.isDirectory()) { const configPath = path.join(quizzDir, entry.name, 'session-configuration.json'); if (fs.existsSync(configPath)) { try { const sessConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); sessions.push({ id: entry.name, title: sessConfig.PackTitle || entry.name }); } catch (e) { sessions.push({ id: entry.name, title: entry.name + " (Erreur Config)" }); } } } }); client.publish(mqttSessionListResponseTopic, JSON.stringify(sessions)); console.log(`[INFO] Liste envoyée : ${sessions.length} sessions`); }); } function sendSessionConfiguration(sessionId) { const sessionFilePath = path.join(quizzDir, sessionId, 'session-configuration.json'); fs.readFile(sessionFilePath, 'utf8', (err, data) => { if (err) { console.error('[ERREUR] Impossible de lire le fichier de session', err); return; } client.publish(mqttSessionGetTopic, data); console.log(`[INFO] Configuration envoyée pour ${sessionId}`); }); } function saveSessionConfiguration(sessionId, newConfig) { const sessionFilePath = path.join(quizzDir, sessionId, 'session-configuration.json'); // Validation if (!newConfig.Questions || !Array.isArray(newConfig.Questions)) { console.error('[ERREUR] Configuration invalide'); return; } const data = JSON.stringify(newConfig, null, 2); fs.writeFile(sessionFilePath, data, (err) => { if (err) { console.error('[ERREUR] Impossible d\'écrire le fichier de session', err); } else { console.log(`[INFO] Session ${sessionId} mise à jour avec succès`); // Confirmer la sauvegarde en renvoyant la config client.publish(mqttSessionGetTopic, data); } }); } function createSession(sessionName) { // Nettoyer le nom pour le dossier const safeName = sessionName.replace(/[^a-z0-9]/gi, '_').toLowerCase(); const newSessionPath = path.join(quizzDir, safeName); if (fs.existsSync(newSessionPath)) { console.error(`[ERREUR] La session ${safeName} existe déjà`); return; } // Créer le dossier try { fs.mkdirSync(newSessionPath, { recursive: true }); // Créer la structure de base const defaultConfig = { PackId: "", PackTitle: sessionName, Questions: [] }; fs.writeFileSync( path.join(newSessionPath, 'session-configuration.json'), JSON.stringify(defaultConfig, null, 2) ); console.log(`[INFO] Nouvelle session créée: ${safeName}`); // Renvoyer la liste mise à jour sendSessionList(); } catch (e) { console.error(`[ERREUR] Impossible de créer la session ${safeName}`, e); } } function deleteSession(sessionId) { const sessionPath = path.join(quizzDir, sessionId); if (!fs.existsSync(sessionPath)) { console.error(`[ERREUR] La session ${sessionId} n'existe pas`); return; } try { fs.rmSync(sessionPath, { recursive: true, force: true }); console.log(`[INFO] Session supprimée: ${sessionId}`); // Renvoyer la liste mise à jour sendSessionList(); } catch (e) { console.error(`[ERREUR] Impossible de supprimer la session ${sessionId}`, e); } } client.on('error', (error) => { console.error('[ERREUR] Erreur de connexion au broker MQTT:', error.message); });