mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge branch 'main' of https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir into feature/qr-code-url-generation
This commit is contained in:
commit
ef192eb784
4 changed files with 116 additions and 47 deletions
|
|
@ -51,9 +51,8 @@ const JoinRoom: React.FC = () => {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// init the answers array, one for each question
|
|
||||||
setAnswers(Array(questions.length).fill({} as AnswerSubmissionToBackendType));
|
|
||||||
console.log(`JoinRoom: useEffect: questions: ${JSON.stringify(questions)}`);
|
console.log(`JoinRoom: useEffect: questions: ${JSON.stringify(questions)}`);
|
||||||
|
setAnswers(questions ? Array(questions.length).fill({} as AnswerSubmissionToBackendType) : []);
|
||||||
}, [questions]);
|
}, [questions]);
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -76,6 +75,7 @@ const JoinRoom: React.FC = () => {
|
||||||
console.log('on(launch-teacher-mode): Received launch-teacher-mode:', questions);
|
console.log('on(launch-teacher-mode): Received launch-teacher-mode:', questions);
|
||||||
setQuizMode('teacher');
|
setQuizMode('teacher');
|
||||||
setIsWaitingForTeacher(true);
|
setIsWaitingForTeacher(true);
|
||||||
|
setQuestions([]); // clear out from last time (in case quiz is repeated)
|
||||||
setQuestions(questions);
|
setQuestions(questions);
|
||||||
// wait for next-question
|
// wait for next-question
|
||||||
});
|
});
|
||||||
|
|
@ -84,6 +84,7 @@ const JoinRoom: React.FC = () => {
|
||||||
|
|
||||||
setQuizMode('student');
|
setQuizMode('student');
|
||||||
setIsWaitingForTeacher(false);
|
setIsWaitingForTeacher(false);
|
||||||
|
setQuestions([]); // clear out from last time (in case quiz is repeated)
|
||||||
setQuestions(questions);
|
setQuestions(questions);
|
||||||
setQuestion(questions[0]);
|
setQuestion(questions[0]);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -45,11 +45,43 @@ const ManageRoom: React.FC = () => {
|
||||||
const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher');
|
const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher');
|
||||||
const [connectingError, setConnectingError] = useState<string>('');
|
const [connectingError, setConnectingError] = useState<string>('');
|
||||||
const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined);
|
const [currentQuestion, setCurrentQuestion] = useState<QuestionType | undefined>(undefined);
|
||||||
const [quizStarted, setQuizStarted] = useState(false);
|
const [quizStarted, setQuizStarted] = useState<boolean>(false);
|
||||||
const [formattedRoomName, setFormattedRoomName] = useState("");
|
const [formattedRoomName, setFormattedRoomName] = useState('');
|
||||||
|
const [newlyConnectedUser, setNewlyConnectedUser] = useState<StudentType | null>(null);
|
||||||
const roomUrl = `${window.location.origin}/student/join-room?roomName=${roomName}`;
|
const roomUrl = `${window.location.origin}/student/join-room?roomName=${roomName}`;
|
||||||
const [showQrModal, setShowQrModal] = useState(false);
|
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(() => {
|
useEffect(() => {
|
||||||
const verifyLogin = async () => {
|
const verifyLogin = async () => {
|
||||||
if (!ApiService.isLoggedIn()) {
|
if (!ApiService.isLoggedIn()) {
|
||||||
|
|
@ -61,6 +93,15 @@ const ManageRoom: React.FC = () => {
|
||||||
verifyLogin();
|
verifyLogin();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!roomName) {
|
||||||
|
console.error('Room name is missing!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Joining room: ${roomName}`);
|
||||||
|
}, [roomName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!roomName || !quizId) {
|
if (!roomName || !quizId) {
|
||||||
window.alert(
|
window.alert(
|
||||||
|
|
@ -77,15 +118,6 @@ const ManageRoom: React.FC = () => {
|
||||||
};
|
};
|
||||||
}, [roomName, navigate]);
|
}, [roomName, navigate]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!roomName) {
|
|
||||||
console.error('Room name is missing!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Joining room: ${roomName}`);
|
|
||||||
}, [roomName]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (quizId) {
|
if (quizId) {
|
||||||
const fetchQuiz = async () => {
|
const fetchQuiz = async () => {
|
||||||
|
|
@ -130,6 +162,17 @@ const ManageRoom: React.FC = () => {
|
||||||
const roomNameUpper = roomName.toUpperCase();
|
const roomNameUpper = roomName.toUpperCase();
|
||||||
setFormattedRoomName(roomNameUpper);
|
setFormattedRoomName(roomNameUpper);
|
||||||
console.log(`Creating WebSocket room named ${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', () => {
|
socket.on('connect', () => {
|
||||||
webSocketService.createRoom(roomNameUpper);
|
webSocketService.createRoom(roomNameUpper);
|
||||||
});
|
});
|
||||||
|
|
@ -144,23 +187,9 @@ const ManageRoom: React.FC = () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('user-joined', (student: StudentType) => {
|
socket.on('user-joined', (student: StudentType) => {
|
||||||
console.log(`Student joined: name = ${student.name}, id = ${student.id}, quizMode = ${quizMode}, quizStarted = ${quizStarted}`);
|
setNewlyConnectedUser(student);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('join-failure', (message) => {
|
socket.on('join-failure', (message) => {
|
||||||
setConnectingError(message);
|
setConnectingError(message);
|
||||||
setSocket(null);
|
setSocket(null);
|
||||||
|
|
@ -175,7 +204,6 @@ const ManageRoom: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
if (socket) {
|
if (socket) {
|
||||||
console.log(`Listening for submit-answer-room in room ${formattedRoomName}`);
|
console.log(`Listening for submit-answer-room in room ${formattedRoomName}`);
|
||||||
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
|
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
|
||||||
|
|
@ -249,10 +277,12 @@ const ManageRoom: React.FC = () => {
|
||||||
if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return;
|
if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return;
|
||||||
|
|
||||||
setCurrentQuestion(quizQuestions[nextQuestionIndex]);
|
setCurrentQuestion(quizQuestions[nextQuestionIndex]);
|
||||||
webSocketService.nextQuestion({roomName: formattedRoomName,
|
webSocketService.nextQuestion({
|
||||||
|
roomName: formattedRoomName,
|
||||||
questions: quizQuestions,
|
questions: quizQuestions,
|
||||||
questionIndex: nextQuestionIndex,
|
questionIndex: nextQuestionIndex,
|
||||||
isLaunch: false});
|
isLaunch: false
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const previousQuestion = () => {
|
const previousQuestion = () => {
|
||||||
|
|
@ -295,7 +325,12 @@ const ManageRoom: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentQuestion(quizQuestions[0]);
|
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 = () => {
|
const launchStudentMode = () => {
|
||||||
|
|
@ -311,21 +346,19 @@ const ManageRoom: React.FC = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const launchQuiz = () => {
|
const launchQuiz = () => {
|
||||||
|
setQuizStarted(true);
|
||||||
if (!socket || !formattedRoomName || !quiz?.content || quiz?.content.length === 0) {
|
if (!socket || !formattedRoomName || !quiz?.content || quiz?.content.length === 0) {
|
||||||
// TODO: This error happens when token expires! Need to handle it properly
|
// TODO: This error happens when token expires! Need to handle it properly
|
||||||
console.log(
|
console.log(
|
||||||
`Error launching quiz. socket: ${socket}, roomName: ${formattedRoomName}, quiz: ${quiz}`
|
`Error launching quiz. socket: ${socket}, roomName: ${formattedRoomName}, quiz: ${quiz}`
|
||||||
);
|
);
|
||||||
setQuizStarted(true);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
console.log(`Launching quiz in ${quizMode} mode...`);
|
||||||
switch (quizMode) {
|
switch (quizMode) {
|
||||||
case 'student':
|
case 'student':
|
||||||
setQuizStarted(true);
|
|
||||||
return launchStudentMode();
|
return launchStudentMode();
|
||||||
case 'teacher':
|
case 'teacher':
|
||||||
setQuizStarted(true);
|
|
||||||
return launchTeacherMode();
|
return launchTeacherMode();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -334,7 +367,12 @@ const ManageRoom: React.FC = () => {
|
||||||
if (quiz?.content && quizQuestions) {
|
if (quiz?.content && quizQuestions) {
|
||||||
setCurrentQuestion(quizQuestions[questionIndex]);
|
setCurrentQuestion(quizQuestions[questionIndex]);
|
||||||
if (quizMode === 'teacher') {
|
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>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<div className="roomHeader">
|
<div className="roomHeader">
|
||||||
<DisconnectButton
|
<DisconnectButton
|
||||||
onReturn={handleReturn}
|
onReturn={handleReturn}
|
||||||
|
|
@ -533,7 +570,6 @@ const ManageRoom: React.FC = () => {
|
||||||
<QuestionDisplay
|
<QuestionDisplay
|
||||||
showAnswer={false}
|
showAnswer={false}
|
||||||
question={currentQuestion?.question as Question}
|
question={currentQuestion?.question as Question}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
start();
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,53 @@
|
||||||
const { MongoClient } = require('mongodb');
|
const { MongoClient } = require('mongodb');
|
||||||
const dotenv = require('dotenv')
|
const dotenv = require('dotenv');
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
class DBConnection {
|
class DBConnection {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.mongoURI = process.env.MONGO_URI;
|
this.mongoURI = process.env.MONGO_URI;
|
||||||
this.databaseName = process.env.MONGO_DATABASE;
|
this.databaseName = process.env.MONGO_DATABASE;
|
||||||
|
this.client = null;
|
||||||
this.connection = null;
|
this.connection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Connect to the database, but don't reconnect if already connected
|
||||||
async connect() {
|
async connect() {
|
||||||
const client = new MongoClient(this.mongoURI);
|
if (this.connection) {
|
||||||
this.connection = await client.connect();
|
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() {
|
getConnection() {
|
||||||
if (!this.connection) {
|
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();
|
const instance = new DBConnection();
|
||||||
module.exports = instance;
|
module.exports = instance;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue