fix some issues

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-07-19 14:59:33 +08:00
parent 29dc0bbcce
commit 2c70f4714e
3 changed files with 50 additions and 7 deletions
+45 -4
View File
@@ -16,6 +16,8 @@ const { getFieldValues } = require("../../utils/fieldValues.util");
const logActivity = require('../../utils/logActivity.util');
const UserNotification = require('../../models/notifications/user_notification.mdl');
const { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
const CompletionRequirement = require('../../models/courses/completion_requirement.mdl');
const { VALID_ENTITY_TYPES } = require('../../utils/courses/completion_requirements.registry');
// ── Models ────────────────────────────────────────────────────────────────────
@@ -218,6 +220,23 @@ exports.createCourseFull = async (req, res) => {
await t.rollback();
return R.error(res, "Each new unit needs a title.", 400);
}
// Requirements only apply to units being created fresh here — units
// attached by unit_id already have their own configured set.
if (!unit.unit_id && unit.requirements?.length) {
const seenTypes = new Set();
for (const r of unit.requirements) {
const allowedEntityTypes = VALID_ENTITY_TYPES[r.type];
if (!allowedEntityTypes || !allowedEntityTypes.includes("unit")) {
await t.rollback();
return R.error(res, `"${r.type}" cannot be configured on a unit.`, 400);
}
if (seenTypes.has(r.type)) {
await t.rollback();
return R.error(res, `Duplicate "${r.type}" requirement — only one per unit is allowed.`, 400);
}
seenTypes.add(r.type);
}
}
for (const lesson of unit.lessons ?? []) {
if (!lesson.lesson_id && !lesson.title) {
await t.rollback();
@@ -301,6 +320,21 @@ exports.createCourseFull = async (req, res) => {
duration_seconds: 0,
createdBy: by,
}, { transaction: t });
if (unitInput.requirements?.length) {
const rows = unitInput.requirements.map((r, ri) => ({
entity_type: "unit",
entity_id: unit.unit_id,
type: r.type,
order: r.order ?? ri,
min_percent: r.type === "watch_percent" ? (r.min_percent ?? 100) : null,
button_label: r.type === "manual_complete" ? (r.button_label || null) : null,
is_required: r.is_required ?? true,
createdBy: by,
updatedBy: by,
}));
await CompletionRequirement.bulkCreate(rows, { transaction: t });
}
}
await CourseUnit.create({
@@ -658,10 +692,15 @@ exports.syncPrerequisites = async (req, res) => {
// ══════════════════════════════════════════════════════════════════════════════
// Literal fragments that resolve a unit's order within THIS course.
// course_id is a 19-digit snowflake-style BIGINT — well past
// Number.MAX_SAFE_INTEGER, so Number(courseId) silently rounds to the wrong
// id and these subqueries match nothing. BigInt preserves full precision
// (and throws on non-numeric input, same effect as the old NaN would've had
// via the caller's try/catch).
const courseOrderLiteral = (courseId) =>
`(SELECT cu.order_index FROM course_units cu WHERE cu.course_id = ${Number(courseId)} AND cu.unit_id = "Unit"."unit_id" LIMIT 1)`;
`(SELECT cu.order_index FROM course_units cu WHERE cu.course_id = ${BigInt(courseId)} AND cu.unit_id = "Unit"."unit_id" LIMIT 1)`;
const courseMembershipLiteral = (courseId) =>
`(SELECT cu.unit_id FROM course_units cu WHERE cu.course_id = ${Number(courseId)})`;
`(SELECT cu.unit_id FROM course_units cu WHERE cu.course_id = ${BigInt(courseId)})`;
exports.getUnits = async (req, res) => {
try {
@@ -1197,10 +1236,12 @@ exports.getUnitPermanentDeleteImpact = async (req, res) => {
// Entity-level archive/permanent-delete lives in /admin/lessons (the library).
// ══════════════════════════════════════════════════════════════════════════════
// Same BigInt fix as courseOrderLiteral/courseMembershipLiteral above —
// unit_id is a 19-digit snowflake BIGINT, past Number.MAX_SAFE_INTEGER.
const unitOrderLiteral = (unitId) =>
`(SELECT ul.order_index FROM unit_lessons ul WHERE ul.unit_id = ${Number(unitId)} AND ul.lesson_id = "Lesson"."lesson_id" LIMIT 1)`;
`(SELECT ul.order_index FROM unit_lessons ul WHERE ul.unit_id = ${BigInt(unitId)} AND ul.lesson_id = "Lesson"."lesson_id" LIMIT 1)`;
const unitMembershipLiteral = (unitId) =>
`(SELECT ul.lesson_id FROM unit_lessons ul WHERE ul.unit_id = ${Number(unitId)})`;
`(SELECT ul.lesson_id FROM unit_lessons ul WHERE ul.unit_id = ${BigInt(unitId)})`;
exports.getLessons = async (req, res) => {
try {
+2 -1
View File
@@ -73,8 +73,9 @@ const UNIT_LIST_COMPUTED = [
},
{
key: "course_count",
label: "Used in courses",
label: "Affiliated",
type: "number",
order: 2, // 1: Title, 2: Affiliated, 3: Subscription, 4: Duration — see units.mdl.js
literal: `(
SELECT CAST(COUNT(*) AS INTEGER)
FROM course_units cu