const { Op } = require("sequelize"); const sequelize = require("../../config/db.config"); const R = require("../../utils/response.util"); const { paginate } = require("../../utils/paginate.util"); const Course = require("../../models/courses/courses.mdl"); const Unit = require("../../models/courses/units.mdl"); const Lesson = require("../../models/courses/lessons.mdl"); const LessonPage = require("../../models/courses/lesson-page.mdl"); const mdl_Users = require("../../models/users/users.mdl"); const notDeleted = { deletedAt: null }; // ─── Helpers ────────────────────────────────────────────────────────────────── const auditByFields = ["createdBy", "updatedBy", "deletedBy"]; const adminExclude = []; // ─── COURSE ─────────────────────────────────────────────────────────────────── exports.getCourses = async (req, res) => { try { const result = await paginate(Course, req, { excludeAttributes: adminExclude, auditOptions: { mdl_Users, parentAlias: "Course" }, context: "list", findOptions: { where: { ...notDeleted } }, }); return R.success(res, "Courses retrieved.", result); } catch (err) { console.error("[COURSE][GET ALL]", err); return R.error(res, "Could not retrieve courses.", 500); } }; exports.getCourse = async (req, res) => { try { const { courseId } = req.params; const course = await Course.findOne({ where: { course_id: courseId, ...notDeleted }, include: [ { model: Unit, as: "units", where: notDeleted, required: false, order_index: [["order", "ASC"]], }, ], order_index: [[{ model: Unit, as: "units" }, "order_index", "ASC"]], }); if (!course) return R.error(res, "Course not found.", 404); return R.success(res, "Course retrieved.", { data: course }); } catch (err) { console.error("[COURSE][GET ONE]", err); return R.error(res, "Could not retrieve course.", 500); } }; exports.createCourse = async (req, res) => { try { const { title, description, order_index } = req.body; if (!title) return R.error(res, "Title is required.", 400); const course = await Course.create({ title, description: description ?? null, order_index: order_index ?? 0, createdBy: req.body.createdBy ?? null, }); return R.success(res, "Course created.", { data: course }, 201); } catch (err) { console.error("[COURSE][CREATE]", err); return R.error(res, "Could not create course.", 500); } }; exports.updateCourse = async (req, res) => { try { const { courseId } = req.params; const course = await Course.findOne({ where: { course_id: courseId, ...notDeleted } }); if (!course) return R.error(res, "Course not found.", 404); const { title, description, order_index } = req.body; if (title !== undefined) course.title = title; if (description !== undefined) course.description = description; if (order_index !== undefined) course.order_index = order_index; course.updatedBy = req.body.updatedBy ?? null; await course.save(); return R.success(res, "Course updated.", { data: course }); } catch (err) { console.error("[COURSE][UPDATE]", err); return R.error(res, "Could not update course.", 500); } }; exports.deleteCourse = async (req, res) => { try { const { courseId } = req.params; const course = await Course.findOne({ where: { course_id: courseId, ...notDeleted } }); if (!course) return R.error(res, "Course not found.", 404); await course.update({ deletedBy: req.body.deletedBy ?? null }); await course.destroy(); return R.success(res, "Course deleted."); } catch (err) { console.error("[COURSE][DELETE]", err); return R.error(res, "Could not delete course.", 500); } }; // ─── UNIT ───────────────────────────────────────────────────────────────────── exports.getUnits = async (req, res) => { try { const { courseId } = req.params; const course = await Course.findOne({ where: { course_id: courseId, ...notDeleted } }); if (!course) return R.error(res, "Course not found.", 404); const result = await paginate(Unit, req, { excludeAttributes: adminExclude, auditOptions: { mdl_Users, parentAlias: "Unit" }, context: "list", findOptions: { where: { course_id: courseId, ...notDeleted }, order: [["order_index", "ASC"]], }, }); return R.success(res, "Units retrieved.", result); } catch (err) { console.error("[UNIT][GET ALL]", err); return R.error(res, "Could not retrieve units.", 500); } }; exports.getUnit = async (req, res) => { try { const { courseId, unitId } = req.params; const unit = await Unit.findOne({ where: { unit_id: unitId, course_id: courseId, ...notDeleted }, include: [ { model: Lesson, as: "lessons", where: notDeleted, required: false, order: [["order_index", "ASC"]], }, ], order: [[{ model: Lesson, as: "lessons" }, "order_index", "ASC"]], }); if (!unit) return R.error(res, "Unit not found.", 404); return R.success(res, "Unit retrieved.", { data: unit }); } catch (err) { console.error("[UNIT][GET ONE]", err); return R.error(res, "Could not retrieve unit.", 500); } }; exports.createUnit = async (req, res) => { try { const { courseId } = req.params; const { title, description, order_index } = req.body; if (!title) return R.error(res, "Title is required.", 400); const course = await Course.findOne({ where: { course_id: courseId, ...notDeleted } }); if (!course) return R.error(res, "Course not found.", 404); const unit = await Unit.create({ course_id: courseId, title, description: description ?? null, order_index: order_index ?? 0, createdBy: req.body.createdBy ?? null, }); return R.success(res, "Unit created.", { data: unit }, 201); } catch (err) { console.error("[UNIT][CREATE]", err); return R.error(res, "Could not create unit.", 500); } }; exports.updateUnit = async (req, res) => { try { const { courseId, unitId } = req.params; const unit = await Unit.findOne({ where: { unit_id: unitId, course_id: courseId, ...notDeleted }, }); if (!unit) return R.error(res, "Unit not found.", 404); const { title, description, order_index } = req.body; if (title !== undefined) unit.title = title; if (description !== undefined) unit.description = description; if (order !== undefined) unit.order = order; unit.updatedBy = req.body.updatedBy ?? null; await unit.save(); return R.success(res, "Unit updated.", { data: unit }); } catch (err) { console.error("[UNIT][UPDATE]", err); return R.error(res, "Could not update unit.", 500); } }; exports.deleteUnit = async (req, res) => { try { const { courseId, unitId } = req.params; const unit = await Unit.findOne({ where: { unit_id: unitId, course_id: courseId, ...notDeleted }, }); if (!unit) return R.error(res, "Unit not found.", 404); await unit.update({ deletedBy: req.body.deletedBy ?? null }); await unit.destroy(); return R.success(res, "Unit deleted."); } catch (err) { console.error("[UNIT][DELETE]", err); return R.error(res, "Could not delete unit.", 500); } }; // ─── LESSON ─────────────────────────────────────────────────────────────────── exports.getLessons = async (req, res) => { try { const { courseId, unitId } = req.params; const unit = await Unit.findOne({ where: { unit_id: unitId, course_id: courseId, ...notDeleted }, }); if (!unit) return R.error(res, "Unit not found.", 404); const result = await paginate(Lesson, req, { excludeAttributes: adminExclude, auditOptions: { mdl_Users, parentAlias: "Lesson" }, context: "list", findOptions: { where: { unit_id: unitId, ...notDeleted }, order: [["order_index", "ASC"]], }, }); return R.success(res, "Lessons retrieved.", result); } catch (err) { console.error("[LESSON][GET ALL]", err); return R.error(res, "Could not retrieve lessons.", 500); } }; exports.getLesson = async (req, res) => { try { const { courseId, unitId, lessonId } = req.params; const lesson = await Lesson.findOne({ where: { lesson_id: lessonId, unit_id: unitId, ...notDeleted }, include: [ { model: LessonPage, as: "page", required: false, }, { model: Unit, as: "unit", where: { course_id: courseId, ...notDeleted }, }, ], }); if (!lesson) return R.error(res, "Lesson not found.", 404); return R.success(res, "Lesson retrieved.", { data: lesson }); } catch (err) { console.error("[LESSON][GET ONE]", err); return R.error(res, "Could not retrieve lesson.", 500); } }; exports.createLesson = async (req, res) => { try { const { courseId, unitId } = req.params; const { title, description, order_index } = req.body; if (!title) return R.error(res, "Title is required.", 400); const unit = await Unit.findOne({ where: { unit_id: unitId, course_id: courseId, ...notDeleted }, }); if (!unit) return R.error(res, "Unit not found.", 404); const lesson = await Lesson.create({ unit_id: unitId, title, description: description ?? null, order_index: order_index ?? 0, createdBy: req.body.createdBy ?? null, }); // ── Auto-create an empty page for this lesson ───────────────────────────── await LessonPage.create({ lesson_id: lesson.lesson_id, blocks: [], createdBy: req.body.createdBy ?? null, }); return R.success(res, "Lesson created.", { data: lesson }, 201); } catch (err) { console.error("[LESSON][CREATE]", err); return R.error(res, "Could not create lesson.", 500); } }; exports.updateLesson = async (req, res) => { try { const { courseId, unitId, lessonId } = req.params; const lesson = await Lesson.findOne({ where: { lesson_id: lessonId, unit_id: unitId, ...notDeleted, }, include: [{ model: Unit, as: "unit", where: { course_id: courseId, ...notDeleted }, }], }); if (!lesson) return R.error(res, "Lesson not found.", 404); const { title, description, order_index } = req.body; if (title !== undefined) lesson.title = title; if (description !== undefined) lesson.description = description; if (order_index !== undefined) lesson.order_index = order_index; lesson.updatedBy = req.body.updatedBy ?? null; await lesson.save(); return R.success(res, "Lesson updated.", { data: lesson }); } catch (err) { console.error("[LESSON][UPDATE]", err); return R.error(res, "Could not update lesson.", 500); } }; exports.deleteLesson = async (req, res) => { try { const { courseId, unitId, lessonId } = req.params; const lesson = await Lesson.findOne({ where: { lesson_id: lessonId, unit_id: unitId, ...notDeleted }, include: [{ model: Unit, as: "unit", where: { course_id: courseId, ...notDeleted }, }], }); if (!lesson) return R.error(res, "Lesson not found.", 404); await lesson.update({ deletedBy: req.body.deletedBy ?? null }); await lesson.destroy(); return R.success(res, "Lesson deleted."); } catch (err) { console.error("[LESSON][DELETE]", err); return R.error(res, "Could not delete lesson.", 500); } }; // ─── LESSON PAGE ────────────────────────────────────────────────────────────── exports.getLessonPage = async (req, res) => { try { const { lessonId } = req.params; const page = await LessonPage.findOne({ where: { lesson_id: lessonId }, }); if (!page) return R.error(res, "Lesson page not found.", 404); return R.success(res, "Lesson page retrieved.", { data: page }); } catch (err) { console.error("[LESSON PAGE][GET]", err); return R.error(res, "Could not retrieve lesson page.", 500); } }; exports.upsertLessonPage = async (req, res) => { try { const { lessonId } = req.params; const { blocks } = req.body; if (!Array.isArray(blocks)) return R.error(res, "blocks must be an array.", 400); const lesson = await Lesson.findOne({ where: { lesson_id: lessonId, ...notDeleted } }); if (!lesson) return R.error(res, "Lesson not found.", 404); const [page, created] = await LessonPage.upsert({ lesson_id: lessonId, blocks, updatedBy: req.body.updatedBy ?? null, createdBy: req.body.updatedBy ?? null, }, { returning: true, }); return R.success( res, created ? "Lesson page created." : "Lesson page updated.", { data: page }, created ? 201 : 200, ); } catch (err) { console.error("[LESSON PAGE][UPSERT]", err); return R.error(res, "Could not save lesson page.", 500); } };