@hed-hog/lms 0.0.364 → 0.0.366

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 (290) hide show
  1. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts +1 -0
  2. package/dist/bitcode-wallet/bitcode-wallet.service.d.ts.map +1 -1
  3. package/dist/bitcode-wallet/bitcode-wallet.service.js +22 -3
  4. package/dist/bitcode-wallet/bitcode-wallet.service.js.map +1 -1
  5. package/dist/class-group/class-group.controller.d.ts +1 -0
  6. package/dist/class-group/class-group.controller.d.ts.map +1 -1
  7. package/dist/class-group/class-group.service.d.ts +1 -0
  8. package/dist/class-group/class-group.service.d.ts.map +1 -1
  9. package/dist/course/course-export-scorm12-worker.service.d.ts +21 -0
  10. package/dist/course/course-export-scorm12-worker.service.d.ts.map +1 -0
  11. package/dist/course/course-export-scorm12-worker.service.js +109 -0
  12. package/dist/course/course-export-scorm12-worker.service.js.map +1 -0
  13. package/dist/course/course-export-scorm12.service.d.ts +42 -0
  14. package/dist/course/course-export-scorm12.service.d.ts.map +1 -0
  15. package/dist/course/course-export-scorm12.service.js +628 -0
  16. package/dist/course/course-export-scorm12.service.js.map +1 -0
  17. package/dist/course/course-export.service.d.ts +84 -0
  18. package/dist/course/course-export.service.d.ts.map +1 -0
  19. package/dist/course/course-export.service.js +237 -0
  20. package/dist/course/course-export.service.js.map +1 -0
  21. package/dist/course/course-structure.controller.d.ts +20 -10
  22. package/dist/course/course-structure.controller.d.ts.map +1 -1
  23. package/dist/course/course-structure.controller.js +20 -4
  24. package/dist/course/course-structure.controller.js.map +1 -1
  25. package/dist/course/course-structure.service.d.ts +12 -4
  26. package/dist/course/course-structure.service.d.ts.map +1 -1
  27. package/dist/course/course-structure.service.js +98 -23
  28. package/dist/course/course-structure.service.js.map +1 -1
  29. package/dist/course/course-video-agent-pipeline.service.d.ts +70 -0
  30. package/dist/course/course-video-agent-pipeline.service.d.ts.map +1 -0
  31. package/dist/course/course-video-agent-pipeline.service.js +398 -0
  32. package/dist/course/course-video-agent-pipeline.service.js.map +1 -0
  33. package/dist/course/course-video-hls.service.d.ts +71 -0
  34. package/dist/course/course-video-hls.service.d.ts.map +1 -0
  35. package/dist/course/course-video-hls.service.js +784 -0
  36. package/dist/course/course-video-hls.service.js.map +1 -0
  37. package/dist/course/course.controller.d.ts +47 -13
  38. package/dist/course/course.controller.d.ts.map +1 -1
  39. package/dist/course/course.controller.js +40 -26
  40. package/dist/course/course.controller.js.map +1 -1
  41. package/dist/course/course.mcp-tools.js +1 -1
  42. package/dist/course/course.mcp-tools.js.map +1 -1
  43. package/dist/course/course.module.d.ts.map +1 -1
  44. package/dist/course/course.module.js +16 -0
  45. package/dist/course/course.module.js.map +1 -1
  46. package/dist/course/course.service.d.ts +8 -9
  47. package/dist/course/course.service.d.ts.map +1 -1
  48. package/dist/course/course.service.js +93 -50
  49. package/dist/course/course.service.js.map +1 -1
  50. package/dist/course/dto/cleanup-course-storage.dto.d.ts +1 -1
  51. package/dist/course/dto/cleanup-course-storage.dto.d.ts.map +1 -1
  52. package/dist/course/dto/cleanup-course-storage.dto.js +1 -0
  53. package/dist/course/dto/cleanup-course-storage.dto.js.map +1 -1
  54. package/dist/course/dto/cleanup-upload-history.dto.d.ts +1 -1
  55. package/dist/course/dto/cleanup-upload-history.dto.d.ts.map +1 -1
  56. package/dist/course/dto/cleanup-upload-history.dto.js +1 -1
  57. package/dist/course/dto/cleanup-upload-history.dto.js.map +1 -1
  58. package/dist/course/dto/create-course-bulk-job.dto.d.ts +2 -1
  59. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -1
  60. package/dist/course/dto/create-course-bulk-job.dto.js +6 -1
  61. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -1
  62. package/dist/course/dto/create-course-export.dto.d.ts +14 -0
  63. package/dist/course/dto/create-course-export.dto.d.ts.map +1 -0
  64. package/dist/course/dto/create-course-export.dto.js +71 -0
  65. package/dist/course/dto/create-course-export.dto.js.map +1 -0
  66. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +2 -2
  67. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  68. package/dist/course/dto/create-course-structure-lesson.dto.js +3 -2
  69. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  70. package/dist/course/ffmpeg.util.d.ts +10 -0
  71. package/dist/course/ffmpeg.util.d.ts.map +1 -0
  72. package/dist/course/ffmpeg.util.js +79 -0
  73. package/dist/course/ffmpeg.util.js.map +1 -0
  74. package/dist/course/lms-bulk-upload-automation.service.d.ts +18 -1
  75. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -1
  76. package/dist/course/lms-bulk-upload-automation.service.js +106 -8
  77. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -1
  78. package/dist/course/lms-bulk-upload-infra.service.d.ts +1 -0
  79. package/dist/course/lms-bulk-upload-infra.service.d.ts.map +1 -1
  80. package/dist/course/lms-bulk-upload-infra.service.js +32 -8
  81. package/dist/course/lms-bulk-upload-infra.service.js.map +1 -1
  82. package/dist/course/lms-bulk-upload.controller.d.ts +30 -3
  83. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
  84. package/dist/course/lms-bulk-upload.controller.js +43 -2
  85. package/dist/course/lms-bulk-upload.controller.js.map +1 -1
  86. package/dist/course/lms-bulk-upload.service.d.ts +11 -0
  87. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
  88. package/dist/course/lms-bulk-upload.service.js +59 -6
  89. package/dist/course/lms-bulk-upload.service.js.map +1 -1
  90. package/dist/course/lms-setting.controller.d.ts +2 -1
  91. package/dist/course/lms-setting.controller.d.ts.map +1 -1
  92. package/dist/course/lms-setting.controller.js +4 -2
  93. package/dist/course/lms-setting.controller.js.map +1 -1
  94. package/dist/course/scorm12-schemas.d.ts +4 -0
  95. package/dist/course/scorm12-schemas.d.ts.map +1 -0
  96. package/dist/course/scorm12-schemas.js +9 -0
  97. package/dist/course/scorm12-schemas.js.map +1 -0
  98. package/dist/enterprise/training/training-admin.controller.d.ts +2 -0
  99. package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
  100. package/dist/enterprise/training/training-admin.service.d.ts +2 -0
  101. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
  102. package/dist/enterprise/training/training-student.service.d.ts +51 -0
  103. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  104. package/dist/enterprise/training/training-student.service.js +217 -4
  105. package/dist/enterprise/training/training-student.service.js.map +1 -1
  106. package/dist/evaluation/evaluation.service.d.ts +18 -0
  107. package/dist/evaluation/evaluation.service.d.ts.map +1 -1
  108. package/dist/evaluation/evaluation.service.js +125 -0
  109. package/dist/evaluation/evaluation.service.js.map +1 -1
  110. package/dist/exam/dto/create-standalone-question.dto.d.ts +12 -0
  111. package/dist/exam/dto/create-standalone-question.dto.d.ts.map +1 -0
  112. package/dist/exam/dto/create-standalone-question.dto.js +70 -0
  113. package/dist/exam/dto/create-standalone-question.dto.js.map +1 -0
  114. package/dist/exam/exam.module.d.ts.map +1 -1
  115. package/dist/exam/exam.module.js +2 -1
  116. package/dist/exam/exam.module.js.map +1 -1
  117. package/dist/exam/exam.service.d.ts +21 -0
  118. package/dist/exam/exam.service.d.ts.map +1 -1
  119. package/dist/exam/exam.service.js +80 -0
  120. package/dist/exam/exam.service.js.map +1 -1
  121. package/dist/exam/question.controller.d.ts +27 -0
  122. package/dist/exam/question.controller.d.ts.map +1 -0
  123. package/dist/exam/question.controller.js +53 -0
  124. package/dist/exam/question.controller.js.map +1 -0
  125. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +4 -0
  126. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -1
  127. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +161 -25
  128. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -1
  129. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -1
  130. package/dist/lms-commerce-access.subscriber.d.ts +11 -0
  131. package/dist/lms-commerce-access.subscriber.d.ts.map +1 -0
  132. package/dist/lms-commerce-access.subscriber.js +74 -0
  133. package/dist/lms-commerce-access.subscriber.js.map +1 -0
  134. package/dist/lms.module.d.ts.map +1 -1
  135. package/dist/lms.module.js +16 -5
  136. package/dist/lms.module.js.map +1 -1
  137. package/dist/platforma/dto/heartbeat.dto.d.ts +9 -0
  138. package/dist/platforma/dto/heartbeat.dto.d.ts.map +1 -0
  139. package/dist/platforma/dto/heartbeat.dto.js +50 -0
  140. package/dist/platforma/dto/heartbeat.dto.js.map +1 -0
  141. package/dist/platforma/handlers/emit-certificate.handler.d.ts +27 -0
  142. package/dist/platforma/handlers/emit-certificate.handler.d.ts.map +1 -0
  143. package/dist/platforma/handlers/emit-certificate.handler.js +117 -0
  144. package/dist/platforma/handlers/emit-certificate.handler.js.map +1 -0
  145. package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts +31 -0
  146. package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts.map +1 -0
  147. package/dist/platforma/handlers/lesson-heartbeat.handler.js +281 -0
  148. package/dist/platforma/handlers/lesson-heartbeat.handler.js.map +1 -0
  149. package/dist/platforma/platforma-heartbeat.service.d.ts +10 -0
  150. package/dist/platforma/platforma-heartbeat.service.d.ts.map +1 -0
  151. package/dist/platforma/platforma-heartbeat.service.js +50 -0
  152. package/dist/platforma/platforma-heartbeat.service.js.map +1 -0
  153. package/dist/platforma/platforma-performance.service.d.ts +121 -0
  154. package/dist/platforma/platforma-performance.service.d.ts.map +1 -0
  155. package/dist/platforma/platforma-performance.service.js +500 -0
  156. package/dist/platforma/platforma-performance.service.js.map +1 -0
  157. package/dist/platforma/platforma-search.service.d.ts +21 -0
  158. package/dist/platforma/platforma-search.service.d.ts.map +1 -0
  159. package/dist/platforma/platforma-search.service.js +64 -0
  160. package/dist/platforma/platforma-search.service.js.map +1 -0
  161. package/dist/platforma/platforma-video.service.d.ts +39 -0
  162. package/dist/platforma/platforma-video.service.d.ts.map +1 -0
  163. package/dist/platforma/platforma-video.service.js +301 -0
  164. package/dist/platforma/platforma-video.service.js.map +1 -0
  165. package/dist/platforma/platforma.controller.d.ts +209 -1
  166. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  167. package/dist/platforma/platforma.controller.js +208 -2
  168. package/dist/platforma/platforma.controller.js.map +1 -1
  169. package/dist/realtime/lms-realtime.controller.d.ts +2 -0
  170. package/dist/realtime/lms-realtime.controller.d.ts.map +1 -1
  171. package/dist/realtime/lms-realtime.controller.js +31 -0
  172. package/dist/realtime/lms-realtime.controller.js.map +1 -1
  173. package/dist/realtime/lms-realtime.service.d.ts +1 -1
  174. package/dist/realtime/lms-realtime.service.d.ts.map +1 -1
  175. package/dist/realtime/lms-realtime.service.js.map +1 -1
  176. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts +5 -0
  177. package/dist/student-xp/dto/grant-skill-card-xp.dto.d.ts.map +1 -0
  178. package/dist/student-xp/dto/grant-skill-card-xp.dto.js +26 -0
  179. package/dist/student-xp/dto/grant-skill-card-xp.dto.js.map +1 -0
  180. package/dist/student-xp/student-xp.controller.d.ts +15 -0
  181. package/dist/student-xp/student-xp.controller.d.ts.map +1 -1
  182. package/dist/student-xp/student-xp.controller.js +24 -0
  183. package/dist/student-xp/student-xp.controller.js.map +1 -1
  184. package/dist/student-xp/student-xp.service.d.ts +16 -0
  185. package/dist/student-xp/student-xp.service.d.ts.map +1 -1
  186. package/dist/student-xp/student-xp.service.js +51 -1
  187. package/dist/student-xp/student-xp.service.js.map +1 -1
  188. package/hedhog/data/evaluation_topic.yaml +17 -0
  189. package/hedhog/data/menu.yaml +0 -17
  190. package/hedhog/data/queue_definition.yaml +48 -0
  191. package/hedhog/data/route.yaml +94 -124
  192. package/hedhog/data/setting_group.yaml +19 -19
  193. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +337 -41
  194. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +182 -29
  195. package/hedhog/frontend/app/classes/_components/classes-calendar-view.tsx.ejs +277 -0
  196. package/hedhog/frontend/app/classes/page.tsx.ejs +127 -20
  197. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +69 -4
  198. package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +420 -0
  199. package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +308 -0
  200. package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +158 -45
  201. package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +13 -13
  202. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +11 -23
  203. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +51 -63
  204. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +8 -3
  205. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +31 -8
  206. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +16 -9
  207. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +201 -401
  208. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +378 -690
  209. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -2
  210. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +3 -9
  211. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +1 -1
  212. package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +1 -8
  213. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +6 -10
  214. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +49 -0
  215. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -3
  216. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +2 -1
  217. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +106 -0
  218. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +28 -1
  219. package/hedhog/frontend/app/courses/[id]/structure/_data/use-lms-settings-query.ts.ejs +0 -2
  220. package/hedhog/frontend/app/courses/page.tsx.ejs +85 -9
  221. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +6 -0
  222. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-calendar-tab.tsx.ejs +264 -0
  223. package/hedhog/frontend/app/enterprise/page.tsx.ejs +104 -47
  224. package/hedhog/frontend/app/exams/page.tsx.ejs +38 -4
  225. package/hedhog/frontend/app/instructors/page.tsx.ejs +87 -46
  226. package/hedhog/frontend/app/paths/page.tsx.ejs +38 -4
  227. package/hedhog/frontend/app/training/page.tsx.ejs +38 -4
  228. package/hedhog/frontend/messages/en.json +44 -28
  229. package/hedhog/frontend/messages/pt.json +47 -29
  230. package/hedhog/table/course_enrollment.yaml +3 -0
  231. package/hedhog/table/course_export.yaml +62 -0
  232. package/hedhog/table/lesson_view_event.yaml +66 -0
  233. package/package.json +14 -9
  234. package/src/bitcode-wallet/bitcode-wallet.service.ts +43 -4
  235. package/src/course/course-export-scorm12-worker.service.ts +124 -0
  236. package/src/course/course-export-scorm12.service.ts +668 -0
  237. package/src/course/course-export.service.ts +280 -0
  238. package/src/course/course-structure.controller.ts +16 -2
  239. package/src/course/course-structure.service.ts +100 -7
  240. package/src/course/course-video-agent-pipeline.service.ts +471 -0
  241. package/src/course/course-video-hls.service.ts +966 -0
  242. package/src/course/course.controller.ts +33 -19
  243. package/src/course/course.mcp-tools.ts +1 -1
  244. package/src/course/course.module.ts +16 -0
  245. package/src/course/course.service.ts +119 -61
  246. package/src/course/dto/cleanup-course-storage.dto.ts +1 -0
  247. package/src/course/dto/cleanup-upload-history.dto.ts +1 -1
  248. package/src/course/dto/create-course-bulk-job.dto.ts +7 -3
  249. package/src/course/dto/create-course-export.dto.ts +56 -0
  250. package/src/course/dto/create-course-structure-lesson.dto.ts +4 -3
  251. package/src/course/ffmpeg.util.ts +65 -0
  252. package/src/course/lms-bulk-upload-automation.service.ts +156 -6
  253. package/src/course/lms-bulk-upload-infra.service.ts +39 -6
  254. package/src/course/lms-bulk-upload.controller.ts +32 -2
  255. package/src/course/lms-bulk-upload.service.ts +70 -7
  256. package/src/course/lms-setting.controller.ts +4 -2
  257. package/src/course/scorm12-schemas.ts +9 -0
  258. package/src/enterprise/training/training-student.service.ts +221 -2
  259. package/src/evaluation/evaluation.service.ts +123 -0
  260. package/src/exam/dto/create-standalone-question.dto.ts +66 -0
  261. package/src/exam/exam.module.ts +2 -1
  262. package/src/exam/exam.service.ts +86 -0
  263. package/src/exam/question.controller.ts +28 -0
  264. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +205 -31
  265. package/src/lms-commerce-access.subscriber.ts +88 -0
  266. package/src/lms.module.ts +16 -5
  267. package/src/platforma/dto/heartbeat.dto.ts +30 -0
  268. package/src/platforma/handlers/emit-certificate.handler.ts +117 -0
  269. package/src/platforma/handlers/lesson-heartbeat.handler.ts +343 -0
  270. package/src/platforma/platforma-heartbeat.service.ts +33 -0
  271. package/src/platforma/platforma-performance.service.ts +606 -0
  272. package/src/platforma/platforma-search.service.ts +48 -0
  273. package/src/platforma/platforma-video.service.ts +346 -0
  274. package/src/platforma/platforma.controller.ts +137 -1
  275. package/src/platforma/platforma.service.ts +268 -268
  276. package/src/realtime/lms-realtime.controller.ts +27 -1
  277. package/src/realtime/lms-realtime.service.ts +2 -1
  278. package/src/student-xp/dto/grant-skill-card-xp.dto.ts +10 -0
  279. package/src/student-xp/student-xp.controller.ts +18 -2
  280. package/src/student-xp/student-xp.service.ts +84 -2
  281. package/hedhog/data/video_resolution_profile.yaml +0 -7
  282. package/hedhog/frontend/app/video-resolution-profiles/page.tsx.ejs +0 -607
  283. package/hedhog/table/course_video_resolution_profile.yaml +0 -22
  284. package/hedhog/table/video_resolution_profile.yaml +0 -18
  285. package/src/video-resolution-profile/dto/create-video-resolution-profile.dto.ts +0 -16
  286. package/src/video-resolution-profile/dto/update-video-resolution-profile.dto.ts +0 -16
  287. package/src/video-resolution-profile/video-resolution-profile.controller.ts +0 -62
  288. package/src/video-resolution-profile/video-resolution-profile.mcp-tools.ts +0 -128
  289. package/src/video-resolution-profile/video-resolution-profile.module.ts +0 -13
  290. package/src/video-resolution-profile/video-resolution-profile.service.ts +0 -117
