refactor: exercise model and auto grading middleware

This commit is contained in:
elangptra 2024-09-30 08:06:15 +07:00
parent 24cba3b988
commit ee4f1814e4
14 changed files with 234 additions and 83 deletions

View File

@ -141,12 +141,32 @@ export const getExerciseById = async (req, res) => {
const questionType = exercise.QUESTION_TYPE; const questionType = exercise.QUESTION_TYPE;
if (questionType === "MCQ") { if (questionType === "MCQ") {
if (exerciseData.multipleChoices) {
exerciseData.multipleChoices = exerciseData.multipleChoices.map(
(choice) => {
const { ANSWER_KEY, ...rest } = choice.dataValues;
return rest;
}
);
}
delete exerciseData.matchingPairs; delete exerciseData.matchingPairs;
delete exerciseData.trueFalse; delete exerciseData.trueFalse;
} else if (questionType === "MPQ") { } else if (questionType === "MPQ") {
if (exerciseData.matchingPairs) {
exerciseData.matchingPairs = exerciseData.matchingPairs.map((pair) => {
const { LEFT_PAIR, RIGHT_PAIR, ...rest } = pair.dataValues;
return rest;
});
}
delete exerciseData.multipleChoices; delete exerciseData.multipleChoices;
delete exerciseData.trueFalse; delete exerciseData.trueFalse;
} else if (questionType === "TFQ") { } else if (questionType === "TFQ") {
if (exerciseData.trueFalse) {
exerciseData.trueFalse = exerciseData.trueFalse.map((tf) => {
const { IS_TRUE, ...rest } = tf.dataValues;
return rest;
});
}
delete exerciseData.multipleChoices; delete exerciseData.multipleChoices;
delete exerciseData.matchingPairs; delete exerciseData.matchingPairs;
} else { } else {
@ -167,7 +187,6 @@ export const getExerciseByLevelId = async (req, res) => {
const { idLevel } = req.params; const { idLevel } = req.params;
const levelExists = await models.Level.findByPk(idLevel); const levelExists = await models.Level.findByPk(idLevel);
if (!levelExists) { if (!levelExists) {
return response(404, null, "Level not found", res); return response(404, null, "Level not found", res);
} }
@ -199,12 +218,34 @@ export const getExerciseByLevelId = async (req, res) => {
const questionType = exercise.QUESTION_TYPE; const questionType = exercise.QUESTION_TYPE;
if (questionType === "MCQ") { if (questionType === "MCQ") {
if (exerciseData.multipleChoices) {
exerciseData.multipleChoices = exerciseData.multipleChoices.map(
(choice) => {
const { ANSWER_KEY, ...rest } = choice.dataValues; // Exclude ANSWER_KEY
return rest;
}
);
}
delete exerciseData.matchingPairs; delete exerciseData.matchingPairs;
delete exerciseData.trueFalse; delete exerciseData.trueFalse;
} else if (questionType === "MPQ") { } else if (questionType === "MPQ") {
if (exerciseData.matchingPairs) {
exerciseData.matchingPairs = exerciseData.matchingPairs.map(
(pair) => {
const { LEFT_PAIR, RIGHT_PAIR, ...rest } = pair.dataValues; // Exclude LEFT_PAIR, RIGHT_PAIR
return rest;
}
);
}
delete exerciseData.multipleChoices; delete exerciseData.multipleChoices;
delete exerciseData.trueFalse; delete exerciseData.trueFalse;
} else if (questionType === "TFQ") { } else if (questionType === "TFQ") {
if (exerciseData.trueFalse) {
exerciseData.trueFalse = exerciseData.trueFalse.map((tf) => {
const { IS_TRUE, ...rest } = tf.dataValues; // Exclude IS_TRUE
return rest;
});
}
delete exerciseData.multipleChoices; delete exerciseData.multipleChoices;
delete exerciseData.matchingPairs; delete exerciseData.matchingPairs;
} else { } else {

View File

@ -36,6 +36,7 @@ export const getLevels = async (req, res) => {
export const getLevelById = async (req, res) => { export const getLevelById = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const level = await models.Level.findByPk(id, { const level = await models.Level.findByPk(id, {
attributes: { attributes: {
exclude: [ exclude: [
@ -47,13 +48,38 @@ export const getLevelById = async (req, res) => {
"ROUTE_6", "ROUTE_6",
], ],
}, },
include: [
{
model: models.Topic,
as: "levelTopic",
attributes: ["NAME_TOPIC"],
include: {
model: models.Section,
as: "topicSection",
attributes: ["NAME_SECTION"],
},
},
],
}); });
if (!level) { if (!level) {
return response(404, null, "Level not found", res); return response(404, null, "Level not found", res);
} }
response(200, level, "Success", res); const levelJSON = level.toJSON();
const NAME_SECTION = levelJSON.levelTopic.topicSection.NAME_SECTION;
const NAME_TOPIC = levelJSON.levelTopic.NAME_TOPIC;
delete levelJSON.levelTopic;
delete levelJSON.levelTopic?.topicSection;
const responsePayload = {
NAME_SECTION,
NAME_TOPIC,
...levelJSON,
};
response(200, responsePayload, "Success", res);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
response(500, null, "Internal Server Error", res); response(500, null, "Internal Server Error", res);
@ -65,7 +91,14 @@ export const getLevelsByTopicId = async (req, res) => {
const { idTopic } = req.params; const { idTopic } = req.params;
const { ID } = req.user; const { ID } = req.user;
const topicExists = await models.Topic.findByPk(idTopic); const topicExists = await models.Topic.findByPk(idTopic, {
include: {
model: models.Section,
as: "topicSection",
attributes: ["NAME_SECTION"],
},
});
if (!topicExists) { if (!topicExists) {
return response(404, null, "Topic not found", res); return response(404, null, "Topic not found", res);
} }
@ -94,6 +127,16 @@ export const getLevelsByTopicId = async (req, res) => {
}, },
required: false, required: false,
}, },
{
model: models.Topic,
as: "levelTopic",
attributes: ["NAME_TOPIC"],
include: {
model: models.Section,
as: "topicSection",
attributes: ["NAME_SECTION"],
},
},
], ],
}); });
@ -129,9 +172,17 @@ export const getLevelsByTopicId = async (req, res) => {
: null; : null;
const levelJSON = level.toJSON(); const levelJSON = level.toJSON();
const NAME_SECTION = levelJSON.levelTopic.topicSection.NAME_SECTION;
const NAME_TOPIC = levelJSON.levelTopic.NAME_TOPIC;
delete levelJSON.stdLearning; delete levelJSON.stdLearning;
delete levelJSON.levelTopic;
delete levelJSON.levelTopic?.topicSection;
return { return {
NAME_SECTION,
NAME_TOPIC,
...levelJSON, ...levelJSON,
ID_STUDENT_LEARNING, ID_STUDENT_LEARNING,
SCORE, SCORE,

View File

@ -52,7 +52,7 @@ export const getTopicBySectionId = async (req, res) => {
}; };
export const createTopic = async (req, res) => { export const createTopic = async (req, res) => {
const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC, OBJECTIVES } = req.body; const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC } = req.body;
if (!ID_SECTION) { if (!ID_SECTION) {
return response(400, null, "Section ID is required", res); return response(400, null, "Section ID is required", res);
@ -66,10 +66,6 @@ export const createTopic = async (req, res) => {
return response(400, null, "Topic description is required", res); return response(400, null, "Topic description is required", res);
} }
if (!OBJECTIVES) {
return response(400, null, "Topic objectives are required", res);
}
try { try {
const section = await models.Section.findByPk(ID_SECTION); const section = await models.Section.findByPk(ID_SECTION);
if (!section) { if (!section) {
@ -80,7 +76,6 @@ export const createTopic = async (req, res) => {
ID_SECTION, ID_SECTION,
NAME_TOPIC, NAME_TOPIC,
DESCRIPTION_TOPIC, DESCRIPTION_TOPIC,
OBJECTIVES,
}); });
response(201, newTopic, "Topic created successfully", res); response(201, newTopic, "Topic created successfully", res);
@ -92,7 +87,7 @@ export const createTopic = async (req, res) => {
export const updateTopicById = async (req, res) => { export const updateTopicById = async (req, res) => {
const { id } = req.params; const { id } = req.params;
const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC, OBJECTIVES } = req.body; const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC } = req.body;
try { try {
const topic = await models.Topic.findByPk(id); const topic = await models.Topic.findByPk(id);
@ -117,10 +112,6 @@ export const updateTopicById = async (req, res) => {
topic.DESCRIPTION_TOPIC = DESCRIPTION_TOPIC; topic.DESCRIPTION_TOPIC = DESCRIPTION_TOPIC;
} }
if (OBJECTIVES) {
topic.OBJECTIVES = OBJECTIVES;
}
await topic.save(); await topic.save();
response(200, topic, "Topic updated successfully", res); response(200, topic, "Topic updated successfully", res);
@ -171,7 +162,6 @@ export const getCompletedTopics = async (req, res) => {
"ID_TOPIC", "ID_TOPIC",
"NAME_TOPIC", "NAME_TOPIC",
"DESCRIPTION_TOPIC", "DESCRIPTION_TOPIC",
"OBJECTIVES",
], ],
}, },
], ],
@ -200,7 +190,6 @@ export const getCompletedTopics = async (req, res) => {
ID_TOPIC: topic.ID_TOPIC, ID_TOPIC: topic.ID_TOPIC,
NAME_TOPIC: topic.NAME_TOPIC, NAME_TOPIC: topic.NAME_TOPIC,
DESCRIPTION_TOPIC: topic.DESCRIPTION_TOPIC, DESCRIPTION_TOPIC: topic.DESCRIPTION_TOPIC,
OBJECTIVES: topic.OBJECTIVES,
}); });
} }
} }

