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 { 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 { const visibility = await Promise.all( records.map((record) => isCoursewareLearnerVisible(record, manifests)), ); return records.filter((_record, index) => visibility[index]); }