@@ -0,0 +1,280 @@
1
+ import { PrismaService } from '@hed-hog/api-prisma';
2
+ import {
3
+ FileService,
4
+ NotificationActionType,
5
+ NotificationService,
6
+ NotificationType,
7
+ } from '@hed-hog/core';
8
+ import { QueueJobService } from '@hed-hog/queue';
9
+ import {
10
+ BadRequestException,
11
+ Inject,
12
+ Injectable,
13
+ NotFoundException,
14
+ forwardRef,
15
+ } from '@nestjs/common';
16
+ import { CreateCourseExportDto } from './dto/create-course-export.dto';
17
+
18
+ export const LMS_COURSE_EXPORT_SCORM12_JOB = 'lms.course.export.scorm12';
19
+
20
+ export type VideoLessonWithFile = {
21
+ id: number;
22
+ title: string;
23
+ order: number;
24
+ content: string | null;
25
+ duration_seconds: number | null;
26
+ session: {
27
+ id: number;
28
+ title: string;
29
+ order: number;
30
+ };
31
+ files: {
32
+ id: number;
33
+ file_id: number | null;
34
+ type: string;
35
+ file: {
36
+ id: number;
37
+ path: string;
38
+ size: number | null;
39
+ } | null;
40
+ }[];
41
+ };
42
+
43
+ @Injectable()
44
+ export class CourseExportService {
45
+ constructor(
46
+ @Inject(forwardRef(() => PrismaService))
47
+ private readonly prisma: PrismaService,
48
+ @Inject(forwardRef(() => QueueJobService))
49
+ private readonly queueJob: QueueJobService,
50
+ @Inject(forwardRef(() => NotificationService))
51
+ private readonly notificationService: NotificationService,
52
+ @Inject(forwardRef(() => FileService))
53
+ private readonly fileService: FileService,
54
+ ) {}
55
+
56
+ async deleteExport(courseId: number, exportId: number): Promise<void> {
57
+ const record = await this.prisma.course_export.findUnique({
58
+ where: { id: exportId },
59
+ select: { id: true, course_id: true, status: true, file_id: true },
60
+ });
61
+
62
+ if (!record || record.course_id !== courseId) {
63
+ throw new NotFoundException('Exportação não encontrada.');
64
+ }
65
+
66
+ if (record.status === 'pending' || record.status === 'processing') {
67
+ throw new BadRequestException(
68
+ 'Não é possível excluir uma exportação em andamento.',
69
+ );
70
+ }
71
+
72
+ if (record.file_id) {
73
+ await this.fileService
74
+ .delete('pt', { ids: [record.file_id] })
75
+ .catch(() => undefined);
76
+ }
77
+
78
+ await this.prisma.course_export.delete({ where: { id: exportId } });
79
+ }
80
+
81
+ async listExports(courseId: number) {
82
+ return this.prisma.course_export.findMany({
83
+ where: { course_id: courseId },
84
+ orderBy: { created_at: 'desc' },
85
+ include: {
86
+ user: {
87
+ select: {
88
+ id: true,
89
+ name: true,
90
+ photo_id: true,
91
+ },
92
+ },
93
+ file: {
94
+ select: {
95
+ id: true,
96
+ path: true,
97
+ filename: true,
98
+ size: true,
99
+ },
100
+ },
101
+ },
102
+ });
103
+ }
104
+
105
+ async enqueueScorm12Export(
106
+ courseId: number,
107
+ userId: number,
108
+ dto: CreateCourseExportDto,
109
+ ) {
110
+ const settings = {
111
+ visual: { ...(dto.visualSettings ?? {}) },
112
+ };
113
+
114
+ const exportRecord = await this.prisma.course_export.create({
115
+ data: {
116
+ course_id: courseId,
117
+ user_id: userId,
118
+ format: dto.format,
119
+ status: 'pending',
120
+ settings,
121
+ },
122
+ });
123
+
124
+ const course = await this.prisma.course.findUnique({
125
+ where: { id: courseId },
126
+ select: { title: true, name: true },
127
+ });
128
+ const courseTitle = course?.title || course?.name || `Curso ${courseId}`;
129
+
130
+ const notification = await this.notificationService.create({
131
+ user_id: userId,
132
+ title: `Exportando "${courseTitle}" — SCORM 1.2`,
133
+ body: 'Aguardando início do processamento...',
134
+ type: NotificationType.Progress,
135
+ progress: 1,
136
+ auto_remove: false,
137
+ action_type: NotificationActionType.Url,
138
+ action_url: `/lms/courses/${courseId}?tab=exportacoes`,
139
+ started_at: new Date().toISOString(),
140
+ });
141
+
142
+ const job = await this.queueJob.enqueue(
143
+ {
144
+ type: LMS_COURSE_EXPORT_SCORM12_JOB,
145
+ queueName: LMS_COURSE_EXPORT_SCORM12_JOB,
146
+ payload: {
147
+ exportId: exportRecord.id,
148
+ courseId,
149
+ userId,
150
+ notificationId: notification.id,
151
+ settings,
152
+ },
153
+ sourceModule: 'lms',
154
+ sourceEntity: 'course_export',
155
+ sourceEntityId: String(exportRecord.id),
156
+ maxAttempts: 3,
157
+ },
158
+ userId,
159
+ );
160
+
161
+ await this.prisma.course_export.update({
162
+ where: { id: exportRecord.id },
163
+ data: {
164
+ queue_job_id: job.id,
165
+ notification_id: notification.id,
166
+ },
167
+ });
168
+
169
+ return {
170
+ exportId: exportRecord.id,
171
+ queueJobId: job.id,
172
+ notificationId: notification.id,
173
+ };
174
+ }
175
+
176
+ async markStarted(exportId: number) {
177
+ await this.prisma.course_export.update({
178
+ where: { id: exportId },
179
+ data: {
180
+ status: 'processing',
181
+ started_at: new Date(),
182
+ },
183
+ });
184
+ }
185
+
186
+ async markCompleted(
187
+ exportId: number,
188
+ fileId: number,
189
+ durationSeconds: number,
190
+ ) {
191
+ await this.prisma.course_export.update({
192
+ where: { id: exportId },
193
+ data: {
194
+ status: 'completed',
195
+ file_id: fileId,
196
+ duration_seconds: durationSeconds,
197
+ finished_at: new Date(),
198
+ },
199
+ });
200
+ }
201
+
202
+ async markFailed(exportId: number, errorMessage: string) {
203
+ await this.prisma.course_export.update({
204
+ where: { id: exportId },
205
+ data: {
206
+ status: 'failed',
207
+ error_message: errorMessage,
208
+ finished_at: new Date(),
209
+ },
210
+ });
211
+ }
212
+
213
+ async getCourseVideoLessons(courseId: number): Promise<VideoLessonWithFile[]> {
214
+ const lessons = await this.prisma.course_lesson.findMany({
215
+ where: {
216
+ type: 'video',
217
+ course_module: {
218
+ course_id: courseId,
219
+ },
220
+ },
221
+ orderBy: [
222
+ { course_module: { order: 'asc' } },
223
+ { order: 'asc' },
224
+ ],
225
+ include: {
226
+ course_module: {
227
+ select: { id: true, title: true, order: true },
228
+ },
229
+ course_lesson_file: {
230
+ where: {
231
+ type: {
232
+ in: ['video_original', 'video_hls', 'video_profile:720p', 'video_profile:1080p'],
233
+ },
234
+ },
235
+ include: {
236
+ file: {
237
+ select: { id: true, path: true, size: true },
238
+ },
239
+ },
240
+ orderBy: { id: 'asc' },
241
+ },
242
+ },
243
+ });
244
+
245
+ return lessons.map((l) => ({
246
+ id: l.id,
247
+ title: l.title,
248
+ order: l.order,
249
+ content: l.content,
250
+ duration_seconds: l.duration_seconds,
251
+ session: {
252
+ id: l.course_module.id,
253
+ title: l.course_module.title,
254
+ order: l.course_module.order,
255
+ },
256
+ files: l.course_lesson_file.map((f) => ({
257
+ id: f.id,
258
+ file_id: f.file_id,
259
+ type: f.type,
260
+ file: f.file
261
+ ? { id: f.file.id, path: f.file.path, size: f.file.size }
262
+ : null,
263
+ })),
264
+ }));
265
+ }
266
+
267
+ getBestVideoFile(lesson: VideoLessonWithFile) {
268
+ const priority = [
269
+ 'video_profile:720p',
270
+ 'video_profile:1080p',
271
+ 'video_original',
272
+ 'video_hls',
273
+ ];
274
+ for (const p of priority) {
275
+ const f = lesson.files.find((f) => f.type === p && f.file_id && f.file);
276
+ if (f) return f;
277
+ }
278
+ return lesson.files.find((f) => f.file_id && f.file) ?? null;
279
+ }
280
+ }
@@ -10,7 +10,9 @@ import {
10
10
  Post,
11
11
  } from '@nestjs/common';
