diff --git a/controllers/contentControllers/exercise.js b/controllers/contentControllers/exercise.js index a503c43..eef569a 100644 --- a/controllers/contentControllers/exercise.js +++ b/controllers/contentControllers/exercise.js @@ -141,12 +141,32 @@ export const getExerciseById = async (req, res) => { const questionType = exercise.QUESTION_TYPE; 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.trueFalse; } 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.trueFalse; } 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.matchingPairs; } else { @@ -167,7 +187,6 @@ export const getExerciseByLevelId = async (req, res) => { const { idLevel } = req.params; const levelExists = await models.Level.findByPk(idLevel); - if (!levelExists) { return response(404, null, "Level not found", res); } @@ -199,12 +218,34 @@ export const getExerciseByLevelId = async (req, res) => { const questionType = exercise.QUESTION_TYPE; 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.trueFalse; } 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.trueFalse; } 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.matchingPairs; } else { diff --git a/controllers/contentControllers/level.js b/controllers/contentControllers/level.js index 80923b5..4145aeb 100644 --- a/controllers/contentControllers/level.js +++ b/controllers/contentControllers/level.js @@ -36,6 +36,7 @@ export const getLevels = async (req, res) => { export const getLevelById = async (req, res) => { try { const { id } = req.params; + const level = await models.Level.findByPk(id, { attributes: { exclude: [ @@ -47,13 +48,38 @@ export const getLevelById = async (req, res) => { "ROUTE_6", ], }, + include: [ + { + model: models.Topic, + as: "levelTopic", + attributes: ["NAME_TOPIC"], + include: { + model: models.Section, + as: "topicSection", + attributes: ["NAME_SECTION"], + }, + }, + ], }); if (!level) { 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) { console.log(error); response(500, null, "Internal Server Error", res); @@ -65,7 +91,14 @@ export const getLevelsByTopicId = async (req, res) => { const { idTopic } = req.params; 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) { return response(404, null, "Topic not found", res); } @@ -94,6 +127,16 @@ export const getLevelsByTopicId = async (req, res) => { }, 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; const levelJSON = level.toJSON(); + + const NAME_SECTION = levelJSON.levelTopic.topicSection.NAME_SECTION; + const NAME_TOPIC = levelJSON.levelTopic.NAME_TOPIC; + delete levelJSON.stdLearning; + delete levelJSON.levelTopic; + delete levelJSON.levelTopic?.topicSection; return { + NAME_SECTION, + NAME_TOPIC, ...levelJSON, ID_STUDENT_LEARNING, SCORE, diff --git a/controllers/contentControllers/topic.js b/controllers/contentControllers/topic.js index 71b8527..d70020d 100644 --- a/controllers/contentControllers/topic.js +++ b/controllers/contentControllers/topic.js @@ -52,7 +52,7 @@ export const getTopicBySectionId = 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) { 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); } - if (!OBJECTIVES) { - return response(400, null, "Topic objectives are required", res); - } - try { const section = await models.Section.findByPk(ID_SECTION); if (!section) { @@ -80,7 +76,6 @@ export const createTopic = async (req, res) => { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC, - OBJECTIVES, }); response(201, newTopic, "Topic created successfully", res); @@ -92,7 +87,7 @@ export const createTopic = async (req, res) => { export const updateTopicById = async (req, res) => { const { id } = req.params; - const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC, OBJECTIVES } = req.body; + const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC } = req.body; try { const topic = await models.Topic.findByPk(id); @@ -117,10 +112,6 @@ export const updateTopicById = async (req, res) => { topic.DESCRIPTION_TOPIC = DESCRIPTION_TOPIC; } - if (OBJECTIVES) { - topic.OBJECTIVES = OBJECTIVES; - } - await topic.save(); response(200, topic, "Topic updated successfully", res); @@ -171,7 +162,6 @@ export const getCompletedTopics = async (req, res) => { "ID_TOPIC", "NAME_TOPIC", "DESCRIPTION_TOPIC", - "OBJECTIVES", ], }, ], @@ -200,7 +190,6 @@ export const getCompletedTopics = async (req, res) => { ID_TOPIC: topic.ID_TOPIC, NAME_TOPIC: topic.NAME_TOPIC, DESCRIPTION_TOPIC: topic.DESCRIPTION_TOPIC, - OBJECTIVES: topic.OBJECTIVES, }); } } diff --git a/controllers/learningControllers/stdExercise.js b/controllers/learningControllers/stdExercise.js index 67dbaf3..a2c46cd 100644 --- a/controllers/learningControllers/stdExercise.js +++ b/controllers/learningControllers/stdExercise.js @@ -29,57 +29,64 @@ export const getStdExerciseById = async (req, res) => { export const stdAnswerExercise = async (req, res, next) => { try { - const { ID_STUDENT_LEARNING, ID_ADMIN_EXERCISE, ANSWER_STUDENT } = req.body; + const { answers } = req.body; - if (!ID_STUDENT_LEARNING) { - return response(400, null, "Id student learning is required", res); + if (!Array.isArray(answers) || answers.length === 0) { + return response(400, null, "Answers array is required", res); } - const existingStdLearning = await models.StdLearning.findByPk( - ID_STUDENT_LEARNING - ); - if (!existingStdLearning) { - return response(404, null, "Id student learning not found", res); - } + for (const answer of answers) { + const { ID_STUDENT_LEARNING, ID_ADMIN_EXERCISE, ANSWER_STUDENT } = answer; - if (!ID_ADMIN_EXERCISE) { - return response(400, null, "Id exercise is required", res); - } + if (!ID_STUDENT_LEARNING) { + return response(400, null, "Id student learning is required", res); + } - const exercise = await models.Exercise.findOne({ - where: { - ID_ADMIN_EXERCISE: ID_ADMIN_EXERCISE, - ID_LEVEL: existingStdLearning.ID_LEVEL, - }, - }); - if (!exercise) { - return response(404, null, "Exercise not found in this level", res); - } + const existingStdLearning = await models.StdLearning.findByPk( + ID_STUDENT_LEARNING + ); + if (!existingStdLearning) { + return response(404, null, "Id student learning not found", res); + } - if (!ANSWER_STUDENT) { - return response(400, null, "Answer is required", res); - } + if (!ID_ADMIN_EXERCISE) { + return response(400, null, "Id exercise is required", res); + } - const existingStdExercise = await models.StdExercise.findOne({ - where: { - ID_STUDENT_LEARNING: ID_STUDENT_LEARNING, - ID_ADMIN_EXERCISE: ID_ADMIN_EXERCISE, - }, - }); - - if (existingStdExercise) { - existingStdExercise.ANSWER_STUDENT = ANSWER_STUDENT; - - await existingStdExercise.save(); - } else { - await models.StdExercise.create({ - ID_STUDENT_LEARNING, - ID_ADMIN_EXERCISE, - ANSWER_STUDENT, + const exercise = await models.Exercise.findOne({ + where: { + ID_ADMIN_EXERCISE: ID_ADMIN_EXERCISE, + ID_LEVEL: existingStdLearning.ID_LEVEL, + }, }); + if (!exercise) { + return response(404, null, "Exercise not found in this level", res); + } + + if (!ANSWER_STUDENT) { + return response(400, null, "Answer is required", res); + } + + const existingStdExercise = await models.StdExercise.findOne({ + where: { + ID_STUDENT_LEARNING: ID_STUDENT_LEARNING, + ID_ADMIN_EXERCISE: ID_ADMIN_EXERCISE, + }, + }); + + if (existingStdExercise) { + existingStdExercise.ANSWER_STUDENT = ANSWER_STUDENT; + await existingStdExercise.save(); + } else { + await models.StdExercise.create({ + ID_STUDENT_LEARNING, + ID_ADMIN_EXERCISE, + ANSWER_STUDENT, + }); + } } - req.params.id = ID_STUDENT_LEARNING; + req.params.id = answers[0].ID_STUDENT_LEARNING; next(); } catch (error) { console.log(error); diff --git a/controllers/monitoringControllers/class.js b/controllers/monitoringControllers/class.js index 1f42547..193df8e 100644 --- a/controllers/monitoringControllers/class.js +++ b/controllers/monitoringControllers/class.js @@ -117,6 +117,8 @@ export const updateStudentClassByName = async (req, res) => { const updateResults = []; let hasError = false; + let successCount = 0; + let failureCount = 0; for (const { NAME_USERS, NISN } of STUDENTS) { if (!NAME_USERS || !NISN) { @@ -126,6 +128,7 @@ export const updateStudentClassByName = async (req, res) => { error: "User name and NISN are required for each student", }); hasError = true; + failureCount++; continue; } @@ -148,6 +151,7 @@ export const updateStudentClassByName = async (req, res) => { error: "Student with the given name and NISN not found", }); hasError = true; + failureCount++; continue; } @@ -158,6 +162,7 @@ export const updateStudentClassByName = async (req, res) => { error: "Student is already in the selected class", }); hasError = true; + failureCount++; continue; } @@ -197,6 +202,8 @@ export const updateStudentClassByName = async (req, res) => { "Student's class and related monitoring updated successfully", studentUpdateResults, }); + + successCount++; } catch (error) { console.error("Error processing student:", error.message); updateResults.push({ @@ -206,24 +213,22 @@ export const updateStudentClassByName = async (req, res) => { details: error.message, }); hasError = true; + failureCount++; } } - if (hasError) { - return response( - 400, - { updateResults }, - "Some students could not be updated due to errors", - res - ); - } else { - return response( - 200, - { updateResults }, - "Students classes updated successfully", - res - ); + let responseMessage = ""; + + if (failureCount === STUDENTS.length) { + responseMessage = "Failed to update all students."; + } else if (successCount > 0 && failureCount > 0) { + responseMessage = + "Some students updated successfully, but there were errors with others."; + } else if (successCount === STUDENTS.length) { + responseMessage = "All students updated successfully."; } + + return response(200, { updateResults }, responseMessage, res); } catch (error) { console.error(error); response(500, null, "Internal Server Error", res); diff --git a/controllers/usersControllers/user.js b/controllers/usersControllers/user.js index 748f009..01d52a1 100644 --- a/controllers/usersControllers/user.js +++ b/controllers/usersControllers/user.js @@ -104,6 +104,13 @@ export const getStudents = async (req, res) => { model: models.Student, as: "students", attributes: ["NISN"], + include: [ + { + model: models.Class, + as: "studentClass", + attributes: ["NAME_CLASS"], + }, + ], }, ], raw: true, @@ -115,6 +122,7 @@ export const getStudents = async (req, res) => { NAME_USERS: student.NAME_USERS, EMAIL: student.EMAIL, NISN: student.students.NISN, + NAME_CLASS: student.students.studentClass.NAME_CLASS, 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) => { try { const { id } = req.params; diff --git a/middlewares/autoGrading.js b/middlewares/autoGrading.js index 884ffa6..379ee98 100644 --- a/middlewares/autoGrading.js +++ b/middlewares/autoGrading.js @@ -19,7 +19,7 @@ export const checkCorrectAnswers = async (req, res, next) => { const exercise = await models.Exercise.findByPk(ID_ADMIN_EXERCISE); 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; switch (questionType) { @@ -79,7 +79,7 @@ export const checkCorrectAnswers = async (req, res, next) => { const correctPercentage = correctCount / matchingPairs.length; stdExercise.IS_CORRECT = correctCount > 0 ? 1 : 0; - stdExercise.RESULT_SCORE_STUDENT = correctPercentage * weight; // Use float arithmetic + stdExercise.RESULT_SCORE_STUDENT = correctPercentage * weight; } break; } diff --git a/models/contentModels/topicModel.js b/models/contentModels/topicModel.js index c4ddb50..8488580 100644 --- a/models/contentModels/topicModel.js +++ b/models/contentModels/topicModel.js @@ -37,13 +37,6 @@ const TopicModel = (DataTypes) => { notEmpty: true, }, }, - OBJECTIVES: { - type: DataTypes.STRING(1024), - allowNull: false, - validate: { - notEmpty: true, - }, - }, TIME_TOPIC: { type: DataTypes.DATE, allowNull: true, diff --git a/models/monitoringModels/classModel.js b/models/monitoringModels/classModel.js index 5c83a72..0c20c81 100644 --- a/models/monitoringModels/classModel.js +++ b/models/monitoringModels/classModel.js @@ -24,7 +24,7 @@ const ClassModel = (DataTypes) => { type: DataTypes.INTEGER, allowNull: true, }, - TIME_REPORT: { + TIME_CLASS: { type: DataTypes.DATE, allowNull: true, defaultValue: DataTypes.NOW, diff --git a/models/usersModels/studentModel.js b/models/usersModels/studentModel.js index 752e053..e5fd107 100644 --- a/models/usersModels/studentModel.js +++ b/models/usersModels/studentModel.js @@ -13,6 +13,17 @@ const StudentModel = (DataTypes) => { notEmpty: true, }, }, + ID_CLASS: { + type: DataTypes.UUID, + allowNull: false, + validate: { + notEmpty: true, + }, + references: { + model: "class", + key: "ID_CLASS", + }, + }, ID: { type: DataTypes.UUID, allowNull: false, diff --git a/public/uploads/avatar/user-0d4423b278e286084768458758c5d8f3.jpg b/public/uploads/avatar/user-0d4423b278e286084768458758c5d8f3.jpg new file mode 100644 index 0000000..6703333 Binary files /dev/null and b/public/uploads/avatar/user-0d4423b278e286084768458758c5d8f3.jpg differ diff --git a/public/uploads/avatar/user-f247bdc2c503a780944ea7eed44bcfd8.jpg b/public/uploads/avatar/user-f247bdc2c503a780944ea7eed44bcfd8.jpg deleted file mode 100644 index 1740d26..0000000 Binary files a/public/uploads/avatar/user-f247bdc2c503a780944ea7eed44bcfd8.jpg and /dev/null differ diff --git a/routes/learning/stdExercise.js b/routes/learning/stdExercise.js index 403db07..f5032fd 100644 --- a/routes/learning/stdExercise.js +++ b/routes/learning/stdExercise.js @@ -2,7 +2,7 @@ import express from "express"; import { getStdExercises, getStdExerciseById, stdAnswerExercise } from "../../controllers/learningControllers/stdExercise.js"; import { verifyLoginUser } from "../../middlewares/User/authUser.js"; import { updateStdLearningById } from "../../controllers/learningControllers/stdLearning.js"; -import { checkCorrectAnswers,calculateScore, checkFirstFiveCorrect, nextLearning } from "../../middlewares/autoGrading.js"; +import { checkCorrectAnswers, calculateScore, checkFirstFiveCorrect, nextLearning } from "../../middlewares/autoGrading.js"; const router = express.Router(); diff --git a/routes/user/user.js b/routes/user/user.js index 0f7c637..0ba3429 100644 --- a/routes/user/user.js +++ b/routes/user/user.js @@ -1,5 +1,5 @@ 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 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/unassigned", verifyLoginUser, adminOrTeacherOnly, getStudentsWithNoClass); + router.get("/user/:id", verifyLoginUser, getUserById); router.get("/user/name/:name", verifyLoginUser, adminOrTeacherOnly, getUserByName);