Skip to content

Commit

Permalink
Improve progress endpoint (#53)
Browse files Browse the repository at this point in the history
* Count exp points only for finished lessons
* Improve lesson progress endpoint
* Return 409 when duplicate
* Prevent further submissions when lesson is already complete
  • Loading branch information
laurogripa authored Oct 27, 2024
1 parent 9865d66 commit 495c6d2
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 19 deletions.
59 changes: 50 additions & 9 deletions src/controllers/progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ProgressModel } from "@/models/Progress";
import { LessonModel } from "@/models/Lesson";
import { Course, CourseModel } from "@/models/Course";
import { UserModel } from "@/models/User";
import { Types } from "mongoose";
import { MongoError } from "mongodb";

export const submitAnswer = async (req: Request, res: Response) => {
const { courseId, lessonId, choice, userId } = req.body;
Expand All @@ -21,6 +23,10 @@ export const submitAnswer = async (req: Request, res: Response) => {
return res.status(400).send({ error: { message: "Course not found" } });
}

// prevent the user from submitting again if the lesson is already complete
const progress = await ProgressModel.findOne({ courseId, lessonId, userId, isCorrect: true });
if (progress) return res.status(200).send(progress);

const isCorrect = choice === lesson.challenge.correctChoice;

let errorMessage;
Expand All @@ -33,8 +39,16 @@ export const submitAnswer = async (req: Request, res: Response) => {
isCorrect,
difficulty: lesson.difficulty,
});
if (newProgress) return res.status(200).send(newProgress);
if (newProgress) return res.status(201).send(newProgress);
} catch (e) {
if (e instanceof MongoError && e.code === 11000) {
return res.status(409).send({
error: {
message: "E11000 duplicate key error",
},
});
}

errorMessage = (e as Error).message;
console.error(`[ERROR][submitAnswer] ${JSON.stringify(e)}`);
}
Expand All @@ -47,14 +61,14 @@ export const submitAnswer = async (req: Request, res: Response) => {
};

export const getLessonProgress = async (req: Request, res: Response) => {
const { userId, lessonId } = req.params;
const { courseId, lessonId, userId } = req.params;

if (!userId || !lessonId) {
if (!courseId || !lessonId || !userId) {
return res.status(400).send({ error: { message: "Missing params" } });
}

try {
const progress = await ProgressModel.find({ userId, lessonId });
const progress = await ProgressModel.find({ courseId, lessonId, userId });
return res.status(200).send(progress);
} catch (e) {
console.error(`[ERROR][getLessonProgress] ${JSON.stringify(e)}`);
Expand All @@ -70,7 +84,7 @@ export const getCourseProgress = async (req: Request, res: Response) => {
}

try {
const progress = await ProgressModel.find({ userId, courseId });
const progress = await ProgressModel.find({ userId, courseId, isCorrect: true });
const course = (await CourseModel.findOne({ _id: courseId }).populate({
path: "modules",
populate: {
Expand Down Expand Up @@ -130,13 +144,40 @@ export const getUserXPAndLevel = async (req: Request, res: Response) => {
return res.status(400).send({ error: { message: "User not found" } });
}

const progress = await ProgressModel.find({ userId });
const progress = await ProgressModel.aggregate([
{
$match: { userId: new Types.ObjectId(userId) },
},
{
$group: {
_id: {
courseId: "$courseId",
lessonId: "$lessonId",
difficulty: "$difficulty",
},
count: { $sum: 1 },
correctCount: { $sum: { $cond: ["$isCorrect", 1, 0] } },
},
},
{
$project: {
courseId: "$_id.courseId",
lessonId: "$_id.lessonId",
difficulty: "$_id.difficulty",
correctAtFirstTry: { $cond: [{ $eq: ["$count", 1] }, { $eq: ["$correctCount", 1] }, false] },
isCorrect: { $gt: ["$correctCount", 0] },
_id: 0,
},
},
]);

let exp = 0;
for (const p of progress) {
const difficulty = p.difficulty as Difficulty;
const points = p.isCorrect ? EXP_POINTS[difficulty].perfect : EXP_POINTS[difficulty].withMistakes;
exp += points;
if (p.isCorrect) {
const difficulty = p.difficulty as Difficulty;
const points = p.correctAtFirstTry ? EXP_POINTS[difficulty].perfect : EXP_POINTS[difficulty].withMistakes;
exp += points;
}
}

const level = calculateLevel(exp);
Expand Down
4 changes: 2 additions & 2 deletions src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ const router = (app: Express) => {

// Progress
app.post("/progress", [authMiddleware], submitAnswer);
app.get("/progress/lesson/:userId/:lessonId", [authMiddleware], getLessonProgress);
app.get("/progress/lesson/:userId/:courseId/:lessonId", [authMiddleware], getLessonProgress);
app.get("/progress/course/:userId/:courseId", [authMiddleware], getCourseProgress);
app.get("/progress/xp-level/:userId", [authMiddleware], getUserXPAndLevel);
app.get("/progress/level/:userId", [authMiddleware], getUserXPAndLevel);
};

export default router;
Loading

0 comments on commit 495c6d2

Please sign in to comment.