2024-09-13 13:03:35 +00:00
|
|
|
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({
|
2024-10-10 02:55:13 +00:00
|
|
|
where: {
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
},
|
2024-09-13 13:03:35 +00:00
|
|
|
attributes: {
|
|
|
|
|
exclude: [
|
|
|
|
|
"ROUTE_1",
|
|
|
|
|
"ROUTE_2",
|
|
|
|
|
"ROUTE_3",
|
|
|
|
|
"ROUTE_4",
|
|
|
|
|
"ROUTE_5",
|
|
|
|
|
"ROUTE_6",
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
response(200, levels, "Success", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
2024-09-19 10:04:18 +00:00
|
|
|
response(500, null, "Error retrieving levels data!", res);
|
2024-09-13 13:03:35 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getLevelById = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
2024-09-30 01:06:15 +00:00
|
|
|
|
2024-09-13 13:03:35 +00:00
|
|
|
const level = await models.Level.findByPk(id, {
|
2024-10-10 02:55:13 +00:00
|
|
|
where: {
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
},
|
2024-09-13 13:03:35 +00:00
|
|
|
attributes: {
|
|
|
|
|
exclude: [
|
|
|
|
|
"ROUTE_1",
|
|
|
|
|
"ROUTE_2",
|
|
|
|
|
"ROUTE_3",
|
|
|
|
|
"ROUTE_4",
|
|
|
|
|
"ROUTE_5",
|
|
|
|
|
"ROUTE_6",
|
|
|
|
|
],
|
|
|
|
|
},
|
2024-09-30 01:06:15 +00:00
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Topic,
|
|
|
|
|
as: "levelTopic",
|
|
|
|
|
attributes: ["NAME_TOPIC"],
|
|
|
|
|
include: {
|
|
|
|
|
model: models.Section,
|
|
|
|
|
as: "topicSection",
|
|
|
|
|
attributes: ["NAME_SECTION"],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
2024-09-13 13:03:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!level) {
|
|
|
|
|
return response(404, null, "Level not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-30 01:06:15 +00:00
|
|
|
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);
|
2024-09-19 10:04:18 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
export const getLevelForAdmin = async (req, res) => {
|
|
|
|
|
const { page = 1, limit = 10, search = "", sort = "time" } = req.query;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const { count, rows: levels } = await models.Level.findAndCountAll({
|
|
|
|
|
where: {
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
...(search && {
|
|
|
|
|
[models.Op.or]: [
|
|
|
|
|
{
|
|
|
|
|
"$levelTopic->topicSection.NAME_SECTION$": {
|
|
|
|
|
[models.Op.like]: `%${search}%`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"$levelTopic.NAME_TOPIC$": {
|
|
|
|
|
[models.Op.like]: `%${search}%`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
NAME_LEVEL: { [models.Op.like]: `%${search}%` },
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
attributes: ["ID_LEVEL", "NAME_LEVEL", "TIME_LEVEL"],
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Topic,
|
|
|
|
|
as: "levelTopic",
|
|
|
|
|
attributes: ["ID_TOPIC", "NAME_TOPIC"],
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Section,
|
|
|
|
|
as: "topicSection",
|
|
|
|
|
attributes: ["ID_SECTION", "NAME_SECTION"],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
distinct: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const formattedLevels = levels.map((level) => ({
|
|
|
|
|
ID_LEVEL: level.ID_LEVEL,
|
|
|
|
|
ID_SECTION: level.levelTopic.topicSection.ID_SECTION,
|
|
|
|
|
ID_TOPIC: level.levelTopic.ID_TOPIC,
|
|
|
|
|
NAME_SECTION: level.levelTopic.topicSection.NAME_SECTION,
|
|
|
|
|
NAME_TOPIC: level.levelTopic.NAME_TOPIC,
|
|
|
|
|
NAME_LEVEL: level.NAME_LEVEL,
|
|
|
|
|
TIME_LEVEL: level.TIME_LEVEL,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
if (sort === "section") {
|
|
|
|
|
formattedLevels.sort((a, b) =>
|
|
|
|
|
a.NAME_SECTION.localeCompare(b.NAME_SECTION)
|
|
|
|
|
);
|
|
|
|
|
} else if (sort === "topic") {
|
|
|
|
|
formattedLevels.sort((a, b) => {
|
|
|
|
|
const topicComparison = a.NAME_TOPIC.localeCompare(b.NAME_TOPIC);
|
|
|
|
|
if (topicComparison === 0) {
|
|
|
|
|
if (a.NAME_LEVEL === "Pretest") return -1;
|
|
|
|
|
if (b.NAME_LEVEL === "Pretest") return 1;
|
|
|
|
|
|
|
|
|
|
const levelA = parseInt(a.NAME_LEVEL.replace("Level ", "")) || 0;
|
|
|
|
|
const levelB = parseInt(b.NAME_LEVEL.replace("Level ", "")) || 0;
|
|
|
|
|
return levelA - levelB;
|
|
|
|
|
}
|
|
|
|
|
return topicComparison;
|
|
|
|
|
});
|
|
|
|
|
} else if (sort === "level") {
|
|
|
|
|
formattedLevels.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 ", "")) || 0;
|
|
|
|
|
const levelB = parseInt(b.NAME_LEVEL.replace("Level ", "")) || 0;
|
|
|
|
|
|
|
|
|
|
return levelA - levelB;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
formattedLevels.sort(
|
|
|
|
|
(a, b) => new Date(b.TIME_LEVEL) - new Date(a.TIME_LEVEL)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const paginatedLevels = formattedLevels.slice(
|
|
|
|
|
(page - 1) * limit,
|
|
|
|
|
page * limit
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const totalPages = Math.ceil(count / limit);
|
|
|
|
|
const currentPage = parseInt(page);
|
|
|
|
|
|
|
|
|
|
response(
|
|
|
|
|
200,
|
|
|
|
|
{
|
|
|
|
|
levels: paginatedLevels,
|
|
|
|
|
currentPage,
|
|
|
|
|
totalPages,
|
|
|
|
|
totalItems: count,
|
|
|
|
|
},
|
|
|
|
|
"Levels retrieved successfully",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Error retrieving levels data!", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
export const getLevelsByTopicId = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { idTopic } = req.params;
|
|
|
|
|
const { ID } = req.user;
|
|
|
|
|
|
2024-09-30 01:06:15 +00:00
|
|
|
const topicExists = await models.Topic.findByPk(idTopic, {
|
|
|
|
|
include: {
|
|
|
|
|
model: models.Section,
|
|
|
|
|
as: "topicSection",
|
|
|
|
|
attributes: ["NAME_SECTION"],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
if (!topicExists) {
|
2024-10-01 08:03:44 +00:00
|
|
|
return res.status(404).json({ message: "Topic not found" });
|
2024-09-19 10:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const levels = await models.Level.findAll({
|
|
|
|
|
where: {
|
|
|
|
|
ID_TOPIC: idTopic,
|
2024-10-10 02:55:13 +00:00
|
|
|
IS_DELETED: 0,
|
2024-09-19 10:04:18 +00:00
|
|
|
},
|
|
|
|
|
attributes: {
|
|
|
|
|
exclude: [
|
|
|
|
|
"ROUTE_1",
|
|
|
|
|
"ROUTE_2",
|
|
|
|
|
"ROUTE_3",
|
|
|
|
|
"ROUTE_4",
|
|
|
|
|
"ROUTE_5",
|
|
|
|
|
"ROUTE_6",
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.StdLearning,
|
|
|
|
|
as: "stdLearning",
|
2024-10-01 08:03:44 +00:00
|
|
|
attributes: [
|
|
|
|
|
"SCORE",
|
|
|
|
|
"ID_STUDENT_LEARNING",
|
|
|
|
|
"STUDENT_START",
|
|
|
|
|
"STUDENT_FINISH",
|
2024-10-10 02:55:13 +00:00
|
|
|
"NEXT_LEARNING",
|
2024-10-01 08:03:44 +00:00
|
|
|
],
|
2024-09-19 10:04:18 +00:00
|
|
|
where: {
|
|
|
|
|
ID: ID,
|
|
|
|
|
},
|
|
|
|
|
required: false,
|
2024-10-01 08:03:44 +00:00
|
|
|
order: [["STUDENT_START", "DESC"]],
|
|
|
|
|
limit: 1,
|
2024-09-19 10:04:18 +00:00
|
|
|
},
|
2024-09-30 01:06:15 +00:00
|
|
|
{
|
|
|
|
|
model: models.Topic,
|
|
|
|
|
as: "levelTopic",
|
|
|
|
|
attributes: ["NAME_TOPIC"],
|
|
|
|
|
include: {
|
|
|
|
|
model: models.Section,
|
|
|
|
|
as: "topicSection",
|
|
|
|
|
attributes: ["NAME_SECTION"],
|
|
|
|
|
},
|
|
|
|
|
},
|
2024-09-19 10:04:18 +00:00
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!levels || levels.length === 0) {
|
|
|
|
|
return res
|
|
|
|
|
.status(404)
|
|
|
|
|
.json({ message: "No levels found for the given topic." });
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-23 06:16:02 +00:00
|
|
|
const lastCompletedLearning = await models.StdLearning.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
ID: ID,
|
2024-10-10 02:55:13 +00:00
|
|
|
STUDENT_FINISH: { [models.Op.not]: null },
|
2024-09-23 06:16:02 +00:00
|
|
|
},
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Level,
|
|
|
|
|
as: "level",
|
|
|
|
|
attributes: ["ID_LEVEL", "NAME_LEVEL"],
|
2024-10-16 04:13:24 +00:00
|
|
|
where: {
|
|
|
|
|
ID_TOPIC: idTopic,
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
},
|
2024-09-23 06:16:02 +00:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
order: [["STUDENT_FINISH", "DESC"]],
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-10 03:31:44 +00:00
|
|
|
let currentLearningLevel = null;
|
2024-10-10 02:55:13 +00:00
|
|
|
|
2024-10-10 03:31:44 +00:00
|
|
|
if (lastCompletedLearning?.NEXT_LEARNING) {
|
|
|
|
|
currentLearningLevel = await models.Level.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
ID_LEVEL: lastCompletedLearning.NEXT_LEARNING,
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
},
|
|
|
|
|
attributes: ["ID_LEVEL", "NAME_LEVEL"],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const nextLearningLevel = currentLearningLevel;
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
const unlockedLevels = lastCompletedLearning
|
|
|
|
|
? await models.Level.findAll({
|
|
|
|
|
where: {
|
|
|
|
|
ID_TOPIC: idTopic,
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
[models.Op.or]: [
|
2024-10-10 03:31:44 +00:00
|
|
|
{ NAME_LEVEL: { [models.Op.lt]: nextLearningLevel?.NAME_LEVEL } },
|
2024-10-10 02:55:13 +00:00
|
|
|
{ NAME_LEVEL: "Pretest" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
attributes: ["NAME_LEVEL"],
|
|
|
|
|
order: [["NAME_LEVEL", "ASC"]],
|
|
|
|
|
})
|
|
|
|
|
: [{ NAME_LEVEL: "Pretest" }];
|
|
|
|
|
|
2024-10-10 03:31:44 +00:00
|
|
|
const unlockedLevelNames = unlockedLevels.map((lvl) => lvl.NAME_LEVEL);
|
|
|
|
|
|
2024-10-14 01:23:37 +00:00
|
|
|
if (
|
|
|
|
|
nextLearningLevel &&
|
|
|
|
|
!unlockedLevelNames.includes(nextLearningLevel.NAME_LEVEL)
|
|
|
|
|
) {
|
2024-10-10 03:31:44 +00:00
|
|
|
unlockedLevelNames.push(nextLearningLevel.NAME_LEVEL);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
const levelsWithScore = levels.map((level) => {
|
|
|
|
|
const SCORE =
|
2024-10-01 08:03:44 +00:00
|
|
|
level.stdLearning && level.stdLearning.length > 0
|
|
|
|
|
? level.stdLearning[0]?.SCORE
|
|
|
|
|
: null;
|
2024-09-19 10:04:18 +00:00
|
|
|
const ID_STUDENT_LEARNING =
|
2024-10-01 08:03:44 +00:00
|
|
|
level.stdLearning && level.stdLearning.length > 0
|
|
|
|
|
? level.stdLearning[0]?.ID_STUDENT_LEARNING
|
2024-09-19 10:04:18 +00:00
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
const levelJSON = level.toJSON();
|
2024-09-30 01:06:15 +00:00
|
|
|
const NAME_SECTION = levelJSON.levelTopic.topicSection.NAME_SECTION;
|
|
|
|
|
const NAME_TOPIC = levelJSON.levelTopic.NAME_TOPIC;
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
delete levelJSON.stdLearning;
|
2024-09-30 01:06:15 +00:00
|
|
|
delete levelJSON.levelTopic;
|
|
|
|
|
delete levelJSON.levelTopic?.topicSection;
|
2024-09-19 10:04:18 +00:00
|
|
|
|
2024-10-10 03:31:44 +00:00
|
|
|
const isUnlocked = unlockedLevelNames.includes(levelJSON.NAME_LEVEL);
|
|
|
|
|
|
|
|
|
|
if (isUnlocked) {
|
|
|
|
|
return {
|
|
|
|
|
NAME_SECTION,
|
|
|
|
|
NAME_TOPIC,
|
|
|
|
|
...levelJSON,
|
|
|
|
|
ID_STUDENT_LEARNING,
|
|
|
|
|
SCORE,
|
|
|
|
|
IS_PRETEST: levelJSON.NAME_LEVEL === "Pretest" ? 1 : 0,
|
|
|
|
|
};
|
|
|
|
|
} else {
|
|
|
|
|
return {
|
|
|
|
|
ID_LEVEL: levelJSON.ID_LEVEL,
|
|
|
|
|
NAME_LEVEL: levelJSON.NAME_LEVEL,
|
|
|
|
|
IS_PRETEST: levelJSON.NAME_LEVEL === "Pretest" ? 1 : 0,
|
|
|
|
|
SCORE,
|
|
|
|
|
};
|
|
|
|
|
}
|
2024-09-19 10:04:18 +00:00
|
|
|
});
|
|
|
|
|
|
2024-09-23 06:16:02 +00:00
|
|
|
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,
|
2024-10-02 02:45:18 +00:00
|
|
|
SCORE: lastCompletedLearning.SCORE,
|
|
|
|
|
NEXT_LEARNING: lastCompletedLearning.NEXT_LEARNING,
|
2024-10-10 02:55:13 +00:00
|
|
|
NEXT_LEARNING_LEVEL: nextLearningLevel
|
|
|
|
|
? nextLearningLevel.NAME_LEVEL
|
|
|
|
|
: null,
|
2024-09-23 06:16:02 +00:00
|
|
|
FINISHED_AT: lastCompletedLearning.STUDENT_FINISH,
|
2024-10-10 03:31:44 +00:00
|
|
|
UNLOCKED_LEVELS: unlockedLevelNames,
|
2024-09-23 06:16:02 +00:00
|
|
|
}
|
2024-10-10 02:55:13 +00:00
|
|
|
: {
|
2024-10-10 03:31:44 +00:00
|
|
|
UNLOCKED_LEVELS: unlockedLevelNames,
|
2024-10-10 02:55:13 +00:00
|
|
|
},
|
2024-09-23 06:16:02 +00:00
|
|
|
levels: sortedLevels,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.status(200).json({ message: "Success", data: responsePayload });
|
2024-09-13 13:03:35 +00:00
|
|
|
} catch (error) {
|
2024-10-01 08:03:44 +00:00
|
|
|
console.error(error);
|
2024-09-13 13:03:35 +00:00
|
|
|
res.status(500).json({ message: "Internal Server Error" });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const createLevel = async (req, res, next) => {
|
2024-09-19 10:04:18 +00:00
|
|
|
const { NAME_LEVEL, ID_SECTION, ID_TOPIC, CONTENT, VIDEO } = req.body;
|
|
|
|
|
const { AUDIO, IMAGE } = req.filesToSave || {};
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
if (!NAME_LEVEL) {
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(400, null, "Level name is required", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ID_SECTION) {
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(400, null, "Section is required", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ID_TOPIC) {
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(400, null, "Topic is required", res);
|
|
|
|
|
}
|
2024-10-10 02:55:13 +00:00
|
|
|
|
2024-10-01 08:03:44 +00:00
|
|
|
const transaction = await models.db.transaction();
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const sectionWithTopic = await models.Topic.findOne({
|
|
|
|
|
where: { ID_SECTION, ID_TOPIC },
|
2024-10-01 08:03:44 +00:00
|
|
|
transaction,
|
2024-09-13 13:03:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!sectionWithTopic) {
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-10-01 08:03:44 +00:00
|
|
|
await transaction.rollback();
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(
|
|
|
|
|
400,
|
|
|
|
|
null,
|
|
|
|
|
"Topic does not relate to the provided Section!",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingLevel = await models.Level.findOne({
|
2024-10-10 02:55:13 +00:00
|
|
|
where: {
|
|
|
|
|
NAME_LEVEL,
|
|
|
|
|
ID_TOPIC,
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
},
|
2024-10-01 08:03:44 +00:00
|
|
|
transaction,
|
2024-09-13 13:03:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existingLevel) {
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-10-01 08:03:44 +00:00
|
|
|
await transaction.rollback();
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(
|
|
|
|
|
409,
|
|
|
|
|
null,
|
|
|
|
|
"A level with this name already exists under this topic",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-01 08:03:44 +00:00
|
|
|
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 }
|
|
|
|
|
);
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
req.body.newLevelId = newLevel.ID_LEVEL;
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
const audioFilename = AUDIO
|
|
|
|
|
? saveFileToDisk(AUDIO, "AUDIO", ID_TOPIC, ID_SECTION, newLevel.ID_LEVEL)
|
2024-09-13 13:03:35 +00:00
|
|
|
: null;
|
2024-09-19 10:04:18 +00:00
|
|
|
const imageFilename = IMAGE
|
|
|
|
|
? saveFileToDisk(IMAGE, "IMAGE", ID_TOPIC, ID_SECTION, newLevel.ID_LEVEL)
|
2024-09-13 13:03:35 +00:00
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
newLevel.AUDIO = audioFilename;
|
|
|
|
|
newLevel.IMAGE = imageFilename;
|
2024-10-01 08:03:44 +00:00
|
|
|
await newLevel.save({ transaction });
|
|
|
|
|
|
|
|
|
|
await transaction.commit();
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
await updateOtherLevelsRoutes(req, res, next);
|
|
|
|
|
|
|
|
|
|
response(201, newLevel, "Level created successfully", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-10-01 08:03:44 +00:00
|
|
|
await transaction.rollback();
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const updateLevelById = async (req, res, next) => {
|
|
|
|
|
const { id } = req.params;
|
2024-09-19 10:04:18 +00:00
|
|
|
const { NAME_LEVEL, ID_SECTION, ID_TOPIC, CONTENT, VIDEO } = req.body;
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
const { AUDIO, IMAGE } = req.filesToSave || {};
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-10-01 08:03:44 +00:00
|
|
|
const transaction = await models.db.transaction();
|
|
|
|
|
|
2024-09-13 13:03:35 +00:00
|
|
|
try {
|
2024-10-01 08:03:44 +00:00
|
|
|
const level = await models.Level.findByPk(id, { transaction });
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
if (!level) {
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-10-01 08:03:44 +00:00
|
|
|
await transaction.rollback();
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(404, null, "Level not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sectionWithTopic = await models.Topic.findOne({
|
|
|
|
|
where: { ID_SECTION, ID_TOPIC },
|
2024-10-01 08:03:44 +00:00
|
|
|
transaction,
|
2024-09-13 13:03:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!sectionWithTopic) {
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-10-01 08:03:44 +00:00
|
|
|
await transaction.rollback();
|
2024-09-13 13:03:35 +00:00
|
|
|
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 },
|
2024-10-10 02:55:13 +00:00
|
|
|
IS_DELETED: 0,
|
2024-09-13 13:03:35 +00:00
|
|
|
},
|
2024-10-01 08:03:44 +00:00
|
|
|
transaction,
|
2024-09-13 13:03:35 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existingLevel) {
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-10-01 08:03:44 +00:00
|
|
|
await transaction.rollback();
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(
|
|
|
|
|
409,
|
|
|
|
|
null,
|
|
|
|
|
"A level with this name already exists under this topic",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NAME_LEVEL) {
|
|
|
|
|
level.NAME_LEVEL = NAME_LEVEL;
|
2024-10-10 02:55:13 +00:00
|
|
|
level.IS_PRETEST = NAME_LEVEL === "Pretest" ? 1 : 0;
|
2024-09-13 13:03:35 +00:00
|
|
|
}
|
|
|
|
|
if (ID_SECTION) level.ID_SECTION = ID_SECTION;
|
|
|
|
|
if (ID_TOPIC) level.ID_TOPIC = ID_TOPIC;
|
|
|
|
|
if (CONTENT) level.CONTENT = CONTENT;
|
2024-10-01 08:03:44 +00:00
|
|
|
if (VIDEO) level.VIDEO = VIDEO;
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
if (AUDIO) {
|
2024-09-13 13:03:35 +00:00
|
|
|
if (level.AUDIO) {
|
|
|
|
|
const oldAudioPath = path.join(
|
2024-11-20 12:56:37 +00:00
|
|
|
"media/uploads/level/audio",
|
2024-09-13 13:03:35 +00:00
|
|
|
level.AUDIO
|
|
|
|
|
);
|
2024-10-01 08:03:44 +00:00
|
|
|
if (fs.existsSync(oldAudioPath)) fs.unlinkSync(oldAudioPath);
|
2024-09-13 13:03:35 +00:00
|
|
|
}
|
|
|
|
|
level.AUDIO = saveFileToDisk(
|
2024-09-19 10:04:18 +00:00
|
|
|
AUDIO,
|
|
|
|
|
"AUDIO",
|
2024-09-13 13:03:35 +00:00
|
|
|
ID_TOPIC || level.ID_TOPIC,
|
|
|
|
|
ID_SECTION || level.ID_SECTION,
|
|
|
|
|
level.ID_LEVEL
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
if (IMAGE) {
|
2024-09-13 13:03:35 +00:00
|
|
|
if (level.IMAGE) {
|
|
|
|
|
const oldImagePath = path.join(
|
2024-11-20 12:56:37 +00:00
|
|
|
"media/uploads/level/image",
|
2024-09-13 13:03:35 +00:00
|
|
|
level.IMAGE
|
|
|
|
|
);
|
2024-10-01 08:03:44 +00:00
|
|
|
if (fs.existsSync(oldImagePath)) fs.unlinkSync(oldImagePath);
|
2024-09-13 13:03:35 +00:00
|
|
|
}
|
|
|
|
|
level.IMAGE = saveFileToDisk(
|
2024-09-19 10:04:18 +00:00
|
|
|
IMAGE,
|
|
|
|
|
"IMAGE",
|
2024-09-13 13:03:35 +00:00
|
|
|
ID_TOPIC || level.ID_TOPIC,
|
|
|
|
|
ID_SECTION || level.ID_SECTION,
|
|
|
|
|
level.ID_LEVEL
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-01 08:03:44 +00:00
|
|
|
await level.save({ transaction });
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-10-01 08:03:44 +00:00
|
|
|
await transaction.commit();
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
await updateOtherLevelsRoutes(req, res, next);
|
|
|
|
|
|
|
|
|
|
response(200, level, "Level updated successfully", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
2024-09-19 10:04:18 +00:00
|
|
|
clearFileBuffers({ AUDIO, IMAGE });
|
2024-10-01 08:03:44 +00:00
|
|
|
await transaction.rollback();
|
2024-09-13 13:03:35 +00:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
level.IS_DELETED = 1;
|
|
|
|
|
await level.save();
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
await models.Exercise.update(
|
|
|
|
|
{ IS_DELETED: 1 },
|
|
|
|
|
{ where: { ID_LEVEL: id } }
|
|
|
|
|
);
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
req.body.newLevelId = level.ID_LEVEL;
|
|
|
|
|
|
|
|
|
|
await updateOtherLevelsRoutesOnDelete(req, res, next);
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
response(
|
|
|
|
|
200,
|
|
|
|
|
null,
|
|
|
|
|
"Level and related exercises soft deleted successfully",
|
|
|
|
|
res
|
|
|
|
|
);
|
2024-09-13 13:03:35 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
return response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
export const deleteLevelFileById = async (req, res) => {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { fileType } = req.body;
|
|
|
|
|
|
|
|
|
|
if (!["audio", "image", "video"].includes(fileType)) {
|
|
|
|
|
return response(400, null, "Invalid file type specified", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const level = await models.Level.findByPk(id);
|
|
|
|
|
|
|
|
|
|
if (!level) {
|
|
|
|
|
return response(404, null, "Level not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let filePath;
|
|
|
|
|
let fileName;
|
|
|
|
|
|
|
|
|
|
if (fileType === "audio" && level.AUDIO) {
|
|
|
|
|
fileName = level.AUDIO;
|
2024-11-20 12:56:37 +00:00
|
|
|
filePath = path.join("media/uploads/level/audio", fileName);
|
2024-10-10 02:55:13 +00:00
|
|
|
level.AUDIO = null;
|
|
|
|
|
} else if (fileType === "image" && level.IMAGE) {
|
|
|
|
|
fileName = level.IMAGE;
|
2024-11-20 12:56:37 +00:00
|
|
|
filePath = path.join("media/uploads/level/image", fileName);
|
2024-10-10 02:55:13 +00:00
|
|
|
level.IMAGE = null;
|
|
|
|
|
} else if (fileType === "video" && level.VIDEO) {
|
|
|
|
|
level.VIDEO = null;
|
|
|
|
|
} else {
|
|
|
|
|
return response(
|
|
|
|
|
404,
|
|
|
|
|
null,
|
|
|
|
|
`${
|
|
|
|
|
fileType.charAt(0).toUpperCase() + fileType.slice(1)
|
|
|
|
|
} file not found`,
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filePath && fs.existsSync(filePath)) {
|
|
|
|
|
fs.unlinkSync(filePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await level.save();
|
|
|
|
|
|
|
|
|
|
response(
|
|
|
|
|
200,
|
|
|
|
|
level,
|
|
|
|
|
`${
|
|
|
|
|
fileType.charAt(0).toUpperCase() + fileType.slice(1)
|
|
|
|
|
} file deleted successfully`,
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-23 02:11:42 +00:00
|
|
|
export const deleteLevelFilesById = async (req, res) => {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { fileName } = req.body;
|
|
|
|
|
|
|
|
|
|
const filePattern =
|
|
|
|
|
/^(AUDIO|IMAGE)-([a-f0-9-]{36})-[a-f0-9]{32}\.(mp3|jpg|jpeg|png)$/;
|
|
|
|
|
const match = fileName.match(filePattern);
|
|
|
|
|
|
|
|
|
|
if (!match) {
|
|
|
|
|
return response(400, null, "Invalid file name format", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const fileType = match[1];
|
|
|
|
|
const levelIdFromFile = match[2];
|
|
|
|
|
|
|
|
|
|
if (levelIdFromFile !== id) {
|
|
|
|
|
return response(400, null, "Level ID in file name does not match", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const level = await models.Level.findByPk(id);
|
|
|
|
|
|
|
|
|
|
if (!level) {
|
|
|
|
|
return response(404, null, "Level not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let filePath;
|
|
|
|
|
|
|
|
|
|
if (fileType === "AUDIO") {
|
2024-11-20 12:56:37 +00:00
|
|
|
filePath = path.join("media/uploads/level/audio", fileName);
|
2024-10-23 02:11:42 +00:00
|
|
|
} else if (fileType === "IMAGE") {
|
2024-11-20 12:56:37 +00:00
|
|
|
filePath = path.join("media/uploads/level/image", fileName);
|
2024-10-23 02:11:42 +00:00
|
|
|
} else {
|
|
|
|
|
return response(400, null, "Invalid file type", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filePath && fs.existsSync(filePath)) {
|
|
|
|
|
fs.unlinkSync(filePath);
|
|
|
|
|
return response(200, null, `${fileType} file deleted successfully`, res);
|
|
|
|
|
} else {
|
|
|
|
|
return response(404, null, "File not found on the server", res);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
export const getPreviousLevel = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { next_learning } = req.params;
|
|
|
|
|
const { ID } = req.user;
|
|
|
|
|
|
|
|
|
|
const currentLevel = await models.Level.findByPk(next_learning, {
|
2024-10-10 02:55:13 +00:00
|
|
|
where: {
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
},
|
2024-09-19 10:04:18 +00:00
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.StdLearning,
|
|
|
|
|
as: "stdLearning",
|
2024-10-01 08:03:44 +00:00
|
|
|
attributes: [
|
|
|
|
|
"SCORE",
|
|
|
|
|
"ID_STUDENT_LEARNING",
|
|
|
|
|
"STUDENT_START",
|
|
|
|
|
"STUDENT_FINISH",
|
|
|
|
|
],
|
2024-09-19 10:04:18 +00:00
|
|
|
where: {
|
|
|
|
|
ID: ID,
|
|
|
|
|
},
|
|
|
|
|
required: false,
|
2024-10-01 08:03:44 +00:00
|
|
|
order: [["STUDENT_FINISH", "DESC"]],
|
|
|
|
|
limit: 1,
|
2024-09-19 10:04:18 +00:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
attributes: {
|
|
|
|
|
exclude: [
|
|
|
|
|
"ROUTE_1",
|
|
|
|
|
"ROUTE_2",
|
|
|
|
|
"ROUTE_3",
|
|
|
|
|
"ROUTE_4",
|
|
|
|
|
"ROUTE_5",
|
|
|
|
|
"ROUTE_6",
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!currentLevel) {
|
2024-10-01 08:03:44 +00:00
|
|
|
return res.status(404).json({ message: "Level not found" });
|
2024-09-19 10:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { NAME_LEVEL, ID_TOPIC } = currentLevel;
|
|
|
|
|
const levelNumber = parseInt(NAME_LEVEL.replace("Level ", ""));
|
|
|
|
|
|
|
|
|
|
if (isNaN(levelNumber)) {
|
2024-10-01 08:03:44 +00:00
|
|
|
return res.status(400).json({ message: "Invalid level format" });
|
2024-09-19 10:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const previousLevels = await models.Level.findAll({
|
|
|
|
|
where: {
|
|
|
|
|
ID_TOPIC: ID_TOPIC,
|
2024-10-10 02:55:13 +00:00
|
|
|
IS_DELETED: 0,
|
2024-09-19 10:04:18 +00:00
|
|
|
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}`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
attributes: {
|
|
|
|
|
exclude: [
|
|
|
|
|
"ROUTE_1",
|
|
|
|
|
"ROUTE_2",
|
|
|
|
|
"ROUTE_3",
|
|
|
|
|
"ROUTE_4",
|
|
|
|
|
"ROUTE_5",
|
|
|
|
|
"ROUTE_6",
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.StdLearning,
|
|
|
|
|
as: "stdLearning",
|
2024-10-01 08:03:44 +00:00
|
|
|
attributes: [
|
|
|
|
|
"SCORE",
|
|
|
|
|
"ID_STUDENT_LEARNING",
|
|
|
|
|
"STUDENT_START",
|
|
|
|
|
"STUDENT_FINISH",
|
|
|
|
|
],
|
2024-09-19 10:04:18 +00:00
|
|
|
where: {
|
|
|
|
|
ID: ID,
|
|
|
|
|
},
|
|
|
|
|
required: false,
|
2024-10-01 08:03:44 +00:00
|
|
|
order: [["STUDENT_START", "DESC"]],
|
|
|
|
|
limit: 1,
|
2024-09-19 10:04:18 +00:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const previousLevelsWithScore = previousLevels.map((level) => {
|
|
|
|
|
const SCORE =
|
2024-10-01 08:03:44 +00:00
|
|
|
level.stdLearning && level.stdLearning.length > 0
|
|
|
|
|
? level.stdLearning[0]?.SCORE
|
|
|
|
|
: null;
|
2024-09-19 10:04:18 +00:00
|
|
|
const ID_STUDENT_LEARNING =
|
2024-10-01 08:03:44 +00:00
|
|
|
level.stdLearning && level.stdLearning.length > 0
|
|
|
|
|
? level.stdLearning[0]?.ID_STUDENT_LEARNING
|
2024-09-19 10:04:18 +00:00
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
const levelJSON = level.toJSON();
|
|
|
|
|
delete levelJSON.stdLearning;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...levelJSON,
|
|
|
|
|
ID_STUDENT_LEARNING,
|
|
|
|
|
SCORE,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const currentLevelWithScore = {
|
|
|
|
|
...currentLevel.toJSON(),
|
|
|
|
|
ID_STUDENT_LEARNING:
|
2024-10-01 08:03:44 +00:00
|
|
|
currentLevel.stdLearning && currentLevel.stdLearning.length > 0
|
|
|
|
|
? currentLevel.stdLearning[0]?.ID_STUDENT_LEARNING
|
2024-09-19 10:04:18 +00:00
|
|
|
: null,
|
|
|
|
|
SCORE:
|
2024-10-01 08:03:44 +00:00
|
|
|
currentLevel.stdLearning && currentLevel.stdLearning.length > 0
|
|
|
|
|
? currentLevel.stdLearning[0]?.SCORE
|
|
|
|
|
: null,
|
2024-09-19 10:04:18 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
delete currentLevelWithScore.stdLearning;
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
const sortedLevels = previousLevelsWithScore.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;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!sortedLevels.length && !currentLevelWithScore) {
|
2024-10-01 08:03:44 +00:00
|
|
|
return res.status(404).json({ message: "No levels found" });
|
2024-09-19 10:04:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = {
|
|
|
|
|
currentLevel: currentLevelWithScore,
|
2024-10-10 02:55:13 +00:00
|
|
|
previousLevels: sortedLevels,
|
2024-09-19 10:04:18 +00:00
|
|
|
};
|
|
|
|
|
|
2024-10-01 08:03:44 +00:00
|
|
|
res.status(200).json({ message: "Success", data: result });
|
2024-09-19 10:04:18 +00:00
|
|
|
} catch (error) {
|
2024-10-01 08:03:44 +00:00
|
|
|
console.error(error);
|
2024-09-19 10:04:18 +00:00
|
|
|
res.status(500).json({ message: "Internal Server Error" });
|
|
|
|
|
}
|
2024-09-13 13:03:35 +00:00
|
|
|
};
|
2024-10-23 02:11:42 +00:00
|
|
|
|
|
|
|
|
export const uploadLevelFile = async (req, res) => {
|
|
|
|
|
const { levelId } = req.params;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const level = await models.Level.findByPk(levelId);
|
|
|
|
|
|
|
|
|
|
if (!level) {
|
|
|
|
|
return response(404, null, "Level not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const filesToSave = req.filesToSave;
|
|
|
|
|
|
|
|
|
|
if (!filesToSave || Object.keys(filesToSave).length === 0) {
|
|
|
|
|
return response(400, null, "No files to upload", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { ID_TOPIC, ID_SECTION } = level;
|
|
|
|
|
const savedFiles = {};
|
|
|
|
|
|
|
|
|
|
Object.keys(filesToSave).forEach((key) => {
|
|
|
|
|
let filename;
|
|
|
|
|
if (key.startsWith("AUDIO")) {
|
|
|
|
|
const audioFile = filesToSave[key];
|
|
|
|
|
filename = saveFileToDisk(
|
|
|
|
|
audioFile,
|
|
|
|
|
"AUDIO",
|
|
|
|
|
ID_TOPIC,
|
|
|
|
|
ID_SECTION,
|
|
|
|
|
levelId
|
|
|
|
|
);
|
|
|
|
|
} else if (key.startsWith("IMAGE")) {
|
|
|
|
|
const imageFile = filesToSave[key];
|
|
|
|
|
filename = saveFileToDisk(
|
|
|
|
|
imageFile,
|
|
|
|
|
"IMAGE",
|
|
|
|
|
ID_TOPIC,
|
|
|
|
|
ID_SECTION,
|
|
|
|
|
levelId
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filename) {
|
|
|
|
|
savedFiles[key] = filename;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (Object.keys(savedFiles).length === 0) {
|
|
|
|
|
return response(400, null, "Failed to save files", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response(200, savedFiles, "Files uploaded successfully", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
return response(500, null, "Internal server error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getLevelFiles = async (req, res) => {
|
|
|
|
|
const { levelId } = req.params;
|
|
|
|
|
|
|
|
|
|
try {
|
2024-11-20 12:56:37 +00:00
|
|
|
const audioFolderPath = path.join("media/uploads/level/audio");
|
|
|
|
|
const imageFolderPath = path.join("media/uploads/level/image");
|
2024-10-23 02:11:42 +00:00
|
|
|
|
|
|
|
|
const getFilesByLevelId = (folderPath, fileType) => {
|
|
|
|
|
if (fs.existsSync(folderPath)) {
|
|
|
|
|
const files = fs.readdirSync(folderPath);
|
|
|
|
|
return files.filter((file) =>
|
|
|
|
|
file.startsWith(`${fileType}-${levelId}`)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const audioFiles = getFilesByLevelId(audioFolderPath, "AUDIO");
|
|
|
|
|
const imageFiles = getFilesByLevelId(imageFolderPath, "IMAGE");
|
|
|
|
|
|
|
|
|
|
if (audioFiles.length === 0 && imageFiles.length === 0) {
|
|
|
|
|
return response(404, null, "No files found for this level", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const levelFiles = {
|
|
|
|
|
audioFiles,
|
|
|
|
|
imageFiles,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return response(200, levelFiles, "Files retrieved successfully", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
return response(500, null, "Internal server error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|