From 9527ae880907bf7ac355c7c59c994362a4c28325 Mon Sep 17 00:00:00 2001 From: lakshyashishir Date: Mon, 17 Feb 2025 02:25:54 +0530 Subject: [PATCH 1/2] add route to get all responses of a user --- .../checkQuiz/response/getAllResponses.ts | 16 ++- .../checkQuiz/response/getUserResponse.ts | 111 ++++++++++++++++++ src/controllers/checkQuiz/response/index.ts | 1 + src/routers/checkQuiz/index.ts | 2 + src/routers/checkQuiz/user.ts | 10 ++ 5 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 src/controllers/checkQuiz/response/getUserResponse.ts create mode 100644 src/routers/checkQuiz/user.ts diff --git a/src/controllers/checkQuiz/response/getAllResponses.ts b/src/controllers/checkQuiz/response/getAllResponses.ts index e5bacd3..4e42069 100644 --- a/src/controllers/checkQuiz/response/getAllResponses.ts +++ b/src/controllers/checkQuiz/response/getAllResponses.ts @@ -17,6 +17,7 @@ const getAllResponses = async (req: getAllResponsesRequest, res: Response) => { const question = await QuestionModel.findById(questionId) if (!question) { sendInvalidInputResponse(res) + return } const responses = await ResponseModel.find({ questionId, quizId }).populate({ path: 'userId', @@ -30,21 +31,24 @@ const getAllResponses = async (req: getAllResponsesRequest, res: Response) => { } }) - const firstResponse = await ResponseModel.findById(responsesToSend[0].responseId).populate({ - path: 'checkedBy', - select: 'personalDetails.name personalDetails.emailAdd', - }) + let firstResponse = null + if (responsesToSend.length > 0) { + firstResponse = await ResponseModel.findById(responsesToSend[0].responseId).populate({ + path: 'checkedBy', + select: 'personalDetails.name personalDetails.emailAdd', + }) + } return res.status(200).json({ responses: responsesToSend || [], - firstResponse: { + firstResponse: firstResponse ? { user: firstResponse?.userId, selectedOptionId: firstResponse?.selectedOptionId, subjectiveAnswer: firstResponse?.subjectiveAnswer, marksAwarded: firstResponse?.marksAwarded, status: firstResponse?.status, checkedBy: firstResponse?.checkedBy, - }, + } : null, // TODO: to remove, Frontend Issue questionId: question?._id, }) diff --git a/src/controllers/checkQuiz/response/getUserResponse.ts b/src/controllers/checkQuiz/response/getUserResponse.ts new file mode 100644 index 0000000..db64cb8 --- /dev/null +++ b/src/controllers/checkQuiz/response/getUserResponse.ts @@ -0,0 +1,111 @@ +import QuizModel from '@models/quiz/quizModel' +import ResponseModel from '@models/response/responseModel' +import UserModel from '@models/user/userModel' +import sendFailureResponse from '@utils/failureResponse' +import sendInvalidInputResponse from '@utils/invalidInputResponse' +import { Request, Response } from 'express' +import { isValidObjectId } from 'mongoose' + +interface GetUserQuizResponsesRequest extends Request { + params: { + quizId: string + userId: string + } +} + +interface FormattedResponse { + questionId: any + sectionDetails: string + question: string + marks: { + awarded?: number + max: number + } + response: { + options?: string[] + text?: string + status: string + } +} + +const getUserQuizResponses = async (req: GetUserQuizResponsesRequest, res: Response) => { + const { quizId, userId } = req.params + + if (!isValidObjectId(quizId) || !isValidObjectId(userId)) { + return sendInvalidInputResponse(res) + } + + try { + const [quiz, user] = await Promise.all([ + QuizModel.findById(quizId), + UserModel.findById(userId).select('personalDetails') + ]) + + if (!quiz || !user) return sendInvalidInputResponse(res) + + const participant = quiz.participants?.find(p => p.userId?.toString() === userId) + if (!participant) return sendInvalidInputResponse(res) + + const responses = await ResponseModel.find({ quizId, userId }) + .populate<{ questionId: any }>('questionId', 'description type maxMarks') + + const questionSectionMap = new Map() + quiz.sections?.forEach((section, sectionIndex) => { + section.questions?.forEach((questionId, questionIndex) => { + questionSectionMap.set(questionId.toString(), { + sectionNumber: sectionIndex + 1, + sectionName: section.name, + questionNumber: questionIndex + 1 + }) + }) + }) + + const formattedResponses: FormattedResponse[] = responses.map(response => { + const sectionInfo = questionSectionMap.get(response.questionId._id.toString()) + return { + questionId: response.questionId._id, + sectionDetails: sectionInfo + ? `${sectionInfo.sectionName || `Section ${sectionInfo.sectionNumber}`} - Question ${sectionInfo.questionNumber}` + : 'General Questions', + question: response.questionId.description, + marks: { + awarded: response.marksAwarded, + max: response.questionId.maxMarks + }, + response: { + options: Array.isArray(response.selectedOptionId) ? response.selectedOptionId : [], + text: response.subjectiveAnswer, + status: response.status + } + } + }) + + return res.status(200).json({ + userDetails: { + id: user._id, + name: user.personalDetails?.name, + email: user.personalDetails?.emailAdd, + contact: user.personalDetails?.phoneNo + }, + quizDetails: { + id: quiz._id, + name: quiz.quizMetadata?.name + }, + registration: participant.registrationData?.customFields || [], + evaluation: { + totalMarks: formattedResponses.reduce((sum, r) => sum + (r.marks.awarded || 0), 0), + status: participant.submitted ? 'Submitted' : 'Pending' + }, + responses: formattedResponses + }) + + } catch (error: unknown) { + return sendFailureResponse({ + res, + error, + messageToSend: 'Failed to get user quiz responses' + }) + } +} + +export default getUserQuizResponses \ No newline at end of file diff --git a/src/controllers/checkQuiz/response/index.ts b/src/controllers/checkQuiz/response/index.ts index abe9622..9a38ed6 100644 --- a/src/controllers/checkQuiz/response/index.ts +++ b/src/controllers/checkQuiz/response/index.ts @@ -1,3 +1,4 @@ export { default as checkResponse } from './checkResponse' export { default as getResponse } from './getResponse' export { default as getAllResponses } from './getAllResponses' +export { default as getUserResponses } from './getUserResponse' \ No newline at end of file diff --git a/src/routers/checkQuiz/index.ts b/src/routers/checkQuiz/index.ts index 758ce19..04094a1 100644 --- a/src/routers/checkQuiz/index.ts +++ b/src/routers/checkQuiz/index.ts @@ -2,6 +2,7 @@ import express from 'express' import * as checkQuizController from '@controllers/checkQuiz' import questionRouter from './question' import responseRouter from './response' +import userRouter from './user' import hasEditAccess from '@utils/hasEditAccess' import isQuizAdmin from '@utils/isQuizAdmin' import isOnboard from '@utils/isOnboard' @@ -10,6 +11,7 @@ const router = express.Router() router.use('/question', questionRouter) router.use('/response', responseRouter) +router.use('/', userRouter) router.get('/dashboard/:quizId/:sectionIndex/', isOnboard, hasEditAccess, checkQuizController.getCheckingDashboard) router.get('/dashboard/:quizId/', isOnboard, hasEditAccess, checkQuizController.getCheckingDashboard) diff --git a/src/routers/checkQuiz/user.ts b/src/routers/checkQuiz/user.ts new file mode 100644 index 0000000..a638650 --- /dev/null +++ b/src/routers/checkQuiz/user.ts @@ -0,0 +1,10 @@ +import express from 'express' +import * as responseController from '@controllers/checkQuiz/response' +import hasEditAccess from '@utils/hasEditAccess' +import isOnboard from '@utils/isOnboard' + +const router = express.Router() + +router.get('/:quizId/user/:userId',isOnboard, hasEditAccess, responseController.getUserResponses) + +export default router From 7e06bb29a4b055ab034b79f71722de65915bd406 Mon Sep 17 00:00:00 2001 From: lakshyashishir Date: Mon, 17 Feb 2025 03:34:06 +0530 Subject: [PATCH 2/2] add search --- src/controllers/checkQuiz/response/index.ts | 3 +- .../checkQuiz/response/searchParticipant.ts | 49 +++++++++++++++++++ src/routers/checkQuiz/user.ts | 3 +- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/controllers/checkQuiz/response/searchParticipant.ts diff --git a/src/controllers/checkQuiz/response/index.ts b/src/controllers/checkQuiz/response/index.ts index 9a38ed6..00b328c 100644 --- a/src/controllers/checkQuiz/response/index.ts +++ b/src/controllers/checkQuiz/response/index.ts @@ -1,4 +1,5 @@ export { default as checkResponse } from './checkResponse' export { default as getResponse } from './getResponse' export { default as getAllResponses } from './getAllResponses' -export { default as getUserResponses } from './getUserResponse' \ No newline at end of file +export { default as getUserResponses } from './getUserResponse' +export { default as searchParticipant } from './searchParticipant' \ No newline at end of file diff --git a/src/controllers/checkQuiz/response/searchParticipant.ts b/src/controllers/checkQuiz/response/searchParticipant.ts new file mode 100644 index 0000000..401448b --- /dev/null +++ b/src/controllers/checkQuiz/response/searchParticipant.ts @@ -0,0 +1,49 @@ +import QuizModel from '@models/quiz/quizModel' +import sendFailureResponse from '@utils/failureResponse' +import sendInvalidInputResponse from '@utils/invalidInputResponse' +import { Request, Response } from 'express' +import { isValidObjectId } from 'mongoose' + +const searchParticipant = async (req: Request, res: Response) => { + const { quizId } = req.params + const { query } = req.query + + if (!query || !isValidObjectId(quizId)) { + return sendInvalidInputResponse(res) + } + + try { + const quiz = await QuizModel.findById(quizId) + .populate({ + path: 'participants.userId', + select: 'personalDetails', + match: { + $or: [ + { 'personalDetails.emailAdd': query }, + { 'personalDetails.phoneNo': query } + ] + } + }) + + if (!quiz) return sendInvalidInputResponse(res) + + const participant = quiz.participants?.find(p => p.userId) + if (!participant) { + return res.status(404).json({ message: 'Participant not found' }) + } + + return res.status(200).json({ + userId: participant.userId?._id, + quizId: quiz._id + }) + + } catch (error: unknown) { + return sendFailureResponse({ + res, + error, + messageToSend: 'Search failed' + }) + } + } + + export default searchParticipant \ No newline at end of file diff --git a/src/routers/checkQuiz/user.ts b/src/routers/checkQuiz/user.ts index a638650..f2c4857 100644 --- a/src/routers/checkQuiz/user.ts +++ b/src/routers/checkQuiz/user.ts @@ -5,6 +5,7 @@ import isOnboard from '@utils/isOnboard' const router = express.Router() -router.get('/:quizId/user/:userId',isOnboard, hasEditAccess, responseController.getUserResponses) +router.get('/:quizId/user/:userId',isOnboard, hasEditAccess,responseController.getUserResponses) +router.get('/:quizId/search',isOnboard, hasEditAccess, responseController.searchParticipant) export default router