import response from "../../response.js"; import models from "../../models/index.js"; import { clearFileBuffers, saveFileToDisk, } from "../../middlewares/Level/uploadLevel.js"; import fs from "fs"; import path from "path"; import { updateOtherLevelsRoutes, updateOtherLevelsRoutesOnDelete, } from "../../middlewares/Level/checkLevel.js"; export const getLevels = async (req, res) => { try { const levels = await models.Level.findAll({ attributes: { exclude: [ "ROUTE_1", "ROUTE_2", "ROUTE_3", "ROUTE_4", "ROUTE_5", "ROUTE_6", ], }, }); response(200, levels, "Success", res); } catch (error) { console.log(error); response(500, null, "Error retrieving levels data!", res); } }; export const getLevelById = async (req, res) => { try { const { id } = req.params; const level = await models.Level.findByPk(id, { attributes: { exclude: [ "ROUTE_1", "ROUTE_2", "ROUTE_3", "ROUTE_4", "ROUTE_5", "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); } 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); } }; export const getLevelsByTopicId = async (req, res) => { try { const { idTopic } = req.params; const { ID } = req.user; const topicExists = await models.Topic.findByPk(idTopic, { include: { model: models.Section, as: "topicSection", attributes: ["NAME_SECTION"], }, }); if (!topicExists) { return res.status(404).json({ message: "Topic not found" }); } 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", "STUDENT_START", "STUDENT_FINISH", ], where: { ID: ID, }, required: false, order: [["STUDENT_START", "DESC"]], limit: 1, }, { model: models.Topic, as: "levelTopic", attributes: ["NAME_TOPIC"], include: { model: models.Section, as: "topicSection", attributes: ["NAME_SECTION"], }, }, ], }); if (!levels || levels.length === 0) { return res .status(404) .json({ message: "No levels found for the given topic." }); } const lastCompletedLearning = await models.StdLearning.findOne({ where: { ID: ID, STUDENT_FINISH: { [models.Op.not]: null, }, }, include: [ { model: models.Level, as: "level", attributes: ["ID_LEVEL", "NAME_LEVEL"], }, ], order: [["STUDENT_FINISH", "DESC"]], }); const levelsWithScore = levels.map((level) => { const SCORE = level.stdLearning && level.stdLearning.length > 0 ? level.stdLearning[0]?.SCORE : null; const ID_STUDENT_LEARNING = level.stdLearning && level.stdLearning.length > 0 ? level.stdLearning[0]?.ID_STUDENT_LEARNING : 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, }; }); const sortedLevels = levelsWithScore.sort((a, b) => { if (a.NAME_LEVEL === "Pretest") return -1; if (b.NAME_LEVEL === "Pretest") return 1; const levelA = parseInt(a.NAME_LEVEL.replace("Level ", "")); const levelB = parseInt(b.NAME_LEVEL.replace("Level ", "")); return levelA - levelB; }); const responsePayload = { lastCompletedLevel: lastCompletedLearning ? { ID_STUDENT_LEARNING: lastCompletedLearning.ID_STUDENT_LEARNING, ID_LEVEL: lastCompletedLearning.level.ID_LEVEL, NAME_LEVEL: lastCompletedLearning.level.NAME_LEVEL, SCORE: lastCompletedLearning.SCORE, NEXT_LEARNING: lastCompletedLearning.NEXT_LEARNING, FINISHED_AT: lastCompletedLearning.STUDENT_FINISH, } : null, levels: sortedLevels, }; res.status(200).json({ message: "Success", data: responsePayload }); } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); } }; export const createLevel = async (req, res, next) => { const { NAME_LEVEL, ID_SECTION, ID_TOPIC, CONTENT, VIDEO } = req.body; const { AUDIO, IMAGE } = req.filesToSave || {}; if (!NAME_LEVEL) { clearFileBuffers({ AUDIO, IMAGE }); return response(400, null, "Level name is required", res); } if (!ID_SECTION) { clearFileBuffers({ AUDIO, IMAGE }); return response(400, null, "Section is required", res); } if (!ID_TOPIC) { clearFileBuffers({ AUDIO, IMAGE }); return response(400, null, "Topic is required", res); } const transaction = await models.db.transaction(); try { const sectionWithTopic = await models.Topic.findOne({ where: { ID_SECTION, ID_TOPIC }, transaction, }); if (!sectionWithTopic) { clearFileBuffers({ AUDIO, IMAGE }); await transaction.rollback(); return response( 400, null, "Topic does not relate to the provided Section!", res ); } const existingLevel = await models.Level.findOne({ where: { NAME_LEVEL, ID_TOPIC }, transaction, }); if (existingLevel) { clearFileBuffers({ AUDIO, IMAGE }); await transaction.rollback(); return response( 409, null, "A level with this name already exists under this topic", res ); } const newLevel = await models.Level.create( { NAME_LEVEL, ID_SECTION, ID_TOPIC, IS_PRETEST: req.body.IS_PRETEST || 0, CONTENT, VIDEO: VIDEO || null, AUDIO: null, IMAGE: null, ROUTE_1: req.body.ROUTE_1, ROUTE_2: req.body.ROUTE_2, ROUTE_3: req.body.ROUTE_3, ROUTE_4: req.body.ROUTE_4, ROUTE_5: req.body.ROUTE_5, ROUTE_6: req.body.ROUTE_6, }, { transaction } ); req.body.newLevelId = newLevel.ID_LEVEL; 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) : null; newLevel.AUDIO = audioFilename; newLevel.IMAGE = imageFilename; await newLevel.save({ transaction }); await transaction.commit(); await updateOtherLevelsRoutes(req, res, next); response(201, newLevel, "Level created successfully", res); } catch (error) { console.log(error); clearFileBuffers({ AUDIO, IMAGE }); await transaction.rollback(); 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, VIDEO } = req.body; const { AUDIO, IMAGE } = req.filesToSave || {}; const transaction = await models.db.transaction(); try { const level = await models.Level.findByPk(id, { transaction }); if (!level) { clearFileBuffers({ AUDIO, IMAGE }); await transaction.rollback(); return response(404, null, "Level not found", res); } const sectionWithTopic = await models.Topic.findOne({ where: { ID_SECTION, ID_TOPIC }, transaction, }); if (!sectionWithTopic) { clearFileBuffers({ AUDIO, IMAGE }); await transaction.rollback(); return response( 400, null, "Topic does not relate to the provided Section", res ); } if (NAME_LEVEL && ID_TOPIC) { const existingLevel = await models.Level.findOne({ where: { NAME_LEVEL, ID_TOPIC, ID_LEVEL: { [models.Sequelize.Op.ne]: id }, }, transaction, }); if (existingLevel) { clearFileBuffers({ AUDIO, IMAGE }); await transaction.rollback(); return response( 409, null, "A level with this name already exists under this topic", res ); } } if (NAME_LEVEL) { level.NAME_LEVEL = NAME_LEVEL; level.IS_PRETEST = NAME_LEVEL === "Level 1" ? 1 : 0; } if (ID_SECTION) level.ID_SECTION = ID_SECTION; if (ID_TOPIC) level.ID_TOPIC = ID_TOPIC; if (CONTENT) level.CONTENT = CONTENT; if (VIDEO) level.VIDEO = VIDEO; if (AUDIO) { if (level.AUDIO) { const oldAudioPath = path.join( "public/uploads/level/audio", level.AUDIO ); if (fs.existsSync(oldAudioPath)) fs.unlinkSync(oldAudioPath); } level.AUDIO = saveFileToDisk( AUDIO, "AUDIO", ID_TOPIC || level.ID_TOPIC, ID_SECTION || level.ID_SECTION, level.ID_LEVEL ); } if (IMAGE) { if (level.IMAGE) { const oldImagePath = path.join( "public/uploads/level/image", level.IMAGE ); if (fs.existsSync(oldImagePath)) fs.unlinkSync(oldImagePath); } level.IMAGE = saveFileToDisk( IMAGE, "IMAGE", ID_TOPIC || level.ID_TOPIC, ID_SECTION || level.ID_SECTION, level.ID_LEVEL ); } await level.save({ transaction }); await transaction.commit(); await updateOtherLevelsRoutes(req, res, next); response(200, level, "Level updated successfully", res); } catch (error) { console.log(error); clearFileBuffers({ AUDIO, IMAGE }); await transaction.rollback(); return response(500, null, "Internal Server Error", res); } }; export const deleteLevelById = async (req, res, next) => { const { id } = req.params; try { const level = await models.Level.findByPk(id); if (!level) { return response(404, null, "Level not found", res); } const deleteFile = (filePath) => { if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } }; if (level.AUDIO) { const audioPath = path.join("public/uploads/level/audio", level.AUDIO); deleteFile(audioPath); } if (level.IMAGE) { const imagePath = path.join("public/uploads/level/image", level.IMAGE); deleteFile(imagePath); } req.body.newLevelId = level.ID_LEVEL; await level.destroy(); await updateOtherLevelsRoutesOnDelete(req, res, next); response(200, null, "Level deleted successfully", res); } catch (error) { console.log(error); return response(500, null, "Internal Server Error", res); } }; 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", "STUDENT_START", "STUDENT_FINISH", ], where: { ID: ID, }, required: false, order: [["STUDENT_FINISH", "DESC"]], limit: 1, }, ], attributes: { exclude: [ "ROUTE_1", "ROUTE_2", "ROUTE_3", "ROUTE_4", "ROUTE_5", "ROUTE_6", ], }, }); if (!currentLevel) { return res.status(404).json({ message: "Level not found" }); } const { NAME_LEVEL, ID_TOPIC } = currentLevel; const levelNumber = parseInt(NAME_LEVEL.replace("Level ", "")); if (isNaN(levelNumber)) { return res.status(400).json({ message: "Invalid level format" }); } 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", "STUDENT_START", "STUDENT_FINISH", ], where: { ID: ID, }, required: false, order: [["STUDENT_START", "DESC"]], limit: 1, }, ], }); const previousLevelsWithScore = previousLevels.map((level) => { const SCORE = level.stdLearning && level.stdLearning.length > 0 ? level.stdLearning[0]?.SCORE : null; const ID_STUDENT_LEARNING = level.stdLearning && 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 && currentLevel.stdLearning.length > 0 ? currentLevel.stdLearning[0]?.ID_STUDENT_LEARNING : null, SCORE: currentLevel.stdLearning && currentLevel.stdLearning.length > 0 ? currentLevel.stdLearning[0]?.SCORE : null, }; delete currentLevelWithScore.stdLearning; if (!previousLevelsWithScore.length && !currentLevelWithScore) { return res.status(404).json({ message: "No levels found" }); } const result = { currentLevel: currentLevelWithScore, previousLevels: previousLevelsWithScore, }; res.status(200).json({ message: "Success", data: result }); } catch (error) { console.error(error); res.status(500).json({ message: "Internal Server Error" }); } };