Files
starr-philproperties/apps/api/utils/courses/resolvePrerequisiteTitles.util.js
T

100 lines
3.5 KiB
JavaScript

"use strict";
const REF_TYPE_CONFIG = {
course: { pk: "course_id" },
unit: { pk: "unit_id" },
lesson: { pk: "lesson_id" },
};
/**
* Enriches CoursePrerequisite rows ({ref_type, ref_id, ...}) with the
* referenced entity's title (and subscription tier slug, where the entity
* has one) — one batched findAll per ref_type instead of a query per row.
* Rows whose referenced entity no longer exists (deleted) get title: null
* so callers can drop or flag them instead of crashing.
*/
async function resolvePrerequisiteTitles(prereqs, { Course, Unit, Lesson }) {
if (!prereqs?.length) return prereqs ?? [];
const models = { course: Course, unit: Unit, lesson: Lesson };
// Only Course/Unit carry a subscription tier gate — Lesson has none.
const attrsByType = { course: ["title", "subscription"], unit: ["title", "subscription"], lesson: ["title"] };
const idsByType = { course: new Set(), unit: new Set(), lesson: new Set() };
for (const p of prereqs) {
if (idsByType[p.ref_type]) idsByType[p.ref_type].add(p.ref_id);
}
const infoMaps = {};
for (const [type, { pk }] of Object.entries(REF_TYPE_CONFIG)) {
const ids = [...idsByType[type]];
infoMaps[type] = new Map();
if (!ids.length) continue;
const rows = await models[type].findAll({
where: { [pk]: ids },
attributes: [pk, ...attrsByType[type]],
});
for (const row of rows) infoMaps[type].set(String(row[pk]), row);
}
return prereqs.map((p) => {
const info = infoMaps[p.ref_type]?.get(String(p.ref_id));
return {
...p,
title: info?.title ?? null,
subscription: info?.subscription ?? null,
};
});
}
/**
* Enriches prerequisite rows (already carrying `title`) with `completed` —
* whether the given learner has finished the referenced course/unit/lesson.
* "Completed" means: a Certificate exists for a course prereq, or the
* matching UnitReadingProgress/LessonReadingProgress row has status
* "completed" for a unit/lesson prereq.
*/
async function resolvePrerequisiteCompletion(prereqs, { Certificate, UnitReadingProgress, LessonReadingProgress }, userId) {
if (!prereqs?.length) return prereqs ?? [];
const idsByType = { course: new Set(), unit: new Set(), lesson: new Set() };
for (const p of prereqs) {
if (idsByType[p.ref_type]) idsByType[p.ref_type].add(p.ref_id);
}
const completedIds = { course: new Set(), unit: new Set(), lesson: new Set() };
const courseIds = [...idsByType.course];
if (courseIds.length) {
const certs = await Certificate.findAll({
where: { user_id: userId, course_id: courseIds },
attributes: ["course_id"],
});
certs.forEach((c) => completedIds.course.add(String(c.course_id)));
}
const unitIds = [...idsByType.unit];
if (unitIds.length) {
const rows = await UnitReadingProgress.findAll({
where: { user_id: userId, unit_id: unitIds, status: "completed" },
attributes: ["unit_id"],
});
rows.forEach((r) => completedIds.unit.add(String(r.unit_id)));
}
const lessonIds = [...idsByType.lesson];
if (lessonIds.length) {
const rows = await LessonReadingProgress.findAll({
where: { user_id: userId, lesson_id: lessonIds, status: "completed" },
attributes: ["lesson_id"],
});
rows.forEach((r) => completedIds.lesson.add(String(r.lesson_id)));
}
return prereqs.map((p) => ({
...p,
completed: completedIds[p.ref_type]?.has(String(p.ref_id)) ?? false,
}));
}
module.exports = { resolvePrerequisiteTitles, resolvePrerequisiteCompletion };