From fe67f020eb71e2c6a695a1247232c12db1c71ed2 Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Sun, 9 Mar 2025 00:54:21 -0500 Subject: [PATCH 1/3] =?UTF-8?q?[BUG]=20=C3=A9tudiant=20qui=20se=20joint=20?= =?UTF-8?q?=C3=A0=20une=20salle=20apr=C3=A8s=20le=20d=C3=A9marrage=20du=20?= =?UTF-8?q?quiz=20est=20bloqu=C3=A9=20Fixes=20#283=20Valeurs=20de=20l'?= =?UTF-8?q?=C3=A9tat=20de=20la=20page=20(quizStarted)=20n'ont=20pas=20leur?= =?UTF-8?q?=20valeur=20actuelle=20dans=20un=20on().=20Alors,=20on=20d?= =?UTF-8?q?=C3=A9place=20la=20logique=20du=20traitement=20du=20nouvel=20?= =?UTF-8?q?=C3=A9tudiant=20dans=20un=20useEffect=20et=20on=20provoque=20le?= =?UTF-8?q?=20useEffect=20dans=20le=20on()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/Student/JoinRoom/JoinRoom.tsx | 5 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 69 +++++++++++++------ 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/client/src/pages/Student/JoinRoom/JoinRoom.tsx b/client/src/pages/Student/JoinRoom/JoinRoom.tsx index dc7e80c..96d1241 100644 --- a/client/src/pages/Student/JoinRoom/JoinRoom.tsx +++ b/client/src/pages/Student/JoinRoom/JoinRoom.tsx @@ -39,9 +39,8 @@ const JoinRoom: React.FC = () => { }, []); useEffect(() => { - // init the answers array, one for each question - setAnswers(Array(questions.length).fill({} as AnswerSubmissionToBackendType)); console.log(`JoinRoom: useEffect: questions: ${JSON.stringify(questions)}`); + setAnswers(questions ? Array(questions.length).fill({} as AnswerSubmissionToBackendType) : []); }, [questions]); @@ -64,6 +63,7 @@ const JoinRoom: React.FC = () => { console.log('on(launch-teacher-mode): Received launch-teacher-mode:', questions); setQuizMode('teacher'); setIsWaitingForTeacher(true); + setQuestions([]); // clear out from last time (in case quiz is repeated) setQuestions(questions); // wait for next-question }); @@ -72,6 +72,7 @@ const JoinRoom: React.FC = () => { setQuizMode('student'); setIsWaitingForTeacher(false); + setQuestions([]); // clear out from last time (in case quiz is repeated) setQuestions(questions); setQuestion(questions[0]); }); diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index bcdc80d..f9d3791 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -36,8 +36,40 @@ const ManageRoom: React.FC = () => { const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher'); const [connectingError, setConnectingError] = useState(''); const [currentQuestion, setCurrentQuestion] = useState(undefined); - const [quizStarted, setQuizStarted] = useState(false); + const [quizStarted, setQuizStarted] = useState(false); const [formattedRoomName, setFormattedRoomName] = useState(""); + const [newlyConnectedUser, setNewlyConnectedUser] = useState(null); + + // Handle the newly connected user in useEffect, because it needs state info + // not available in the socket.on() callback + useEffect(() => { + if (newlyConnectedUser) { + console.log(`Handling newly connected user: ${newlyConnectedUser.name}`); + setStudents((prevStudents) => [...prevStudents, newlyConnectedUser]); + + // only send nextQuestion if the quiz has started + if (!quizStarted) { + console.log(`!quizStarted: returning.... `); + return; + } + + if (quizMode === 'teacher') { + webSocketService.nextQuestion({ + roomName: formattedRoomName, + questions: quizQuestions, + questionIndex: Number(currentQuestion?.question.id) - 1, + isLaunch: true // started late + }); + } else if (quizMode === 'student') { + webSocketService.launchStudentModeQuiz(formattedRoomName, quizQuestions); + } else { + console.error('Invalid quiz mode:', quizMode); + } + + // Reset the newly connected user state + setNewlyConnectedUser(null); + } + }, [newlyConnectedUser, quizStarted, quizMode, formattedRoomName, quizQuestions, currentQuestion]); useEffect(() => { const verifyLogin = async () => { @@ -110,6 +142,17 @@ const ManageRoom: React.FC = () => { const roomNameUpper = roomName.toUpperCase(); setFormattedRoomName(roomNameUpper); console.log(`Creating WebSocket room named ${roomNameUpper}`); + + /** + * ATTENTION: Lire les variables d'état dans + * les .on() n'est pas une bonne pratique. + * Les valeurs sont celles au moment de la création + * de la fonction et non au moment de l'exécution. + * Il faut utiliser des refs pour les valeurs qui + * changent fréquemment. Sinon, utiliser un trigger + * de useEffect pour mettre déclencher un traitement + * (voir user-joined plus bas). + */ socket.on('connect', () => { webSocketService.createRoom(roomNameUpper); }); @@ -124,23 +167,9 @@ const ManageRoom: React.FC = () => { }); socket.on('user-joined', (student: StudentType) => { - console.log(`Student joined: name = ${student.name}, id = ${student.id}, quizMode = ${quizMode}, quizStarted = ${quizStarted}`); - - setStudents((prevStudents) => [...prevStudents, student]); - - // only send nextQuestion if the quiz has started - if (!quizStarted) return; - - if (quizMode === 'teacher') { - webSocketService.nextQuestion( - {roomName: formattedRoomName, - questions: quizQuestions, - questionIndex: Number(currentQuestion?.question.id) - 1, - isLaunch: false}); - } else if (quizMode === 'student') { - webSocketService.launchStudentModeQuiz(formattedRoomName, quizQuestions); - } + setNewlyConnectedUser(student); }); + socket.on('join-failure', (message) => { setConnectingError(message); setSocket(null); @@ -286,21 +315,19 @@ const ManageRoom: React.FC = () => { }; const launchQuiz = () => { + setQuizStarted(true); if (!socket || !formattedRoomName || !quiz?.content || quiz?.content.length === 0) { // TODO: This error happens when token expires! Need to handle it properly console.log( `Error launching quiz. socket: ${socket}, roomName: ${formattedRoomName}, quiz: ${quiz}` ); - setQuizStarted(true); - return; } + console.log(`Launching quiz in ${quizMode} mode...`); switch (quizMode) { case 'student': - setQuizStarted(true); return launchStudentMode(); case 'teacher': - setQuizStarted(true); return launchTeacherMode(); } }; From 29de2a7671f84eb8b6d7bb7c1d72c2161a2d064e Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Sun, 9 Mar 2025 01:19:31 -0500 Subject: [PATCH 2/3] =?UTF-8?q?Correction=20de=20bogue=20trouv=C3=A9=20par?= =?UTF-8?q?=20test!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/Teacher/ManageRoom/ManageRoom.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index f9d3791..01d9c27 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -69,7 +69,7 @@ const ManageRoom: React.FC = () => { // Reset the newly connected user state setNewlyConnectedUser(null); } - }, [newlyConnectedUser, quizStarted, quizMode, formattedRoomName, quizQuestions, currentQuestion]); + }, [newlyConnectedUser]); useEffect(() => { const verifyLogin = async () => { From dcaee719d17f9f67d8447440fae0683320f10668 Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Mon, 10 Mar 2025 15:20:07 -0400 Subject: [PATCH 3/3] =?UTF-8?q?Fixes=20#286=20-=20Faire=20un=20vrai=20sing?= =?UTF-8?q?leton=20pour=20la=20connexion=20=C3=A0=20la=20base=20de=20donn?= =?UTF-8?q?=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/app.js | 7 +++++++ server/config/db.js | 39 ++++++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/server/app.js b/server/app.js index 32f64b9..938d2f0 100644 --- a/server/app.js +++ b/server/app.js @@ -127,4 +127,11 @@ async function start() { }); } +// Graceful shutdown on SIGINT (Ctrl+C) +process.on('SIGINT', async () => { + console.log('Shutting down...'); + await db.closeConnection(); + process.exit(0); +}); + start(); diff --git a/server/config/db.js b/server/config/db.js index cf492bf..ccc43e7 100644 --- a/server/config/db.js +++ b/server/config/db.js @@ -1,28 +1,53 @@ const { MongoClient } = require('mongodb'); -const dotenv = require('dotenv') +const dotenv = require('dotenv'); dotenv.config(); class DBConnection { - constructor() { this.mongoURI = process.env.MONGO_URI; this.databaseName = process.env.MONGO_DATABASE; + this.client = null; this.connection = null; } + // Connect to the database, but don't reconnect if already connected async connect() { - const client = new MongoClient(this.mongoURI); - this.connection = await client.connect(); + if (this.connection) { + console.log('Using existing MongoDB connection'); + return this.connection; + } + + try { + // Create the MongoClient only if the connection does not exist + this.client = new MongoClient(this.mongoURI); + await this.client.connect(); + this.connection = this.client.db(this.databaseName); + console.log('MongoDB connected'); + return this.connection; + } catch (error) { + console.error('MongoDB connection error:', error); + throw new Error('Failed to connect to MongoDB'); + } } + // Return the current database connection getConnection() { if (!this.connection) { - throw new Error('Connexion MongoDB non établie'); + throw new Error('MongoDB connection not established'); + } + return this.connection; + } + + // Close the MongoDB connection gracefully + async closeConnection() { + if (this.client) { + await this.client.close(); + console.log('MongoDB connection closed'); } - return this.connection.db(this.databaseName); } } +// Exporting the singleton instance const instance = new DBConnection(); -module.exports = instance; \ No newline at end of file +module.exports = instance;