mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Ajout de roomID
This commit is contained in:
parent
0febb5a394
commit
369cc218f7
11 changed files with 509 additions and 547 deletions
59
.gitignore
vendored
59
.gitignore
vendored
|
|
@ -129,3 +129,62 @@ dist
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
db-backup/
|
db-backup/
|
||||||
|
|
||||||
|
# Node.js / React (évite de versionner les dépendances)
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
npm-debug.log
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Dossier de sortie du build (React/Vite/Webpack)
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Fichiers environnement (ne pas exposer tes variables sensibles)
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Visual Studio & VS Code (évite les fichiers temporaires et caches)
|
||||||
|
.vscode/
|
||||||
|
.vs/
|
||||||
|
*.code-workspace
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.cache
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Fichiers spécifiques de Visual Studio
|
||||||
|
FileContentIndex/
|
||||||
|
*.vsidx
|
||||||
|
.vsconfig
|
||||||
|
slnx.sqlite
|
||||||
|
VSWorkspaceState.json
|
||||||
|
ProjectSettings.json
|
||||||
|
DocumentLayout*.json
|
||||||
|
|
||||||
|
# Logs et fichiers temporaires
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Ignorer les fichiers de tests/jest
|
||||||
|
coverage/
|
||||||
|
*.test.js
|
||||||
|
*.test.ts
|
||||||
|
*.test.tsx
|
||||||
|
jest.config.js
|
||||||
|
|
||||||
|
# Ignorer les fichiers de dépendances frontend (si utilisés)
|
||||||
|
.bower_components/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Cache TypeScript et fichiers de sortie (évite les fichiers générés)
|
||||||
|
*.tsbuildinfo
|
||||||
|
*.tscache
|
||||||
|
*.d.ts
|
||||||
|
|
|
||||||
300
client/package-lock.json
generated
300
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -18,11 +18,11 @@
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@mui/icons-material": "^6.4.1",
|
"@mui/icons-material": "^6.4.3",
|
||||||
"@mui/lab": "^5.0.0-alpha.153",
|
"@mui/lab": "^5.0.0-alpha.153",
|
||||||
"@mui/material": "^6.1.0",
|
"@mui/material": "^6.4.3",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.7.9",
|
||||||
"dompurify": "^3.2.3",
|
"dompurify": "^3.2.3",
|
||||||
"esbuild": "^0.23.1",
|
"esbuild": "^0.23.1",
|
||||||
"gift-pegjs": "^2.0.0-beta.1",
|
"gift-pegjs": "^2.0.0-beta.1",
|
||||||
|
|
@ -33,9 +33,9 @@
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-modal": "^3.16.1",
|
"react-modal": "^3.16.1",
|
||||||
"react-router-dom": "^6.26.2",
|
"react-router-dom": "^6.29.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"socket.io-client": "^4.7.2",
|
"socket.io-client": "^4.8.1",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"vite-plugin-checker": "^0.8.0"
|
"vite-plugin-checker": "^0.8.0"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
// QuizType.tsx
|
|
||||||
export interface QuizType {
|
export interface QuizType {
|
||||||
_id: string;
|
_id: string;
|
||||||
folderId: string;
|
folderId: string;
|
||||||
|
|
@ -6,6 +5,7 @@ export interface QuizType {
|
||||||
userId: string;
|
userId: string;
|
||||||
title: string;
|
title: string;
|
||||||
content: string[];
|
content: string[];
|
||||||
|
roomId: string;
|
||||||
created_at: Date;
|
created_at: Date;
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ interface Props {
|
||||||
students: StudentType[];
|
students: StudentType[];
|
||||||
launchQuiz: () => void;
|
launchQuiz: () => void;
|
||||||
setQuizMode: (mode: 'student' | 'teacher') => void;
|
setQuizMode: (mode: 'student' | 'teacher') => void;
|
||||||
|
isSocketConnected?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StudentWaitPage: React.FC<Props> = ({ students, launchQuiz, setQuizMode }) => {
|
const StudentWaitPage: React.FC<Props> = ({ students, launchQuiz, setQuizMode, isSocketConnected = true }) => {
|
||||||
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
|
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -25,7 +26,7 @@ const StudentWaitPage: React.FC<Props> = ({ students, launchQuiz, setQuizMode })
|
||||||
fullWidth
|
fullWidth
|
||||||
sx={{ fontWeight: 600, fontSize: 20 }}
|
sx={{ fontWeight: 600, fontSize: 20 }}
|
||||||
>
|
>
|
||||||
Lancer
|
{isSocketConnected ? 'Lancer' : 'En attente de connexion...'}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
// ManageRoom.tsx
|
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs';
|
import { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs';
|
||||||
import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer } from "gift-pegjs/typeGuards";
|
import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer } from "gift-pegjs/typeGuards";
|
||||||
import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
|
import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
|
||||||
// import { QuestionService } from '../../../services/QuestionService';
|
|
||||||
import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService';
|
import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService';
|
||||||
import { QuizType } from '../../../Types/QuizType';
|
import { QuizType } from '../../../Types/QuizType';
|
||||||
import GroupIcon from '@mui/icons-material/Group';
|
import GroupIcon from '@mui/icons-material/Group';
|
||||||
|
|
@ -18,14 +16,12 @@ import LoadingCircle from 'src/components/LoadingCircle/LoadingCircle';
|
||||||
import { Refresh, Error } from '@mui/icons-material';
|
import { Refresh, Error } from '@mui/icons-material';
|
||||||
import StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage';
|
import StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage';
|
||||||
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
|
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
|
||||||
//import QuestionNavigation from 'src/components/QuestionNavigation/QuestionNavigation';
|
|
||||||
import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
||||||
import ApiService from '../../../services/ApiService';
|
import ApiService from '../../../services/ApiService';
|
||||||
import { QuestionType } from 'src/Types/QuestionType';
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
|
||||||
const ManageRoom: React.FC = () => {
|
const ManageRoom: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [roomName, setRoomName] = useState<string>('');
|
|
||||||
const [socket, setSocket] = useState<Socket | null>(null);
|
const [socket, setSocket] = useState<Socket | null>(null);
|
||||||
const [students, setStudents] = useState<StudentType[]>([]);
|
const [students, setStudents] = useState<StudentType[]>([]);
|
||||||
const quizId = useParams<{ id: string }>();
|
const quizId = useParams<{ id: string }>();
|
||||||
|
|
@ -35,30 +31,39 @@ const ManageRoom: React.FC = () => {
|
||||||
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(false);
|
||||||
|
const [isSocketConnected, setIsSocketConnected] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (quizId.id) {
|
if (quizId.id) {
|
||||||
const fetchquiz = async () => {
|
const fetchquiz = async () => {
|
||||||
|
try {
|
||||||
|
const quiz = await ApiService.getQuiz(quizId.id as string);
|
||||||
|
|
||||||
const quiz = await ApiService.getQuiz(quizId.id as string);
|
if (!quiz) {
|
||||||
|
window.alert(`Une erreur est survenue.\n Le quiz ${quizId.id} n'a pas été trouvé\nVeuillez réessayer plus tard`)
|
||||||
|
console.error('Quiz not found for id:', quizId.id);
|
||||||
|
navigate('/teacher/dashboard');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!quiz) {
|
if ((quiz as QuizType).roomId) {
|
||||||
window.alert(`Une erreur est survenue.\n Le quiz ${quizId.id} n'a pas été trouvé\nVeuillez réessayer plus tard`)
|
setQuiz(quiz as QuizType);
|
||||||
console.error('Quiz not found for id:', quizId.id);
|
|
||||||
|
if (!socket) {
|
||||||
|
console.log('Initializing WebSocket connection...');
|
||||||
|
initializeWebSocketConnection((quiz as QuizType).roomId);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('Quiz data is not valid, roomId is missing.');
|
||||||
|
navigate('/teacher/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load quiz:', error);
|
||||||
|
setConnectingError('Erreur de chargement du quiz');
|
||||||
navigate('/teacher/dashboard');
|
navigate('/teacher/dashboard');
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setQuiz(quiz as QuizType);
|
|
||||||
|
|
||||||
if (!socket) {
|
|
||||||
console.log(`no socket in ManageRoom, creating one.`);
|
|
||||||
createWebSocketRoom();
|
|
||||||
}
|
|
||||||
|
|
||||||
// return () => {
|
|
||||||
// webSocketService.disconnect();
|
|
||||||
// };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchquiz();
|
fetchquiz();
|
||||||
|
|
@ -72,74 +77,83 @@ const ManageRoom: React.FC = () => {
|
||||||
}, [quizId]);
|
}, [quizId]);
|
||||||
|
|
||||||
const disconnectWebSocket = () => {
|
const disconnectWebSocket = () => {
|
||||||
if (socket) {
|
if (socket && quiz) {
|
||||||
webSocketService.endQuiz(roomName);
|
webSocketService.endQuiz(quiz.roomId);
|
||||||
webSocketService.disconnect();
|
webSocketService.disconnect();
|
||||||
setSocket(null);
|
setSocket(null);
|
||||||
setQuizQuestions(undefined);
|
setQuizQuestions(undefined);
|
||||||
setCurrentQuestion(undefined);
|
setCurrentQuestion(undefined);
|
||||||
setStudents(new Array<StudentType>());
|
setStudents(new Array<StudentType>());
|
||||||
setRoomName('');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createWebSocketRoom = () => {
|
|
||||||
console.log('Creating WebSocket room...');
|
const initializeWebSocketConnection = (roomId: string) => {
|
||||||
setConnectingError('');
|
console.log('Initializing WebSocket for room:', roomId);
|
||||||
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
|
const socket = webSocketService.connect(ENV_VARIABLES.VITE_BACKEND_SOCKET_URL);
|
||||||
|
console.log('Socket connection status:', webSocketService.isConnected());
|
||||||
|
|
||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
webSocketService.createRoom();
|
console.log('WebSocket connected, joining room:', roomId);
|
||||||
|
setIsSocketConnected(true);
|
||||||
|
webSocketService.joinRoom(roomId, 'Teacher');
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('connect_error', (error) => {
|
socket.on('connect_error', (error) => {
|
||||||
setConnectingError('Erreur lors de la connexion... Veuillez réessayer');
|
setConnectingError('Erreur lors de la connexion... Veuillez réessayer');
|
||||||
console.error('ManageRoom: WebSocket connection error:', error);
|
console.error('Connection error:', error);
|
||||||
});
|
|
||||||
socket.on('create-success', (roomName: string) => {
|
|
||||||
setRoomName(roomName);
|
|
||||||
});
|
|
||||||
socket.on('create-failure', () => {
|
|
||||||
console.log('Error creating room.');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('user-joined', (student: StudentType) => {
|
socket.on('user-joined', (student: StudentType) => {
|
||||||
console.log(`Student joined: name = ${student.name}, id = ${student.id}`);
|
console.log(`Student joined: name = ${student.name}, id = ${student.id}`);
|
||||||
|
|
||||||
setStudents((prevStudents) => [...prevStudents, student]);
|
setStudents((prevStudents) => [...prevStudents, student]);
|
||||||
|
|
||||||
if (quizMode === 'teacher') {
|
if (quiz && socket.connected) {
|
||||||
webSocketService.nextQuestion(roomName, currentQuestion);
|
if (quizMode === 'teacher') {
|
||||||
} else if (quizMode === 'student') {
|
webSocketService.nextQuestion(quiz.roomId, currentQuestion);
|
||||||
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
|
} else if (quizMode === 'student') {
|
||||||
|
webSocketService.launchStudentModeQuiz(quiz.roomId, quizQuestions);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Quiz data is not available or Socket is not connected.");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('join-failure', (message) => {
|
socket.on('join-failure', (message) => {
|
||||||
setConnectingError(message);
|
setConnectingError(message);
|
||||||
setSocket(null);
|
setSocket(null);
|
||||||
|
setIsSocketConnected(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('user-disconnected', (userId: string) => {
|
socket.on('user-disconnected', (userId: string) => {
|
||||||
console.log(`Student left: id = ${userId}`);
|
console.log(`Student left: id = ${userId}`);
|
||||||
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId));
|
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId));
|
||||||
});
|
});
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
console.log('WebSocket disconnected');
|
||||||
|
setIsSocketConnected(false);
|
||||||
|
});
|
||||||
setSocket(socket);
|
setSocket(socket);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// This is here to make sure the correct value is sent when user join
|
// This is here to make sure the correct value is sent when user join
|
||||||
if (socket) {
|
if (socket && quiz?.roomId) {
|
||||||
console.log(`Listening for user-joined in room ${roomName}`);
|
console.log(`Listening for user-joined in room ${quiz.roomId}`);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
socket.on('user-joined', (_student: StudentType) => {
|
socket.on('user-joined', (_student: StudentType) => {
|
||||||
if (quizMode === 'teacher') {
|
if (quizMode === 'teacher') {
|
||||||
webSocketService.nextQuestion(roomName, currentQuestion);
|
webSocketService.nextQuestion(quiz.roomId, currentQuestion);
|
||||||
|
|
||||||
} else if (quizMode === 'student') {
|
} else if (quizMode === 'student') {
|
||||||
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
|
webSocketService.launchStudentModeQuiz(quiz.roomId, quizQuestions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (socket) {
|
if (socket) {
|
||||||
// handle the case where user submits an answer
|
// handle the case where user submits an answer
|
||||||
console.log(`Listening for submit-answer-room in room ${roomName}`);
|
console.log(`Listening for submit-answer-room in room ${quiz?.roomId}`);
|
||||||
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
|
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
|
||||||
const { answer, idQuestion, idUser, username } = answerData;
|
const { answer, idQuestion, idUser, username } = answerData;
|
||||||
console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`);
|
console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`);
|
||||||
|
|
@ -189,70 +203,6 @@ const ManageRoom: React.FC = () => {
|
||||||
|
|
||||||
}, [socket, currentQuestion, quizQuestions]);
|
}, [socket, currentQuestion, quizQuestions]);
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// if (socket) {
|
|
||||||
// const submitAnswerHandler = (answerData: answerSubmissionType) => {
|
|
||||||
// const { answer, idQuestion, username } = answerData;
|
|
||||||
// console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`);
|
|
||||||
|
|
||||||
// // print the list of current student names
|
|
||||||
// console.log('Current students:');
|
|
||||||
// students.forEach((student) => {
|
|
||||||
// console.log(student.name);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Update the students state using the functional form of setStudents
|
|
||||||
// setStudents((prevStudents) => {
|
|
||||||
// let foundStudent = false;
|
|
||||||
// const updatedStudents = prevStudents.map((student) => {
|
|
||||||
// if (student.id === username) {
|
|
||||||
// foundStudent = true;
|
|
||||||
// const updatedAnswers = student.answers.map((ans) => {
|
|
||||||
// const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!), idQuestion };
|
|
||||||
// console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`);
|
|
||||||
// return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans);
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// return { ...student, answers: updatedAnswers };
|
|
||||||
// }
|
|
||||||
// return student;
|
|
||||||
// });
|
|
||||||
// if (!foundStudent) {
|
|
||||||
// console.log(`Student ${username} not found in the list of students in LiveResults`);
|
|
||||||
// }
|
|
||||||
// return updatedStudents;
|
|
||||||
// });
|
|
||||||
|
|
||||||
|
|
||||||
// // make a copy of the students array so we can update it
|
|
||||||
// // const updatedStudents = [...students];
|
|
||||||
|
|
||||||
// // const student = updatedStudents.find((student) => student.id === idUser);
|
|
||||||
// // if (!student) {
|
|
||||||
// // // this is a bad thing if an answer was submitted but the student isn't in the list
|
|
||||||
// // console.log(`Student ${idUser} not found in the list of students in LiveResults`);
|
|
||||||
// // return;
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// // const isCorrect = checkIfIsCorrect(answer, idQuestion);
|
|
||||||
// // const newAnswer: Answer = { answer, isCorrect, idQuestion };
|
|
||||||
// // student.answers.push(newAnswer);
|
|
||||||
// // // print list of answers
|
|
||||||
// // console.log('Answers:');
|
|
||||||
// // student.answers.forEach((answer) => {
|
|
||||||
// // console.log(answer.answer);
|
|
||||||
// // });
|
|
||||||
// // setStudents(updatedStudents); // update the state
|
|
||||||
// };
|
|
||||||
|
|
||||||
// socket.on('submit-answer', submitAnswerHandler);
|
|
||||||
// return () => {
|
|
||||||
// socket.off('submit-answer');
|
|
||||||
// };
|
|
||||||
// }
|
|
||||||
// }, [socket]);
|
|
||||||
|
|
||||||
|
|
||||||
const nextQuestion = () => {
|
const nextQuestion = () => {
|
||||||
if (!quizQuestions || !currentQuestion || !quiz?.content) return;
|
if (!quizQuestions || !currentQuestion || !quiz?.content) return;
|
||||||
|
|
||||||
|
|
@ -261,7 +211,7 @@ 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, quizQuestions[nextQuestionIndex]);
|
webSocketService.nextQuestion(quiz.roomId, quizQuestions[nextQuestionIndex]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const previousQuestion = () => {
|
const previousQuestion = () => {
|
||||||
|
|
@ -271,7 +221,7 @@ const ManageRoom: React.FC = () => {
|
||||||
|
|
||||||
if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return;
|
if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return;
|
||||||
setCurrentQuestion(quizQuestions[prevQuestionIndex]);
|
setCurrentQuestion(quizQuestions[prevQuestionIndex]);
|
||||||
webSocketService.nextQuestion(roomName, quizQuestions[prevQuestionIndex]);
|
webSocketService.nextQuestion(quiz.roomId, quizQuestions[prevQuestionIndex]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const initializeQuizQuestion = () => {
|
const initializeQuizQuestion = () => {
|
||||||
|
|
@ -291,37 +241,37 @@ const ManageRoom: React.FC = () => {
|
||||||
|
|
||||||
const launchTeacherMode = () => {
|
const launchTeacherMode = () => {
|
||||||
const quizQuestions = initializeQuizQuestion();
|
const quizQuestions = initializeQuizQuestion();
|
||||||
console.log('launchTeacherMode - quizQuestions:', quizQuestions);
|
if (quizQuestions && quiz?.roomId) {
|
||||||
|
setCurrentQuestion(quizQuestions[0]);
|
||||||
if (!quizQuestions) {
|
webSocketService.nextQuestion(quiz.roomId, quizQuestions[0]);
|
||||||
console.log('Error launching quiz (launchTeacherMode). No questions found.');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentQuestion(quizQuestions[0]);
|
|
||||||
webSocketService.nextQuestion(roomName, quizQuestions[0]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const launchStudentMode = () => {
|
const launchStudentMode = () => {
|
||||||
const quizQuestions = initializeQuizQuestion();
|
const quizQuestions = initializeQuizQuestion();
|
||||||
console.log('launchStudentMode - quizQuestions:', quizQuestions);
|
if (quizQuestions && quiz?.roomId) {
|
||||||
|
setQuizQuestions(quizQuestions);
|
||||||
if (!quizQuestions) {
|
webSocketService.launchStudentModeQuiz(quiz.roomId, quizQuestions);
|
||||||
console.log('Error launching quiz (launchStudentMode). No questions found.');
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
setQuizQuestions(quizQuestions);
|
|
||||||
webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const launchQuiz = () => {
|
const launchQuiz = async () => {
|
||||||
if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) {
|
if (!isSocketConnected) {
|
||||||
// TODO: This error happens when token expires! Need to handle it properly
|
console.log("Waiting for socket connection...");
|
||||||
console.log(`Error launching quiz. socket: ${socket}, roomName: ${roomName}, quiz: ${quiz}`);
|
window.alert("En attente de la connexion au serveur... Veuillez réessayer dans quelques secondes.");
|
||||||
setQuizStarted(true);
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!quiz?.roomId) {
|
||||||
|
console.error("Room ID is missing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!quiz?.content || quiz.content.length === 0) {
|
||||||
|
console.error("Quiz content is missing or empty.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (quizMode) {
|
switch (quizMode) {
|
||||||
case 'student':
|
case 'student':
|
||||||
setQuizStarted(true);
|
setQuizStarted(true);
|
||||||
|
|
@ -329,7 +279,9 @@ const ManageRoom: React.FC = () => {
|
||||||
case 'teacher':
|
case 'teacher':
|
||||||
setQuizStarted(true);
|
setQuizStarted(true);
|
||||||
return launchTeacherMode();
|
return launchTeacherMode();
|
||||||
|
default:
|
||||||
|
console.error("Invalid quiz mode.");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -338,7 +290,7 @@ const ManageRoom: React.FC = () => {
|
||||||
setCurrentQuestion(quizQuestions[questionIndex]);
|
setCurrentQuestion(quizQuestions[questionIndex]);
|
||||||
|
|
||||||
if (quizMode === 'teacher') {
|
if (quizMode === 'teacher') {
|
||||||
webSocketService.nextQuestion(roomName, quizQuestions[questionIndex]);
|
webSocketService.nextQuestion(quiz.roomId, quizQuestions[questionIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -403,11 +355,11 @@ const ManageRoom: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!roomName) {
|
if (!quiz || !quiz?.roomId) {
|
||||||
return (
|
return (
|
||||||
<div className="center">
|
<div className="center">
|
||||||
{!connectingError ? (
|
{!connectingError ? (
|
||||||
<LoadingCircle text="Veuillez attendre la connexion au serveur..." />
|
<LoadingCircle text="Chargement du quiz..." />
|
||||||
) : (
|
) : (
|
||||||
<div className="center-v-align">
|
<div className="center-v-align">
|
||||||
<Error sx={{ padding: 0 }} />
|
<Error sx={{ padding: 0 }} />
|
||||||
|
|
@ -415,7 +367,13 @@ const ManageRoom: React.FC = () => {
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
startIcon={<Refresh />}
|
startIcon={<Refresh />}
|
||||||
onClick={createWebSocketRoom}
|
onClick={() => {
|
||||||
|
if (quiz?.roomId) {
|
||||||
|
initializeWebSocketConnection(quiz.roomId);
|
||||||
|
} else {
|
||||||
|
console.error("Room ID is not available");
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Reconnecter
|
Reconnecter
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -439,7 +397,7 @@ const ManageRoom: React.FC = () => {
|
||||||
|
|
||||||
<div className='headerContent' style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
<div className='headerContent' style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
|
||||||
<div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
|
<div style={{ flex: 1, display: 'flex', justifyContent: 'center' }}>
|
||||||
<div className='title'>Salle: {roomName}</div>
|
<div className='title'>Salle: {quiz?.roomId}</div>
|
||||||
</div>
|
</div>
|
||||||
{quizStarted && (
|
{quizStarted && (
|
||||||
<div className='userCount subtitle smallText' style={{ display: 'flex', alignItems: 'center' }}>
|
<div className='userCount subtitle smallText' style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
|
@ -526,6 +484,7 @@ const ManageRoom: React.FC = () => {
|
||||||
students={students}
|
students={students}
|
||||||
launchQuiz={launchQuiz}
|
launchQuiz={launchQuiz}
|
||||||
setQuizMode={setQuizMode}
|
setQuizMode={setQuizMode}
|
||||||
|
isSocketConnected={isSocketConnected}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
@ -535,4 +494,4 @@ const ManageRoom: React.FC = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ManageRoom;
|
export default ManageRoom;
|
||||||
|
|
@ -13,169 +13,185 @@ class QuizController {
|
||||||
create = async (req, res, next) => {
|
create = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { title, content, folderId } = req.body;
|
const { title, content, folderId } = req.body;
|
||||||
|
|
||||||
if (!title || !content || !folderId) {
|
if (!title || !content || !folderId) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this folder mine
|
|
||||||
const owner = await this.folders.getOwner(folderId);
|
const owner = await this.folders.getOwner(folderId);
|
||||||
|
|
||||||
if (owner != req.user.userId) {
|
if (owner != req.user.userId) {
|
||||||
throw new AppError(FOLDER_NOT_FOUND);
|
throw new AppError(FOLDER_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.quizzes.create(title, content, folderId, req.user.userId);
|
const roomId = this.generateRoomId();
|
||||||
|
|
||||||
|
const result = await this.quizzes.create(title, content, folderId, req.user.userId, roomId);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new AppError(QUIZ_ALREADY_EXISTS);
|
throw new AppError(QUIZ_ALREADY_EXISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message: 'Quiz créé avec succès.'
|
message: 'Quiz créé avec succès.',
|
||||||
|
roomId: roomId,
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
getRoomID = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { quizId } = req.params;
|
||||||
|
|
||||||
|
if (!quizId) {
|
||||||
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
|
}
|
||||||
|
|
||||||
|
const roomId = await this.quizzes.getRoomID(quizId);
|
||||||
|
|
||||||
|
if (!roomId) {
|
||||||
|
throw new AppError(QUIZ_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
roomId: roomId
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
get = async (req, res, next) => {
|
get = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { quizId } = req.params;
|
const { quizId } = req.params;
|
||||||
|
|
||||||
if (!quizId) {
|
if (!quizId) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await this.quizzes.getContent(quizId);
|
const content = await this.quizzes.getContent(quizId);
|
||||||
|
|
||||||
if (!content) {
|
if (!content) {
|
||||||
throw new AppError(GETTING_QUIZ_ERROR);
|
throw new AppError(GETTING_QUIZ_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this quiz mine
|
|
||||||
if (content.userId != req.user.userId) {
|
if (content.userId != req.user.userId) {
|
||||||
throw new AppError(QUIZ_NOT_FOUND);
|
throw new AppError(QUIZ_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
data: content
|
data: content
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
delete = async (req, res, next) => {
|
delete = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { quizId } = req.params;
|
const { quizId } = req.params;
|
||||||
|
|
||||||
if (!quizId) {
|
if (!quizId) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this quiz mine
|
|
||||||
const owner = await this.quizzes.getOwner(quizId);
|
const owner = await this.quizzes.getOwner(quizId);
|
||||||
|
|
||||||
if (owner != req.user.userId) {
|
if (owner != req.user.userId) {
|
||||||
throw new AppError(QUIZ_NOT_FOUND);
|
throw new AppError(QUIZ_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.quizzes.delete(quizId);
|
const result = await this.quizzes.delete(quizId);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new AppError(DELETE_QUIZ_ERROR);
|
throw new AppError(DELETE_QUIZ_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message: 'Quiz supprimé avec succès.'
|
message: 'Quiz supprimé avec succès.'
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
update = async (req, res, next) => {
|
update = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { quizId, newTitle, newContent } = req.body;
|
const { quizId, newTitle, newContent } = req.body;
|
||||||
|
|
||||||
if (!newTitle || !newContent || !quizId) {
|
if (!newTitle || !newContent || !quizId) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this quiz mine
|
|
||||||
const owner = await this.quizzes.getOwner(quizId);
|
const owner = await this.quizzes.getOwner(quizId);
|
||||||
|
|
||||||
if (owner != req.user.userId) {
|
if (owner != req.user.userId) {
|
||||||
throw new AppError(QUIZ_NOT_FOUND);
|
throw new AppError(QUIZ_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.quizzes.update(quizId, newTitle, newContent);
|
const result = await this.quizzes.update(quizId, newTitle, newContent);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new AppError(UPDATE_QUIZ_ERROR);
|
throw new AppError(UPDATE_QUIZ_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message: 'Quiz mis à jours avec succès.'
|
message: 'Quiz mis à jours avec succès.'
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
move = async (req, res, next) => {
|
move = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { quizId, newFolderId } = req.body;
|
const { quizId, newFolderId } = req.body;
|
||||||
|
|
||||||
if (!quizId || !newFolderId) {
|
if (!quizId || !newFolderId) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this quiz mine
|
|
||||||
const quizOwner = await this.quizzes.getOwner(quizId);
|
const quizOwner = await this.quizzes.getOwner(quizId);
|
||||||
|
|
||||||
if (quizOwner != req.user.userId) {
|
if (quizOwner != req.user.userId) {
|
||||||
throw new AppError(QUIZ_NOT_FOUND);
|
throw new AppError(QUIZ_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this folder mine
|
|
||||||
const folderOwner = await this.folders.getOwner(newFolderId);
|
const folderOwner = await this.folders.getOwner(newFolderId);
|
||||||
|
|
||||||
if (folderOwner != req.user.userId) {
|
if (folderOwner != req.user.userId) {
|
||||||
throw new AppError(FOLDER_NOT_FOUND);
|
throw new AppError(FOLDER_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.quizzes.move(quizId, newFolderId);
|
const result = await this.quizzes.move(quizId, newFolderId);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new AppError(MOVING_QUIZ_ERROR);
|
throw new AppError(MOVING_QUIZ_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message: 'Utilisateur déplacé avec succès.'
|
message: 'Utilisateur déplacé avec succès.'
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
copy = async (req, _res, _next) => {
|
copy = async (req, _res, _next) => {
|
||||||
const { quizId, newTitle, folderId } = req.body;
|
const { quizId, newTitle, folderId } = req.body;
|
||||||
|
|
||||||
if (!quizId || !newTitle || !folderId) {
|
if (!quizId || !newTitle || !folderId) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new AppError(NOT_IMPLEMENTED);
|
throw new AppError(NOT_IMPLEMENTED);
|
||||||
// const { quizId } = req.params;
|
// const { quizId } = req.params;
|
||||||
// const { newUserId } = req.body;
|
// const { newUserId } = req.body;
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
// //Trouver le quiz a dupliquer
|
// //Trouver le quiz a dupliquer
|
||||||
// const conn = db.getConnection();
|
// const conn = db.getConnection();
|
||||||
|
|
@ -189,7 +205,7 @@ class QuizController {
|
||||||
// //Ajout du duplicata
|
// //Ajout du duplicata
|
||||||
// await conn.collection('quiz').insertOne({ ...quiztoduplicate, userId: ObjectId.createFromHexString(newUserId) });
|
// await conn.collection('quiz').insertOne({ ...quiztoduplicate, userId: ObjectId.createFromHexString(newUserId) });
|
||||||
// res.json(Response.ok("Dossier dupliqué avec succès pour un autre utilisateur"));
|
// res.json(Response.ok("Dossier dupliqué avec succès pour un autre utilisateur"));
|
||||||
|
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// if (error.message.startsWith("Quiz non trouvé")) {
|
// if (error.message.startsWith("Quiz non trouvé")) {
|
||||||
// return res.status(404).json(Response.badRequest(error.message));
|
// return res.status(404).json(Response.badRequest(error.message));
|
||||||
|
|
@ -197,18 +213,18 @@ class QuizController {
|
||||||
// res.status(500).json(Response.serverError(error.message));
|
// res.status(500).json(Response.serverError(error.message));
|
||||||
// }
|
// }
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteQuizzesByFolderId = async (req, res, next) => {
|
deleteQuizzesByFolderId = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { folderId } = req.body;
|
const { folderId } = req.body;
|
||||||
|
|
||||||
if (!folderId) {
|
if (!folderId) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the method from the Quiz model to delete quizzes by folder ID
|
// Call the method from the Quiz model to delete quizzes by folder ID
|
||||||
await this.quizzes.deleteQuizzesByFolderId(folderId);
|
await this.quizzes.deleteQuizzesByFolderId(folderId);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message: 'Quizzes deleted successfully.'
|
message: 'Quizzes deleted successfully.'
|
||||||
});
|
});
|
||||||
|
|
@ -216,10 +232,10 @@ class QuizController {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
duplicate = async (req, res, next) => {
|
duplicate = async (req, res, next) => {
|
||||||
const { quizId } = req.body;
|
const { quizId } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newQuizId = await this.quizzes.duplicate(quizId, req.user.userId);
|
const newQuizId = await this.quizzes.duplicate(quizId, req.user.userId);
|
||||||
res.status(200).json({ success: true, newQuizId });
|
res.status(200).json({ success: true, newQuizId });
|
||||||
|
|
@ -227,7 +243,7 @@ class QuizController {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
quizExists = async (title, userId) => {
|
quizExists = async (title, userId) => {
|
||||||
try {
|
try {
|
||||||
const existingFile = await this.quizzes.quizExists(title, userId);
|
const existingFile = await this.quizzes.quizExists(title, userId);
|
||||||
|
|
@ -236,82 +252,90 @@ class QuizController {
|
||||||
throw new AppError(GETTING_QUIZ_ERROR);
|
throw new AppError(GETTING_QUIZ_ERROR);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
share = async (req, res, next) => {
|
share = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { quizId, email } = req.body;
|
const { quizId, email } = req.body;
|
||||||
|
|
||||||
if (!quizId || !email) {
|
if (!quizId || !email) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = `${process.env.FRONTEND_URL}/teacher/Share/${quizId}`;
|
const link = `${process.env.FRONTEND_URL}/teacher/Share/${quizId}`;
|
||||||
|
|
||||||
emailer.quizShare(email, link);
|
emailer.quizShare(email, link);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message: 'Quiz partagé avec succès.'
|
message: 'Quiz partagé avec succès.'
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getShare = async (req, res, next) => {
|
getShare = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { quizId } = req.params;
|
const { quizId } = req.params;
|
||||||
|
|
||||||
if (!quizId) {
|
if (!quizId) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await this.quizzes.getContent(quizId);
|
const content = await this.quizzes.getContent(quizId);
|
||||||
|
|
||||||
if (!content) {
|
if (!content) {
|
||||||
throw new AppError(GETTING_QUIZ_ERROR);
|
throw new AppError(GETTING_QUIZ_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
data: content.title
|
data: content.title
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
receiveShare = async (req, res, next) => {
|
receiveShare = async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const { quizId, folderId } = req.body;
|
const { quizId, folderId } = req.body;
|
||||||
|
|
||||||
if (!quizId || !folderId) {
|
if (!quizId || !folderId) {
|
||||||
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
throw new AppError(MISSING_REQUIRED_PARAMETER);
|
||||||
}
|
}
|
||||||
|
|
||||||
const folderOwner = await this.folders.getOwner(folderId);
|
const folderOwner = await this.folders.getOwner(folderId);
|
||||||
if (folderOwner != req.user.userId) {
|
if (folderOwner != req.user.userId) {
|
||||||
throw new AppError(FOLDER_NOT_FOUND);
|
throw new AppError(FOLDER_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await this.quizzes.getContent(quizId);
|
const content = await this.quizzes.getContent(quizId);
|
||||||
if (!content) {
|
if (!content) {
|
||||||
throw new AppError(GETTING_QUIZ_ERROR);
|
throw new AppError(GETTING_QUIZ_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await this.quizzes.create(content.title, content.content, folderId, req.user.userId);
|
const result = await this.quizzes.create(content.title, content.content, folderId, req.user.userId);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new AppError(QUIZ_ALREADY_EXISTS);
|
throw new AppError(QUIZ_ALREADY_EXISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
message: 'Quiz partagé reçu.'
|
message: 'Quiz partagé reçu.'
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
generateRoomId(length = 6) {
|
||||||
|
const characters = "0123456789";
|
||||||
|
let result = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = QuizController;
|
module.exports = QuizController;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
class AppError extends Error {
|
class AppError extends Error {
|
||||||
constructor (errorCode) {
|
constructor(errorCode = { message: "Something went wrong", code: 500 }) {
|
||||||
super(errorCode.message)
|
super(errorCode.message);
|
||||||
this.statusCode = errorCode.code;
|
this.statusCode = errorCode.code || 500;
|
||||||
this.isOperational = true; // Optional: to distinguish operational errors from programming errors
|
this.isOperational = true;
|
||||||
|
Error.captureStackTrace(this, this.constructor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = AppError;
|
module.exports = AppError;
|
||||||
|
|
@ -2,20 +2,21 @@ const { ObjectId } = require('mongodb');
|
||||||
const { generateUniqueTitle } = require('./utils');
|
const { generateUniqueTitle } = require('./utils');
|
||||||
|
|
||||||
class Quiz {
|
class Quiz {
|
||||||
|
|
||||||
constructor(db) {
|
constructor(db) {
|
||||||
// console.log("Quiz constructor: db", db)
|
|
||||||
this.db = db;
|
this.db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(title, content, folderId, userId) {
|
|
||||||
// console.log(`quizzes: create title: ${title}, folderId: ${folderId}, userId: ${userId}`);
|
async create(title, content, folderId, userId, roomId) {
|
||||||
await this.db.connect()
|
await this.db.connect()
|
||||||
const conn = this.db.getConnection();
|
const conn = this.db.getConnection();
|
||||||
|
|
||||||
const quizCollection = conn.collection('files');
|
const quizCollection = conn.collection('files');
|
||||||
|
|
||||||
const existingQuiz = await quizCollection.findOne({ title: title, folderId: folderId, userId: userId })
|
const existingQuiz = await quizCollection.findOne({
|
||||||
|
title: title,
|
||||||
|
folderId: folderId,
|
||||||
|
userId: userId
|
||||||
|
});
|
||||||
|
|
||||||
if (existingQuiz) {
|
if (existingQuiz) {
|
||||||
throw new Error(`Quiz already exists with title: ${title}, folderId: ${folderId}, userId: ${userId}`);
|
throw new Error(`Quiz already exists with title: ${title}, folderId: ${folderId}, userId: ${userId}`);
|
||||||
|
|
@ -26,13 +27,12 @@ class Quiz {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
title: title,
|
title: title,
|
||||||
content: content,
|
content: content,
|
||||||
|
roomId: roomId,
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
updated_at: new Date()
|
updated_at: new Date()
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await quizCollection.insertOne(newQuiz);
|
const result = await quizCollection.insertOne(newQuiz);
|
||||||
// console.log("quizzes: create insertOne result", result);
|
|
||||||
|
|
||||||
return result.insertedId;
|
return result.insertedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,6 +58,14 @@ class Quiz {
|
||||||
return quiz;
|
return quiz;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRoomID(quizId) {
|
||||||
|
await this.db.connect()
|
||||||
|
const conn = this.db.getConnection();
|
||||||
|
const quizCollection = conn.collection('files');
|
||||||
|
const quiz = await quizCollection.findOne({ _id: ObjectId.createFromHexString(quizId) });
|
||||||
|
return quiz.roomId;
|
||||||
|
}
|
||||||
|
|
||||||
async delete(quizId) {
|
async delete(quizId) {
|
||||||
await this.db.connect()
|
await this.db.connect()
|
||||||
const conn = this.db.getConnection();
|
const conn = this.db.getConnection();
|
||||||
|
|
@ -89,12 +97,12 @@ class Quiz {
|
||||||
|
|
||||||
const result = await quizCollection.updateOne(
|
const result = await quizCollection.updateOne(
|
||||||
{ _id: ObjectId.createFromHexString(quizId) },
|
{ _id: ObjectId.createFromHexString(quizId) },
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
title: newTitle,
|
title: newTitle,
|
||||||
content: newContent,
|
content: newContent,
|
||||||
updated_at: new Date()
|
updated_at: new Date()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -108,7 +116,7 @@ class Quiz {
|
||||||
const quizCollection = conn.collection('files');
|
const quizCollection = conn.collection('files');
|
||||||
|
|
||||||
const result = await quizCollection.updateOne(
|
const result = await quizCollection.updateOne(
|
||||||
{ _id: ObjectId.createFromHexString(quizId) },
|
{ _id: ObjectId.createFromHexString(quizId) },
|
||||||
{ $set: { folderId: newFolderId } }
|
{ $set: { folderId: newFolderId } }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -143,13 +151,12 @@ class Quiz {
|
||||||
async quizExists(title, userId) {
|
async quizExists(title, userId) {
|
||||||
await this.db.connect();
|
await this.db.connect();
|
||||||
const conn = this.db.getConnection();
|
const conn = this.db.getConnection();
|
||||||
|
|
||||||
const filesCollection = conn.collection('files');
|
const filesCollection = conn.collection('files');
|
||||||
const existingFolder = await filesCollection.findOne({ title: title, userId: userId });
|
const existingFolder = await filesCollection.findOne({ title: title, userId: userId });
|
||||||
|
|
||||||
return existingFolder !== null;
|
return existingFolder !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Quiz;
|
module.exports = Quiz;
|
||||||
|
|
@ -10,6 +10,7 @@ if (!quizzes) {
|
||||||
|
|
||||||
router.post("/create", jwt.authenticate, asyncHandler(quizzes.create));
|
router.post("/create", jwt.authenticate, asyncHandler(quizzes.create));
|
||||||
router.get("/get/:quizId", jwt.authenticate, asyncHandler(asyncHandler(quizzes.get)));
|
router.get("/get/:quizId", jwt.authenticate, asyncHandler(asyncHandler(quizzes.get)));
|
||||||
|
router.get('/getRoomID/:quizId', jwt.authenticate, asyncHandler(quizzes.getRoomID));
|
||||||
router.delete("/delete/:quizId", jwt.authenticate, asyncHandler(quizzes.delete));
|
router.delete("/delete/:quizId", jwt.authenticate, asyncHandler(quizzes.delete));
|
||||||
router.put("/update", jwt.authenticate, asyncHandler(quizzes.update));
|
router.put("/update", jwt.authenticate, asyncHandler(quizzes.update));
|
||||||
router.put("/move", jwt.authenticate, asyncHandler(quizzes.move));
|
router.put("/move", jwt.authenticate, asyncHandler(quizzes.move));
|
||||||
|
|
@ -20,4 +21,4 @@ router.put("/Share", jwt.authenticate, asyncHandler(quizzes.share));
|
||||||
router.get("/getShare/:quizId", jwt.authenticate, asyncHandler(quizzes.getShare));
|
router.get("/getShare/:quizId", jwt.authenticate, asyncHandler(quizzes.getShare));
|
||||||
router.post("/receiveShare", jwt.authenticate, asyncHandler(quizzes.receiveShare));
|
router.post("/receiveShare", jwt.authenticate, asyncHandler(quizzes.receiveShare));
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
@ -2,124 +2,92 @@ const MAX_USERS_PER_ROOM = 60;
|
||||||
const MAX_TOTAL_CONNECTIONS = 2000;
|
const MAX_TOTAL_CONNECTIONS = 2000;
|
||||||
|
|
||||||
const setupWebsocket = (io) => {
|
const setupWebsocket = (io) => {
|
||||||
let totalConnections = 0;
|
let totalConnections = 0;
|
||||||
|
|
||||||
io.on("connection", (socket) => {
|
io.on("connection", (socket) => {
|
||||||
if (totalConnections >= MAX_TOTAL_CONNECTIONS) {
|
if (totalConnections >= MAX_TOTAL_CONNECTIONS) {
|
||||||
console.log("Connection limit reached. Disconnecting client.");
|
console.log("Connection limit reached. Disconnecting client.");
|
||||||
socket.emit(
|
socket.emit(
|
||||||
"join-failure",
|
"join-failure",
|
||||||
"Le nombre maximum de connexions a été atteint"
|
"Le nombre maximum de connexions a été atteint"
|
||||||
);
|
);
|
||||||
socket.disconnect(true);
|
socket.disconnect(true);
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
totalConnections++;
|
|
||||||
console.log(
|
|
||||||
"A user connected:",
|
|
||||||
socket.id,
|
|
||||||
"| Total connections:",
|
|
||||||
totalConnections
|
|
||||||
);
|
|
||||||
|
|
||||||
socket.on("create-room", (sentRoomName) => {
|
|
||||||
if (sentRoomName) {
|
|
||||||
const roomName = sentRoomName.toUpperCase();
|
|
||||||
if (!io.sockets.adapter.rooms.get(roomName)) {
|
|
||||||
socket.join(roomName);
|
|
||||||
socket.emit("create-success", roomName);
|
|
||||||
} else {
|
|
||||||
socket.emit("create-failure");
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
const roomName = generateRoomName();
|
totalConnections++;
|
||||||
if (!io.sockets.adapter.rooms.get(roomName)) {
|
console.log(
|
||||||
socket.join(roomName);
|
"A user connected:",
|
||||||
socket.emit("create-success", roomName);
|
socket.id,
|
||||||
} else {
|
"| Total connections:",
|
||||||
socket.emit("create-failure");
|
totalConnections
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
socket.on("join-room", ({ enteredRoomName, username }) => {
|
||||||
|
if (io.sockets.adapter.rooms.has(enteredRoomName)) {
|
||||||
|
const clientsInRoom =
|
||||||
|
io.sockets.adapter.rooms.get(enteredRoomName).size;
|
||||||
|
|
||||||
|
if (clientsInRoom <= MAX_USERS_PER_ROOM) {
|
||||||
|
const newStudent = {
|
||||||
|
id: socket.id,
|
||||||
|
name: username,
|
||||||
|
answers: [],
|
||||||
|
};
|
||||||
|
socket.join(enteredRoomName);
|
||||||
|
socket
|
||||||
|
.to(enteredRoomName)
|
||||||
|
.emit("user-joined", newStudent);
|
||||||
|
socket.emit("join-success");
|
||||||
|
} else {
|
||||||
|
socket.emit("join-failure", "La salle est remplie");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
socket.emit("join-failure", "Le nom de la salle n'existe pas");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("next-question", ({ roomName, question }) => {
|
||||||
|
socket.to(roomName).emit("next-question", question);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("launch-student-mode", ({ roomName, questions }) => {
|
||||||
|
socket.to(roomName).emit("launch-student-mode", questions);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("end-quiz", ({ roomName }) => {
|
||||||
|
socket.to(roomName).emit("end-quiz");
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("message", (data) => {
|
||||||
|
console.log("Received message from", socket.id, ":", data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("disconnect", () => {
|
||||||
|
totalConnections--;
|
||||||
|
console.log(
|
||||||
|
"A user disconnected:",
|
||||||
|
socket.id,
|
||||||
|
"| Total connections:",
|
||||||
|
totalConnections
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const [room] of io.sockets.adapter.rooms) {
|
||||||
|
if (room !== socket.id) {
|
||||||
|
io.to(room).emit("user-disconnected", socket.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("submit-answer", ({ roomName, username, answer, idQuestion }) => {
|
||||||
|
socket.to(roomName).emit("submit-answer-room", {
|
||||||
|
idUser: socket.id,
|
||||||
|
username,
|
||||||
|
answer,
|
||||||
|
idQuestion,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on("join-room", ({ enteredRoomName, username }) => {
|
|
||||||
if (io.sockets.adapter.rooms.has(enteredRoomName)) {
|
|
||||||
const clientsInRoom =
|
|
||||||
io.sockets.adapter.rooms.get(enteredRoomName).size;
|
|
||||||
|
|
||||||
if (clientsInRoom <= MAX_USERS_PER_ROOM) {
|
|
||||||
const newStudent = {
|
|
||||||
id: socket.id,
|
|
||||||
name: username,
|
|
||||||
answers: [],
|
|
||||||
};
|
|
||||||
socket.join(enteredRoomName);
|
|
||||||
socket
|
|
||||||
.to(enteredRoomName)
|
|
||||||
.emit("user-joined", newStudent);
|
|
||||||
socket.emit("join-success");
|
|
||||||
} else {
|
|
||||||
socket.emit("join-failure", "La salle est remplie");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
socket.emit("join-failure", "Le nom de la salle n'existe pas");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("next-question", ({ roomName, question }) => {
|
|
||||||
// console.log("next-question", roomName, question);
|
|
||||||
socket.to(roomName).emit("next-question", question);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("launch-student-mode", ({ roomName, questions }) => {
|
|
||||||
socket.to(roomName).emit("launch-student-mode", questions);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("end-quiz", ({ roomName }) => {
|
|
||||||
socket.to(roomName).emit("end-quiz");
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("message", (data) => {
|
|
||||||
console.log("Received message from", socket.id, ":", data);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("disconnect", () => {
|
|
||||||
totalConnections--;
|
|
||||||
console.log(
|
|
||||||
"A user disconnected:",
|
|
||||||
socket.id,
|
|
||||||
"| Total connections:",
|
|
||||||
totalConnections
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const [room] of io.sockets.adapter.rooms) {
|
|
||||||
if (room !== socket.id) {
|
|
||||||
io.to(room).emit("user-disconnected", socket.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("submit-answer", ({ roomName, username, answer, idQuestion }) => {
|
|
||||||
socket.to(roomName).emit("submit-answer-room", {
|
|
||||||
idUser: socket.id,
|
|
||||||
username,
|
|
||||||
answer,
|
|
||||||
idQuestion,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const generateRoomName = (length = 6) => {
|
|
||||||
const characters = "0123456789";
|
|
||||||
let result = "";
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += characters.charAt(
|
|
||||||
Math.floor(Math.random() * characters.length)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { setupWebsocket };
|
module.exports = { setupWebsocket };
|
||||||
Loading…
Reference in a new issue