@hed-hog/lms 0.0.361 → 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 +2 -0
- 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 +116 -0
- package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload.controller.js +71 -2
- package/dist/course/lms-bulk-upload.controller.js.map +1 -1
- package/dist/course/lms-bulk-upload.service.d.ts +142 -3
- package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
- package/dist/course/lms-bulk-upload.service.js +606 -21
- package/dist/course/lms-bulk-upload.service.js.map +1 -1
- package/dist/course/lms-setting.controller.d.ts +4 -1
- package/dist/course/lms-setting.controller.d.ts.map +1 -1
- package/dist/course/lms-setting.controller.js +21 -6
- 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 +88 -1
- 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/route.yaml +512 -0
- package/hedhog/data/setting_group.yaml +1 -1
- 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 +8 -7
- 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 +2 -0
- 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 +79 -3
- package/src/course/lms-bulk-upload.service.ts +1029 -204
- package/src/course/lms-setting.controller.ts +22 -6
- 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,10 +1,36 @@
|
|
|
1
1
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
FileService,
|
|
4
|
+
IntegrationDeveloperApiService,
|
|
5
|
+
NotificationService,
|
|
6
|
+
} from '@hed-hog/core';
|
|
7
|
+
import { IJobHandler, QueueHandlerRegistry, QueueJobService } from '@hed-hog/queue';
|
|
8
|
+
import {
|
|
9
|
+
BadRequestException,
|
|
10
|
+
Inject,
|
|
11
|
+
Injectable,
|
|
12
|
+
Logger,
|
|
13
|
+
NotFoundException,
|
|
14
|
+
OnModuleInit,
|
|
15
|
+
forwardRef,
|
|
16
|
+
} from '@nestjs/common';
|
|
4
17
|
import { CourseOperationsIntegrationService } from './course-operations-integration.service';
|
|
18
|
+
import {
|
|
19
|
+
COURSE_STORAGE_CLEANUP_CATEGORIES,
|
|
20
|
+
type CourseStorageCleanupCategory,
|
|
21
|
+
} from './dto/cleanup-course-storage.dto';
|
|
5
22
|
import { CreateCourseDto } from './dto/create-course.dto';
|
|
6
23
|
import { UpdateCourseDto } from './dto/update-course.dto';
|
|
7
24
|
|
|
25
|
+
export const LMS_COURSE_DELETE_JOB = 'lms.course.delete';
|
|
26
|
+
export const LMS_COURSE_STORAGE_CLEANUP_JOB = 'lms.course.storage.cleanup';
|
|
27
|
+
|
|
28
|
+
type CourseStorageRow = {
|
|
29
|
+
file_id: number | null;
|
|
30
|
+
file_size: number | null;
|
|
31
|
+
storage_category: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
8
34
|
type CourseImageTypeSlug = 'course-logo' | 'course-banner';
|
|
9
35
|
|
|
10
36
|
type CourseExtraFields = {
|
|
@@ -30,15 +56,249 @@ type PersistCourseExtrasInput = {
|
|
|
30
56
|
};
|
|
31
57
|
|
|
32
58
|
@Injectable()
|
|
33
|
-
export class CourseService {
|
|
59
|
+
export class CourseService implements OnModuleInit, IJobHandler {
|
|
60
|
+
private readonly logger = new Logger(CourseService.name);
|
|
61
|
+
|
|
34
62
|
constructor(
|
|
35
63
|
private readonly prisma: PrismaService,
|
|
36
64
|
private readonly fileService: FileService,
|
|
37
65
|
@Inject(forwardRef(() => IntegrationDeveloperApiService))
|
|
38
66
|
private readonly integrationApi: IntegrationDeveloperApiService,
|
|
67
|
+
@Inject(forwardRef(() => QueueJobService))
|
|
68
|
+
private readonly queueJobService: QueueJobService,
|
|
69
|
+
@Inject(forwardRef(() => QueueHandlerRegistry))
|
|
70
|
+
private readonly queueRegistry: QueueHandlerRegistry,
|
|
71
|
+
@Inject(forwardRef(() => NotificationService))
|
|
72
|
+
private readonly notificationService: NotificationService,
|
|
39
73
|
private readonly operationsIntegration: CourseOperationsIntegrationService,
|
|
40
74
|
) {}
|
|
41
75
|
|
|
76
|
+
onModuleInit() {
|
|
77
|
+
this.queueRegistry.register(LMS_COURSE_DELETE_JOB, this);
|
|
78
|
+
this.queueRegistry.register(LMS_COURSE_STORAGE_CLEANUP_JOB, this);
|
|
79
|
+
this.logger.log(`Registered handler for "${LMS_COURSE_DELETE_JOB}"`);
|
|
80
|
+
this.logger.log(`Registered handler for "${LMS_COURSE_STORAGE_CLEANUP_JOB}"`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async handle(job: {
|
|
84
|
+
id: number;
|
|
85
|
+
type: string;
|
|
86
|
+
queue_name: string;
|
|
87
|
+
payload: Record<string, any>;
|
|
88
|
+
attempts: number;
|
|
89
|
+
max_attempts: number;
|
|
90
|
+
source_module?: string | null;
|
|
91
|
+
source_entity?: string | null;
|
|
92
|
+
source_entity_id?: string | null;
|
|
93
|
+
}) {
|
|
94
|
+
if (job.type === LMS_COURSE_STORAGE_CLEANUP_JOB) {
|
|
95
|
+
return this.handleStorageCleanupJob(job);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (job.type === LMS_COURSE_DELETE_JOB) {
|
|
99
|
+
return this.handleCourseDeleteJob(job);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
throw new BadRequestException(`Unsupported job type: ${job.type}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private async handleCourseDeleteJob(job: {
|
|
106
|
+
id: number;
|
|
107
|
+
type: string;
|
|
108
|
+
queue_name: string;
|
|
109
|
+
payload: Record<string, any>;
|
|
110
|
+
attempts: number;
|
|
111
|
+
max_attempts: number;
|
|
112
|
+
source_module?: string | null;
|
|
113
|
+
source_entity?: string | null;
|
|
114
|
+
source_entity_id?: string | null;
|
|
115
|
+
}) {
|
|
116
|
+
const { courseId, notificationId, notificationUserId } = job.payload as {
|
|
117
|
+
courseId?: number;
|
|
118
|
+
notificationId?: number;
|
|
119
|
+
notificationUserId?: number;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
if (!courseId) {
|
|
123
|
+
throw new BadRequestException('Missing courseId in job payload');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const notify = async (
|
|
127
|
+
progress: number,
|
|
128
|
+
body: string,
|
|
129
|
+
success?: boolean,
|
|
130
|
+
) => {
|
|
131
|
+
if (!notificationId || !notificationUserId) return;
|
|
132
|
+
|
|
133
|
+
await this.notificationService
|
|
134
|
+
.updateProgress(notificationUserId, notificationId, {
|
|
135
|
+
progress,
|
|
136
|
+
body,
|
|
137
|
+
...(success !== undefined ? { success } : {}),
|
|
138
|
+
})
|
|
139
|
+
.catch(() => undefined);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
await notify(5, 'Preparando exclusão completa do curso...');
|
|
144
|
+
|
|
145
|
+
const course = await this.prisma.course.findUnique({
|
|
146
|
+
where: { id: courseId },
|
|
147
|
+
select: { id: true, status: true, title: true, slug: true },
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (!course) {
|
|
151
|
+
throw new NotFoundException('Course not found');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (course.status !== 'archived') {
|
|
155
|
+
throw new BadRequestException('ONLY_ARCHIVED_COURSE_CAN_BE_DELETED');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const impact = await this.getCourseDeletionImpact(courseId);
|
|
159
|
+
await notify(
|
|
160
|
+
20,
|
|
161
|
+
`Removendo ${impact.deletionImpact.fileCount} arquivo(s) relacionados ao curso...`,
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (impact.deletionImpact.fileIds.length > 0) {
|
|
165
|
+
await this.fileService
|
|
166
|
+
.delete('pt', { ids: impact.deletionImpact.fileIds })
|
|
167
|
+
.catch(() => undefined);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
await notify(75, 'Removendo registros do curso e dependências...');
|
|
171
|
+
|
|
172
|
+
await this.prisma.course.delete({ where: { id: courseId } });
|
|
173
|
+
|
|
174
|
+
await notify(90, 'Finalizando exclusão e publicando evento...');
|
|
175
|
+
|
|
176
|
+
await this.integrationApi
|
|
177
|
+
.publishEvent({
|
|
178
|
+
eventName: 'lms.course.deleted',
|
|
179
|
+
sourceModule: 'lms',
|
|
180
|
+
aggregateType: 'course',
|
|
181
|
+
aggregateId: String(courseId),
|
|
182
|
+
payload: { id: courseId },
|
|
183
|
+
})
|
|
184
|
+
.catch(() => null);
|
|
185
|
+
|
|
186
|
+
await notify(100, 'Curso e todos os arquivos relacionados foram excluídos.', true);
|
|
187
|
+
|
|
188
|
+
return { success: true, courseId };
|
|
189
|
+
} catch (error) {
|
|
190
|
+
const message =
|
|
191
|
+
error instanceof Error && error.message
|
|
192
|
+
? error.message
|
|
193
|
+
: 'Falha ao excluir o curso.';
|
|
194
|
+
await notify(100, message, false);
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private async handleStorageCleanupJob(job: {
|
|
200
|
+
id: number;
|
|
201
|
+
type: string;
|
|
202
|
+
queue_name: string;
|
|
203
|
+
payload: Record<string, any>;
|
|
204
|
+
attempts: number;
|
|
205
|
+
max_attempts: number;
|
|
206
|
+
source_module?: string | null;
|
|
207
|
+
source_entity?: string | null;
|
|
208
|
+
source_entity_id?: string | null;
|
|
209
|
+
}) {
|
|
210
|
+
const { courseId, category, notificationId, notificationUserId } =
|
|
211
|
+
job.payload as {
|
|
212
|
+
courseId?: number;
|
|
213
|
+
category?: string;
|
|
214
|
+
notificationId?: number;
|
|
215
|
+
notificationUserId?: number;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
if (!courseId) {
|
|
219
|
+
throw new BadRequestException('Missing courseId in job payload');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!this.isSupportedStorageCleanupCategory(category)) {
|
|
223
|
+
throw new BadRequestException('Invalid storage category');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const notify = async (
|
|
227
|
+
progress: number,
|
|
228
|
+
body: string,
|
|
229
|
+
success?: boolean,
|
|
230
|
+
) => {
|
|
231
|
+
if (!notificationId || !notificationUserId) return;
|
|
232
|
+
|
|
233
|
+
await this.notificationService
|
|
234
|
+
.updateProgress(notificationUserId, notificationId, {
|
|
235
|
+
progress,
|
|
236
|
+
body,
|
|
237
|
+
...(success !== undefined ? { success } : {}),
|
|
238
|
+
})
|
|
239
|
+
.catch(() => undefined);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const categoryLabel = this.getStorageCategoryLabel(category);
|
|
244
|
+
await notify(5, `Preparando limpeza da categoria ${categoryLabel}...`);
|
|
245
|
+
|
|
246
|
+
const impact = await this.getCourseStorageCategoryImpact(courseId, category);
|
|
247
|
+
|
|
248
|
+
if (impact.fileCount === 0) {
|
|
249
|
+
await notify(100, `Nenhum arquivo encontrado em ${categoryLabel}.`, true);
|
|
250
|
+
return {
|
|
251
|
+
success: true,
|
|
252
|
+
status: 'empty',
|
|
253
|
+
courseId,
|
|
254
|
+
category,
|
|
255
|
+
cleanupImpact: impact,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
await notify(
|
|
260
|
+
25,
|
|
261
|
+
`Removendo vínculos da categoria ${categoryLabel} (${impact.fileCount} arquivo(s))...`,
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
await this.deleteCourseStorageCategoryLinks(courseId, category);
|
|
265
|
+
|
|
266
|
+
await notify(
|
|
267
|
+
65,
|
|
268
|
+
`Excluindo ${impact.fileCount} arquivo(s) da categoria ${categoryLabel}...`,
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
if (impact.fileIds.length > 0) {
|
|
272
|
+
await this.fileService.delete('pt', { ids: impact.fileIds }).catch(() => undefined);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
await notify(
|
|
276
|
+
100,
|
|
277
|
+
`Categoria ${categoryLabel} limpa com sucesso (${this.formatBytes(impact.totalBytes)} liberados).`,
|
|
278
|
+
true,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
success: true,
|
|
283
|
+
status: 'processed',
|
|
284
|
+
courseId,
|
|
285
|
+
category,
|
|
286
|
+
cleanupImpact: {
|
|
287
|
+
fileCount: impact.fileCount,
|
|
288
|
+
totalBytes: impact.totalBytes,
|
|
289
|
+
formattedSize: this.formatBytes(impact.totalBytes),
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
} catch (error) {
|
|
293
|
+
const message =
|
|
294
|
+
error instanceof Error && error.message
|
|
295
|
+
? error.message
|
|
296
|
+
: 'Falha ao limpar a categoria de armazenamento.';
|
|
297
|
+
await notify(100, message, false);
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
42
302
|
private normalizeLevel(value?: string | null) {
|
|
43
303
|
if (!value) return undefined;
|
|
44
304
|
const normalized = String(value).trim().toLowerCase();
|
|
@@ -285,7 +545,16 @@ export class CourseService {
|
|
|
285
545
|
},
|
|
286
546
|
},
|
|
287
547
|
file: {
|
|
288
|
-
select: {
|
|
548
|
+
select: {
|
|
549
|
+
id: true,
|
|
550
|
+
filename: true,
|
|
551
|
+
size: true,
|
|
552
|
+
file_mimetype: {
|
|
553
|
+
select: {
|
|
554
|
+
name: true,
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
},
|
|
289
558
|
},
|
|
290
559
|
},
|
|
291
560
|
orderBy: [{ is_primary: 'desc' }, { order: 'asc' }],
|
|
@@ -386,7 +655,7 @@ export class CourseService {
|
|
|
386
655
|
};
|
|
387
656
|
});
|
|
388
657
|
|
|
389
|
-
|
|
658
|
+
const mapped = this.mapCourse(
|
|
390
659
|
c,
|
|
391
660
|
{
|
|
392
661
|
lessonCount,
|
|
@@ -399,6 +668,19 @@ export class CourseService {
|
|
|
399
668
|
projectLinksById.get(id),
|
|
400
669
|
lessonInstructors,
|
|
401
670
|
);
|
|
671
|
+
|
|
672
|
+
const deletionImpact = await this.getCourseDeletionImpact(id);
|
|
673
|
+
|
|
674
|
+
return {
|
|
675
|
+
...mapped,
|
|
676
|
+
deletionImpact: {
|
|
677
|
+
fileCount: deletionImpact.deletionImpact.fileCount,
|
|
678
|
+
totalBytes: deletionImpact.deletionImpact.totalBytes,
|
|
679
|
+
formattedSize: this.formatBytes(deletionImpact.deletionImpact.totalBytes),
|
|
680
|
+
countsBySource: deletionImpact.deletionImpact.countsBySource,
|
|
681
|
+
enrollmentCount: mapped.enrollmentCount ?? 0,
|
|
682
|
+
},
|
|
683
|
+
};
|
|
402
684
|
}
|
|
403
685
|
|
|
404
686
|
async create(dto: CreateCourseDto) {
|
|
@@ -713,10 +995,10 @@ export class CourseService {
|
|
|
713
995
|
return updateResult;
|
|
714
996
|
}
|
|
715
997
|
|
|
716
|
-
async remove(id: number) {
|
|
998
|
+
async remove(id: number, userId?: number | null) {
|
|
717
999
|
const course = await this.prisma.course.findUnique({
|
|
718
1000
|
where: { id },
|
|
719
|
-
select: { id: true, status: true },
|
|
1001
|
+
select: { id: true, status: true, title: true, slug: true },
|
|
720
1002
|
});
|
|
721
1003
|
|
|
722
1004
|
if (!course) {
|
|
@@ -727,69 +1009,431 @@ export class CourseService {
|
|
|
727
1009
|
throw new BadRequestException('ONLY_ARCHIVED_COURSE_CAN_BE_DELETED');
|
|
728
1010
|
}
|
|
729
1011
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
await this.fileService.delete('pt', { ids: fileIds }).catch(() => undefined);
|
|
1012
|
+
if (!userId) {
|
|
1013
|
+
throw new BadRequestException('Authenticated user is required');
|
|
733
1014
|
}
|
|
734
1015
|
|
|
735
|
-
await this.
|
|
1016
|
+
const impact = await this.getCourseDeletionImpact(id);
|
|
1017
|
+
|
|
1018
|
+
const notification = await this.notificationService.create({
|
|
1019
|
+
user_id: userId,
|
|
1020
|
+
title: `Excluindo curso "${course.title ?? course.slug}"`,
|
|
1021
|
+
body: `Preparando remoção de ${impact.deletionImpact.fileCount} arquivo(s) (${this.formatBytes(impact.deletionImpact.totalBytes)}).`,
|
|
1022
|
+
type: 'progress' as any,
|
|
1023
|
+
progress: 1,
|
|
1024
|
+
started_at: new Date().toISOString(),
|
|
1025
|
+
auto_remove: false,
|
|
1026
|
+
action_type: 'url' as any,
|
|
1027
|
+
action_url: '/lms/courses',
|
|
1028
|
+
action_data: {
|
|
1029
|
+
source: 'lms-course-delete',
|
|
1030
|
+
courseId: id,
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
736
1033
|
|
|
737
|
-
await this.
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1034
|
+
const job = await this.queueJobService.enqueue(
|
|
1035
|
+
{
|
|
1036
|
+
type: LMS_COURSE_DELETE_JOB,
|
|
1037
|
+
queueName: LMS_COURSE_DELETE_JOB,
|
|
1038
|
+
payload: {
|
|
1039
|
+
courseId: id,
|
|
1040
|
+
notificationId: notification.id,
|
|
1041
|
+
notificationUserId: userId,
|
|
1042
|
+
},
|
|
1043
|
+
sourceModule: 'lms',
|
|
1044
|
+
sourceEntity: 'course',
|
|
1045
|
+
sourceEntityId: String(id),
|
|
1046
|
+
maxAttempts: 3,
|
|
1047
|
+
},
|
|
1048
|
+
userId,
|
|
1049
|
+
);
|
|
744
1050
|
|
|
745
|
-
return {
|
|
1051
|
+
return {
|
|
1052
|
+
success: true,
|
|
1053
|
+
status: 'queued',
|
|
1054
|
+
queueJobId: job.id,
|
|
1055
|
+
notificationId: notification.id,
|
|
1056
|
+
deletionImpact: {
|
|
1057
|
+
fileCount: impact.deletionImpact.fileCount,
|
|
1058
|
+
totalBytes: impact.deletionImpact.totalBytes,
|
|
1059
|
+
formattedSize: this.formatBytes(impact.deletionImpact.totalBytes),
|
|
1060
|
+
},
|
|
1061
|
+
};
|
|
746
1062
|
}
|
|
747
1063
|
|
|
748
|
-
|
|
1064
|
+
async enqueueStorageCategoryCleanup(
|
|
1065
|
+
courseId: number,
|
|
1066
|
+
category: CourseStorageCleanupCategory,
|
|
1067
|
+
userId?: number | null,
|
|
1068
|
+
) {
|
|
1069
|
+
if (!userId) {
|
|
1070
|
+
throw new BadRequestException('Authenticated user is required');
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (!this.isSupportedStorageCleanupCategory(category)) {
|
|
1074
|
+
throw new BadRequestException('Invalid storage category');
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
const course = await this.prisma.course.findUnique({
|
|
1078
|
+
where: { id: courseId },
|
|
1079
|
+
select: { id: true, title: true, slug: true },
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
if (!course) {
|
|
1083
|
+
throw new NotFoundException('Course not found');
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
const categoryLabel = this.getStorageCategoryLabel(category);
|
|
1087
|
+
const impact = await this.getCourseStorageCategoryImpact(courseId, category);
|
|
1088
|
+
|
|
1089
|
+
if (impact.fileCount === 0) {
|
|
1090
|
+
return {
|
|
1091
|
+
success: true,
|
|
1092
|
+
status: 'empty',
|
|
1093
|
+
cleanupImpact: {
|
|
1094
|
+
fileCount: 0,
|
|
1095
|
+
totalBytes: 0,
|
|
1096
|
+
formattedSize: this.formatBytes(0),
|
|
1097
|
+
},
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const notification = await this.notificationService.create({
|
|
1102
|
+
user_id: userId,
|
|
1103
|
+
title: `Limpando armazenamento (${categoryLabel})`,
|
|
1104
|
+
body: `Preparando remoção de ${impact.fileCount} arquivo(s) (${this.formatBytes(impact.totalBytes)}).`,
|
|
1105
|
+
type: 'progress' as any,
|
|
1106
|
+
progress: 1,
|
|
1107
|
+
started_at: new Date().toISOString(),
|
|
1108
|
+
auto_remove: false,
|
|
1109
|
+
action_type: 'url' as any,
|
|
1110
|
+
action_url: `/lms/courses/${courseId}`,
|
|
1111
|
+
action_data: {
|
|
1112
|
+
source: 'lms-course-storage-cleanup',
|
|
1113
|
+
courseId,
|
|
1114
|
+
category,
|
|
1115
|
+
},
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
const job = await this.queueJobService.enqueue(
|
|
1119
|
+
{
|
|
1120
|
+
type: LMS_COURSE_STORAGE_CLEANUP_JOB,
|
|
1121
|
+
queueName: LMS_COURSE_STORAGE_CLEANUP_JOB,
|
|
1122
|
+
payload: {
|
|
1123
|
+
courseId,
|
|
1124
|
+
category,
|
|
1125
|
+
notificationId: notification.id,
|
|
1126
|
+
notificationUserId: userId,
|
|
1127
|
+
},
|
|
1128
|
+
sourceModule: 'lms',
|
|
1129
|
+
sourceEntity: 'course',
|
|
1130
|
+
sourceEntityId: String(courseId),
|
|
1131
|
+
maxAttempts: 3,
|
|
1132
|
+
},
|
|
1133
|
+
userId,
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
return {
|
|
1137
|
+
success: true,
|
|
1138
|
+
status: 'queued',
|
|
1139
|
+
queueJobId: job.id,
|
|
1140
|
+
notificationId: notification.id,
|
|
1141
|
+
category,
|
|
1142
|
+
cleanupImpact: {
|
|
1143
|
+
fileCount: impact.fileCount,
|
|
1144
|
+
totalBytes: impact.totalBytes,
|
|
1145
|
+
formattedSize: this.formatBytes(impact.totalBytes),
|
|
1146
|
+
},
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
private isSupportedStorageCleanupCategory(
|
|
1151
|
+
value: unknown,
|
|
1152
|
+
): value is CourseStorageCleanupCategory {
|
|
1153
|
+
return (
|
|
1154
|
+
typeof value === 'string' &&
|
|
1155
|
+
COURSE_STORAGE_CLEANUP_CATEGORIES.includes(
|
|
1156
|
+
value as CourseStorageCleanupCategory,
|
|
1157
|
+
)
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
private getStorageCategoryLabel(category: CourseStorageCleanupCategory) {
|
|
1162
|
+
const labels: Record<CourseStorageCleanupCategory, string> = {
|
|
1163
|
+
video_original: 'Vídeos originais',
|
|
1164
|
+
video_profile: 'Vídeos convertidos',
|
|
1165
|
+
lesson_audio: 'Áudios',
|
|
1166
|
+
extracted_image: 'Imagens extraídas',
|
|
1167
|
+
student_download: 'Downloads do aluno',
|
|
1168
|
+
supplementary_material: 'Materiais de apoio',
|
|
1169
|
+
course_image: 'Imagens do curso',
|
|
1170
|
+
course_file: 'Arquivos do curso',
|
|
1171
|
+
other_lesson_file: 'Outros arquivos',
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
return labels[category];
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
private async getCourseStorageCategoryImpact(
|
|
1178
|
+
courseId: number,
|
|
1179
|
+
category: CourseStorageCleanupCategory,
|
|
1180
|
+
) {
|
|
1181
|
+
const rows = await this.getCourseStorageRows(courseId);
|
|
1182
|
+
|
|
1183
|
+
const uniqueFiles = new Map<number, number>();
|
|
1184
|
+
for (const row of rows) {
|
|
1185
|
+
const rowCategory = String(row.storage_category || 'other_lesson_file');
|
|
1186
|
+
if (rowCategory !== category) continue;
|
|
1187
|
+
|
|
1188
|
+
const fileId = Number(row.file_id ?? 0);
|
|
1189
|
+
if (fileId <= 0) continue;
|
|
1190
|
+
|
|
1191
|
+
if (!uniqueFiles.has(fileId)) {
|
|
1192
|
+
uniqueFiles.set(fileId, Number(row.file_size ?? 0) || 0);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const fileIds = Array.from(uniqueFiles.keys());
|
|
1197
|
+
const totalBytes = Array.from(uniqueFiles.values()).reduce(
|
|
1198
|
+
(sum, size) => sum + size,
|
|
1199
|
+
0,
|
|
1200
|
+
);
|
|
1201
|
+
|
|
1202
|
+
return {
|
|
1203
|
+
category,
|
|
1204
|
+
fileIds,
|
|
1205
|
+
fileCount: fileIds.length,
|
|
1206
|
+
totalBytes,
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
private async deleteCourseStorageCategoryLinks(
|
|
1211
|
+
courseId: number,
|
|
1212
|
+
category: CourseStorageCleanupCategory,
|
|
1213
|
+
) {
|
|
1214
|
+
if (category === 'course_image') {
|
|
1215
|
+
await this.prisma.course_image.deleteMany({ where: { course_id: courseId } });
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
if (category === 'course_file') {
|
|
1220
|
+
await this.prisma.course_file.deleteMany({ where: { course_id: courseId } });
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
if (category === 'extracted_image') {
|
|
1225
|
+
await this.prisma.$executeRaw`
|
|
1226
|
+
DELETE FROM course_lesson_video_frame clvf
|
|
1227
|
+
USING course_lesson cl, course_module cm
|
|
1228
|
+
WHERE clvf.course_lesson_id = cl.id
|
|
1229
|
+
AND cl.course_module_id = cm.id
|
|
1230
|
+
AND cm.course_id = ${courseId}
|
|
1231
|
+
`;
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if (category === 'video_profile') {
|
|
1236
|
+
await this.prisma.$executeRaw`
|
|
1237
|
+
DELETE FROM course_lesson_file clf
|
|
1238
|
+
USING course_lesson cl, course_module cm
|
|
1239
|
+
WHERE clf.course_lesson_id = cl.id
|
|
1240
|
+
AND cl.course_module_id = cm.id
|
|
1241
|
+
AND cm.course_id = ${courseId}
|
|
1242
|
+
AND clf.type LIKE 'video_profile:%'
|
|
1243
|
+
`;
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (category === 'other_lesson_file') {
|
|
1248
|
+
await this.prisma.$executeRaw`
|
|
1249
|
+
DELETE FROM course_lesson_file clf
|
|
1250
|
+
USING course_lesson cl, course_module cm
|
|
1251
|
+
WHERE clf.course_lesson_id = cl.id
|
|
1252
|
+
AND cl.course_module_id = cm.id
|
|
1253
|
+
AND cm.course_id = ${courseId}
|
|
1254
|
+
AND (
|
|
1255
|
+
clf.type IS NULL
|
|
1256
|
+
OR (
|
|
1257
|
+
clf.type <> 'video_original'
|
|
1258
|
+
AND clf.type <> 'lesson_audio'
|
|
1259
|
+
AND clf.type <> 'student_download'
|
|
1260
|
+
AND clf.type <> 'supplementary_material'
|
|
1261
|
+
AND clf.type NOT LIKE 'video_profile:%'
|
|
1262
|
+
)
|
|
1263
|
+
)
|
|
1264
|
+
`;
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
await this.prisma.course_lesson_file.deleteMany({
|
|
1269
|
+
where: {
|
|
1270
|
+
course_lesson: { course_module: { course_id: courseId } },
|
|
1271
|
+
type: category,
|
|
1272
|
+
},
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
private async getCourseStorageRows(courseId: number) {
|
|
1277
|
+
return this.prisma.$queryRaw<CourseStorageRow[]>`
|
|
1278
|
+
SELECT files.file_id, files.file_size, files.storage_category
|
|
1279
|
+
FROM (
|
|
1280
|
+
SELECT ci.file_id, f.size AS file_size, 'course_image' AS storage_category
|
|
1281
|
+
FROM course_image ci
|
|
1282
|
+
INNER JOIN file f ON f.id = ci.file_id
|
|
1283
|
+
WHERE ci.course_id = ${courseId}
|
|
1284
|
+
|
|
1285
|
+
UNION ALL
|
|
1286
|
+
|
|
1287
|
+
SELECT cf.file_id, f.size AS file_size, 'course_file' AS storage_category
|
|
1288
|
+
FROM course_file cf
|
|
1289
|
+
INNER JOIN file f ON f.id = cf.file_id
|
|
1290
|
+
WHERE cf.course_id = ${courseId}
|
|
1291
|
+
|
|
1292
|
+
UNION ALL
|
|
1293
|
+
|
|
1294
|
+
SELECT clf.file_id,
|
|
1295
|
+
f.size AS file_size,
|
|
1296
|
+
CASE
|
|
1297
|
+
WHEN clf.type = 'video_original' THEN 'video_original'
|
|
1298
|
+
WHEN clf.type LIKE 'video_profile:%' THEN 'video_profile'
|
|
1299
|
+
WHEN clf.type = 'lesson_audio' THEN 'lesson_audio'
|
|
1300
|
+
WHEN clf.type = 'student_download' THEN 'student_download'
|
|
1301
|
+
WHEN clf.type = 'supplementary_material' THEN 'supplementary_material'
|
|
1302
|
+
ELSE 'other_lesson_file'
|
|
1303
|
+
END AS storage_category
|
|
1304
|
+
FROM course_lesson_file clf
|
|
1305
|
+
INNER JOIN file f ON f.id = clf.file_id
|
|
1306
|
+
INNER JOIN course_lesson cl ON cl.id = clf.course_lesson_id
|
|
1307
|
+
INNER JOIN course_module cm ON cm.id = cl.course_module_id
|
|
1308
|
+
WHERE cm.course_id = ${courseId}
|
|
1309
|
+
|
|
1310
|
+
UNION ALL
|
|
1311
|
+
|
|
1312
|
+
SELECT clvf.file_id, f.size AS file_size, 'extracted_image' AS storage_category
|
|
1313
|
+
FROM course_lesson_video_frame clvf
|
|
1314
|
+
INNER JOIN file f ON f.id = clvf.file_id
|
|
1315
|
+
INNER JOIN course_lesson cl ON cl.id = clvf.course_lesson_id
|
|
1316
|
+
INNER JOIN course_module cm ON cm.id = cl.course_module_id
|
|
1317
|
+
WHERE cm.course_id = ${courseId}
|
|
1318
|
+
) AS files
|
|
1319
|
+
WHERE files.file_id IS NOT NULL
|
|
1320
|
+
`;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
private async getCourseDeletionImpact(courseId: number) {
|
|
749
1324
|
const rows = await this.prisma.$queryRaw<
|
|
750
|
-
Array<{
|
|
1325
|
+
Array<{
|
|
1326
|
+
file_id: number | null;
|
|
1327
|
+
file_size: number | null;
|
|
1328
|
+
source_type: string;
|
|
1329
|
+
}>
|
|
751
1330
|
>`
|
|
752
|
-
SELECT DISTINCT files.file_id
|
|
1331
|
+
SELECT DISTINCT files.file_id, files.file_size, files.source_type
|
|
753
1332
|
FROM (
|
|
754
|
-
SELECT ci.file_id
|
|
1333
|
+
SELECT ci.file_id, f.size AS file_size, 'course_image' AS source_type
|
|
755
1334
|
FROM course_image ci
|
|
1335
|
+
INNER JOIN file f ON f.id = ci.file_id
|
|
756
1336
|
WHERE ci.course_id = ${courseId}
|
|
757
1337
|
|
|
758
1338
|
UNION ALL
|
|
759
1339
|
|
|
760
|
-
SELECT cf.file_id
|
|
1340
|
+
SELECT cf.file_id, f.size AS file_size, 'course_file' AS source_type
|
|
761
1341
|
FROM course_file cf
|
|
1342
|
+
INNER JOIN file f ON f.id = cf.file_id
|
|
762
1343
|
WHERE cf.course_id = ${courseId}
|
|
763
1344
|
|
|
764
1345
|
UNION ALL
|
|
765
1346
|
|
|
766
|
-
SELECT clf.file_id
|
|
1347
|
+
SELECT clf.file_id, f.size AS file_size, 'course_lesson_file' AS source_type
|
|
767
1348
|
FROM course_lesson_file clf
|
|
1349
|
+
INNER JOIN file f ON f.id = clf.file_id
|
|
768
1350
|
INNER JOIN course_lesson cl ON cl.id = clf.course_lesson_id
|
|
769
1351
|
INNER JOIN course_module cm ON cm.id = cl.course_module_id
|
|
770
1352
|
WHERE cm.course_id = ${courseId}
|
|
771
1353
|
|
|
772
1354
|
UNION ALL
|
|
773
1355
|
|
|
774
|
-
SELECT clvf.file_id
|
|
1356
|
+
SELECT clvf.file_id, f.size AS file_size, 'course_lesson_video_frame' AS source_type
|
|
775
1357
|
FROM course_lesson_video_frame clvf
|
|
1358
|
+
INNER JOIN file f ON f.id = clvf.file_id
|
|
776
1359
|
INNER JOIN course_lesson cl ON cl.id = clvf.course_lesson_id
|
|
777
1360
|
INNER JOIN course_module cm ON cm.id = cl.course_module_id
|
|
778
1361
|
WHERE cm.course_id = ${courseId}
|
|
779
1362
|
|
|
780
1363
|
UNION ALL
|
|
781
1364
|
|
|
782
|
-
SELECT cgm.file_id
|
|
1365
|
+
SELECT cgm.file_id, f.size AS file_size, 'course_class_group_material' AS source_type
|
|
783
1366
|
FROM course_class_group_material cgm
|
|
1367
|
+
INNER JOIN file f ON f.id = cgm.file_id
|
|
784
1368
|
INNER JOIN course_class_group ccg ON ccg.id = cgm.course_class_group_id
|
|
785
1369
|
WHERE ccg.course_id = ${courseId}
|
|
1370
|
+
|
|
1371
|
+
UNION ALL
|
|
1372
|
+
|
|
1373
|
+
SELECT cln.frame_file_id AS file_id, f.size AS file_size, 'course_lesson_note_frame' AS source_type
|
|
1374
|
+
FROM course_lesson_note cln
|
|
1375
|
+
INNER JOIN file f ON f.id = cln.frame_file_id
|
|
1376
|
+
INNER JOIN course_lesson cl ON cl.id = cln.course_lesson_id
|
|
1377
|
+
INNER JOIN course_module cm ON cm.id = cl.course_module_id
|
|
1378
|
+
WHERE cm.course_id = ${courseId}
|
|
786
1379
|
) AS files
|
|
787
1380
|
WHERE files.file_id IS NOT NULL
|
|
788
1381
|
`;
|
|
789
1382
|
|
|
790
|
-
|
|
791
|
-
.
|
|
792
|
-
|
|
1383
|
+
const uniqueRows = Array.from(
|
|
1384
|
+
rows.reduce((map, row) => {
|
|
1385
|
+
const fileId = Number(row.file_id ?? 0);
|
|
1386
|
+
if (fileId > 0 && !map.has(fileId)) {
|
|
1387
|
+
map.set(fileId, {
|
|
1388
|
+
fileId,
|
|
1389
|
+
size: Number(row.file_size ?? 0) || 0,
|
|
1390
|
+
sourceType: row.source_type,
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
return map;
|
|
1394
|
+
}, new Map<number, { fileId: number; size: number; sourceType: string }>()).values(),
|
|
1395
|
+
);
|
|
1396
|
+
|
|
1397
|
+
const countsBySource = rows.reduce<Record<string, number>>((acc, row) => {
|
|
1398
|
+
const key = String(row.source_type || 'unknown');
|
|
1399
|
+
acc[key] = (acc[key] ?? 0) + 1;
|
|
1400
|
+
return acc;
|
|
1401
|
+
}, {});
|
|
1402
|
+
|
|
1403
|
+
return {
|
|
1404
|
+
deletionImpact: {
|
|
1405
|
+
fileIds: uniqueRows.map((row) => row.fileId),
|
|
1406
|
+
fileCount: uniqueRows.length,
|
|
1407
|
+
totalBytes: uniqueRows.reduce((sum, row) => sum + row.size, 0),
|
|
1408
|
+
countsBySource,
|
|
1409
|
+
},
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
private formatBytes(bytes: number) {
|
|
1414
|
+
if (!Number.isFinite(bytes) || bytes <= 0) {
|
|
1415
|
+
return '0 B';
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
1419
|
+
let value = bytes;
|
|
1420
|
+
let unitIndex = 0;
|
|
1421
|
+
|
|
1422
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
1423
|
+
value /= 1024;
|
|
1424
|
+
unitIndex += 1;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
const digits = unitIndex === 0 ? 0 : value >= 10 ? 1 : 2;
|
|
1428
|
+
return `${value.toFixed(digits)} ${units[unitIndex]}`;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
private async getCourseRelatedFileIds(courseId: number) {
|
|
1432
|
+
const impact = await this.getCourseDeletionImpact(courseId);
|
|
1433
|
+
|
|
1434
|
+
return impact.deletionImpact.fileIds.filter(
|
|
1435
|
+
(id) => Number.isInteger(id) && id > 0,
|
|
1436
|
+
);
|
|
793
1437
|
}
|
|
794
1438
|
|
|
795
1439
|
private mapCourse(
|
|
@@ -1238,4 +1882,177 @@ export class CourseService {
|
|
|
1238
1882
|
})),
|
|
1239
1883
|
);
|
|
1240
1884
|
}
|
|
1885
|
+
|
|
1886
|
+
async getCourseContentOverview(courseId: number) {
|
|
1887
|
+
const [
|
|
1888
|
+
moduleCount,
|
|
1889
|
+
lessons,
|
|
1890
|
+
transcriptionRows,
|
|
1891
|
+
xpMapRows,
|
|
1892
|
+
extractedImageCount,
|
|
1893
|
+
resourceFileCount,
|
|
1894
|
+
storageRows,
|
|
1895
|
+
] = await Promise.all([
|
|
1896
|
+
this.prisma.course_module.count({ where: { course_id: courseId } }),
|
|
1897
|
+
this.prisma.course_lesson.findMany({
|
|
1898
|
+
where: { course_module: { course_id: courseId } },
|
|
1899
|
+
select: { id: true, type: true, content: true, published: true },
|
|
1900
|
+
}),
|
|
1901
|
+
this.prisma.course_lesson_transcription_segment.findMany({
|
|
1902
|
+
where: { course_lesson: { course_module: { course_id: courseId } } },
|
|
1903
|
+
select: { course_lesson_id: true },
|
|
1904
|
+
distinct: ['course_lesson_id'],
|
|
1905
|
+
}),
|
|
1906
|
+
this.prisma.lesson_xp_map.findMany({
|
|
1907
|
+
where: { course_lesson: { course_module: { course_id: courseId } } },
|
|
1908
|
+
select: { course_lesson_id: true },
|
|
1909
|
+
}),
|
|
1910
|
+
this.prisma.course_lesson_video_frame.count({
|
|
1911
|
+
where: { course_lesson: { course_module: { course_id: courseId } } },
|
|
1912
|
+
}),
|
|
1913
|
+
this.prisma.course_lesson_file.count({
|
|
1914
|
+
where: {
|
|
1915
|
+
course_lesson: { course_module: { course_id: courseId } },
|
|
1916
|
+
type: { in: ['student_download', 'supplementary_material'] },
|
|
1917
|
+
},
|
|
1918
|
+
}),
|
|
1919
|
+
this.getCourseStorageRows(courseId),
|
|
1920
|
+
]);
|
|
1921
|
+
|
|
1922
|
+
const transcriptionLessonIds = new Set(
|
|
1923
|
+
transcriptionRows.map((r) => r.course_lesson_id),
|
|
1924
|
+
);
|
|
1925
|
+
const xpLessonIds = new Set(xpMapRows.map((r) => r.course_lesson_id));
|
|
1926
|
+
|
|
1927
|
+
const lessonsByType = { video: 0, questao: 0, post: 0, exercicio: 0 };
|
|
1928
|
+
let publishedLessonCount = 0;
|
|
1929
|
+
let videoWithTranscription = 0;
|
|
1930
|
+
let videoWithXp = 0;
|
|
1931
|
+
|
|
1932
|
+
const categoryOrder = [
|
|
1933
|
+
'video_original',
|
|
1934
|
+
'video_profile',
|
|
1935
|
+
'lesson_audio',
|
|
1936
|
+
'extracted_image',
|
|
1937
|
+
'student_download',
|
|
1938
|
+
'supplementary_material',
|
|
1939
|
+
'course_image',
|
|
1940
|
+
'course_file',
|
|
1941
|
+
'other_lesson_file',
|
|
1942
|
+
];
|
|
1943
|
+
|
|
1944
|
+
const uniqueStorageFiles = new Map<number, number>();
|
|
1945
|
+
const uniqueStorageCategoryEntries = new Set<string>();
|
|
1946
|
+
const storageCategoryMap = new Map<
|
|
1947
|
+
string,
|
|
1948
|
+
{ key: string; fileCount: number; totalBytes: number }
|
|
1949
|
+
>();
|
|
1950
|
+
|
|
1951
|
+
for (const row of storageRows) {
|
|
1952
|
+
const fileId = Number(row.file_id ?? 0);
|
|
1953
|
+
if (fileId <= 0) continue;
|
|
1954
|
+
|
|
1955
|
+
const size = Number(row.file_size ?? 0) || 0;
|
|
1956
|
+
const category = String(row.storage_category || 'other_lesson_file');
|
|
1957
|
+
|
|
1958
|
+
if (!uniqueStorageFiles.has(fileId)) {
|
|
1959
|
+
uniqueStorageFiles.set(fileId, size);
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
const categoryEntryKey = `${category}:${fileId}`;
|
|
1963
|
+
if (uniqueStorageCategoryEntries.has(categoryEntryKey)) {
|
|
1964
|
+
continue;
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
uniqueStorageCategoryEntries.add(categoryEntryKey);
|
|
1968
|
+
|
|
1969
|
+
const current = storageCategoryMap.get(category) ?? {
|
|
1970
|
+
key: category,
|
|
1971
|
+
fileCount: 0,
|
|
1972
|
+
totalBytes: 0,
|
|
1973
|
+
};
|
|
1974
|
+
|
|
1975
|
+
current.fileCount += 1;
|
|
1976
|
+
current.totalBytes += size;
|
|
1977
|
+
storageCategoryMap.set(category, current);
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
const storageCategories = Array.from(storageCategoryMap.values()).sort(
|
|
1981
|
+
(a, b) => {
|
|
1982
|
+
const orderA = categoryOrder.indexOf(a.key);
|
|
1983
|
+
const orderB = categoryOrder.indexOf(b.key);
|
|
1984
|
+
|
|
1985
|
+
if (orderA !== -1 || orderB !== -1) {
|
|
1986
|
+
return (orderA === -1 ? Number.MAX_SAFE_INTEGER : orderA) -
|
|
1987
|
+
(orderB === -1 ? Number.MAX_SAFE_INTEGER : orderB);
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
return b.totalBytes - a.totalBytes;
|
|
1991
|
+
},
|
|
1992
|
+
);
|
|
1993
|
+
|
|
1994
|
+
for (const lesson of lessons) {
|
|
1995
|
+
if (lesson.published) publishedLessonCount++;
|
|
1996
|
+
|
|
1997
|
+
let sourceType: string | undefined;
|
|
1998
|
+
try {
|
|
1999
|
+
const parsed = lesson.content
|
|
2000
|
+
? (JSON.parse(lesson.content as string) as Record<string, unknown>)
|
|
2001
|
+
: null;
|
|
2002
|
+
sourceType =
|
|
2003
|
+
typeof parsed?.sourceType === 'string'
|
|
2004
|
+
? parsed.sourceType
|
|
2005
|
+
: undefined;
|
|
2006
|
+
} catch {
|
|
2007
|
+
// ignore malformed content
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
let uiType: 'video' | 'questao' | 'post' | 'exercicio';
|
|
2011
|
+
if (
|
|
2012
|
+
sourceType === 'video' ||
|
|
2013
|
+
sourceType === 'questao' ||
|
|
2014
|
+
sourceType === 'post' ||
|
|
2015
|
+
sourceType === 'exercicio'
|
|
2016
|
+
) {
|
|
2017
|
+
uiType = sourceType;
|
|
2018
|
+
} else if (lesson.type === 'video') {
|
|
2019
|
+
uiType = 'video';
|
|
2020
|
+
} else if (lesson.type === 'quiz') {
|
|
2021
|
+
uiType = 'questao';
|
|
2022
|
+
} else {
|
|
2023
|
+
uiType = 'post';
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
lessonsByType[uiType]++;
|
|
2027
|
+
|
|
2028
|
+
if (uiType === 'video') {
|
|
2029
|
+
if (transcriptionLessonIds.has(lesson.id)) videoWithTranscription++;
|
|
2030
|
+
if (xpLessonIds.has(lesson.id)) videoWithXp++;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
return {
|
|
2035
|
+
structure: {
|
|
2036
|
+
moduleCount,
|
|
2037
|
+
lessonCount: lessons.length,
|
|
2038
|
+
publishedLessonCount,
|
|
2039
|
+
lessonsByType,
|
|
2040
|
+
},
|
|
2041
|
+
videos: {
|
|
2042
|
+
lessonCount: lessonsByType.video,
|
|
2043
|
+
withTranscription: videoWithTranscription,
|
|
2044
|
+
withXp: videoWithXp,
|
|
2045
|
+
},
|
|
2046
|
+
media: { extractedImageCount },
|
|
2047
|
+
resources: { fileCount: resourceFileCount },
|
|
2048
|
+
storage: {
|
|
2049
|
+
totalBytes: Array.from(uniqueStorageFiles.values()).reduce(
|
|
2050
|
+
(sum, size) => sum + size,
|
|
2051
|
+
0,
|
|
2052
|
+
),
|
|
2053
|
+
totalFileCount: uniqueStorageFiles.size,
|
|
2054
|
+
categories: storageCategories,
|
|
2055
|
+
},
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
1241
2058
|
}
|