51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
import { courseManifestRepo } from '@/lib/course-manifest-repo/store';
|
|
import type { CourseManifestRepo } from '@/lib/course-manifest-repo/types';
|
|
import type { CoursewareRecord } from '@/lib/courseware-repo/types';
|
|
|
|
/**
|
|
* Standalone coursewares become visible when their registry status is
|
|
* published. Large-course-owned versions additionally require an immutable
|
|
* schema-v2 manifest that contains this exact id/version/hash pin.
|
|
*/
|
|
export async function isCoursewareLearnerVisible(
|
|
record: CoursewareRecord,
|
|
manifests: CourseManifestRepo = courseManifestRepo,
|
|
): Promise<boolean> {
|
|
if (record.status !== 'published' || !record.complete) return false;
|
|
|
|
const courseId = record.courseId;
|
|
const moduleIndex = record.courseModuleIndex;
|
|
if (courseId === undefined && moduleIndex === undefined) return true;
|
|
if (
|
|
typeof courseId !== 'string' ||
|
|
!/^[a-zA-Z0-9_-]+$/.test(courseId) ||
|
|
!Number.isInteger(moduleIndex) ||
|
|
(moduleIndex ?? 0) < 1
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
return await manifests.hasExactPin(courseId, {
|
|
index: moduleIndex as number,
|
|
coursewareId: record.coursewareId,
|
|
coursewareVersion: record.version,
|
|
contentHash: record.contentHash,
|
|
});
|
|
} catch {
|
|
// Manifest state is an authorization/visibility boundary. Corrupt or
|
|
// unavailable state must hide the course-scoped bundle.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export async function filterLearnerVisibleCoursewares(
|
|
records: readonly CoursewareRecord[],
|
|
manifests: CourseManifestRepo = courseManifestRepo,
|
|
): Promise<CoursewareRecord[]> {
|
|
const visibility = await Promise.all(
|
|
records.map((record) => isCoursewareLearnerVisible(record, manifests)),
|
|
);
|
|
return records.filter((_record, index) => visibility[index]);
|
|
}
|