View File

@ -29,7 +29,14 @@ export const getStdExerciseById = async (req, res) => {
export const stdAnswerExercise = async (req, res, next) => { export const stdAnswerExercise = async (req, res, next) => {
try { try {
const { ID_STUDENT_LEARNING, ID_ADMIN_EXERCISE, ANSWER_STUDENT } = req.body; const { answers } = req.body;
if (!Array.isArray(answers) || answers.length === 0) {
return response(400, null, "Answers array is required", res);
}
for (const answer of answers) {
const { ID_STUDENT_LEARNING, ID_ADMIN_EXERCISE, ANSWER_STUDENT } = answer;
if (!ID_STUDENT_LEARNING) { if (!ID_STUDENT_LEARNING) {
return response(400, null, "Id student learning is required", res); return response(400, null, "Id student learning is required", res);
@ -69,7 +76,6 @@ export const stdAnswerExercise = async (req, res, next) => {
if (existingStdExercise) { if (existingStdExercise) {
existingStdExercise.ANSWER_STUDENT = ANSWER_STUDENT; existingStdExercise.ANSWER_STUDENT = ANSWER_STUDENT;
await existingStdExercise.save(); await existingStdExercise.save();
} else { } else {
await models.StdExercise.create({ await models.StdExercise.create({
@ -78,8 +84,9 @@ export const stdAnswerExercise = async (req, res, next) => {
ANSWER_STUDENT, ANSWER_STUDENT,
}); });
} }
}
req.params.id = ID_STUDENT_LEARNING; req.params.id = answers[0].ID_STUDENT_LEARNING;
next(); next();
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@ -117,6 +117,8 @@ export const updateStudentClassByName = async (req, res) => {
const updateResults = []; const updateResults = [];
let hasError = false; let hasError = false;
let successCount = 0;
let failureCount = 0;
for (const { NAME_USERS, NISN } of STUDENTS) { for (const { NAME_USERS, NISN } of STUDENTS) {
if (!NAME_USERS || !NISN) { if (!NAME_USERS || !NISN) {
@ -126,6 +128,7 @@ export const updateStudentClassByName = async (req, res) => {
error: "User name and NISN are required for each student", error: "User name and NISN are required for each student",
}); });
hasError = true; hasError = true;
failureCount++;
continue; continue;
} }
@ -148,6 +151,7 @@ export const updateStudentClassByName = async (req, res) => {
error: "Student with the given name and NISN not found", error: "Student with the given name and NISN not found",
}); });
hasError = true; hasError = true;
failureCount++;
continue; continue;
} }
@ -158,6 +162,7 @@ export const updateStudentClassByName = async (req, res) => {
error: "Student is already in the selected class", error: "Student is already in the selected class",
}); });
hasError = true; hasError = true;
failureCount++;
continue; continue;
} }
@ -197,6 +202,8 @@ export const updateStudentClassByName = async (req, res) => {
"Student's class and related monitoring updated successfully", "Student's class and related monitoring updated successfully",
studentUpdateResults, studentUpdateResults,
}); });
successCount++;
} catch (error) { } catch (error) {
console.error("Error processing student:", error.message); console.error("Error processing student:", error.message);
updateResults.push({ updateResults.push({
@ -206,24 +213,22 @@ export const updateStudentClassByName = async (req, res) => {
details: error.message, details: error.message,
}); });
hasError = true; hasError = true;
failureCount++;
} }
} }
if (hasError) { let responseMessage = "";
return response(
400, if (failureCount === STUDENTS.length) {
{ updateResults }, responseMessage = "Failed to update all students.";
"Some students could not be updated due to errors", } else if (successCount > 0 && failureCount > 0) {
res responseMessage =
); "Some students updated successfully, but there were errors with others.";
} else { } else if (successCount === STUDENTS.length) {
return response( responseMessage = "All students updated successfully.";
200,
{ updateResults },
"Students classes updated successfully",
res
);
} }
return response(200, { updateResults }, responseMessage, res);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
response(500, null, "Internal Server Error", res); response(500, null, "Internal Server Error", res);

