2024-09-13 13:03:35 +00:00
|
|
|
import response from "../../response.js";
|
|
|
|
|
import models from "../../models/index.js";
|
|
|
|
|
import fs from "fs";
|
|
|
|
|
import path from "path";
|
2024-10-10 02:55:13 +00:00
|
|
|
import {
|
|
|
|
|
clearFileBuffers,
|
|
|
|
|
saveFileToDisk,
|
|
|
|
|
} from "../../middlewares/uploadExercise.js";
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
export const getExercises = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const exercises = await models.Exercise.findAll({
|
2024-10-10 02:55:13 +00:00
|
|
|
where: { IS_DELETED: 0 },
|
2024-09-13 13:03:35 +00:00
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.MultipleChoices,
|
|
|
|
|
as: "multipleChoices",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
model: models.MatchingPairs,
|
|
|
|
|
as: "matchingPairs",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
model: models.TrueFalse,
|
|
|
|
|
as: "trueFalse",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (exercises.length === 0) {
|
|
|
|
|
return response(404, null, "No exercises found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const result = 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, result, "Success", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
res.status(500).json({ message: "Internal Server Error" });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getExerciseById = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
const exercise = await models.Exercise.findOne({
|
|
|
|
|
where: { ID_ADMIN_EXERCISE: id, IS_DELETED: 0 },
|
2024-09-13 13:03:35 +00:00
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.MultipleChoices,
|
|
|
|
|
as: "multipleChoices",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
model: models.MatchingPairs,
|
|
|
|
|
as: "matchingPairs",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
model: models.TrueFalse,
|
|
|
|
|
as: "trueFalse",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!exercise) {
|
|
|
|
|
return response(404, null, "Exercise not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const exerciseData = { ...exercise.dataValues };
|
|
|
|
|
const questionType = exercise.QUESTION_TYPE;
|
|
|
|
|
|
|
|
|
|
if (questionType === "MCQ") {
|
2024-09-30 01:06:15 +00:00
|
|
|
if (exerciseData.multipleChoices) {
|
|
|
|
|
exerciseData.multipleChoices = exerciseData.multipleChoices.map(
|
2024-10-10 02:55:13 +00:00
|
|
|
(choice) => choice.dataValues
|
2024-09-30 01:06:15 +00:00
|
|
|
);
|
|
|
|
|
}
|
2024-09-13 13:03:35 +00:00
|
|
|
delete exerciseData.matchingPairs;
|
|
|
|
|
delete exerciseData.trueFalse;
|
|
|
|
|
} else if (questionType === "MPQ") {
|
2024-09-30 01:06:15 +00:00
|
|
|
if (exerciseData.matchingPairs) {
|
2024-10-10 02:55:13 +00:00
|
|
|
exerciseData.matchingPairs = exerciseData.matchingPairs.map(
|
|
|
|
|
(pair) => pair.dataValues
|
|
|
|
|
);
|
2024-09-30 01:06:15 +00:00
|
|
|
}
|
2024-09-13 13:03:35 +00:00
|
|
|
delete exerciseData.multipleChoices;
|
|
|
|
|
delete exerciseData.trueFalse;
|
|
|
|
|
} else if (questionType === "TFQ") {
|
2024-09-30 01:06:15 +00:00
|
|
|
if (exerciseData.trueFalse) {
|
2024-10-10 02:55:13 +00:00
|
|
|
exerciseData.trueFalse = exerciseData.trueFalse.map(
|
|
|
|
|
(tf) => tf.dataValues
|
|
|
|
|
);
|
2024-09-30 01:06:15 +00:00
|
|
|
}
|
2024-09-13 13:03:35 +00:00
|
|
|
delete exerciseData.multipleChoices;
|
|
|
|
|
delete exerciseData.matchingPairs;
|
|
|
|
|
} else {
|
|
|
|
|
delete exerciseData.multipleChoices;
|
|
|
|
|
delete exerciseData.matchingPairs;
|
|
|
|
|
delete exerciseData.trueFalse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response(200, exerciseData, "Success", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
res.status(500).json({ message: "Internal Server Error" });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
export const getExercisesForAdmin = async (req, res) => {
|
|
|
|
|
const { page = 1, limit = 10, search = "", sort = "time" } = req.query;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const { rows: exercises } = await models.Exercise.findAndCountAll({
|
|
|
|
|
where: {
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
...(search && {
|
|
|
|
|
[models.Op.or]: [
|
|
|
|
|
{
|
|
|
|
|
"$levelExercise->levelTopic->topicSection.NAME_SECTION$": {
|
|
|
|
|
[models.Op.like]: `%${search}%`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"$levelExercise->levelTopic.NAME_TOPIC$": {
|
|
|
|
|
[models.Op.like]: `%${search}%`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
"$levelExercise.NAME_LEVEL$": { [models.Op.like]: `%${search}%` },
|
|
|
|
|
},
|
|
|
|
|
{ QUESTION: { [models.Op.like]: `%${search}%` } },
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
distinct: true,
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Level,
|
|
|
|
|
as: "levelExercise",
|
|
|
|
|
attributes: ["NAME_LEVEL"],
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Topic,
|
|
|
|
|
as: "levelTopic",
|
|
|
|
|
attributes: ["NAME_TOPIC"],
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Section,
|
|
|
|
|
as: "topicSection",
|
|
|
|
|
attributes: ["NAME_SECTION"],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
model: models.MultipleChoices,
|
|
|
|
|
as: "multipleChoices",
|
|
|
|
|
attributes: ["ANSWER_KEY"],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
model: models.MatchingPairs,
|
|
|
|
|
as: "matchingPairs",
|
|
|
|
|
attributes: ["LEFT_PAIR", "RIGHT_PAIR"],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
model: models.TrueFalse,
|
|
|
|
|
as: "trueFalse",
|
|
|
|
|
attributes: ["IS_TRUE"],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const formattedExercises = exercises.map((exercise) => {
|
|
|
|
|
const questionType = exercise.QUESTION_TYPE;
|
|
|
|
|
let answerKey = null;
|
|
|
|
|
|
|
|
|
|
const nameSection =
|
|
|
|
|
exercise.levelExercise?.levelTopic?.topicSection?.NAME_SECTION ||
|
|
|
|
|
"Unknown Section";
|
|
|
|
|
const nameTopic =
|
|
|
|
|
exercise.levelExercise?.levelTopic?.NAME_TOPIC || "Unknown Topic";
|
|
|
|
|
|
|
|
|
|
if (questionType === "MCQ" && exercise.multipleChoices.length > 0) {
|
|
|
|
|
answerKey = exercise.multipleChoices[0].ANSWER_KEY;
|
|
|
|
|
} 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) {
|
|
|
|
|
answerKey = exercise.trueFalse[0].IS_TRUE === 1 ? "true" : "false";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
ID_ADMIN_EXERCISE: exercise.ID_ADMIN_EXERCISE,
|
|
|
|
|
NAME_SECTION: nameSection,
|
|
|
|
|
NAME_TOPIC: nameTopic,
|
|
|
|
|
NAME_LEVEL: exercise.levelExercise.NAME_LEVEL,
|
|
|
|
|
TITLE: exercise.TITLE,
|
|
|
|
|
QUESTION: exercise.QUESTION,
|
|
|
|
|
QUESTION_TYPE: questionType,
|
|
|
|
|
ANSWER_KEY: answerKey,
|
|
|
|
|
TIME_ADMIN_EXC: exercise.TIME_ADMIN_EXC,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const filteredExercises = formattedExercises.filter(
|
|
|
|
|
(exercise) =>
|
|
|
|
|
exercise.NAME_SECTION !== "Unknown Section" &&
|
|
|
|
|
exercise.NAME_TOPIC !== "Unknown Topic"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (sort === "section") {
|
|
|
|
|
filteredExercises.sort((a, b) => {
|
|
|
|
|
return a.NAME_SECTION.localeCompare(b.NAME_SECTION);
|
|
|
|
|
});
|
|
|
|
|
} else if (sort === "topic") {
|
|
|
|
|
filteredExercises.sort((a, b) => {
|
|
|
|
|
return a.NAME_TOPIC.localeCompare(b.NAME_TOPIC);
|
|
|
|
|
});
|
|
|
|
|
} else if (sort === "level") {
|
|
|
|
|
filteredExercises.sort((a, b) => {
|
|
|
|
|
return a.NAME_LEVEL.localeCompare(b.NAME_LEVEL);
|
|
|
|
|
});
|
|
|
|
|
} else if (sort === "question") {
|
|
|
|
|
filteredExercises.sort((a, b) => {
|
|
|
|
|
return a.QUESTION.localeCompare(b.QUESTION);
|
|
|
|
|
});
|
|
|
|
|
} else if (sort === "answer") {
|
|
|
|
|
filteredExercises.sort((a, b) => {
|
|
|
|
|
return (a.ANSWER_KEY || "").localeCompare(b.ANSWER_KEY || "");
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
filteredExercises.sort((a, b) => {
|
|
|
|
|
return new Date(b.TIME_ADMIN_EXC) - new Date(a.TIME_ADMIN_EXC);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const formattedSortedExercises = filteredExercises.map((exercise) => {
|
|
|
|
|
const formattedTimeAdminExc = new Date(exercise.TIME_ADMIN_EXC)
|
|
|
|
|
.toLocaleString("en-GB", {
|
|
|
|
|
hour12: false,
|
|
|
|
|
hour: "2-digit",
|
|
|
|
|
minute: "2-digit",
|
|
|
|
|
day: "2-digit",
|
|
|
|
|
month: "2-digit",
|
|
|
|
|
year: "numeric",
|
|
|
|
|
})
|
|
|
|
|
.replace(/(\d{2}\/\d{2}\/\d{4}), (\d{2}:\d{2})/, "$2 $1");
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...exercise,
|
|
|
|
|
TIME_ADMIN_EXC: formattedTimeAdminExc,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const paginatedExercises = formattedSortedExercises.slice(
|
|
|
|
|
(page - 1) * limit,
|
|
|
|
|
page * limit
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const totalPages = Math.ceil(formattedSortedExercises.length / limit);
|
|
|
|
|
|
|
|
|
|
response(
|
|
|
|
|
200,
|
|
|
|
|
{
|
|
|
|
|
exercises: paginatedExercises,
|
|
|
|
|
currentPage: parseInt(page),
|
|
|
|
|
totalPages: totalPages,
|
|
|
|
|
totalExercises: formattedSortedExercises.length,
|
|
|
|
|
},
|
|
|
|
|
"Success",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Error retrieving exercises data!", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
export const getExerciseByLevelId = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { idLevel } = req.params;
|
|
|
|
|
|
2024-10-02 02:45:18 +00:00
|
|
|
const levelExists = await models.Level.findByPk(idLevel, {
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Topic,
|
|
|
|
|
as: "levelTopic",
|
|
|
|
|
attributes: ["NAME_TOPIC"],
|
2024-10-14 08:18:13 +00:00
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Section,
|
|
|
|
|
as: "topicSection",
|
|
|
|
|
attributes: ["NAME_SECTION"],
|
|
|
|
|
},
|
|
|
|
|
],
|
2024-10-02 02:45:18 +00:00
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
attributes: ["NAME_LEVEL"],
|
|
|
|
|
});
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
if (!levelExists) {
|
|
|
|
|
return response(404, null, "Level not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const exercises = await models.Exercise.findAll({
|
2024-10-10 02:55:13 +00:00
|
|
|
where: { ID_LEVEL: idLevel, IS_DELETED: 0 },
|
2024-09-19 10:04:18 +00:00
|
|
|
include: [
|
2024-10-14 08:18:13 +00:00
|
|
|
{ model: models.MultipleChoices, as: "multipleChoices" },
|
|
|
|
|
{ model: models.MatchingPairs, as: "matchingPairs" },
|
|
|
|
|
{ model: models.TrueFalse, as: "trueFalse" },
|
2024-09-19 10:04:18 +00:00
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
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") {
|
2024-09-30 01:06:15 +00:00
|
|
|
if (exerciseData.multipleChoices) {
|
|
|
|
|
exerciseData.multipleChoices = exerciseData.multipleChoices.map(
|
|
|
|
|
(choice) => {
|
2024-09-30 03:20:31 +00:00
|
|
|
const { ANSWER_KEY, ...rest } = choice.dataValues;
|
2024-09-30 01:06:15 +00:00
|
|
|
return rest;
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-09-19 10:04:18 +00:00
|
|
|
delete exerciseData.matchingPairs;
|
|
|
|
|
delete exerciseData.trueFalse;
|
|
|
|
|
} else if (questionType === "MPQ") {
|
2024-09-30 01:06:15 +00:00
|
|
|
if (exerciseData.matchingPairs) {
|
|
|
|
|
exerciseData.matchingPairs = exerciseData.matchingPairs.map(
|
2024-09-30 03:20:31 +00:00
|
|
|
(pair) => pair.dataValues
|
2024-09-30 01:06:15 +00:00
|
|
|
);
|
|
|
|
|
}
|
2024-09-19 10:04:18 +00:00
|
|
|
delete exerciseData.multipleChoices;
|
|
|
|
|
delete exerciseData.trueFalse;
|
|
|
|
|
} else if (questionType === "TFQ") {
|
2024-09-30 01:06:15 +00:00
|
|
|
if (exerciseData.trueFalse) {
|
|
|
|
|
exerciseData.trueFalse = exerciseData.trueFalse.map((tf) => {
|
2024-09-30 03:20:31 +00:00
|
|
|
const { IS_TRUE, ...rest } = tf.dataValues;
|
2024-09-30 01:06:15 +00:00
|
|
|
return rest;
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-09-19 10:04:18 +00:00
|
|
|
delete exerciseData.multipleChoices;
|
|
|
|
|
delete exerciseData.matchingPairs;
|
|
|
|
|
} else {
|
|
|
|
|
delete exerciseData.multipleChoices;
|
|
|
|
|
delete exerciseData.matchingPairs;
|
|
|
|
|
delete exerciseData.trueFalse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return exerciseData;
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-02 02:45:18 +00:00
|
|
|
const responsePayload = {
|
2024-10-14 08:18:13 +00:00
|
|
|
NAME_SECTION: levelExists.levelTopic.topicSection.NAME_SECTION,
|
2024-10-16 03:24:41 +00:00
|
|
|
NAME_TOPIC: levelExists.levelTopic.NAME_TOPIC,
|
|
|
|
|
NAME_LEVEL: levelExists.NAME_LEVEL,
|
|
|
|
|
EXERCISES: formattedExercises,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
response(200, responsePayload, "Success", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
res.status(500).json({ message: "Internal Server Error" });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getExerciseByLevelIdForAdmin = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { idLevel } = req.params;
|
|
|
|
|
|
|
|
|
|
const levelExists = await models.Level.findByPk(idLevel, {
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Topic,
|
|
|
|
|
as: "levelTopic",
|
|
|
|
|
attributes: ["NAME_TOPIC"],
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Section,
|
|
|
|
|
as: "topicSection",
|
|
|
|
|
attributes: ["NAME_SECTION"],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
attributes: ["NAME_LEVEL"],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!levelExists) {
|
|
|
|
|
return response(404, null, "Level not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const exercises = await models.Exercise.findAll({
|
|
|
|
|
where: { ID_LEVEL: idLevel, IS_DELETED: 0 },
|
|
|
|
|
include: [
|
|
|
|
|
{ model: models.MultipleChoices, as: "multipleChoices" },
|
|
|
|
|
{ model: models.MatchingPairs, as: "matchingPairs" },
|
|
|
|
|
{ model: models.TrueFalse, as: "trueFalse" },
|
|
|
|
|
],
|
2024-10-18 07:27:44 +00:00
|
|
|
order: [
|
|
|
|
|
[
|
|
|
|
|
models.Sequelize.literal(
|
|
|
|
|
"CAST(SUBSTRING_INDEX(TITLE, ' ', -1) AS UNSIGNED)"
|
|
|
|
|
),
|
|
|
|
|
"ASC",
|
|
|
|
|
],
|
|
|
|
|
],
|
2024-10-16 03:24:41 +00:00
|
|
|
});
|
|
|
|
|
|
2024-10-18 07:02:54 +00:00
|
|
|
let formattedExercises = [];
|
|
|
|
|
|
|
|
|
|
if (exercises && exercises.length > 0) {
|
|
|
|
|
formattedExercises = exercises.map((exercise) => {
|
|
|
|
|
const exerciseData = { ...exercise.dataValues };
|
|
|
|
|
const questionType = exercise.QUESTION_TYPE;
|
|
|
|
|
|
|
|
|
|
if (questionType === "MCQ") {
|
|
|
|
|
if (exerciseData.multipleChoices) {
|
|
|
|
|
exerciseData.multipleChoices = exerciseData.multipleChoices.map(
|
|
|
|
|
(choice) => choice.dataValues
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
delete exerciseData.matchingPairs;
|
|
|
|
|
delete exerciseData.trueFalse;
|
|
|
|
|
} else if (questionType === "MPQ") {
|
|
|
|
|
if (exerciseData.matchingPairs) {
|
|
|
|
|
exerciseData.matchingPairs = exerciseData.matchingPairs.map(
|
|
|
|
|
(pair) => pair.dataValues
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
delete exerciseData.multipleChoices;
|
|
|
|
|
delete exerciseData.trueFalse;
|
|
|
|
|
} else if (questionType === "TFQ") {
|
|
|
|
|
if (exerciseData.trueFalse) {
|
|
|
|
|
exerciseData.trueFalse = exerciseData.trueFalse.map(
|
|
|
|
|
(tf) => tf.dataValues
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
delete exerciseData.multipleChoices;
|
|
|
|
|
delete exerciseData.matchingPairs;
|
|
|
|
|
} else {
|
|
|
|
|
delete exerciseData.multipleChoices;
|
|
|
|
|
delete exerciseData.matchingPairs;
|
|
|
|
|
delete exerciseData.trueFalse;
|
2024-10-16 03:24:41 +00:00
|
|
|
}
|
|
|
|
|
|
2024-10-18 07:02:54 +00:00
|
|
|
return exerciseData;
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-10-16 03:24:41 +00:00
|
|
|
|
|
|
|
|
const responsePayload = {
|
|
|
|
|
NAME_SECTION: levelExists.levelTopic.topicSection.NAME_SECTION,
|
2024-10-02 02:45:18 +00:00
|
|
|
NAME_TOPIC: levelExists.levelTopic.NAME_TOPIC,
|
|
|
|
|
NAME_LEVEL: levelExists.NAME_LEVEL,
|
|
|
|
|
EXERCISES: formattedExercises,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
response(200, responsePayload, "Success", res);
|
2024-09-19 10:04:18 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
res.status(500).json({ message: "Internal Server Error" });
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
export const createExercises = async (req, res) => {
|
|
|
|
|
const { exercises } = req.body;
|
|
|
|
|
|
|
|
|
|
if (!exercises || !Array.isArray(exercises) || exercises.length === 0) {
|
|
|
|
|
return response(400, null, "Exercises array is required", res);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-13 13:03:35 +00:00
|
|
|
const transaction = await models.db.transaction();
|
|
|
|
|
|
|
|
|
|
try {
|
2024-10-10 02:55:13 +00:00
|
|
|
const createdExercises = [];
|
|
|
|
|
|
|
|
|
|
const levelId = exercises[0]?.ID_LEVEL;
|
|
|
|
|
let lastExercise = await models.Exercise.findOne({
|
|
|
|
|
where: { ID_LEVEL: levelId },
|
2024-10-18 07:02:54 +00:00
|
|
|
order: [
|
|
|
|
|
[
|
|
|
|
|
models.Sequelize.literal(
|
|
|
|
|
"CAST(SUBSTRING_INDEX(TITLE, ' ', -1) AS UNSIGNED)"
|
|
|
|
|
),
|
|
|
|
|
"DESC",
|
|
|
|
|
],
|
|
|
|
|
],
|
2024-10-10 02:55:13 +00:00
|
|
|
limit: 1,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let lastTitleNumber = 0;
|
|
|
|
|
|
|
|
|
|
if (lastExercise && lastExercise.TITLE) {
|
|
|
|
|
const lastTitleParts = lastExercise.TITLE.split(" ");
|
|
|
|
|
lastTitleNumber = parseInt(lastTitleParts[1], 10) || 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < exercises.length; i++) {
|
|
|
|
|
const exerciseData = exercises[i];
|
|
|
|
|
const { ID_LEVEL, TITLE, QUESTION, SCORE_WEIGHT, VIDEO, QUESTION_TYPE } =
|
|
|
|
|
exerciseData;
|
|
|
|
|
|
|
|
|
|
if (!ID_LEVEL) throw new Error("Level ID is required");
|
|
|
|
|
if (!QUESTION) throw new Error("Question is required");
|
|
|
|
|
if (!SCORE_WEIGHT) throw new Error("Score weight is required");
|
|
|
|
|
|
|
|
|
|
const level = await models.Level.findByPk(ID_LEVEL);
|
|
|
|
|
if (!level) throw new Error("Level not found");
|
|
|
|
|
|
|
|
|
|
let generatedTitle = TITLE;
|
|
|
|
|
if (!TITLE) {
|
|
|
|
|
lastTitleNumber++;
|
2024-10-18 07:24:10 +00:00
|
|
|
generatedTitle = `Question ${lastTitleNumber}`;
|
2024-10-10 02:55:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingExercise = await models.Exercise.findOne({
|
|
|
|
|
where: { ID_LEVEL, TITLE: generatedTitle },
|
|
|
|
|
});
|
|
|
|
|
if (existingExercise) {
|
|
|
|
|
throw new Error(
|
2024-10-16 06:58:47 +00:00
|
|
|
`An exercise with the TITLE ${generatedTitle} already exists`
|
2024-10-10 02:55:13 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newExercise = await models.Exercise.create(
|
2024-09-13 13:03:35 +00:00
|
|
|
{
|
2024-10-10 02:55:13 +00:00
|
|
|
ID_LEVEL,
|
|
|
|
|
TITLE: generatedTitle,
|
|
|
|
|
QUESTION,
|
|
|
|
|
SCORE_WEIGHT,
|
|
|
|
|
QUESTION_TYPE,
|
|
|
|
|
AUDIO: null,
|
|
|
|
|
VIDEO: VIDEO || null,
|
|
|
|
|
IMAGE: null,
|
2024-09-13 13:03:35 +00:00
|
|
|
},
|
2024-10-10 02:55:13 +00:00
|
|
|
{ transaction }
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const AUDIO = req.filesToSave[`AUDIO[${i}]`] || null;
|
|
|
|
|
const IMAGE = req.filesToSave[`IMAGE[${i}]`] || 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
|
|
|
|
|
)
|
|
|
|
|
: null;
|
|
|
|
|
|
|
|
|
|
newExercise.AUDIO = audioFilename;
|
|
|
|
|
newExercise.IMAGE = imageFilename;
|
|
|
|
|
await newExercise.save({ transaction });
|
|
|
|
|
|
|
|
|
|
let questionDetails = null;
|
|
|
|
|
switch (QUESTION_TYPE) {
|
|
|
|
|
case "MCQ":
|
|
|
|
|
const {
|
|
|
|
|
OPTION_A,
|
|
|
|
|
OPTION_B,
|
|
|
|
|
OPTION_C,
|
|
|
|
|
OPTION_D,
|
|
|
|
|
OPTION_E,
|
|
|
|
|
ANSWER_KEY,
|
|
|
|
|
} = exerciseData;
|
|
|
|
|
if (
|
|
|
|
|
!OPTION_A ||
|
|
|
|
|
!OPTION_B ||
|
|
|
|
|
!OPTION_C ||
|
|
|
|
|
!OPTION_D ||
|
|
|
|
|
!OPTION_E ||
|
|
|
|
|
!ANSWER_KEY
|
|
|
|
|
) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
"All options and answer key are required for Multiple Choice"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
questionDetails = await models.MultipleChoices.create(
|
|
|
|
|
{
|
|
|
|
|
ID_ADMIN_EXERCISE: newExercise.ID_ADMIN_EXERCISE,
|
|
|
|
|
OPTION_A,
|
|
|
|
|
OPTION_B,
|
|
|
|
|
OPTION_C,
|
|
|
|
|
OPTION_D,
|
|
|
|
|
OPTION_E,
|
|
|
|
|
ANSWER_KEY,
|
|
|
|
|
},
|
|
|
|
|
{ transaction }
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "MPQ":
|
|
|
|
|
const { PAIRS } = exerciseData;
|
|
|
|
|
if (!PAIRS || !Array.isArray(PAIRS) || PAIRS.length === 0) {
|
|
|
|
|
throw new Error("At least one pair is required for Matching Pairs");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const matchingPairsPromises = PAIRS.map((pair) =>
|
|
|
|
|
models.MatchingPairs.create(
|
|
|
|
|
{
|
|
|
|
|
ID_ADMIN_EXERCISE: newExercise.ID_ADMIN_EXERCISE,
|
|
|
|
|
LEFT_PAIR: pair.LEFT_PAIR,
|
|
|
|
|
RIGHT_PAIR: pair.RIGHT_PAIR,
|
|
|
|
|
},
|
|
|
|
|
{ transaction }
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
await Promise.all(matchingPairsPromises);
|
|
|
|
|
questionDetails = PAIRS;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "TFQ":
|
|
|
|
|
const { IS_TRUE } = exerciseData;
|
|
|
|
|
if (typeof IS_TRUE === "undefined") {
|
|
|
|
|
throw new Error("IS_TRUE is required for True/False");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
questionDetails = await models.TrueFalse.create(
|
|
|
|
|
{
|
|
|
|
|
ID_ADMIN_EXERCISE: newExercise.ID_ADMIN_EXERCISE,
|
|
|
|
|
IS_TRUE,
|
|
|
|
|
},
|
|
|
|
|
{ transaction }
|
|
|
|
|
);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
throw new Error("Unsupported question type");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newExercise.dataValues.questionDetails = questionDetails;
|
|
|
|
|
createdExercises.push(newExercise);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await transaction.commit();
|
|
|
|
|
|
|
|
|
|
response(201, createdExercises, "Exercises created successfully", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
response(500, null, error.message || "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const updateExerciseById = async (req, res) => {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { ID_LEVEL, QUESTION, SCORE_WEIGHT, VIDEO, PAIRS } = req.body;
|
|
|
|
|
const { IMAGE, AUDIO } = req.filesToSave || {};
|
|
|
|
|
|
|
|
|
|
const transaction = await models.db.transaction();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const exercise = await models.Exercise.findByPk(id, { transaction });
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
if (!exercise) {
|
2024-10-10 02:55:13 +00:00
|
|
|
clearFileBuffers({ IMAGE, AUDIO });
|
2024-09-13 13:03:35 +00:00
|
|
|
await transaction.rollback();
|
|
|
|
|
return response(404, null, "Exercise not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
const { QUESTION_TYPE } = exercise;
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
if (ID_LEVEL) {
|
|
|
|
|
const level = await models.Level.findByPk(ID_LEVEL, { transaction });
|
|
|
|
|
if (!level) {
|
|
|
|
|
clearFileBuffers({ IMAGE, AUDIO });
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return response(404, null, "Level not found", res);
|
|
|
|
|
}
|
|
|
|
|
exercise.ID_LEVEL = ID_LEVEL;
|
|
|
|
|
}
|
|
|
|
|
if (QUESTION) exercise.QUESTION = QUESTION;
|
|
|
|
|
if (SCORE_WEIGHT) exercise.SCORE_WEIGHT = SCORE_WEIGHT;
|
|
|
|
|
if (VIDEO) exercise.VIDEO = VIDEO;
|
|
|
|
|
|
|
|
|
|
if (AUDIO) {
|
|
|
|
|
if (exercise.AUDIO) {
|
|
|
|
|
const oldAudioPath = path.join(
|
2024-11-20 12:56:37 +00:00
|
|
|
"media/uploads/exercise/audio",
|
2024-10-10 02:55:13 +00:00
|
|
|
exercise.AUDIO
|
|
|
|
|
);
|
|
|
|
|
if (fs.existsSync(oldAudioPath)) {
|
|
|
|
|
fs.unlinkSync(oldAudioPath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
exercise.AUDIO = saveFileToDisk(
|
|
|
|
|
AUDIO,
|
|
|
|
|
"AUDIO",
|
|
|
|
|
ID_LEVEL || exercise.ID_LEVEL,
|
|
|
|
|
exercise.ID_ADMIN_EXERCISE
|
2024-09-13 13:03:35 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
if (IMAGE) {
|
|
|
|
|
if (exercise.IMAGE) {
|
|
|
|
|
const oldImagePath = path.join(
|
2024-11-20 12:56:37 +00:00
|
|
|
"media/uploads/exercise/image",
|
2024-10-10 02:55:13 +00:00
|
|
|
exercise.IMAGE
|
|
|
|
|
);
|
|
|
|
|
if (fs.existsSync(oldImagePath)) {
|
|
|
|
|
fs.unlinkSync(oldImagePath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
exercise.IMAGE = saveFileToDisk(
|
|
|
|
|
IMAGE,
|
|
|
|
|
"IMAGE",
|
|
|
|
|
ID_LEVEL || exercise.ID_LEVEL,
|
|
|
|
|
exercise.ID_ADMIN_EXERCISE
|
2024-09-13 13:03:35 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
await exercise.save({ transaction });
|
|
|
|
|
|
|
|
|
|
let payload = {
|
|
|
|
|
ID_ADMIN_EXERCISE: exercise.ID_ADMIN_EXERCISE,
|
|
|
|
|
ID_LEVEL: exercise.ID_LEVEL,
|
|
|
|
|
QUESTION: exercise.QUESTION,
|
|
|
|
|
SCORE_WEIGHT: exercise.SCORE_WEIGHT,
|
|
|
|
|
QUESTION_TYPE: exercise.QUESTION_TYPE,
|
|
|
|
|
AUDIO: exercise.AUDIO,
|
|
|
|
|
VIDEO: exercise.VIDEO,
|
|
|
|
|
IMAGE: exercise.IMAGE,
|
|
|
|
|
IS_DELETED: exercise.IS_DELETED,
|
|
|
|
|
TIME_ADMIN_EXC: exercise.TIME_ADMIN_EXC,
|
|
|
|
|
};
|
2024-09-13 13:03:35 +00:00
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
if (QUESTION_TYPE === "MPQ") {
|
|
|
|
|
if (!Array.isArray(PAIRS) || PAIRS.length === 0) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return response(
|
|
|
|
|
400,
|
|
|
|
|
null,
|
|
|
|
|
"At least one pair is required for Matching Pairs",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existingPairs = await models.MatchingPairs.findAll({
|
|
|
|
|
where: { ID_ADMIN_EXERCISE: exercise.ID_ADMIN_EXERCISE },
|
2024-09-13 13:03:35 +00:00
|
|
|
transaction,
|
|
|
|
|
});
|
2024-10-10 02:55:13 +00:00
|
|
|
|
|
|
|
|
const pairsToDelete = new Set(
|
|
|
|
|
existingPairs.map((pair) => pair.ID_MATCHING_PAIRS)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
for (const pair of PAIRS) {
|
|
|
|
|
if (pair.ID_MATCHING_PAIRS) {
|
|
|
|
|
const existingPair = existingPairs.find(
|
|
|
|
|
(p) => p.ID_MATCHING_PAIRS === pair.ID_MATCHING_PAIRS
|
|
|
|
|
);
|
|
|
|
|
if (existingPair) {
|
|
|
|
|
existingPair.LEFT_PAIR = pair.LEFT_PAIR;
|
|
|
|
|
existingPair.RIGHT_PAIR = pair.RIGHT_PAIR;
|
|
|
|
|
await existingPair.save({ transaction });
|
|
|
|
|
pairsToDelete.delete(existingPair.ID_MATCHING_PAIRS);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
await models.MatchingPairs.create(
|
|
|
|
|
{
|
|
|
|
|
ID_ADMIN_EXERCISE: exercise.ID_ADMIN_EXERCISE,
|
|
|
|
|
LEFT_PAIR: pair.LEFT_PAIR,
|
|
|
|
|
RIGHT_PAIR: pair.RIGHT_PAIR,
|
|
|
|
|
},
|
|
|
|
|
{ transaction }
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const pairId of pairsToDelete) {
|
|
|
|
|
await models.MatchingPairs.destroy({
|
|
|
|
|
where: { ID_MATCHING_PAIRS: pairId },
|
|
|
|
|
transaction,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatedPairs = await models.MatchingPairs.findAll({
|
|
|
|
|
where: { ID_ADMIN_EXERCISE: exercise.ID_ADMIN_EXERCISE },
|
|
|
|
|
});
|
|
|
|
|
payload.matchingPairs = updatedPairs;
|
|
|
|
|
} else if (QUESTION_TYPE === "MCQ") {
|
|
|
|
|
const { OPTION_A, OPTION_B, OPTION_C, OPTION_D, OPTION_E, ANSWER_KEY } =
|
|
|
|
|
req.body;
|
|
|
|
|
const multipleChoices = await models.MultipleChoices.findOne({
|
|
|
|
|
where: { ID_ADMIN_EXERCISE: exercise.ID_ADMIN_EXERCISE },
|
2024-09-13 13:03:35 +00:00
|
|
|
transaction,
|
|
|
|
|
});
|
2024-10-10 02:55:13 +00:00
|
|
|
|
|
|
|
|
if (!multipleChoices) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return response(404, null, "Multiple Choices not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (OPTION_A) multipleChoices.OPTION_A = OPTION_A;
|
|
|
|
|
if (OPTION_B) multipleChoices.OPTION_B = OPTION_B;
|
|
|
|
|
if (OPTION_C) multipleChoices.OPTION_C = OPTION_C;
|
|
|
|
|
if (OPTION_D) multipleChoices.OPTION_D = OPTION_D;
|
|
|
|
|
if (OPTION_E) multipleChoices.OPTION_E = OPTION_E;
|
|
|
|
|
if (ANSWER_KEY) multipleChoices.ANSWER_KEY = ANSWER_KEY;
|
|
|
|
|
|
|
|
|
|
await multipleChoices.save({ transaction });
|
|
|
|
|
payload.multipleChoices = multipleChoices;
|
|
|
|
|
} else if (QUESTION_TYPE === "TFQ") {
|
|
|
|
|
const { IS_TRUE } = req.body;
|
|
|
|
|
const trueFalse = await models.TrueFalse.findOne({
|
|
|
|
|
where: { ID_ADMIN_EXERCISE: exercise.ID_ADMIN_EXERCISE },
|
2024-09-13 13:03:35 +00:00
|
|
|
transaction,
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
if (!trueFalse) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return response(404, null, "True/False exercise not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof IS_TRUE !== "undefined") {
|
|
|
|
|
trueFalse.IS_TRUE = IS_TRUE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await trueFalse.save({ transaction });
|
|
|
|
|
payload.trueFalse = trueFalse;
|
|
|
|
|
}
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
await transaction.commit();
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
response(200, payload, "Exercise updated successfully", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
clearFileBuffers({ IMAGE, AUDIO });
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-16 06:58:47 +00:00
|
|
|
export const updateExerciseTitle = async (req, res) => {
|
|
|
|
|
const { ID_LEVEL, exercises } = req.body;
|
|
|
|
|
const transaction = await models.db.transaction();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const existingExercises = await models.Exercise.findAll({
|
|
|
|
|
where: {
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
ID_LEVEL,
|
|
|
|
|
},
|
2024-10-18 07:24:10 +00:00
|
|
|
order: [
|
|
|
|
|
[
|
|
|
|
|
models.Sequelize.literal(
|
|
|
|
|
"CAST(SUBSTRING_INDEX(TITLE, ' ', -1) AS UNSIGNED)"
|
|
|
|
|
),
|
|
|
|
|
"ASC",
|
|
|
|
|
],
|
|
|
|
|
],
|
2024-10-16 06:58:47 +00:00
|
|
|
transaction,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (exercises.length !== existingExercises.length) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return response(400, null, "Mismatch in exercise count", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sortedTitles = existingExercises.map((ex) => ex.TITLE);
|
|
|
|
|
const updatedExercises = [];
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < exercises.length; i++) {
|
|
|
|
|
const { ID_ADMIN_EXERCISE } = exercises[i];
|
|
|
|
|
const newTitle = sortedTitles[i];
|
|
|
|
|
|
|
|
|
|
const exerciseToUpdate = await models.Exercise.findByPk(
|
|
|
|
|
ID_ADMIN_EXERCISE,
|
|
|
|
|
{
|
|
|
|
|
transaction,
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!exerciseToUpdate) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return response(
|
|
|
|
|
404,
|
|
|
|
|
null,
|
|
|
|
|
`Exercise with ID ${ID_ADMIN_EXERCISE} not found`,
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (exerciseToUpdate.TITLE !== newTitle) {
|
|
|
|
|
await exerciseToUpdate.update({ TITLE: newTitle }, { transaction });
|
|
|
|
|
|
|
|
|
|
updatedExercises.push({
|
|
|
|
|
ID_ADMIN_EXERCISE: exerciseToUpdate.ID_ADMIN_EXERCISE,
|
|
|
|
|
TITLE: newTitle,
|
|
|
|
|
oldTitle: exercises[i].TITLE,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await transaction.commit();
|
|
|
|
|
|
|
|
|
|
response(
|
|
|
|
|
200,
|
|
|
|
|
{ updatedExercises },
|
|
|
|
|
"Exercises titles swapped successfully",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
export const deleteExerciseById = async (req, res) => {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const transaction = await models.db.transaction();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const exercise = await models.Exercise.findByPk(id);
|
|
|
|
|
|
|
|
|
|
if (!exercise) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return response(404, null, "Exercise not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await exercise.update({ IS_DELETED: 1 }, { transaction });
|
|
|
|
|
|
|
|
|
|
await transaction.commit();
|
|
|
|
|
response(200, null, "Exercise soft-deleted successfully", res);
|
2024-09-13 13:03:35 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const deleteExerciseFileById = async (req, res) => {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { fileType } = req.body;
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
if (!["audio", "image", "video"].includes(fileType)) {
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(400, null, "Invalid file type specified", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const exercise = await models.Exercise.findByPk(id);
|
|
|
|
|
|
|
|
|
|
if (!exercise) {
|
|
|
|
|
return response(404, null, "Exercise not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let filePath;
|
|
|
|
|
let fileName;
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
if (fileType === "audio" && exercise.AUDIO) {
|
2024-09-13 13:03:35 +00:00
|
|
|
fileName = exercise.AUDIO;
|
2024-11-20 12:56:37 +00:00
|
|
|
filePath = path.join("media/uploads/exercise/audio", fileName);
|
2024-09-13 13:03:35 +00:00
|
|
|
exercise.AUDIO = null;
|
|
|
|
|
} else if (fileType === "image" && exercise.IMAGE) {
|
|
|
|
|
fileName = exercise.IMAGE;
|
2024-11-20 12:56:37 +00:00
|
|
|
filePath = path.join("media/uploads/exercise/image", fileName);
|
2024-09-13 13:03:35 +00:00
|
|
|
exercise.IMAGE = null;
|
2024-09-19 10:04:18 +00:00
|
|
|
} else if (fileType === "video" && exercise.VIDEO) {
|
|
|
|
|
exercise.VIDEO = null;
|
2024-09-13 13:03:35 +00:00
|
|
|
} else {
|
|
|
|
|
return response(
|
|
|
|
|
404,
|
|
|
|
|
null,
|
|
|
|
|
`${
|
|
|
|
|
fileType.charAt(0).toUpperCase() + fileType.slice(1)
|
|
|
|
|
} file not found`,
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fs.existsSync(filePath)) {
|
|
|
|
|
fs.unlinkSync(filePath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await exercise.save();
|
|
|
|
|
|
|
|
|
|
response(
|
|
|
|
|
200,
|
|
|
|
|
exercise,
|
|
|
|
|
`${
|
|
|
|
|
fileType.charAt(0).toUpperCase() + fileType.slice(1)
|
|
|
|
|
} file deleted successfully`,
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|