mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready to test
Testing Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: course_reading_progress.service.js
|
||||
* Type of Program: Service
|
||||
* Description: UPSERT-based progress tracking for user reading activity across the course hierarchy.
|
||||
*
|
||||
* upsertLessonRead — main entry point, called when a user reads a lesson.
|
||||
* UPSERTs the lesson row, then derives and UPSERTs the parent unit and course rows.
|
||||
* All three writes run in a single transaction.
|
||||
*
|
||||
* UPSERT key: (user_id, type, reference_id)
|
||||
*
|
||||
* Derivation rules:
|
||||
* unit → completed when ALL its non-deleted lessons have a completed row for this user
|
||||
* course → completed when ALL its non-deleted units have a completed row for this user
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 21, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
'use strict';
|
||||
|
||||
const sequelize = require('../config/db.config');
|
||||
const CourseReadingProgress = require('../models/courses/course_reading_progress.mdl');
|
||||
const Lesson = require('../models/courses/lessons.mdl');
|
||||
const Unit = require('../models/courses/units.mdl');
|
||||
|
||||
// ─── Core UPSERT ─────────────────────────────────────────────────────────────
|
||||
|
||||
async function upsertProgress({ userId, courseId, type, referenceId, status }, t) {
|
||||
const now = new Date();
|
||||
const [record] = await CourseReadingProgress.upsert(
|
||||
{
|
||||
user_id: userId,
|
||||
course_id: courseId,
|
||||
reference_id: referenceId,
|
||||
type,
|
||||
status,
|
||||
completed_at: status === 'completed' ? now : null,
|
||||
last_accessed_at: now,
|
||||
createdBy: userId,
|
||||
updatedBy: userId,
|
||||
},
|
||||
{
|
||||
conflictFields: ['user_id', 'type', 'reference_id'],
|
||||
returning: true,
|
||||
transaction: t,
|
||||
}
|
||||
);
|
||||
return record;
|
||||
}
|
||||
|
||||
// ─── Derivation helpers ───────────────────────────────────────────────────────
|
||||
|
||||
// Unit is completed when every non-deleted lesson under it has a completed row for this user.
|
||||
async function deriveUnitStatus(userId, courseId, unitId, t) {
|
||||
const lessons = await Lesson.findAll({
|
||||
where: { unit_id: unitId },
|
||||
attributes: ['uuid'],
|
||||
transaction: t,
|
||||
});
|
||||
if (!lessons.length) return 'in_progress';
|
||||
|
||||
const lessonUuids = lessons.map(l => l.uuid);
|
||||
const completedCount = await CourseReadingProgress.count({
|
||||
where: {
|
||||
user_id: userId,
|
||||
course_id: courseId,
|
||||
type: 'lesson',
|
||||
reference_id: lessonUuids,
|
||||
status: 'completed',
|
||||
},
|
||||
transaction: t,
|
||||
});
|
||||
|
||||
return completedCount === lessons.length ? 'completed' : 'in_progress';
|
||||
}
|
||||
|
||||
// Course is completed when every non-deleted unit under it has a completed row for this user.
|
||||
async function deriveCourseStatus(userId, courseId, t) {
|
||||
const units = await Unit.findAll({
|
||||
where: { course_id: courseId },
|
||||
attributes: ['uuid'],
|
||||
transaction: t,
|
||||
});
|
||||
if (!units.length) return 'in_progress';
|
||||
|
||||
const unitUuids = units.map(u => u.uuid);
|
||||
const completedCount = await CourseReadingProgress.count({
|
||||
where: {
|
||||
user_id: userId,
|
||||
course_id: courseId,
|
||||
type: 'unit',
|
||||
reference_id: unitUuids,
|
||||
status: 'completed',
|
||||
},
|
||||
transaction: t,
|
||||
});
|
||||
|
||||
return completedCount === units.length ? 'completed' : 'in_progress';
|
||||
}
|
||||
|
||||
// ─── Main entry point ─────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Called when a user reads (or finishes reading) a lesson.
|
||||
* Writes three rows in one transaction: lesson → unit → course.
|
||||
*
|
||||
* @param {number} userId
|
||||
* @param {Object} payload
|
||||
* @param {number} payload.courseId — course BIGINT PK (FK on course_reading_progress)
|
||||
* @param {string} payload.courseUuid — course UUID (reference_id for the course row)
|
||||
* @param {number} payload.unitId — unit BIGINT PK (used to query sibling lessons)
|
||||
* @param {string} payload.unitUuid — unit UUID (reference_id for the unit row)
|
||||
* @param {string} payload.lessonUuid — lesson UUID (reference_id for the lesson row)
|
||||
* @param {string} payload.lessonStatus — 'in_progress' | 'completed'
|
||||
* @returns {{ lesson, unit, course }} — status snapshot for each level
|
||||
*/
|
||||
async function upsertLessonRead(userId, { courseId, courseUuid, unitId, unitUuid, lessonUuid, lessonStatus = 'in_progress' }) {
|
||||
const t = await sequelize.transaction();
|
||||
try {
|
||||
// 1. Lesson
|
||||
await upsertProgress({
|
||||
userId,
|
||||
courseId,
|
||||
type: 'lesson',
|
||||
referenceId: lessonUuid,
|
||||
status: lessonStatus,
|
||||
}, t);
|
||||
|
||||
// 2. Unit — derived from all sibling lessons
|
||||
const unitStatus = await deriveUnitStatus(userId, courseId, unitId, t);
|
||||
await upsertProgress({
|
||||
userId,
|
||||
courseId,
|
||||
type: 'unit',
|
||||
referenceId: unitUuid,
|
||||
status: unitStatus,
|
||||
}, t);
|
||||
|
||||
// 3. Course — derived from all units
|
||||
const courseStatus = await deriveCourseStatus(userId, courseId, t);
|
||||
await upsertProgress({
|
||||
userId,
|
||||
courseId,
|
||||
type: 'course',
|
||||
referenceId: courseUuid,
|
||||
status: courseStatus,
|
||||
}, t);
|
||||
|
||||
await t.commit();
|
||||
|
||||
return {
|
||||
lesson: { reference_id: lessonUuid, status: lessonStatus },
|
||||
unit: { reference_id: unitUuid, status: unitStatus },
|
||||
course: { reference_id: courseUuid, status: courseStatus },
|
||||
};
|
||||
} catch (err) {
|
||||
await t.rollback();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Exports ──────────────────────────────────────────────────────────────────
|
||||
|
||||
module.exports = {
|
||||
upsertLessonRead,
|
||||
upsertProgress,
|
||||
deriveUnitStatus,
|
||||
deriveCourseStatus,
|
||||
};
|
||||
Reference in New Issue
Block a user