@hed-hog/lms 0.0.365 → 0.0.370

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 (243) hide show
  1. package/dist/certificate/certificate.controller.d.ts +1 -1
  2. package/dist/certificate/certificate.controller.d.ts.map +1 -1
  3. package/dist/certificate/certificate.controller.js +4 -2
  4. package/dist/certificate/certificate.controller.js.map +1 -1
  5. package/dist/certificate/certificate.service.d.ts +50 -0
  6. package/dist/certificate/certificate.service.d.ts.map +1 -1
  7. package/dist/certificate/certificate.service.js +73 -0
  8. package/dist/certificate/certificate.service.js.map +1 -1
  9. package/dist/class-group/class-group.controller.d.ts +1 -0
  10. package/dist/class-group/class-group.controller.d.ts.map +1 -1
  11. package/dist/class-group/class-group.service.d.ts +1 -0
  12. package/dist/class-group/class-group.service.d.ts.map +1 -1
  13. package/dist/course/course-ai-usage.service.d.ts +58 -0
  14. package/dist/course/course-ai-usage.service.d.ts.map +1 -0
  15. package/dist/course/course-ai-usage.service.js +176 -0
  16. package/dist/course/course-ai-usage.service.js.map +1 -0
  17. package/dist/course/course-audio-transcription.service.d.ts +65 -1
  18. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  19. package/dist/course/course-audio-transcription.service.js +381 -29
  20. package/dist/course/course-audio-transcription.service.js.map +1 -1
  21. package/dist/course/course-export-scorm12.service.d.ts +3 -0
  22. package/dist/course/course-export-scorm12.service.d.ts.map +1 -1
  23. package/dist/course/course-export-scorm12.service.js +141 -6
  24. package/dist/course/course-export-scorm12.service.js.map +1 -1
  25. package/dist/course/course-export.service.d.ts.map +1 -1
  26. package/dist/course/course-export.service.js +2 -1
  27. package/dist/course/course-export.service.js.map +1 -1
  28. package/dist/course/course-lesson.controller.d.ts +25 -3
  29. package/dist/course/course-lesson.controller.d.ts.map +1 -1
  30. package/dist/course/course-lesson.controller.js +71 -8
  31. package/dist/course/course-lesson.controller.js.map +1 -1
  32. package/dist/course/course-structure.controller.d.ts +30 -7
  33. package/dist/course/course-structure.controller.d.ts.map +1 -1
  34. package/dist/course/course-structure.controller.js +37 -4
  35. package/dist/course/course-structure.controller.js.map +1 -1
  36. package/dist/course/course-structure.service.d.ts +37 -5
  37. package/dist/course/course-structure.service.d.ts.map +1 -1
  38. package/dist/course/course-structure.service.js +165 -20
  39. package/dist/course/course-structure.service.js.map +1 -1
  40. package/dist/course/course-transcription-translation.service.d.ts +31 -0
  41. package/dist/course/course-transcription-translation.service.d.ts.map +1 -0
  42. package/dist/course/course-transcription-translation.service.js +227 -0
  43. package/dist/course/course-transcription-translation.service.js.map +1 -0
  44. package/dist/course/course-video-agent-pipeline.service.d.ts +70 -0
  45. package/dist/course/course-video-agent-pipeline.service.d.ts.map +1 -0
  46. package/dist/course/course-video-agent-pipeline.service.js +398 -0
  47. package/dist/course/course-video-agent-pipeline.service.js.map +1 -0
  48. package/dist/course/course-video-hls.service.d.ts +14 -0
  49. package/dist/course/course-video-hls.service.d.ts.map +1 -1
  50. package/dist/course/course-video-hls.service.js +25 -8
  51. package/dist/course/course-video-hls.service.js.map +1 -1
  52. package/dist/course/course.controller.d.ts +2 -0
  53. package/dist/course/course.controller.d.ts.map +1 -1
  54. package/dist/course/course.module.d.ts.map +1 -1
  55. package/dist/course/course.module.js +9 -0
  56. package/dist/course/course.module.js.map +1 -1
  57. package/dist/course/course.service.d.ts +2 -0
  58. package/dist/course/course.service.d.ts.map +1 -1
  59. package/dist/course/course.service.js +36 -2
  60. package/dist/course/course.service.js.map +1 -1
  61. package/dist/course/dto/create-course-bulk-job.dto.d.ts +2 -1
  62. package/dist/course/dto/create-course-bulk-job.dto.d.ts.map +1 -1
  63. package/dist/course/dto/create-course-bulk-job.dto.js +6 -1
  64. package/dist/course/dto/create-course-bulk-job.dto.js.map +1 -1
  65. package/dist/course/dto/create-course-export.dto.d.ts +1 -0
  66. package/dist/course/dto/create-course-export.dto.d.ts.map +1 -1
  67. package/dist/course/dto/create-course-export.dto.js +6 -0
  68. package/dist/course/dto/create-course-export.dto.js.map +1 -1
  69. package/dist/course/ffmpeg.util.d.ts +10 -0
  70. package/dist/course/ffmpeg.util.d.ts.map +1 -0
  71. package/dist/course/ffmpeg.util.js +79 -0
  72. package/dist/course/ffmpeg.util.js.map +1 -0
  73. package/dist/course/lms-bulk-upload-automation.service.d.ts +3 -1
  74. package/dist/course/lms-bulk-upload-automation.service.d.ts.map +1 -1
  75. package/dist/course/lms-bulk-upload-automation.service.js +33 -16
  76. package/dist/course/lms-bulk-upload-automation.service.js.map +1 -1
  77. package/dist/course/lms-bulk-upload.controller.d.ts +3 -0
  78. package/dist/course/lms-bulk-upload.controller.d.ts.map +1 -1
  79. package/dist/course/lms-bulk-upload.service.d.ts +3 -0
  80. package/dist/course/lms-bulk-upload.service.d.ts.map +1 -1
  81. package/dist/course/lms-bulk-upload.service.js +48 -29
  82. package/dist/course/lms-bulk-upload.service.js.map +1 -1
  83. package/dist/course/subtitle.util.d.ts +46 -0
  84. package/dist/course/subtitle.util.d.ts.map +1 -0
  85. package/dist/course/subtitle.util.js +206 -0
  86. package/dist/course/subtitle.util.js.map +1 -0
  87. package/dist/enterprise/training/training-admin.controller.d.ts +2 -0
  88. package/dist/enterprise/training/training-admin.controller.d.ts.map +1 -1
  89. package/dist/enterprise/training/training-admin.service.d.ts +2 -0
  90. package/dist/enterprise/training/training-admin.service.d.ts.map +1 -1
  91. package/dist/enterprise/training/training-student.service.d.ts +27 -0
  92. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  93. package/dist/enterprise/training/training-student.service.js +197 -10
  94. package/dist/enterprise/training/training-student.service.js.map +1 -1
  95. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts +3 -1
  96. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.d.ts.map +1 -1
  97. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js +19 -5
  98. package/dist/lesson-xp-map/lesson-xp-ai-calculation.service.js.map +1 -1
  99. package/dist/lesson-xp-map/lesson-xp-map.module.d.ts.map +1 -1
  100. package/dist/lesson-xp-map/lesson-xp-map.module.js +2 -1
  101. package/dist/lesson-xp-map/lesson-xp-map.module.js.map +1 -1
  102. package/dist/libraries/lms/tsconfig.tsbuildinfo +1 -1
  103. package/dist/lms.module.d.ts.map +1 -1
  104. package/dist/lms.module.js +14 -0
  105. package/dist/lms.module.js.map +1 -1
  106. package/dist/platforma/dto/heartbeat.dto.d.ts +9 -0
  107. package/dist/platforma/dto/heartbeat.dto.d.ts.map +1 -0
  108. package/dist/platforma/dto/heartbeat.dto.js +50 -0
  109. package/dist/platforma/dto/heartbeat.dto.js.map +1 -0
  110. package/dist/platforma/handlers/emit-certificate.handler.d.ts +27 -0
  111. package/dist/platforma/handlers/emit-certificate.handler.d.ts.map +1 -0
  112. package/dist/platforma/handlers/emit-certificate.handler.js +117 -0
  113. package/dist/platforma/handlers/emit-certificate.handler.js.map +1 -0
  114. package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts +31 -0
  115. package/dist/platforma/handlers/lesson-heartbeat.handler.d.ts.map +1 -0
  116. package/dist/platforma/handlers/lesson-heartbeat.handler.js +281 -0
  117. package/dist/platforma/handlers/lesson-heartbeat.handler.js.map +1 -0
  118. package/dist/platforma/platforma-heartbeat.service.d.ts +10 -0
  119. package/dist/platforma/platforma-heartbeat.service.d.ts.map +1 -0
  120. package/dist/platforma/platforma-heartbeat.service.js +50 -0
  121. package/dist/platforma/platforma-heartbeat.service.js.map +1 -0
  122. package/dist/platforma/platforma-performance.service.d.ts +121 -0
  123. package/dist/platforma/platforma-performance.service.d.ts.map +1 -0
  124. package/dist/platforma/platforma-performance.service.js +500 -0
  125. package/dist/platforma/platforma-performance.service.js.map +1 -0
  126. package/dist/platforma/platforma-search.service.d.ts +21 -0
  127. package/dist/platforma/platforma-search.service.d.ts.map +1 -0
  128. package/dist/platforma/platforma-search.service.js +64 -0
  129. package/dist/platforma/platforma-search.service.js.map +1 -0
  130. package/dist/platforma/platforma-video.service.d.ts +8 -0
  131. package/dist/platforma/platforma-video.service.d.ts.map +1 -1
  132. package/dist/platforma/platforma-video.service.js +45 -2
  133. package/dist/platforma/platforma-video.service.js.map +1 -1
  134. package/dist/platforma/platforma.controller.d.ts +213 -1
  135. package/dist/platforma/platforma.controller.d.ts.map +1 -1
  136. package/dist/platforma/platforma.controller.js +159 -2
  137. package/dist/platforma/platforma.controller.js.map +1 -1
  138. package/dist/realtime/lms-realtime.controller.d.ts +2 -0
  139. package/dist/realtime/lms-realtime.controller.d.ts.map +1 -1
  140. package/dist/realtime/lms-realtime.controller.js +31 -0
  141. package/dist/realtime/lms-realtime.controller.js.map +1 -1
  142. package/dist/realtime/lms-realtime.service.d.ts +1 -1
  143. package/dist/realtime/lms-realtime.service.d.ts.map +1 -1
  144. package/dist/realtime/lms-realtime.service.js.map +1 -1
  145. package/dist/training/dto/create-training.dto.d.ts +9 -0
  146. package/dist/training/dto/create-training.dto.d.ts.map +1 -1
  147. package/dist/training/dto/create-training.dto.js +45 -1
  148. package/dist/training/dto/create-training.dto.js.map +1 -1
  149. package/dist/training/training.controller.d.ts +144 -0
  150. package/dist/training/training.controller.d.ts.map +1 -1
  151. package/dist/training/training.service.d.ts +149 -0
  152. package/dist/training/training.service.d.ts.map +1 -1
  153. package/dist/training/training.service.js +332 -167
  154. package/dist/training/training.service.js.map +1 -1
  155. package/hedhog/data/image_type.yaml +10 -0
  156. package/hedhog/data/route.yaml +251 -0
  157. package/hedhog/data/setting_group.yaml +97 -0
  158. package/hedhog/frontend/app/bulk-upload-sessions/page.tsx.ejs +139 -27
  159. package/hedhog/frontend/app/certificates/models/page.tsx.ejs +182 -29
  160. package/hedhog/frontend/app/classes/_components/classes-calendar-view.tsx.ejs +277 -0
  161. package/hedhog/frontend/app/classes/page.tsx.ejs +127 -20
  162. package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +69 -57
  163. package/hedhog/frontend/app/courses/[id]/_components/CourseIssuedCertificatesCard.tsx.ejs +168 -0
  164. package/hedhog/frontend/app/courses/[id]/structure/_components/course-ai-costs-tab.tsx.ejs +191 -0
  165. package/hedhog/frontend/app/courses/[id]/structure/_components/course-export-sheet.tsx.ejs +81 -1
  166. package/hedhog/frontend/app/courses/[id]/structure/_components/course-exports-tab.tsx.ejs +12 -0
  167. package/hedhog/frontend/app/courses/[id]/structure/_components/course-overview-tab.tsx.ejs +141 -30
  168. package/hedhog/frontend/app/courses/[id]/structure/_components/course-xp-overview-tab.tsx.ejs +13 -13
  169. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +69 -1
  170. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson-xp-tab.tsx.ejs +11 -23
  171. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +267 -19
  172. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +114 -86
  173. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +239 -31
  174. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +344 -59
  175. package/hedhog/frontend/app/courses/[id]/structure/_components/lesson-video-preview.tsx.ejs +200 -0
  176. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +1 -0
  177. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +3 -0
  178. package/hedhog/frontend/app/courses/[id]/structure/_components/xp-premium-pills.tsx.ejs +1 -8
  179. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +19 -7
  180. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +4 -0
  181. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-ai-costs.ts.ejs +40 -0
  182. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-content-overview.ts.ejs +2 -0
  183. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-exports.ts.ejs +25 -0
  184. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +148 -0
  185. package/hedhog/frontend/app/courses/[id]/structure/_data/use-transcription-segments.ts.ejs +157 -8
  186. package/hedhog/frontend/app/courses/_components/CourseRowActions.tsx.ejs +1 -22
  187. package/hedhog/frontend/app/courses/page.tsx.ejs +66 -13
  188. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +6 -0
  189. package/hedhog/frontend/app/enterprise/_components/enterprise-classes-calendar-tab.tsx.ejs +264 -0
  190. package/hedhog/frontend/app/enterprise/page.tsx.ejs +104 -47
  191. package/hedhog/frontend/app/exams/page.tsx.ejs +38 -4
  192. package/hedhog/frontend/app/instructors/page.tsx.ejs +87 -46
  193. package/hedhog/frontend/app/paths/page.tsx.ejs +650 -168
  194. package/hedhog/frontend/app/training/page.tsx.ejs +38 -4
  195. package/hedhog/frontend/messages/en.json +41 -12
  196. package/hedhog/frontend/messages/pt.json +44 -13
  197. package/hedhog/query/triggers.sql +33 -0
  198. package/hedhog/table/course_ai_usage.yaml +46 -0
  199. package/hedhog/table/course_enrollment.yaml +3 -0
  200. package/hedhog/table/course_lesson.yaml +3 -0
  201. package/hedhog/table/course_lesson_answer.yaml +37 -0
  202. package/hedhog/table/course_lesson_transcription_segment.yaml +8 -0
  203. package/hedhog/table/learning_path.yaml +6 -0
  204. package/hedhog/table/learning_path_module.yaml +22 -0
  205. package/hedhog/table/learning_path_step.yaml +9 -6
  206. package/hedhog/table/lesson_view_event.yaml +66 -0
  207. package/package.json +8 -7
  208. package/src/certificate/certificate.controller.ts +2 -0
  209. package/src/certificate/certificate.service.ts +99 -0
  210. package/src/course/course-ai-usage.service.ts +221 -0
  211. package/src/course/course-audio-transcription.service.ts +471 -43
  212. package/src/course/course-export-scorm12.service.ts +149 -5
  213. package/src/course/course-export.service.ts +1 -0
  214. package/src/course/course-lesson.controller.ts +59 -6
  215. package/src/course/course-structure.controller.ts +19 -1
  216. package/src/course/course-structure.service.ts +184 -10
  217. package/src/course/course-transcription-translation.service.ts +293 -0
  218. package/src/course/course-video-agent-pipeline.service.ts +471 -0
  219. package/src/course/course-video-hls.service.ts +30 -10
  220. package/src/course/course.module.ts +9 -0
  221. package/src/course/course.service.ts +46 -1
  222. package/src/course/dto/create-course-bulk-job.dto.ts +7 -3
  223. package/src/course/dto/create-course-export.dto.ts +6 -0
  224. package/src/course/ffmpeg.util.ts +65 -0
  225. package/src/course/lms-bulk-upload-automation.service.ts +33 -8
  226. package/src/course/lms-bulk-upload.service.ts +20 -1
  227. package/src/course/subtitle.util.ts +220 -0
  228. package/src/enterprise/training/training-student.service.ts +224 -4
  229. package/src/lesson-xp-map/lesson-xp-ai-calculation.service.ts +14 -0
  230. package/src/lesson-xp-map/lesson-xp-map.module.ts +2 -1
  231. package/src/lms.module.ts +14 -0
  232. package/src/platforma/dto/heartbeat.dto.ts +30 -0
  233. package/src/platforma/handlers/emit-certificate.handler.ts +117 -0
  234. package/src/platforma/handlers/lesson-heartbeat.handler.ts +343 -0
  235. package/src/platforma/platforma-heartbeat.service.ts +33 -0
  236. package/src/platforma/platforma-performance.service.ts +606 -0
  237. package/src/platforma/platforma-search.service.ts +48 -0
  238. package/src/platforma/platforma-video.service.ts +59 -3
  239. package/src/platforma/platforma.controller.ts +130 -0
  240. package/src/realtime/lms-realtime.controller.ts +27 -1
  241. package/src/realtime/lms-realtime.service.ts +2 -1
  242. package/src/training/dto/create-training.dto.ts +36 -0
  243. package/src/training/training.service.ts +360 -163
