From c74203bc805c0f231919bc5f3740068fefa751c1 Mon Sep 17 00:00:00 2001 From: Boon Hian Date: Sat, 17 Feb 2024 16:09:03 +0800 Subject: [PATCH] fix: modify some function to use cont-serv-repo architecture --- .../src/controllers/questionaire.ts | 5 +- apps/challenges/src/controllers/submission.ts | 67 ++++-------------- apps/challenges/src/model/question.ts | 6 ++ apps/challenges/src/model/response.ts | 5 ++ apps/challenges/src/model/submission.ts | 25 +++---- apps/challenges/src/repo/questionRepo.ts | 46 ++++++++++++ apps/challenges/src/repo/submissionRepo.ts | 13 ++++ .../challenges/src/service/questionService.ts | 34 +++++++++ .../src/service/submissionService.ts | 70 +++++++++++++++++++ 9 files changed, 203 insertions(+), 68 deletions(-) create mode 100644 apps/challenges/src/model/response.ts create mode 100644 apps/challenges/src/repo/questionRepo.ts create mode 100644 apps/challenges/src/repo/submissionRepo.ts create mode 100644 apps/challenges/src/service/questionService.ts create mode 100644 apps/challenges/src/service/submissionService.ts diff --git a/apps/challenges/src/controllers/questionaire.ts b/apps/challenges/src/controllers/questionaire.ts index baf6be1f..3bd1f8ee 100644 --- a/apps/challenges/src/controllers/questionaire.ts +++ b/apps/challenges/src/controllers/questionaire.ts @@ -1,7 +1,7 @@ import { Request, Response } from "express"; const asyncHandler = require('express-async-handler'); -const Question = require('../model/question'); -const Submission = require('../model/submission'); +import Question from '../model/question'; +import Submission from '../model/submission'; import Season from "../model/season"; import { isValidObjectId } from "../utils/db"; @@ -59,6 +59,7 @@ const setQuestion = asyncHandler(async (req: Request, res: Response) => { 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, diff --git a/apps/challenges/src/controllers/submission.ts b/apps/challenges/src/controllers/submission.ts index d48d0fb1..5846e06e 100644 --- a/apps/challenges/src/controllers/submission.ts +++ b/apps/challenges/src/controllers/submission.ts @@ -1,9 +1,13 @@ import { Request, Response } from "express"; const asyncHandler = require('express-async-handler'); -const Question = require('../model/question'); -const Submission = require('../model/submission'); +import Question from '../model/question'; +import Submission from '../model/submission'; import Season from "../model/season"; import { isValidObjectId } from "../utils/db"; +import QuestionService from "../service/questionService"; +import SubmissionService from "../service/submissionService"; +import { isValidCreateSubmissionRequest } from "../utils/validator"; +import mongoose from 'mongoose'; // @desc Get submissions @@ -50,60 +54,15 @@ const setSubmission = asyncHandler(async (req: Request, res: Response) => { } try { - const question = await Question.findById(questionId); + const submission = isValidCreateSubmissionRequest.parse(req.body); + const createSubmissionReq = { + question: new mongoose.Types.ObjectId(submission.question), + user: new mongoose.Types.ObjectId(submission.user), + answer: submission.answer + }; - // cooldown period of 3 seconds per user per question - // if (question.submissions.find((submission: any) => submission.user == req.body.user && new Date(submission.createdAt) > new Date(new Date().getTime() - 3 * 1000))) { - // return res.status(400).json({ message: 'Cooldown period of 10 seconds per user' }); - // } + await SubmissionService.createSubmission(createSubmissionReq); - if (!question) { - return res.status(404).json({ message: 'Question not found' }); - } - - if (!question.active) { - return res.status(400).json({ message: 'Question is not active' }); - } - - if (new Date(question.expiry) < new Date()) { - return res.status(400).json({ message: 'Question has expired' }); - } - - const submission = await Submission.create({ - user: req.body.user, - leaderboard: req.body.leaderboard, - answer: req.body.answer, - correct: req.body.answer === question.answer, - points_awarded: req.body.answer === question.answer ? question.points : 0, - question: questionId, - attempt: question.submissions.find((submission: any) => submission.user == req.body.user) ? question.submissions.find((submission: any) => submission.user == req.body.user).attempt + 1 : 1 - }); - - // Update question submissions array using $push and $inc submission counts - await Question.findByIdAndUpdate(questionId, { - $push: { submissions: submission._id }, - $inc: { submissions_count: 1, correct_submissions_count: req.body.answer === question.answer ? 1 : 0 } }, - { new: true }); - - // Retrieve user and update points of the entry in the leaderboard - const leaderboard = await Season.findOne({ _id: req.body.leaderboard }); - // TODO: Update leaderboard rankings - /* - const ranking = leaderboard?.rankings.find((ranking: any) => ranking.user == req.body.user); - if (!ranking) { - await Season.findByIdAndUpdate(req.body.leaderboard, { $push: { rankings: { user: req.body.user, points: submission.points_awarded } } }, { new: true }); - } else { - // if there is previous submission for the same question, remove the points from the previous submission and add the points from the new submission - const prevSubmission = await Submission.find({ user: req.body.user, question: questionId, _id: { $ne: submission._id } }); - if (prevSubmission.length > 0) { - // get the highest points awarded for the question - const highestPoints = Math.max(...prevSubmission.map((submission: any) => submission.points_awarded)); - await Season.findByIdAndUpdate(req.body.leaderboard, { $set: { 'rankings.$[elem].points': ranking.points - highestPoints + submission.points_awarded } }, { arrayFilters: [{ 'elem.user': req.body.user }], new: true }); - } else { - await Season.findByIdAndUpdate(req.body.leaderboard, { $set: { 'rankings.$[elem].points': ranking.points + submission.points_awarded } }, { arrayFilters: [{ 'elem.user': req.body.user }], new: true }); - } - } - */ res.status(201).json({ message: 'Answer submitted' }); } catch (error) { diff --git a/apps/challenges/src/model/question.ts b/apps/challenges/src/model/question.ts index 042974c7..b712cc9b 100644 --- a/apps/challenges/src/model/question.ts +++ b/apps/challenges/src/model/question.ts @@ -5,6 +5,7 @@ export interface QuestionModel { question_title: string; question_desc: string; question_date: Date; + seasonID: mongoose.Types.ObjectId; expiry: Date; points: number; answer: string; @@ -31,6 +32,11 @@ const questionSchema: Schema = new Schema({ type: Date, required: [true, 'Please add a question date'] }, + seasonID: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Season', + required: [true, 'Please add a season ID'] + }, expiry: { type: Date, required: [true, 'Please add an expiry date'] diff --git a/apps/challenges/src/model/response.ts b/apps/challenges/src/model/response.ts new file mode 100644 index 00000000..04ad531b --- /dev/null +++ b/apps/challenges/src/model/response.ts @@ -0,0 +1,5 @@ +export interface GeneralResp { + status: number; + message: string; + data?: any; +} \ No newline at end of file diff --git a/apps/challenges/src/model/submission.ts b/apps/challenges/src/model/submission.ts index 3b957f35..d117636f 100644 --- a/apps/challenges/src/model/submission.ts +++ b/apps/challenges/src/model/submission.ts @@ -1,13 +1,18 @@ import mongoose, { Schema, model } from 'mongoose'; +export interface CreateSubmissionReq { + user: mongoose.Types.ObjectId; + question: mongoose.Types.ObjectId; + answer: string; +} + export interface SubmissionModel { user: mongoose.Types.ObjectId; seasonID: mongoose.Types.ObjectId; + question: mongoose.Types.ObjectId; answer: string; correct?: boolean; points_awarded?: number; - question?: mongoose.Types.ObjectId; - attempt?: number; } const submissionSchema: Schema = new Schema({ @@ -19,7 +24,12 @@ const submissionSchema: Schema = new Schema({ seasonID: { type: mongoose.Schema.Types.ObjectId, required: true, - ref: 'Leaderboard', + ref: 'Season', + }, + question: { + type: mongoose.Schema.Types.ObjectId, + required: true, + ref: 'Question', }, answer: { type: String, @@ -31,15 +41,6 @@ const submissionSchema: Schema = new Schema({ points_awarded: { type: Number, }, - question: { - type: mongoose.Schema.Types.ObjectId, - required: true, - ref: 'Question', - }, - attempt: { - type: Number, - default: 1, - }, }, { timestamps: true, }); diff --git a/apps/challenges/src/repo/questionRepo.ts b/apps/challenges/src/repo/questionRepo.ts new file mode 100644 index 00000000..054f103b --- /dev/null +++ b/apps/challenges/src/repo/questionRepo.ts @@ -0,0 +1,46 @@ +import Question, { QuestionModel } from "../model/question" +import mongoose from 'mongoose'; + +const getQuestionByID = async ( + questionID: mongoose.Types.ObjectId, +): Promise => { + const question = await Question.findOne({ + _id: questionID + }); + return question; +} + +const updateQuestionByID = async ( + questionID: mongoose.Types.ObjectId, + questionModel: QuestionModel +): Promise => { + const question = await Question.findOneAndUpdate({ + _id: questionID + }, questionModel, { new: true }); + return question; +} + +const updateQuestionSubmissions = async ( + questionID: mongoose.Types.ObjectId, + submissionID: mongoose.Types.ObjectId, + isCorrect: boolean +): Promise => { + const question = await Question.findOneAndUpdate({ + _id: questionID + }, { + $push: { submissions: submissionID }, + $inc: { + submissions_count: 1, + correct_submissions_count: isCorrect ? 1 : 0 + } + }, { new: true }); + return question; +} + +const QuestionRepo = { + getQuestionByID, + updateQuestionByID, + updateQuestionSubmissions +} + +export { QuestionRepo as default } \ No newline at end of file diff --git a/apps/challenges/src/repo/submissionRepo.ts b/apps/challenges/src/repo/submissionRepo.ts new file mode 100644 index 00000000..ed5005a4 --- /dev/null +++ b/apps/challenges/src/repo/submissionRepo.ts @@ -0,0 +1,13 @@ +import Submission, { SubmissionModel } from "../model/submission"; + +const createSubmission = ( + submission: SubmissionModel +) => { + return Submission.create(submission); +} + +const SubmissionRepo = { + createSubmission, +} + +export default SubmissionRepo; \ No newline at end of file diff --git a/apps/challenges/src/service/questionService.ts b/apps/challenges/src/service/questionService.ts new file mode 100644 index 00000000..c250cde2 --- /dev/null +++ b/apps/challenges/src/service/questionService.ts @@ -0,0 +1,34 @@ +import mongoose from 'mongoose'; +import QuestionRepo from '../repo/questionRepo'; + +const getQuestionByID = async( + questionID: string, +) => { + if (!mongoose.isValidObjectId(questionID)) { + throw new Error('Invalid question ID'); + } + const _id = new mongoose.Types.ObjectId(questionID); + const question = await QuestionRepo.getQuestionByID(_id); + return question; +} + +const updateQuestionSubmissions = async( + questionID: string, + submissionID: string, + isCorrect: boolean +) => { + if (!mongoose.isValidObjectId(questionID) || !mongoose.isValidObjectId(submissionID)) { + throw new Error('Invalid question or submission ID'); + } + const _questionID = new mongoose.Types.ObjectId(questionID); + const _submissionID = new mongoose.Types.ObjectId(submissionID); + return await QuestionRepo.updateQuestionSubmissions(_questionID, _submissionID, isCorrect); +} + + +const QuestionService = { + getQuestionByID, + updateQuestionSubmissions +} + +export { QuestionService as default } \ No newline at end of file diff --git a/apps/challenges/src/service/submissionService.ts b/apps/challenges/src/service/submissionService.ts new file mode 100644 index 00000000..d74fdffa --- /dev/null +++ b/apps/challenges/src/service/submissionService.ts @@ -0,0 +1,70 @@ +import { CreateSubmissionReq, SubmissionModel } from "../model/submission"; +import SubmissionRepo from "../repo/submissionRepo"; +import QuestionService from "./questionService"; +import { GeneralResp } from "../model/response"; + +const createSubmission = async ( + submission: CreateSubmissionReq +): Promise => { + var question; + try { + question = await QuestionService.getQuestionByID(submission.question.toString()); + + if (!question) { + throw new Error('Question not found'); + } + + if (!question.active) { + throw new Error('Question is not active'); + } + + if (new Date(question.expiry) < new Date()) { + throw new Error('Question has expired'); + } + } catch (err) { + return { + status: 400, + message: (err as Error).message + } + } + + 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 + } + 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 + ); + + if (!question) { + throw new Error('Failed to update question submissions'); + } + + return { + status: 201, + message: 'Answer submitted', + data: result + }; + } catch (err) { + return { + status: 500, + message: (err as Error).message + } + } +} + +const SubmissionService = { + createSubmission, +} + +export default SubmissionService; \ No newline at end of file