diff --git a/src/controllers/checkQuiz/generateLeaderBoard.ts b/src/controllers/checkQuiz/generateLeaderBoard.ts index 74ef144..8f074d9 100644 --- a/src/controllers/checkQuiz/generateLeaderBoard.ts +++ b/src/controllers/checkQuiz/generateLeaderBoard.ts @@ -1,4 +1,4 @@ -import LeaderboardModel from '@models/leaderboard/leaderboardModel'; +import LeaderboardModel from '@models/leaderboard/sectionLeaderboardModel'; import ResponseModel from '@models/response/responseModel'; import UserModel from '@models/user/userModel'; import sendFailureResponse from '@utils/failureResponse'; @@ -10,6 +10,7 @@ import { ResponseStatus } from 'types'; interface generateLeaderBoardRequest extends Request { params: { quizId: string; + sectionIndex?: string; }; query: { search?: string; @@ -19,6 +20,8 @@ interface generateLeaderBoardRequest extends Request { interface Participant { userId: Types.ObjectId; marks: number; + sectionMarks: number[]; + totalMarks: number; questionsAttempted: number; questionsChecked: number; } @@ -38,24 +41,50 @@ function prefixSearch(searchQuery: string, name: string, phoneNumber: string) { const generateLeaderBoard = async (req: generateLeaderBoardRequest, res: Response) => { const { quizId } = req.params; const searchQuery = req.query.search as string; - + const sectionIndex = req.params.sectionIndex; + + let sectionIndexNum: number | null = null; + if (sectionIndex && sectionIndex !== 'null') { + sectionIndexNum = parseInt(sectionIndex, 10); + if (isNaN(sectionIndexNum)) { + return sendFailureResponse({ + res, + error: new Error('Invalid section index'), + messageToSend: 'Invalid section index', + }); + } + } try { const quiz = await getQuiz(quizId); const participants: Participant[] = []; await Promise.all( - quiz?.participants?.map(async (participant) => { - const responses = await ResponseModel.find({ quizId: quizId, userId: participant.userId }); - - let score = 0; + quiz.participants?.map(async (participant) => { + let totalMarks = 0; + const sectionMarks: number[] = new Array(quiz.sections.length).fill(0); let questionsAttempted = 0; let questionsChecked = 0; - responses.forEach((response) => { - score += response.marksAwarded || 0; - questionsAttempted++; - questionsChecked += response.status === ResponseStatus.checked ? 1 : 0; - }); + await Promise.all( + quiz.sections.map(async (section, sectionIdx) => { + await Promise.all( + section.questions.map(async (question) => { + const response = await ResponseModel.findOne({ + quizId, + questionId: question._id, + userId: participant.userId, + }); + + if (response) { + sectionMarks[sectionIdx] += response.marksAwarded || 0; + totalMarks += response.marksAwarded || 0; + questionsAttempted++; + questionsChecked += response.status === ResponseStatus.checked ? 1 : 0; + } + }), + ); + }), + ); const user = await UserModel.findById(participant.userId); @@ -67,7 +96,9 @@ const generateLeaderBoard = async (req: generateLeaderBoardRequest, res: Respons ) { const leaderboardEntry: Participant = { userId: participant.userId, - marks: score, + sectionMarks: sectionMarks, + totalMarks: totalMarks, + marks: sectionIndexNum == null ? totalMarks : sectionMarks[sectionIndexNum], questionsAttempted: questionsAttempted, questionsChecked: questionsChecked, }; @@ -79,6 +110,7 @@ const generateLeaderBoard = async (req: generateLeaderBoardRequest, res: Respons ); const sortedParticipants = participants.sort((a, b) => b.marks - a.marks); + console.log(sortedParticipants); await LeaderboardModel.findOneAndUpdate( { quizId: quizId }, { @@ -101,4 +133,4 @@ const generateLeaderBoard = async (req: generateLeaderBoardRequest, res: Respons } }; -export default generateLeaderBoard; +export default generateLeaderBoard; \ No newline at end of file diff --git a/src/controllers/checkQuiz/generateSectionLeaderboard.ts b/src/controllers/checkQuiz/generateSectionLeaderboard.ts index 674ca06..e32949a 100644 --- a/src/controllers/checkQuiz/generateSectionLeaderboard.ts +++ b/src/controllers/checkQuiz/generateSectionLeaderboard.ts @@ -1,129 +1,129 @@ -import SectionLeaderboardModel from "@models/leaderboard/sectionLeaderboardModel"; -import ResponseModel from "@models/response/responseModel"; -import UserModel from '@models/user/userModel'; -import sendFailureResponse from "@utils/failureResponse"; -import getQuiz from "@utils/getQuiz"; -import sendInvalidInputResponse from "@utils/invalidInputResponse"; -import { Request, Response } from "express"; -import { Types } from "mongoose"; -import { ResponseStatus } from "types"; - -interface generateSectionLeaderboardRequest extends Request { - params: { - quizId: string; - sectionIndex: string; - }; - query: { - search: string; - searchQuery?: string; - }; -} - -interface Participant { - userId: Types.ObjectId; - marks: number; - questionsAttempted: number; - questionsChecked: number; -} - -function prefixSearch(searchQuery: string, name: string, phoneNumber: string) { - if (!searchQuery || searchQuery === '') return true; - if (/^\d+$/.test(searchQuery)) { - return phoneNumber.startsWith(searchQuery); - } - if (/^[a-zA-Z]+$/.test(searchQuery)) { - return name.toLowerCase().startsWith(searchQuery.toLowerCase()); - } - return false; -} - -const generateSectionLeaderBoard = async (req: generateSectionLeaderboardRequest, res: Response) => { - const { quizId } = req.params; - const sectionIndex = parseInt(req.params.sectionIndex, 10); - const searchQuery = req.query.search as string; - - try { - const quiz = await getQuiz(quizId); - if (!quiz || !quiz?.sections || sectionIndex >= quiz.sections.length) { - return sendInvalidInputResponse(res); - } - - const sectionQuestions = quiz.sections[sectionIndex]?.questions || []; - const participants: Participant[] = []; - - for (const participant of quiz.participants || []) { - const { userId } = participant; - - let marks = 0; - let questionsAttempted = 0; - let questionsChecked = 0; - - for (const question of sectionQuestions) { - const response = await ResponseModel.findOne({ - quizId, - questionId: question._id, - userId - }); - - if (response) { - marks += response.marksAwarded || 0; - questionsAttempted++; - questionsChecked += response.status === ResponseStatus.checked ? 1 : 0; - } - } - - if (Types.ObjectId.isValid(userId)) { - participants.push({ - userId: userId, - marks, - questionsAttempted, - questionsChecked, - }); - } - } - - const filteredParticipantsPromises = participants.map(async (participant) => { - const user = await UserModel.findById(participant.userId); - if (user) { - const name = user.personalDetails?.name?.toLowerCase() || ""; - const phoneNumber = user.personalDetails?.phoneNo || ""; - if (prefixSearch(searchQuery, name, phoneNumber)) { - return participant; - } - } - return null; - }); - - const filteredParticipants = (await Promise.all(filteredParticipantsPromises)).filter((p): p is Participant => p !== null); - - const sortedParticipants = filteredParticipants.sort((a, b) => b.marks - a.marks); - - await SectionLeaderboardModel.findOneAndUpdate( - { - quizId, - sectionIndex - }, - { - quizId, - sectionIndex, - participants: sortedParticipants, - }, - { - upsert: true, - }, - ); - - return res.status(200).json({ - message: `Leaderboard for section ${sectionIndex} generated successfully`, - leaderboard: sortedParticipants, - }); - } catch (error: unknown) { - return sendFailureResponse({ - res, - error, - messageToSend: 'Failed to generate leaderboard', - }); - } -}; - -export default generateSectionLeaderBoard; +// import SectionLeaderboardModel from "@models/leaderboard/sectionLeaderboardModel"; +// import ResponseModel from "@models/response/responseModel"; +// import UserModel from '@models/user/userModel'; +// import sendFailureResponse from "@utils/failureResponse"; +// import getQuiz from "@utils/getQuiz"; +// import sendInvalidInputResponse from "@utils/invalidInputResponse"; +// import { Request, Response } from "express"; +// import { Types } from "mongoose"; +// import { ResponseStatus } from "types"; + +// interface generateSectionLeaderboardRequest extends Request { +// params: { +// quizId: string; +// sectionIndex: string; +// }; +// query: { +// search: string; +// searchQuery?: string; +// }; +// } + +// interface Participant { +// userId: Types.ObjectId; +// marks: number; +// questionsAttempted: number; +// questionsChecked: number; +// } + +// function prefixSearch(searchQuery: string, name: string, phoneNumber: string) { +// if (!searchQuery || searchQuery === '') return true; +// if (/^\d+$/.test(searchQuery)) { +// return phoneNumber.startsWith(searchQuery); +// } +// if (/^[a-zA-Z]+$/.test(searchQuery)) { +// return name.toLowerCase().startsWith(searchQuery.toLowerCase()); +// } +// return false; +// } + +// const generateSectionLeaderBoard = async (req: generateSectionLeaderboardRequest, res: Response) => { +// const { quizId } = req.params; +// const sectionIndex = parseInt(req.params.sectionIndex, 10); +// const searchQuery = req.query.search as string; + +// try { +// const quiz = await getQuiz(quizId); +// if (!quiz || !quiz?.sections || sectionIndex >= quiz.sections.length) { +// return sendInvalidInputResponse(res); +// } + +// const sectionQuestions = quiz.sections[sectionIndex]?.questions || []; +// const participants: Participant[] = []; + +// for (const participant of quiz.participants || []) { +// const { userId } = participant; + +// let marks = 0; +// let questionsAttempted = 0; +// let questionsChecked = 0; + +// for (const question of sectionQuestions) { +// const response = await ResponseModel.findOne({ +// quizId, +// questionId: question._id, +// userId +// }); + +// if (response) { +// marks += response.marksAwarded || 0; +// questionsAttempted++; +// questionsChecked += response.status === ResponseStatus.checked ? 1 : 0; +// } +// } + +// if (Types.ObjectId.isValid(userId)) { +// participants.push({ +// userId: userId, +// marks, +// questionsAttempted, +// questionsChecked, +// }); +// } +// } + +// const filteredParticipantsPromises = participants.map(async (participant) => { +// const user = await UserModel.findById(participant.userId); +// if (user) { +// const name = user.personalDetails?.name?.toLowerCase() || ""; +// const phoneNumber = user.personalDetails?.phoneNo || ""; +// if (prefixSearch(searchQuery, name, phoneNumber)) { +// return participant; +// } +// } +// return null; +// }); + +// const filteredParticipants = (await Promise.all(filteredParticipantsPromises)).filter((p): p is Participant => p !== null); + +// const sortedParticipants = filteredParticipants.sort((a, b) => b.marks - a.marks); + +// await SectionLeaderboardModel.findOneAndUpdate( +// { +// quizId, +// sectionIndex +// }, +// { +// quizId, +// sectionIndex, +// participants: sortedParticipants, +// }, +// { +// upsert: true, +// }, +// ); + +// return res.status(200).json({ +// message: `Leaderboard for section ${sectionIndex} generated successfully`, +// leaderboard: sortedParticipants, +// }); +// } catch (error: unknown) { +// return sendFailureResponse({ +// res, +// error, +// messageToSend: 'Failed to generate leaderboard', +// }); +// } +// }; + +// export default generateSectionLeaderBoard; diff --git a/src/controllers/checkQuiz/getCheckingDashboard.ts b/src/controllers/checkQuiz/getCheckingDashboard.ts index 788530e..f4678b3 100644 --- a/src/controllers/checkQuiz/getCheckingDashboard.ts +++ b/src/controllers/checkQuiz/getCheckingDashboard.ts @@ -2,7 +2,7 @@ import { Request, Response } from 'express'; import QuizModel from '@models/quiz/quizModel'; import sendInvalidInputResponse from '@utils/invalidInputResponse'; import sendFailureResponse from '@utils/failureResponse'; -import LeaderboardModel from '@models/leaderboard/leaderboardModel'; +import LeaderboardModel from '@models/leaderboard/sectionLeaderboardModel'; import UserModel from '@models/user/userModel'; import { Types } from 'mongoose'; diff --git a/src/models/leaderboard/leaderboardModel.ts b/src/models/leaderboard/leaderboardModel.ts index f6338d2..bf3164d 100644 --- a/src/models/leaderboard/leaderboardModel.ts +++ b/src/models/leaderboard/leaderboardModel.ts @@ -1,7 +1,7 @@ -import mongoose from 'mongoose' -import { ModelNames, ILeaderboard } from 'types' -import leaderboardSchema from './leaderboardSchema' +// import mongoose from 'mongoose' +// import { ModelNames, ILeaderboard } from 'types' +// import leaderboardSchema from './leaderboardSchema' -const LeaderboardModel = mongoose.model(ModelNames.Leaderboard, leaderboardSchema) +// const LeaderboardModel = mongoose.model(ModelNames.Leaderboard, leaderboardSchema) -export default LeaderboardModel +// export default LeaderboardModel diff --git a/src/models/leaderboard/leaderboardSchema.ts b/src/models/leaderboard/leaderboardSchema.ts index bda6dc1..99b6060 100644 --- a/src/models/leaderboard/leaderboardSchema.ts +++ b/src/models/leaderboard/leaderboardSchema.ts @@ -1,33 +1,33 @@ -import { Schema } from 'mongoose' -import { ModelNames, ILeaderboard } from 'types' +// import { Schema } from 'mongoose' +// import { ModelNames, ILeaderboard } from 'types' -const leaderboardSchema = new Schema({ - quizId: { - type: Schema.Types.ObjectId, - ref: ModelNames.Quiz, - required: true, - }, - participants: [ - { - userId: { - type: Schema.Types.ObjectId, - ref: ModelNames.User, - required: true, - }, - marks: { - type: Number, - required: true, - }, - questionsAttempted: { - type: Number, - required: true, - }, - questionsChecked: { - type: Number, - required: true, - }, - }, - ], -}) +// const leaderboardSchema = new Schema({ +// quizId: { +// type: Schema.Types.ObjectId, +// ref: ModelNames.Quiz, +// required: true, +// }, +// participants: [ +// { +// userId: { +// type: Schema.Types.ObjectId, +// ref: ModelNames.User, +// required: true, +// }, +// marks: { +// type: Number, +// required: true, +// }, +// questionsAttempted: { +// type: Number, +// required: true, +// }, +// questionsChecked: { +// type: Number, +// required: true, +// }, +// }, +// ], +// }) -export default leaderboardSchema +// export default leaderboardSchema diff --git a/src/models/leaderboard/sectionLeaderboardSchema.ts b/src/models/leaderboard/sectionLeaderboardSchema.ts index 12f69a7..2370279 100644 --- a/src/models/leaderboard/sectionLeaderboardSchema.ts +++ b/src/models/leaderboard/sectionLeaderboardSchema.ts @@ -1,17 +1,12 @@ -import { Schema } from "mongoose" -import { ModelNames, ISectionLeaderboard } from "types" +import { Schema } from "mongoose"; +import { ModelNames, ISectionLeaderboard } from "types"; -//find a better way to do this const sectionLeaderboardSchema = new Schema({ quizId: { type: Schema.Types.ObjectId, ref: ModelNames.Quiz, required: true, }, - sectionIndex: { - type:Number, - required:true - }, participants: [ { userId: { @@ -19,7 +14,13 @@ const sectionLeaderboardSchema = new Schema({ ref: ModelNames.User, required: true, }, - marks: { + sectionMarks: [ + { + type: Number, + required: true, + }, + ], + totalMarks: { type: Number, required: true, }, @@ -32,7 +33,7 @@ const sectionLeaderboardSchema = new Schema({ required: true, }, }, - ], -}) + ], +}); -export default sectionLeaderboardSchema \ No newline at end of file +export default sectionLeaderboardSchema; diff --git a/src/routers/checkQuiz/index.ts b/src/routers/checkQuiz/index.ts index dfa2dce..a3350b6 100644 --- a/src/routers/checkQuiz/index.ts +++ b/src/routers/checkQuiz/index.ts @@ -20,8 +20,8 @@ router.patch('/leaderboard/:quizId/:searchQuery', isOnboard, isQuizAdmin, checkQ router.get('/sectionLeaderboard/:quizId/:sectionIndex', isOnboard, hasEditAccess, checkQuizController.getCheckingSection) router.get('/sectionLeaderboard/:quizId/:sectionIndex/:searchQuery', isOnboard, hasEditAccess, checkQuizController.getCheckingSection) -router.patch('/generateSectionLeaderboard/:quizId/:sectionIndex', isOnboard, isQuizAdmin, checkQuizController.generateSectionLeaderboard); -router.patch('/generateSectionLeaderboard/:quizId/:sectionIndex/:searchQuery', isOnboard, isQuizAdmin, checkQuizController.generateSectionLeaderboard); +router.patch('/generateSectionLeaderboard/:quizId/:sectionIndex', isOnboard, isQuizAdmin, checkQuizController.generateLeaderBoard); +router.patch('/generateSectionLeaderboard/:quizId/:sectionIndex/:searchQuery', isOnboard, isQuizAdmin, checkQuizController.generateLeaderBoard); export default router