@@ -1,5 +1,6 @@
1
1
  import { FileService } from '@hed-hog/core';
2
2
  import { PrismaService } from '@hed-hog/api-prisma';
3
+ import { balanceSubtitleLines } from '../course/subtitle.util';
3
4
  import {
4
5
  forwardRef,
5
6
  Inject,
@@ -128,6 +129,56 @@ export class PlatformaVideoService {
128
129
  stream.pipe(res);
129
130
  }
130
131
 
132
+ // ---------------------------------------------------------------------------
133
+ // Admin preview endpoints (no published check)
134
+ // ---------------------------------------------------------------------------
135
+
136
+ async generateAdminHlsToken(lessonId: number) {
137
+ const lesson = await this.prisma.course_lesson.findFirst({
138
+ where: { id: lessonId },
139
+ select: {
140
+ id: true,
141
+ course_lesson_file: {
142
+ where: { type: 'video_hls', file_id: { not: null } },
143
+ select: { id: true },
144
+ },
145
+ },
146
+ });
147
+
148
+ if (!lesson) throw new NotFoundException('Lesson not found');
149
+ if (lesson.course_lesson_file.length === 0) throw new NotFoundException('No HLS stream available for this lesson');
150
+
151
+ const token = this.jwtService.sign(
152
+ { lessonId },
153
+ { secret: process.env.JWT_SECRET, expiresIn: '4h' },
154
+ );
155
+
156
+ return { token, expiresIn: 14400 };
157
+ }
158
+
159
+ async generateAdminSubtitlesToken(lessonId: number, localeId?: number) {
160
+ const lesson = await this.prisma.course_lesson.findFirst({
161
+ where: { id: lessonId },
162
+ select: { id: true },
163
+ });
164
+
165
+ if (!lesson) throw new NotFoundException('Lesson not found');
166
+
167
+ const where: Record<string, unknown> = { course_lesson_id: lessonId };
168
+ if (localeId !== undefined) where['locale_id'] = localeId;
169
+
170
+ const count = await (this.prisma as any).course_lesson_transcription_segment.count({ where });
171
+
172
+ if (count === 0) throw new NotFoundException('No subtitles available for this lesson');
173
+
174
+ const token = this.jwtService.sign(
175
+ { lessonId, localeId: localeId ?? null, type: 'subtitles' },
176
+ { secret: process.env.JWT_SECRET, expiresIn: '4h' },
177
+ );
178
+
179
+ return { token, expiresIn: 14400 };
180
+ }
181
+
131
182
  // ---------------------------------------------------------------------------
132
183
  // HLS endpoints
133
184
  // ---------------------------------------------------------------------------
@@ -299,18 +350,23 @@ export class PlatformaVideoService {
299
350
 
300
351
  async serveSubtitles(token: string, res: Response) {
301
352
  let lessonId: number;
353
+ let localeId: number | null | undefined;
302
354
  try {
303
- const payload = this.jwtService.verify<{ lessonId: number; type: string }>(token, {
355
+ const payload = this.jwtService.verify<{ lessonId: number; localeId?: number | null; type: string }>(token, {
304
356
  secret: process.env.JWT_SECRET,
305
357
  });
306
358
  if (payload.type !== 'subtitles') throw new Error('Wrong token type');
307
359
  lessonId = payload.lessonId;
360
+ localeId = payload.localeId;
308
361
  } catch {
309
362
  throw new UnauthorizedException('Invalid or expired subtitles token');
310
363
  }
311
364
 
365
+ const where: Record<string, unknown> = { course_lesson_id: lessonId };
366
+ if (localeId != null) where['locale_id'] = localeId;
367
+
312
368
  const segments = await (this.prisma as any).course_lesson_transcription_segment.findMany({
313
- where: { course_lesson_id: lessonId },
369
+ where,
314
370
  orderBy: { start_seconds: 'asc' },
315
371
  select: { start_seconds: true, end_seconds: true, text: true },
316
372
  });
@@ -339,7 +395,7 @@ export class PlatformaVideoService {
339
395
  const cues = segments.map((seg, i) => {
340
396
  const start = this.secondsToVttTime(seg.start_seconds);
341
397
  const end = this.secondsToVttTime(seg.end_seconds);
342
- return `${i + 1}\n${start} --> ${end}\n${seg.text}`;
398
+ return `${i + 1}\n${start} --> ${end}\n${balanceSubtitleLines(seg.text)}`;
343
399
  });
344
400
  return `WEBVTT\n\n${cues.join('\n\n')}\n`;
345
401
  }
@@ -4,10 +4,18 @@ import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, Put, Q
4
4
  import type { Request, Response } from 'express';
5
5
  import { BitcodeWalletService } from '../bitcode-wallet/bitcode-wallet.service';
6
6
  import { CreateCurrentBitcodeWalletTransactionDto } from '../bitcode-wallet/dto/create-current-bitcode-wallet-transaction.dto';
7
+ import { LmsCertificateService } from '../certificate/certificate.service';
8
+ import { UpdateCertificatePublicAccessDto } from '../certificate/dto/update-certificate-public-access.dto';
9
+ import { CourseLessonDiscussionService } from '../course-lesson-discussion/course-lesson-discussion.service';
10
+ import { CreateCourseLessonDiscussionTopicDto } from '../course-lesson-discussion/dto/create-course-lesson-discussion-topic.dto';
7
11
  import { EvaluationService } from '../evaluation/evaluation.service';
8
12
  import { TrainingStudentService } from '../enterprise/training/training-student.service';
13
+ import { HeartbeatDto } from './dto/heartbeat.dto';
9
14
  import { UpdatePlatformaProfileDto } from './dto/update-profile.dto';
15
+ import { PlatformaHeartbeatService } from './platforma-heartbeat.service';
16
+ import { PlatformaPerformanceService } from './platforma-performance.service';
10
17
  import { PlatformaService } from './platforma.service';
18
+ import { PlatformaSearchService } from './platforma-search.service';
11
19
  import { PlatformaVideoService } from './platforma-video.service';
12
20
 
13
21
  @NoRole()
@@ -20,6 +28,11 @@ export class PlataformaController {
20
28
  private readonly bitcodeWalletService: BitcodeWalletService,
21
29
  private readonly platformaVideoService: PlatformaVideoService,
22
30
  private readonly evaluationService: EvaluationService,
31
+ private readonly heartbeatService: PlatformaHeartbeatService,
32
+ private readonly performanceService: PlatformaPerformanceService,
33
+ private readonly searchService: PlatformaSearchService,
34
+ private readonly certificateService: LmsCertificateService,
35
+ private readonly discussionService: CourseLessonDiscussionService,
23
36
  ) {}
24
37
 
25
38
  @Get()
@@ -54,11 +67,48 @@ export class PlataformaController {
54
67
  return this.trainingStudentService.getPublishedCourseBySlug(slug, userId);
55
68
  }
56
69
 
70
+ @Get('search')
71
+ search(
72
+ @Query('q') q = '',
73
+ @Query('types') types?: string,
74
+ @Query('pageSize') pageSize?: string,
75
+ ) {
76
+ return this.searchService.search(
77
+ q,
78
+ types ? types.split(',').filter(Boolean) : [],
79
+ pageSize ? Math.max(1, Number(pageSize)) : 5,
80
+ );
81
+ }
82
+
57
83
  @Get('lessons/:lessonId')
58
84
  getLessonDetail(@Param('lessonId', ParseIntPipe) lessonId: number) {
59
85
  return this.trainingStudentService.getLessonDetail(lessonId);
60
86
  }
61
87
 
88
+ @Get('lessons/:lessonId/my-answers')
89
+ getLessonStudentAnswers(
90
+ @Param('lessonId', ParseIntPipe) lessonId: number,
91
+ @User('id') userId: number,
92
+ ) {
93
+ return this.trainingStudentService.getLessonStudentAnswers(userId, lessonId);
94
+ }
95
+
96
+ @Post('lessons/:lessonId/questions/:questionId/answer')
97
+ submitLessonAnswer(
98
+ @Param('lessonId', ParseIntPipe) lessonId: number,
99
+ @Param('questionId', ParseIntPipe) questionId: number,
100
+ @User('id') userId: number,
101
+ @Body() body: { optionId?: number; answerText?: string },
102
+ ) {
103
+ return this.trainingStudentService.submitLessonAnswer(
104
+ userId,
105
+ lessonId,
106
+ questionId,
107
+ body.optionId ?? null,
108
+ body.answerText ?? null,
109
+ );
110
+ }
111
+
62
112
  @Get('lessons/:lessonId/video-token')
63
113
  getVideoToken(
64
114
  @Param('lessonId', ParseIntPipe) lessonId: number,
@@ -226,6 +276,28 @@ export class PlataformaController {
226
276
  return this.bitcodeWalletService.createCurrentUserTransaction(userId, dto);
227
277
  }
228
278
 
279
+ @Get('performance/summary')
280
+ getPerformanceSummary(
281
+ @User('id') userId: number,
282
+ @Query('period') period?: string,
283
+ ) {
284
+ return this.performanceService.getSummary(userId, period ?? '30d');
285
+ }
286
+
287
+ @Post('heartbeat')
288
+ heartbeat(
289
+ @User('id') userId: number,
290
+ @Body() dto: HeartbeatDto,
291
+ @Req() req: Request,
292
+ ) {
293
+ const ip =
294
+ (req.headers['x-forwarded-for'] as string)?.split(',')[0]?.trim() ??
295
+ req.socket?.remoteAddress ??
296
+ '';
297
+ const userAgent = (req.headers['user-agent'] as string) ?? '';
298
+ return this.heartbeatService.enqueue(userId, ip, userAgent, dto);
299
+ }
300
+
229
301
  @Get('profile')
230
302
  getProfile(@User('id') userId: number) {
231
303
  return this.platforma.getProfile(userId);
@@ -238,4 +310,62 @@ export class PlataformaController {
238
310
  ) {
239
311
  return this.platforma.updateProfile(userId, dto);
240
312
  }
313
+
314
+ @Get('certificates')
315
+ getStudentCertificates(
316
+ @User('id') userId: number,
317
+ @Query('page') page?: string,
318
+ @Query('pageSize') pageSize?: string,
319
+ @Query('type') type?: string,
320
+ ) {
321
+ return this.certificateService.listIssuedForStudent(userId, {
322
+ page: page ? Number(page) : 1,
323
+ pageSize: pageSize ? Number(pageSize) : 12,
324
+ type,
325
+ });
326
+ }
327
+
328
+ @Patch('certificates/:id/public-access')
329
+ updateStudentCertificateAccess(
330
+ @User('id') userId: number,
331
+ @Param('id', ParseIntPipe) id: number,
332
+ @Body() dto: UpdateCertificatePublicAccessDto,
333
+ ) {
334
+ return this.certificateService.updatePublicAccessForStudent(userId, id, dto.publicAccess);
335
+ }
336
+
337
+ @Get('lessons/:lessonId/discussion/topics')
338
+ listDiscussionTopics(
339
+ @Param('lessonId', ParseIntPipe) lessonId: number,
340
+ @User('id') userId: number,
341
+ ) {
342
+ return this.discussionService.listTopics(lessonId, userId);
343
+ }
344
+
345
+ @Post('lessons/:lessonId/discussion/topics')
346
+ createDiscussionTopic(
347
+ @Param('lessonId', ParseIntPipe) lessonId: number,
348
+ @User('id') userId: number,
349
+ @Body() dto: CreateCourseLessonDiscussionTopicDto,
350
+ ) {
351
+ return this.discussionService.createTopic(lessonId, userId, dto);
352
+ }
353
+
354
+ @Post('lessons/:lessonId/discussion/topics/:topicId/likes')
355
+ likeDiscussionTopic(
356
+ @Param('lessonId', ParseIntPipe) lessonId: number,
357
+ @Param('topicId', ParseIntPipe) topicId: number,
358
+ @User('id') userId: number,
359
+ ) {
360
+ return this.discussionService.likeTopic(lessonId, topicId, userId);
361
+ }
362
+
363
+ @Delete('lessons/:lessonId/discussion/topics/:topicId/likes')
364
+ unlikeDiscussionTopic(
365
+ @Param('lessonId', ParseIntPipe) lessonId: number,
366
+ @Param('topicId', ParseIntPipe) topicId: number,
367
+ @User('id') userId: number,
368
+ ) {
369
+ return this.discussionService.unlikeTopic(lessonId, topicId, userId);
370
+ }
241
371
  }
@@ -1,4 +1,5 @@
1
- import { Controller, Get } from '@nestjs/common';
1
+ import { Controller, Get, Req, Res } from '@nestjs/common';
2
+ import type { Request, Response } from 'express';
2
3
  import { LmsRealtimeCursor, LmsRealtimeService } from './lms-realtime.service';
3
4
 
4
5
  @Controller('lms/realtime')
@@ -9,4 +10,29 @@ export class LmsRealtimeController {
9
10
  getCursor(): LmsRealtimeCursor {
10
11
  return this.lmsRealtime.getCursor();
11
12
  }
13
+
14
+ @Get('activity/stream')
15
+ streamActivity(@Req() req: Request, @Res() res: Response) {
16
+ res.set({
17
+ 'Content-Type': 'text/event-stream',
18
+ 'Cache-Control': 'no-cache',
19
+ Connection: 'keep-alive',
20
+ 'X-Accel-Buffering': 'no',
21
+ });
22
+ res.flushHeaders();
23
+
24
+ const unsub = this.lmsRealtime.subscribe((event) => {
25
+ if (event.domain === 'lesson_view') {
26
+ res.write(`data: ${JSON.stringify(event)}\n\n`);
27
+ }
28
+ });
29
+
30
+ // Send a heartbeat every 25s to keep the connection alive through proxies
31
+ const heartbeat = setInterval(() => res.write(': ping\n\n'), 25_000);
32
+
33
+ req.on('close', () => {
34
+ clearInterval(heartbeat);
35
+ unsub();
36
+ });
37
+ }
12
38
  }
@@ -8,7 +8,8 @@ export type LmsRealtimeDomain =
8
8
  | 'exam_attempt'
9
9
  | 'enterprise'
10
10
  | 'instructor'
11
- | 'achievement';
11
+ | 'achievement'
12
+ | 'lesson_view';
12
13
 
13
14
  export type LmsRealtimeEventType =
14
15
  | 'created'
@@ -31,6 +31,28 @@ export class LearningPathItemDto {
31
31
  isRequired?: boolean;
32
32
  }
33
33
 
34
+ export class LearningPathModuleDto {
35
+ @IsString()
36
+ @IsNotEmpty()
37
+ @MaxLength(255)
38
+ title: string;
39
+
40
+ @IsString()
41
+ @IsOptional()
42
+ description?: string;
43
+
44
+ @IsInt()
45
+ @Min(0)
46
+ @IsOptional()
47
+ order?: number;
48
+
49
+ @IsArray()
50
+ @ValidateNested({ each: true })
51
+ @Type(() => LearningPathItemDto)
52
+ @IsOptional()
53
+ items?: LearningPathItemDto[];
54
+ }
55
+
34
56
  export class CreateTrainingDto {
35
57
  @IsString()
36
58
  @IsNotEmpty()
@@ -82,4 +104,18 @@ export class CreateTrainingDto {
82
104
  @IsOptional()
83
105
  @MaxLength(32)
84
106
  secondaryColor?: string;
107
+
108
+ @IsInt()
109
+ @IsOptional()
110
+ bannerFileId?: number | null;
111
+
112
+ @IsInt()
113
+ @IsOptional()
114
+ certificateTemplateId?: number | null;
115
+
116
+ @IsArray()
117
+ @ValidateNested({ each: true })
118
+ @Type(() => LearningPathModuleDto)
119
+ @IsOptional()
120
+ modules?: LearningPathModuleDto[];
85
121
  }