@hed-hog/lms 0.0.355 → 0.0.358

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 (115) hide show
  1. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  2. package/dist/course/course-audio-transcription.service.js +15 -7
  3. package/dist/course/course-audio-transcription.service.js.map +1 -1
  4. package/dist/course/course-operations-integration.service.d.ts +31 -0
  5. package/dist/course/course-operations-integration.service.d.ts.map +1 -1
  6. package/dist/course/course-operations-integration.service.js +286 -22
  7. package/dist/course/course-operations-integration.service.js.map +1 -1
  8. package/dist/course/course-operations.controller.d.ts +10 -0
  9. package/dist/course/course-operations.controller.d.ts.map +1 -0
  10. package/dist/course/course-operations.controller.js +67 -0
  11. package/dist/course/course-operations.controller.js.map +1 -0
  12. package/dist/course/course-structure.controller.d.ts +15 -1
  13. package/dist/course/course-structure.controller.d.ts.map +1 -1
  14. package/dist/course/course-structure.service.d.ts +25 -1
  15. package/dist/course/course-structure.service.d.ts.map +1 -1
  16. package/dist/course/course-structure.service.js +160 -24
  17. package/dist/course/course-structure.service.js.map +1 -1
  18. package/dist/course/course.module.d.ts.map +1 -1
  19. package/dist/course/course.module.js +4 -1
  20. package/dist/course/course.module.js.map +1 -1
  21. package/dist/course/course.service.d.ts +4 -2
  22. package/dist/course/course.service.d.ts.map +1 -1
  23. package/dist/course/course.service.js +61 -2
  24. package/dist/course/course.service.js.map +1 -1
  25. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +3 -0
  26. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  27. package/dist/course/dto/create-course-structure-lesson.dto.js +15 -0
  28. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  29. package/dist/course/dto/create-course-structure-session.dto.d.ts +1 -0
  30. package/dist/course/dto/create-course-structure-session.dto.d.ts.map +1 -1
  31. package/dist/course/dto/create-course-structure-session.dto.js +5 -0
  32. package/dist/course/dto/create-course-structure-session.dto.js.map +1 -1
  33. package/dist/course/dto/update-course-operations-config.dto.d.ts +6 -0
  34. package/dist/course/dto/update-course-operations-config.dto.d.ts.map +1 -0
  35. package/dist/course/dto/update-course-operations-config.dto.js +33 -0
  36. package/dist/course/dto/update-course-operations-config.dto.js.map +1 -0
  37. package/dist/course/lms-operations-task.subscriber.d.ts +13 -0
  38. package/dist/course/lms-operations-task.subscriber.d.ts.map +1 -0
  39. package/dist/course/lms-operations-task.subscriber.js +57 -0
  40. package/dist/course/lms-operations-task.subscriber.js.map +1 -0
  41. package/dist/enterprise/enterprise.service.js +1 -1
  42. package/dist/enterprise/enterprise.service.js.map +1 -1
  43. package/dist/enterprise/training/training-student.controller.d.ts +0 -95
  44. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
  45. package/dist/enterprise/training/training-student.controller.js +1 -34
  46. package/dist/enterprise/training/training-student.controller.js.map +1 -1
  47. package/dist/enterprise/training/training-student.service.d.ts +63 -0
  48. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  49. package/dist/enterprise/training/training-student.service.js +320 -4
  50. package/dist/enterprise/training/training-student.service.js.map +1 -1
  51. package/dist/instructor/instructor.service.d.ts.map +1 -1
  52. package/dist/instructor/instructor.service.js +12 -3
  53. package/dist/instructor/instructor.service.js.map +1 -1
  54. package/dist/lms.module.d.ts.map +1 -1
  55. package/dist/lms.module.js +2 -0
  56. package/dist/lms.module.js.map +1 -1
  57. package/dist/platforma/platforma.controller.d.ts +287 -0
  58. package/dist/platforma/platforma.controller.d.ts.map +1 -0
  59. package/dist/platforma/platforma.controller.js +147 -0
  60. package/dist/platforma/platforma.controller.js.map +1 -0
  61. package/hedhog/data/route.yaml +102 -9
  62. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -2
  63. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -13
  64. package/hedhog/frontend/app/_lib/editor/templateSerializer.ts.ejs +26 -4
  65. package/hedhog/frontend/app/_lib/editor/types.ts.ejs +6 -0
  66. package/hedhog/frontend/app/certificates/models/CanvasStage.tsx.ejs +447 -31
  67. package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +59 -47
  68. package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +201 -35
  69. package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +36 -5
  70. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2 -2
  71. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
  72. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +17 -6
  73. package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +382 -0
  74. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +45 -8
  75. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +177 -67
  76. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +25 -60
  77. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -0
  78. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +11 -0
  79. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +22 -0
  80. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +79 -64
  81. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +31 -14
  82. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +4 -4
  83. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +39 -27
  84. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +24 -2
  85. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +41 -6
  86. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-query.ts.ejs +2 -2
  87. package/hedhog/frontend/app/courses/page.tsx.ejs +80 -103
  88. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +1 -1
  89. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +2 -2
  90. package/hedhog/frontend/app/enterprise/page.tsx.ejs +18 -6
  91. package/hedhog/frontend/app/exams/[id]/page.tsx.ejs +5 -4
  92. package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +16 -10
  93. package/hedhog/frontend/app/paths/page.tsx.ejs +1 -1
  94. package/hedhog/frontend/app/training/page.tsx.ejs +1 -1
  95. package/hedhog/frontend/messages/en.json +7 -2
  96. package/hedhog/frontend/messages/pt.json +7 -2
  97. package/hedhog/table/course_lesson.yaml +2 -2
  98. package/hedhog/table/course_module.yaml +3 -0
  99. package/package.json +8 -8
  100. package/src/course/course-audio-transcription.service.ts +21 -8
  101. package/src/course/course-operations-integration.service.ts +460 -22
  102. package/src/course/course-operations.controller.ts +45 -0
  103. package/src/course/course-structure.service.ts +209 -4
  104. package/src/course/course.module.ts +4 -1
  105. package/src/course/course.service.ts +67 -1
  106. package/src/course/dto/create-course-structure-lesson.dto.ts +17 -0
  107. package/src/course/dto/create-course-structure-session.dto.ts +13 -1
  108. package/src/course/dto/update-course-operations-config.dto.ts +16 -0
  109. package/src/course/lms-operations-task.subscriber.ts +44 -0
  110. package/src/enterprise/enterprise.service.ts +1 -1
  111. package/src/enterprise/training/training-student.controller.ts +3 -27
  112. package/src/enterprise/training/training-student.service.ts +350 -2
  113. package/src/instructor/instructor.service.ts +12 -3
  114. package/src/lms.module.ts +2 -0
  115. package/src/platforma/platforma.controller.ts +92 -0
