mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -2535,59 +2535,6 @@ exports.getLessonsFlat = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// One row per unit quiz (a quiz is always unit-scoped, unit_id unique on
|
||||
// unit_quizzes) — `courses[]` is the deduped set of courses the parent unit
|
||||
// is attached to, batch-fetched the same way as getUnitsFlat/getLessonsFlat.
|
||||
// Used by the pass_quiz task requirement picker — same "no content yet"
|
||||
// convention as read_*: question_count === 0 is flagged the same way
|
||||
// duration_seconds === 0 is for content requirements.
|
||||
exports.getQuizzesFlat = async (req, res) => {
|
||||
try {
|
||||
const rows = await sequelize.query(`
|
||||
SELECT
|
||||
q.quiz_id, q.uuid, q.title, u.title AS unit_title,
|
||||
(SELECT CAST(COUNT(*) AS INTEGER) FROM quiz_questions qq
|
||||
WHERE qq.quiz_id = q.quiz_id AND qq."deletedAt" IS NULL) AS question_count
|
||||
FROM unit_quizzes q
|
||||
JOIN units u ON u.unit_id = q.unit_id AND u."deletedAt" IS NULL
|
||||
WHERE q."deletedAt" IS NULL
|
||||
ORDER BY u.title ASC, q.title ASC
|
||||
`, { type: sequelize.QueryTypes.SELECT });
|
||||
|
||||
const quizIds = rows.map((r) => r.quiz_id);
|
||||
const courseLinkRows = quizIds.length ? await sequelize.query(`
|
||||
SELECT q.quiz_id, c.uuid, c.title
|
||||
FROM unit_quizzes q
|
||||
JOIN course_units cu ON cu.unit_id = q.unit_id
|
||||
JOIN courses c ON c.course_id = cu.course_id AND c."deletedAt" IS NULL
|
||||
WHERE q.quiz_id IN (:quizIds)
|
||||
ORDER BY c.title ASC
|
||||
`, { replacements: { quizIds }, type: sequelize.QueryTypes.SELECT }) : [];
|
||||
|
||||
const coursesByQuiz = new Map();
|
||||
for (const row of courseLinkRows) {
|
||||
const list = coursesByQuiz.get(row.quiz_id) ?? [];
|
||||
list.push({ uuid: row.uuid, title: row.title });
|
||||
coursesByQuiz.set(row.quiz_id, list);
|
||||
}
|
||||
|
||||
const data = rows.map((r) => ({
|
||||
uuid: r.uuid,
|
||||
title: r.title || `${r.unit_title} Quiz`,
|
||||
unit_title: r.unit_title ?? "",
|
||||
courses: coursesByQuiz.get(r.quiz_id) ?? [],
|
||||
question_count: Number(r.question_count ?? 0),
|
||||
// duration_seconds doesn't apply to quizzes — ContentPicker's "no
|
||||
// content" check keys off duration_seconds === 0, so surface the same
|
||||
// signal under that name rather than adding a second code path.
|
||||
duration_seconds: Number(r.question_count ?? 0),
|
||||
}));
|
||||
return R.success(res, "Quizzes retrieved.", data);
|
||||
} catch (err) {
|
||||
console.error("[QUIZ][GET FLAT]", err);
|
||||
return R.error(res, "Could not retrieve quizzes.", 500);
|
||||
}
|
||||
};
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// COURSE INSTRUCTORS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
@@ -33,8 +33,6 @@ const LessonReadingProgress = require('../../models/courses/lesson_reading_progr
|
||||
const UnitReadingProgress = require('../../models/courses/unit_reading_progress.mdl');
|
||||
const Lesson = require('../../models/courses/lessons.mdl');
|
||||
const Unit = require('../../models/courses/units.mdl');
|
||||
const QuizAttempt = require('../../models/courses/quiz_attempt.mdl');
|
||||
const UnitQuiz = require('../../models/courses/unit_quiz.mdl');
|
||||
|
||||
// ─── URL normalizer — ensures protocol prefix so link_url is never a relative path ──
|
||||
const normalizeUrl = (url) => {
|
||||
@@ -679,14 +677,14 @@ const PREREQUISITE_INCLUDE = {
|
||||
class TaskValidationError extends Error {}
|
||||
|
||||
// ─── Pre-completed assignees check ────────────────────────────────────────────
|
||||
// A read_course/read_unit/read_lesson/pass_quiz requirement can reference content
|
||||
// A read_course/read_unit/read_lesson requirement can reference content
|
||||
// that a task list's assignees already finished BEFORE this requirement existed.
|
||||
// That's not an error — task_reading_progress_sync.service.js's hydrateReadTaskProgress
|
||||
// (client-side, on task list load) already auto-marks it done for them — but the
|
||||
// admin creating/editing the task has no visibility into it otherwise. This is a
|
||||
// heads-up, not a validator: it never blocks create/update, only informs.
|
||||
const READ_TYPE_TO_PROGRESS_TYPE = { read_course: 'course', read_unit: 'unit', read_lesson: 'lesson' };
|
||||
const progressKeyType = (reqType) => (reqType === 'pass_quiz' ? 'pass_quiz' : READ_TYPE_TO_PROGRESS_TYPE[reqType]);
|
||||
const progressKeyType = (reqType) => READ_TYPE_TO_PROGRESS_TYPE[reqType];
|
||||
|
||||
async function getPreCompletedAssignees(taskListId, requirements, transaction) {
|
||||
const contentReqs = (requirements ?? []).filter(
|
||||
@@ -772,30 +770,6 @@ async function getPreCompletedAssignees(taskListId, requirements, transaction) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── pass_quiz ────────────────────────────────────────────────────────────
|
||||
const quizReqs = contentReqs.filter((r) => r.type === 'pass_quiz');
|
||||
if (quizReqs.length) {
|
||||
const quizUuids = [...new Set(quizReqs.map((r) => r.reference_id))];
|
||||
const quizzes = await UnitQuiz.findAll({
|
||||
where: { uuid: { [Op.in]: quizUuids } },
|
||||
attributes: ['quiz_id', 'uuid'],
|
||||
transaction,
|
||||
});
|
||||
const uuidByQuizId = new Map(quizzes.map((q) => [q.quiz_id, q.uuid]));
|
||||
const quizIds = quizzes.map((q) => q.quiz_id);
|
||||
if (quizIds.length) {
|
||||
const rows = await QuizAttempt.findAll({
|
||||
where: { quiz_id: { [Op.in]: quizIds }, user_id: { [Op.in]: userIds }, passed: true },
|
||||
attributes: ['user_id', 'quiz_id'],
|
||||
transaction,
|
||||
});
|
||||
for (const row of rows) {
|
||||
const uuid = uuidByQuizId.get(row.quiz_id);
|
||||
if (uuid) markCompleted('pass_quiz', uuid, row.user_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── One entry per requirement that has at least one already-completed assignee ──
|
||||
const results = [];
|
||||
for (const r of contentReqs) {
|
||||
|
||||
Reference in New Issue
Block a user