@hed-hog/lms 0.0.361 → 0.0.365

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.
Files changed (434) hide show
  1. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts +66 -0
  2. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -1
  3. package/dist/bitcode-wallet/bitcode-wallet.service.js +91 -0
  4. package/dist/bitcode-wallet/bitcode-wallet.service.js.map +1 -1
  5. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.d.ts +8 -0
  6. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.d.ts.map +1 -0
  7. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.js +40 -0
  8. package/dist/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.js.map +1 -0
  9. package/dist/class-group/class-group.controller.d.ts +16 -16
  10. package/dist/class-group/class-group.service.d.ts +12 -12
  11. package/dist/course/course-audio-transcription.service.d.ts +3 -2
  12. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  13. package/dist/course/course-audio-transcription.service.js +49 -8
  14. package/dist/course/course-audio-transcription.service.js.map +1 -1
  15. package/dist/course/course-export-scorm12-worker.service.d.ts +21 -0
  16. package/dist/course/course-export-scorm12-worker.service.d.ts.map +1 -0
  17. package/dist/course/course-export-scorm12-worker.service.js +109 -0
  18. package/dist/course/course-export-scorm12-worker.service.js.map +1 -0
  19. package/dist/course/course-export-scorm12.service.d.ts +42 -0
  20. package/dist/course/course-export-scorm12.service.d.ts.map +1 -0
  21. package/dist/course/course-export-scorm12.service.js +628 -0
  22. package/dist/course/course-export-scorm12.service.js.map +1 -0
  23. package/dist/course/course-export.service.d.ts +84 -0
  24. package/dist/course/course-export.service.d.ts.map +1 -0
  25. package/dist/course/course-export.service.js +237 -0
  26. package/dist/course/course-export.service.js.map +1 -0
  27. package/dist/course/course-lesson.controller.d.ts +4 -0
  28. package/dist/course/course-lesson.controller.d.ts.map +1 -1
  29. package/dist/course/course-lesson.controller.js +10 -0
  30. package/dist/course/course-lesson.controller.js.map +1 -1
  31. package/dist/course/course-structure.controller.d.ts +24 -9
  32. package/dist/course/course-structure.controller.d.ts.map +1 -1
  33. package/dist/course/course-structure.controller.js +30 -3
  34. package/dist/course/course-structure.controller.js.map +1 -1
  35. package/dist/course/course-structure.service.d.ts +25 -3
  36. package/dist/course/course-structure.service.d.ts.map +1 -1
  37. package/dist/course/course-structure.service.js +234 -24
  38. package/dist/course/course-structure.service.js.map +1 -1
  39. package/dist/course/course-video-conversion.service.d.ts +8 -0
  40. package/dist/course/course-video-conversion.service.d.ts.map +1 -1
  41. package/dist/course/course-video-conversion.service.js +87 -51
  42. package/dist/course/course-video-conversion.service.js.map +1 -1
  43. package/dist/course/course-video-hls.service.d.ts +57 -0
  44. package/dist/course/course-video-hls.service.d.ts.map +1 -0
  45. package/dist/course/course-video-hls.service.js +767 -0
  46. package/dist/course/course-video-hls.service.js.map +1 -0
  47. package/dist/course/course.controller.d.ts +115 -11
  48. package/dist/course/course.controller.d.ts.map +1 -1
  49. package/dist/course/course.controller.js +66 -28
  50. package/dist/course/course.controller.js.map +1 -1
  51. package/dist/course/course.mcp-tools.js +1 -1
  52. package/dist/course/course.mcp-tools.js.map +1 -1
  53. package/dist/course/course.module.d.ts.map +1 -1
  54. package/dist/course/course.module.js +13 -0
  55. package/dist/course/course.module.js.map +1 -1
  56. package/dist/course/course.service.d.ts +112 -11
  57. package/dist/course/course.service.d.ts.map +1 -1
  58. package/dist/course/course.service.js +682 -72
  59. package/dist/course/course.service.js.map +1 -1
  60. package/dist/course/dto/cleanup-course-storage.dto.d.ts +6 -0
  61. package/dist/course/dto/cleanup-course-storage.dto.d.ts.map +1 -0
  62. package/dist/course/dto/cleanup-course-storage.dto.js +34 -0
  63. package/dist/course/dto/cleanup-course-storage.dto.js.map +1 -0
  64. package/dist/course/dto/cleanup-upload-history.dto.d.ts +9 -0
  65. package/dist/course/dto/cleanup-upload-history.dto.d.ts.map +1 -0
  66. package/dist/course/dto/cleanup-upload-history.dto.js +36 -0
  67. package/dist/course/dto/cleanup-upload-history.dto.js.map +1 -0
  68. package/dist/course/dto/create-course-bulk-job.dto.d.ts +5 -0
  69. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -0
  70. package/dist/course/dto/create-course-bulk-job.dto.js +26 -0
  71. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -0
  72. package/dist/course/dto/create-course-export.dto.d.ts +14 -0
  73. package/dist/course/dto/create-course-export.dto.d.ts.map +1 -0
  74. package/dist/course/dto/create-course-export.dto.js +71 -0
  75. package/dist/course/dto/create-course-export.dto.js.map +1 -0
  76. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +2 -2
  77. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  78. package/dist/course/dto/create-course-structure-lesson.dto.js +3 -2
  79. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  80. package/dist/course/lms-bulk-upload-automation.service.d.ts +54 -0
  81. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -0
  82. package/dist/course/lms-bulk-upload-automation.service.js +537 -0
  83. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -0
  84. package/dist/course/lms-bulk-upload-infra.service.d.ts +32 -0
  85. package/dist/course/lms-bulk-upload-infra.service.d.ts.map +1 -0
  86. package/dist/course/lms-bulk-upload-infra.service.js +301 -0
  87. package/dist/course/lms-bulk-upload-infra.service.js.map +1 -0
  88. package/dist/course/lms-bulk-upload.constants.d.ts +4 -0
  89. package/dist/course/lms-bulk-upload.constants.d.ts.map +1 -0
  90. package/dist/course/lms-bulk-upload.constants.js +7 -0
  91. package/dist/course/lms-bulk-upload.constants.js.map +1 -0
  92. package/dist/course/lms-bulk-upload.controller.d.ts +144 -1
  93. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
  94. package/dist/course/lms-bulk-upload.controller.js +114 -4
  95. package/dist/course/lms-bulk-upload.controller.js.map +1 -1
  96. package/dist/course/lms-bulk-upload.service.d.ts +153 -3
  97. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
  98. package/dist/course/lms-bulk-upload.service.js +659 -21
  99. package/dist/course/lms-bulk-upload.service.js.map +1 -1
  100. package/dist/course/lms-setting.controller.d.ts +6 -2
  101. package/dist/course/lms-setting.controller.d.ts.map +1 -1
  102. package/dist/course/lms-setting.controller.js +25 -8
  103. package/dist/course/lms-setting.controller.js.map +1 -1
  104. package/dist/course/scorm12-schemas.d.ts +4 -0
  105. package/dist/course/scorm12-schemas.d.ts.map +1 -0
  106. package/dist/course/scorm12-schemas.js +9 -0
  107. package/dist/course/scorm12-schemas.js.map +1 -0
  108. package/dist/enterprise/enterprise.controller.d.ts +20 -20
  109. package/dist/enterprise/enterprise.service.d.ts +20 -20
  110. package/dist/enterprise/training/training-admin.controller.d.ts +11 -11
  111. package/dist/enterprise/training/training-admin.service.d.ts +11 -11
  112. package/dist/enterprise/training/training-instructor.controller.d.ts +2 -2
  113. package/dist/enterprise/training/training-instructor.service.d.ts +2 -2
  114. package/dist/enterprise/training/training-student.controller.d.ts +1 -1
  115. package/dist/enterprise/training/training-student.service.d.ts +52 -1
  116. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  117. package/dist/enterprise/training/training-student.service.js +217 -4
  118. package/dist/enterprise/training/training-student.service.js.map +1 -1
  119. package/dist/enterprise/training/training-viewer.controller.d.ts +2 -2
  120. package/dist/evaluation/evaluation.controller.d.ts +8 -8
  121. package/dist/evaluation/evaluation.service.d.ts +26 -8
  122. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  123. package/dist/evaluation/evaluation.service.js +125 -0
  124. package/dist/evaluation/evaluation.service.js.map +1 -1
  125. package/dist/exam/dto/create-standalone-question.dto.d.ts +12 -0
  126. package/dist/exam/dto/create-standalone-question.dto.d.ts.map +1 -0
  127. package/dist/exam/dto/create-standalone-question.dto.js +70 -0
  128. package/dist/exam/dto/create-standalone-question.dto.js.map +1 -0
  129. package/dist/exam/exam.module.d.ts.map +1 -1
  130. package/dist/exam/exam.module.js +2 -1
  131. package/dist/exam/exam.module.js.map +1 -1
  132. package/dist/exam/exam.service.d.ts +21 -0
  133. package/dist/exam/exam.service.d.ts.map +1 -1
  134. package/dist/exam/exam.service.js +80 -0
  135. package/dist/exam/exam.service.js.map +1 -1
  136. package/dist/exam/question.controller.d.ts +27 -0
  137. package/dist/exam/question.controller.d.ts.map +1 -0
  138. package/dist/exam/question.controller.js +53 -0
  139. package/dist/exam/question.controller.js.map +1 -0
  140. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts +6 -0
  141. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.d.ts.map +1 -0
  142. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js +34 -0
  143. package/dist/lesson-xp-map/dto/create-lesson-xp-map.dto.js.map +1 -0
  144. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts +28 -0
  145. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.d.ts.map +1 -0
  146. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js +123 -0
  147. package/dist/lesson-xp-map/dto/create-lesson-xp-segment.dto.js.map +1 -0
  148. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts +4 -0
  149. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.d.ts.map +1 -0
  150. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js +22 -0
  151. package/dist/lesson-xp-map/dto/review-lesson-xp-map.dto.js.map +1 -0
  152. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts +10 -0
  153. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.d.ts.map +1 -0
  154. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js +52 -0
  155. package/dist/lesson-xp-map/dto/update-lesson-xp-map.dto.js.map +1 -0
  156. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts +15 -0
  157. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.d.ts.map +1 -0
  158. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js +86 -0
  159. package/dist/lesson-xp-map/dto/update-lesson-xp-segment.dto.js.map +1 -0
  160. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +30 -0
  161. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -0
  162. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +440 -0
  163. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -0
  164. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts +87 -0
  165. package/dist/lesson-xp-map/lesson-xp-map.controller.d.ts.map +1 -0
  166. package/dist/lesson-xp-map/lesson-xp-map.controller.js +185 -0
  167. package/dist/lesson-xp-map/lesson-xp-map.controller.js.map +1 -0
  168. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts +3 -0
  169. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -0
  170. package/dist/lesson-xp-map/lesson-xp-map.module.js +34 -0
  171. package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -0
  172. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts +84 -0
  173. package/dist/lesson-xp-map/lesson-xp-map.service.d.ts.map +1 -0
  174. package/dist/lesson-xp-map/lesson-xp-map.service.js +353 -0
  175. package/dist/lesson-xp-map/lesson-xp-map.service.js.map +1 -0
  176. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts +10 -0
  177. package/dist/lesson-xp-map/lesson-xp-segment.controller.d.ts.map +1 -0
  178. package/dist/lesson-xp-map/lesson-xp-segment.controller.js +63 -0
  179. package/dist/lesson-xp-map/lesson-xp-segment.controller.js.map +1 -0
  180. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts +27 -0
  181. package/dist/lesson-xp-map/lesson-xp-segment.service.d.ts.map +1 -0
  182. package/dist/lesson-xp-map/lesson-xp-segment.service.js +194 -0
  183. package/dist/lesson-xp-map/lesson-xp-segment.service.js.map +1 -0
  184. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -0
  185. package/dist/lms-commerce-access.subscriber.d.ts +11 -0
  186. package/dist/lms-commerce-access.subscriber.d.ts.map +1 -0
  187. package/dist/lms-commerce-access.subscriber.js +74 -0
  188. package/dist/lms-commerce-access.subscriber.js.map +1 -0
  189. package/dist/lms.module.d.ts.map +1 -1
  190. package/dist/lms.module.js +21 -5
  191. package/dist/lms.module.js.map +1 -1
  192. package/dist/platforma/dto/update-profile.dto.d.ts +17 -0
  193. package/dist/platforma/dto/update-profile.dto.d.ts.map +1 -0
  194. package/dist/platforma/dto/update-profile.dto.js +87 -0
  195. package/dist/platforma/dto/update-profile.dto.js.map +1 -0
  196. package/dist/platforma/platforma-video.service.d.ts +39 -0
  197. package/dist/platforma/platforma-video.service.d.ts.map +1 -0
  198. package/dist/platforma/platforma-video.service.js +301 -0
  199. package/dist/platforma/platforma-video.service.js.map +1 -0
  200. package/dist/platforma/platforma.controller.d.ts +182 -1
  201. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  202. package/dist/platforma/platforma.controller.js +243 -2
  203. package/dist/platforma/platforma.controller.js.map +1 -1
  204. package/dist/platforma/platforma.service.d.ts +27 -0
  205. package/dist/platforma/platforma.service.d.ts.map +1 -0
  206. package/dist/platforma/platforma.service.js +274 -0
  207. package/dist/platforma/platforma.service.js.map +1 -0
  208. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts +5 -0
  209. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts.map +1 -0
  210. package/dist/student-xp/dto/grant-skill-card-xp.dto.js +26 -0
  211. package/dist/student-xp/dto/grant-skill-card-xp.dto.js.map +1 -0
  212. package/dist/student-xp/student-xp.controller.d.ts +56 -0
  213. package/dist/student-xp/student-xp.controller.d.ts.map +1 -0
  214. package/dist/student-xp/student-xp.controller.js +138 -0
  215. package/dist/student-xp/student-xp.controller.js.map +1 -0
  216. package/dist/student-xp/student-xp.module.d.ts +3 -0
  217. package/dist/student-xp/student-xp.module.d.ts.map +1 -0
  218. package/dist/student-xp/student-xp.module.js +25 -0
  219. package/dist/student-xp/student-xp.module.js.map +1 -0
  220. package/dist/student-xp/student-xp.service.d.ts +81 -0
  221. package/dist/student-xp/student-xp.service.d.ts.map +1 -0
  222. package/dist/student-xp/student-xp.service.js +247 -0
  223. package/dist/student-xp/student-xp.service.js.map +1 -0
  224. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts +12 -0
  225. package/dist/xp-catalog/dto/create-xp-area.dto.d.ts.map +1 -0
  226. package/dist/xp-catalog/dto/create-xp-area.dto.js +63 -0
  227. package/dist/xp-catalog/dto/create-xp-area.dto.js.map +1 -0
  228. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts +11 -0
  229. package/dist/xp-catalog/dto/create-xp-learning-type.dto.d.ts.map +1 -0
  230. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js +57 -0
  231. package/dist/xp-catalog/dto/create-xp-learning-type.dto.js.map +1 -0
  232. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts +11 -0
  233. package/dist/xp-catalog/dto/create-xp-skill.dto.d.ts.map +1 -0
  234. package/dist/xp-catalog/dto/create-xp-skill.dto.js +57 -0
  235. package/dist/xp-catalog/dto/create-xp-skill.dto.js.map +1 -0
  236. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts +12 -0
  237. package/dist/xp-catalog/dto/update-xp-area.dto.d.ts.map +1 -0
  238. package/dist/xp-catalog/dto/update-xp-area.dto.js +66 -0
  239. package/dist/xp-catalog/dto/update-xp-area.dto.js.map +1 -0
  240. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts +11 -0
  241. package/dist/xp-catalog/dto/update-xp-learning-type.dto.d.ts.map +1 -0
  242. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js +60 -0
  243. package/dist/xp-catalog/dto/update-xp-learning-type.dto.js.map +1 -0
  244. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts +11 -0
  245. package/dist/xp-catalog/dto/update-xp-skill.dto.d.ts.map +1 -0
  246. package/dist/xp-catalog/dto/update-xp-skill.dto.js +60 -0
  247. package/dist/xp-catalog/dto/update-xp-skill.dto.js.map +1 -0
  248. package/dist/xp-catalog/xp-area.controller.d.ts +25 -0
  249. package/dist/xp-catalog/xp-area.controller.d.ts.map +1 -0
  250. package/dist/xp-catalog/xp-area.controller.js +105 -0
  251. package/dist/xp-catalog/xp-area.controller.js.map +1 -0
  252. package/dist/xp-catalog/xp-area.service.d.ts +35 -0
  253. package/dist/xp-catalog/xp-area.service.d.ts.map +1 -0
  254. package/dist/xp-catalog/xp-area.service.js +168 -0
  255. package/dist/xp-catalog/xp-area.service.js.map +1 -0
  256. package/dist/xp-catalog/xp-catalog.module.d.ts +3 -0
  257. package/dist/xp-catalog/xp-catalog.module.d.ts.map +1 -0
  258. package/dist/xp-catalog/xp-catalog.module.js +29 -0
  259. package/dist/xp-catalog/xp-catalog.module.js.map +1 -0
  260. package/dist/xp-catalog/xp-learning-type.controller.d.ts +20 -0
  261. package/dist/xp-catalog/xp-learning-type.controller.d.ts.map +1 -0
  262. package/dist/xp-catalog/xp-learning-type.controller.js +96 -0
  263. package/dist/xp-catalog/xp-learning-type.controller.js.map +1 -0
  264. package/dist/xp-catalog/xp-learning-type.service.d.ts +30 -0
  265. package/dist/xp-catalog/xp-learning-type.service.d.ts.map +1 -0
  266. package/dist/xp-catalog/xp-learning-type.service.js +146 -0
  267. package/dist/xp-catalog/xp-learning-type.service.js.map +1 -0
  268. package/dist/xp-catalog/xp-skill.controller.d.ts +26 -0
  269. package/dist/xp-catalog/xp-skill.controller.d.ts.map +1 -0
  270. package/dist/xp-catalog/xp-skill.controller.js +113 -0
  271. package/dist/xp-catalog/xp-skill.controller.js.map +1 -0
  272. package/dist/xp-catalog/xp-skill.service.d.ts +37 -0
  273. package/dist/xp-catalog/xp-skill.service.d.ts.map +1 -0
  274. package/dist/xp-catalog/xp-skill.service.js +174 -0
  275. package/dist/xp-catalog/xp-skill.service.js.map +1 -0
  276. package/hedhog/data/evaluation_topic.yaml +17 -0
  277. package/hedhog/data/menu.yaml +91 -7
  278. package/hedhog/data/queue_definition.yaml +48 -0
  279. package/hedhog/data/route.yaml +511 -29
  280. package/hedhog/data/setting_group.yaml +20 -20
  281. package/hedhog/data/xp_area.yaml +164 -0
  282. package/hedhog/data/xp_learning_type.yaml +131 -0
  283. package/hedhog/data/xp_skill.yaml +1834 -0
  284. package/hedhog/frontend/app/achievements/page.tsx.ejs +108 -118
  285. package/hedhog/frontend/app/bitcodes/page.tsx.ejs +22 -34
  286. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +1749 -0
  287. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +21 -45
  288. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +40 -74
  289. package/hedhog/frontend/app/classes/page.tsx.ejs +56 -85
  290. package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +3 -2
  291. package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +48 -5
  292. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +73 -8
  293. package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +420 -0
  294. package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +308 -0
  295. package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +19 -2
  296. package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +1172 -0
  297. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +16 -0
  298. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +2 -0
  299. package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +623 -0
  300. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +1458 -0
  301. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +87 -46
  302. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +8 -3
  303. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +31 -8
  304. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +16 -9
  305. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +618 -480
  306. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +672 -737
  307. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -2
  308. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +3 -0
  309. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +1 -0
  310. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +101 -85
  311. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +24 -10
  312. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +3 -0
  313. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +1 -1
  314. package/hedhog/frontend/app/courses/[id]/structure/_components/use-tree-display-settings.ts.ejs +7 -1
  315. package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +44 -0
  316. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +6 -10
  317. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +49 -0
  318. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -3
  319. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +53 -0
  320. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +106 -0
  321. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +80 -1
  322. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-xp-overview.ts.ejs +76 -0
  323. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lesson-xp-map.ts.ejs +128 -0
  324. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +0 -2
  325. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +30 -0
  326. package/hedhog/frontend/app/courses/[id]/structure/_utils/xp-color-config.ts.ejs +115 -0
  327. package/hedhog/frontend/app/courses/_components/CourseDeleteDialog.tsx.ejs +223 -0
  328. package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +89 -0
  329. package/hedhog/frontend/app/courses/page.tsx.ejs +445 -230
  330. package/hedhog/frontend/app/enterprise/page.tsx.ejs +39 -63
  331. package/hedhog/frontend/app/exams/[id]/questions/page.tsx.ejs +53 -77
  332. package/hedhog/frontend/app/exams/page.tsx.ejs +54 -90
  333. package/hedhog/frontend/app/instructor-skills/page.tsx.ejs +23 -36
  334. package/hedhog/frontend/app/instructors/page.tsx.ejs +72 -81
  335. package/hedhog/frontend/app/paths/page.tsx.ejs +40 -68
  336. package/hedhog/frontend/app/training/page.tsx.ejs +40 -68
  337. package/hedhog/frontend/app/xp/areas/page.tsx.ejs +782 -0
  338. package/hedhog/frontend/app/xp/learning-types/page.tsx.ejs +690 -0
  339. package/hedhog/frontend/app/xp/skills/page.tsx.ejs +811 -0
  340. package/hedhog/frontend/messages/en.json +412 -31
  341. package/hedhog/frontend/messages/pt.json +412 -31
  342. package/hedhog/table/course_export.yaml +62 -0
  343. package/hedhog/table/lesson_xp_map.yaml +50 -0
  344. package/hedhog/table/lesson_xp_segment.yaml +40 -0
  345. package/hedhog/table/lesson_xp_segment_area.yaml +24 -0
  346. package/hedhog/table/lesson_xp_segment_learning_type.yaml +24 -0
  347. package/hedhog/table/lesson_xp_segment_skill.yaml +24 -0
  348. package/hedhog/table/lms_bulk_upload_item.yaml +44 -0
  349. package/hedhog/table/lms_bulk_upload_session.yaml +42 -0
  350. package/hedhog/table/student_area_xp.yaml +30 -0
  351. package/hedhog/table/student_learning_type_xp.yaml +30 -0
  352. package/hedhog/table/student_skill_xp.yaml +30 -0
  353. package/hedhog/table/student_xp_event.yaml +34 -0
  354. package/hedhog/table/xp_area.yaml +39 -0
  355. package/hedhog/table/xp_learning_type.yaml +34 -0
  356. package/hedhog/table/xp_skill.yaml +39 -0
  357. package/package.json +13 -8
  358. package/src/bitcode-wallet/bitcode-wallet.service.ts +152 -0
  359. package/src/bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto.ts +32 -0
  360. package/src/course/course-audio-transcription.service.ts +58 -21
  361. package/src/course/course-export-scorm12-worker.service.ts +124 -0
  362. package/src/course/course-export-scorm12.service.ts +668 -0
  363. package/src/course/course-export.service.ts +280 -0
  364. package/src/course/course-lesson.controller.ts +6 -1
  365. package/src/course/course-structure.controller.ts +23 -1
  366. package/src/course/course-structure.service.ts +273 -7
  367. package/src/course/course-video-conversion.service.ts +113 -75
  368. package/src/course/course-video-hls.service.ts +946 -0
  369. package/src/course/course.controller.ts +54 -21
  370. package/src/course/course.mcp-tools.ts +1 -1
  371. package/src/course/course.module.ts +13 -0
  372. package/src/course/course.service.ts +906 -76
  373. package/src/course/dto/cleanup-course-storage.dto.ts +23 -0
  374. package/src/course/dto/cleanup-upload-history.dto.ts +26 -0
  375. package/src/course/dto/create-course-bulk-job.dto.ts +10 -0
  376. package/src/course/dto/create-course-export.dto.ts +56 -0
  377. package/src/course/dto/create-course-structure-lesson.dto.ts +4 -3
  378. package/src/course/lms-bulk-upload-automation.service.ts +707 -0
  379. package/src/course/lms-bulk-upload-infra.service.ts +360 -0
  380. package/src/course/lms-bulk-upload.constants.ts +5 -0
  381. package/src/course/lms-bulk-upload.controller.ts +110 -4
  382. package/src/course/lms-bulk-upload.service.ts +1092 -204
  383. package/src/course/lms-setting.controller.ts +26 -8
  384. package/src/course/scorm12-schemas.ts +9 -0
  385. package/src/enterprise/training/training-student.service.ts +221 -2
  386. package/src/evaluation/evaluation.service.ts +123 -0
  387. package/src/exam/dto/create-standalone-question.dto.ts +66 -0
  388. package/src/exam/exam.module.ts +2 -1
  389. package/src/exam/exam.service.ts +86 -0
  390. package/src/exam/question.controller.ts +28 -0
  391. package/src/lesson-xp-map/dto/create-lesson-xp-map.dto.ts +17 -0
  392. package/src/lesson-xp-map/dto/create-lesson-xp-segment.dto.ts +102 -0
  393. package/src/lesson-xp-map/dto/review-lesson-xp-map.dto.ts +7 -0
  394. package/src/lesson-xp-map/dto/update-lesson-xp-map.dto.ts +36 -0
  395. package/src/lesson-xp-map/dto/update-lesson-xp-segment.dto.ts +78 -0
  396. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +570 -0
  397. package/src/lesson-xp-map/lesson-xp-map.controller.ts +116 -0
  398. package/src/lesson-xp-map/lesson-xp-map.module.ts +21 -0
  399. package/src/lesson-xp-map/lesson-xp-map.service.ts +442 -0
  400. package/src/lesson-xp-map/lesson-xp-segment.controller.ts +36 -0
  401. package/src/lesson-xp-map/lesson-xp-segment.service.ts +229 -0
  402. package/src/lms-commerce-access.subscriber.ts +88 -0
  403. package/src/lms.module.ts +21 -5
  404. package/src/platforma/dto/update-profile.dto.ts +59 -0
  405. package/src/platforma/platforma-video.service.ts +346 -0
  406. package/src/platforma/platforma.controller.ts +152 -3
  407. package/src/platforma/platforma.service.ts +268 -0
  408. package/src/student-xp/dto/grant-skill-card-xp.dto.ts +10 -0
  409. package/src/student-xp/student-xp.controller.ts +92 -0
  410. package/src/student-xp/student-xp.module.ts +12 -0
  411. package/src/student-xp/student-xp.service.ts +318 -0
  412. package/src/xp-catalog/dto/create-xp-area.dto.ts +40 -0
  413. package/src/xp-catalog/dto/create-xp-learning-type.dto.ts +35 -0
  414. package/src/xp-catalog/dto/create-xp-skill.dto.ts +35 -0
  415. package/src/xp-catalog/dto/update-xp-area.dto.ts +43 -0
  416. package/src/xp-catalog/dto/update-xp-learning-type.dto.ts +38 -0
  417. package/src/xp-catalog/dto/update-xp-skill.dto.ts +38 -0
  418. package/src/xp-catalog/xp-area.controller.ts +64 -0
  419. package/src/xp-catalog/xp-area.service.ts +196 -0
  420. package/src/xp-catalog/xp-catalog.module.ts +16 -0
  421. package/src/xp-catalog/xp-learning-type.controller.ts +59 -0
  422. package/src/xp-catalog/xp-learning-type.service.ts +170 -0
  423. package/src/xp-catalog/xp-skill.controller.ts +71 -0
  424. package/src/xp-catalog/xp-skill.service.ts +205 -0
  425. package/hedhog/data/video_resolution_profile.yaml +0 -7
  426. package/hedhog/frontend/app/video-resolution-profiles/page.tsx.ejs +0 -607
  427. package/hedhog/table/course_video_resolution_profile.yaml +0 -22
  428. package/hedhog/table/video_resolution_profile.yaml +0 -18
  429. package/src/video-resolution-profile/dto/create-video-resolution-profile.dto.ts +0 -16
  430. package/src/video-resolution-profile/dto/update-video-resolution-profile.dto.ts +0 -16
  431. package/src/video-resolution-profile/video-resolution-profile.controller.ts +0 -62
  432. package/src/video-resolution-profile/video-resolution-profile.mcp-tools.ts +0 -128
  433. package/src/video-resolution-profile/video-resolution-profile.module.ts +0 -13
  434. package/src/video-resolution-profile/video-resolution-profile.service.ts +0 -117