@@ -18,6 +18,12 @@ import { UpdateTranscriptionSegmentsDTO } from './dto/update-transcription-segme
18
18
 
19
19
  type LessonUiType = 'video' | 'questao' | 'post' | 'exercicio';
20
20
  type LessonDbType = 'video' | 'text' | 'quiz';
21
+ type LessonProductionStatus =
22
+ | 'preparada'
23
+ | 'gravada'
24
+ | 'editada'
25
+ | 'finalizada'
26
+ | 'publicada';
21
27
  type LessonFileType =
22
28
  | 'video_original'
23
29
  | `video_profile:${number}`
@@ -70,6 +76,7 @@ export class CourseStructureService {
70
76
  person: {
71
77
  select: {
72
78
  name: true,
79
+ avatar_id: true,
73
80
  },
74
81
  },
75
82
  },
@@ -96,9 +103,16 @@ export class CourseStructureService {
96
103
  this.operationsIntegration.getCourseProjectLinks([courseId]),
97
104
  ]);
98
105
 
99
- const lessonTaskLinks = await this.operationsIntegration.getLessonTaskLinks(
100
- modules.flatMap((module) => module.course_lesson.map((lesson) => lesson.id)),
106
+ const lessonIds = modules.flatMap((module) =>
107
+ module.course_lesson.map((lesson) => lesson.id),
101
108
  );
109
+
110
+ const [lessonTaskLinks, sessionPublishedById, lessonPublishedById] =
111
+ await Promise.all([
112
+ this.operationsIntegration.getLessonTaskLinks(lessonIds),
113
+ this.getSessionPublishedByIds(modules.map((module) => module.id)),
114
+ this.getLessonPublishedByIds(lessonIds),
115
+ ]);
102
116
  const courseProject = courseProjectLinks.get(courseId);
103
117
 
104
118
  const sessoes = modules.map((module, index) => ({
@@ -106,6 +120,7 @@ export class CourseStructureService {
106
120
  codigo: this.formatCode('S', module.order ?? index + 1),
107
121
  titulo: module.title,
108
122
  duracao: module.duration_minutes ?? 0,
123
+ published: sessionPublishedById.get(module.id) ?? false,
109
124
  collapsed: false,
110
125
  }));
111
126
 
@@ -135,6 +150,10 @@ export class CourseStructureService {
135
150
  ? String(lesson.course_lesson_question[0].question_id)
136
151
  : undefined,
137
152
  conteudoPost: parsedContent?.conteudoPost,
153
+ statusProducao: this.normalizeProductionStatus(
154
+ parsedContent?.statusProducao,
155
+ ),
156
+ published: lessonPublishedById.get(lesson.id) ?? false,
138
157
  recursos: lesson.course_lesson_file.map((fileLink) => ({
139
158
  id: String(fileLink.id),
140
159
  nome: fileLink.title,
@@ -153,7 +172,8 @@ export class CourseStructureService {
153
172
  instrutores: lesson.course_lesson_instructor.map((item) => ({
154
173
  id: String(item.instructor_id),
155
174
  role: item.role,
156
- nome: this.personName(item.instructor?.person),
175
+ name: this.personName(item.instructor?.person),
176
+ avatarId: item.instructor?.person?.avatar_id ?? null,
157
177
  })),
158
178
  operationsTaskId: lessonTaskLinks.get(lesson.id)?.taskId ?? null,
159
179
  operationsTaskName: lessonTaskLinks.get(lesson.id)?.taskName ?? null,
@@ -169,6 +189,7 @@ export class CourseStructureService {
169
189
  instructors: instructors.map((instructor) => ({
170
190
  id: instructor.id,
171
191
  name: instructor.name,
192
+ avatarId: instructor.avatarId ?? null,
172
193
  })),
173
194
  curso: courseRecord
174
195
  ? {
@@ -244,11 +265,15 @@ export class CourseStructureService {
244
265
  },
245
266
  });
246
267
 
268
+ const published = dto.published ?? false;
269
+ await this.setSessionPublished(created.id, published);
270
+
247
271
  return {
248
272
  id: String(created.id),
249
273
  codigo: this.formatCode('S', created.order ?? nextOrder),
250
274
  titulo: created.title,
251
275
  duracao: created.duration_minutes ?? 0,
276
+ published,
252
277
  collapsed: false,
253
278
  };
254
279
  }
@@ -282,11 +307,17 @@ export class CourseStructureService {
282
307
  },
283
308
  });
284
309
 
310
+ const published = dto.published ?? (await this.getSessionPublished(sessionId));
311
+ if (dto.published !== undefined) {
312
+ await this.setSessionPublished(sessionId, dto.published);
313
+ }
314
+
285
315
  return {
286
316
  id: String(updated.id),
287
317
  codigo: this.formatCode('S', updated.order ?? 0),
288
318
  titulo: updated.title,
289
319
  duracao: updated.duration_minutes ?? 0,
320
+ published,
290
321
  collapsed: false,
291
322
  };
292
323
  }
@@ -335,6 +366,23 @@ export class CourseStructureService {
335
366
  const created = await this.prisma.$transaction(async (tx) => {
336
367
  const nextOrder = await this.nextLessonOrder(sessionId, tx);
337
368
  const mappedType = this.mapUiTypeToDbType(dto.tipo);
369
+ const normalizedStatus = this.normalizeProductionStatus(dto.statusProducao);
370
+ const shouldPublish = dto.published ?? false;
371
+
372
+ if (
373
+ shouldPublish &&
374
+ normalizedStatus !== 'publicada' &&
375
+ !dto.confirmarPublicacaoComStatus
376
+ ) {
377
+ throw new BadRequestException(
378
+ 'LESSON_PUBLISH_REQUIRES_STATUS_CONFIRMATION',
379
+ );
380
+ }
381
+
382
+ const statusProducao: LessonProductionStatus =
383
+ shouldPublish && dto.confirmarPublicacaoComStatus
384
+ ? 'publicada'
385
+ : normalizedStatus;
338
386
 
339
387
  const lesson = await tx.course_lesson.create({
340
388
  data: {
@@ -344,10 +392,15 @@ export class CourseStructureService {
344
392
  type: mappedType,
345
393
  duration_seconds: dto.duracao * 60,
346
394
  order: nextOrder,
347
- content: this.serializeContent(dto),
395
+ content: this.serializeContent({
396
+ ...dto,
397
+ statusProducao,
398
+ }),
348
399
  },
349
400
  });
350
401
 
402
+ await this.setLessonPublished(lesson.id, shouldPublish, tx);
403
+
351
404
  await this.syncLessonRelations(lesson.id, dto, tx);
352
405
 
353
406
  const projectLinks = await this.operationsIntegration.getCourseProjectLinks(
@@ -410,10 +463,36 @@ export class CourseStructureService {
410
463
  conteudoPost: dto.conteudoPost,
411
464
  exameVinculado: dto.exameVinculado,
412
465
  descricaoPrivada: dto.descricaoPrivada,
466
+ statusProducao: (() => {
467
+ const parsed = this.parseLessonContent(lesson.content);
468
+ const currentStatus = this.normalizeProductionStatus(
469
+ parsed?.statusProducao,
470
+ );
471
+ const nextStatus = this.normalizeProductionStatus(
472
+ dto.statusProducao ?? currentStatus,
473
+ );
474
+
475
+ const shouldPublish = dto.published;
476
+ if (shouldPublish === true && nextStatus !== 'publicada') {
477
+ if (!dto.confirmarPublicacaoComStatus) {
478
+ throw new BadRequestException(
479
+ 'LESSON_PUBLISH_REQUIRES_STATUS_CONFIRMATION',
480
+ );
481
+ }
482
+
483
+ return 'publicada';
484
+ }
485
+
486
+ return nextStatus;
487
+ })(),
413
488
  }),
414
489
  },
415
490
  });
416
491
 
492
+ if (dto.published !== undefined) {
493
+ await this.setLessonPublished(lessonId, dto.published);
494
+ }
495
+
417
496
  await this.syncLessonRelations(lessonId, dto);
418
497
  await this.operationsIntegration.syncTaskForLesson(lessonId);
419
498
 
@@ -437,6 +516,7 @@ export class CourseStructureService {
437
516
  throw new NotFoundException('Lesson not found for this session');
438
517
  }
439
518
 
519
+ await this.operationsIntegration.deleteTasksForLesson(lessonId, this.prisma);
440
520
  await this.prisma.course_lesson.delete({ where: { id: lessonId } });
441
521
 
442
522
  return { success: true };
@@ -756,6 +836,9 @@ export class CourseStructureService {
756
836
  },
757
837
  });
758
838
 
839
+ const sourcePublished = await this.getSessionPublished(sourceModule.id);
840
+ await this.setSessionPublished(newModule.id, sourcePublished);
841
+
759
842
  const newLessons = [];
760
843
  const projectLinks = await this.operationsIntegration.getCourseProjectLinks([
761
844
  courseId,
@@ -775,6 +858,9 @@ export class CourseStructureService {
775
858
  },
776
859
  });
777
860
 
861
+ const lessonPublished = await this.getLessonPublished(lesson.id);
862
+ await this.setLessonPublished(newLesson.id, lessonPublished);
863
+
778
864
  if (lesson.course_lesson_file.length > 0) {
779
865
  await (this.prisma as any).course_lesson_file.createMany({
780
866
  data: lesson.course_lesson_file.map((f) => ({
@@ -825,6 +911,7 @@ export class CourseStructureService {
825
911
  codigo: this.formatCode('S', newModule.order ?? nextOrder),
826
912
  titulo: newModule.title,
827
913
  duracao: newModule.duration_minutes ?? 0,
914
+ published: sourcePublished,
828
915
  collapsed: false,
829
916
  },
830
917
  lessons: newLessons,
@@ -877,6 +964,9 @@ export class CourseStructureService {
877
964
  },
878
965
  });
879
966
 
967
+ const sourcePublished = await this.getLessonPublished(sourceLesson.id);
968
+ await this.setLessonPublished(newLesson.id, sourcePublished);
969
+
880
970
  if (sourceLesson.course_lesson_file.length > 0) {
881
971
  await (this.prisma as any).course_lesson_file.createMany({
882
972
  data: sourceLesson.course_lesson_file.map((f) => ({
@@ -966,6 +1056,9 @@ export class CourseStructureService {
966
1056
  },
967
1057
  });
968
1058
 
1059
+ const sourcePublished = await this.getLessonPublished(lesson.id);
1060
+ await this.setLessonPublished(newLesson.id, sourcePublished);
1061
+
969
1062
  if (lesson.course_lesson_file.length > 0) {
970
1063
  await (this.prisma as any).course_lesson_file.createMany({
971
1064
  data: lesson.course_lesson_file.map((f) => ({
@@ -1182,6 +1275,7 @@ export class CourseStructureService {
1182
1275
  const taskLinks = await this.operationsIntegration.getLessonTaskLinks([
1183
1276
  lessonId,
1184
1277
  ]);
1278
+ const lessonPublished = await this.getLessonPublished(lessonId);
1185
1279
  const parsedContent = this.parseLessonContent(lesson.content);
1186
1280
  const tipo = this.mapDbTypeToUiType(lesson.type, parsedContent?.sourceType);
1187
1281
 
@@ -1205,6 +1299,8 @@ export class CourseStructureService {
1205
1299
  : lesson.course_lesson_question[0]?.question_id != null
1206
1300
  ? String(lesson.course_lesson_question[0].question_id)
1207
1301
  : undefined,
1302
+ statusProducao: this.normalizeProductionStatus(parsedContent?.statusProducao),
1303
+ published: lessonPublished,
1208
1304
  conteudoPost: parsedContent?.conteudoPost,
1209
1305
  recursos: lesson.course_lesson_file.map((fileLink) => ({
1210
1306
  id: String(fileLink.id),
@@ -1290,10 +1386,12 @@ export class CourseStructureService {
1290
1386
  conteudoPost?: string;
1291
1387
  exameVinculado?: number;
1292
1388
  descricaoPrivada?: string;
1389
+ statusProducao?: LessonProductionStatus;
1293
1390
  }) {
1294
1391
  const payload: Record<string, unknown> = {
1295
1392
  sourceType: data.tipo,
1296
1393
  descricaoPrivada: data.descricaoPrivada ?? '',
1394
+ statusProducao: this.normalizeProductionStatus(data.statusProducao),
1297
1395
  };
1298
1396
 
1299
1397
  if (data.tipo === 'video') {
@@ -1317,6 +1415,113 @@ export class CourseStructureService {
1317
1415
  return JSON.stringify(payload);
1318
1416
  }
1319
1417
 
1418
+ private normalizeProductionStatus(
1419
+ value?: string | null,
1420
+ ): LessonProductionStatus {
1421
+ if (!value) return 'preparada';
1422
+
1423
+ const normalized = String(value).trim().toLowerCase();
1424
+ if (normalized === 'preparada') return 'preparada';
1425
+ if (normalized === 'gravada') return 'gravada';
1426
+ if (normalized === 'editada') return 'editada';
1427
+ if (normalized === 'finalizada') return 'finalizada';
1428
+ if (normalized === 'publicada') return 'publicada';
1429
+
1430
+ return 'preparada';
1431
+ }
1432
+
1433
+ private normalizeIdList(ids: number[]) {
1434
+ return ids
1435
+ .map((id) => Number(id))
1436
+ .filter((id) => Number.isInteger(id) && id > 0);
1437
+ }
1438
+
1439
+ private buildIdCsv(ids: number[]) {
1440
+ return this.normalizeIdList(ids).join(',');
1441
+ }
1442
+
1443
+ private async getSessionPublishedByIds(sessionIds: number[]) {
1444
+ const publishedById = new Map<number, boolean>();
1445
+ const csv = this.buildIdCsv(sessionIds);
1446
+
1447
+ if (!csv) return publishedById;
1448
+
1449
+ try {
1450
+ const rows = await this.prisma.$queryRawUnsafe<
1451
+ Array<{ id: number; published: boolean }>
1452
+ >(
1453
+ `SELECT id, published FROM course_module WHERE id IN (${csv})`,
1454
+ );
1455
+
1456
+ for (const row of rows) {
1457
+ publishedById.set(row.id, Boolean(row.published));
1458
+ }
1459
+ } catch {
1460
+ return publishedById;
1461
+ }
1462
+
1463
+ return publishedById;
1464
+ }
1465
+
1466
+ private async getSessionPublished(sessionId: number) {
1467
+ const rows = await this.prisma.$queryRawUnsafe<
1468
+ Array<{ published: boolean }>
1469
+ >(
1470
+ `SELECT published FROM course_module WHERE id = ${Number(sessionId)} LIMIT 1`,
1471
+ );
1472
+
1473
+ return Boolean(rows[0]?.published);
1474
+ }
1475
+
1476
+ private async setSessionPublished(sessionId: number, published: boolean) {
1477
+ await this.prisma.$executeRawUnsafe(
1478
+ `UPDATE course_module SET published = ${published ? 'true' : 'false'} WHERE id = ${Number(sessionId)}`,
1479
+ );
1480
+ }
1481
+
1482
+ private async getLessonPublishedByIds(lessonIds: number[]) {
1483
+ const publishedById = new Map<number, boolean>();
1484
+ const csv = this.buildIdCsv(lessonIds);
1485
+
1486
+ if (!csv) return publishedById;
1487
+
1488
+ try {
1489
+ const rows = await this.prisma.$queryRawUnsafe<
1490
+ Array<{ id: number; published: boolean }>
1491
+ >(
1492
+ `SELECT id, published FROM course_lesson WHERE id IN (${csv})`,
1493
+ );
1494
+
1495
+ for (const row of rows) {
1496
+ publishedById.set(row.id, Boolean(row.published));
1497
+ }
1498
+ } catch {
1499
+ return publishedById;
1500
+ }
1501
+
1502
+ return publishedById;
1503
+ }
1504
+
1505
+ private async getLessonPublished(lessonId: number) {
1506
+ const rows = await this.prisma.$queryRawUnsafe<
1507
+ Array<{ published: boolean }>
1508
+ >(
1509
+ `SELECT published FROM course_lesson WHERE id = ${Number(lessonId)} LIMIT 1`,
1510
+ );
1511
+
1512
+ return Boolean(rows[0]?.published);
1513
+ }
1514
+
1515
+ private async setLessonPublished(
1516
+ lessonId: number,
1517
+ published: boolean,
1518
+ client: any = this.prisma,
1519
+ ) {
1520
+ await client.$executeRawUnsafe(
1521
+ `UPDATE course_lesson SET published = ${published ? 'true' : 'false'} WHERE id = ${Number(lessonId)}`,
1522
+ );
1523
+ }
1524
+
1320
1525
  private parseLessonContent(content?: string | null): any {
1321
1526
  if (!content) return null;
1322
1527
 
@@ -4,6 +4,7 @@ import { QueueModule } from '@hed-hog/queue';
4
4
  import { forwardRef, Module } from '@nestjs/common';
5
5
  import { InstructorModule } from '../instructor/instructor.module';
6
6
  import { CourseLessonController } from './course-lesson.controller';
7
+ import { CourseOperationsController } from './course-operations.controller';
7
8
  import { CourseOperationsIntegrationService } from './course-operations-integration.service';
8
9
  import { CourseStructureController } from './course-structure.controller';
9
10
  import { LmsSettingController } from './lms-setting.controller';
@@ -12,6 +13,7 @@ import { CourseVideoConversionService } from './course-video-conversion.service'
12
13
  import { CourseController } from './course.controller';
13
14
  import { LmsCoursesMcpTools } from './course.mcp-tools';
14
15
  import { CourseService } from './course.service';
16
+ import { LmsOperationsTaskSubscriber } from './lms-operations-task.subscriber';
15
17
 
16
18
  @Module({
17
19
  imports: [
@@ -20,13 +22,14 @@ import { CourseService } from './course.service';
20
22
  forwardRef(() => CoreModule),
21
23
  forwardRef(() => QueueModule),
22
24
  ],
23
- controllers: [CourseController, CourseStructureController, CourseLessonController, LmsSettingController],
25
+ controllers: [CourseController, CourseStructureController, CourseLessonController, LmsSettingController, CourseOperationsController],
24
26
  providers: [
25
27
  CourseOperationsIntegrationService,
26
28
  CourseService,
27
29
  CourseStructureService,
28
30
  CourseVideoConversionService,
29
31
  LmsCoursesMcpTools,
32
+ LmsOperationsTaskSubscriber,
30
33
  ],
31
34
  exports: [
32
35
  forwardRef(() => CourseService),
@@ -1,5 +1,5 @@
1
1
  import { PrismaService } from '@hed-hog/api-prisma';
2
- import { IntegrationDeveloperApiService } from '@hed-hog/core';
2
+ import { FileService, IntegrationDeveloperApiService } from '@hed-hog/core';
3
3
  import { BadRequestException, Inject, Injectable, forwardRef } from '@nestjs/common';
4
4
  import { CourseOperationsIntegrationService } from './course-operations-integration.service';
5
5
  import { CreateCourseDto } from './dto/create-course.dto';
@@ -33,6 +33,7 @@ type PersistCourseExtrasInput = {
33
33
  export class CourseService {
34
34
  constructor(
35
35
  private readonly prisma: PrismaService,
36
+ private readonly fileService: FileService,
36
37
  @Inject(forwardRef(() => IntegrationDeveloperApiService))
37
38
  private readonly integrationApi: IntegrationDeveloperApiService,
38
39
  private readonly operationsIntegration: CourseOperationsIntegrationService,
@@ -713,6 +714,24 @@ export class CourseService {
713
714
  }
714
715
 
715
716
  async remove(id: number) {
717
+ const course = await this.prisma.course.findUnique({
718
+ where: { id },
719
+ select: { id: true, status: true },
720
+ });
721
+
722
+ if (!course) {
723
+ throw new BadRequestException('Course not found');
724
+ }
725
+
726
+ if (course.status !== 'archived') {
727
+ throw new BadRequestException('ONLY_ARCHIVED_COURSE_CAN_BE_DELETED');
728
+ }
729
+
730
+ const fileIds = await this.getCourseRelatedFileIds(id);
731
+ if (fileIds.length > 0) {
732
+ await this.fileService.delete('pt', { ids: fileIds }).catch(() => undefined);
733
+ }
734
+
716
735
  await this.prisma.course.delete({ where: { id } });
717
736
 
718
737
  await this.integrationApi.publishEvent({
@@ -726,6 +745,53 @@ export class CourseService {
726
745
  return { success: true };
727
746
  }
728
747
 
748
+ private async getCourseRelatedFileIds(courseId: number) {
749
+ const rows = await this.prisma.$queryRaw<
750
+ Array<{ file_id: number | null }>
751
+ >`
752
+ SELECT DISTINCT files.file_id
753
+ FROM (
754
+ SELECT ci.file_id
755
+ FROM course_image ci
756
+ WHERE ci.course_id = ${courseId}
757
+
758
+ UNION ALL
759
+
760
+ SELECT cf.file_id
761
+ FROM course_file cf
762
+ WHERE cf.course_id = ${courseId}
763
+
764
+ UNION ALL
765
+
766
+ SELECT clf.file_id
767
+ FROM course_lesson_file clf
768
+ INNER JOIN course_lesson cl ON cl.id = clf.course_lesson_id
769
+ INNER JOIN course_module cm ON cm.id = cl.course_module_id
770
+ WHERE cm.course_id = ${courseId}
771
+
772
+ UNION ALL
773
+
774
+ SELECT clvf.file_id
775
+ FROM course_lesson_video_frame clvf
776
+ INNER JOIN course_lesson cl ON cl.id = clvf.course_lesson_id
777
+ INNER JOIN course_module cm ON cm.id = cl.course_module_id
778
+ WHERE cm.course_id = ${courseId}
779
+
780
+ UNION ALL
781
+
782
+ SELECT cgm.file_id
783
+ FROM course_class_group_material cgm
784
+ INNER JOIN course_class_group ccg ON ccg.id = cgm.course_class_group_id
785
+ WHERE ccg.course_id = ${courseId}
786
+ ) AS files
787
+ WHERE files.file_id IS NOT NULL
788
+ `;
789
+
790
+ return rows
791
+ .map((row) => Number(row.file_id))
792
+ .filter((id) => Number.isInteger(id) && id > 0);
793
+ }
794
+
729
795
  private mapCourse(
730
796
  c: any,
731
797
  metrics?: {
@@ -82,6 +82,23 @@ export class CreateCourseStructureLessonDto {
82
82
  @IsOptional()
83
83
  duracao: number = 0;
84
84
 
85
+ @IsBoolean()
86
+ @IsOptional()
87
+ published?: boolean;
88
+
89
+ @IsEnum(['preparada', 'gravada', 'editada', 'finalizada', 'publicada'])
90
+ @IsOptional()
91
+ statusProducao?:
92
+ | 'preparada'
93
+ | 'gravada'
94
+ | 'editada'
95
+ | 'finalizada'
96
+ | 'publicada';
97
+
98
+ @IsBoolean()
99
+ @IsOptional()
100
+ confirmarPublicacaoComStatus?: boolean;
101
+
85
102
  @IsEnum(['youtube', 'vimeo', 'file_storage', 'bunny', 'custom'])
86
103
  @IsOptional()
87
104
  videoProvedor?: 'youtube' | 'vimeo' | 'file_storage' | 'bunny' | 'custom';
@@ -1,4 +1,12 @@
1
- import { IsInt, IsNotEmpty, IsOptional, IsString, MaxLength, Min } from 'class-validator';
1
+ import {
2
+ IsBoolean,
3
+ IsInt,
4
+ IsNotEmpty,
5
+ IsOptional,
6
+ IsString,
7
+ MaxLength,
8
+ Min,
9
+ } from 'class-validator';
2
10
 
3
11
  export class CreateCourseStructureSessionDto {
4
12
  @IsString()
@@ -19,4 +27,8 @@ export class CreateCourseStructureSessionDto {
19
27
  @IsString()
20
28
  @IsOptional()
21
29
  descricao?: string;
30
+
31
+ @IsBoolean()
32
+ @IsOptional()
33
+ published?: boolean;
22
34
  }
@@ -0,0 +1,16 @@
1
+ import { IsIn, IsInt, IsOptional, IsPositive, IsString } from 'class-validator';
2
+
3
+ export class UpdateCourseOperationsConfigDto {
4
+ @IsOptional()
5
+ @IsInt()
6
+ @IsPositive()
7
+ projectId?: number | null;
8
+
9
+ @IsOptional()
10
+ @IsIn(['per_lesson', 'per_production_status'])
11
+ taskMode?: 'per_lesson' | 'per_production_status' | null;
12
+
13
+ @IsOptional()
14
+ @IsString()
15
+ completionStatus?: string | null;
16
+ }
@@ -0,0 +1,44 @@
1
+ import { IntegrationDeveloperApiService } from '@hed-hog/core';
2
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
3
+ import { PrismaService } from '@hed-hog/api-prisma';
4
+ import { CourseOperationsIntegrationService } from './course-operations-integration.service';
5
+
6
+ @Injectable()
7
+ export class LmsOperationsTaskSubscriber implements OnModuleInit {
8
+ private readonly logger = new Logger(LmsOperationsTaskSubscriber.name);
9
+
10
+ constructor(
11
+ private readonly integrationApi: IntegrationDeveloperApiService,
12
+ private readonly operationsIntegration: CourseOperationsIntegrationService,
13
+ private readonly prisma: PrismaService,
14
+ ) {}
15
+
16
+ onModuleInit(): void {
17
+ this.integrationApi.subscribeMany([
18
+ {
19
+ eventName: 'operations.task.updated',
20
+ consumerName: 'lms.lesson-status-from-task',
21
+ priority: 5,
22
+ handler: async (event) => {
23
+ try {
24
+ const status = event.payload?.status as string | undefined;
25
+ if (status !== 'done') return;
26
+
27
+ const taskId = Number(event.aggregateId);
28
+ if (!taskId || !Number.isInteger(taskId) || taskId <= 0) return;
29
+
30
+ await this.operationsIntegration.applyTaskCompletionToLesson(
31
+ taskId,
32
+ '',
33
+ this.prisma,
34
+ );
35
+ } catch (err) {
36
+ this.logger.error(
37
+ `Error applying task completion to lesson: ${String(err)}`,
38
+ );
39
+ }
40
+ },
41
+ },
42
+ ]);
43
+ }
44
+ }
@@ -67,7 +67,7 @@ export class EnterpriseService {
67
67
  take: pageSize,
68
68
  orderBy: { name: 'asc' },
69
69
  include: {
70
- person: { select: { id: true, name: true } },
70
+ person: { select: { id: true, name: true, avatar_id: true } },
71
71
  _count: {
72
72
  select: {
73
73
  enterprise_user: true,
@@ -1,20 +1,12 @@
1
- import { Role, User } from '@hed-hog/api';
2
- import { Body, Controller, Get, Param, ParseIntPipe, Post, Query } from '@nestjs/common';
1
+ import { NoRole, User } from '@hed-hog/api';
2
+ import { Controller, Get, Param, ParseIntPipe, Query } from '@nestjs/common';
3
3
  import { TrainingStudentService } from './training-student.service';
4
4
 
5
- @Role()
5
+ @NoRole()
6
6
  @Controller('lms/enterprise/training/student')
7
7
  export class TrainingStudentController {
8
8
  constructor(private readonly trainingStudentService: TrainingStudentService) {}
9
9
 
10
- @Get()
11
- getDashboard(
12
- @User('id') userId: number,
13
- @Query('enterpriseId', new ParseIntPipe({ optional: true })) enterpriseId?: number,
14
- ) {
15
- return this.trainingStudentService.getDashboard(userId, enterpriseId);
16
- }
17
-
18
10
  @Get('class-groups')
19
11
  getClassGroups(
20
12
  @User('id') userId: number,
@@ -62,20 +54,4 @@ export class TrainingStudentController {
62
54
  ) {
63
55
  return this.trainingStudentService.getMyEvaluations(userId, classGroupId);
64
56
  }
65
-
66
- @Get('streak')
67
- getStreak(
68
- @User('id') userId: number,
69
- @Query('days', new ParseIntPipe({ optional: true })) days?: number,
70
- ) {
71
- return this.trainingStudentService.getStreak(userId, days);
72
- }
73
-
74
- @Post('streak/activities')
75
- registerStreakActivity(
76
- @User('id') userId: number,
77
- @Body() body?: { activityType?: string; source?: string; activityDateKey?: string },
78
- ) {
79
- return this.trainingStudentService.registerStreakActivity(userId, body);
80
- }
81
57
  }