mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
course,tasklist,task and completed validation
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: task_reading_progress_sync.service.js
|
||||
* Type of Program: Service
|
||||
* Description: Bridges course/unit/lesson completion (course_reading_progress) to Task
|
||||
* requirements of type read_course/read_unit/read_lesson, in both directions:
|
||||
* Description: Bridges course/unit/lesson completion (course_reading_progress, plus the
|
||||
* standalone lesson_reading_progress/unit_reading_progress system-of-record
|
||||
* used when a lesson/unit has no parent course — see the junction revamp) to
|
||||
* Task requirements of type read_course/read_unit/read_lesson, in both directions:
|
||||
*
|
||||
* hydrateReadTaskProgress(userId, requirements)
|
||||
* — given a list of TaskRequirement rows (typically when a task/task list is newly
|
||||
@@ -23,6 +25,10 @@
|
||||
|
||||
const { Op } = require('sequelize');
|
||||
const CourseReadingProgress = require('../models/courses/course_reading_progress.mdl');
|
||||
const LessonReadingProgress = require('../models/courses/lesson_reading_progress.mdl');
|
||||
const UnitReadingProgress = require('../models/courses/unit_reading_progress.mdl');
|
||||
const Lesson = require('../models/courses/lessons.mdl');
|
||||
const Unit = require('../models/courses/units.mdl');
|
||||
const { TaskProgress } = require('../models/task/task_progress.mdl');
|
||||
const { Task, TaskRequirement, TaskListGroup } = require('../models/task/task.mdl');
|
||||
const { mdl_UserGroupMembers } = require('../models/users/user_groups.mdl');
|
||||
@@ -56,6 +62,60 @@ function normalizeRequirement(row) {
|
||||
};
|
||||
}
|
||||
|
||||
// ─── Standalone (no parent course) lesson/unit completions ───────────────────
|
||||
// lesson_reading_progress/unit_reading_progress are keyed by numeric PK, but
|
||||
// TaskRequirement.reference_id is the lesson/unit UUID — resolve UUID -> PK
|
||||
// first, then look up completion, then map back to the `type:uuid` key shape
|
||||
// hydrateReadTaskProgress's caller expects. read_course has no standalone
|
||||
// table (a course always has a courseId by definition), so it's skipped.
|
||||
async function getStandaloneCompletedReading(userId, referencesByProgressType, transaction) {
|
||||
const entries = [];
|
||||
|
||||
const lessonUuids = [...(referencesByProgressType.lesson ?? [])];
|
||||
if (lessonUuids.length) {
|
||||
const lessons = await Lesson.findAll({
|
||||
where: { uuid: { [Op.in]: lessonUuids } },
|
||||
attributes: ['lesson_id', 'uuid'],
|
||||
transaction,
|
||||
});
|
||||
if (lessons.length) {
|
||||
const uuidByLessonId = new Map(lessons.map((l) => [readAttr(l, 'lesson_id'), readAttr(l, 'uuid')]));
|
||||
const rows = await LessonReadingProgress.findAll({
|
||||
where: { user_id: userId, lesson_id: { [Op.in]: [...uuidByLessonId.keys()] }, status: 'completed' },
|
||||
attributes: ['lesson_id', 'completed_at'],
|
||||
transaction,
|
||||
});
|
||||
for (const row of rows) {
|
||||
const uuid = uuidByLessonId.get(readAttr(row, 'lesson_id'));
|
||||
if (uuid) entries.push([`lesson:${uuid}`, readAttr(row, 'completed_at')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const unitUuids = [...(referencesByProgressType.unit ?? [])];
|
||||
if (unitUuids.length) {
|
||||
const units = await Unit.findAll({
|
||||
where: { uuid: { [Op.in]: unitUuids } },
|
||||
attributes: ['unit_id', 'uuid'],
|
||||
transaction,
|
||||
});
|
||||
if (units.length) {
|
||||
const uuidByUnitId = new Map(units.map((u) => [readAttr(u, 'unit_id'), readAttr(u, 'uuid')]));
|
||||
const rows = await UnitReadingProgress.findAll({
|
||||
where: { user_id: userId, unit_id: { [Op.in]: [...uuidByUnitId.keys()] }, status: 'completed' },
|
||||
attributes: ['unit_id', 'completed_at'],
|
||||
transaction,
|
||||
});
|
||||
for (const row of rows) {
|
||||
const uuid = uuidByUnitId.get(readAttr(row, 'unit_id'));
|
||||
if (uuid) entries.push([`unit:${uuid}`, readAttr(row, 'completed_at')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
async function hydrateReadTaskProgress(userId, requirements = [], options = {}) {
|
||||
const readRequirements = requirements
|
||||
.map(normalizeRequirement)
|
||||
@@ -85,8 +145,6 @@ async function hydrateReadTaskProgress(userId, requirements = [], options = {})
|
||||
transaction: options.transaction,
|
||||
});
|
||||
|
||||
if (!completedReadingRows.length) return [];
|
||||
|
||||
const completedReading = new Map(
|
||||
completedReadingRows.map((row) => [
|
||||
`${readAttr(row, 'type')}:${readAttr(row, 'reference_id')}`,
|
||||
@@ -94,6 +152,18 @@ async function hydrateReadTaskProgress(userId, requirements = [], options = {})
|
||||
])
|
||||
);
|
||||
|
||||
// ── Standalone lessons/units (no parent course) don't have a
|
||||
// CourseReadingProgress row at all — their system-of-record is
|
||||
// lesson_reading_progress/unit_reading_progress instead (see
|
||||
// completion_requirements.service.js#persistStatus). Merge those in too,
|
||||
// resolving UUID (the TaskRequirement's reference_id) -> numeric PK first.
|
||||
const standaloneEntries = await getStandaloneCompletedReading(userId, referencesByProgressType, options.transaction);
|
||||
for (const [key, completedAt] of standaloneEntries) {
|
||||
if (!completedReading.has(key)) completedReading.set(key, completedAt);
|
||||
}
|
||||
|
||||
if (!completedReading.size) return [];
|
||||
|
||||
const now = new Date();
|
||||
const rowsToUpsert = readRequirements.filter((req) =>
|
||||
completedReading.has(`${READ_TYPE_TO_PROGRESS_TYPE[req.type]}:${req.reference_id}`)
|
||||
|
||||
Reference in New Issue
Block a user