Files
openmaic/OpenMAIC/lib/courseware-repo/learner-visibility.ts
2026-08-16 14:58:47 +08:00

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]);
}