course,tasklist,task and completed validation

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-17 13:04:27 +08:00
parent 612805acaf
commit d49e3be4d2
26 changed files with 1174 additions and 69 deletions
+45 -4
View File
@@ -6,6 +6,7 @@ const R = require("../../utils/response.util");
const { paginate } = require("../../utils/paginate.util");
const { recomputeDurations, recomputeUnitDuration, recomputeCourseDuration, formatDuration } = require("../../utils/duration.util");
const { syncObjectivesCreate, syncObjectivesUpdate } = require("../../utils/courses/objectives.util");
const { resolvePrerequisiteTitles } = require("../../utils/courses/resolvePrerequisiteTitles.util");
const { syncJunction } = require("../../utils/courses/junction.util");
const { archiveOne, archiveMany } = require("../../utils/courses/archive.util");
const { restoreOne, restoreMany } = require("../../utils/courses/restore.util");
@@ -23,7 +24,7 @@ const {
Unit, Lesson, LessonPage,
CourseUnit, UnitLesson,
CourseObjective, LessonObjective,
CoursePrerequisite, CourseAssessment,
CoursePrerequisite, CourseRole, CourseAssessment,
UnitQuiz, QuizQuestion, QuizOption,
QuizAttempt, AssessmentSession,
CourseInstructor, CourseAchievement,
@@ -102,10 +103,12 @@ exports.getCourse = async (req, res) => {
},
{ model: CourseObjective, as: "objectives", required: false },
{ model: CoursePrerequisite, as: "prerequisites", required: false },
{ model: CourseRole, as: "roles", required: false },
{ model: CourseAssessment, as: "assessment", required: false },
],
order: [
[{ model: CourseObjective, as: "objectives" }, "order_index", "ASC"],
[{ model: CourseRole, as: "roles" }, "order_index", "ASC"],
],
});
@@ -113,6 +116,7 @@ exports.getCourse = async (req, res) => {
const plain = course.toJSON();
plain.units = flattenUnits(plain.units); // junction order_index → flat field, sorted
plain.prerequisites = await resolvePrerequisiteTitles(plain.prerequisites, { Course, Unit, Lesson });
return R.success(res, "Course retrieved.", { data: plain });
} catch (err) {
console.error("[COURSE][GET ONE]", err);
@@ -127,6 +131,7 @@ exports.createCourse = async (req, res) => {
title, description, order_index,
course_code, level, subscription, status,
objectives = [],
roles = [],
category_ids = [],
achievement_keys = [],
badge_color, badge_asset_id, badge_image_url,
@@ -151,6 +156,7 @@ exports.createCourse = async (req, res) => {
}, { transaction: t });
await syncObjectivesCreate(CourseObjective, "course_id", course.course_id, objectives, t);
await syncObjectivesCreate(CourseRole, "course_id", course.course_id, roles, t);
await syncJunction(CourseProductCat, course.course_id, category_ids, "category_id", t);
if (achievement_keys.length) {
@@ -196,6 +202,8 @@ exports.createCourseFull = async (req, res) => {
title, description, order_index,
course_code, level, subscription, status,
objectives = [],
roles = [],
prerequisites = [],
category_ids = [],
achievement_keys = [],
badge_color, badge_asset_id, badge_image_url,
@@ -218,6 +226,18 @@ exports.createCourseFull = async (req, res) => {
}
}
const validRefTypes = ["course", "unit", "lesson"];
for (const p of prerequisites) {
if (!validRefTypes.includes(p.ref_type)) {
await t.rollback();
return R.error(res, `Invalid ref_type: ${p.ref_type}`, 400);
}
if (p.ref_id === undefined || p.ref_id === null || p.ref_id === "") {
await t.rollback();
return R.error(res, "Each prerequisite needs an item selected.", 400);
}
}
const course = await Course.create({
title,
description: description ?? null,
@@ -234,8 +254,21 @@ exports.createCourseFull = async (req, res) => {
}, { transaction: t });
await syncObjectivesCreate(CourseObjective, "course_id", course.course_id, objectives, t);
await syncObjectivesCreate(CourseRole, "course_id", course.course_id, roles, t);
await syncJunction(CourseProductCat, course.course_id, category_ids, "category_id", t);
if (prerequisites.length) {
await CoursePrerequisite.bulkCreate(
prerequisites.map((p, i) => ({
course_id: course.course_id,
ref_type: p.ref_type,
ref_id: p.ref_id,
order_index: i,
})),
{ transaction: t },
);
}
if (achievement_keys.length) {
await CourseAchievement.bulkCreate(
achievement_keys.slice(0, 1).map((key, i) => ({ course_id: course.course_id, achievement_key: key, order_index: i })),
@@ -344,7 +377,7 @@ exports.updateCourse = async (req, res) => {
const {
title, description, order_index,
course_code, level, subscription, status,
objectives, category_ids,
objectives, roles, category_ids,
badge_color, badge_asset_id, badge_image_url,
updatedBy,
} = req.body;
@@ -363,6 +396,7 @@ exports.updateCourse = async (req, res) => {
await course.save({ transaction: t });
if (objectives !== undefined) await syncObjectivesUpdate(CourseObjective, "course_id", courseId, objectives, t);
if (roles !== undefined) await syncObjectivesUpdate(CourseRole, "course_id", courseId, roles, t, "role_id");
if (category_ids !== undefined) await syncJunction(CourseProductCat, courseId, category_ids, "category_id", t);
await t.commit();
@@ -562,7 +596,8 @@ exports.getPrerequisites = async (req, res) => {
where: { course_id: courseId },
order: [["order_index", "ASC"]],
});
return R.success(res, "Prerequisites retrieved.", { data: prereqs });
const data = await resolvePrerequisiteTitles(prereqs.map((p) => p.toJSON()), { Course, Unit, Lesson });
return R.success(res, "Prerequisites retrieved.", { data });
} catch (err) {
console.error("[PREREQ][GET ALL]", err);
return R.error(res, "Could not retrieve prerequisites.", 500);
@@ -587,6 +622,10 @@ exports.syncPrerequisites = async (req, res) => {
await t.rollback();
return R.error(res, `Invalid ref_type: ${p.ref_type}`, 400);
}
if (p.ref_id === undefined || p.ref_id === null || p.ref_id === "") {
await t.rollback();
return R.error(res, "Each prerequisite needs an item selected.", 400);
}
}
await CoursePrerequisite.bulkCreate(
prerequisites.map((p, i) => ({
@@ -2265,7 +2304,7 @@ exports.getCoursesFlat = async (req, res) => {
try {
const data = await Course.findAll({
where: notDeleted,
attributes: ["uuid", "title", "subscription", "duration_seconds"],
attributes: ["course_id", "uuid", "title", "subscription", "duration_seconds"],
order: [["title", "ASC"]],
});
return R.success(res, "Courses retrieved.", data);
@@ -2343,6 +2382,7 @@ exports.getUnitsFlat = async (req, res) => {
}
const data = rows.map((r) => ({
unit_id: r.unit_id,
uuid: r.uuid,
title: r.title,
duration_seconds: Number(r.duration_seconds ?? 0),
@@ -2389,6 +2429,7 @@ exports.getLessonsFlat = async (req, res) => {
}
const data = rows.map((r) => ({
lesson_id: r.lesson_id,
uuid: r.uuid,
title: r.title,
duration_seconds: Number(r.duration_seconds ?? 0),