12
12
  import { CourseStructureService } from './course-structure.service';
13
+ import { CourseVideoAgentPipelineService } from './course-video-agent-pipeline.service';
13
14
  import { CourseVideoConversionService } from './course-video-conversion.service';
15
+ import { CourseVideoHlsService } from './course-video-hls.service';
14
16
  import { CreateCourseBulkJobDto } from './dto/create-course-bulk-job.dto';
15
17
  import { CreateCourseLessonFrameDto } from './dto/create-course-lesson-frame.dto';
16
18
  import {
@@ -33,6 +35,8 @@ export class CourseStructureController {
33
35
  constructor(
34
36
  private readonly courseStructureService: CourseStructureService,
35
37
  private readonly courseVideoConversionService: CourseVideoConversionService,
38
+ private readonly courseVideoHlsService: CourseVideoHlsService,
39
+ private readonly courseVideoAgentPipelineService: CourseVideoAgentPipelineService,
36
40
  ) {}
37
41
 
38
42
  @Get()
@@ -160,7 +164,7 @@ export class CourseStructureController {
160
164
  @Param('lessonId', ParseIntPipe) lessonId: number,
161
165
  @Body() dto: CreateLessonVideoConversionDto,
162
166
  ) {
163
- return this.courseVideoConversionService.enqueueConversion({
167
+ return this.courseVideoAgentPipelineService.startProcessing({
164
168
  userId,
165
169
  courseId,
166
170
  sessionId,
@@ -229,12 +233,22 @@ export class CourseStructureController {
229
233
  return this.courseStructureService.duplicateLesson(courseId, sessionId, lessonId);
230
234
  }
231
235
 
236
+ @Get('video-processing-stats')
237
+ getVideoProcessingStats(@Param('id', ParseIntPipe) courseId: number) {
238
+ return this.courseStructureService.getVideoProcessingStats(courseId);
239
+ }
240
+
232
241
  @Post('bulk-jobs')
233
242
  createBulkJobs(
234
243
  @User() user: any,
235
244
  @Param('id', ParseIntPipe) courseId: number,
236
245
  @Body() dto: CreateCourseBulkJobDto,
237
246
  ) {
238
- return this.courseStructureService.bulkEnqueueJobs(courseId, dto.jobType, user?.id ?? 0);
247
+ return this.courseStructureService.bulkEnqueueJobs(
248
+ courseId,
249
+ dto.jobType,
250
+ user?.id ?? 0,
251
+ dto.reprocessAlreadyProcessed,
252
+ );
239
253
  }
240
254
  }
@@ -4,6 +4,7 @@ import { QueueJobService } from '@hed-hog/queue';
4
4
  import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
5
5
  import { InstructorService } from '../instructor/instructor.service';
6
6
  import { CourseOperationsIntegrationService } from './course-operations-integration.service';
7
+ import { CourseVideoHlsService } from './course-video-hls.service';
7
8
  import { CreateCourseLessonFrameDto } from './dto/create-course-lesson-frame.dto';
8
9
  import { CreateCourseStructureLessonDto } from './dto/create-course-structure-lesson.dto';
9
10
  import { CreateCourseStructureSessionDto } from './dto/create-course-structure-session.dto';
@@ -17,7 +18,7 @@ import { UpdateCourseStructureLessonDto } from './dto/update-course-structure-le
17
18
  import { UpdateCourseStructureSessionDto } from './dto/update-course-structure-session.dto';
18
19
  import { UpdateTranscriptionSegmentsDTO } from './dto/update-transcription-segments.dto';
19
20
 
20
- type LessonUiType = 'video' | 'questao' | 'post' | 'exercicio';
21
+ type LessonUiType = 'video' | 'questao' | 'post';
21
22
  type LessonDbType = 'video' | 'text' | 'quiz';
22
23
  type LessonProductionStatus =
23
24
  | 'preparada'
@@ -27,6 +28,7 @@ type LessonProductionStatus =
27
28
  | 'publicada';
28
29
  type LessonFileType =
29
30
  | 'video_original'
31
+ | 'video_hls'
30
32
  | `video_profile:${number}`
31
33
  | 'lesson_audio'
32
34
  | 'student_download'
@@ -42,6 +44,7 @@ export class CourseStructureService {
42
44
  private readonly instructorService: InstructorService,
43
45
  private readonly operationsIntegration: CourseOperationsIntegrationService,
44
46
  private readonly queueJob: QueueJobService,
47
+ private readonly courseVideoHlsService: CourseVideoHlsService,
45
48
  ) {}
46
49
 
47
50
  async getStructure(courseId: number) {
@@ -102,6 +105,7 @@ export class CourseStructureService {
102
105
  title: true,
103
106
  description: true,
104
107
  offering_type: true,
108
+ status: true,
105
109
  },
106
110
  }),
107
111
  this.operationsIntegration.getCourseProjectLinks([courseId]),
@@ -146,7 +150,7 @@ export class CourseStructureService {
146
150
  videoUrl: parsedContent?.videoUrl,
147
151
  duracaoAutomatica: parsedContent?.duracaoAutomatica,
148
152
  transcricao: parsedContent?.transcricao,
149
- videoConversionJobId: parsedContent?.videoConversionJobId,
153
+ videoConversionJobId: parsedContent?.videoConversionJobId ?? parsedContent?.videoHlsJobId,
150
154
  exameVinculado:
151
155
  parsedContent?.exameVinculado != null
152
156
  ? String(parsedContent.exameVinculado)
@@ -203,6 +207,7 @@ export class CourseStructureService {
203
207
  titulo: courseRecord.title,
204
208
  descricao: courseRecord.description ?? '',
205
209
  offeringType: courseRecord.offering_type ?? 'on_demand',
210
+ status: courseRecord.status ?? 'draft',
206
211
  recursos: courseResources,
207
212
  operationsProjectId: courseProject?.projectId ?? null,
208
213
  operationsProjectCode: courseProject?.projectCode ?? null,
@@ -1226,10 +1231,48 @@ export class CourseStructureService {
1226
1231
  return true;
1227
1232
  }
1228
1233
 
1234
+ async getVideoProcessingStats(courseId: number): Promise<{
1235
+ withOriginal: number;
1236
+ withoutOriginal: number;
1237
+ alreadyProcessed: number;
1238
+ }> {
1239
+ const lessons = await (this.prisma as any).course_lesson.findMany({
1240
+ where: {
1241
+ type: 'video',
1242
+ course_module: { course_id: courseId },
1243
+ },
1244
+ select: {
1245
+ id: true,
1246
+ course_lesson_file: { select: { type: true } },
1247
+ },
1248
+ });
1249
+
1250
+ let withOriginal = 0;
1251
+ let withoutOriginal = 0;
1252
+ let alreadyProcessed = 0;
1253
+
1254
+ for (const lesson of lessons) {
1255
+ const files: { type: string }[] = lesson.course_lesson_file;
1256
+ const hasOriginal = files.some((f) => f.type === 'video_original');
1257
+ if (!hasOriginal) {
1258
+ withoutOriginal++;
1259
+ continue;
1260
+ }
1261
+ withOriginal++;
1262
+ const hasProcessed = files.some(
1263
+ (f) => f.type === 'video_hls' || f.type.startsWith('video_profile:'),
1264
+ );
1265
+ if (hasProcessed) alreadyProcessed++;
1266
+ }
1267
+
1268
+ return { withOriginal, withoutOriginal, alreadyProcessed };
1269
+ }
1270
+
1229
1271
  async bulkEnqueueJobs(
1230
1272
  courseId: number,
1231
- jobType: 'transcription' | 'xp_recalculation',
1273
+ jobType: 'transcription' | 'xp_recalculation' | 'video_processing',
1232
1274
  userId: number,
1275
+ reprocessAlreadyProcessed?: boolean,
1233
1276
  ): Promise<{
1234
1277
  queued: number;
1235
1278
  skipped: number;
@@ -1273,6 +1316,53 @@ export class CourseStructureService {
1273
1316
  });
1274
1317
  queued++;
1275
1318
  }
1319
+ } else if (jobType === 'video_processing') {
1320
+ const videoLessons = await (this.prisma as any).course_lesson.findMany({
1321
+ where: {
1322
+ type: 'video',
1323
+ course_module: { course_id: courseId },
1324
+ },
1325
+ select: {
1326
+ id: true,
1327
+ course_module_id: true,
1328
+ course_lesson_file: { select: { type: true, file_id: true } },
1329
+ },
1330
+ });
1331
+
1332
+ for (const lesson of videoLessons) {
1333
+ const files: { type: string; file_id: number }[] = lesson.course_lesson_file;
1334
+ const originalFile = files.find((f) => f.type === 'video_original');
1335
+
1336
+ if (!originalFile) {
1337
+ skipped++;
1338
+ continue;
1339
+ }
1340
+
1341
+ const isAlreadyProcessed = files.some(
1342
+ (f) => f.type === 'video_hls' || f.type.startsWith('video_profile:'),
1343
+ );
1344
+
1345
+ if (isAlreadyProcessed && !reprocessAlreadyProcessed) {
1346
+ skipped++;
1347
+ continue;
1348
+ }
1349
+
1350
+ try {
1351
+ await this.courseVideoHlsService.enqueueHls({
1352
+ userId,
1353
+ courseId,
1354
+ sessionId: lesson.course_module_id,
1355
+ lessonId: lesson.id,
1356
+ originalFileId: originalFile.file_id,
1357
+ });
1358
+ queued++;
1359
+ } catch (err: unknown) {
1360
+ this.logger.warn(
1361
+ `bulkEnqueueJobs(video_processing): skipped lesson ${lesson.id} — ${err instanceof Error ? err.message : String(err)}`,
1362
+ );
1363
+ skipped++;
1364
+ }
1365
+ }
1276
1366
  } else {
1277
1367
  for (const lesson of lessons) {
1278
1368
  const segments = await (this.prisma as any).course_lesson_transcription_segment.findFirst({
@@ -1465,7 +1555,7 @@ export class CourseStructureService {
1465
1555
  videoUrl: parsedContent?.videoUrl,
1466
1556
  duracaoAutomatica: parsedContent?.duracaoAutomatica,
1467
1557
  transcricao: parsedContent?.transcricao,
1468
- videoConversionJobId: parsedContent?.videoConversionJobId,
1558
+ videoConversionJobId: parsedContent?.videoConversionJobId ?? parsedContent?.videoHlsJobId,
1469
1559
  exameVinculado:
1470
1560
  parsedContent?.exameVinculado != null
1471
1561
  ? String(parsedContent.exameVinculado)
@@ -1538,8 +1628,7 @@ export class CourseStructureService {
1538
1628
  if (
1539
1629
  sourceType === 'video' ||
1540
1630
  sourceType === 'questao' ||
1541
- sourceType === 'post' ||
1542
- sourceType === 'exercicio'
1631
+ sourceType === 'post'
1543
1632
  ) {
1544
1633
  return sourceType;
1545
1634
  }
@@ -1577,7 +1666,7 @@ export class CourseStructureService {
1577
1666
  }
1578
1667
  }
1579
1668
 
1580
- if (data.tipo === 'post' || data.tipo === 'exercicio') {
1669
+ if (data.tipo === 'post') {
1581
1670
  payload.conteudoPost = data.conteudoPost ?? '';
1582
1671
  }
1583
1672
 
@@ -1710,6 +1799,10 @@ export class CourseStructureService {
1710
1799
  return 'video_original';
1711
1800
  }
1712
1801
 
1802
+ if (value === 'video_hls') {
1803
+ return 'video_hls';
1804
+ }
1805
+
1713
1806
  if (value?.startsWith('video_profile:')) {
1714
1807
  const profileId = Number(value.replace('video_profile:', ''));
1715
1808
  if (Number.isInteger(profileId) && profileId > 0) {