2024-09-13 13:03:35 +00:00
|
|
|
import response from "../../response.js";
|
|
|
|
|
import models from "../../models/index.js";
|
2024-10-16 03:24:41 +00:00
|
|
|
import { autoGenerateLevel } from "../../middlewares/Level/autoGenerateLevel.js";
|
2024-08-12 02:44:06 +00:00
|
|
|
|
|
|
|
|
export const getTopics = async (req, res) => {
|
|
|
|
|
try {
|
2024-10-10 02:55:13 +00:00
|
|
|
const topics = await models.Topic.findAll({
|
|
|
|
|
where: {
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
},
|
|
|
|
|
});
|
2024-08-12 02:44:06 +00:00
|
|
|
response(200, topics, "Success", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Error retrieving topics data!", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const getTopicById = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const topic = await models.Topic.findByPk(id);
|
|
|
|
|
|
|
|
|
|
if (!topic) {
|
|
|
|
|
return response(404, null, "Topic not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
response(200, topic, "Success", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
2024-09-13 13:03:35 +00:00
|
|
|
response(500, null, "Internal Server Error", res);
|
2024-08-12 02:44:06 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-19 10:04:18 +00:00
|
|
|
export const getTopicBySectionId = async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { sectionId } = req.params;
|
2024-10-10 02:55:13 +00:00
|
|
|
const userId = req.user.ID;
|
2024-09-19 10:04:18 +00:00
|
|
|
|
|
|
|
|
const sectionExists = await models.Section.findByPk(sectionId);
|
2024-10-03 03:32:34 +00:00
|
|
|
if (!sectionExists) {
|
|
|
|
|
return response(404, null, "Section not found", res);
|
|
|
|
|
}
|
2024-09-19 10:04:18 +00:00
|
|
|
|
|
|
|
|
const topics = await models.Topic.findAll({
|
2024-10-10 02:55:13 +00:00
|
|
|
where: { ID_SECTION: sectionId, IS_DELETED: 0 },
|
|
|
|
|
attributes: ["ID_TOPIC", "NAME_TOPIC", "DESCRIPTION_TOPIC"],
|
2024-09-19 10:04:18 +00:00
|
|
|
});
|
|
|
|
|
|
2024-10-03 03:32:34 +00:00
|
|
|
if (!topics || topics.length === 0) {
|
|
|
|
|
return response(404, null, "No topics found for this section", res);
|
|
|
|
|
}
|
2024-09-19 10:04:18 +00:00
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
const topicsWithCompletionStatus = await Promise.all(
|
|
|
|
|
topics.map(async (topic) => {
|
|
|
|
|
const level6 = await models.Level.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
NAME_LEVEL: "Level 6",
|
|
|
|
|
ID_TOPIC: topic.ID_TOPIC,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let isCompleted = 0;
|
|
|
|
|
if (level6) {
|
|
|
|
|
const stdLearning = await models.StdLearning.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
ID: userId,
|
|
|
|
|
ID_LEVEL: level6.ID_LEVEL,
|
|
|
|
|
IS_PASS: 1,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
isCompleted = stdLearning ? 1 : 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...topic.get({ plain: true }),
|
|
|
|
|
IS_COMPLETED: isCompleted,
|
|
|
|
|
};
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
response(200, topicsWithCompletionStatus, "Success", res);
|
2024-09-19 10:04:18 +00:00
|
|
|
} catch (error) {
|
2024-10-10 02:55:13 +00:00
|
|
|
console.error(error);
|
2024-09-19 10:04:18 +00:00
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
export const getTopicForAdmin = async (req, res) => {
|
|
|
|
|
const { page = 1, limit = 10, search = "", sort = "time" } = req.query;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const { count, rows: topics } = await models.Topic.findAndCountAll({
|
|
|
|
|
where: {
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
...(search && {
|
|
|
|
|
[models.Op.or]: [
|
|
|
|
|
{
|
|
|
|
|
"$topicSection.NAME_SECTION$": {
|
|
|
|
|
[models.Op.like]: `%${search}%`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
NAME_TOPIC: {
|
|
|
|
|
[models.Op.like]: `%${search}%`,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
attributes: ["ID_TOPIC", "NAME_TOPIC", "TIME_TOPIC"],
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Section,
|
|
|
|
|
as: "topicSection",
|
|
|
|
|
attributes: ["ID_SECTION", "NAME_SECTION"],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
distinct: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const formattedTopics = topics.map((topic) => ({
|
|
|
|
|
ID_SECTION: topic.topicSection.ID_SECTION,
|
|
|
|
|
ID_TOPIC: topic.ID_TOPIC,
|
|
|
|
|
NAME_SECTION: topic.topicSection.NAME_SECTION,
|
|
|
|
|
NAME_TOPIC: topic.NAME_TOPIC,
|
|
|
|
|
TIME_TOPIC: topic.TIME_TOPIC,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
if (sort === "section") {
|
|
|
|
|
formattedTopics.sort((a, b) =>
|
|
|
|
|
a.NAME_SECTION.localeCompare(b.NAME_SECTION)
|
|
|
|
|
);
|
|
|
|
|
} else if (sort === "topic") {
|
|
|
|
|
formattedTopics.sort((a, b) => a.NAME_TOPIC.localeCompare(b.NAME_TOPIC));
|
|
|
|
|
} else {
|
|
|
|
|
formattedTopics.sort(
|
|
|
|
|
(a, b) => new Date(b.TIME_TOPIC) - new Date(a.TIME_TOPIC)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const paginatedTopics = formattedTopics.slice(
|
|
|
|
|
(page - 1) * limit,
|
|
|
|
|
page * limit
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const totalPages = Math.ceil(count / limit);
|
|
|
|
|
const currentPage = parseInt(page);
|
|
|
|
|
|
|
|
|
|
response(
|
|
|
|
|
200,
|
|
|
|
|
{
|
|
|
|
|
topics: paginatedTopics,
|
|
|
|
|
currentPage,
|
|
|
|
|
totalPages,
|
|
|
|
|
totalItems: count,
|
|
|
|
|
},
|
|
|
|
|
"Topics retrieved successfully",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Error retrieving topics data!", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2024-08-12 02:44:06 +00:00
|
|
|
export const createTopic = async (req, res) => {
|
2024-09-30 01:06:15 +00:00
|
|
|
const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC } = req.body;
|
2024-09-13 13:03:35 +00:00
|
|
|
|
|
|
|
|
if (!ID_SECTION) {
|
|
|
|
|
return response(400, null, "Section ID is required", res);
|
|
|
|
|
}
|
2024-08-12 02:44:06 +00:00
|
|
|
|
2024-09-13 13:03:35 +00:00
|
|
|
if (!NAME_TOPIC) {
|
|
|
|
|
return response(400, null, "Topic name is required", res);
|
2024-08-12 02:44:06 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-13 13:03:35 +00:00
|
|
|
if (!DESCRIPTION_TOPIC) {
|
2024-09-19 10:04:18 +00:00
|
|
|
return response(400, null, "Topic description is required", res);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 06:58:47 +00:00
|
|
|
const transaction = await models.db.transaction();
|
2024-10-16 03:24:41 +00:00
|
|
|
|
2024-08-12 02:44:06 +00:00
|
|
|
try {
|
2024-09-13 13:03:35 +00:00
|
|
|
const section = await models.Section.findByPk(ID_SECTION);
|
|
|
|
|
if (!section) {
|
2024-10-16 03:24:41 +00:00
|
|
|
await transaction.rollback();
|
2024-09-13 13:03:35 +00:00
|
|
|
return response(404, null, "Section not found", res);
|
2024-08-12 02:44:06 +00:00
|
|
|
}
|
|
|
|
|
|
2024-10-16 06:58:47 +00:00
|
|
|
const existingTopic = await models.Topic.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
ID_SECTION,
|
|
|
|
|
NAME_TOPIC,
|
|
|
|
|
IS_DELETED: 0,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (existingTopic) {
|
|
|
|
|
await transaction.rollback();
|
|
|
|
|
return response(
|
|
|
|
|
409,
|
|
|
|
|
null,
|
|
|
|
|
"Topic with the same name already exists",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 03:24:41 +00:00
|
|
|
const newTopic = await models.Topic.create(
|
|
|
|
|
{
|
|
|
|
|
ID_SECTION,
|
|
|
|
|
NAME_TOPIC,
|
|
|
|
|
DESCRIPTION_TOPIC,
|
|
|
|
|
},
|
|
|
|
|
{ transaction }
|
|
|
|
|
);
|
2024-08-12 02:44:06 +00:00
|
|
|
|
2024-10-16 03:24:41 +00:00
|
|
|
req.body.ID_TOPIC = newTopic.ID_TOPIC;
|
|
|
|
|
|
|
|
|
|
await autoGenerateLevel(req, res, async () => {
|
|
|
|
|
const levels = res.locals.createdLevels || [];
|
|
|
|
|
|
|
|
|
|
const payload = {
|
|
|
|
|
topic: newTopic,
|
|
|
|
|
levels,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await transaction.commit();
|
|
|
|
|
response(
|
|
|
|
|
201,
|
|
|
|
|
payload,
|
|
|
|
|
"Topic and related Level created successfully",
|
|
|
|
|
res
|
|
|
|
|
);
|
|
|
|
|
});
|
2024-08-12 02:44:06 +00:00
|
|
|
} catch (error) {
|
2024-10-16 03:24:41 +00:00
|
|
|
await transaction.rollback();
|
2024-08-12 02:44:06 +00:00
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const updateTopicById = async (req, res) => {
|
|
|
|
|
const { id } = req.params;
|
2024-09-30 01:06:15 +00:00
|
|
|
const { ID_SECTION, NAME_TOPIC, DESCRIPTION_TOPIC } = req.body;
|
2024-08-12 02:44:06 +00:00
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const topic = await models.Topic.findByPk(id);
|
|
|
|
|
|
|
|
|
|
if (!topic) {
|
|
|
|
|
return response(404, null, "Topic not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-13 13:03:35 +00:00
|
|
|
if (ID_SECTION) {
|
|
|
|
|
const section = await models.Section.findByPk(ID_SECTION);
|
|
|
|
|
if (!section) {
|
|
|
|
|
return response(404, null, "Section not found", res);
|
2024-08-12 02:44:06 +00:00
|
|
|
}
|
2024-09-13 13:03:35 +00:00
|
|
|
topic.ID_SECTION = ID_SECTION;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (NAME_TOPIC) {
|
|
|
|
|
topic.NAME_TOPIC = NAME_TOPIC;
|
2024-08-12 02:44:06 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-13 13:03:35 +00:00
|
|
|
if (DESCRIPTION_TOPIC) {
|
|
|
|
|
topic.DESCRIPTION_TOPIC = DESCRIPTION_TOPIC;
|
2024-08-12 02:44:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await topic.save();
|
|
|
|
|
|
|
|
|
|
response(200, topic, "Topic updated successfully", res);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const deleteTopicById = async (req, res) => {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const topic = await models.Topic.findByPk(id);
|
|
|
|
|
|
|
|
|
|
if (!topic) {
|
|
|
|
|
return response(404, null, "Topic not found", res);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
topic.IS_DELETED = 1;
|
|
|
|
|
await topic.save();
|
|
|
|
|
|
|
|
|
|
await models.Level.update({ IS_DELETED: 1 }, { where: { ID_TOPIC: id } });
|
|
|
|
|
|
|
|
|
|
await models.Exercise.update(
|
|
|
|
|
{ IS_DELETED: 1 },
|
|
|
|
|
{
|
|
|
|
|
where: {
|
|
|
|
|
ID_LEVEL: {
|
|
|
|
|
[models.Op.in]: models.Sequelize.literal(
|
|
|
|
|
`(SELECT ID_LEVEL FROM level WHERE ID_TOPIC = '${id}')`
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
);
|
2024-08-12 02:44:06 +00:00
|
|
|
|
2024-10-10 02:55:13 +00:00
|
|
|
response(
|
|
|
|
|
200,
|
|
|
|
|
null,
|
|
|
|
|
"Topic, levels, and related exercises soft deleted successfully",
|
|
|
|
|
res
|
|
|
|
|
);
|
2024-08-12 02:44:06 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
console.log(error);
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-09-19 10:04:18 +00:00
|
|
|
|
2024-09-30 02:18:18 +00:00
|
|
|
export const getCompletedTopicsBySection = async (req, res) => {
|
2024-09-19 10:04:18 +00:00
|
|
|
try {
|
|
|
|
|
const user = req.user;
|
|
|
|
|
const userId = user.ID;
|
|
|
|
|
|
2024-10-16 04:13:24 +00:00
|
|
|
const userLearnings = await models.StdLearning.findAll({
|
|
|
|
|
where: { ID: userId },
|
2024-09-19 10:04:18 +00:00
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Level,
|
|
|
|
|
as: "level",
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Topic,
|
|
|
|
|
as: "levelTopic",
|
2024-09-30 02:18:18 +00:00
|
|
|
attributes: ["ID_TOPIC", "NAME_TOPIC", "DESCRIPTION_TOPIC"],
|
|
|
|
|
include: [
|
|
|
|
|
{
|
|
|
|
|
model: models.Section,
|
|
|
|
|
as: "topicSection",
|
|
|
|
|
attributes: [
|
|
|
|
|
"ID_SECTION",
|
|
|
|
|
"NAME_SECTION",
|
|
|
|
|
"DESCRIPTION_SECTION",
|
2024-10-02 07:04:17 +00:00
|
|
|
"THUMBNAIL",
|
2024-09-30 02:18:18 +00:00
|
|
|
],
|
|
|
|
|
},
|
2024-09-19 10:04:18 +00:00
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-16 04:13:24 +00:00
|
|
|
if (!userLearnings.length) {
|
|
|
|
|
return response(200, null, "No topics found for this user", res);
|
2024-10-10 02:55:13 +00:00
|
|
|
}
|
2024-09-19 10:04:18 +00:00
|
|
|
|
2024-09-30 02:18:18 +00:00
|
|
|
const completedSections = {};
|
|
|
|
|
|
2024-10-16 04:13:24 +00:00
|
|
|
for (const learning of userLearnings) {
|
|
|
|
|
const { level } = learning;
|
|
|
|
|
const { levelTopic: topic } = level;
|
|
|
|
|
const { topicSection: section } = topic;
|
|
|
|
|
|
|
|
|
|
const totalTopicsInSection = await models.Topic.count({
|
|
|
|
|
where: { ID_SECTION: section.ID_SECTION },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!completedSections[section.ID_SECTION]) {
|
|
|
|
|
completedSections[section.ID_SECTION] = {
|
|
|
|
|
ID_SECTION: section.ID_SECTION,
|
|
|
|
|
NAME_SECTION: section.NAME_SECTION,
|
|
|
|
|
DESCRIPTION_SECTION: section.DESCRIPTION_SECTION,
|
|
|
|
|
THUMBNAIL: section.THUMBNAIL,
|
|
|
|
|
TOTAL_TOPICS: totalTopicsInSection,
|
2024-10-16 06:58:47 +00:00
|
|
|
COMPLETED_TOPICS: 0,
|
2024-10-16 04:13:24 +00:00
|
|
|
};
|
|
|
|
|
}
|
2024-09-19 10:04:18 +00:00
|
|
|
|
|
|
|
|
const level6 = await models.Level.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
NAME_LEVEL: "Level 6",
|
2024-09-30 02:18:18 +00:00
|
|
|
ID_TOPIC: topic.ID_TOPIC,
|
2024-09-19 10:04:18 +00:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-16 06:58:47 +00:00
|
|
|
if (
|
|
|
|
|
level6 &&
|
|
|
|
|
level.ID_LEVEL === level6.ID_LEVEL &&
|
|
|
|
|
learning.IS_PASS === 1
|
|
|
|
|
) {
|
2024-09-30 02:18:18 +00:00
|
|
|
completedSections[section.ID_SECTION].COMPLETED_TOPICS++;
|
2024-09-19 10:04:18 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 04:13:24 +00:00
|
|
|
const result = Object.values(completedSections).map((section) => {
|
2024-10-16 06:58:47 +00:00
|
|
|
section.COMPLETED_TOPICS = section.COMPLETED_TOPICS || 0;
|
2024-10-16 04:13:24 +00:00
|
|
|
return section;
|
|
|
|
|
});
|
2024-10-02 06:20:02 +00:00
|
|
|
|
2024-10-16 06:58:47 +00:00
|
|
|
response(
|
|
|
|
|
200,
|
|
|
|
|
result,
|
|
|
|
|
"Completed topics by section fetched successfully",
|
|
|
|
|
res
|
|
|
|
|
);
|
2024-09-19 10:04:18 +00:00
|
|
|
} catch (error) {
|
|
|
|
|
console.error(error);
|
|
|
|
|
response(500, null, "Internal Server Error", res);
|
|
|
|
|
}
|
|
|
|
|
};
|