@hed-hog/lms 0.0.358 → 0.0.364
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bitcode-wallet/bitcode-wallet.service.d.ts +65 -0
- package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -1
- package/dist/bitcode-wallet/bitcode-wallet.service.js +72 -0
- package/dist/bitcode-wallet/bitcode-wallet.service.js.map +1 -1
- package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.d.ts +8 -0
- package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.d.ts.map +1 -0
- package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.js +40 -0
- package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.js.map +1 -0
- package/dist/class-group/class-group.controller.d.ts +16 -16
- package/dist/class-group/class-group.service.d.ts +12 -12
- package/dist/course/course-audio-transcription.service.d.ts +3 -2
- package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
- package/dist/course/course-audio-transcription.service.js +49 -8
- package/dist/course/course-audio-transcription.service.js.map +1 -1
- package/dist/course/course-lesson.controller.d.ts +4 -0
- package/dist/course/course-lesson.controller.d.ts.map +1 -1
- package/dist/course/course-lesson.controller.js +10 -0
- package/dist/course/course-lesson.controller.js.map +1 -1
- package/dist/course/course-structure.controller.d.ts +11 -4
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.controller.js +14 -0
- package/dist/course/course-structure.controller.js.map +1 -1
- package/dist/course/course-structure.service.d.ts +15 -1
- package/dist/course/course-structure.service.d.ts.map +1 -1
- package/dist/course/course-structure.service.js +139 -4
- package/dist/course/course-structure.service.js.map +1 -1
- package/dist/course/course-video-conversion.service.d.ts +8 -0
- package/dist/course/course-video-conversion.service.d.ts.map +1 -1
- package/dist/course/course-video-conversion.service.js +87 -51
- package/dist/course/course-video-conversion.service.js.map +1 -1
- package/dist/course/course.controller.d.ts +73 -1
- package/dist/course/course.controller.d.ts.map +1 -1
- package/dist/course/course.controller.js +27 -3
- package/dist/course/course.controller.js.map +1 -1
- package/dist/course/course.module.d.ts.map +1 -1
- package/dist/course/course.module.js +15 -3
- package/dist/course/course.module.js.map +1 -1
- package/dist/course/course.service.d.ts +108 -4
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +631 -30
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/dto/cleanup-course-storage.dto.d.ts +6 -0
- package/dist/course/dto/cleanup-course-storage.dto.d.ts.map +1 -0
- package/dist/course/dto/cleanup-course-storage.dto.js +33 -0
- package/dist/course/dto/cleanup-course-storage.dto.js.map +1 -0
- package/dist/course/dto/cleanup-upload-history.dto.d.ts +9 -0
- package/dist/course/dto/cleanup-upload-history.dto.d.ts.map +1 -0
- package/dist/course/dto/cleanup-upload-history.dto.js +36 -0
- package/dist/course/dto/cleanup-upload-history.dto.js.map +1 -0
- package/dist/course/dto/create-course-bulk-job.dto.d.ts +4 -0
- package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -0
- package/dist/course/dto/create-course-bulk-job.dto.js +21 -0
- package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -0
- package/dist/course/lms-bulk-upload-automation.service.d.ts +39 -0
- package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -0
- package/dist/course/lms-bulk-upload-automation.service.js +443 -0
- package/dist/course/lms-bulk-upload-automation.service.js.map +1 -0
- package/dist/course/lms-bulk-upload-infra.service.d.ts +31 -0
- package/dist/course/lms-bulk-upload-infra.service.d.ts.map +1 -0
- package/dist/course/lms-bulk-upload-infra.service.js +277 -0
- package/dist/course/lms-bulk-upload-infra.service.js.map +1 -0
- package/dist/course/lms-bulk-upload.constants.d.ts +4 -0
- package/dist/course/lms-bulk-upload.constants.d.ts.map +1 -0
- package/dist/course/lms-bulk-upload.constants.js +7 -0
- package/dist/course/lms-bulk-upload.constants.js.map +1 -0
- package/dist/course/lms-bulk-upload.controller.d.ts +153 -0
- package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -0
- package/dist/course/lms-bulk-upload.controller.js +129 -0
- package/dist/course/lms-bulk-upload.controller.js.map +1 -0
- package/dist/course/lms-bulk-upload.service.d.ts +181 -0
- package/dist/course/lms-bulk-upload.service.d.ts.map +1 -0
- package/dist/course/lms-bulk-upload.service.js +754 -0
- package/dist/course/lms-bulk-upload.service.js.map +1 -0
- package/dist/course/lms-setting.controller.d.ts +7 -1
- package/dist/course/lms-setting.controller.d.ts.map +1 -1
- package/dist/course/lms-setting.controller.js +26 -3
- package/dist/course/lms-setting.controller.js.map +1 -1
- package/dist/enterprise/enterprise.controller.d.ts +20 -20
- package/dist/enterprise/enterprise.service.d.ts +20 -20
- package/dist/enterprise/training/training-admin.controller.d.ts +11 -11
- package/dist/enterprise/training/training-admin.service.d.ts +11 -11
- package/dist/enterprise/training/training-instructor.controller.d.ts +2 -2
- package/dist/enterprise/training/training-instructor.service.d.ts +2 -2
- package/dist/enterprise/training/training-student.controller.d.ts +1 -1
- package/dist/enterprise/training/training-student.service.d.ts +1 -1
- package/dist/enterprise/training/training-viewer.controller.d.ts +2 -2
- package/dist/evaluation/evaluation.controller.d.ts +8 -8
- package/dist/evaluation/evaluation.service.d.ts +8 -8
- package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts +6 -0
- package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts.map +1 -0
- package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js +34 -0
- package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js.map +1 -0
- package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts +28 -0
- package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts.map +1 -0
- package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js +123 -0
- package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js.map +1 -0
- package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts +4 -0
- package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts.map +1 -0
- package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js +22 -0
- package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js.map +1 -0
- package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts +10 -0
- package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts.map +1 -0
- package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js +52 -0
- package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js.map +1 -0
- package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts +15 -0
- package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts.map +1 -0
- package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js +86 -0
- package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +26 -0
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +304 -0
- package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts +87 -0
- package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-map.controller.js +185 -0
- package/dist/lesson-xp-map/lesson-xp-map.controller.js.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-map.module.d.ts +3 -0
- package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-map.module.js +34 -0
- package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-map.service.d.ts +84 -0
- package/dist/lesson-xp-map/lesson-xp-map.service.d.ts.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-map.service.js +353 -0
- package/dist/lesson-xp-map/lesson-xp-map.service.js.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts +10 -0
- package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-segment.controller.js +63 -0
- package/dist/lesson-xp-map/lesson-xp-segment.controller.js.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts +27 -0
- package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts.map +1 -0
- package/dist/lesson-xp-map/lesson-xp-segment.service.js +194 -0
- package/dist/lesson-xp-map/lesson-xp-segment.service.js.map +1 -0
- package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -0
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js +17 -2
- package/dist/lms.module.js.map +1 -1
- package/dist/platforma/dto/update-profile.dto.d.ts +17 -0
- package/dist/platforma/dto/update-profile.dto.d.ts.map +1 -0
- package/dist/platforma/dto/update-profile.dto.js +87 -0
- package/dist/platforma/dto/update-profile.dto.js.map +1 -0
- package/dist/platforma/platforma.controller.d.ts +94 -7
- package/dist/platforma/platforma.controller.d.ts.map +1 -1
- package/dist/platforma/platforma.controller.js +85 -2
- package/dist/platforma/platforma.controller.js.map +1 -1
- package/dist/platforma/platforma.service.d.ts +27 -0
- package/dist/platforma/platforma.service.d.ts.map +1 -0
- package/dist/platforma/platforma.service.js +274 -0
- package/dist/platforma/platforma.service.js.map +1 -0
- package/dist/student-xp/student-xp.controller.d.ts +41 -0
- package/dist/student-xp/student-xp.controller.d.ts.map +1 -0
- package/dist/student-xp/student-xp.controller.js +114 -0
- package/dist/student-xp/student-xp.controller.js.map +1 -0
- package/dist/student-xp/student-xp.module.d.ts +3 -0
- package/dist/student-xp/student-xp.module.d.ts.map +1 -0
- package/dist/student-xp/student-xp.module.js +25 -0
- package/dist/student-xp/student-xp.module.js.map +1 -0
- package/dist/student-xp/student-xp.service.d.ts +65 -0
- package/dist/student-xp/student-xp.service.d.ts.map +1 -0
- package/dist/student-xp/student-xp.service.js +197 -0
- package/dist/student-xp/student-xp.service.js.map +1 -0
- package/dist/xp-catalog/dto/create-xp-area.dto.d.ts +12 -0
- package/dist/xp-catalog/dto/create-xp-area.dto.d.ts.map +1 -0
- package/dist/xp-catalog/dto/create-xp-area.dto.js +63 -0
- package/dist/xp-catalog/dto/create-xp-area.dto.js.map +1 -0
- package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts +11 -0
- package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts.map +1 -0
- package/dist/xp-catalog/dto/create-xp-learning-type.dto.js +57 -0
- package/dist/xp-catalog/dto/create-xp-learning-type.dto.js.map +1 -0
- package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts +11 -0
- package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts.map +1 -0
- package/dist/xp-catalog/dto/create-xp-skill.dto.js +57 -0
- package/dist/xp-catalog/dto/create-xp-skill.dto.js.map +1 -0
- package/dist/xp-catalog/dto/update-xp-area.dto.d.ts +12 -0
- package/dist/xp-catalog/dto/update-xp-area.dto.d.ts.map +1 -0
- package/dist/xp-catalog/dto/update-xp-area.dto.js +66 -0
- package/dist/xp-catalog/dto/update-xp-area.dto.js.map +1 -0
- package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts +11 -0
- package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts.map +1 -0
- package/dist/xp-catalog/dto/update-xp-learning-type.dto.js +60 -0
- package/dist/xp-catalog/dto/update-xp-learning-type.dto.js.map +1 -0
- package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts +11 -0
- package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts.map +1 -0
- package/dist/xp-catalog/dto/update-xp-skill.dto.js +60 -0
- package/dist/xp-catalog/dto/update-xp-skill.dto.js.map +1 -0
- package/dist/xp-catalog/xp-area.controller.d.ts +25 -0
- package/dist/xp-catalog/xp-area.controller.d.ts.map +1 -0
- package/dist/xp-catalog/xp-area.controller.js +105 -0
- package/dist/xp-catalog/xp-area.controller.js.map +1 -0
- package/dist/xp-catalog/xp-area.service.d.ts +35 -0
- package/dist/xp-catalog/xp-area.service.d.ts.map +1 -0
- package/dist/xp-catalog/xp-area.service.js +168 -0
- package/dist/xp-catalog/xp-area.service.js.map +1 -0
- package/dist/xp-catalog/xp-catalog.module.d.ts +3 -0
- package/dist/xp-catalog/xp-catalog.module.d.ts.map +1 -0
- package/dist/xp-catalog/xp-catalog.module.js +29 -0
- package/dist/xp-catalog/xp-catalog.module.js.map +1 -0
- package/dist/xp-catalog/xp-learning-type.controller.d.ts +20 -0
- package/dist/xp-catalog/xp-learning-type.controller.d.ts.map +1 -0
- package/dist/xp-catalog/xp-learning-type.controller.js +96 -0
- package/dist/xp-catalog/xp-learning-type.controller.js.map +1 -0
- package/dist/xp-catalog/xp-learning-type.service.d.ts +30 -0
- package/dist/xp-catalog/xp-learning-type.service.d.ts.map +1 -0
- package/dist/xp-catalog/xp-learning-type.service.js +146 -0
- package/dist/xp-catalog/xp-learning-type.service.js.map +1 -0
- package/dist/xp-catalog/xp-skill.controller.d.ts +26 -0
- package/dist/xp-catalog/xp-skill.controller.d.ts.map +1 -0
- package/dist/xp-catalog/xp-skill.controller.js +113 -0
- package/dist/xp-catalog/xp-skill.controller.js.map +1 -0
- package/dist/xp-catalog/xp-skill.service.d.ts +37 -0
- package/dist/xp-catalog/xp-skill.service.d.ts.map +1 -0
- package/dist/xp-catalog/xp-skill.service.js +174 -0
- package/dist/xp-catalog/xp-skill.service.js.map +1 -0
- package/hedhog/data/menu.yaml +101 -0
- package/hedhog/data/role.yaml +8 -0
- package/hedhog/data/route.yaml +547 -0
- package/hedhog/data/setting_group.yaml +33 -0
- package/hedhog/data/xp_area.yaml +164 -0
- package/hedhog/data/xp_learning_type.yaml +131 -0
- package/hedhog/data/xp_skill.yaml +1834 -0
- package/hedhog/frontend/app/achievements/page.tsx.ejs +108 -118
- package/hedhog/frontend/app/bitcodes/page.tsx.ejs +22 -34
- package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +1453 -0
- package/hedhog/frontend/app/certificates/models/page.tsx.ejs +21 -45
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +40 -74
- package/hedhog/frontend/app/classes/page.tsx.ejs +56 -85
- package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +3 -2
- package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +48 -5
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +4 -4
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +19 -2
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +1170 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +16 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +2 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +623 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +1458 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +55 -2
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +442 -104
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +296 -49
- package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +3 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +1 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +101 -85
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +21 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +3 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/use-tree-display-settings.ts.ejs +7 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +44 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +54 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +52 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-xp-overview.ts.ejs +76 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-xp-map.ts.ejs +128 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +30 -0
- package/hedhog/frontend/app/courses/[id]/structure/_utils/xp-color-config.ts.ejs +115 -0
- package/hedhog/frontend/app/courses/_components/CourseDeleteDialog.tsx.ejs +223 -0
- package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +89 -0
- package/hedhog/frontend/app/courses/page.tsx.ejs +400 -230
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -63
- package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +53 -77
- package/hedhog/frontend/app/exams/page.tsx.ejs +54 -90
- package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +23 -36
- package/hedhog/frontend/app/instructors/page.tsx.ejs +72 -81
- package/hedhog/frontend/app/paths/page.tsx.ejs +40 -68
- package/hedhog/frontend/app/training/page.tsx.ejs +40 -68
- package/hedhog/frontend/app/xp/areas/page.tsx.ejs +782 -0
- package/hedhog/frontend/app/xp/learning-types/page.tsx.ejs +690 -0
- package/hedhog/frontend/app/xp/skills/page.tsx.ejs +811 -0
- package/hedhog/frontend/messages/en.json +386 -3
- package/hedhog/frontend/messages/pt.json +386 -3
- package/hedhog/table/lesson_xp_map.yaml +50 -0
- package/hedhog/table/lesson_xp_segment.yaml +40 -0
- package/hedhog/table/lesson_xp_segment_area.yaml +24 -0
- package/hedhog/table/lesson_xp_segment_learning_type.yaml +24 -0
- package/hedhog/table/lesson_xp_segment_skill.yaml +24 -0
- package/hedhog/table/lms_bulk_upload_item.yaml +44 -0
- package/hedhog/table/lms_bulk_upload_session.yaml +42 -0
- package/hedhog/table/student_area_xp.yaml +30 -0
- package/hedhog/table/student_learning_type_xp.yaml +30 -0
- package/hedhog/table/student_skill_xp.yaml +30 -0
- package/hedhog/table/student_xp_event.yaml +34 -0
- package/hedhog/table/xp_area.yaml +39 -0
- package/hedhog/table/xp_learning_type.yaml +34 -0
- package/hedhog/table/xp_skill.yaml +39 -0
- package/package.json +9 -8
- package/src/bitcode-wallet/bitcode-wallet.service.ts +113 -0
- package/src/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.ts +32 -0
- package/src/course/course-audio-transcription.service.ts +58 -21
- package/src/course/course-lesson.controller.ts +6 -1
- package/src/course/course-structure.controller.ts +10 -0
- package/src/course/course-structure.service.ts +174 -1
- package/src/course/course-video-conversion.service.ts +113 -75
- package/src/course/course.controller.ts +22 -3
- package/src/course/course.module.ts +15 -3
- package/src/course/course.service.ts +847 -30
- package/src/course/dto/cleanup-course-storage.dto.ts +22 -0
- package/src/course/dto/cleanup-upload-history.dto.ts +26 -0
- package/src/course/dto/create-course-bulk-job.dto.ts +6 -0
- package/src/course/lms-bulk-upload-automation.service.ts +560 -0
- package/src/course/lms-bulk-upload-infra.service.ts +327 -0
- package/src/course/lms-bulk-upload.constants.ts +5 -0
- package/src/course/lms-bulk-upload.controller.ts +103 -0
- package/src/course/lms-bulk-upload.service.ts +1029 -0
- package/src/course/lms-setting.controller.ts +28 -1
- package/src/lesson-xp-map/dto/create-lesson-xp-map.dto.ts +17 -0
- package/src/lesson-xp-map/dto/create-lesson-xp-segment.dto.ts +102 -0
- package/src/lesson-xp-map/dto/review-lesson-xp-map.dto.ts +7 -0
- package/src/lesson-xp-map/dto/update-lesson-xp-map.dto.ts +36 -0
- package/src/lesson-xp-map/dto/update-lesson-xp-segment.dto.ts +78 -0
- package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +396 -0
- package/src/lesson-xp-map/lesson-xp-map.controller.ts +116 -0
- package/src/lesson-xp-map/lesson-xp-map.module.ts +21 -0
- package/src/lesson-xp-map/lesson-xp-map.service.ts +442 -0
- package/src/lesson-xp-map/lesson-xp-segment.controller.ts +36 -0
- package/src/lesson-xp-map/lesson-xp-segment.service.ts +229 -0
- package/src/lms.module.ts +17 -2
- package/src/platforma/dto/update-profile.dto.ts +59 -0
- package/src/platforma/platforma.controller.ts +57 -2
- package/src/platforma/platforma.service.ts +268 -0
- package/src/student-xp/student-xp.controller.ts +76 -0
- package/src/student-xp/student-xp.module.ts +12 -0
- package/src/student-xp/student-xp.service.ts +236 -0
- package/src/xp-catalog/dto/create-xp-area.dto.ts +40 -0
- package/src/xp-catalog/dto/create-xp-learning-type.dto.ts +35 -0
- package/src/xp-catalog/dto/create-xp-skill.dto.ts +35 -0
- package/src/xp-catalog/dto/update-xp-area.dto.ts +43 -0
- package/src/xp-catalog/dto/update-xp-learning-type.dto.ts +38 -0
- package/src/xp-catalog/dto/update-xp-skill.dto.ts +38 -0
- package/src/xp-catalog/xp-area.controller.ts +64 -0
- package/src/xp-catalog/xp-area.service.ts +196 -0
- package/src/xp-catalog/xp-catalog.module.ts +16 -0
- package/src/xp-catalog/xp-learning-type.controller.ts +59 -0
- package/src/xp-catalog/xp-learning-type.service.ts +170 -0
- package/src/xp-catalog/xp-skill.controller.ts +71 -0
- package/src/xp-catalog/xp-skill.service.ts +205 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
2
2
|
import { FileService } from '@hed-hog/core';
|
|
3
|
-
import {
|
|
3
|
+
import { QueueJobService } from '@hed-hog/queue';
|
|
4
|
+
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
|
4
5
|
import { InstructorService } from '../instructor/instructor.service';
|
|
5
6
|
import { CourseOperationsIntegrationService } from './course-operations-integration.service';
|
|
6
7
|
import { CreateCourseLessonFrameDto } from './dto/create-course-lesson-frame.dto';
|
|
@@ -33,11 +34,14 @@ type LessonFileType =
|
|
|
33
34
|
|
|
34
35
|
@Injectable()
|
|
35
36
|
export class CourseStructureService {
|
|
37
|
+
private readonly logger = new Logger(CourseStructureService.name);
|
|
38
|
+
|
|
36
39
|
constructor(
|
|
37
40
|
private readonly prisma: PrismaService,
|
|
38
41
|
private readonly fileService: FileService,
|
|
39
42
|
private readonly instructorService: InstructorService,
|
|
40
43
|
private readonly operationsIntegration: CourseOperationsIntegrationService,
|
|
44
|
+
private readonly queueJob: QueueJobService,
|
|
41
45
|
) {}
|
|
42
46
|
|
|
43
47
|
async getStructure(courseId: number) {
|
|
@@ -1151,6 +1155,175 @@ export class CourseStructureService {
|
|
|
1151
1155
|
});
|
|
1152
1156
|
}
|
|
1153
1157
|
|
|
1158
|
+
async startTranscription(lessonId: number) {
|
|
1159
|
+
const lesson = await (this.prisma as any).course_lesson.findUnique({
|
|
1160
|
+
where: { id: lessonId },
|
|
1161
|
+
select: { id: true, course_module: { select: { course_id: true } } },
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
if (!lesson) {
|
|
1165
|
+
throw new NotFoundException(`Lesson ${lessonId} not found`);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const audioFiles = await this.getAudioFiles(lessonId);
|
|
1169
|
+
|
|
1170
|
+
if (!audioFiles.length) {
|
|
1171
|
+
throw new BadRequestException('No audio file found for this lesson. Upload or convert a video first.');
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const audioFileId = audioFiles[audioFiles.length - 1].file_id;
|
|
1175
|
+
|
|
1176
|
+
const job = await this.queueJob.enqueue({
|
|
1177
|
+
type: 'lms.audio.transcribe',
|
|
1178
|
+
queueName: 'lms.audio.transcribe',
|
|
1179
|
+
payload: {
|
|
1180
|
+
courseId: lesson.course_module.course_id,
|
|
1181
|
+
lessonId,
|
|
1182
|
+
audioFileId,
|
|
1183
|
+
},
|
|
1184
|
+
sourceModule: 'lms',
|
|
1185
|
+
sourceEntity: 'course_lesson',
|
|
1186
|
+
sourceEntityId: String(lessonId),
|
|
1187
|
+
maxAttempts: 3,
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
const startedOnDemand = await this.startTranscriptionOnDemandIfNeeded(job.id);
|
|
1191
|
+
|
|
1192
|
+
if (startedOnDemand) {
|
|
1193
|
+
return { queueJobId: job.id, status: 'processing' };
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
return { queueJobId: job.id, status: job.status };
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
private async startTranscriptionOnDemandIfNeeded(jobId: number): Promise<boolean> {
|
|
1200
|
+
const freshWorkerCutoff = new Date(Date.now() - 2 * 60 * 1000);
|
|
1201
|
+
|
|
1202
|
+
const onlineWorkers = await (this.prisma as any).queue_worker_heartbeat.count({
|
|
1203
|
+
where: {
|
|
1204
|
+
status: 'online',
|
|
1205
|
+
last_heartbeat_at: { gte: freshWorkerCutoff },
|
|
1206
|
+
},
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
if (onlineWorkers > 0) {
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
void this.queueJob
|
|
1214
|
+
.execute(jobId, undefined, 15 * 60 * 1000)
|
|
1215
|
+
.then(() => {
|
|
1216
|
+
this.logger.debug(
|
|
1217
|
+
`Started on-demand transcription execution for queue job ${jobId} because no online workers were found.`,
|
|
1218
|
+
);
|
|
1219
|
+
})
|
|
1220
|
+
.catch((error: unknown) => {
|
|
1221
|
+
this.logger.warn(
|
|
1222
|
+
`Failed to execute transcription job ${jobId} on-demand fallback: ${error instanceof Error ? error.message : String(error)}`,
|
|
1223
|
+
);
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
return true;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
async bulkEnqueueJobs(
|
|
1230
|
+
courseId: number,
|
|
1231
|
+
jobType: 'transcription' | 'xp_recalculation',
|
|
1232
|
+
userId: number,
|
|
1233
|
+
): Promise<{
|
|
1234
|
+
queued: number;
|
|
1235
|
+
skipped: number;
|
|
1236
|
+
skippedWithoutTranscription: number;
|
|
1237
|
+
warnings: string[];
|
|
1238
|
+
}> {
|
|
1239
|
+
const lessons = await (this.prisma as any).course_lesson.findMany({
|
|
1240
|
+
where: {
|
|
1241
|
+
course_module: { course_id: courseId },
|
|
1242
|
+
},
|
|
1243
|
+
select: { id: true },
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
let queued = 0;
|
|
1247
|
+
let skipped = 0;
|
|
1248
|
+
let skippedWithoutTranscription = 0;
|
|
1249
|
+
const warnings: string[] = [];
|
|
1250
|
+
|
|
1251
|
+
if (jobType === 'transcription') {
|
|
1252
|
+
for (const lesson of lessons) {
|
|
1253
|
+
const audioFiles = await (this.prisma as any).course_lesson_file.findMany({
|
|
1254
|
+
where: { course_lesson_id: lesson.id, type: 'lesson_audio' },
|
|
1255
|
+
select: { file_id: true },
|
|
1256
|
+
orderBy: { created_at: 'asc' },
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
if (!audioFiles.length) {
|
|
1260
|
+
skipped++;
|
|
1261
|
+
continue;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
const audioFileId = audioFiles[audioFiles.length - 1].file_id;
|
|
1265
|
+
await this.queueJob.enqueue({
|
|
1266
|
+
type: 'lms.audio.transcribe',
|
|
1267
|
+
queueName: 'lms.audio.transcribe',
|
|
1268
|
+
payload: { courseId, lessonId: lesson.id, audioFileId },
|
|
1269
|
+
sourceModule: 'lms',
|
|
1270
|
+
sourceEntity: 'course_lesson',
|
|
1271
|
+
sourceEntityId: String(lesson.id),
|
|
1272
|
+
maxAttempts: 3,
|
|
1273
|
+
});
|
|
1274
|
+
queued++;
|
|
1275
|
+
}
|
|
1276
|
+
} else {
|
|
1277
|
+
for (const lesson of lessons) {
|
|
1278
|
+
const segments = await (this.prisma as any).course_lesson_transcription_segment.findFirst({
|
|
1279
|
+
where: { course_lesson_id: lesson.id },
|
|
1280
|
+
select: { id: true },
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
if (!segments) {
|
|
1284
|
+
skippedWithoutTranscription++;
|
|
1285
|
+
skipped++;
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
let xpMap = await (this.prisma as any).lesson_xp_map.findFirst({
|
|
1290
|
+
where: { course_lesson_id: lesson.id },
|
|
1291
|
+
select: { id: true },
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
if (!xpMap) {
|
|
1295
|
+
const created = await (this.prisma as any).lesson_xp_map.create({
|
|
1296
|
+
data: {
|
|
1297
|
+
course_lesson_id: lesson.id,
|
|
1298
|
+
status: 'pending',
|
|
1299
|
+
},
|
|
1300
|
+
select: { id: true },
|
|
1301
|
+
});
|
|
1302
|
+
xpMap = created;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
await this.queueJob.enqueue({
|
|
1306
|
+
type: 'lms.lesson.xp.calculate',
|
|
1307
|
+
queueName: 'lms.lesson.xp.calculate',
|
|
1308
|
+
payload: { lessonId: lesson.id, mapId: xpMap.id, userId },
|
|
1309
|
+
sourceModule: 'lms',
|
|
1310
|
+
sourceEntity: 'course_lesson',
|
|
1311
|
+
sourceEntityId: String(lesson.id),
|
|
1312
|
+
maxAttempts: 3,
|
|
1313
|
+
});
|
|
1314
|
+
queued++;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
if (skippedWithoutTranscription > 0) {
|
|
1318
|
+
warnings.push(
|
|
1319
|
+
`${skippedWithoutTranscription} aula(s) sem transcrição foram ignoradas. Gere a transcrição antes de recalcular o XP.`,
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
return { queued, skipped, skippedWithoutTranscription, warnings };
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1154
1327
|
private async syncLessonRelations(
|
|
1155
1328
|
lessonId: number,
|
|
1156
1329
|
dto: Partial<
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { execFile } from 'child_process';
|
|
14
14
|
import { existsSync, promises as fs, readdirSync } from 'fs';
|
|
15
15
|
import { tmpdir } from 'os';
|
|
16
|
-
import { basename, join } from 'path';
|
|
16
|
+
import { basename, extname, join } from 'path';
|
|
17
17
|
import { promisify } from 'util';
|
|
18
18
|
|
|
19
19
|
export const LMS_VIDEO_CONVERSION_JOB = 'lms.video.convert';
|
|
@@ -32,6 +32,20 @@ type LessonFrameResult = {
|
|
|
32
32
|
|
|
33
33
|
const execFileAsync = promisify(execFile);
|
|
34
34
|
|
|
35
|
+
const VIDEO_FILE_EXTENSIONS = new Set([
|
|
36
|
+
'.mp4',
|
|
37
|
+
'.m4v',
|
|
38
|
+
'.mov',
|
|
39
|
+
'.mkv',
|
|
40
|
+
'.webm',
|
|
41
|
+
'.avi',
|
|
42
|
+
'.wmv',
|
|
43
|
+
'.flv',
|
|
44
|
+
'.ogv',
|
|
45
|
+
'.m2ts',
|
|
46
|
+
'.ts',
|
|
47
|
+
]);
|
|
48
|
+
|
|
35
49
|
type NotificationContext = {
|
|
36
50
|
userId: number;
|
|
37
51
|
notificationId: number;
|
|
@@ -187,7 +201,7 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
187
201
|
}
|
|
188
202
|
|
|
189
203
|
const mimetype = original.file_mimetype?.name ?? '';
|
|
190
|
-
if (!mimetype.
|
|
204
|
+
if (!this.isVideoOriginalFile(mimetype, original.filename, original.path)) {
|
|
191
205
|
throw new BadRequestException('Original file must be a video');
|
|
192
206
|
}
|
|
193
207
|
|
|
@@ -257,7 +271,14 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
257
271
|
|
|
258
272
|
async handle(job: {
|
|
259
273
|
id: number;
|
|
274
|
+
type: string;
|
|
275
|
+
queue_name: string;
|
|
260
276
|
payload: Record<string, any>;
|
|
277
|
+
attempts: number;
|
|
278
|
+
max_attempts: number;
|
|
279
|
+
source_module?: string | null;
|
|
280
|
+
source_entity?: string | null;
|
|
281
|
+
source_entity_id?: string | null;
|
|
261
282
|
}): Promise<any> {
|
|
262
283
|
const { courseId, sessionId, lessonId, originalFileId, profiles, overwrite } =
|
|
263
284
|
job.payload as {
|
|
@@ -299,6 +320,14 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
299
320
|
throw new Error('Course has no active profiles to process');
|
|
300
321
|
}
|
|
301
322
|
|
|
323
|
+
const workerConcurrency =
|
|
324
|
+
parseInt(process.env.QUEUE_WORKER_CONCURRENCY ?? '1', 10) || 1;
|
|
325
|
+
const queueDef = await (this.prisma as any).queue_definition.findUnique({
|
|
326
|
+
where: { name: LMS_VIDEO_CONVERSION_JOB },
|
|
327
|
+
select: { max_concurrency: true },
|
|
328
|
+
});
|
|
329
|
+
const queueMaxConcurrency: number | null = queueDef?.max_concurrency ?? null;
|
|
330
|
+
|
|
302
331
|
const maxInputBytes = this.getPositiveIntegerEnv('LMS_VIDEO_MAX_INPUT_BYTES');
|
|
303
332
|
const ffmpegTimeoutMs =
|
|
304
333
|
this.getPositiveIntegerEnv('LMS_VIDEO_FFMPEG_TIMEOUT_MS') ?? 1000 * 60 * 60;
|
|
@@ -333,12 +362,17 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
333
362
|
maxInputBytes ? { maxBytes: maxInputBytes } : undefined,
|
|
334
363
|
);
|
|
335
364
|
|
|
365
|
+
const inputSizeBytes = downloadedOriginal.size;
|
|
366
|
+
const inputSizeMb = parseFloat((inputSizeBytes / (1024 * 1024)).toFixed(2));
|
|
367
|
+
|
|
336
368
|
this.logger.log(
|
|
337
|
-
`Queue job ${job.id}: original file downloaded (lesson=${lessonId}, size=${
|
|
369
|
+
`Queue job ${job.id}: original file downloaded (lesson=${lessonId}, size=${inputSizeBytes})`,
|
|
338
370
|
);
|
|
339
|
-
await emitProgress(
|
|
371
|
+
await emitProgress(`Vídeo original baixado (${inputSizeMb} MB). Lendo duração...`, {
|
|
340
372
|
phase: 'probe_duration',
|
|
341
373
|
lessonId,
|
|
374
|
+
inputSizeBytes,
|
|
375
|
+
inputSizeMb,
|
|
342
376
|
});
|
|
343
377
|
|
|
344
378
|
let probedDuration: number | null = null;
|
|
@@ -368,10 +402,65 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
368
402
|
uploadMBps: number;
|
|
369
403
|
totalMs: number;
|
|
370
404
|
}> = [];
|
|
405
|
+
|
|
406
|
+
const buildMetrics = () => ({
|
|
407
|
+
totalMs: Date.now() - jobStartedAt,
|
|
408
|
+
totalOutputBytes: results.reduce((acc, item) => acc + item.outputBytes, 0),
|
|
409
|
+
inputSizeBytes,
|
|
410
|
+
inputSizeMb,
|
|
411
|
+
averageProfileMs:
|
|
412
|
+
results.length > 0
|
|
413
|
+
? Math.round(results.reduce((acc, item) => acc + item.totalMs, 0) / results.length)
|
|
414
|
+
: 0,
|
|
415
|
+
averageConversionMBps:
|
|
416
|
+
results.length > 0
|
|
417
|
+
? Number(
|
|
418
|
+
(results.reduce((acc, item) => acc + item.conversionMBps, 0) / results.length).toFixed(2),
|
|
419
|
+
)
|
|
420
|
+
: 0,
|
|
421
|
+
averageUploadMBps:
|
|
422
|
+
results.length > 0
|
|
423
|
+
? Number(
|
|
424
|
+
(results.reduce((acc, item) => acc + item.uploadMBps, 0) / results.length).toFixed(2),
|
|
425
|
+
)
|
|
426
|
+
: 0,
|
|
427
|
+
workerConcurrency,
|
|
428
|
+
queueMaxConcurrency,
|
|
429
|
+
});
|
|
371
430
|
let frameResults: LessonFrameResult[] = [];
|
|
372
431
|
|
|
432
|
+
// On retries (attempts > 1), skip profiles that were already successfully
|
|
433
|
+
// converted and uploaded in a previous attempt of this same job.
|
|
434
|
+
const alreadyDoneProfileIds = new Set<number>();
|
|
435
|
+
if (job.attempts > 1) {
|
|
436
|
+
const existingFiles = await (this.prisma as any).course_lesson_file.findMany({
|
|
437
|
+
where: {
|
|
438
|
+
course_lesson_id: lessonId,
|
|
439
|
+
type: { startsWith: 'video_profile:' },
|
|
440
|
+
},
|
|
441
|
+
select: { type: true },
|
|
442
|
+
});
|
|
443
|
+
for (const f of existingFiles as Array<{ type: string }>) {
|
|
444
|
+
const profileId = parseInt(f.type.split(':')[1], 10);
|
|
445
|
+
if (!isNaN(profileId)) alreadyDoneProfileIds.add(profileId);
|
|
446
|
+
}
|
|
447
|
+
if (alreadyDoneProfileIds.size > 0) {
|
|
448
|
+
this.logger.log(
|
|
449
|
+
`Queue job ${job.id}: retry attempt ${job.attempts}, skipping already-converted profiles: [${[...alreadyDoneProfileIds].join(', ')}]`,
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
373
454
|
try {
|
|
374
455
|
for (const profile of profilesToProcess) {
|
|
456
|
+
if (alreadyDoneProfileIds.has(profile.id)) {
|
|
457
|
+
await emitProgress(
|
|
458
|
+
`Perfil ${profile.name || profile.id} já convertido em tentativa anterior, pulando.`,
|
|
459
|
+
{ phase: 'skip_profile', profileId: profile.id, profileName: profile.name },
|
|
460
|
+
);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
|
|
375
464
|
const profileStartedAt = Date.now();
|
|
376
465
|
const outputPath = join(
|
|
377
466
|
workDir,
|
|
@@ -381,7 +470,7 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
381
470
|
`Queue job ${job.id}: converting profile ${profile.id} for lesson ${lessonId}`,
|
|
382
471
|
);
|
|
383
472
|
await emitProgress(
|
|
384
|
-
`Convertendo vídeo para o perfil ${profile.name || profile.id}...`,
|
|
473
|
+
`Convertendo vídeo para o perfil ${profile.name || profile.id} (entrada: ${inputSizeMb} MB)...`,
|
|
385
474
|
{
|
|
386
475
|
phase: 'convert_profile',
|
|
387
476
|
lessonId,
|
|
@@ -389,6 +478,7 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
389
478
|
profileName: profile.name,
|
|
390
479
|
profileIndex: results.length + 1,
|
|
391
480
|
profileCount: profilesToProcess.length,
|
|
481
|
+
inputSizeMb,
|
|
392
482
|
},
|
|
393
483
|
);
|
|
394
484
|
this.logger.debug(
|
|
@@ -529,28 +619,7 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
529
619
|
extractedFrames: frameResults.length,
|
|
530
620
|
frames: frameResults,
|
|
531
621
|
transcriptionJobId: transcriptionJob.id,
|
|
532
|
-
metrics:
|
|
533
|
-
totalMs: Date.now() - jobStartedAt,
|
|
534
|
-
totalOutputBytes: results.reduce((acc, item) => acc + item.outputBytes, 0),
|
|
535
|
-
averageProfileMs:
|
|
536
|
-
results.length > 0
|
|
537
|
-
? Math.round(results.reduce((acc, item) => acc + item.totalMs, 0) / results.length)
|
|
538
|
-
: 0,
|
|
539
|
-
averageConversionMBps:
|
|
540
|
-
results.length > 0
|
|
541
|
-
? Number(
|
|
542
|
-
(
|
|
543
|
-
results.reduce((acc, item) => acc + item.conversionMBps, 0) / results.length
|
|
544
|
-
).toFixed(2),
|
|
545
|
-
)
|
|
546
|
-
: 0,
|
|
547
|
-
averageUploadMBps:
|
|
548
|
-
results.length > 0
|
|
549
|
-
? Number(
|
|
550
|
-
(results.reduce((acc, item) => acc + item.uploadMBps, 0) / results.length).toFixed(2),
|
|
551
|
-
)
|
|
552
|
-
: 0,
|
|
553
|
-
},
|
|
622
|
+
metrics: buildMetrics(),
|
|
554
623
|
};
|
|
555
624
|
} else {
|
|
556
625
|
let transcriptionSkipReason = 'audio extraction returned no file id';
|
|
@@ -617,28 +686,7 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
617
686
|
extractedFrames: frameResults.length,
|
|
618
687
|
frames: frameResults,
|
|
619
688
|
transcriptionJobId: transcriptionJob.id,
|
|
620
|
-
metrics:
|
|
621
|
-
totalMs: Date.now() - jobStartedAt,
|
|
622
|
-
totalOutputBytes: results.reduce((acc, item) => acc + item.outputBytes, 0),
|
|
623
|
-
averageProfileMs:
|
|
624
|
-
results.length > 0
|
|
625
|
-
? Math.round(results.reduce((acc, item) => acc + item.totalMs, 0) / results.length)
|
|
626
|
-
: 0,
|
|
627
|
-
averageConversionMBps:
|
|
628
|
-
results.length > 0
|
|
629
|
-
? Number(
|
|
630
|
-
(
|
|
631
|
-
results.reduce((acc, item) => acc + item.conversionMBps, 0) / results.length
|
|
632
|
-
).toFixed(2),
|
|
633
|
-
)
|
|
634
|
-
: 0,
|
|
635
|
-
averageUploadMBps:
|
|
636
|
-
results.length > 0
|
|
637
|
-
? Number(
|
|
638
|
-
(results.reduce((acc, item) => acc + item.uploadMBps, 0) / results.length).toFixed(2),
|
|
639
|
-
)
|
|
640
|
-
: 0,
|
|
641
|
-
},
|
|
689
|
+
metrics: buildMetrics(),
|
|
642
690
|
};
|
|
643
691
|
}
|
|
644
692
|
} catch (fallbackError) {
|
|
@@ -693,11 +741,8 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
693
741
|
this.logger.log(`Queue job ${job.id}: cleaned temporary folder ${workDir}`);
|
|
694
742
|
}
|
|
695
743
|
|
|
696
|
-
const totalMs = Date.now() - jobStartedAt;
|
|
697
|
-
const totalOutputBytes = results.reduce((acc, item) => acc + item.outputBytes, 0);
|
|
698
|
-
|
|
699
744
|
this.logger.log(
|
|
700
|
-
`Queue job ${job.id}: LMS conversion finished (lesson=${lessonId}, profiles=${results.length}
|
|
745
|
+
`Queue job ${job.id}: LMS conversion finished (lesson=${lessonId}, profiles=${results.length})`,
|
|
701
746
|
);
|
|
702
747
|
|
|
703
748
|
if (notificationContext) {
|
|
@@ -717,28 +762,7 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
717
762
|
extractedFrames: frameResults.length,
|
|
718
763
|
frames: frameResults,
|
|
719
764
|
transcriptionJobId: null,
|
|
720
|
-
metrics:
|
|
721
|
-
totalMs,
|
|
722
|
-
totalOutputBytes,
|
|
723
|
-
averageProfileMs:
|
|
724
|
-
results.length > 0
|
|
725
|
-
? Math.round(results.reduce((acc, item) => acc + item.totalMs, 0) / results.length)
|
|
726
|
-
: 0,
|
|
727
|
-
averageConversionMBps:
|
|
728
|
-
results.length > 0
|
|
729
|
-
? Number(
|
|
730
|
-
(
|
|
731
|
-
results.reduce((acc, item) => acc + item.conversionMBps, 0) / results.length
|
|
732
|
-
).toFixed(2),
|
|
733
|
-
)
|
|
734
|
-
: 0,
|
|
735
|
-
averageUploadMBps:
|
|
736
|
-
results.length > 0
|
|
737
|
-
? Number(
|
|
738
|
-
(results.reduce((acc, item) => acc + item.uploadMBps, 0) / results.length).toFixed(2),
|
|
739
|
-
)
|
|
740
|
-
: 0,
|
|
741
|
-
},
|
|
765
|
+
metrics: buildMetrics(),
|
|
742
766
|
};
|
|
743
767
|
}
|
|
744
768
|
|
|
@@ -1378,4 +1402,18 @@ export class CourseVideoConversionService implements OnModuleInit, IJobHandler {
|
|
|
1378
1402
|
return null;
|
|
1379
1403
|
}
|
|
1380
1404
|
}
|
|
1405
|
+
|
|
1406
|
+
private isVideoOriginalFile(
|
|
1407
|
+
mimetype?: string | null,
|
|
1408
|
+
filename?: string | null,
|
|
1409
|
+
path?: string | null,
|
|
1410
|
+
): boolean {
|
|
1411
|
+
if (typeof mimetype === 'string' && mimetype.startsWith('video/')) {
|
|
1412
|
+
return true;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
const candidateName = filename ?? path ?? '';
|
|
1416
|
+
const extension = extname(candidateName).toLowerCase();
|
|
1417
|
+
return VIDEO_FILE_EXTENSIONS.has(extension);
|
|
1418
|
+
}
|
|
1381
1419
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { Role } from '@hed-hog/api';
|
|
2
|
+
import { Role, User } from '@hed-hog/api';
|
|
3
3
|
import {
|
|
4
4
|
Body,
|
|
5
5
|
Controller,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from '@nestjs/common';
|
|
16
16
|
import { CourseService } from './course.service';
|
|
17
17
|
import { CreateCourseDto } from './dto/create-course.dto';
|
|
18
|
+
import { CleanupCourseStorageDto } from './dto/cleanup-course-storage.dto';
|
|
18
19
|
import { UpdateCourseDto } from './dto/update-course.dto';
|
|
19
20
|
|
|
20
21
|
@Role()
|
|
@@ -64,6 +65,24 @@ export class CourseController {
|
|
|
64
65
|
return this.courseService.stats();
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
@Get(':id/overview')
|
|
69
|
+
getCourseContentOverview(@Param('id', ParseIntPipe) id: number) {
|
|
70
|
+
return this.courseService.getCourseContentOverview(id);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Post(':id/storage/cleanup')
|
|
74
|
+
cleanupCourseStorageCategory(
|
|
75
|
+
@Param('id', ParseIntPipe) id: number,
|
|
76
|
+
@Body() body: CleanupCourseStorageDto,
|
|
77
|
+
@User() user: any,
|
|
78
|
+
) {
|
|
79
|
+
return this.courseService.enqueueStorageCategoryCleanup(
|
|
80
|
+
id,
|
|
81
|
+
body.category,
|
|
82
|
+
Number(user?.id || 0) || null,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
67
86
|
@Get(':id')
|
|
68
87
|
getById(@Param('id', ParseIntPipe) id: number) {
|
|
69
88
|
return this.courseService.getById(id);
|
|
@@ -83,8 +102,8 @@ export class CourseController {
|
|
|
83
102
|
}
|
|
84
103
|
|
|
85
104
|
@Delete(':id')
|
|
86
|
-
remove(@Param('id', ParseIntPipe) id: number) {
|
|
87
|
-
return this.courseService.remove(id);
|
|
105
|
+
remove(@Param('id', ParseIntPipe) id: number, @User() user: any) {
|
|
106
|
+
return this.courseService.remove(id, Number(user?.id || 0) || null);
|
|
88
107
|
}
|
|
89
108
|
|
|
90
109
|
@Get(':courseId/video-resolution-profiles')
|
|
@@ -4,16 +4,19 @@ import { QueueModule } from '@hed-hog/queue';
|
|
|
4
4
|
import { forwardRef, Module } from '@nestjs/common';
|
|
5
5
|
import { InstructorModule } from '../instructor/instructor.module';
|
|
6
6
|
import { CourseLessonController } from './course-lesson.controller';
|
|
7
|
-
import { CourseOperationsController } from './course-operations.controller';
|
|
8
7
|
import { CourseOperationsIntegrationService } from './course-operations-integration.service';
|
|
8
|
+
import { CourseOperationsController } from './course-operations.controller';
|
|
9
9
|
import { CourseStructureController } from './course-structure.controller';
|
|
10
|
-
import { LmsSettingController } from './lms-setting.controller';
|
|
11
10
|
import { CourseStructureService } from './course-structure.service';
|
|
12
11
|
import { CourseVideoConversionService } from './course-video-conversion.service';
|
|
13
12
|
import { CourseController } from './course.controller';
|
|
14
13
|
import { LmsCoursesMcpTools } from './course.mcp-tools';
|
|
14
|
+
import { LmsBulkUploadInfraService } from './lms-bulk-upload-infra.service';
|
|
15
15
|
import { CourseService } from './course.service';
|
|
16
|
+
import { LmsBulkUploadController } from './lms-bulk-upload.controller';
|
|
17
|
+
import { LmsBulkUploadService } from './lms-bulk-upload.service';
|
|
16
18
|
import { LmsOperationsTaskSubscriber } from './lms-operations-task.subscriber';
|
|
19
|
+
import { LmsSettingController } from './lms-setting.controller';
|
|
17
20
|
|
|
18
21
|
@Module({
|
|
19
22
|
imports: [
|
|
@@ -22,12 +25,21 @@ import { LmsOperationsTaskSubscriber } from './lms-operations-task.subscriber';
|
|
|
22
25
|
forwardRef(() => CoreModule),
|
|
23
26
|
forwardRef(() => QueueModule),
|
|
24
27
|
],
|
|
25
|
-
controllers: [
|
|
28
|
+
controllers: [
|
|
29
|
+
CourseController,
|
|
30
|
+
CourseStructureController,
|
|
31
|
+
CourseLessonController,
|
|
32
|
+
LmsSettingController,
|
|
33
|
+
CourseOperationsController,
|
|
34
|
+
LmsBulkUploadController,
|
|
35
|
+
],
|
|
26
36
|
providers: [
|
|
27
37
|
CourseOperationsIntegrationService,
|
|
28
38
|
CourseService,
|
|
29
39
|
CourseStructureService,
|
|
30
40
|
CourseVideoConversionService,
|
|
41
|
+
LmsBulkUploadInfraService,
|
|
42
|
+
LmsBulkUploadService,
|
|
31
43
|
LmsCoursesMcpTools,
|
|
32
44
|
LmsOperationsTaskSubscriber,
|
|
33
45
|
],
|