@@ -0,0 +1,707 @@
1
+ import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
2
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
3
+ import { PrismaService } from '@hed-hog/api-prisma';
4
+ import {
5
+ FileService,
6
+ SettingService,
7
+ WebhookCommandRegistry,
8
+ WebhookIntegrationService,
9
+ } from '@hed-hog/core';
10
+ import { QueueHandlerRegistry, QueueJobService } from '@hed-hog/queue';
11
+ import {
12
+ BadRequestException,
13
+ Inject,
14
+ Injectable,
15
+ Logger,
16
+ OnModuleInit,
17
+ forwardRef,
18
+ } from '@nestjs/common';
19
+ import { createWriteStream, promises as fs } from 'fs';
20
+ import { tmpdir } from 'os';
21
+ import { basename, extname, join } from 'path';
22
+ import { Readable } from 'stream';
23
+ import { pipeline } from 'stream/promises';
24
+ import { CourseVideoConversionService } from './course-video-conversion.service';
25
+ import { CourseVideoHlsService } from './course-video-hls.service';
26
+ import {
27
+ LMS_BULK_UPLOAD_DOWNLOAD_ORIGINAL_JOB,
28
+ LMS_BULK_UPLOAD_RECEIVE_VIDEO_COMMAND,
29
+ } from './lms-bulk-upload.constants';
30
+
31
+ type StorageContext = {
32
+ region: string;
33
+ bucket: string;
34
+ accessKeyId: string;
35
+ secretAccessKey: string;
36
+ sessionToken?: string;
37
+ roleArn?: string;
38
+ externalId?: string;
39
+ durationSeconds: number;
40
+ };
41
+
42
+ @Injectable()
43
+ export class LmsBulkUploadAutomationService implements OnModuleInit {
44
+ private readonly logger = new Logger(LmsBulkUploadAutomationService.name);
45
+
46
+ constructor(
47
+ @Inject(forwardRef(() => PrismaService))
48
+ private readonly prisma: PrismaService,
49
+ @Inject(forwardRef(() => SettingService))
50
+ private readonly settingService: SettingService,
51
+ @Inject(forwardRef(() => FileService))
52
+ private readonly fileService: FileService,
53
+ @Inject(forwardRef(() => QueueHandlerRegistry))
54
+ private readonly queueHandlers: QueueHandlerRegistry,
55
+ @Inject(forwardRef(() => QueueJobService))
56
+ private readonly queueJobService: QueueJobService,
57
+ @Inject(forwardRef(() => WebhookCommandRegistry))
58
+ private readonly commandRegistry: WebhookCommandRegistry,
59
+ @Inject(forwardRef(() => WebhookIntegrationService))
60
+ private readonly webhookIntegrationService: WebhookIntegrationService,
61
+ @Inject(forwardRef(() => CourseVideoConversionService))
62
+ private readonly courseVideoConversionService: CourseVideoConversionService,
63
+ @Inject(forwardRef(() => CourseVideoHlsService))
64
+ private readonly courseVideoHlsService: CourseVideoHlsService,
65
+ ) {}
66
+
67
+ onModuleInit(): void {
68
+ this.queueHandlers.register(LMS_BULK_UPLOAD_DOWNLOAD_ORIGINAL_JOB, this as any);
69
+ this.commandRegistry.register({
70
+ slug: LMS_BULK_UPLOAD_RECEIVE_VIDEO_COMMAND,
71
+ name: 'Receber vídeo do upload em massa',
72
+ description:
73
+ 'Reconhece o arquivo recebido no upload em massa, vincula a aula e agenda o download do vídeo original do S3.',
74
+ inputSchema: {
75
+ type: 'object',
76
+ properties: {
77
+ bucket: { type: 'string' },
78
+ key: { type: 'string' },
79
+ fileName: { type: 'string' },
80
+ uploadId: { type: 'string' },
81
+ },
82
+ additionalProperties: true,
83
+ },
84
+ handler: async (_params, context) => this.handleWebhookReceive(context ?? {}),
85
+ });
86
+
87
+ this.logger.log(
88
+ `Registered webhook command and queue handler for LMS bulk upload automation`,
89
+ );
90
+ }
91
+
92
+ private async handleWebhookReceive(context: Record<string, unknown>) {
93
+ const payload = this.normalizeWebhookBody(context.webhookBody ?? context);
94
+ const bucket = payload.bucket || '';
95
+ const key = payload.key || '';
96
+ const fileName = payload.fileName || basename(key);
97
+
98
+ if (!bucket || !key) {
99
+ throw new BadRequestException('Webhook payload does not contain bucket and key.');
100
+ }
101
+
102
+ const match = await this.findLessonMatch(fileName);
103
+ const uploadItem = await this.findUploadItem(payload.uploadId, fileName, key);
104
+ const now = new Date();
105
+
106
+ if (uploadItem) {
107
+ await this.prisma.$executeRawUnsafe(
108
+ `UPDATE "lms_bulk_upload_item"
109
+ SET
110
+ "status" = 'received',
111
+ "progress_percent" = 100,
112
+ "error_message" = NULL,
113
+ "uploaded_key" = $2,
114
+ "received_at" = $3,
115
+ "completed_at" = $3,
116
+ "matched_course_id" = $4,
117
+ "matched_session_id" = $5,
118
+ "matched_lesson_id" = $6,
119
+ "updated_at" = $3
120
+ WHERE "id" = $1`,
121
+ uploadItem.id,
122
+ key,
123
+ now,
124
+ match?.courseId ?? null,
125
+ match?.sessionId ?? null,
126
+ match?.lessonId ?? null,
127
+ );
128
+ }
129
+
130
+ if (!match) {
131
+ if (uploadItem) {
132
+ await this.prisma.$executeRawUnsafe(
133
+ `UPDATE "lms_bulk_upload_item"
134
+ SET "status" = 'lesson_not_found', "received_at" = $2, "updated_at" = $2
135
+ WHERE "id" = $1`,
136
+ uploadItem.id,
137
+ now,
138
+ );
139
+ }
140
+ return {
141
+ received: true,
142
+ matched: false,
143
+ uploadItemId: uploadItem?.id ?? null,
144
+ };
145
+ }
146
+
147
+ const job = await this.queueJobService.enqueue({
148
+ type: LMS_BULK_UPLOAD_DOWNLOAD_ORIGINAL_JOB,
149
+ queueName: LMS_BULK_UPLOAD_DOWNLOAD_ORIGINAL_JOB,
150
+ payload: {
151
+ bucket,
152
+ key,
153
+ fileName,
154
+ uploadItemId: uploadItem?.id ?? null,
155
+ userId: uploadItem?.userId ?? payload.userId ?? 0,
156
+ courseId: match.courseId,
157
+ sessionId: match.sessionId,
158
+ lessonId: match.lessonId,
159
+ matchedCourseId: match.courseId,
160
+ matchedSessionId: match.sessionId,
161
+ matchedLessonId: match.lessonId,
162
+ },
163
+ maxAttempts: 3,
164
+ sourceModule: 'lms',
165
+ sourceEntity: 'lms_bulk_upload_item',
166
+ sourceEntityId: uploadItem?.id ? String(uploadItem.id) : fileName,
167
+ priority: -6,
168
+ }, uploadItem?.userId ?? payload.userId ?? undefined);
169
+
170
+ return {
171
+ received: true,
172
+ matched: true,
173
+ uploadItemId: uploadItem?.id ?? null,
174
+ queueJobId: job.id,
175
+ lesson: {
176
+ courseId: match.courseId,
177
+ sessionId: match.sessionId,
178
+ lessonId: match.lessonId,
179
+ courseTitle: match.courseTitle,
180
+ sessionTitle: match.sessionTitle,
181
+ lessonTitle: match.lessonTitle,
182
+ courseLogoFileId: match.courseLogoFileId,
183
+ },
184
+ };
185
+ }
186
+
187
+ async handle(job: { id: number; payload: Record<string, any> }) {
188
+ const payload = job.payload as {
189
+ bucket?: string;
190
+ key?: string;
191
+ fileName?: string;
192
+ uploadItemId?: number | null;
193
+ userId?: number;
194
+ courseId?: number;
195
+ sessionId?: number;
196
+ lessonId?: number;
197
+ };
198
+
199
+ const bucket = String(payload.bucket ?? '').trim();
200
+ const key = String(payload.key ?? '').trim();
201
+ const fileName = String(payload.fileName ?? '').trim() || basename(key);
202
+ const userId = Number(payload.userId ?? 0);
203
+ const courseId = Number(payload.courseId ?? 0);
204
+ const sessionId = Number(payload.sessionId ?? 0);
205
+ const lessonId = Number(payload.lessonId ?? 0);
206
+
207
+ if (!bucket || !key || !userId || !courseId || !sessionId || !lessonId) {
208
+ throw new BadRequestException('Invalid LMS bulk upload queue payload.');
209
+ }
210
+
211
+ const storage = await this.resolveStorageContext();
212
+ const tempCredentials = await this.fileService.getTemporaryCredentials({
213
+ accessKeyId: storage.accessKeyId,
214
+ secretAccessKey: storage.secretAccessKey,
215
+ sessionToken: storage.sessionToken,
216
+ region: storage.region,
217
+ roleArn: storage.roleArn,
218
+ externalId: storage.externalId,
219
+ durationSeconds: storage.durationSeconds,
220
+ sessionName: `lms-bulk-upload-u${userId}-${Date.now()}`,
221
+ });
222
+
223
+ const workDir = await fs.mkdtemp(join(tmpdir(), 'lms-bulk-upload-'));
224
+ const tempFilePath = join(
225
+ workDir,
226
+ `${Date.now()}-${basename(fileName || key)}${extname(fileName || key) || '.mp4'}`,
227
+ );
228
+
229
+ try {
230
+ const s3 = new S3Client({
231
+ region: storage.region,
232
+ credentials: {
233
+ accessKeyId: tempCredentials.AccessKeyId,
234
+ secretAccessKey: tempCredentials.SecretAccessKey,
235
+ sessionToken: tempCredentials.SessionToken,
236
+ },
237
+ });
238
+
239
+ const response = await s3.send(
240
+ new GetObjectCommand({ Bucket: bucket, Key: key }),
241
+ );
242
+ const body = response.Body as any;
243
+ if (!body) {
244
+ throw new BadRequestException('Could not read the S3 object body.');
245
+ }
246
+
247
+ const readable = typeof body.pipe === 'function' ? body : Readable.fromWeb(body);
248
+ await pipeline(readable, createWriteStream(tempFilePath));
249
+
250
+ const uploaded = await this.fileService.uploadFromPath(
251
+ 'lms/lessons/originals',
252
+ tempFilePath,
253
+ {
254
+ originalname: fileName,
255
+ filename: fileName,
256
+ mimetype: this.inferVideoMimeType(fileName || key),
257
+ },
258
+ );
259
+
260
+ await this.courseVideoHlsService.enqueueHls({
261
+ userId,
262
+ courseId,
263
+ sessionId,
264
+ lessonId,
265
+ originalFileId: uploaded.id,
266
+ });
267
+
268
+ if (Number.isInteger(Number(payload.uploadItemId)) && Number(payload.uploadItemId) > 0) {
269
+ await this.prisma.$executeRawUnsafe(
270
+ `UPDATE "lms_bulk_upload_item"
271
+ SET "status" = 'done', "completed_at" = NOW(), "updated_at" = NOW()
272
+ WHERE "id" = $1`,
273
+ Number(payload.uploadItemId),
274
+ ).catch(() => undefined);
275
+ }
276
+
277
+ return {
278
+ success: true,
279
+ queueJobId: job.id,
280
+ originalFileId: uploaded.id,
281
+ };
282
+ } catch (error) {
283
+ if (Number.isInteger(Number(payload.uploadItemId)) && Number(payload.uploadItemId) > 0) {
284
+ await this.prisma.$executeRawUnsafe(
285
+ `UPDATE "lms_bulk_upload_item"
286
+ SET "status" = 'error', "error_message" = $2, "progress_percent" = 100, "updated_at" = NOW()
287
+ WHERE "id" = $1`,
288
+ Number(payload.uploadItemId),
289
+ error instanceof Error ? error.message : 'Unknown error',
290
+ ).catch(() => undefined);
291
+ }
292
+
293
+ throw error;
294
+ } finally {
295
+ await fs.rm(workDir, { recursive: true, force: true }).catch(() => undefined);
296
+ }
297
+ }
298
+
299
+ private async findUploadItem(
300
+ uploadId: string | undefined,
301
+ fileName: string,
302
+ key: string,
303
+ ): Promise<{ id: number; userId: number } | null> {
304
+ const rows = await this.prisma.$queryRawUnsafe<Array<{ id: number; user_id: number }>>(
305
+ `SELECT i."id", s."user_id"
306
+ FROM "lms_bulk_upload_item" i
307
+ INNER JOIN "lms_bulk_upload_session" s ON s."id" = i."session_id"
308
+ WHERE ($1::text IS NOT NULL AND i."upload_id" = $1)
309
+ OR i."file_name" = $2
310
+ OR i."uploaded_key" = $3
311
+ OR i."uploaded_key" = $2
312
+ ORDER BY i."id" DESC
313
+ LIMIT 1`,
314
+ uploadId || null,
315
+ fileName,
316
+ key,
317
+ );
318
+
319
+ const row = rows[0];
320
+ if (!row) {
321
+ return null;
322
+ }
323
+
324
+ return { id: Number(row.id), userId: Number(row.user_id) };
325
+ }
326
+
327
+ private async findLessonMatch(fileName: string) {
328
+ const parsed = this.parseFilename(fileName);
329
+ if (!parsed) {
330
+ return null;
331
+ }
332
+
333
+ const courses = await (this.prisma as any).course.findMany({
334
+ where: {
335
+ code: { equals: parsed.courseCode, mode: 'insensitive' },
336
+ },
337
+ select: {
338
+ id: true,
339
+ code: true,
340
+ title: true,
341
+ name: true,
342
+ slug: true,
343
+ course_image: {
344
+ select: {
345
+ file_id: true,
346
+ image_type: { select: { slug: true } },
347
+ },
348
+ },
349
+ course_module: {
350
+ orderBy: { order: 'asc' },
351
+ select: {
352
+ id: true,
353
+ title: true,
354
+ order: true,
355
+ course_lesson: {
356
+ orderBy: { order: 'asc' },
357
+ select: {
358
+ id: true,
359
+ title: true,
360
+ order: true,
361
+ },
362
+ },
363
+ },
364
+ },
365
+ },
366
+ });
367
+
368
+ for (const course of courses ?? []) {
369
+ for (const session of course.course_module ?? []) {
370
+ const sessionNumeric = parsed.sessionCode.replace(/[^0-9]/g, '');
371
+ const moduleNumeric = String(Math.max(Number(session.order ?? 0), 0)).padStart(2, '0');
372
+ if (!sessionNumeric || sessionNumeric !== moduleNumeric) {
373
+ continue;
374
+ }
375
+
376
+ for (const lesson of session.course_lesson ?? []) {
377
+ if (this.normalizeComparableText(lesson.title) !== parsed.lessonTitle) {
378
+ continue;
379
+ }
380
+
381
+ const logo = course.course_image?.find(
382
+ (image: any) => image.image_type?.slug === 'course-logo',
383
+ );
384
+
385
+ return {
386
+ courseId: Number(course.id),
387
+ courseTitle: String(course.title ?? course.name ?? ''),
388
+ courseSlug: String(course.slug ?? ''),
389
+ courseLogoFileId: logo?.file_id ? Number(logo.file_id) : null,
390
+ sessionId: Number(session.id),
391
+ sessionTitle: String(session.title ?? ''),
392
+ lessonId: Number(lesson.id),
393
+ lessonTitle: String(lesson.title ?? ''),
394
+ };
395
+ }
396
+ }
397
+ }
398
+
399
+ return null;
400
+ }
401
+
402
+ private parseFilename(fileName: string) {
403
+ const withoutTimestamp = fileName.replace(/^\d+[-_]+/, '');
404
+ const base = this.normalizeComparableText(withoutTimestamp.replace(/\.[^.]+$/, ''));
405
+ const parts = base.split('_').filter(Boolean);
406
+ if (parts.length < 3) {
407
+ return null;
408
+ }
409
+
410
+ const RESOLUTION_SUFFIXES = new Set(['1080', '720', '480', '360', '240', '4k', '2k', 'uhd', 'fhd', 'hd', 'sd']);
411
+ let titleParts = parts.slice(2);
412
+ if (titleParts.length > 1 && RESOLUTION_SUFFIXES.has(titleParts[titleParts.length - 1])) {
413
+ titleParts = titleParts.slice(0, -1);
414
+ }
415
+
416
+ return {
417
+ sessionCode: this.normalizeComparableText(parts[0]),
418
+ courseCode: this.normalizeComparableText(parts[1]),
419
+ lessonTitle: this.normalizeComparableText(titleParts.join('_')),
420
+ };
421
+ }
422
+
423
+ private normalizeWebhookBody(body: unknown) {
424
+ const raw = typeof body === 'string' ? this.safeJsonParse(body) : (body ?? {});
425
+ const records = Array.isArray((raw as any).Records)
426
+ ? (raw as any).Records
427
+ : Array.isArray((raw as any).records)
428
+ ? (raw as any).records
429
+ : [];
430
+ const record = records[0] ?? (raw as any).detail ?? raw;
431
+ const bucket = this.firstString(
432
+ (raw as any)?.bucketName,
433
+ typeof (raw as any)?.bucket === 'string' ? (raw as any)?.bucket : undefined,
434
+ (raw as any)?.bucket?.name,
435
+ record?.s3?.bucket?.name,
436
+ record?.bucket?.name,
437
+ record?.detail?.bucket?.name,
438
+ );
439
+ const key = this.decodeKey(
440
+ this.firstString(
441
+ (raw as any)?.key,
442
+ (raw as any)?.object?.key,
443
+ record?.s3?.object?.key,
444
+ record?.object?.key,
445
+ record?.detail?.object?.key,
446
+ ),
447
+ );
448
+
449
+ return {
450
+ bucket,
451
+ key,
452
+ fileName:
453
+ this.firstString(
454
+ (raw as any)?.fileName,
455
+ (raw as any)?.file_name,
456
+ record?.fileName,
457
+ record?.file_name,
458
+ ) || (key ? basename(key) : ''),
459
+ uploadId: this.firstString(
460
+ (raw as any)?.uploadId,
461
+ (raw as any)?.upload_id,
462
+ record?.uploadId,
463
+ record?.upload_id,
464
+ ),
465
+ userId:
466
+ Number(
467
+ (raw as any)?.userId ?? (raw as any)?.user_id ?? record?.userId ?? 0,
468
+ ) || 0,
469
+ };
470
+ }
471
+
472
+ private async resolveStorageContext(): Promise<StorageContext> {
473
+ const settings = await this.settingService.getSettingValues([
474
+ 'lms-bulk-upload-storage-profile-id',
475
+ 'lms-bulk-upload-sts-duration-seconds',
476
+ ]);
477
+
478
+ const storageProfileId = Number(settings['lms-bulk-upload-storage-profile-id'] ?? 0);
479
+
480
+ if (!Number.isFinite(storageProfileId) || storageProfileId <= 0) {
481
+ throw new BadRequestException('O perfil de armazenamento para upload em massa não está configurado. Acesse as configurações e defina o perfil de integração S3.');
482
+ }
483
+
484
+ const profile = await (this.prisma as any).integration_profile.findUnique({
485
+ where: { id: storageProfileId },
486
+ include: {
487
+ integration_type: { select: { slug: true } },
488
+ integration_provider: { select: { slug: true } },
489
+ },
490
+ });
491
+
492
+ if (!profile) {
493
+ throw new BadRequestException('Bulk upload storage profile not found.');
494
+ }
495
+
496
+ const config = profile.config as Record<string, any>;
497
+ const bucket = String(config.bucket ?? '').trim();
498
+ const accessKeyId = String(config.access_key_id ?? '').trim();
499
+ const secretAccessKey = String(config.secret_access_key ?? '').trim();
500
+
501
+ if (!bucket || !accessKeyId || !secretAccessKey) {
502
+ throw new BadRequestException('Bulk upload storage profile does not expose AWS credentials.');
503
+ }
504
+
505
+ return {
506
+ region: String(config.region ?? '').trim() || 'us-east-1',
507
+ bucket,
508
+ accessKeyId,
509
+ secretAccessKey,
510
+ sessionToken: String(config.session_token ?? '').trim() || undefined,
511
+ roleArn: String(config.role_arn ?? '').trim() || undefined,
512
+ externalId: String(config.external_id ?? '').trim() || undefined,
513
+ durationSeconds: Math.max(
514
+ 900,
515
+ Number(settings['lms-bulk-upload-sts-duration-seconds'] ?? 3600) || 3600,
516
+ ),
517
+ };
518
+ }
519
+
520
+ private inferVideoMimeType(filename: string) {
521
+ switch (extname(filename).toLowerCase()) {
522
+ case '.mp4':
523
+ case '.m4v':
524
+ return 'video/mp4';
525
+ case '.mov':
526
+ return 'video/quicktime';
527
+ case '.mkv':
528
+ return 'video/x-matroska';
529
+ case '.webm':
530
+ return 'video/webm';
531
+ case '.avi':
532
+ return 'video/x-msvideo';
533
+ case '.wmv':
534
+ return 'video/x-ms-wmv';
535
+ case '.flv':
536
+ return 'video/x-flv';
537
+ case '.ogv':
538
+ return 'video/ogg';
539
+ case '.m2ts':
540
+ case '.ts':
541
+ return 'video/mp2t';
542
+ default:
543
+ return 'video/mp4';
544
+ }
545
+ }
546
+
547
+ private decodeKey(value: string) {
548
+ if (!value) {
549
+ return value;
550
+ }
551
+
552
+ try {
553
+ return decodeURIComponent(value.replace(/\+/g, ' '));
554
+ } catch {
555
+ return value;
556
+ }
557
+ }
558
+
559
+ private firstString(...values: Array<unknown>) {
560
+ for (const value of values) {
561
+ const normalized = String(value ?? '').trim();
562
+ if (normalized) {
563
+ return normalized;
564
+ }
565
+ }
566
+
567
+ return '';
568
+ }
569
+
570
+ private normalizeComparableText(value: string) {
571
+ return String(value ?? '')
572
+ .normalize('NFD')
573
+ .replace(/[\u0300-\u036f]/g, '')
574
+ .replace(/[^a-zA-Z0-9]+/g, '_')
575
+ .replace(/_+/g, '_')
576
+ .replace(/^_+|_+$/g, '')
577
+ .toLowerCase();
578
+ }
579
+
580
+ private formatCode(prefix: 'S' | 'A', order: number) {
581
+ return `${prefix}${String(Math.max(order, 0)).padStart(2, '0')}`;
582
+ }
583
+
584
+ async linkLessonManually(
585
+ itemId: number,
586
+ params: { courseId: number; sessionId: number; lessonId: number; userId: number },
587
+ ) {
588
+ const items = await this.prisma.$queryRawUnsafe<
589
+ Array<{ id: number; uploaded_key: string | null; file_name: string }>
590
+ >(
591
+ `SELECT i."id", i."uploaded_key", i."file_name"
592
+ FROM "lms_bulk_upload_item" i
593
+ WHERE i."id" = $1
594
+ LIMIT 1`,
595
+ itemId,
596
+ );
597
+
598
+ const item = items[0];
599
+ if (!item) {
600
+ throw new BadRequestException('Upload item not found.');
601
+ }
602
+
603
+ const lessonRows = await this.prisma.$queryRawUnsafe<Array<{ id: number }>>(
604
+ `SELECT cl."id"
605
+ FROM "course_lesson" cl
606
+ INNER JOIN "course_module" cm ON cm."id" = cl."course_module_id"
607
+ WHERE cl."id" = $1 AND cm."id" = $2 AND cm."course_id" = $3
608
+ LIMIT 1`,
609
+ params.lessonId,
610
+ params.sessionId,
611
+ params.courseId,
612
+ );
613
+
614
+ if (!lessonRows[0]) {
615
+ throw new BadRequestException('Lesson does not belong to the given course and module.');
616
+ }
617
+
618
+ const now = new Date();
619
+ await this.prisma.$executeRawUnsafe(
620
+ `UPDATE "lms_bulk_upload_item"
621
+ SET "matched_course_id" = $2,
622
+ "matched_session_id" = $3,
623
+ "matched_lesson_id" = $4,
624
+ "status" = 'received',
625
+ "updated_at" = $5
626
+ WHERE "id" = $1`,
627
+ itemId,
628
+ params.courseId,
629
+ params.sessionId,
630
+ params.lessonId,
631
+ now,
632
+ );
633
+
634
+ const bucket = await this.resolveStorageContext();
635
+ const key = String(item.uploaded_key ?? '').trim();
636
+
637
+ if (!key) {
638
+ throw new BadRequestException('Upload item does not have a stored S3 key yet.');
639
+ }
640
+
641
+ const job = await this.queueJobService.enqueue(
642
+ {
643
+ type: LMS_BULK_UPLOAD_DOWNLOAD_ORIGINAL_JOB,
644
+ queueName: LMS_BULK_UPLOAD_DOWNLOAD_ORIGINAL_JOB,
645
+ payload: {
646
+ bucket: bucket.bucket,
647
+ key,
648
+ fileName: item.file_name,
649
+ uploadItemId: itemId,
650
+ userId: params.userId,
651
+ courseId: params.courseId,
652
+ sessionId: params.sessionId,
653
+ lessonId: params.lessonId,
654
+ },
655
+ maxAttempts: 3,
656
+ sourceModule: 'lms',
657
+ sourceEntity: 'lms_bulk_upload_item',
658
+ sourceEntityId: String(itemId),
659
+ priority: -6,
660
+ },
661
+ params.userId,
662
+ );
663
+
664
+ return { success: true, queueJobId: job.id };
665
+ }
666
+
667
+ async getItemVideoPresignedUrl(itemId: number): Promise<{ url: string; expiresIn: number }> {
668
+ const items = await this.prisma.$queryRawUnsafe<
669
+ Array<{ uploaded_key: string | null }>
670
+ >(
671
+ `SELECT "uploaded_key" FROM "lms_bulk_upload_item" WHERE "id" = $1 LIMIT 1`,
672
+ itemId,
673
+ );
674
+
675
+ const key = String(items[0]?.uploaded_key ?? '').trim();
676
+ if (!key) {
677
+ throw new BadRequestException('Upload item does not have a stored S3 key.');
678
+ }
679
+
680
+ const storage = await this.resolveStorageContext();
681
+ const s3 = new S3Client({
682
+ region: storage.region,
683
+ credentials: {
684
+ accessKeyId: storage.accessKeyId,
685
+ secretAccessKey: storage.secretAccessKey,
686
+ sessionToken: storage.sessionToken,
687
+ },
688
+ });
689
+
690
+ const expiresIn = 3600;
691
+ const url = await getSignedUrl(
692
+ s3,
693
+ new GetObjectCommand({ Bucket: storage.bucket, Key: key }),
694
+ { expiresIn },
695
+ );
696
+
697
+ return { url, expiresIn };
698
+ }
699
+
700
+ private safeJsonParse(value: string) {
701
+ try {
702
+ return JSON.parse(value);
703
+ } catch {
704
+ return {};
705
+ }
706
+ }
707
+ }