mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -0,0 +1,138 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: task_reading_progress_sync.service.js
|
||||
* Type of Program: Service
|
||||
* Description: Backfills task_progress for read_* task requirements from course_reading_progress.
|
||||
*
|
||||
* This covers the case where a user already completed reading a course/unit/lesson
|
||||
* before a task requiring that item was created or assigned.
|
||||
***********************************************************************************************************************************************************************/
|
||||
'use strict';
|
||||
|
||||
const { Op } = require('sequelize');
|
||||
const CourseReadingProgress = require('../models/courses/course_reading_progress.mdl');
|
||||
const { TaskProgress } = require('../models/task/task_progress.mdl');
|
||||
|
||||
const READ_TYPE_TO_PROGRESS_TYPE = {
|
||||
read_course: 'course',
|
||||
read_unit: 'unit',
|
||||
read_lesson: 'lesson',
|
||||
};
|
||||
|
||||
const READ_REQUIREMENT_TYPES = Object.keys(READ_TYPE_TO_PROGRESS_TYPE);
|
||||
|
||||
function readAttr(row, attr) {
|
||||
if (!row) return undefined;
|
||||
if (typeof row.get === 'function') return row.get(attr);
|
||||
return row[attr];
|
||||
}
|
||||
|
||||
function normalizeRequirement(row) {
|
||||
const type = readAttr(row, 'type');
|
||||
if (!READ_REQUIREMENT_TYPES.includes(type)) return null;
|
||||
|
||||
const referenceId = readAttr(row, 'reference_id');
|
||||
if (!referenceId) return null;
|
||||
|
||||
return {
|
||||
task_id: readAttr(row, 'task_id'),
|
||||
requirement_id: readAttr(row, 'requirement_id'),
|
||||
reference_id: referenceId,
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
async function hydrateReadTaskProgress(userId, requirements = [], options = {}) {
|
||||
const readRequirements = requirements
|
||||
.map(normalizeRequirement)
|
||||
.filter((req) => req && req.task_id && req.requirement_id);
|
||||
|
||||
if (!readRequirements.length) return [];
|
||||
|
||||
const referencesByProgressType = readRequirements.reduce((acc, req) => {
|
||||
const progressType = READ_TYPE_TO_PROGRESS_TYPE[req.type];
|
||||
if (!acc[progressType]) acc[progressType] = new Set();
|
||||
acc[progressType].add(req.reference_id);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const where = {
|
||||
user_id: userId,
|
||||
status: 'completed',
|
||||
[Op.or]: Object.entries(referencesByProgressType).map(([type, references]) => ({
|
||||
type,
|
||||
reference_id: { [Op.in]: [...references] },
|
||||
})),
|
||||
};
|
||||
|
||||
const completedReadingRows = await CourseReadingProgress.findAll({
|
||||
where,
|
||||
attributes: ['type', 'reference_id', 'completed_at'],
|
||||
transaction: options.transaction,
|
||||
});
|
||||
|
||||
if (!completedReadingRows.length) return [];
|
||||
|
||||
const completedReading = new Map(
|
||||
completedReadingRows.map((row) => [
|
||||
`${readAttr(row, 'type')}:${readAttr(row, 'reference_id')}`,
|
||||
readAttr(row, 'completed_at'),
|
||||
])
|
||||
);
|
||||
|
||||
const now = new Date();
|
||||
const rowsToUpsert = readRequirements.filter((req) =>
|
||||
completedReading.has(`${READ_TYPE_TO_PROGRESS_TYPE[req.type]}:${req.reference_id}`)
|
||||
);
|
||||
|
||||
if (!rowsToUpsert.length) return [];
|
||||
|
||||
const existingProgressRows = await TaskProgress.findAll({
|
||||
where: {
|
||||
user_id: userId,
|
||||
completed: true,
|
||||
requirement_id: { [Op.in]: rowsToUpsert.map((req) => req.requirement_id) },
|
||||
reference_id: { [Op.in]: rowsToUpsert.map((req) => req.reference_id) },
|
||||
},
|
||||
attributes: ['requirement_id', 'reference_id'],
|
||||
transaction: options.transaction,
|
||||
});
|
||||
|
||||
const existingProgress = new Set(
|
||||
existingProgressRows.map((row) =>
|
||||
`${readAttr(row, 'requirement_id')}:${readAttr(row, 'reference_id')}`
|
||||
)
|
||||
);
|
||||
|
||||
const missingRows = rowsToUpsert.filter((req) =>
|
||||
!existingProgress.has(`${req.requirement_id}:${req.reference_id}`)
|
||||
);
|
||||
|
||||
if (!missingRows.length) return [];
|
||||
|
||||
await Promise.all(missingRows.map((req) =>
|
||||
TaskProgress.upsert(
|
||||
{
|
||||
task_id: req.task_id,
|
||||
requirement_id: req.requirement_id,
|
||||
user_id: userId,
|
||||
reference_id: req.reference_id,
|
||||
type: req.type,
|
||||
completed: true,
|
||||
completed_at: completedReading.get(`${READ_TYPE_TO_PROGRESS_TYPE[req.type]}:${req.reference_id}`) ?? now,
|
||||
createdBy: userId,
|
||||
updatedBy: userId,
|
||||
},
|
||||
{
|
||||
conflictFields: ['requirement_id', 'user_id', 'reference_id'],
|
||||
transaction: options.transaction,
|
||||
}
|
||||
)
|
||||
));
|
||||
|
||||
return missingRows;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hydrateReadTaskProgress,
|
||||
READ_REQUIREMENT_TYPES,
|
||||
};
|
||||
Reference in New Issue
Block a user