mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
139 lines
4.7 KiB
JavaScript
139 lines
4.7 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* 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,
|
|
};
|