mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
duration fix when data is deleted for courses
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -4,7 +4,7 @@ const { Op, Sequelize } = require("sequelize");
|
|||||||
const sequelize = require("../../config/db.config");
|
const sequelize = require("../../config/db.config");
|
||||||
const R = require("../../utils/response.util");
|
const R = require("../../utils/response.util");
|
||||||
const { paginate } = require("../../utils/paginate.util");
|
const { paginate } = require("../../utils/paginate.util");
|
||||||
const { recomputeDurations, formatDuration } = require("../../utils/duration.util");
|
const { recomputeDurations, recomputeUnitDuration, recomputeCourseDuration, formatDuration } = require("../../utils/duration.util");
|
||||||
const { syncObjectivesCreate, syncObjectivesUpdate } = require("../../utils/courses/objectives.util");
|
const { syncObjectivesCreate, syncObjectivesUpdate } = require("../../utils/courses/objectives.util");
|
||||||
const { syncJunction } = require("../../utils/courses/junction.util");
|
const { syncJunction } = require("../../utils/courses/junction.util");
|
||||||
const { archiveOne, archiveMany } = require("../../utils/courses/archive.util");
|
const { archiveOne, archiveMany } = require("../../utils/courses/archive.util");
|
||||||
@@ -545,6 +545,7 @@ exports.archiveUnit = async (req, res) => {
|
|||||||
const record = await archiveOne(Unit, { unit_id: unitId, course_id: courseId, ...notDeleted }, req.user.user_id, t);
|
const record = await archiveOne(Unit, { unit_id: unitId, course_id: courseId, ...notDeleted }, req.user.user_id, t);
|
||||||
if (!record) return R.error(res, "Unit not found.", 404);
|
if (!record) return R.error(res, "Unit not found.", 404);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try { await recomputeCourseDuration(courseId); } catch (durErr) { console.error("[UNIT][ARCHIVE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'archive_unit', { entityType: 'unit', entityId: Number(unitId) });
|
logActivity(req.user.user_id, 'archive_unit', { entityType: 'unit', entityId: Number(unitId) });
|
||||||
return R.success(res, "Unit archived.");
|
return R.success(res, "Unit archived.");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -566,6 +567,7 @@ exports.bulkArchiveUnits = async (req, res) => {
|
|||||||
|
|
||||||
const count = await archiveMany(Unit, "unit_id", validIds, req.user.user_id, t);
|
const count = await archiveMany(Unit, "unit_id", validIds, req.user.user_id, t);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try { await recomputeCourseDuration(courseId); } catch (durErr) { console.error("[UNIT][BULK ARCHIVE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'bulk_archive_units', { entityType: 'unit', details: { ids: validIds, count } });
|
logActivity(req.user.user_id, 'bulk_archive_units', { entityType: 'unit', details: { ids: validIds, count } });
|
||||||
return R.success(res, `${count} unit${count !== 1 ? "s" : ""} archived.`);
|
return R.success(res, `${count} unit${count !== 1 ? "s" : ""} archived.`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -622,6 +624,7 @@ exports.restoreUnit = async (req, res) => {
|
|||||||
const record = await restoreOne(Unit, { unit_id: unitId, course_id: courseId, ...onlyDeleted }, req.user.user_id, t);
|
const record = await restoreOne(Unit, { unit_id: unitId, course_id: courseId, ...onlyDeleted }, req.user.user_id, t);
|
||||||
if (!record) return R.error(res, "Archived unit not found.", 404);
|
if (!record) return R.error(res, "Archived unit not found.", 404);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try { await recomputeCourseDuration(courseId); } catch (durErr) { console.error("[UNIT][RESTORE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'restore_unit', { entityType: 'unit', entityId: Number(unitId) });
|
logActivity(req.user.user_id, 'restore_unit', { entityType: 'unit', entityId: Number(unitId) });
|
||||||
return R.success(res, "Unit restored.", { data: record });
|
return R.success(res, "Unit restored.", { data: record });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -643,6 +646,7 @@ exports.bulkRestoreUnits = async (req, res) => {
|
|||||||
|
|
||||||
const count = await restoreMany(Unit, "unit_id", validIds, req.user.user_id, t);
|
const count = await restoreMany(Unit, "unit_id", validIds, req.user.user_id, t);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try { await recomputeCourseDuration(courseId); } catch (durErr) { console.error("[UNIT][BULK RESTORE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'bulk_restore_units', { entityType: 'unit', details: { ids: validIds, count } });
|
logActivity(req.user.user_id, 'bulk_restore_units', { entityType: 'unit', details: { ids: validIds, count } });
|
||||||
return R.success(res, `${count} unit${count !== 1 ? "s" : ""} restored.`);
|
return R.success(res, `${count} unit${count !== 1 ? "s" : ""} restored.`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -660,6 +664,7 @@ exports.permanentlyDeleteUnit = async (req, res) => {
|
|||||||
if (record === null) return R.error(res, "Unit not found.", 404);
|
if (record === null) return R.error(res, "Unit not found.", 404);
|
||||||
if (record === false) return R.error(res, "Unit must be archived before it can be permanently deleted.", 400);
|
if (record === false) return R.error(res, "Unit must be archived before it can be permanently deleted.", 400);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try { await recomputeCourseDuration(courseId); } catch (durErr) { console.error("[UNIT][PERMANENT DELETE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'permanently_delete_unit', { entityType: 'unit', entityId: Number(unitId) });
|
logActivity(req.user.user_id, 'permanently_delete_unit', { entityType: 'unit', entityId: Number(unitId) });
|
||||||
return R.success(res, "Unit permanently deleted.");
|
return R.success(res, "Unit permanently deleted.");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -681,6 +686,7 @@ exports.bulkPermanentlyDeleteUnits = async (req, res) => {
|
|||||||
|
|
||||||
const count = await permanentDeleteMany(Unit, "unit_id", validIds, t);
|
const count = await permanentDeleteMany(Unit, "unit_id", validIds, t);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try { await recomputeCourseDuration(courseId); } catch (durErr) { console.error("[UNIT][BULK PERMANENT DELETE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'bulk_permanently_delete_units', { entityType: 'unit', details: { ids: validIds, count } });
|
logActivity(req.user.user_id, 'bulk_permanently_delete_units', { entityType: 'unit', details: { ids: validIds, count } });
|
||||||
return R.success(res, `${count} unit${count !== 1 ? "s" : ""} permanently deleted.`);
|
return R.success(res, `${count} unit${count !== 1 ? "s" : ""} permanently deleted.`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -841,6 +847,10 @@ exports.archiveLesson = async (req, res) => {
|
|||||||
|
|
||||||
if (!record) return R.error(res, "Lesson not found.", 404);
|
if (!record) return R.error(res, "Lesson not found.", 404);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try {
|
||||||
|
await recomputeUnitDuration(unitId);
|
||||||
|
await recomputeCourseDuration(courseId);
|
||||||
|
} catch (durErr) { console.error("[LESSON][ARCHIVE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'archive_lesson', { entityType: 'lesson', entityId: Number(lessonId) });
|
logActivity(req.user.user_id, 'archive_lesson', { entityType: 'lesson', entityId: Number(lessonId) });
|
||||||
return R.success(res, "Lesson archived.");
|
return R.success(res, "Lesson archived.");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -865,6 +875,10 @@ exports.bulkArchiveLessons = async (req, res) => {
|
|||||||
|
|
||||||
const count = await archiveMany(Lesson, "lesson_id", validIds, req.user.user_id, t);
|
const count = await archiveMany(Lesson, "lesson_id", validIds, req.user.user_id, t);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try {
|
||||||
|
await recomputeUnitDuration(unitId);
|
||||||
|
await recomputeCourseDuration(courseId);
|
||||||
|
} catch (durErr) { console.error("[LESSON][BULK ARCHIVE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'bulk_archive_lessons', { entityType: 'lesson', details: { ids: validIds, count } });
|
logActivity(req.user.user_id, 'bulk_archive_lessons', { entityType: 'lesson', details: { ids: validIds, count } });
|
||||||
return R.success(res, `${count} lesson${count !== 1 ? "s" : ""} archived.`);
|
return R.success(res, `${count} lesson${count !== 1 ? "s" : ""} archived.`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -929,6 +943,10 @@ exports.restoreLesson = async (req, res) => {
|
|||||||
const record = await restoreOne(Lesson, { lesson_id: lessonId, unit_id: unitId, ...onlyDeleted }, req.user.user_id, t);
|
const record = await restoreOne(Lesson, { lesson_id: lessonId, unit_id: unitId, ...onlyDeleted }, req.user.user_id, t);
|
||||||
if (!record) return R.error(res, "Archived lesson not found.", 404);
|
if (!record) return R.error(res, "Archived lesson not found.", 404);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try {
|
||||||
|
await recomputeUnitDuration(unitId);
|
||||||
|
await recomputeCourseDuration(courseId);
|
||||||
|
} catch (durErr) { console.error("[LESSON][RESTORE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'restore_lesson', { entityType: 'lesson', entityId: Number(lessonId) });
|
logActivity(req.user.user_id, 'restore_lesson', { entityType: 'lesson', entityId: Number(lessonId) });
|
||||||
return R.success(res, "Lesson restored.", { data: record });
|
return R.success(res, "Lesson restored.", { data: record });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -953,6 +971,10 @@ exports.bulkRestoreLessons = async (req, res) => {
|
|||||||
|
|
||||||
const count = await restoreMany(Lesson, "lesson_id", validIds, req.user.user_id, t);
|
const count = await restoreMany(Lesson, "lesson_id", validIds, req.user.user_id, t);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try {
|
||||||
|
await recomputeUnitDuration(unitId);
|
||||||
|
await recomputeCourseDuration(courseId);
|
||||||
|
} catch (durErr) { console.error("[LESSON][BULK RESTORE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'bulk_restore_lessons', { entityType: 'lesson', details: { ids: validIds, count } });
|
logActivity(req.user.user_id, 'bulk_restore_lessons', { entityType: 'lesson', details: { ids: validIds, count } });
|
||||||
return R.success(res, `${count} lesson${count !== 1 ? "s" : ""} restored.`);
|
return R.success(res, `${count} lesson${count !== 1 ? "s" : ""} restored.`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -974,6 +996,10 @@ exports.permanentlyDeleteLesson = async (req, res) => {
|
|||||||
if (record === null) return R.error(res, "Lesson not found.", 404);
|
if (record === null) return R.error(res, "Lesson not found.", 404);
|
||||||
if (record === false) return R.error(res, "Lesson must be archived before it can be permanently deleted.", 400);
|
if (record === false) return R.error(res, "Lesson must be archived before it can be permanently deleted.", 400);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try {
|
||||||
|
await recomputeUnitDuration(unitId);
|
||||||
|
await recomputeCourseDuration(courseId);
|
||||||
|
} catch (durErr) { console.error("[LESSON][PERMANENT DELETE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'permanently_delete_lesson', { entityType: 'lesson', entityId: Number(lessonId) });
|
logActivity(req.user.user_id, 'permanently_delete_lesson', { entityType: 'lesson', entityId: Number(lessonId) });
|
||||||
return R.success(res, "Lesson permanently deleted.");
|
return R.success(res, "Lesson permanently deleted.");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -998,6 +1024,10 @@ exports.bulkPermanentlyDeleteLessons = async (req, res) => {
|
|||||||
|
|
||||||
const count = await permanentDeleteMany(Lesson, "lesson_id", validIds, t);
|
const count = await permanentDeleteMany(Lesson, "lesson_id", validIds, t);
|
||||||
await t.commit();
|
await t.commit();
|
||||||
|
try {
|
||||||
|
await recomputeUnitDuration(unitId);
|
||||||
|
await recomputeCourseDuration(courseId);
|
||||||
|
} catch (durErr) { console.error("[LESSON][BULK PERMANENT DELETE][DURATION]", durErr); }
|
||||||
logActivity(req.user.user_id, 'bulk_permanently_delete_lessons', { entityType: 'lesson', details: { ids: validIds, count } });
|
logActivity(req.user.user_id, 'bulk_permanently_delete_lessons', { entityType: 'lesson', details: { ids: validIds, count } });
|
||||||
return R.success(res, `${count} lesson${count !== 1 ? "s" : ""} permanently deleted.`);
|
return R.success(res, `${count} lesson${count !== 1 ? "s" : ""} permanently deleted.`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
+47
-22
@@ -47,6 +47,44 @@ function estimateBlockDuration(block) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute and persist a unit's duration_seconds from the sum of its
|
||||||
|
* non-archived lessons. Used both by recomputeDurations() (a lesson's
|
||||||
|
* content changed) and directly by archive/restore/delete handlers (a
|
||||||
|
* lesson was added to or removed from the "counts toward duration" set
|
||||||
|
* without any of its content changing).
|
||||||
|
*/
|
||||||
|
async function recomputeUnitDuration(unitId) {
|
||||||
|
const Lesson = require("../models/courses/lessons.mdl");
|
||||||
|
const Unit = require("../models/courses/units.mdl");
|
||||||
|
|
||||||
|
const [unitResult] = await Lesson.sequelize.query(`
|
||||||
|
SELECT COALESCE(SUM(duration_seconds), 0) AS total
|
||||||
|
FROM lessons
|
||||||
|
WHERE unit_id = :unitId AND "deletedAt" IS NULL
|
||||||
|
`, { replacements: { unitId }, type: Lesson.sequelize.QueryTypes.SELECT });
|
||||||
|
|
||||||
|
await Unit.update({ duration_seconds: unitResult.total }, { where: { unit_id: unitId } });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recompute and persist a course's duration_seconds from the sum of its
|
||||||
|
* non-archived units. See recomputeUnitDuration() above for why this is
|
||||||
|
* split out on its own instead of only living inside recomputeDurations().
|
||||||
|
*/
|
||||||
|
async function recomputeCourseDuration(courseId) {
|
||||||
|
const Unit = require("../models/courses/units.mdl");
|
||||||
|
const { Course } = require("../models/courses/courses.mdl");
|
||||||
|
|
||||||
|
const [courseResult] = await Unit.sequelize.query(`
|
||||||
|
SELECT COALESCE(SUM(duration_seconds), 0) AS total
|
||||||
|
FROM units
|
||||||
|
WHERE course_id = :courseId AND "deletedAt" IS NULL
|
||||||
|
`, { replacements: { courseId }, type: Unit.sequelize.QueryTypes.SELECT });
|
||||||
|
|
||||||
|
await Course.update({ duration_seconds: courseResult.total }, { where: { course_id: courseId } });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recompute and persist duration_seconds up the chain:
|
* Recompute and persist duration_seconds up the chain:
|
||||||
* blocks → lesson → unit → course
|
* blocks → lesson → unit → course
|
||||||
@@ -54,7 +92,6 @@ function estimateBlockDuration(block) {
|
|||||||
async function recomputeDurations(lessonId) {
|
async function recomputeDurations(lessonId) {
|
||||||
const Lesson = require("../models/courses/lessons.mdl");
|
const Lesson = require("../models/courses/lessons.mdl");
|
||||||
const Unit = require("../models/courses/units.mdl");
|
const Unit = require("../models/courses/units.mdl");
|
||||||
const { Course } = require("../models/courses/courses.mdl");
|
|
||||||
const LessonPage = require("../models/courses/lesson_page.mdl");
|
const LessonPage = require("../models/courses/lesson_page.mdl");
|
||||||
|
|
||||||
// 1. Lesson duration from blocks
|
// 1. Lesson duration from blocks
|
||||||
@@ -69,29 +106,11 @@ async function recomputeDurations(lessonId) {
|
|||||||
|
|
||||||
// 2. Unit duration — sum of its lessons
|
// 2. Unit duration — sum of its lessons
|
||||||
const lesson = await Lesson.findOne({ where: { lesson_id: lessonId } });
|
const lesson = await Lesson.findOne({ where: { lesson_id: lessonId } });
|
||||||
const [unitResult] = await Lesson.sequelize.query(`
|
await recomputeUnitDuration(lesson.unit_id);
|
||||||
SELECT COALESCE(SUM(duration_seconds), 0) AS total
|
|
||||||
FROM lessons
|
|
||||||
WHERE unit_id = :unitId AND "deletedAt" IS NULL
|
|
||||||
`, { replacements: { unitId: lesson.unit_id }, type: Lesson.sequelize.QueryTypes.SELECT });
|
|
||||||
|
|
||||||
await Unit.update(
|
|
||||||
{ duration_seconds: unitResult.total },
|
|
||||||
{ where: { unit_id: lesson.unit_id } }
|
|
||||||
);
|
|
||||||
|
|
||||||
// 3. Course duration — sum of its units
|
// 3. Course duration — sum of its units
|
||||||
const unit = await Unit.findOne({ where: { unit_id: lesson.unit_id } });
|
const unit = await Unit.findOne({ where: { unit_id: lesson.unit_id } });
|
||||||
const [courseResult] = await Lesson.sequelize.query(`
|
await recomputeCourseDuration(unit.course_id);
|
||||||
SELECT COALESCE(SUM(duration_seconds), 0) AS total
|
|
||||||
FROM units
|
|
||||||
WHERE course_id = :courseId AND "deletedAt" IS NULL
|
|
||||||
`, { replacements: { courseId: unit.course_id }, type: Lesson.sequelize.QueryTypes.SELECT });
|
|
||||||
|
|
||||||
await Course.update(
|
|
||||||
{ duration_seconds: courseResult.total },
|
|
||||||
{ where: { course_id: unit.course_id } }
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,4 +129,10 @@ function formatDuration(seconds) {
|
|||||||
return parts.join(" ");
|
return parts.join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { estimateBlockDuration, recomputeDurations, formatDuration };
|
module.exports = {
|
||||||
|
estimateBlockDuration,
|
||||||
|
recomputeDurations,
|
||||||
|
recomputeUnitDuration,
|
||||||
|
recomputeCourseDuration,
|
||||||
|
formatDuration,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user