Merge branch 'main' of https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir into feature/qr-code-url-generation

This commit is contained in:
NouhailaAater 2025-03-12 14:42:27 -04:00
commit ef192eb784
4 changed files with 116 additions and 47 deletions

View file

@ -51,9 +51,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]);
@ -76,6 +75,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
});
@ -84,6 +84,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]);
});

View file

@ -45,11 +45,43 @@ const ManageRoom: React.FC = () => {
const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher');
const [connectingError, setConnectingError] = useState<string>('');
const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined);
const [quizStarted, setQuizStarted] = useState(false);
const [formattedRoomName, setFormattedRoomName] = useState("");
const [quizStarted, setQuizStarted] = useState<boolean>(false);
const [formattedRoomName, setFormattedRoomName] = useState('');
const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null);
const roomUrl = `${window.location.origin}/student/join-room?roomName=${roomName}`;
const [showQrModal, setShowQrModal] = useState(false);
// 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]);
useEffect(() => {
const verifyLogin = async () => {
if (!ApiService.isLoggedIn()) {
@ -61,6 +93,15 @@ const ManageRoom: React.FC = () => {
verifyLogin();
}, []);
useEffect(() => {
if (!roomName) {
console.error('Room name is missing!');
return;
}
console.log(`Joining room: ${roomName}`);
}, [roomName]);
useEffect(() => {
if (!roomName || !quizId) {
window.alert(
@ -77,15 +118,6 @@ const ManageRoom: React.FC = () => {
};
}, [roomName, navigate]);
useEffect(() => {
if (!roomName) {
console.error('Room name is missing!');
return;
}
console.log(`Joining room: ${roomName}`);
}, [roomName]);
useEffect(() => {
if (quizId) {
const fetchQuiz = async () => {
@ -130,6 +162,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);
});
@ -144,23 +187,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);
@ -175,7 +204,6 @@ const ManageRoom: React.FC = () => {
};
useEffect(() => {
if (socket) {
console.log(`Listening for submit-answer-room in room ${formattedRoomName}`);
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
@ -249,10 +277,12 @@ const ManageRoom: React.FC = () => {
if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return;
setCurrentQuestion(quizQuestions[nextQuestionIndex]);
webSocketService.nextQuestion({roomName: formattedRoomName,
webSocketService.nextQuestion({
roomName: formattedRoomName,
questions: quizQuestions,
questionIndex: nextQuestionIndex,
isLaunch: false});
isLaunch: false
});
};
const previousQuestion = () => {
@ -295,7 +325,12 @@ const ManageRoom: React.FC = () => {
}
setCurrentQuestion(quizQuestions[0]);
webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: 0, isLaunch: true});
webSocketService.nextQuestion({
roomName: formattedRoomName,
questions: quizQuestions,
questionIndex: 0,
isLaunch: true
});
};
const launchStudentMode = () => {
@ -311,21 +346,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();
}
};
@ -334,7 +367,12 @@ const ManageRoom: React.FC = () => {
if (quiz?.content && quizQuestions) {
setCurrentQuestion(quizQuestions[questionIndex]);
if (quizMode === 'teacher') {
webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex, isLaunch: false});
webSocketService.nextQuestion({
roomName: formattedRoomName,
questions: quizQuestions,
questionIndex,
isLaunch: false
});
}
}
};
@ -473,7 +511,6 @@ const ManageRoom: React.FC = () => {
</Button>
</DialogActions>
</Dialog>
<div className="roomHeader">
<DisconnectButton
onReturn={handleReturn}
@ -533,7 +570,6 @@ const ManageRoom: React.FC = () => {
<QuestionDisplay
showAnswer={false}
question={currentQuestion?.question as Question}
/>
)}

View file

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

View file

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