diff --git a/controllers/auth/auth.js b/controllers/auth/auth.js index 6330fd2..0e09121 100644 --- a/controllers/auth/auth.js +++ b/controllers/auth/auth.js @@ -13,46 +13,44 @@ const transporter = nodemailer.createTransport({ }); export const registerAdmin = async (req, res) => { - const { name, email, password, confirmPassword } = req.body; + const { NAME_USERS, EMAIL, PASSWORD, CONFIRM_PASSWORD } = req.body; - if (!name) { + if (!NAME_USERS) { return response(400, null, "Name is required!", res); } - if (!email) { + if (!EMAIL) { return response(400, null, "Email is required!", res); } - if (!password) { + if (!PASSWORD) { return response(400, null, "Password is required!", res); } - if (!confirmPassword) { + if (!CONFIRM_PASSWORD) { return response(400, null, "Confirm Password is required!", res); } - if (password !== confirmPassword) { + if (PASSWORD !== CONFIRM_PASSWORD) { return response(400, null, "Passwords do not match!", res); } try { const salt = await bcrypt.genSalt(10); - const hashedPassword = await bcrypt.hash(password, salt); + const hashedPassword = await bcrypt.hash(PASSWORD, salt); const newUser = await models.User.create({ - NAME_USERS: name, - EMAIL: email, + NAME_USERS: NAME_USERS, + EMAIL: EMAIL, PASSWORD: hashedPassword, ROLE: "admin", - PICTURE: "default-avatar.jpeg", }); const adminResponse = { - id: newUser.ID, - name: newUser.NAME_USERS, - email: newUser.EMAIL, - role: newUser.ROLE, - picture: newUser.PICTURE, + ID: newUser.ID, + NAME_USERS: newUser.NAME_USERS, + EMAIL: newUser.EMAIL, + ROLE: newUser.ROLE, }; response(200, adminResponse, "Admin registration successful", res); @@ -68,29 +66,29 @@ export const registerAdmin = async (req, res) => { }; export const registerTeacher = async (req, res) => { - const { name, email, nip, password, confirmPassword } = req.body; + const { NAME_USERS, EMAIL, NIP, PASSWORD, CONFIRM_PASSWORD } = req.body; - if (!name) { + if (!NAME_USERS) { return response(400, null, "Name is required!", res); } - if (!email) { + if (!EMAIL) { return response(400, null, "Email is required!", res); } - if (!nip) { + if (!NIP) { return response(400, null, "NIP is required for teachers!", res); } - if (!password) { + if (!PASSWORD) { return response(400, null, "Password is required!", res); } - if (!confirmPassword) { + if (!CONFIRM_PASSWORD) { return response(400, null, "Confirm Password is required!", res); } - if (password !== confirmPassword) { + if (PASSWORD !== CONFIRM_PASSWORD) { return response(400, null, "Passwords do not match!", res); } @@ -98,15 +96,14 @@ export const registerTeacher = async (req, res) => { try { const salt = await bcrypt.genSalt(10); - const hashedPassword = await bcrypt.hash(password, salt); + const hashedPassword = await bcrypt.hash(PASSWORD, salt); const newUser = await models.User.create( { - NAME_USERS: name, - EMAIL: email, + NAME_USERS: NAME_USERS, + EMAIL: EMAIL, PASSWORD: hashedPassword, ROLE: "teacher", - PICTURE: "default-avatar.jpeg", }, { transaction } ); @@ -114,7 +111,7 @@ export const registerTeacher = async (req, res) => { await models.Teacher.create( { ID: newUser.ID, - NIP: nip, + NIP: NIP, }, { transaction } ); @@ -122,12 +119,11 @@ export const registerTeacher = async (req, res) => { await transaction.commit(); const teacherResponse = { - id: newUser.ID, - name: newUser.NAME_USERS, - email: newUser.EMAIL, - nip: nip, - role: newUser.ROLE, - picture: newUser.PICTURE, + ID: newUser.ID, + NAME_USERS: newUser.NAME_USERS, + EMAIL: newUser.EMAIL, + NIP: NIP, + ROLE: newUser.ROLE, }; response(200, teacherResponse, "Teacher registration successful", res); @@ -152,29 +148,29 @@ export const registerTeacher = async (req, res) => { }; export const registerStudent = async (req, res) => { - const { name, email, nisn, password, confirmPassword } = req.body; + const { NAME_USERS, EMAIL, NISN, PASSWORD, CONFIRM_PASSWORD } = req.body; - if (!name) { + if (!NAME_USERS) { return response(400, null, "Name is required!", res); } - if (!email) { + if (!EMAIL) { return response(400, null, "Email is required!", res); } - if (!nisn) { + if (!NISN) { return response(400, null, "NISN is required for students!", res); } - if (!password) { + if (!PASSWORD) { return response(400, null, "Password is required!", res); } - if (!confirmPassword) { + if (!CONFIRM_PASSWORD) { return response(400, null, "Confirm Password is required!", res); } - if (password !== confirmPassword) { + if (PASSWORD !== CONFIRM_PASSWORD) { return response(400, null, "Passwords do not match!", res); } @@ -182,15 +178,14 @@ export const registerStudent = async (req, res) => { try { const salt = await bcrypt.genSalt(10); - const hashedPassword = await bcrypt.hash(password, salt); + const hashedPassword = await bcrypt.hash(PASSWORD, salt); const newUser = await models.User.create( { - NAME_USERS: name, - EMAIL: email, + NAME_USERS: NAME_USERS, + EMAIL: EMAIL, PASSWORD: hashedPassword, ROLE: "student", - PICTURE: "default-avatar.jpeg", }, { transaction } ); @@ -198,7 +193,7 @@ export const registerStudent = async (req, res) => { await models.Student.create( { ID: newUser.ID, - NISN: nisn, + NISN: NISN, }, { transaction } ); @@ -206,12 +201,11 @@ export const registerStudent = async (req, res) => { await transaction.commit(); const studentResponse = { - id: newUser.ID, - name: newUser.NAME_USERS, - email: newUser.EMAIL, - nisn: nisn, - role: newUser.ROLE, - picture: newUser.PICTURE, + ID: newUser.ID, + NAME_USERS: newUser.NAME_USERS, + EMAIL: newUser.EMAIL, + NISN: NISN, + ROLE: newUser.ROLE, }; response(200, studentResponse, "Student registration successful", res); @@ -236,40 +230,42 @@ export const registerStudent = async (req, res) => { }; export const loginUser = async (req, res) => { - const { email, password } = req.body; + const { EMAIL, PASSWORD } = req.body; - if (!email) { + if (!EMAIL) { return response(400, null, "Email is required!", res); } - if (!password) { + if (!PASSWORD) { return response(400, null, "Password is required!", res); } try { - const user = await models.User.findOne({ where: { email } }); + const user = await models.User.findOne({ where: { EMAIL } }); if (!user) { return response(404, null, "User data not found!", res); } - const validPassword = await bcrypt.compare(password, user.PASSWORD); + const validPassword = await bcrypt.compare(PASSWORD, user.PASSWORD); if (!validPassword) { return response(401, null, "The password you entered is incorrect!", res); } const accessToken = jwt.sign( - { id: user.ID }, + { ID: user.ID, + ROLE: user.ROLE + }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: "6h" } ); const userResponse = { - id: user.ID, - name: user.NAME_USERS, - email: user.EMAIL, - roles: user.ROLE, - token: `Bearer ${accessToken}`, + ID: user.ID, + NAME_USERS: user.NAME_USERS, + EMAIL: user.EMAIL, + ROLE: user.ROLE, + TOKEN: `Bearer ${accessToken}`, }; response(200, userResponse, "Login successful", res); @@ -284,14 +280,14 @@ export const logoutUser = (req, res) => { }; export const forgotPassword = async (req, res) => { - const { email } = req.body; + const { EMAIL } = req.body; - if (!email) { + if (!EMAIL) { return response(400, null, "Email is required!", res); } try { - const user = await models.User.findOne({ where: { EMAIL: email } }); + const user = await models.User.findOne({ where: { EMAIL: EMAIL } }); if (!user) { return response(404, null, "Email is not registered!", res); @@ -327,26 +323,26 @@ export const forgotPassword = async (req, res) => { }; export const resetPassword = async (req, res) => { - const { token, newPassword, confirmNewPassword } = req.body; + const { TOKEN, NEW_PASSWORD, CONFIRM_NEW_PASSWORD } = req.body; - if (!token) { + if (!TOKEN) { return response(400, null, "Token is required!", res); } - if (!newPassword) { + if (!NEW_PASSWORD) { return response(400, null, "New password is required!", res); } - if (!confirmNewPassword) { + if (!CONFIRM_NEW_PASSWORD) { return response(400, null, "Confirm new password is required!", res); } - if (newPassword !== confirmNewPassword) { + if (NEW_PASSWORD !== CONFIRM_NEW_PASSWORD) { return response(400, null, "Passwords do not match!", res); } try { - const decoded = jwt.verify(token, process.env.RESET_PASSWORD_SECRET); + const decoded = jwt.verify(TOKEN, process.env.RESET_PASSWORD_SECRET); const user = await models.User.findOne({ where: { ID: decoded.id } }); if (!user) { @@ -354,7 +350,7 @@ export const resetPassword = async (req, res) => { } const salt = await bcrypt.genSalt(10); - const hashedPassword = await bcrypt.hash(newPassword, salt); + const hashedPassword = await bcrypt.hash(NEW_PASSWORD, salt); user.PASSWORD = hashedPassword; await user.save(); diff --git a/controllers/contentControllers/exercise.js b/controllers/contentControllers/exercise.js index af52997..a503c43 100644 --- a/controllers/contentControllers/exercise.js +++ b/controllers/contentControllers/exercise.js @@ -83,17 +83,11 @@ export const getExercisesForAdmin = async (req, res) => { if (questionType === "MCQ" && exercise.multipleChoices.length > 0) { answerKey = exercise.multipleChoices[0].ANSWER_KEY; - } else if ( - questionType === "MPQ" && - exercise.matchingPairs.length > 0 - ) { + } else if (questionType === "MPQ" && exercise.matchingPairs.length > 0) { answerKey = exercise.matchingPairs .map((pair) => `${pair.LEFT_PAIR}-${pair.RIGHT_PAIR}`) .join(", "); - } else if ( - questionType === "TFQ" && - exercise.trueFalse.length > 0 - ) { + } else if (questionType === "TFQ" && exercise.trueFalse.length > 0) { answerKey = exercise.trueFalse[0].IS_TRUE === 1 ? "true" : "false"; } @@ -168,6 +162,141 @@ export const getExerciseById = async (req, res) => { } }; +export const getExerciseByLevelId = async (req, res) => { + try { + const { idLevel } = req.params; + + const levelExists = await models.Level.findByPk(idLevel); + + if (!levelExists) { + return response(404, null, "Level not found", res); + } + + const exercises = await models.Exercise.findAll({ + where: { ID_LEVEL: idLevel }, + include: [ + { + model: models.MultipleChoices, + as: "multipleChoices", + }, + { + model: models.MatchingPairs, + as: "matchingPairs", + }, + { + model: models.TrueFalse, + as: "trueFalse", + }, + ], + }); + + if (!exercises || exercises.length === 0) { + return response(404, null, "No exercises found for this level", res); + } + + const formattedExercises = exercises.map((exercise) => { + const exerciseData = { ...exercise.dataValues }; + const questionType = exercise.QUESTION_TYPE; + + if (questionType === "MCQ") { + delete exerciseData.matchingPairs; + delete exerciseData.trueFalse; + } else if (questionType === "MPQ") { + delete exerciseData.multipleChoices; + delete exerciseData.trueFalse; + } else if (questionType === "TFQ") { + delete exerciseData.multipleChoices; + delete exerciseData.matchingPairs; + } else { + delete exerciseData.multipleChoices; + delete exerciseData.matchingPairs; + delete exerciseData.trueFalse; + } + + return exerciseData; + }); + + response(200, formattedExercises, "Success", res); + } catch (error) { + console.log(error); + res.status(500).json({ message: "Internal Server Error" }); + } +}; + +// export const deleteExerciseById = async (req, res) => { +// const { id } = req.params; +// const transaction = await models.db.transaction(); + +// try { +// const exercise = await models.Exercise.findByPk(id, { +// include: [ +// { +// model: models.MultipleChoices, +// as: "multipleChoices", +// }, +// { +// model: models.MatchingPairs, +// as: "matchingPairs", +// }, +// { +// model: models.TrueFalse, +// as: "trueFalse", +// }, +// ], +// }); + +// if (!exercise) { +// await transaction.rollback(); +// return response(404, null, "Exercise not found", res); +// } + +// if (exercise.AUDIO) { +// const audioPath = path.join( +// "public/uploads/exercise/audio", +// exercise.AUDIO +// ); +// if (fs.existsSync(audioPath)) fs.unlinkSync(audioPath); +// } + +// if (exercise.IMAGE) { +// const imagePath = path.join( +// "public/uploads/exercise/image", +// exercise.IMAGE +// ); +// if (fs.existsSync(imagePath)) fs.unlinkSync(imagePath); +// } + +// const questionType = exercise.QUESTION_TYPE; + +// if (questionType === "MCQ") { +// await models.MultipleChoices.destroy({ +// where: { ID_ADMIN_EXERCISE: id }, +// transaction, +// }); +// } else if (questionType === "MPQ") { +// await models.MatchingPairs.destroy({ +// where: { ID_ADMIN_EXERCISE: id }, +// transaction, +// }); +// } else if (questionType === "TFQ") { +// await models.TrueFalse.destroy({ +// where: { ID_ADMIN_EXERCISE: id }, +// transaction, +// }); +// } + +// await exercise.destroy({ transaction }); + +// await transaction.commit(); + +// response(200, null, "Exercise and related data deleted successfully", res); +// } catch (error) { +// console.log(error); +// await transaction.rollback(); +// response(500, null, "Internal Server Error", res); +// } +// }; + export const deleteExerciseById = async (req, res) => { const { id } = req.params; const transaction = await models.db.transaction(); @@ -195,13 +324,10 @@ export const deleteExerciseById = async (req, res) => { return response(404, null, "Exercise not found", res); } - if (exercise.VIDEO) { - const videoPath = path.join( - "public/uploads/exercise/video", - exercise.VIDEO - ); - if (fs.existsSync(videoPath)) fs.unlinkSync(videoPath); - } + await models.StdExercise.destroy({ + where: { ID_ADMIN_EXERCISE: id }, + transaction, + }); if (exercise.AUDIO) { const audioPath = path.join( @@ -254,7 +380,7 @@ export const deleteExerciseFileById = async (req, res) => { const { id } = req.params; const { fileType } = req.body; - if (!["audio", "video", "image"].includes(fileType)) { + if (!["audio", "image", "video"].includes(fileType)) { return response(400, null, "Invalid file type specified", res); } @@ -268,11 +394,7 @@ export const deleteExerciseFileById = async (req, res) => { let filePath; let fileName; - if (fileType === "video" && exercise.VIDEO) { - fileName = exercise.VIDEO; - filePath = path.join("public/uploads/exercise/video", fileName); - exercise.VIDEO = null; - } else if (fileType === "audio" && exercise.AUDIO) { + if (fileType === "audio" && exercise.AUDIO) { fileName = exercise.AUDIO; filePath = path.join("public/uploads/exercise/audio", fileName); exercise.AUDIO = null; @@ -280,6 +402,8 @@ export const deleteExerciseFileById = async (req, res) => { fileName = exercise.IMAGE; filePath = path.join("public/uploads/exercise/image", fileName); exercise.IMAGE = null; + } else if (fileType === "video" && exercise.VIDEO) { + exercise.VIDEO = null; } else { return response( 404, diff --git a/controllers/contentControllers/level.js b/controllers/contentControllers/level.js index 23148f6..37e2367 100644 --- a/controllers/contentControllers/level.js +++ b/controllers/contentControllers/level.js @@ -29,7 +29,7 @@ export const getLevels = async (req, res) => { response(200, levels, "Success", res); } catch (error) { console.log(error); - res.status(500).json({ message: "Internal Server Error" }); + response(500, null, "Error retrieving levels data!", res); } }; @@ -54,6 +54,74 @@ export const getLevelById = async (req, res) => { } response(200, level, "Success", res); + } catch (error) { + console.log(error); + response(500, null, "Internal Server Error", res); + } +}; + +export const getLevelsByTopicId = async (req, res) => { + try { + const { idTopic } = req.params; + const { ID } = req.user; + + const topicExists = await models.Topic.findByPk(idTopic); + if (!topicExists) { + return response(404, null, "Topic not found", res); + } + + const levels = await models.Level.findAll({ + where: { + ID_TOPIC: idTopic, + }, + attributes: { + exclude: [ + "ROUTE_1", + "ROUTE_2", + "ROUTE_3", + "ROUTE_4", + "ROUTE_5", + "ROUTE_6", + ], + }, + include: [ + { + model: models.StdLearning, + as: "stdLearning", + attributes: ["SCORE", "ID_STUDENT_LEARNING"], + where: { + ID: ID, + }, + required: false, + }, + ], + }); + + if (!levels || levels.length === 0) { + return res + .status(404) + .json({ message: "No levels found for the given topic." }); + } + + const levelsWithScore = levels.map((level) => { + const SCORE = + level.stdLearning.length > 0 ? level.stdLearning[0].SCORE : 0; + const ID_STUDENT_LEARNING = + level.stdLearning.length > 0 + ? level.stdLearning[0].ID_STUDENT_LEARNING + : null; + + const levelJSON = level.toJSON(); + delete levelJSON.stdLearning; + + return { + ...levelJSON, + ID_STUDENT_LEARNING, + SCORE, + }; + }); + + res.status(200).json({ message: "Success", data: levelsWithScore }); } catch (error) { console.log(error); res.status(500).json({ message: "Internal Server Error" }); @@ -61,21 +129,21 @@ export const getLevelById = async (req, res) => { }; export const createLevel = async (req, res, next) => { - const { NAME_LEVEL, ID_SECTION, ID_TOPIC, CONTENT } = req.body; - const { video, image, audio } = req.filesToSave || {}; + const { NAME_LEVEL, ID_SECTION, ID_TOPIC, CONTENT, VIDEO } = req.body; + const { AUDIO, IMAGE } = req.filesToSave || {}; if (!NAME_LEVEL) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response(400, null, "Level name is required", res); } if (!ID_SECTION) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response(400, null, "Section is required", res); } if (!ID_TOPIC) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response(400, null, "Topic is required", res); } @@ -85,7 +153,7 @@ export const createLevel = async (req, res, next) => { }); if (!sectionWithTopic) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response( 400, null, @@ -99,7 +167,7 @@ export const createLevel = async (req, res, next) => { }); if (existingLevel) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response( 409, null, @@ -114,7 +182,7 @@ export const createLevel = async (req, res, next) => { ID_TOPIC, IS_PRETEST: req.body.IS_PRETEST || 0, CONTENT, - VIDEO: null, + VIDEO: VIDEO || null, AUDIO: null, IMAGE: null, ROUTE_1: req.body.ROUTE_1, @@ -127,17 +195,13 @@ export const createLevel = async (req, res, next) => { req.body.newLevelId = newLevel.ID_LEVEL; - const videoFilename = video - ? saveFileToDisk(video, "video", ID_TOPIC, ID_SECTION, newLevel.ID_LEVEL) + const audioFilename = AUDIO + ? saveFileToDisk(AUDIO, "AUDIO", ID_TOPIC, ID_SECTION, newLevel.ID_LEVEL) : null; - const audioFilename = audio - ? saveFileToDisk(audio, "audio", ID_TOPIC, ID_SECTION, newLevel.ID_LEVEL) - : null; - const imageFilename = image - ? saveFileToDisk(image, "image", ID_TOPIC, ID_SECTION, newLevel.ID_LEVEL) + const imageFilename = IMAGE + ? saveFileToDisk(IMAGE, "IMAGE", ID_TOPIC, ID_SECTION, newLevel.ID_LEVEL) : null; - newLevel.VIDEO = videoFilename; newLevel.AUDIO = audioFilename; newLevel.IMAGE = imageFilename; await newLevel.save(); @@ -147,22 +211,22 @@ export const createLevel = async (req, res, next) => { response(201, newLevel, "Level created successfully", res); } catch (error) { console.log(error); - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response(500, null, "Internal Server Error", res); } }; export const updateLevelById = async (req, res, next) => { const { id } = req.params; - const { NAME_LEVEL, ID_SECTION, ID_TOPIC, CONTENT } = req.body; + const { NAME_LEVEL, ID_SECTION, ID_TOPIC, CONTENT, VIDEO } = req.body; - const { video, image, audio } = req.filesToSave || {}; + const { AUDIO, IMAGE } = req.filesToSave || {}; try { const level = await models.Level.findByPk(id); if (!level) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response(404, null, "Level not found", res); } @@ -171,7 +235,7 @@ export const updateLevelById = async (req, res, next) => { }); if (!sectionWithTopic) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response( 400, null, @@ -190,7 +254,7 @@ export const updateLevelById = async (req, res, next) => { }); if (existingLevel) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response( 409, null, @@ -212,26 +276,11 @@ export const updateLevelById = async (req, res, next) => { if (ID_TOPIC) level.ID_TOPIC = ID_TOPIC; if (CONTENT) level.CONTENT = CONTENT; - if (video) { - if (level.VIDEO) { - const oldVideoPath = path.join( - "public/uploads/level/video", - level.VIDEO - ); - if (fs.existsSync(oldVideoPath)) { - fs.unlinkSync(oldVideoPath); - } - } - level.VIDEO = saveFileToDisk( - video, - "video", - ID_TOPIC || level.ID_TOPIC, - ID_SECTION || level.ID_SECTION, - level.ID_LEVEL - ); + if (VIDEO) { + level.VIDEO = VIDEO; } - if (audio) { + if (AUDIO) { if (level.AUDIO) { const oldAudioPath = path.join( "public/uploads/level/audio", @@ -242,15 +291,15 @@ export const updateLevelById = async (req, res, next) => { } } level.AUDIO = saveFileToDisk( - audio, - "audio", + AUDIO, + "AUDIO", ID_TOPIC || level.ID_TOPIC, ID_SECTION || level.ID_SECTION, level.ID_LEVEL ); } - if (image) { + if (IMAGE) { if (level.IMAGE) { const oldImagePath = path.join( "public/uploads/level/image", @@ -261,8 +310,8 @@ export const updateLevelById = async (req, res, next) => { } } level.IMAGE = saveFileToDisk( - image, - "image", + IMAGE, + "IMAGE", ID_TOPIC || level.ID_TOPIC, ID_SECTION || level.ID_SECTION, level.ID_LEVEL @@ -278,7 +327,7 @@ export const updateLevelById = async (req, res, next) => { response(200, level, "Level updated successfully", res); } catch (error) { console.log(error); - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ AUDIO, IMAGE }); return response(500, null, "Internal Server Error", res); } }; @@ -299,11 +348,6 @@ export const deleteLevelById = async (req, res, next) => { } }; - if (level.VIDEO) { - const videoPath = path.join("public/uploads/level/video", level.VIDEO); - deleteFile(videoPath); - } - if (level.AUDIO) { const audioPath = path.join("public/uploads/level/audio", level.AUDIO); deleteFile(audioPath); @@ -327,6 +371,132 @@ export const deleteLevelById = async (req, res, next) => { } }; -export const unlockPreviousRoutes = async (req, res) => { - const { NEXT_LEARNING } = req.params; +export const getPreviousLevel = async (req, res) => { + try { + const { next_learning } = req.params; + const { ID } = req.user; + + const currentLevel = await models.Level.findByPk(next_learning, { + include: [ + { + model: models.StdLearning, + as: "stdLearning", + attributes: ["SCORE", "ID_STUDENT_LEARNING"], + where: { + ID: ID, + }, + required: false, + }, + ], + attributes: { + exclude: [ + "ROUTE_1", + "ROUTE_2", + "ROUTE_3", + "ROUTE_4", + "ROUTE_5", + "ROUTE_6", + ], + }, + }); + + if (!currentLevel) { + return response(404, null, "Level not found", res); + } + + const { NAME_LEVEL, ID_TOPIC } = currentLevel; + const levelNumber = parseInt(NAME_LEVEL.replace("Level ", "")); + + if (isNaN(levelNumber)) { + return response(400, null, "Invalid level format", res); + } + + const previousLevels = await models.Level.findAll({ + where: { + ID_TOPIC: ID_TOPIC, + NAME_LEVEL: { + [models.Op.or]: [ + { [models.Op.like]: "Pretest" }, + { [models.Op.regexp]: `^Level [0-9]+$` }, + ], + }, + [models.Op.or]: [ + { IS_PRETEST: 1 }, + { + NAME_LEVEL: { + [models.Op.lt]: `Level ${levelNumber}`, + }, + }, + ], + }, + order: [["NAME_LEVEL", "ASC"]], + attributes: { + exclude: [ + "ROUTE_1", + "ROUTE_2", + "ROUTE_3", + "ROUTE_4", + "ROUTE_5", + "ROUTE_6", + ], + }, + include: [ + { + model: models.StdLearning, + as: "stdLearning", + attributes: ["SCORE", "ID_STUDENT_LEARNING"], + where: { + ID: ID, + }, + required: false, + }, + ], + }); + + const previousLevelsWithScore = previousLevels.map((level) => { + const SCORE = + level.stdLearning.length > 0 ? level.stdLearning[0].SCORE : 0; + const ID_STUDENT_LEARNING = + level.stdLearning.length > 0 + ? level.stdLearning[0].ID_STUDENT_LEARNING + : null; + + const levelJSON = level.toJSON(); + delete levelJSON.stdLearning; + + return { + ...levelJSON, + ID_STUDENT_LEARNING, + SCORE, + }; + }); + + const currentLevelWithScore = { + ...currentLevel.toJSON(), + ID_STUDENT_LEARNING: + currentLevel.stdLearning.length > 0 + ? currentLevel.stdLearning[0].ID_STUDENT_LEARNING + : null, + SCORE: + currentLevel.stdLearning.length > 0 + ? currentLevel.stdLearning[0].SCORE + : 0, + }; + + delete currentLevelWithScore.stdLearning; + + if (!previousLevelsWithScore.length && !currentLevelWithScore) { + return response(404, null, "No levels found", res); + } + + const result = { + currentLevel: currentLevelWithScore, + previousLevels: previousLevelsWithScore, + }; + + response(200, result, "Success", res); + } catch (error) { + console.log(error); + res.status(500).json({ message: "Internal Server Error" }); + } }; diff --git a/controllers/contentControllers/section.js b/controllers/contentControllers/section.js index 5f2938e..7710aae 100644 --- a/controllers/contentControllers/section.js +++ b/controllers/contentControllers/section.js @@ -36,15 +36,15 @@ export const getSectionById = async (req, res) => { export const createSection = async (req, res) => { const { NAME_SECTION, DESCRIPTION_SECTION } = req.body; - const { thumbnail } = req.filesToSave || {}; + const { THUMBNAIL } = req.filesToSave || {}; if (!NAME_SECTION) { - clearFileBuffers({ thumbnail }); + clearFileBuffers({ THUMBNAIL }); return response(400, null, "Section name is required", res); } if (!DESCRIPTION_SECTION) { - clearFileBuffers({ thumbnail }); + clearFileBuffers({ THUMBNAIL }); return response(400, null, "Description is required", res); } @@ -55,8 +55,8 @@ export const createSection = async (req, res) => { THUMBNAIL: null, }); - const thumbnailFilename = thumbnail - ? saveFileToDisk(thumbnail, "thumbnail", newSection.ID_SECTION) + const thumbnailFilename = THUMBNAIL + ? saveFileToDisk(THUMBNAIL, "THUMBNAIL", newSection.ID_SECTION) : null; newSection.THUMBNAIL = thumbnailFilename; @@ -65,7 +65,7 @@ export const createSection = async (req, res) => { response(201, newSection, "Section created successfully", res); } catch (error) { console.log(error); - clearFileBuffers({ thumbnail }); + clearFileBuffers({ THUMBNAIL }); response(500, null, "Internal Server Error", res); } }; @@ -74,20 +74,20 @@ export const updateSectionById = async (req, res) => { const { id } = req.params; const { NAME_SECTION, DESCRIPTION_SECTION } = req.body; - const { thumbnail } = req.filesToSave || {}; + const { THUMBNAIL } = req.filesToSave || {}; try { const section = await models.Section.findByPk(id); if (!section) { - clearFileBuffers({ thumbnail }); + clearFileBuffers({ THUMBNAIL }); return response(404, null, "Section not found", res); } if (NAME_SECTION) section.NAME_SECTION = NAME_SECTION; if (DESCRIPTION_SECTION) section.DESCRIPTION_SECTION = DESCRIPTION_SECTION; - if (thumbnail) { + if (THUMBNAIL) { if (section.THUMBNAIL) { const oldThumbnailPath = path.join( "public/uploads/section", @@ -98,8 +98,8 @@ export const updateSectionById = async (req, res) => { } } section.THUMBNAIL = saveFileToDisk( - thumbnail, - "thumbnail", + THUMBNAIL, + "THUMBNAIL", section.ID_SECTION ); } @@ -109,7 +109,7 @@ export const updateSectionById = async (req, res) => { response(200, section, "Section updated successfully", res); } catch (error) { console.log(error); - clearFileBuffers({ thumbnail }); + clearFileBuffers({ THUMBNAIL }); response(500, null, "Internal Server Error", res); } }; diff --git a/controllers/contentControllers/topic.js b/controllers/contentControllers/topic.js index 9e032e8..71b8527 100644 --- a/controllers/contentControllers/topic.js +++ b/controllers/contentControllers/topic.js @@ -27,8 +27,32 @@ export const getTopicById = async (req, res) => { } }; +export const getTopicBySectionId = async (req, res) => { + try { + const { sectionId } = req.params; + + const sectionExists = await models.Section.findByPk(sectionId); + if (!sectionExists) { + return response(404, null, "Section not found", res); + } + + const topics = await models.Topic.findAll({ + where: { ID_SECTION: sectionId }, + }); + + if (!topics || topics.length === 0) { + return response(404, null, "No topics found for this section", res); + } + + response(200, topics, "Success", res); + } catch (error) { + console.log(error); + response(500, null, "Internal Server Error", res); + } +}; + export const createTopic = async (req, res) => { - const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC } = req.body; + const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC, OBJECTIVES } = req.body; if (!ID_SECTION) { return response(400, null, "Section ID is required", res); @@ -39,7 +63,11 @@ export const createTopic = async (req, res) => { } if (!DESCRIPTION_TOPIC) { - 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 { @@ -52,6 +80,7 @@ export const createTopic = async (req, res) => { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC, + OBJECTIVES, }); response(201, newTopic, "Topic created successfully", res); @@ -63,7 +92,7 @@ export const createTopic = async (req, res) => { export const updateTopicById = async (req, res) => { const { id } = req.params; - const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC } = req.body; + const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC, OBJECTIVES } = req.body; try { const topic = await models.Topic.findByPk(id); @@ -88,6 +117,10 @@ 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); @@ -115,3 +148,75 @@ export const deleteTopicById = async (req, res) => { response(500, null, "Internal Server Error", res); } }; + +export const getCompletedTopics = async (req, res) => { + try { + const user = req.user; + const userId = user.ID; + + const completedLevels = await models.StdLearning.findAll({ + where: { + ID: userId, + IS_PASS: 1, + }, + include: [ + { + model: models.Level, + as: "level", + include: [ + { + model: models.Topic, + as: "levelTopic", + attributes: [ + "ID_TOPIC", + "NAME_TOPIC", + "DESCRIPTION_TOPIC", + "OBJECTIVES", + ], + }, + ], + }, + ], + }); + + if (!completedLevels.length) { + return response(404, null, "No completed topics found", res); + } + + const completedTopics = []; + for (const levelRecord of completedLevels) { + const level = levelRecord.level; + + const level6 = await models.Level.findOne({ + where: { + NAME_LEVEL: "Level 6", + ID_TOPIC: level.ID_TOPIC, + }, + }); + + if (level6 && level.ID_LEVEL === level6.ID_LEVEL) { + const topic = level.levelTopic; + completedTopics.push({ + ID_TOPIC: topic.ID_TOPIC, + NAME_TOPIC: topic.NAME_TOPIC, + DESCRIPTION_TOPIC: topic.DESCRIPTION_TOPIC, + OBJECTIVES: topic.OBJECTIVES, + }); + } + } + + if (!completedTopics.length) { + return response(404, null, "No completed topics for Level 6 found", res); + } + + response( + 200, + completedTopics, + "Completed topics fetched successfully", + res + ); + } catch (error) { + console.error(error); + response(500, null, "Internal Server Error", res); + } +}; diff --git a/controllers/exerciseTypesControllers/matchingPairs.js b/controllers/exerciseTypesControllers/matchingPairs.js index 59c4ed6..99e3442 100644 --- a/controllers/exerciseTypesControllers/matchingPairs.js +++ b/controllers/exerciseTypesControllers/matchingPairs.js @@ -8,7 +8,7 @@ import fs from "fs"; import path from "path"; export const createMatchingPairsExercise = async (req, res) => { - const { ID_LEVEL, TITLE, QUESTION, SCORE_WEIGHT } = req.body; + const { ID_LEVEL, TITLE, QUESTION, SCORE_WEIGHT, VIDEO } = req.body; let PAIRS = req.body.PAIRS; try { @@ -19,7 +19,7 @@ export const createMatchingPairsExercise = async (req, res) => { return response(400, null, "Invalid PAIRS format", res); } - const { video, image, audio } = req.filesToSave || {}; + const { IMAGE, AUDIO } = req.filesToSave || {}; if (!ID_LEVEL) return response(400, null, "Level ID is required", res); if (!QUESTION) return response(400, null, "Question is required", res); @@ -33,7 +33,7 @@ export const createMatchingPairsExercise = async (req, res) => { try { const level = await models.Level.findByPk(ID_LEVEL); if (!level) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); return response(404, null, "Level not found", res); } @@ -49,7 +49,7 @@ export const createMatchingPairsExercise = async (req, res) => { where: { ID_LEVEL, TITLE: generatedTitle }, }); if (existingExercise) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); return response( 400, null, @@ -66,23 +66,19 @@ export const createMatchingPairsExercise = async (req, res) => { SCORE_WEIGHT, QUESTION_TYPE: "MPQ", AUDIO: null, - VIDEO: null, + VIDEO: VIDEO || null, IMAGE: null, }, { transaction } ); - const videoFilename = video - ? saveFileToDisk(video, "video", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) + const audioFilename = AUDIO + ? saveFileToDisk(AUDIO, "AUDIO", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) : null; - const audioFilename = audio - ? saveFileToDisk(audio, "audio", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) - : null; - const imageFilename = image - ? saveFileToDisk(image, "image", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) + const imageFilename = IMAGE + ? saveFileToDisk(IMAGE, "IMAGE", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) : null; - newExercise.VIDEO = videoFilename; newExercise.AUDIO = audioFilename; newExercise.IMAGE = imageFilename; await newExercise.save({ transaction }); @@ -111,14 +107,14 @@ export const createMatchingPairsExercise = async (req, res) => { } catch (error) { console.error(error); await transaction.rollback(); - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); response(500, null, "Internal Server Error", res); } }; export const updateMatchingPairsExerciseById = async (req, res) => { const { id } = req.params; - const { ID_LEVEL, QUESTION, SCORE_WEIGHT } = req.body; + const { ID_LEVEL, QUESTION, SCORE_WEIGHT, VIDEO } = req.body; let PAIRS = req.body.PAIRS; try { @@ -129,14 +125,14 @@ export const updateMatchingPairsExerciseById = async (req, res) => { return response(400, null, "Invalid PAIRS format", res); } - const { video, image, audio } = req.filesToSave || {}; + const { IMAGE, AUDIO } = req.filesToSave || {}; const transaction = await models.db.transaction(); try { const exercise = await models.Exercise.findByPk(id, { transaction }); if (!exercise) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); return response(404, null, "Exercise not found", res); } @@ -144,7 +140,7 @@ export const updateMatchingPairsExerciseById = async (req, res) => { if (ID_LEVEL) { const level = await models.Level.findByPk(ID_LEVEL, { transaction }); if (!level) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); return response(404, null, "Level not found", res); } @@ -153,26 +149,9 @@ export const updateMatchingPairsExerciseById = async (req, res) => { if (QUESTION) exercise.QUESTION = QUESTION; if (SCORE_WEIGHT) exercise.SCORE_WEIGHT = SCORE_WEIGHT; + if (VIDEO) exercise.VIDEO = VIDEO; - if (video) { - if (exercise.VIDEO) { - const oldVideoPath = path.join( - "public/uploads/exercise/video", - exercise.VIDEO - ); - if (fs.existsSync(oldVideoPath)) { - fs.unlinkSync(oldVideoPath); - } - } - exercise.VIDEO = saveFileToDisk( - video, - "video", - ID_LEVEL || exercise.ID_LEVEL, - exercise.ID_ADMIN_EXERCISE - ); - } - - if (audio) { + if (AUDIO) { if (exercise.AUDIO) { const oldAudioPath = path.join( "public/uploads/exercise/audio", @@ -183,14 +162,14 @@ export const updateMatchingPairsExerciseById = async (req, res) => { } } exercise.AUDIO = saveFileToDisk( - audio, - "audio", + AUDIO, + "AUDIO", ID_LEVEL || exercise.ID_LEVEL, exercise.ID_ADMIN_EXERCISE ); } - if (image) { + if (IMAGE) { if (exercise.IMAGE) { const oldImagePath = path.join( "public/uploads/exercise/image", @@ -201,8 +180,8 @@ export const updateMatchingPairsExerciseById = async (req, res) => { } } exercise.IMAGE = saveFileToDisk( - image, - "image", + IMAGE, + "IMAGE", ID_LEVEL || exercise.ID_LEVEL, exercise.ID_ADMIN_EXERCISE ); @@ -267,7 +246,7 @@ export const updateMatchingPairsExerciseById = async (req, res) => { } catch (error) { console.error(error); await transaction.rollback(); - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); response(500, null, "Internal Server Error", res); } }; diff --git a/controllers/exerciseTypesControllers/multipleChoices.js b/controllers/exerciseTypesControllers/multipleChoices.js index 2ef2729..26940da 100644 --- a/controllers/exerciseTypesControllers/multipleChoices.js +++ b/controllers/exerciseTypesControllers/multipleChoices.js @@ -19,9 +19,10 @@ export const createMultipleChoicesExercise = async (req, res) => { OPTION_E, ANSWER_KEY, SCORE_WEIGHT, + VIDEO, } = req.body; - const { video, image, audio } = req.filesToSave || {}; + const { IMAGE, AUDIO } = req.filesToSave || {}; if (!ID_LEVEL) return response(400, null, "Level ID is required", res); if (!QUESTION) return response(400, null, "Question is required", res); @@ -39,7 +40,7 @@ export const createMultipleChoicesExercise = async (req, res) => { try { const level = await models.Level.findByPk(ID_LEVEL); if (!level) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); return response(404, null, "Level not found", res); } @@ -55,7 +56,7 @@ export const createMultipleChoicesExercise = async (req, res) => { where: { ID_LEVEL, TITLE: generatedTitle }, }); if (existingExercise) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); return response( 400, null, @@ -72,23 +73,19 @@ export const createMultipleChoicesExercise = async (req, res) => { SCORE_WEIGHT, QUESTION_TYPE: "MCQ", AUDIO: null, - VIDEO: null, + VIDEO: VIDEO || null, IMAGE: null, }, { transaction } ); - const videoFilename = video - ? saveFileToDisk(video, "video", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) + const audioFilename = AUDIO + ? saveFileToDisk(AUDIO, "AUDIO", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) : null; - const audioFilename = audio - ? saveFileToDisk(audio, "audio", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) - : null; - const imageFilename = image - ? saveFileToDisk(image, "image", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) + const imageFilename = IMAGE + ? saveFileToDisk(IMAGE, "IMAGE", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) : null; - newExercise.VIDEO = videoFilename; newExercise.AUDIO = audioFilename; newExercise.IMAGE = imageFilename; await newExercise.save({ transaction }); @@ -117,7 +114,7 @@ export const createMultipleChoicesExercise = async (req, res) => { } catch (error) { console.error(error); await transaction.rollback(); - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); response(500, null, "Internal Server Error", res); } }; @@ -134,9 +131,10 @@ export const updateMultipleChoicesExerciseById = async (req, res) => { OPTION_E, ANSWER_KEY, SCORE_WEIGHT, + VIDEO, } = req.body; - const { video, image, audio } = req.filesToSave || {}; + const { IMAGE, AUDIO } = req.filesToSave || {}; const transaction = await models.db.transaction(); @@ -144,7 +142,7 @@ export const updateMultipleChoicesExerciseById = async (req, res) => { const exercise = await models.Exercise.findByPk(id, { transaction }); if (!exercise) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); return response(404, null, "Exercise not found", res); } @@ -152,7 +150,7 @@ export const updateMultipleChoicesExerciseById = async (req, res) => { if (ID_LEVEL) { const level = await models.Level.findByPk(ID_LEVEL, { transaction }); if (!level) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); return response(404, null, "Level not found", res); } @@ -161,26 +159,9 @@ export const updateMultipleChoicesExerciseById = async (req, res) => { if (QUESTION) exercise.QUESTION = QUESTION; if (SCORE_WEIGHT) exercise.SCORE_WEIGHT = SCORE_WEIGHT; + if (VIDEO) exercise.VIDEO = VIDEO; - if (video) { - if (exercise.VIDEO) { - const oldVideoPath = path.join( - "public/uploads/exercise/video", - exercise.VIDEO - ); - if (fs.existsSync(oldVideoPath)) { - fs.unlinkSync(oldVideoPath); - } - } - exercise.VIDEO = saveFileToDisk( - video, - "video", - ID_LEVEL || exercise.ID_LEVEL, - exercise.ID_ADMIN_EXERCISE - ); - } - - if (audio) { + if (AUDIO) { if (exercise.AUDIO) { const oldAudioPath = path.join( "public/uploads/exercise/audio", @@ -191,14 +172,14 @@ export const updateMultipleChoicesExerciseById = async (req, res) => { } } exercise.AUDIO = saveFileToDisk( - audio, - "audio", + AUDIO, + "AUDIO", ID_LEVEL || exercise.ID_LEVEL, exercise.ID_ADMIN_EXERCISE ); } - if (image) { + if (IMAGE) { if (exercise.IMAGE) { const oldImagePath = path.join( "public/uploads/exercise/image", @@ -209,8 +190,8 @@ export const updateMultipleChoicesExerciseById = async (req, res) => { } } exercise.IMAGE = saveFileToDisk( - image, - "image", + IMAGE, + "IMAGE", ID_LEVEL || exercise.ID_LEVEL, exercise.ID_ADMIN_EXERCISE ); @@ -224,7 +205,7 @@ export const updateMultipleChoicesExerciseById = async (req, res) => { }); if (!multipleChoices) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); return response(404, null, "Multiple Choices not found", res); } @@ -248,7 +229,7 @@ export const updateMultipleChoicesExerciseById = async (req, res) => { response(200, payload, "Exercise updated successfully", res); } catch (error) { console.log(error); - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); response(500, null, "Internal Server Error", res); } diff --git a/controllers/exerciseTypesControllers/trueFalse.js b/controllers/exerciseTypesControllers/trueFalse.js index 5549640..20a0e6c 100644 --- a/controllers/exerciseTypesControllers/trueFalse.js +++ b/controllers/exerciseTypesControllers/trueFalse.js @@ -8,9 +8,8 @@ import fs from "fs"; import path from "path"; export const createTrueFalseExercise = async (req, res) => { - const { ID_LEVEL, TITLE, QUESTION, IS_TRUE, SCORE_WEIGHT } = req.body; - - const { video, image, audio } = req.filesToSave || {}; + const { ID_LEVEL, TITLE, QUESTION, IS_TRUE, SCORE_WEIGHT, VIDEO } = req.body; + const { IMAGE, AUDIO } = req.filesToSave || {}; if (!ID_LEVEL) return response(400, null, "Level ID is required", res); if (!QUESTION) return response(400, null, "Question is required", res); @@ -24,7 +23,7 @@ export const createTrueFalseExercise = async (req, res) => { try { const level = await models.Level.findByPk(ID_LEVEL); if (!level) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); return response(404, null, "Level not found", res); } @@ -40,7 +39,7 @@ export const createTrueFalseExercise = async (req, res) => { where: { ID_LEVEL, TITLE: generatedTitle }, }); if (existingExercise) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); return response( 400, null, @@ -57,23 +56,19 @@ export const createTrueFalseExercise = async (req, res) => { SCORE_WEIGHT, QUESTION_TYPE: "TFQ", AUDIO: null, - VIDEO: null, + VIDEO: VIDEO || null, IMAGE: null, }, { transaction } ); - const videoFilename = video - ? saveFileToDisk(video, "video", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) + const audioFilename = AUDIO + ? saveFileToDisk(AUDIO, "AUDIO", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) : null; - const audioFilename = audio - ? saveFileToDisk(audio, "audio", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) - : null; - const imageFilename = image - ? saveFileToDisk(image, "image", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) + const imageFilename = IMAGE + ? saveFileToDisk(IMAGE, "IMAGE", ID_LEVEL, newExercise.ID_ADMIN_EXERCISE) : null; - newExercise.VIDEO = videoFilename; newExercise.AUDIO = audioFilename; newExercise.IMAGE = imageFilename; await newExercise.save({ transaction }); @@ -97,16 +92,15 @@ export const createTrueFalseExercise = async (req, res) => { } catch (error) { console.error(error); await transaction.rollback(); - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); response(500, null, "Internal Server Error", res); } }; export const updateTrueFalseExerciseById = async (req, res) => { const { id } = req.params; - const { ID_LEVEL, QUESTION, IS_TRUE, SCORE_WEIGHT } = req.body; - - const { video, image, audio } = req.filesToSave || {}; + const { ID_LEVEL, QUESTION, IS_TRUE, SCORE_WEIGHT, VIDEO } = req.body; + const { IMAGE, AUDIO } = req.filesToSave || {}; const transaction = await models.db.transaction(); @@ -114,7 +108,7 @@ export const updateTrueFalseExerciseById = async (req, res) => { const exercise = await models.Exercise.findByPk(id, { transaction }); if (!exercise) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); return response(404, null, "Exercise not found", res); } @@ -122,7 +116,7 @@ export const updateTrueFalseExerciseById = async (req, res) => { if (ID_LEVEL) { const level = await models.Level.findByPk(ID_LEVEL, { transaction }); if (!level) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); return response(404, null, "Level not found", res); } @@ -131,26 +125,9 @@ export const updateTrueFalseExerciseById = async (req, res) => { if (QUESTION) exercise.QUESTION = QUESTION; if (SCORE_WEIGHT) exercise.SCORE_WEIGHT = SCORE_WEIGHT; + if (VIDEO) exercise.VIDEO = VIDEO; - if (video) { - if (exercise.VIDEO) { - const oldVideoPath = path.join( - "public/uploads/exercise/video", - exercise.VIDEO - ); - if (fs.existsSync(oldVideoPath)) { - fs.unlinkSync(oldVideoPath); - } - } - exercise.VIDEO = saveFileToDisk( - video, - "video", - ID_LEVEL || exercise.ID_LEVEL, - exercise.ID_ADMIN_EXERCISE - ); - } - - if (audio) { + if (AUDIO) { if (exercise.AUDIO) { const oldAudioPath = path.join( "public/uploads/exercise/audio", @@ -161,14 +138,14 @@ export const updateTrueFalseExerciseById = async (req, res) => { } } exercise.AUDIO = saveFileToDisk( - audio, - "audio", + AUDIO, + "AUDIO", ID_LEVEL || exercise.ID_LEVEL, exercise.ID_ADMIN_EXERCISE ); } - if (image) { + if (IMAGE) { if (exercise.IMAGE) { const oldImagePath = path.join( "public/uploads/exercise/image", @@ -179,8 +156,8 @@ export const updateTrueFalseExerciseById = async (req, res) => { } } exercise.IMAGE = saveFileToDisk( - image, - "image", + IMAGE, + "IMAGE", ID_LEVEL || exercise.ID_LEVEL, exercise.ID_ADMIN_EXERCISE ); @@ -194,7 +171,7 @@ export const updateTrueFalseExerciseById = async (req, res) => { }); if (!trueFalse) { - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); return response(404, null, "True/False exercise not found", res); } @@ -215,8 +192,8 @@ export const updateTrueFalseExerciseById = async (req, res) => { response(200, payload, "True/False exercise updated successfully", res); } catch (error) { console.error(error); - clearFileBuffers({ video, image, audio }); + clearFileBuffers({ IMAGE, AUDIO }); await transaction.rollback(); response(500, null, "Internal Server Error", res); } -}; +}; \ No newline at end of file diff --git a/controllers/usersControllers/user.js b/controllers/usersControllers/user.js index d5ffa91..748f009 100644 --- a/controllers/usersControllers/user.js +++ b/controllers/usersControllers/user.js @@ -129,7 +129,7 @@ export const getUserById = async (req, res) => { try { const { id } = req.params; - const user = await models.User.findByPk(id, { + const userWithDetails = await models.User.findByPk(id, { attributes: { exclude: ["PASSWORD"], }, @@ -143,32 +143,45 @@ export const getUserById = async (req, res) => { model: models.Student, as: "students", attributes: ["NISN"], + include: [ + { + model: models.Class, + as: "studentClass", + attributes: ["NAME_CLASS"], + }, + ], }, ], }); - if (!user) { + if (!userWithDetails) { return response(404, null, "User not found", res); } let additionalField = null; - if (user.ROLE === "teacher") { - additionalField = { NIP: user.teachers.NIP }; - } else if (user.ROLE === "student") { - additionalField = { NISN: user.students.NISN }; + if (userWithDetails.ROLE === "teacher") { + additionalField = { NIP: userWithDetails.teachers.NIP }; + } else if (userWithDetails.ROLE === "student") { + additionalField = { + NISN: userWithDetails.students.NISN, + NAME_CLASS: userWithDetails.students.studentClass + ? userWithDetails.students.studentClass.NAME_CLASS + : null, + }; } const responseObject = { - ID: user.ID, - NAME_USERS: user.NAME_USERS, - EMAIL: user.EMAIL, - ROLE: user.ROLE, + ID: userWithDetails.ID, + NAME_USERS: userWithDetails.NAME_USERS, + EMAIL: userWithDetails.EMAIL, + ROLE: userWithDetails.ROLE, ...additionalField, + PICTURE: userWithDetails.PICTURE, }; response(200, responseObject, "Success", res); } catch (error) { - console.log(error); + console.error(error); response(500, null, "Error retrieving user data!", res); } }; @@ -177,7 +190,7 @@ export const getUserByName = async (req, res) => { try { const { name } = req.params; - const user = await models.User.findOne({ + const userWithDetails = await models.User.findOne({ where: { NAME_USERS: name }, attributes: { exclude: ["PASSWORD"], @@ -192,44 +205,57 @@ export const getUserByName = async (req, res) => { model: models.Student, as: "students", attributes: ["NISN"], + include: [ + { + model: models.Class, + as: "studentClass", + attributes: ["NAME_CLASS"], + }, + ], }, ], }); - if (!user) { + if (!userWithDetails) { return response(404, null, "User not found", res); } let additionalField = null; - if (user.ROLE === "teacher" ) { - additionalField = { NIP: user.teachers.NIP }; - } else if (user.ROLE === "student") { - additionalField = { NISN: user.students.NISN }; + if (userWithDetails.ROLE === "teacher") { + additionalField = { NIP: userWithDetails.teachers.NIP }; + } else if (userWithDetails.ROLE === "student") { + additionalField = { + NISN: userWithDetails.students.NISN, + NAME_CLASS: userWithDetails.students.studentClass + ? userWithDetails.students.studentClass.NAME_CLASS + : null, + }; } const responseObject = { - ID: user.ID, - NAME_USERS: user.NAME_USERS, - EMAIL: user.EMAIL, - ROLE: user.ROLE, + ID: userWithDetails.ID, + NAME_USERS: userWithDetails.NAME_USERS, + EMAIL: userWithDetails.EMAIL, + ROLE: userWithDetails.ROLE, ...additionalField, + PICTURE: userWithDetails.PICTURE, }; - return response(200, responseObject, "Success", res); + response(200, responseObject, "Success", res); } catch (error) { - console.log(error); - return response(500, null, "Error retrieving user data!", res); + console.error(error); + response(500, null, "Error retrieving user data!", res); } }; export const updateUserById = async (req, res) => { const transaction = await models.db.transaction(); - const { picture } = req.filesToSave || {}; + const { PICTURE } = req.filesToSave || {}; try { const { id } = req.params; - const { name, email, nip, nisn } = req.body; + const { NAME_USERS, EMAIL, NIP, NISN } = req.body; const user = await models.User.findByPk(id, { include: [ @@ -248,77 +274,77 @@ export const updateUserById = async (req, res) => { }); if (!user) { - clearFileBuffers({ picture }); + clearFileBuffers({ PICTURE }); await transaction.rollback(); return response(404, null, "User not found", res); } - if (user.ROLE === "teacher" && nisn) { - clearFileBuffers({ picture }); + if (user.ROLE === "teacher" && NISN) { + clearFileBuffers({ PICTURE }); await transaction.rollback(); return response(400, null, "Role is teacher, but NISN is provided", res); } - if (user.ROLE === "student" && nip) { - clearFileBuffers({ picture }); + if (user.ROLE === "student" && NIP) { + clearFileBuffers({ PICTURE }); await transaction.rollback(); return response(400, null, "Role is student, but NIP is provided", res); } - if (email && email !== user.EMAIL) { + if (EMAIL && EMAIL !== user.EMAIL) { const emailExists = await models.User.findOne({ - where: { EMAIL: email }, + where: { EMAIL: EMAIL }, transaction, }); if (emailExists) { - clearFileBuffers({ picture }); + clearFileBuffers({ PICTURE }); await transaction.rollback(); return response(400, null, "Email already in use", res); } - user.EMAIL = email; + user.EMAIL = EMAIL; } - user.NAME_USERS = name || user.NAME_USERS; + user.NAME_USERS = NAME_USERS || user.NAME_USERS; - if (user.ROLE === "teacher" && nip) { + if (user.ROLE === "teacher" && NIP) { let teacher = await models.Teacher.findOne({ where: { ID: id }, transaction, }); if (teacher) { - teacher.NIP = nip; + teacher.NIP = NIP; await teacher.save({ transaction }); } else { teacher = await models.Teacher.create( - { ID: id, NIP: nip }, + { ID: id, NIP: NIP }, { transaction } ); } } - if (user.ROLE === "student" && nisn) { + if (user.ROLE === "student" && NISN) { let student = await models.Student.findOne({ where: { ID: id }, transaction, }); if (student) { - student.NISN = nisn; + student.NISN = NISN; await student.save({ transaction }); } else { student = await models.Student.create( - { ID: id, NISN: nisn }, + { ID: id, NISN: NISN }, { transaction } ); } } - if (picture) { + if (PICTURE) { if (user.PICTURE) { const oldPicturePath = path.join("public/uploads/avatar", user.PICTURE); if (fs.existsSync(oldPicturePath)) { fs.unlinkSync(oldPicturePath); } } - user.PICTURE = saveFileToDisk(picture, user.ID, user.NAME_USERS); + user.PICTURE = saveFileToDisk(PICTURE, user.ID, user.NAME_USERS); } await user.save({ transaction }); @@ -341,9 +367,23 @@ export const updateUserById = async (req, res) => { await transaction.commit(); - return response(200, user, "User updated successfully", res); + let userResponse = { + ID: user.ID, + NAME_USERS: user.NAME_USERS, + EMAIL: user.EMAIL, + ROLE: user.ROLE, + PICTURE: user.PICTURE, + }; + + if (user.ROLE === "student" && user.students) { + userResponse.NISN = user.students.NISN; + } else if (user.ROLE === "teacher" && user.teachers) { + userResponse.NIP = user.teachers.NIP; + } + + return response(200, userResponse, "User updated successfully", res); } catch (error) { - clearFileBuffers({ picture }); + clearFileBuffers({ PICTURE }); await transaction.rollback(); console.log(error); return response(500, null, "Internal Server Error", res); @@ -353,13 +393,13 @@ export const updateUserById = async (req, res) => { export const updateUserPasswordById = async (req, res) => { try { const { id } = req.params; - const { oldPassword, password, confirmPassword } = req.body; + const { OLD_PASSWORD, PASSWORD, CONFIRM_PASSWORD } = req.body; - if (!oldPassword || !password || !confirmPassword) { + if (!OLD_PASSWORD || !PASSWORD || !CONFIRM_PASSWORD) { return response(400, null, "All fields must be filled.", res); } - if (password !== confirmPassword) { + if (PASSWORD !== CONFIRM_PASSWORD) { return response( 400, null, @@ -373,13 +413,13 @@ export const updateUserPasswordById = async (req, res) => { return response(404, null, "User not found.", res); } - const isMatch = await bcrypt.compare(oldPassword, user.PASSWORD); + const isMatch = await bcrypt.compare(OLD_PASSWORD, user.PASSWORD); if (!isMatch) { return response(400, null, "Incorrect old password.", res); } const salt = await bcrypt.genSalt(10); - const hashedPassword = await bcrypt.hash(password, salt); + const hashedPassword = await bcrypt.hash(PASSWORD, salt); user.PASSWORD = hashedPassword; await user.save(); @@ -422,23 +462,29 @@ export const deleteUserById = async (req, res) => { export const getMe = async (req, res) => { try { - const user = req.user; // User object from verifyLoginUser middleware + const user = req.user; - // Retrieve teacher or student details based on the user's role const userWithDetails = await models.User.findByPk(user.ID, { attributes: { - exclude: ["PASSWORD"], // Exclude sensitive information + exclude: ["PASSWORD"], }, include: [ { model: models.Teacher, as: "teachers", - attributes: ["NIP"], // Include NIP for teacher + attributes: ["NIP"], }, { model: models.Student, as: "students", - attributes: ["NISN"], // Include NISN for student + attributes: ["NISN"], + include: [ + { + model: models.Class, + as: "studentClass", + attributes: ["NAME_CLASS"], + }, + ], }, ], }); @@ -447,27 +493,30 @@ export const getMe = async (req, res) => { return response(404, null, "User not found", res); } - // Determine additional field based on user role let additionalField = null; if (userWithDetails.ROLE === "teacher") { additionalField = { NIP: userWithDetails.teachers.NIP }; } else if (userWithDetails.ROLE === "student") { - additionalField = { NISN: userWithDetails.students.NISN }; + additionalField = { + NISN: userWithDetails.students.NISN, + NAME_CLASS: userWithDetails.students.studentClass + ? userWithDetails.students.studentClass.NAME_CLASS + : null, + }; } - // Construct the response object const responseObject = { ID: userWithDetails.ID, NAME_USERS: userWithDetails.NAME_USERS, EMAIL: userWithDetails.EMAIL, ROLE: userWithDetails.ROLE, ...additionalField, + PICTURE: userWithDetails.PICTURE, }; - // Send the response response(200, responseObject, "Success", res); } catch (error) { console.error(error); response(500, null, "Error retrieving user data!", res); } -}; \ No newline at end of file +}; diff --git a/middlewares/Level/uploadLevel.js b/middlewares/Level/uploadLevel.js index d8362c5..69ee642 100644 --- a/middlewares/Level/uploadLevel.js +++ b/middlewares/Level/uploadLevel.js @@ -10,20 +10,7 @@ const fileFilter = (req, file, cb) => { const ext = path.extname(file.originalname).toLowerCase(); switch (file.fieldname) { - case "video": - if (ext === ".mp4") { - cb(null, true); - } else { - cb( - new Error( - "Invalid file type, only .mp4 files are allowed for video!" - ), - false - ); - } - break; - - case "audio": + case "AUDIO": if (ext === ".mp3") { cb(null, true); } else { @@ -36,7 +23,7 @@ const fileFilter = (req, file, cb) => { } break; - case "image": + case "IMAGE": if (ext === ".jpg" || ext === ".jpeg" || ext === ".png") { cb(null, true); } else { @@ -61,9 +48,8 @@ const upload = multer({ fileSize: 100 * 1024 * 1024, }, }).fields([ - { name: "video", maxCount: 1 }, - { name: "audio", maxCount: 1 }, - { name: "image", maxCount: 1 }, + { name: "AUDIO", maxCount: 1 }, + { name: "IMAGE", maxCount: 1 }, ]); const handleUpload = (req, res, next) => { @@ -73,42 +59,35 @@ const handleUpload = (req, res, next) => { } const files = req.files; - const video = files?.video ? files.video[0] : null; - const audio = files?.audio ? files.audio[0] : null; - const image = files?.image ? files.image[0] : null; + const AUDIO = files?.AUDIO ? files.AUDIO[0] : null; + const IMAGE = files?.IMAGE ? files.IMAGE[0] : null; try { let validFiles = true; let errorMessages = []; - if (video && video.size > 30 * 1024 * 1024) { + if (AUDIO && AUDIO.size > 10 * 1024 * 1024) { validFiles = false; - video.buffer = null; - errorMessages.push("Video file exceeds the size limit of 30MB"); - } - - if (audio && audio.size > 10 * 1024 * 1024) { - validFiles = false; - audio.buffer = null; + AUDIO.buffer = null; errorMessages.push("Audio file exceeds the size limit of 10MB"); } - if (image && image.size > 5 * 1024 * 1024) { + if (IMAGE && IMAGE.size > 5 * 1024 * 1024) { validFiles = false; - image.buffer = null; + IMAGE.buffer = null; errorMessages.push("Image file exceeds the size limit of 5MB"); } if (validFiles) { - req.filesToSave = { video, audio, image }; + req.filesToSave = { AUDIO, IMAGE }; next(); } else { - clearFileBuffers({ video, audio, image }); + clearFileBuffers({ AUDIO, IMAGE }); return response(400, null, errorMessages.join("; "), res); } } catch (error) { console.log(error); - clearFileBuffers({ video, audio, image }); + clearFileBuffers({ AUDIO, IMAGE }); return response(500, null, "Internal Server Error", res); } }); @@ -136,13 +115,10 @@ export const saveFileToDisk = (file, type, topicId, sectionId, levelId) => { let folderPath; switch (type) { - case "video": - folderPath = path.join("public/uploads/level/video"); - break; - case "audio": + case "AUDIO": folderPath = path.join("public/uploads/level/audio"); break; - case "image": + case "IMAGE": folderPath = path.join("public/uploads/level/image"); break; default: diff --git a/middlewares/User/authUser.js b/middlewares/User/authUser.js index de925ca..edcc1e5 100644 --- a/middlewares/User/authUser.js +++ b/middlewares/User/authUser.js @@ -14,7 +14,7 @@ export const verifyLoginUser = async (req, res, next) => { try { const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET); - const user = await models.User.findByPk(decoded.id, { + const user = await models.User.findByPk(decoded.ID, { attributes: { exclude: ["PASSWORD"], }, diff --git a/middlewares/User/uploadUser.js b/middlewares/User/uploadUser.js index dbb0046..4d7afdb 100644 --- a/middlewares/User/uploadUser.js +++ b/middlewares/User/uploadUser.js @@ -24,7 +24,7 @@ const upload = multer({ storage: memoryStorage, fileFilter, limits: { fileSize: 5 * 1024 * 1024 }, -}).fields([{ name: "picture", maxCount: 1 }]); +}).fields([{ name: "PICTURE", maxCount: 1 }]); const handleUpload = (req, res, next) => { upload(req, res, (err) => { @@ -33,23 +33,23 @@ const handleUpload = (req, res, next) => { } const files = req.files; - const picture = files?.picture ? files.picture[0] : null; + const PICTURE = files?.PICTURE ? files.PICTURE[0] : null; try { let validFiles = true; let errorMessages = []; - if (picture && picture.size > 5 * 1024 * 1024) { + if (PICTURE && PICTURE.size > 5 * 1024 * 1024) { validFiles = false; - picture.buffer = null; + PICTURE.buffer = null; errorMessages.push("Picture file exceeds the size limit of 5MB"); } if (validFiles) { - req.filesToSave = { picture }; + req.filesToSave = { PICTURE }; next(); } else { - clearFileBuffers({ picture }); + clearFileBuffers({ PICTURE }); return response(400, null, errorMessages.join(", "), res); } } catch (error) { diff --git a/middlewares/uploadExercise.js b/middlewares/uploadExercise.js index ce3365b..70c9460 100644 --- a/middlewares/uploadExercise.js +++ b/middlewares/uploadExercise.js @@ -10,20 +10,7 @@ const fileFilter = (req, file, cb) => { const ext = path.extname(file.originalname).toLowerCase(); switch (file.fieldname) { - case "video": - if (ext === ".mp4") { - cb(null, true); - } else { - cb( - new Error( - "Invalid file type, only .mp4 files are allowed for video!" - ), - false - ); - } - break; - - case "audio": + case "AUDIO": if (ext === ".mp3") { cb(null, true); } else { @@ -36,7 +23,7 @@ const fileFilter = (req, file, cb) => { } break; - case "image": + case "IMAGE": if (ext === ".jpg" || ext === ".jpeg" || ext === ".png") { cb(null, true); } else { @@ -61,9 +48,8 @@ const upload = multer({ fileSize: 100 * 1024 * 1024, }, }).fields([ - { name: "video", maxCount: 1 }, - { name: "audio", maxCount: 1 }, - { name: "image", maxCount: 1 }, + { name: "AUDIO", maxCount: 1 }, + { name: "IMAGE", maxCount: 1 }, ]); const handleUpload = (req, res, next) => { @@ -73,42 +59,35 @@ const handleUpload = (req, res, next) => { } const files = req.files; - const video = files?.video ? files.video[0] : null; - const audio = files?.audio ? files.audio[0] : null; - const image = files?.image ? files.image[0] : null; + const AUDIO = files?.AUDIO ? files.AUDIO[0] : null; + const IMAGE = files?.IMAGE ? files.IMAGE[0] : null; try { let validFiles = true; let errorMessages = []; - if (video && video.size > 30 * 1024 * 1024) { + if (AUDIO && AUDIO.size > 10 * 1024 * 1024) { validFiles = false; - video.buffer = null; - errorMessages.push("Video file exceeds the size limit of 30MB"); - } - - if (audio && audio.size > 10 * 1024 * 1024) { - validFiles = false; - audio.buffer = null; + AUDIO.buffer = null; errorMessages.push("Audio file exceeds the size limit of 10MB"); } - if (image && image.size > 5 * 1024 * 1024) { + if (IMAGE && IMAGE.size > 5 * 1024 * 1024) { validFiles = false; - image.buffer = null; + IMAGE.buffer = null; errorMessages.push("Image file exceeds the size limit of 5MB"); } if (validFiles) { - req.filesToSave = { video, audio, image }; + req.filesToSave = { AUDIO, IMAGE }; next(); } else { - clearFileBuffers({ video, audio, image }); + clearFileBuffers({ AUDIO, IMAGE }); return response(400, null, errorMessages.join("; "), res); } } catch (error) { console.log(error); - clearFileBuffers({ video, audio, image }); + clearFileBuffers({ video, AUDIO, IMAGE }); return response(500, null, "Internal Server Error", res); } }); @@ -136,13 +115,10 @@ export const saveFileToDisk = (file, type, levelId, exerciseId) => { let folderPath; switch (type) { - case "video": - folderPath = path.join("public/uploads/exercise/video"); - break; - case "audio": + case "AUDIO": folderPath = path.join("public/uploads/exercise/audio"); break; - case "image": + case "IMAGE": folderPath = path.join("public/uploads/exercise/image"); break; default: diff --git a/middlewares/uploadSection.js b/middlewares/uploadSection.js index 62c388c..1b8630d 100644 --- a/middlewares/uploadSection.js +++ b/middlewares/uploadSection.js @@ -24,7 +24,7 @@ const upload = multer({ storage: memoryStorage, fileFilter, limits: { fileSize: 5 * 1024 * 1024 }, -}).fields([{ name: "thumbnail", maxCount: 1 }]); +}).fields([{ name: "THUMBNAIL", maxCount: 1 }]); const handleUpload = (req, res, next) => { upload(req, res, (err) => { @@ -33,28 +33,28 @@ const handleUpload = (req, res, next) => { } const files = req.files; - const thumbnail = files?.thumbnail ? files.thumbnail[0] : null; + const THUMBNAIL = files?.THUMBNAIL ? files.THUMBNAIL[0] : null; try { let validFiles = true; let errorMessages = []; - if (thumbnail && thumbnail.size > 5 * 1024 * 1024) { + if (THUMBNAIL && THUMBNAIL.size > 5 * 1024 * 1024) { validFiles = false; - thumbnail.buffer = null; + THUMBNAIL.buffer = null; errorMessages.push("Thumbnail file exceeds the size limit of 5MB"); } if (validFiles) { - req.filesToSave = { thumbnail }; + req.filesToSave = { THUMBNAIL }; next(); } else { - clearFileBuffers({ thumbnail }); + clearFileBuffers({ THUMBNAIL }); return response(400, null, errorMessages.join(", "), res); } } catch (error) { console.log(error); - clearFileBuffers({ thumbnail }); + clearFileBuffers({ THUMBNAIL }); return response(500, null, "Internal Server Error", res); } }); diff --git a/models/contentModels/topicModel.js b/models/contentModels/topicModel.js index 8488580..c4ddb50 100644 --- a/models/contentModels/topicModel.js +++ b/models/contentModels/topicModel.js @@ -37,6 +37,13 @@ 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/public/uploads/avatar/user-8087dbf0bf98571ae2d8db93f96c51b3.jpeg b/public/uploads/avatar/user-8087dbf0bf98571ae2d8db93f96c51b3.jpeg new file mode 100644 index 0000000..953f28d Binary files /dev/null and b/public/uploads/avatar/user-8087dbf0bf98571ae2d8db93f96c51b3.jpeg differ diff --git a/public/uploads/avatar/user-899f0c3a99d44dc1c8ba0ef5b9313b9d.jpeg b/public/uploads/avatar/user-899f0c3a99d44dc1c8ba0ef5b9313b9d.jpeg new file mode 100644 index 0000000..39859e4 Binary files /dev/null and b/public/uploads/avatar/user-899f0c3a99d44dc1c8ba0ef5b9313b9d.jpeg differ diff --git a/public/uploads/avatar/user-d3b55e105e3f6aaae08de98c6d4fb98d.jpeg b/public/uploads/avatar/user-d3b55e105e3f6aaae08de98c6d4fb98d.jpeg new file mode 100644 index 0000000..953f28d Binary files /dev/null and b/public/uploads/avatar/user-d3b55e105e3f6aaae08de98c6d4fb98d.jpeg differ diff --git a/public/uploads/exercise/audio/AUDIO-009f53d1-d1e0-4cdd-b857-14d1e7a88efb-453784d90757a3577d6ebe66791a41f3.mp3 b/public/uploads/exercise/audio/AUDIO-009f53d1-d1e0-4cdd-b857-14d1e7a88efb-453784d90757a3577d6ebe66791a41f3.mp3 new file mode 100644 index 0000000..9bf1b59 Binary files /dev/null and b/public/uploads/exercise/audio/AUDIO-009f53d1-d1e0-4cdd-b857-14d1e7a88efb-453784d90757a3577d6ebe66791a41f3.mp3 differ diff --git a/public/uploads/exercise/audio/AUDIO-110c367d-cd55-42ff-8035-1c9b8bfb6077-453784d90757a3577d6ebe66791a41f3.mp3 b/public/uploads/exercise/audio/AUDIO-110c367d-cd55-42ff-8035-1c9b8bfb6077-453784d90757a3577d6ebe66791a41f3.mp3 new file mode 100644 index 0000000..9bf1b59 Binary files /dev/null and b/public/uploads/exercise/audio/AUDIO-110c367d-cd55-42ff-8035-1c9b8bfb6077-453784d90757a3577d6ebe66791a41f3.mp3 differ diff --git a/public/uploads/exercise/audio/AUDIO-e4df6d83-e5ad-41bb-92bc-cd749b35f9e6-453784d90757a3577d6ebe66791a41f3.mp3 b/public/uploads/exercise/audio/AUDIO-e4df6d83-e5ad-41bb-92bc-cd749b35f9e6-453784d90757a3577d6ebe66791a41f3.mp3 new file mode 100644 index 0000000..9bf1b59 Binary files /dev/null and b/public/uploads/exercise/audio/AUDIO-e4df6d83-e5ad-41bb-92bc-cd749b35f9e6-453784d90757a3577d6ebe66791a41f3.mp3 differ diff --git a/public/uploads/exercise/image/IMAGE-0a38f532-c028-4034-8c2c-737ef9be5a6d-58e4b8fcbf104d7b100c4a3c12abf306.jpeg b/public/uploads/exercise/image/IMAGE-0a38f532-c028-4034-8c2c-737ef9be5a6d-58e4b8fcbf104d7b100c4a3c12abf306.jpeg new file mode 100644 index 0000000..39859e4 Binary files /dev/null and b/public/uploads/exercise/image/IMAGE-0a38f532-c028-4034-8c2c-737ef9be5a6d-58e4b8fcbf104d7b100c4a3c12abf306.jpeg differ diff --git a/public/uploads/exercise/image/IMAGE-44b293ca-9308-417e-8b35-5cc033b93422-58e4b8fcbf104d7b100c4a3c12abf306.jpeg b/public/uploads/exercise/image/IMAGE-44b293ca-9308-417e-8b35-5cc033b93422-58e4b8fcbf104d7b100c4a3c12abf306.jpeg new file mode 100644 index 0000000..39859e4 Binary files /dev/null and b/public/uploads/exercise/image/IMAGE-44b293ca-9308-417e-8b35-5cc033b93422-58e4b8fcbf104d7b100c4a3c12abf306.jpeg differ diff --git a/public/uploads/exercise/image/IMAGE-75d7697a-fd7e-4346-b312-b9074444a291-58e4b8fcbf104d7b100c4a3c12abf306.jpeg b/public/uploads/exercise/image/IMAGE-75d7697a-fd7e-4346-b312-b9074444a291-58e4b8fcbf104d7b100c4a3c12abf306.jpeg new file mode 100644 index 0000000..39859e4 Binary files /dev/null and b/public/uploads/exercise/image/IMAGE-75d7697a-fd7e-4346-b312-b9074444a291-58e4b8fcbf104d7b100c4a3c12abf306.jpeg differ diff --git a/public/uploads/exercise/image/IMAGE-f3e0dbf9-beea-40c9-ba1c-7394628344ae-58e4b8fcbf104d7b100c4a3c12abf306.jpeg b/public/uploads/exercise/image/IMAGE-f3e0dbf9-beea-40c9-ba1c-7394628344ae-58e4b8fcbf104d7b100c4a3c12abf306.jpeg new file mode 100644 index 0000000..39859e4 Binary files /dev/null and b/public/uploads/exercise/image/IMAGE-f3e0dbf9-beea-40c9-ba1c-7394628344ae-58e4b8fcbf104d7b100c4a3c12abf306.jpeg differ diff --git a/public/uploads/level/audio/AUDIO-b964bce4-f29f-4642-801d-8382b71a8fc9-d9937102a559fbce6d6c55d66e799066.mp3 b/public/uploads/level/audio/AUDIO-b964bce4-f29f-4642-801d-8382b71a8fc9-d9937102a559fbce6d6c55d66e799066.mp3 new file mode 100644 index 0000000..9bf1b59 Binary files /dev/null and b/public/uploads/level/audio/AUDIO-b964bce4-f29f-4642-801d-8382b71a8fc9-d9937102a559fbce6d6c55d66e799066.mp3 differ diff --git a/public/uploads/level/audio/AUDIO-c81ad023-0dcc-4050-be54-3d898a8eb9c4-d9937102a559fbce6d6c55d66e799066.mp3 b/public/uploads/level/audio/AUDIO-c81ad023-0dcc-4050-be54-3d898a8eb9c4-d9937102a559fbce6d6c55d66e799066.mp3 new file mode 100644 index 0000000..9bf1b59 Binary files /dev/null and b/public/uploads/level/audio/AUDIO-c81ad023-0dcc-4050-be54-3d898a8eb9c4-d9937102a559fbce6d6c55d66e799066.mp3 differ diff --git a/public/uploads/level/audio/AUDIO-cb315d09-230c-4b3b-ad5b-7ea12ef43d4e-d6dbdf591fc91b865f7797a7b778afc9.mp3 b/public/uploads/level/audio/AUDIO-cb315d09-230c-4b3b-ad5b-7ea12ef43d4e-d6dbdf591fc91b865f7797a7b778afc9.mp3 new file mode 100644 index 0000000..03b707c Binary files /dev/null and b/public/uploads/level/audio/AUDIO-cb315d09-230c-4b3b-ad5b-7ea12ef43d4e-d6dbdf591fc91b865f7797a7b778afc9.mp3 differ diff --git a/public/uploads/level/audio/AUDIO-d107216f-a8e2-4234-9a1a-dd119029008d-d6dbdf591fc91b865f7797a7b778afc9.mp3 b/public/uploads/level/audio/AUDIO-d107216f-a8e2-4234-9a1a-dd119029008d-d6dbdf591fc91b865f7797a7b778afc9.mp3 new file mode 100644 index 0000000..03b707c Binary files /dev/null and b/public/uploads/level/audio/AUDIO-d107216f-a8e2-4234-9a1a-dd119029008d-d6dbdf591fc91b865f7797a7b778afc9.mp3 differ diff --git a/public/uploads/level/image/IMAGE-2d889ceb-a620-446d-aba4-896bec99f8ee-f19cfaa031a896ebf1e3a3c28c18ad0c.jpeg b/public/uploads/level/image/IMAGE-2d889ceb-a620-446d-aba4-896bec99f8ee-f19cfaa031a896ebf1e3a3c28c18ad0c.jpeg new file mode 100644 index 0000000..6439585 Binary files /dev/null and b/public/uploads/level/image/IMAGE-2d889ceb-a620-446d-aba4-896bec99f8ee-f19cfaa031a896ebf1e3a3c28c18ad0c.jpeg differ diff --git a/public/uploads/level/image/IMAGE-c81ad023-0dcc-4050-be54-3d898a8eb9c4-f19cfaa031a896ebf1e3a3c28c18ad0c.jpeg b/public/uploads/level/image/IMAGE-c81ad023-0dcc-4050-be54-3d898a8eb9c4-f19cfaa031a896ebf1e3a3c28c18ad0c.jpeg new file mode 100644 index 0000000..6439585 Binary files /dev/null and b/public/uploads/level/image/IMAGE-c81ad023-0dcc-4050-be54-3d898a8eb9c4-f19cfaa031a896ebf1e3a3c28c18ad0c.jpeg differ diff --git a/public/uploads/level/image/IMAGE-cb315d09-230c-4b3b-ad5b-7ea12ef43d4e-6b2edf95644c9c7176c9d0837ec8eabc.png b/public/uploads/level/image/IMAGE-cb315d09-230c-4b3b-ad5b-7ea12ef43d4e-6b2edf95644c9c7176c9d0837ec8eabc.png new file mode 100644 index 0000000..73653c9 Binary files /dev/null and b/public/uploads/level/image/IMAGE-cb315d09-230c-4b3b-ad5b-7ea12ef43d4e-6b2edf95644c9c7176c9d0837ec8eabc.png differ diff --git a/public/uploads/level/image/IMAGE-d107216f-a8e2-4234-9a1a-dd119029008d-e1567771f810befaf146065bf799e38f.jpeg b/public/uploads/level/image/IMAGE-d107216f-a8e2-4234-9a1a-dd119029008d-e1567771f810befaf146065bf799e38f.jpeg new file mode 100644 index 0000000..953f28d Binary files /dev/null and b/public/uploads/level/image/IMAGE-d107216f-a8e2-4234-9a1a-dd119029008d-e1567771f810befaf146065bf799e38f.jpeg differ diff --git a/public/uploads/section/THUMBNAIL-3e9cff3b-42ea-4c51-ab2f-836a7e6adf50-dbef05e3259efe0c2cb073f6eb32dc14.png b/public/uploads/section/THUMBNAIL-3e9cff3b-42ea-4c51-ab2f-836a7e6adf50-dbef05e3259efe0c2cb073f6eb32dc14.png new file mode 100644 index 0000000..73653c9 Binary files /dev/null and b/public/uploads/section/THUMBNAIL-3e9cff3b-42ea-4c51-ab2f-836a7e6adf50-dbef05e3259efe0c2cb073f6eb32dc14.png differ diff --git a/public/uploads/section/THUMBNAIL-49390272-efbd-4f1c-9b63-39fa8905b891-81346cbd4317524acfd617bf14c006ce.jpeg b/public/uploads/section/THUMBNAIL-49390272-efbd-4f1c-9b63-39fa8905b891-81346cbd4317524acfd617bf14c006ce.jpeg new file mode 100644 index 0000000..953f28d Binary files /dev/null and b/public/uploads/section/THUMBNAIL-49390272-efbd-4f1c-9b63-39fa8905b891-81346cbd4317524acfd617bf14c006ce.jpeg differ diff --git a/public/uploads/section/thumbnail-39e8b29b-e6d6-4e63-b597-c012896c67b7-dbef05e3259efe0c2cb073f6eb32dc14.png b/public/uploads/section/thumbnail-39e8b29b-e6d6-4e63-b597-c012896c67b7-dbef05e3259efe0c2cb073f6eb32dc14.png new file mode 100644 index 0000000..73653c9 Binary files /dev/null and b/public/uploads/section/thumbnail-39e8b29b-e6d6-4e63-b597-c012896c67b7-dbef05e3259efe0c2cb073f6eb32dc14.png differ diff --git a/public/uploads/section/thumbnail-6cad196e-ac27-4eea-a4d7-30eccd1f622d-dbef05e3259efe0c2cb073f6eb32dc14.png b/public/uploads/section/thumbnail-6cad196e-ac27-4eea-a4d7-30eccd1f622d-dbef05e3259efe0c2cb073f6eb32dc14.png new file mode 100644 index 0000000..73653c9 Binary files /dev/null and b/public/uploads/section/thumbnail-6cad196e-ac27-4eea-a4d7-30eccd1f622d-dbef05e3259efe0c2cb073f6eb32dc14.png differ diff --git a/routes/contents/exercise.js b/routes/contents/exercise.js index 03cc01f..462f3dd 100644 --- a/routes/contents/exercise.js +++ b/routes/contents/exercise.js @@ -1,5 +1,5 @@ import express from "express"; -import { getExercises, getExercisesForAdmin, getExerciseById, deleteExerciseById, deleteExerciseFileById } from "../../controllers/contentControllers/exercise.js"; +import { getExercises, getExercisesForAdmin, getExerciseById, getExerciseByLevelId, deleteExerciseById, deleteExerciseFileById } from "../../controllers/contentControllers/exercise.js"; import { createMultipleChoicesExercise, updateMultipleChoicesExerciseById } from "../../controllers/exerciseTypesControllers/multipleChoices.js"; import { createMatchingPairsExercise, updateMatchingPairsExerciseById } from "../../controllers/exerciseTypesControllers/matchingPairs.js"; import { createTrueFalseExercise, updateTrueFalseExerciseById } from "../../controllers/exerciseTypesControllers/trueFalse.js"; @@ -13,6 +13,8 @@ router.get("/exercise", verifyLoginUser, getExercises); router.get("/exercise/admin", verifyLoginUser, adminOnly, getExercisesForAdmin); +router.get("/exercise/level/:idLevel", verifyLoginUser, getExerciseByLevelId); + router.get("/exercise/:id", verifyLoginUser, getExerciseById); router.post("/exercise/multiple-choices", verifyLoginUser, adminOnly, handleUpload, createMultipleChoicesExercise); diff --git a/routes/contents/level.js b/routes/contents/level.js index 55a0b85..ee50d16 100644 --- a/routes/contents/level.js +++ b/routes/contents/level.js @@ -1,6 +1,5 @@ import express from "express"; -// import { getAllLevels, getAllLevelById, getLevels, getLevelById, createLevel, updateLevelById, deleteLevelById, getRoutes, getRouteById, updateRouteById } from "../controllers/level.js"; -import { getLevels, getLevelById, createLevel, updateLevelById, deleteLevelById } from "../../controllers/contentControllers/level.js"; +import { getLevels, getLevelById, getLevelsByTopicId, createLevel, updateLevelById, deleteLevelById, getPreviousLevel } from "../../controllers/contentControllers/level.js"; import { verifyLoginUser, adminOnly } from "../../middlewares/User/authUser.js"; import handleUpload from '../../middlewares/Level/uploadLevel.js'; import {checkLevelsPerTopic, autoCalculateRoutes, getSectionAndTopicByLevelId } from '../../middlewares/Level/checkLevel.js'; @@ -8,14 +7,14 @@ import {checkLevelsPerTopic, autoCalculateRoutes, getSectionAndTopicByLevelId } const router = express.Router(); -// router.get("/levels", verifyLoginUser, adminOnly, getAllLevels); - -// router.get("/levels/:id", verifyLoginUser, adminOnly, getAllLevelById); - router.get("/level", verifyLoginUser, getLevels); +router.get("/level/topic/:idTopic", verifyLoginUser, getLevelsByTopicId); + router.get("/level/:id", verifyLoginUser, getLevelById); +router.get("/previous/level/:next_learning", verifyLoginUser, getPreviousLevel); + router.post("/level", verifyLoginUser, adminOnly, handleUpload, checkLevelsPerTopic, autoCalculateRoutes, createLevel); router.put("/level/:id", verifyLoginUser, adminOnly, handleUpload, getSectionAndTopicByLevelId, autoCalculateRoutes, updateLevelById); diff --git a/routes/contents/topic.js b/routes/contents/topic.js index a0748eb..e0cc130 100644 --- a/routes/contents/topic.js +++ b/routes/contents/topic.js @@ -1,5 +1,5 @@ import express from "express"; -import { getTopics, getTopicById, createTopic, updateTopicById, deleteTopicById } from "../../controllers/contentControllers/topic.js"; +import { getTopics, getTopicById, getTopicBySectionId, createTopic, updateTopicById, deleteTopicById, getCompletedTopics } from "../../controllers/contentControllers/topic.js"; import { verifyLoginUser, adminOnly } from "../../middlewares/User/authUser.js"; @@ -7,6 +7,10 @@ const router = express.Router(); router.get("/topic", verifyLoginUser, getTopics); +router.get("/topic/complete", verifyLoginUser, getCompletedTopics); + +router.get("/topic/section/:sectionId", verifyLoginUser, getTopicBySectionId); + router.get("/topic/:id", verifyLoginUser, getTopicById); router.post("/topic", verifyLoginUser, adminOnly, createTopic); diff --git a/routes/user/user.js b/routes/user/user.js index 81c061f..0f7c637 100644 --- a/routes/user/user.js +++ b/routes/user/user.js @@ -20,9 +20,9 @@ router.get("/user/name/:name", verifyLoginUser, adminOrTeacherOnly, getUserByNam router.get("/getMe", verifyLoginUser, getMe); -router.post("/user/update/:id", verifyLoginUser, handleUpload, updateUserById); +router.put("/user/update/:id", verifyLoginUser, handleUpload, updateUserById); -router.post("/user/update/password/:id", verifyLoginUser, updateUserPasswordById); +router.put("/user/update/password/:id", verifyLoginUser, updateUserPasswordById); router.delete("/user/delete/:id", verifyLoginUser, adminOnly, deleteUserById);