Skip to content

Commit

Permalink
feat: add ability to do custom validation
Browse files Browse the repository at this point in the history
  • Loading branch information
BoonHianLim committed Feb 24, 2024
1 parent a426e95 commit f40d7ef
Show file tree
Hide file tree
Showing 10 changed files with 190 additions and 95 deletions.
36 changes: 19 additions & 17 deletions apps/challenges/src/controllers/questionaire.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Request, Response } from "express";
const asyncHandler = require('express-async-handler');
import Question from '../model/question';
import Question, { CreateQuestionReq } from '../model/question';
import Submission from '../model/submission';
import Season from "../model/season";
import { isValidObjectId } from "../utils/db";
import QuestionService from "../service/questionService";
import { isValidCreateQuestionRequest } from "../utils/validator";
import { z } from "zod";

// @desc Get questions
// @route GET /api/question
Expand Down Expand Up @@ -35,7 +38,7 @@ const getQuestion = asyncHandler(async (req: Request, res: Response) => {
}

try {
const question = await Question.findById(questionId);
const question = await QuestionService.getQuestionByID(questionId);

if (!question) {
return res.status(404).json({ message: 'Question not found' });
Expand All @@ -54,21 +57,20 @@ const getQuestion = asyncHandler(async (req: Request, res: Response) => {
// @access Private
const setQuestion = asyncHandler(async (req: Request, res: Response) => {
try {
const question = await Question.create({
question_no: req.body.question_no,
question_title: req.body.question_title,
question_desc: req.body.question_desc,
question_date: req.body.question_date,
seasonID: req.body.season_id,
expiry: req.body.expiry,
points: req.body.points,
answer: req.body.answer,
});

res.status(201).json(question);
} catch (error) {
if ((error as Error).name === 'ValidationError') {
return res.status(400).json({ message: (error as Error).message });
const question = isValidCreateQuestionRequest.parse(req.body);

const createQuestionReq: CreateQuestionReq = {
...question,
question_date: new Date(question.question_date),
expiry: new Date(question.expiry),
}

const resp = await QuestionService.createQuestion(createQuestionReq);

res.status(resp.status).json(resp);
} catch (err) {
if (err instanceof z.ZodError) {
return res.status(400).json({ message: (err as Error).message });
}

res.status(500).json({ message: 'Internal Server Error' });
Expand Down
72 changes: 6 additions & 66 deletions apps/challenges/src/controllers/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,96 +43,36 @@ const getSubmission = asyncHandler(async (req: Request, res: Response) => {
})

// @desc Set submission
// @route POST /api/submission
// @route POST /api/submission/
// @access Private

const setSubmission = asyncHandler(async (req: Request, res: Response) => {
const questionId = req.body.question;
const questionID = req.body.question;

if (!isValidObjectId(questionId)) {
if (!isValidObjectId(questionID)) {
return res.status(400).json({ message: 'Invalid question ID' });
}

try {
const submission = isValidCreateSubmissionRequest.parse(req.body);
const createSubmissionReq = {
question: new mongoose.Types.ObjectId(submission.question),
user: new mongoose.Types.ObjectId(submission.user),
question: new mongoose.Types.ObjectId(submission.question),
answer: submission.answer
};

await SubmissionService.createSubmission(createSubmissionReq);

res.status(201).json({ message: 'Answer submitted' });

} catch (error) {
res.status(500).json({ message: 'Internal Server Error' });
}
})

// @desc Update submission
// @route PUT /api/submission/:id
// @access Private
const updateSubmission = asyncHandler(async (req: Request, res: Response) => {
const submissionId = req.params.id;

if (!isValidObjectId(submissionId)) {
return res.status(400).json({ message: 'Invalid submission ID' });
}

try {
const submission = await Submission.findById(submissionId);

if (!submission) {
return res.status(404).json({ message: 'Submission not found' });
}

const updatedSubmission = await Submission.findByIdAndUpdate(submissionId, req.body, { new: true });
const resp = await SubmissionService.createSubmission(createSubmissionReq);

// Re-evaluate the points awarded

res.status(resp.status).json(resp);


res.status(200).json(updatedSubmission);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal Server Error' });
}
})

// @desc Delete submission
// @route DELETE /api/submission/:id
// @access Private
const deleteSubmission = asyncHandler(async (req: Request, res: Response) => {
const submissionId = req.params.id;

if (!isValidObjectId(submissionId)) {
return res.status(400).json({ message: 'Invalid submission ID' });
}

try {
const submission = await Submission.findById(submissionId);

if (!submission) {
return res.status(404).json({ message: 'Submission not found' });
}

await submission.remove()

res.status(200).json({message: 'Submission deleted'});
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal Server Error' });
}
})


const SubmissionController = {
getSubmissions,
getSubmission,
setSubmission,
updateSubmission,
deleteSubmission
};

export { SubmissionController as default };
33 changes: 28 additions & 5 deletions apps/challenges/src/model/question.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,41 @@
import mongoose, { Schema, Document } from 'mongoose';

export interface CreateQuestionReq {
question_no: string;
question_title: string;
question_desc: string;
question_date: Date;
season_id: string;
expiry: Date;
points: number;
validation_function: string;
}

export interface GetUserQuestionResp {
id: string;
question_no: string;
question_title: string;
question_desc: string;
question_date: Date;
seasonID: string;
expiry: Date;
points: number;
}

export interface QuestionModel {
_id: mongoose.Types.ObjectId;
question_no: string;
question_title: string;
question_desc: string;
question_date: Date;
seasonID: mongoose.Types.ObjectId;
expiry: Date;
points: number;
answer: string;
submissions: Array<mongoose.Types.ObjectId>;
submissions_count: number;
correct_submissions_count: number;
active: boolean;
validation_function : string;
}

const questionSchema: Schema<QuestionModel> = new Schema({
Expand Down Expand Up @@ -45,10 +68,6 @@ const questionSchema: Schema<QuestionModel> = new Schema({
type: Number,
required: [true, 'Please add a points value']
},
answer: {
type: String,
required: [true, 'Please add an answer']
},
submissions: {
type: [mongoose.Types.ObjectId],
ref: 'Submission'
Expand All @@ -65,6 +84,10 @@ const questionSchema: Schema<QuestionModel> = new Schema({
type: Boolean,
default: true
},
validation_function: {
type: String,
required: [true, 'Please add a validation function']
}
}, {
timestamps: true
});
Expand Down
29 changes: 28 additions & 1 deletion apps/challenges/src/repo/questionRepo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Question, { QuestionModel } from "../model/question"
import Question, { CreateQuestionReq, QuestionModel } from "../model/question"
import mongoose from 'mongoose';

const getQuestionByID = async (
Expand All @@ -10,6 +10,32 @@ const getQuestionByID = async (
return question;
}

const createQuestionByReq = async (
req: CreateQuestionReq
): Promise<QuestionModel | null> => {
const questionModel = {
_id: new mongoose.Types.ObjectId(),
question_no: req.question_no,
question_title: req.question_title,
question_desc: req.question_desc,
question_date: req.question_date,
seasonID: new mongoose.Types.ObjectId(req.season_id),
expiry: req.expiry,
points: req.points,
submissions: [],
submissions_count: 0,
correct_submissions_count: 0,
active: true,
validation_function: req.validation_function
}

const question = await Question.create(questionModel);

await question.save();

return question;
}

const updateQuestionByID = async (
questionID: mongoose.Types.ObjectId,
questionModel: QuestionModel
Expand Down Expand Up @@ -39,6 +65,7 @@ const updateQuestionSubmissions = async (

const QuestionRepo = {
getQuestionByID,
createQuestionByReq,
updateQuestionByID,
updateQuestionSubmissions
}
Expand Down
1 change: 0 additions & 1 deletion apps/challenges/src/routes/questionaire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import QuestionController from "../controllers/questionaire";
import SubmissionController from "../controllers/submission";

router.route('/').get(QuestionController.getQuestions).post(QuestionController.setQuestion);
router.route('/active').get(QuestionController.getActiveQuestions);
router.route('/:id').get(QuestionController.getQuestion).delete(QuestionController.deleteQuestion).put(QuestionController.updateQuestion);

export { router as default };
2 changes: 1 addition & 1 deletion apps/challenges/src/routes/submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ const router = express.Router();
import SubmissionController from "../controllers/submission";

router.route('/').get(SubmissionController.getSubmissions).post(SubmissionController.setSubmission);
router.route('/:id').get(SubmissionController.getSubmission).delete(SubmissionController.deleteSubmission).put(SubmissionController.updateSubmission);
router.route('/:id').get(SubmissionController.getSubmission);

export { router as default };
27 changes: 26 additions & 1 deletion apps/challenges/src/service/questionService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import mongoose from 'mongoose';
import QuestionRepo from '../repo/questionRepo';
import { CreateQuestionReq } from '../model/question';
import ValidationService from './validationService';
import { GeneralResp } from '../model/response';

const getQuestionByID = async(
questionID: string,
Expand All @@ -12,6 +15,27 @@ const getQuestionByID = async(
return question;
}

const createQuestion = async (
req: CreateQuestionReq,
): Promise<GeneralResp> => {
if (!ValidationService.getValidationFunction(req.validation_function)){
console.log('Invalid validation function');
return {
status: 400,
message: 'Invalid validation function',
data: null,
};
}

const question = await QuestionRepo.createQuestionByReq(req);

return {
status: 201,
message: 'Question created',
data: question,
};
}

const updateQuestionSubmissions = async(
questionID: string,
submissionID: string,
Expand All @@ -28,7 +52,8 @@ const updateQuestionSubmissions = async(

const QuestionService = {
getQuestionByID,
updateQuestionSubmissions
updateQuestionSubmissions,
createQuestion
}

export { QuestionService as default }
16 changes: 13 additions & 3 deletions apps/challenges/src/service/submissionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CreateSubmissionReq, SubmissionModel } from "../model/submission";
import SubmissionRepo from "../repo/submissionRepo";
import QuestionService from "./questionService";
import { GeneralResp } from "../model/response";
import ValidationService from "./validationService";

const createSubmission = async (
submission: CreateSubmissionReq
Expand All @@ -28,22 +29,29 @@ const createSubmission = async (
}
}

let isCorrect = false;
try {
isCorrect = await ValidationService.validateAnswer(submission.question.toString(), submission.answer);
} catch (err) {
console.log("submissionService createSubmission fail to validate answer: ", err);
}

try {
const dbSubmission = {
user: submission.user,
seasonID: question.seasonID,
question: submission.question,
answer: submission.answer,
correct: submission.answer === question.answer,
points_awarded: submission.answer === question.answer ? question.points : 0
correct: isCorrect,
points_awarded: isCorrect ? question.points : 0
}
const result = await SubmissionRepo.createSubmission(dbSubmission);

// Update question submissions array using $push and $inc submission counts
question = await QuestionService.updateQuestionSubmissions(
submission.question.toString(),
result._id.toString(),
submission.answer === question.answer
isCorrect
);

if (!question) {
Expand All @@ -63,6 +71,8 @@ const createSubmission = async (
}
}



const SubmissionService = {
createSubmission,
}
Expand Down
Loading

0 comments on commit f40d7ef

Please sign in to comment.