token issues more for pdf preview missing

With DEV to QAS live test environment

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-08-05 20:43:16 +08:00
parent 0c7f5ccd0f
commit 141b7ab592
5 changed files with 170 additions and 26 deletions
+31 -1
View File
@@ -12,7 +12,7 @@ const { syncJunction } = require("../../utils/courses/junction.util");
const { archiveOne, archiveMany } = require("../../utils/courses/archive.util");
const { restoreOne, restoreMany } = require("../../utils/courses/restore.util");
const { permanentDeleteOne, permanentDeleteMany } = require("../../utils/courses/permanentDelete.util");
const { flattenUnits, flattenLessons, nextOrderIndex, reorderJunction } = require("../../utils/courses/hierarchy.util");
const { flattenUnits, flattenLessons, nextOrderIndex, reorderJunction, getCourseUnitIds, countCourseLessons } = require("../../utils/courses/hierarchy.util");
const { getFieldValues } = require("../../utils/fieldValues.util");
const logActivity = require('../../utils/logActivity.util');
const UserNotification = require('../../models/notifications/user_notification.mdl');
@@ -2393,6 +2393,36 @@ exports.getCoursesFlat = async (req, res) => {
}
};
// One course's structure counts, looked up by uuid — used by the Task
// requirement viewer (ViewTask.jsx) to show how many units/lessons/quizzes a
// "Read a Course" requirement actually points to, without pulling the full
// nested course tree.
exports.getCourseStructureCounts = async (req, res) => {
try {
const { uuid } = req.params;
const course = await Course.findOne({ where: { uuid, ...notDeleted }, attributes: ["course_id"] });
if (!course) return R.error(res, "Course not found.", 404);
const unitIds = await getCourseUnitIds(course.course_id);
const [lessonCount, quizCount] = await Promise.all([
countCourseLessons(course.course_id),
unitIds.length
? UnitQuiz.count({ where: { unit_id: { [Op.in]: unitIds } } })
: 0,
]);
return R.success(res, "Course structure counts retrieved.", {
unitCount: unitIds.length,
lessonCount,
quizCount,
});
} catch (err) {
console.error("[COURSE][STRUCTURE COUNTS]", err);
return R.error(res, "Could not retrieve course structure counts.", 500);
}
};
exports.getCoursesBySubscription = async (req, res) => {
try {
const { slug } = req.query;
+98 -2
View File
@@ -558,8 +558,8 @@ exports.unassignGroups = async (req, res) => {
// Body: { group_ids: [1, 2, 3] }
//
// Replaces the full assigned-group set in one request/one transaction, so an
// edit that both adds and removes groups only costs a single sensitiveOpsLimiter
// hit instead of two (assign + unassign).
// edit that both adds and removes groups is a single atomic operation instead
// of two separate calls (assign + unassign).
exports.syncGroups = async (req, res) => {
const t = await sequelize.transaction();
@@ -1056,6 +1056,102 @@ exports.createTask = async (req, res) => {
}
};
// ─── BULK CREATE ──────────────────────────────────────────────────────────────
// POST /:taskListId/tasks/bulk
// Body: { tasks: [{ name, description, deadline, requirements }, ...] }
//
// Batches Task + TaskRequirement creation into one transaction/request — used
// by the Create Task List wizard so queuing N tasks costs one request instead
// of N sequential POST .../tasks calls (see CreateTaskList.jsx handleCreate).
// Prerequisite wiring is intentionally not supported here: queued tasks can't
// reference a not-yet-created sibling task's id, same as createTask today
// when called from this flow.
exports.createTasksBulk = async (req, res) => {
const t = await sequelize.transaction();
try {
const { taskListId } = req.params;
const { tasks } = req.body;
if (!Array.isArray(tasks) || !tasks.length)
return R.error(res, 'No tasks provided.', 400);
if (tasks.some((task) => !task.name)) {
return R.error(res, 'Every task requires a name.', 400);
}
const taskList = await TaskList.findByPk(taskListId, { transaction: t });
if (!taskList) {
await t.rollback();
return R.error(res, 'Task list not found.', 404);
}
const startOrderIndex = await nextOrderIndex(Task, { task_list_id: taskListId }, t);
const createdTasks = await Task.bulkCreate(
tasks.map((task, i) => ({
task_list_id: taskListId,
name: task.name,
description: task.description,
deadline: task.deadline || null,
order_index: startOrderIndex + i,
createdBy: req.user.user_id,
updatedBy: req.user.user_id,
})),
{ transaction: t }
);
const reqRows = createdTasks.flatMap((task, i) =>
(tasks[i].requirements ?? []).map((r, j) => ({
...r,
task_id: task.task_id,
order: r.order ?? j,
reference_id: r.reference_id || null, // '' → null (UUID column)
reference_label: r.reference_label || null, // '' → null
link_url: normalizeUrl(r.link_url),
link_label: r.link_label || null,
createdBy: req.user.user_id,
updatedBy: req.user.user_id,
}))
);
if (reqRows.length) await TaskRequirement.bulkCreate(reqRows, { transaction: t });
await t.commit();
const taskIds = createdTasks.map((task) => task.task_id);
const full = await Task.findAll({
where: { task_id: { [Op.in]: taskIds } },
attributes: { exclude: adminExclude },
include: [{
model: TaskRequirement,
as: 'requirements',
attributes: { exclude: adminExclude },
order: [['order', 'ASC']],
}, PREREQUISITE_INCLUDE],
order: [['order_index', 'ASC']],
});
// Heads-up only — never blocks creation. See getPreCompletedAssignees.
const allRequirements = full.flatMap((task) =>
(task.requirements ?? []).map((r) => ({ type: r.type, reference_id: r.reference_id, reference_label: r.reference_label }))
);
const warnings = await getPreCompletedAssignees(taskListId, allRequirements);
logActivity(req.user.user_id, 'bulk_create_tasks', {
entityType: 'task_list',
entityId: Number(taskListId),
details: { count: createdTasks.length, task_ids: taskIds },
});
return R.success(res, `${createdTasks.length} task(s) created successfully.`, {
tasks: full.map((task) => task.toJSON()),
warnings,
}, 201);
} catch (err) {
await t.rollback();
console.error('[ADMIN][BULK CREATE TASKS]', err);
return R.error(res, 'Could not create tasks.', 500);
}
};
// ─── UPDATE ───────────────────────────────────────────────────────────────────
exports.updateTask = async (req, res) => {