diff --git a/VNode/services/game/session-manager.js b/VNode/services/game/session-manager.js new file mode 100644 index 00000000..a5ad5ee9 --- /dev/null +++ b/VNode/services/game/session-manager.js @@ -0,0 +1,290 @@ +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); +});