View File

@ -104,6 +104,13 @@ export const getStudents = async (req, res) => {
model: models.Student, model: models.Student,
as: "students", as: "students",
attributes: ["NISN"], attributes: ["NISN"],
include: [
{
model: models.Class,
as: "studentClass",
attributes: ["NAME_CLASS"],
},
],
}, },
], ],
raw: true, raw: true,
@ -115,6 +122,7 @@ export const getStudents = async (req, res) => {
NAME_USERS: student.NAME_USERS, NAME_USERS: student.NAME_USERS,
EMAIL: student.EMAIL, EMAIL: student.EMAIL,
NISN: student.students.NISN, NISN: student.students.NISN,
NAME_CLASS: student.students.studentClass.NAME_CLASS,
ROLE: student.ROLE, ROLE: student.ROLE,
})); }));
@ -125,6 +133,50 @@ export const getStudents = async (req, res) => {
} }
}; };
export const getStudentsWithNoClass = async (req, res) => {
try {
const studentsWithoutClass = await models.User.findAll({
where: {
ROLE: "student",
},
attributes: ["ID", "NAME_USERS", "EMAIL", "ROLE"],
include: [
{
model: models.Student,
as: "students",
attributes: ["NISN", "ID_CLASS"],
include: [
{
model: models.Class,
as: "studentClass",
attributes: ["NAME_CLASS"],
},
],
where: {
ID_CLASS: null,
},
},
],
raw: true,
nest: true,
});
const formattedStudents = studentsWithoutClass.map((student) => ({
ID: student.ID,
NAME_USERS: student.NAME_USERS,
EMAIL: student.EMAIL,
NISN: student.students.NISN,
NAME_CLASS: student.students.studentClass.NAME_CLASS,
ROLE: student.ROLE,
}));
response(200, formattedStudents, "Success", res);
} catch (error) {
console.log(error);
response(500, null, "Error retrieving students with no class", res);
}
};
export const getUserById = async (req, res) => { export const getUserById = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;

View File

@ -19,7 +19,7 @@ export const checkCorrectAnswers = async (req, res, next) => {
const exercise = await models.Exercise.findByPk(ID_ADMIN_EXERCISE); const exercise = await models.Exercise.findByPk(ID_ADMIN_EXERCISE);
if (!exercise) continue; if (!exercise) continue;
const weight = parseFloat(exercise.SCORE_WEIGHT); // Ensure weight is a float const weight = parseFloat(exercise.SCORE_WEIGHT);
const questionType = exercise.QUESTION_TYPE; const questionType = exercise.QUESTION_TYPE;
switch (questionType) { switch (questionType) {
@ -79,7 +79,7 @@ export const checkCorrectAnswers = async (req, res, next) => {
const correctPercentage = correctCount / matchingPairs.length; const correctPercentage = correctCount / matchingPairs.length;
stdExercise.IS_CORRECT = correctCount > 0 ? 1 : 0; stdExercise.IS_CORRECT = correctCount > 0 ? 1 : 0;
stdExercise.RESULT_SCORE_STUDENT = correctPercentage * weight; // Use float arithmetic stdExercise.RESULT_SCORE_STUDENT = correctPercentage * weight;
} }
break; break;
} }

View File

@ -37,13 +37,6 @@ const TopicModel = (DataTypes) => {
notEmpty: true, notEmpty: true,
}, },
}, },
OBJECTIVES: {
type: DataTypes.STRING(1024),
allowNull: false,
validate: {
notEmpty: true,
},
},
TIME_TOPIC: { TIME_TOPIC: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: true, allowNull: true,

View File

@ -24,7 +24,7 @@ const ClassModel = (DataTypes) => {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
allowNull: true, allowNull: true,
}, },
TIME_REPORT: { TIME_CLASS: {
type: DataTypes.DATE, type: DataTypes.DATE,
allowNull: true, allowNull: true,
defaultValue: DataTypes.NOW, defaultValue: DataTypes.NOW,

View File

@ -13,6 +13,17 @@ const StudentModel = (DataTypes) => {
notEmpty: true, notEmpty: true,
}, },
}, },
ID_CLASS: {
type: DataTypes.UUID,
allowNull: false,
validate: {
notEmpty: true,
},
references: {
model: "class",
key: "ID_CLASS",
},
},
ID: { ID: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: false, allowNull: false,

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View File

@ -1,5 +1,5 @@
import express from "express"; import express from "express";
import { getUsers, getAdmins, getTeachers, getStudents, getUserById, getUserByName, updateUserById, updateUserPasswordById, deleteUserById, getMe } from "../../controllers/usersControllers/user.js"; import { getUsers, getAdmins, getTeachers, getStudents, getStudentsWithNoClass, getUserById, getUserByName, updateUserById, updateUserPasswordById, deleteUserById, getMe } from "../../controllers/usersControllers/user.js";
import { verifyLoginUser, adminOnly, adminOrTeacherOnly } from "../../middlewares/User/authUser.js"; import { verifyLoginUser, adminOnly, adminOrTeacherOnly } from "../../middlewares/User/authUser.js";
import handleUpload from "../../middlewares/User/uploadUser.js"; import handleUpload from "../../middlewares/User/uploadUser.js";
@ -14,6 +14,8 @@ router.get("/user/teacher", verifyLoginUser, adminOnly, getTeachers);
router.get("/user/student", verifyLoginUser, adminOrTeacherOnly, getStudents); router.get("/user/student", verifyLoginUser, adminOrTeacherOnly, getStudents);
router.get("/user/student/unassigned", verifyLoginUser, adminOrTeacherOnly, getStudentsWithNoClass);
router.get("/user/:id", verifyLoginUser, getUserById); router.get("/user/:id", verifyLoginUser, getUserById);
router.get("/user/name/:name", verifyLoginUser, adminOrTeacherOnly, getUserByName); router.get("/user/name/:name", verifyLoginUser, adminOrTeacherOnly, getUserByName);