@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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Role } from '@hed-hog/api';
|
|
2
|
+
import {
|
|
3
|
+
Body,
|
|
4
|
+
Controller,
|
|
5
|
+
Delete,
|
|
6
|
+
Get,
|
|
7
|
+
HttpCode,
|
|
8
|
+
HttpStatus,
|
|
9
|
+
Param,
|
|
10
|
+
ParseIntPipe,
|
|
11
|
+
Patch,
|
|
12
|
+
Post,
|
|
13
|
+
Query,
|
|
14
|
+
Request,
|
|
15
|
+
} from '@nestjs/common';
|
|
16
|
+
import { CreateLessonXpMapDto } from './dto/create-lesson-xp-map.dto';
|
|
17
|
+
import { CreateLessonXpSegmentDto } from './dto/create-lesson-xp-segment.dto';
|
|
18
|
+
import { RejectLessonXpMapDto } from './dto/review-lesson-xp-map.dto';
|
|
19
|
+
import { UpdateLessonXpMapDto } from './dto/update-lesson-xp-map.dto';
|
|
20
|
+
import { LessonXpAiCalculationService } from './lesson-xp-ai-calculation.service';
|
|
21
|
+
import { LessonXpMapService } from './lesson-xp-map.service';
|
|
22
|
+
import { LessonXpSegmentService } from './lesson-xp-segment.service';
|
|
23
|
+
|
|
24
|
+
@Role()
|
|
25
|
+
@Controller('lms/xp/lesson-maps')
|
|
26
|
+
export class LessonXpMapController {
|
|
27
|
+
constructor(
|
|
28
|
+
private readonly lessonXpMapService: LessonXpMapService,
|
|
29
|
+
private readonly lessonXpSegmentService: LessonXpSegmentService,
|
|
30
|
+
private readonly lessonXpAiCalculationService: LessonXpAiCalculationService,
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
@Get()
|
|
34
|
+
list(
|
|
35
|
+
@Query('page') page?: string,
|
|
36
|
+
@Query('pageSize') pageSize?: string,
|
|
37
|
+
@Query('search') search?: string,
|
|
38
|
+
@Query('status') status?: string,
|
|
39
|
+
@Query('courseLessonId') courseLessonId?: string,
|
|
40
|
+
) {
|
|
41
|
+
return this.lessonXpMapService.list({
|
|
42
|
+
page: Number(page) || 1,
|
|
43
|
+
pageSize: Number(pageSize) || 20,
|
|
44
|
+
search,
|
|
45
|
+
status,
|
|
46
|
+
courseLessonId: courseLessonId ? Number(courseLessonId) : undefined,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@Post()
|
|
51
|
+
create(@Body() dto: CreateLessonXpMapDto) {
|
|
52
|
+
return this.lessonXpMapService.create(dto);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Get('lesson/:lessonId')
|
|
56
|
+
getByLesson(@Param('lessonId', ParseIntPipe) lessonId: number) {
|
|
57
|
+
return this.lessonXpMapService.getByLessonId(lessonId);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Get('course/:courseId/overview')
|
|
61
|
+
getCourseOverview(@Param('courseId', ParseIntPipe) courseId: number) {
|
|
62
|
+
return this.lessonXpMapService.getCourseOverview(courseId);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@Post('lesson/:lessonId/recalculate')
|
|
66
|
+
recalculate(@Param('lessonId', ParseIntPipe) lessonId: number, @Request() req: any) {
|
|
67
|
+
return this.lessonXpAiCalculationService.triggerCalculation(lessonId, req.user?.id ?? 0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Get(':id')
|
|
71
|
+
getById(@Param('id', ParseIntPipe) id: number) {
|
|
72
|
+
return this.lessonXpMapService.getById(id);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@Patch(':id')
|
|
76
|
+
update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateLessonXpMapDto) {
|
|
77
|
+
return this.lessonXpMapService.update(id, dto);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@Delete(':id')
|
|
81
|
+
@HttpCode(HttpStatus.NO_CONTENT)
|
|
82
|
+
remove(@Param('id', ParseIntPipe) id: number) {
|
|
83
|
+
return this.lessonXpMapService.delete(id);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@Post(':id/approve')
|
|
87
|
+
approve(@Param('id', ParseIntPipe) id: number, @Request() req: any) {
|
|
88
|
+
const reviewerPersonId: number = req.user?.personId ?? req.user?.id ?? 0;
|
|
89
|
+
return this.lessonXpMapService.approve(id, reviewerPersonId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@Post(':id/reject')
|
|
93
|
+
reject(
|
|
94
|
+
@Param('id', ParseIntPipe) id: number,
|
|
95
|
+
@Body() dto: RejectLessonXpMapDto,
|
|
96
|
+
@Request() req: any,
|
|
97
|
+
) {
|
|
98
|
+
const reviewerPersonId: number = req.user?.personId ?? req.user?.id ?? 0;
|
|
99
|
+
return this.lessonXpMapService.reject(id, reviewerPersonId, dto.reviewNotes);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Segment sub-resource
|
|
103
|
+
@Get(':id/segments')
|
|
104
|
+
listSegments(
|
|
105
|
+
@Param('id', ParseIntPipe) id: number,
|
|
106
|
+
@Query('page') page?: string,
|
|
107
|
+
@Query('pageSize') pageSize?: string,
|
|
108
|
+
) {
|
|
109
|
+
return this.lessonXpSegmentService.listByMap(id, { page: Number(page) || 1, pageSize: Number(pageSize) || 50 });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@Post(':id/segments')
|
|
113
|
+
createSegment(@Param('id', ParseIntPipe) id: number, @Body() dto: CreateLessonXpSegmentDto) {
|
|
114
|
+
return this.lessonXpSegmentService.create(id, dto);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PrismaModule } from '@hed-hog/api-prisma';
|
|
2
|
+
import { CoreModule } from '@hed-hog/core';
|
|
3
|
+
import { QueueModule } from '@hed-hog/queue';
|
|
4
|
+
import { forwardRef, Module } from '@nestjs/common';
|
|
5
|
+
import { LessonXpAiCalculationService } from './lesson-xp-ai-calculation.service';
|
|
6
|
+
import { LessonXpMapController } from './lesson-xp-map.controller';
|
|
7
|
+
import { LessonXpMapService } from './lesson-xp-map.service';
|
|
8
|
+
import { LessonXpSegmentController } from './lesson-xp-segment.controller';
|
|
9
|
+
import { LessonXpSegmentService } from './lesson-xp-segment.service';
|
|
10
|
+
|
|
11
|
+
@Module({
|
|
12
|
+
imports: [
|
|
13
|
+
forwardRef(() => PrismaModule),
|
|
14
|
+
forwardRef(() => CoreModule),
|
|
15
|
+
forwardRef(() => QueueModule),
|
|
16
|
+
],
|
|
17
|
+
controllers: [LessonXpMapController, LessonXpSegmentController],
|
|
18
|
+
providers: [LessonXpMapService, LessonXpSegmentService, LessonXpAiCalculationService],
|
|
19
|
+
exports: [LessonXpMapService, LessonXpSegmentService, LessonXpAiCalculationService],
|
|
20
|
+
})
|
|
21
|
+
export class LessonXpMapModule {}
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { PrismaService } from '@hed-hog/api-prisma';
|
|
2
|
+
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
3
|
+
import { CreateLessonXpMapDto } from './dto/create-lesson-xp-map.dto';
|
|
4
|
+
import { UpdateLessonXpMapDto } from './dto/update-lesson-xp-map.dto';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class LessonXpMapService {
|
|
8
|
+
constructor(private readonly prisma: PrismaService) {}
|
|
9
|
+
|
|
10
|
+
async list(params: {
|
|
11
|
+
page?: number;
|
|
12
|
+
pageSize?: number;
|
|
13
|
+
search?: string;
|
|
14
|
+
status?: string;
|
|
15
|
+
courseLessonId?: number;
|
|
16
|
+
}) {
|
|
17
|
+
const page = Math.max(Number(params.page) || 1, 1);
|
|
18
|
+
const pageSize = Math.max(Number(params.pageSize) || 20, 1);
|
|
19
|
+
const offset = (page - 1) * pageSize;
|
|
20
|
+
|
|
21
|
+
const values: unknown[] = [];
|
|
22
|
+
const where: string[] = ['1 = 1'];
|
|
23
|
+
|
|
24
|
+
if (params.search?.trim()) {
|
|
25
|
+
const p = this.param(values, `%${params.search.trim()}%`);
|
|
26
|
+
where.push(`cl.title ILIKE ${p}`);
|
|
27
|
+
}
|
|
28
|
+
if (params.status?.trim()) {
|
|
29
|
+
where.push(`m.status::text = ${this.param(values, params.status.trim())}`);
|
|
30
|
+
}
|
|
31
|
+
if (params.courseLessonId) {
|
|
32
|
+
where.push(`m.course_lesson_id = ${this.param(values, params.courseLessonId)}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const whereClause = where.join(' AND ');
|
|
36
|
+
|
|
37
|
+
const totalRow = await this.querySingle<{ total: string }>(
|
|
38
|
+
`SELECT COUNT(*)::text AS total
|
|
39
|
+
FROM lesson_xp_map m
|
|
40
|
+
JOIN course_lesson cl ON cl.id = m.course_lesson_id
|
|
41
|
+
WHERE ${whereClause}`,
|
|
42
|
+
values,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const q = [...values];
|
|
46
|
+
const rows = await this.queryRows<any>(
|
|
47
|
+
`SELECT
|
|
48
|
+
m.id,
|
|
49
|
+
m.course_lesson_id AS "courseLessonId",
|
|
50
|
+
cl.title AS "lessonTitle",
|
|
51
|
+
m.version,
|
|
52
|
+
m.total_xp AS "totalXp",
|
|
53
|
+
m.status::text AS status,
|
|
54
|
+
m.generated_at::text AS "generatedAt",
|
|
55
|
+
m.reviewed_at::text AS "reviewedAt",
|
|
56
|
+
m.reviewed_by_person_id AS "reviewedByPersonId",
|
|
57
|
+
m.review_notes AS "reviewNotes",
|
|
58
|
+
m.ai_model_version AS "aiModelVersion",
|
|
59
|
+
m.processing_error AS "processingError",
|
|
60
|
+
m.created_at::text AS "createdAt",
|
|
61
|
+
m.updated_at::text AS "updatedAt",
|
|
62
|
+
(SELECT COUNT(*) FROM lesson_xp_segment s WHERE s.lesson_xp_map_id = m.id)::int AS "segmentCount"
|
|
63
|
+
FROM lesson_xp_map m
|
|
64
|
+
JOIN course_lesson cl ON cl.id = m.course_lesson_id
|
|
65
|
+
WHERE ${whereClause}
|
|
66
|
+
ORDER BY m.updated_at DESC
|
|
67
|
+
LIMIT ${this.param(q, pageSize)} OFFSET ${this.param(q, offset)}`,
|
|
68
|
+
q,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
const total = Number(totalRow?.total ?? 0);
|
|
72
|
+
return { data: rows, total, page, pageSize, lastPage: Math.ceil(total / pageSize) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async getById(id: number) {
|
|
76
|
+
const row = await this.querySingle<any>(
|
|
77
|
+
`SELECT
|
|
78
|
+
m.id,
|
|
79
|
+
m.course_lesson_id AS "courseLessonId",
|
|
80
|
+
cl.title AS "lessonTitle",
|
|
81
|
+
m.version,
|
|
82
|
+
m.total_xp AS "totalXp",
|
|
83
|
+
m.status::text AS status,
|
|
84
|
+
m.generated_at::text AS "generatedAt",
|
|
85
|
+
m.reviewed_at::text AS "reviewedAt",
|
|
86
|
+
m.reviewed_by_person_id AS "reviewedByPersonId",
|
|
87
|
+
m.review_notes AS "reviewNotes",
|
|
88
|
+
m.ai_model_version AS "aiModelVersion",
|
|
89
|
+
m.processing_error AS "processingError",
|
|
90
|
+
m.created_at::text AS "createdAt",
|
|
91
|
+
m.updated_at::text AS "updatedAt"
|
|
92
|
+
FROM lesson_xp_map m
|
|
93
|
+
JOIN course_lesson cl ON cl.id = m.course_lesson_id
|
|
94
|
+
WHERE m.id = $1`,
|
|
95
|
+
[id],
|
|
96
|
+
);
|
|
97
|
+
if (!row) throw new NotFoundException('Lesson XP map not found');
|
|
98
|
+
|
|
99
|
+
const segments = await this.queryRows<any>(
|
|
100
|
+
`SELECT
|
|
101
|
+
s.id,
|
|
102
|
+
s.start_seconds::float AS "startSeconds",
|
|
103
|
+
s.end_seconds::float AS "endSeconds",
|
|
104
|
+
s.xp_value AS "xpValue",
|
|
105
|
+
s.difficulty::text AS difficulty,
|
|
106
|
+
s.should_grant_xp AS "shouldGrantXp",
|
|
107
|
+
s.ai_summary AS "aiSummary",
|
|
108
|
+
s.ai_confidence::float AS "aiConfidence",
|
|
109
|
+
s.sort_order AS "sortOrder",
|
|
110
|
+
COALESCE(
|
|
111
|
+
(SELECT json_agg(json_build_object('xpAreaId', sa.xp_area_id, 'weightPercent', sa.weight_percent::float))
|
|
112
|
+
FROM lesson_xp_segment_area sa WHERE sa.lesson_xp_segment_id = s.id),
|
|
113
|
+
'[]'
|
|
114
|
+
) AS areas,
|
|
115
|
+
COALESCE(
|
|
116
|
+
(SELECT json_agg(json_build_object('xpSkillId', ss.xp_skill_id, 'weightPercent', ss.weight_percent::float))
|
|
117
|
+
FROM lesson_xp_segment_skill ss WHERE ss.lesson_xp_segment_id = s.id),
|
|
118
|
+
'[]'
|
|
119
|
+
) AS skills,
|
|
120
|
+
COALESCE(
|
|
121
|
+
(SELECT json_agg(
|
|
122
|
+
json_build_object(
|
|
123
|
+
'xpLearningTypeId', sl.xp_learning_type_id,
|
|
124
|
+
'weightPercent', sl.weight_percent::float,
|
|
125
|
+
'slug', lt.slug,
|
|
126
|
+
'name', COALESCE(lt_pt.name, lt_en.name, lt.slug),
|
|
127
|
+
'multiplier', lt.multiplier::float
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
FROM lesson_xp_segment_learning_type sl
|
|
131
|
+
JOIN xp_learning_type lt ON lt.id = sl.xp_learning_type_id
|
|
132
|
+
LEFT JOIN xp_learning_type_locale lt_pt
|
|
133
|
+
ON lt_pt.xp_learning_type_id = lt.id
|
|
134
|
+
AND COALESCE(
|
|
135
|
+
(SELECT code FROM locale WHERE id = NULLIF(to_jsonb(lt_pt)->>'locale_id', '')::int),
|
|
136
|
+
to_jsonb(lt_pt)->>'locale'
|
|
137
|
+
) = 'pt'
|
|
138
|
+
LEFT JOIN xp_learning_type_locale lt_en
|
|
139
|
+
ON lt_en.xp_learning_type_id = lt.id
|
|
140
|
+
AND COALESCE(
|
|
141
|
+
(SELECT code FROM locale WHERE id = NULLIF(to_jsonb(lt_en)->>'locale_id', '')::int),
|
|
142
|
+
to_jsonb(lt_en)->>'locale'
|
|
143
|
+
) = 'en'
|
|
144
|
+
WHERE sl.lesson_xp_segment_id = s.id),
|
|
145
|
+
'[]'
|
|
146
|
+
) AS "learningTypes"
|
|
147
|
+
FROM lesson_xp_segment s
|
|
148
|
+
WHERE s.lesson_xp_map_id = $1
|
|
149
|
+
ORDER BY s.sort_order ASC, s.start_seconds ASC`,
|
|
150
|
+
[id],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return { ...row, segments };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async getByLessonId(lessonId: number) {
|
|
157
|
+
const row = await this.querySingle<{ id: number }>(
|
|
158
|
+
`SELECT id FROM lesson_xp_map WHERE course_lesson_id = $1`,
|
|
159
|
+
[lessonId],
|
|
160
|
+
);
|
|
161
|
+
if (!row) return null;
|
|
162
|
+
return this.getById(row.id);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async getCourseOverview(courseId: number) {
|
|
166
|
+
const lessons = await this.queryRows<{
|
|
167
|
+
courseLessonId: number;
|
|
168
|
+
lessonCode: string | null;
|
|
169
|
+
lessonTitle: string;
|
|
170
|
+
totalXp: number;
|
|
171
|
+
status: string;
|
|
172
|
+
segmentCount: number;
|
|
173
|
+
hasMap: boolean;
|
|
174
|
+
}>(
|
|
175
|
+
`SELECT
|
|
176
|
+
l.id AS "courseLessonId",
|
|
177
|
+
l.id::text AS "lessonCode",
|
|
178
|
+
l.title AS "lessonTitle",
|
|
179
|
+
COALESCE(lm.total_xp, 0)::int AS "totalXp",
|
|
180
|
+
COALESCE(lm.status::text, 'pending') AS status,
|
|
181
|
+
COALESCE(
|
|
182
|
+
(SELECT COUNT(*)::int
|
|
183
|
+
FROM lesson_xp_segment s
|
|
184
|
+
WHERE s.lesson_xp_map_id = lm.id
|
|
185
|
+
AND s.should_grant_xp = true),
|
|
186
|
+
0
|
|
187
|
+
)::int AS "segmentCount",
|
|
188
|
+
(lm.id IS NOT NULL) AS "hasMap"
|
|
189
|
+
FROM course_lesson l
|
|
190
|
+
JOIN course_module cm ON cm.id = l.course_module_id
|
|
191
|
+
LEFT JOIN lesson_xp_map lm ON lm.course_lesson_id = l.id
|
|
192
|
+
WHERE cm.course_id = $1
|
|
193
|
+
ORDER BY l.id ASC`,
|
|
194
|
+
[courseId],
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const areas = await this.queryRows<{
|
|
198
|
+
xpAreaId: number;
|
|
199
|
+
slug: string;
|
|
200
|
+
name: string;
|
|
201
|
+
xp: number;
|
|
202
|
+
lessonCount: number;
|
|
203
|
+
segmentCount: number;
|
|
204
|
+
}>(
|
|
205
|
+
`SELECT
|
|
206
|
+
a.id AS "xpAreaId",
|
|
207
|
+
a.slug,
|
|
208
|
+
COALESCE(a_pt.name, a_en.name, a.slug) AS name,
|
|
209
|
+
SUM(ROUND(s.xp_value * (sa.weight_percent::numeric / 100)))::int AS xp,
|
|
210
|
+
COUNT(DISTINCT lm.course_lesson_id)::int AS "lessonCount",
|
|
211
|
+
COUNT(*)::int AS "segmentCount"
|
|
212
|
+
FROM lesson_xp_map lm
|
|
213
|
+
JOIN course_lesson l ON l.id = lm.course_lesson_id
|
|
214
|
+
JOIN course_module cm ON cm.id = l.course_module_id
|
|
215
|
+
JOIN lesson_xp_segment s
|
|
216
|
+
ON s.lesson_xp_map_id = lm.id
|
|
217
|
+
AND s.should_grant_xp = true
|
|
218
|
+
JOIN lesson_xp_segment_area sa ON sa.lesson_xp_segment_id = s.id
|
|
219
|
+
JOIN xp_area a ON a.id = sa.xp_area_id
|
|
220
|
+
LEFT JOIN xp_area_locale a_pt
|
|
221
|
+
ON a_pt.xp_area_id = a.id
|
|
222
|
+
AND a_pt.locale_id = (SELECT id FROM locale WHERE code = 'pt')
|
|
223
|
+
LEFT JOIN xp_area_locale a_en
|
|
224
|
+
ON a_en.xp_area_id = a.id
|
|
225
|
+
AND a_en.locale_id = (SELECT id FROM locale WHERE code = 'en')
|
|
226
|
+
WHERE cm.course_id = $1
|
|
227
|
+
GROUP BY a.id, a.slug, a_pt.name, a_en.name
|
|
228
|
+
ORDER BY xp DESC, name ASC`,
|
|
229
|
+
[courseId],
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const skills = await this.queryRows<{
|
|
233
|
+
xpSkillId: number;
|
|
234
|
+
slug: string;
|
|
235
|
+
name: string;
|
|
236
|
+
xp: number;
|
|
237
|
+
lessonCount: number;
|
|
238
|
+
segmentCount: number;
|
|
239
|
+
}>(
|
|
240
|
+
`SELECT
|
|
241
|
+
sk.id AS "xpSkillId",
|
|
242
|
+
sk.slug,
|
|
243
|
+
COALESCE(sk_pt.name, sk_en.name, sk.slug) AS name,
|
|
244
|
+
SUM(ROUND(s.xp_value * (ss.weight_percent::numeric / 100)))::int AS xp,
|
|
245
|
+
COUNT(DISTINCT lm.course_lesson_id)::int AS "lessonCount",
|
|
246
|
+
COUNT(*)::int AS "segmentCount"
|
|
247
|
+
FROM lesson_xp_map lm
|
|
248
|
+
JOIN course_lesson l ON l.id = lm.course_lesson_id
|
|
249
|
+
JOIN course_module cm ON cm.id = l.course_module_id
|
|
250
|
+
JOIN lesson_xp_segment s
|
|
251
|
+
ON s.lesson_xp_map_id = lm.id
|
|
252
|
+
AND s.should_grant_xp = true
|
|
253
|
+
JOIN lesson_xp_segment_skill ss ON ss.lesson_xp_segment_id = s.id
|
|
254
|
+
JOIN xp_skill sk ON sk.id = ss.xp_skill_id
|
|
255
|
+
LEFT JOIN xp_skill_locale sk_pt
|
|
256
|
+
ON sk_pt.xp_skill_id = sk.id
|
|
257
|
+
AND sk_pt.locale_id = (SELECT id FROM locale WHERE code = 'pt')
|
|
258
|
+
LEFT JOIN xp_skill_locale sk_en
|
|
259
|
+
ON sk_en.xp_skill_id = sk.id
|
|
260
|
+
AND sk_en.locale_id = (SELECT id FROM locale WHERE code = 'en')
|
|
261
|
+
WHERE cm.course_id = $1
|
|
262
|
+
GROUP BY sk.id, sk.slug, sk_pt.name, sk_en.name
|
|
263
|
+
ORDER BY xp DESC, name ASC`,
|
|
264
|
+
[courseId],
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const learningTypes = await this.queryRows<{
|
|
268
|
+
xpLearningTypeId: number;
|
|
269
|
+
slug: string;
|
|
270
|
+
name: string;
|
|
271
|
+
multiplier: number;
|
|
272
|
+
xp: number;
|
|
273
|
+
lessonCount: number;
|
|
274
|
+
segmentCount: number;
|
|
275
|
+
}>(
|
|
276
|
+
`SELECT
|
|
277
|
+
lt.id AS "xpLearningTypeId",
|
|
278
|
+
lt.slug,
|
|
279
|
+
COALESCE(lt_pt.name, lt_en.name, lt.slug) AS name,
|
|
280
|
+
lt.multiplier::float AS multiplier,
|
|
281
|
+
SUM(ROUND(s.xp_value * (sl.weight_percent::numeric / 100)))::int AS xp,
|
|
282
|
+
COUNT(DISTINCT lm.course_lesson_id)::int AS "lessonCount",
|
|
283
|
+
COUNT(*)::int AS "segmentCount"
|
|
284
|
+
FROM lesson_xp_map lm
|
|
285
|
+
JOIN course_lesson l ON l.id = lm.course_lesson_id
|
|
286
|
+
JOIN course_module cm ON cm.id = l.course_module_id
|
|
287
|
+
JOIN lesson_xp_segment s
|
|
288
|
+
ON s.lesson_xp_map_id = lm.id
|
|
289
|
+
AND s.should_grant_xp = true
|
|
290
|
+
JOIN lesson_xp_segment_learning_type sl ON sl.lesson_xp_segment_id = s.id
|
|
291
|
+
JOIN xp_learning_type lt ON lt.id = sl.xp_learning_type_id
|
|
292
|
+
LEFT JOIN xp_learning_type_locale lt_pt
|
|
293
|
+
ON lt_pt.xp_learning_type_id = lt.id
|
|
294
|
+
AND COALESCE(
|
|
295
|
+
(SELECT code FROM locale WHERE id = NULLIF(to_jsonb(lt_pt)->>'locale_id', '')::int),
|
|
296
|
+
to_jsonb(lt_pt)->>'locale'
|
|
297
|
+
) = 'pt'
|
|
298
|
+
LEFT JOIN xp_learning_type_locale lt_en
|
|
299
|
+
ON lt_en.xp_learning_type_id = lt.id
|
|
300
|
+
AND COALESCE(
|
|
301
|
+
(SELECT code FROM locale WHERE id = NULLIF(to_jsonb(lt_en)->>'locale_id', '')::int),
|
|
302
|
+
to_jsonb(lt_en)->>'locale'
|
|
303
|
+
) = 'en'
|
|
304
|
+
WHERE cm.course_id = $1
|
|
305
|
+
GROUP BY lt.id, lt.slug, lt.multiplier, lt_pt.name, lt_en.name
|
|
306
|
+
ORDER BY xp DESC, name ASC`,
|
|
307
|
+
[courseId],
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const totalXp = lessons.reduce((sum, lesson) => sum + Number(lesson.totalXp || 0), 0);
|
|
311
|
+
const mappedLessons = lessons.filter((lesson) => lesson.hasMap).length;
|
|
312
|
+
const totalSegments = lessons.reduce((sum, lesson) => sum + Number(lesson.segmentCount || 0), 0);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
summary: {
|
|
316
|
+
totalLessons: lessons.length,
|
|
317
|
+
mappedLessons,
|
|
318
|
+
unmappedLessons: Math.max(lessons.length - mappedLessons, 0),
|
|
319
|
+
totalXp,
|
|
320
|
+
totalSegments,
|
|
321
|
+
averageXpPerMappedLesson: mappedLessons > 0 ? totalXp / mappedLessons : 0,
|
|
322
|
+
},
|
|
323
|
+
lessons,
|
|
324
|
+
areas: this.withShare(areas, totalXp),
|
|
325
|
+
skills: this.withShare(skills, totalXp),
|
|
326
|
+
learningTypes: this.withShare(learningTypes, totalXp),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async create(dto: CreateLessonXpMapDto) {
|
|
331
|
+
const existing = await this.querySingle<{ id: number }>(
|
|
332
|
+
`SELECT id FROM lesson_xp_map WHERE course_lesson_id = $1`,
|
|
333
|
+
[dto.courseLessonId],
|
|
334
|
+
);
|
|
335
|
+
if (existing) throw new NotFoundException(`A lesson XP map already exists for lesson ${dto.courseLessonId}. Use PATCH to update it.`);
|
|
336
|
+
|
|
337
|
+
const created = await this.querySingle<{ id: number }>(
|
|
338
|
+
`INSERT INTO lesson_xp_map (course_lesson_id, version, total_xp, status, ai_model_version, created_at, updated_at)
|
|
339
|
+
VALUES ($1, $2, 0, 'pending'::lesson_xp_map_status_d4e5f6a7b8_enum, $3, NOW(), NOW())
|
|
340
|
+
RETURNING id`,
|
|
341
|
+
[dto.courseLessonId, dto.version ?? 1, dto.aiModelVersion ?? null],
|
|
342
|
+
);
|
|
343
|
+
return this.getById(created!.id);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async update(id: number, dto: UpdateLessonXpMapDto) {
|
|
347
|
+
await this.ensureExists(id);
|
|
348
|
+
|
|
349
|
+
const updates: string[] = [];
|
|
350
|
+
const values: unknown[] = [];
|
|
351
|
+
|
|
352
|
+
if (dto.status !== undefined) updates.push(`status = ${this.param(values, dto.status)}::lesson_xp_map_status_d4e5f6a7b8_enum`);
|
|
353
|
+
if (dto.reviewNotes !== undefined) updates.push(`review_notes = ${this.param(values, dto.reviewNotes)}`);
|
|
354
|
+
if (dto.aiModelVersion !== undefined) updates.push(`ai_model_version = ${this.param(values, dto.aiModelVersion)}`);
|
|
355
|
+
if (dto.processingError !== undefined) updates.push(`processing_error = ${this.param(values, dto.processingError)}`);
|
|
356
|
+
if (dto.totalXp !== undefined) updates.push(`total_xp = ${this.param(values, dto.totalXp)}`);
|
|
357
|
+
|
|
358
|
+
if (updates.length > 0) {
|
|
359
|
+
values.push(id);
|
|
360
|
+
await this.prisma.$executeRawUnsafe(
|
|
361
|
+
`UPDATE lesson_xp_map SET ${updates.join(', ')}, updated_at = NOW() WHERE id = $${values.length}`,
|
|
362
|
+
...values,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return this.getById(id);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async delete(id: number) {
|
|
370
|
+
await this.ensureExists(id);
|
|
371
|
+
await this.prisma.$executeRawUnsafe(`DELETE FROM lesson_xp_map WHERE id = $1`, id);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async approve(id: number, reviewerPersonId: number) {
|
|
375
|
+
await this.ensureExists(id);
|
|
376
|
+
await this.prisma.$executeRawUnsafe(
|
|
377
|
+
`UPDATE lesson_xp_map
|
|
378
|
+
SET status = 'approved'::lesson_xp_map_status_d4e5f6a7b8_enum,
|
|
379
|
+
reviewed_at = NOW(),
|
|
380
|
+
reviewed_by_person_id = $1,
|
|
381
|
+
updated_at = NOW()
|
|
382
|
+
WHERE id = $2`,
|
|
383
|
+
reviewerPersonId, id,
|
|
384
|
+
);
|
|
385
|
+
return this.getById(id);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async reject(id: number, reviewerPersonId: number, notes?: string) {
|
|
389
|
+
await this.ensureExists(id);
|
|
390
|
+
await this.prisma.$executeRawUnsafe(
|
|
391
|
+
`UPDATE lesson_xp_map
|
|
392
|
+
SET status = 'rejected'::lesson_xp_map_status_d4e5f6a7b8_enum,
|
|
393
|
+
reviewed_at = NOW(),
|
|
394
|
+
reviewed_by_person_id = $1,
|
|
395
|
+
review_notes = $2,
|
|
396
|
+
updated_at = NOW()
|
|
397
|
+
WHERE id = $3`,
|
|
398
|
+
reviewerPersonId, notes ?? null, id,
|
|
399
|
+
);
|
|
400
|
+
return this.getById(id);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async recalculateTotalXp(mapId: number) {
|
|
404
|
+
await this.prisma.$executeRawUnsafe(
|
|
405
|
+
`UPDATE lesson_xp_map
|
|
406
|
+
SET total_xp = COALESCE(
|
|
407
|
+
(SELECT SUM(xp_value) FROM lesson_xp_segment
|
|
408
|
+
WHERE lesson_xp_map_id = $1 AND should_grant_xp = true),
|
|
409
|
+
0
|
|
410
|
+
),
|
|
411
|
+
updated_at = NOW()
|
|
412
|
+
WHERE id = $1`,
|
|
413
|
+
mapId,
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private async ensureExists(id: number) {
|
|
418
|
+
const row = await this.querySingle<{ id: number }>(`SELECT id FROM lesson_xp_map WHERE id = $1`, [id]);
|
|
419
|
+
if (!row) throw new NotFoundException('Lesson XP map not found');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private param(values: unknown[], value: unknown) {
|
|
423
|
+
values.push(value);
|
|
424
|
+
return `$${values.length}`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private withShare<T extends { xp: number }>(items: T[], totalXp: number) {
|
|
428
|
+
return items.map((item) => ({
|
|
429
|
+
...item,
|
|
430
|
+
sharePercent: totalXp > 0 ? Number(((item.xp / totalXp) * 100).toFixed(2)) : 0,
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private async queryRows<T>(query: string, values: unknown[] = []) {
|
|
435
|
+
return this.prisma.$queryRawUnsafe<T[]>(query, ...values);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
private async querySingle<T>(query: string, values: unknown[] = []) {
|
|
439
|
+
const rows = await this.prisma.$queryRawUnsafe<T[]>(query, ...values);
|
|
440
|
+
return rows[0] ?? null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Role } from '@hed-hog/api';
|
|
2
|
+
import {
|
|
3
|
+
Body,
|
|
4
|
+
Controller,
|
|
5
|
+
Delete,
|
|
6
|
+
Get,
|
|
7
|
+
HttpCode,
|
|
8
|
+
HttpStatus,
|
|
9
|
+
Param,
|
|
10
|
+
ParseIntPipe,
|
|
11
|
+
Patch,
|
|
12
|
+
} from '@nestjs/common';
|
|
13
|
+
import { UpdateLessonXpSegmentDto } from './dto/update-lesson-xp-segment.dto';
|
|
14
|
+
import { LessonXpSegmentService } from './lesson-xp-segment.service';
|
|
15
|
+
|
|
16
|
+
@Role()
|
|
17
|
+
@Controller('lms/xp/segments')
|
|
18
|
+
export class LessonXpSegmentController {
|
|
19
|
+
constructor(private readonly lessonXpSegmentService: LessonXpSegmentService) {}
|
|
20
|
+
|
|
21
|
+
@Get(':id')
|
|
22
|
+
getById(@Param('id', ParseIntPipe) id: number) {
|
|
23
|
+
return this.lessonXpSegmentService.getById(id);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Patch(':id')
|
|
27
|
+
update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateLessonXpSegmentDto) {
|
|
28
|
+
return this.lessonXpSegmentService.update(id, dto);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Delete(':id')
|
|
32
|
+
@HttpCode(HttpStatus.NO_CONTENT)
|
|
33
|
+
remove(@Param('id', ParseIntPipe) id: number) {
|
|
34
|
+
return this.lessonXpSegmentService.delete(id);
|
|
35
|
+
}
|
|
36
|
+
}
|