@hed-hog/lms 0.0.355 → 0.0.357

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 (73) 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-structure.controller.d.ts +12 -0
  5. package/dist/course/course-structure.controller.d.ts.map +1 -1
  6. package/dist/course/course-structure.service.d.ts +22 -0
  7. package/dist/course/course-structure.service.d.ts.map +1 -1
  8. package/dist/course/course-structure.service.js +147 -18
  9. package/dist/course/course-structure.service.js.map +1 -1
  10. package/dist/course/course.service.d.ts +4 -2
  11. package/dist/course/course.service.d.ts.map +1 -1
  12. package/dist/course/course.service.js +61 -2
  13. package/dist/course/course.service.js.map +1 -1
  14. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +3 -0
  15. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  16. package/dist/course/dto/create-course-structure-lesson.dto.js +15 -0
  17. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  18. package/dist/course/dto/create-course-structure-session.dto.d.ts +1 -0
  19. package/dist/course/dto/create-course-structure-session.dto.d.ts.map +1 -1
  20. package/dist/course/dto/create-course-structure-session.dto.js +5 -0
  21. package/dist/course/dto/create-course-structure-session.dto.js.map +1 -1
  22. package/dist/enterprise/training/training-student.controller.d.ts +0 -95
  23. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
  24. package/dist/enterprise/training/training-student.controller.js +1 -34
  25. package/dist/enterprise/training/training-student.controller.js.map +1 -1
  26. package/dist/enterprise/training/training-student.service.d.ts +63 -0
  27. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  28. package/dist/enterprise/training/training-student.service.js +320 -4
  29. package/dist/enterprise/training/training-student.service.js.map +1 -1
  30. package/dist/lms.module.d.ts.map +1 -1
  31. package/dist/lms.module.js +2 -0
  32. package/dist/lms.module.js.map +1 -1
  33. package/dist/platforma/platforma.controller.d.ts +287 -0
  34. package/dist/platforma/platforma.controller.d.ts.map +1 -0
  35. package/dist/platforma/platforma.controller.js +147 -0
  36. package/dist/platforma/platforma.controller.js.map +1 -0
  37. package/hedhog/data/route.yaml +75 -9
  38. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -2
  39. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -13
  40. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2 -2
  41. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
  42. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +17 -6
  43. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +14 -7
  44. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +92 -53
  45. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +25 -60
  46. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -0
  47. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +11 -0
  48. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +22 -0
  49. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +79 -64
  50. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +31 -14
  51. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +3 -4
  52. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +28 -24
  53. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +20 -0
  54. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +41 -6
  55. package/hedhog/frontend/app/courses/page.tsx.ejs +59 -15
  56. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +1 -1
  57. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +2 -2
  58. package/hedhog/frontend/app/paths/page.tsx.ejs +1 -1
  59. package/hedhog/frontend/app/training/page.tsx.ejs +1 -1
  60. package/hedhog/frontend/messages/en.json +7 -2
  61. package/hedhog/frontend/messages/pt.json +7 -2
  62. package/hedhog/table/course_lesson.yaml +2 -2
  63. package/hedhog/table/course_module.yaml +3 -0
  64. package/package.json +8 -8
  65. package/src/course/course-audio-transcription.service.ts +21 -8
  66. package/src/course/course-structure.service.ts +204 -3
  67. package/src/course/course.service.ts +67 -1
  68. package/src/course/dto/create-course-structure-lesson.dto.ts +17 -0
  69. package/src/course/dto/create-course-structure-session.dto.ts +13 -1
  70. package/src/enterprise/training/training-student.controller.ts +3 -27
  71. package/src/enterprise/training/training-student.service.ts +350 -2
  72. package/src/lms.module.ts +2 -0
  73. package/src/platforma/platforma.controller.ts +92 -0
@@ -1,5 +1,5 @@
1
1
  import { PrismaService } from '@hed-hog/api-prisma';
2
- import { FileService, NotificationService, SettingService } from '@hed-hog/core';
2
+ import { buildAiConfigFromIntegration, FileService, NotificationService, SettingService } from '@hed-hog/core';
3
3
  import { IJobHandler, QueueHandlerRegistry } from '@hed-hog/queue';
4
4
  import {
5
5
  Inject,
@@ -153,12 +153,9 @@ export class CourseAudioTranscriptionService implements OnModuleInit, IJobHandle
153
153
  };
154
154
 
155
155
  const settings = await this.settingService.getSettingValues([
156
- 'ai-openai-api-key',
157
- 'ai-openai-api-key-enabled',
156
+ 'ai-openai-profile-id',
158
157
  'lms-audio-transcription-enabled',
159
158
  ]);
160
- const apiKey = settings['ai-openai-api-key'] as string;
161
- const enabled = settings['ai-openai-api-key-enabled'];
162
159
  const transcriptionEnabled = settings['lms-audio-transcription-enabled'] !== false;
163
160
 
164
161
  if (!transcriptionEnabled) {
@@ -176,15 +173,31 @@ export class CourseAudioTranscriptionService implements OnModuleInit, IJobHandle
176
173
  return;
177
174
  }
178
175
 
179
- if (!enabled || !apiKey) {
176
+ const profileId = Number(settings['ai-openai-profile-id']);
177
+ let apiKey = '';
178
+
179
+ if (profileId) {
180
+ const profile = await (this.prismaService as any).integration_profile.findUnique({
181
+ where: { id: profileId },
182
+ include: { integration_provider: { select: { slug: true } } },
183
+ });
184
+ if (profile) {
185
+ apiKey = buildAiConfigFromIntegration(
186
+ profile.integration_provider.slug,
187
+ profile.config,
188
+ ).apiKey;
189
+ }
190
+ }
191
+
192
+ if (!apiKey) {
180
193
  this.logger.warn(
181
- `Transcription skipped for lesson ${lessonId}: OpenAI key not configured`,
194
+ `Transcription skipped for lesson ${lessonId}: OpenAI profile not configured`,
182
195
  );
183
196
  if (notificationContext) {
184
197
  await this.updateAsyncNotification(
185
198
  notificationContext,
186
199
  100,
187
- 'Transcrição não iniciada: chave da IA não configurada.',
200
+ 'Transcrição não iniciada: perfil de IA não configurado em Settings → AI.',
188
201
  false,
189
202
  );
190
203
  }
@@ -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}`
@@ -96,9 +102,16 @@ export class CourseStructureService {
96
102
  this.operationsIntegration.getCourseProjectLinks([courseId]),
97
103
  ]);
98
104
 
99
- const lessonTaskLinks = await this.operationsIntegration.getLessonTaskLinks(
100
- modules.flatMap((module) => module.course_lesson.map((lesson) => lesson.id)),
105
+ const lessonIds = modules.flatMap((module) =>
106
+ module.course_lesson.map((lesson) => lesson.id),
101
107
  );
108
+
109
+ const [lessonTaskLinks, sessionPublishedById, lessonPublishedById] =
110
+ await Promise.all([
111
+ this.operationsIntegration.getLessonTaskLinks(lessonIds),
112
+ this.getSessionPublishedByIds(modules.map((module) => module.id)),
113
+ this.getLessonPublishedByIds(lessonIds),
114
+ ]);
102
115
  const courseProject = courseProjectLinks.get(courseId);
103
116
 
104
117
  const sessoes = modules.map((module, index) => ({
@@ -106,6 +119,7 @@ export class CourseStructureService {
106
119
  codigo: this.formatCode('S', module.order ?? index + 1),
107
120
  titulo: module.title,
108
121
  duracao: module.duration_minutes ?? 0,
122
+ published: sessionPublishedById.get(module.id) ?? false,
109
123
  collapsed: false,
110
124
  }));
111
125
 
@@ -135,6 +149,10 @@ export class CourseStructureService {
135
149
  ? String(lesson.course_lesson_question[0].question_id)
136
150
  : undefined,
137
151
  conteudoPost: parsedContent?.conteudoPost,
152
+ statusProducao: this.normalizeProductionStatus(
153
+ parsedContent?.statusProducao,
154
+ ),
155
+ published: lessonPublishedById.get(lesson.id) ?? false,
138
156
  recursos: lesson.course_lesson_file.map((fileLink) => ({
139
157
  id: String(fileLink.id),
140
158
  nome: fileLink.title,
@@ -244,11 +262,15 @@ export class CourseStructureService {
244
262
  },
245
263
  });
246
264
 
265
+ const published = dto.published ?? false;
266
+ await this.setSessionPublished(created.id, published);
267
+
247
268
  return {
248
269
  id: String(created.id),
249
270
  codigo: this.formatCode('S', created.order ?? nextOrder),
250
271
  titulo: created.title,
251
272
  duracao: created.duration_minutes ?? 0,
273
+ published,
252
274
  collapsed: false,
253
275
  };
254
276
  }
@@ -282,11 +304,17 @@ export class CourseStructureService {
282
304
  },
283
305
  });
284
306
 
307
+ const published = dto.published ?? (await this.getSessionPublished(sessionId));
308
+ if (dto.published !== undefined) {
309
+ await this.setSessionPublished(sessionId, dto.published);
310
+ }
311
+
285
312
  return {
286
313
  id: String(updated.id),
287
314
  codigo: this.formatCode('S', updated.order ?? 0),
288
315
  titulo: updated.title,
289
316
  duracao: updated.duration_minutes ?? 0,
317
+ published,
290
318
  collapsed: false,
291
319
  };
292
320
  }
@@ -335,6 +363,23 @@ export class CourseStructureService {
335
363
  const created = await this.prisma.$transaction(async (tx) => {
336
364
  const nextOrder = await this.nextLessonOrder(sessionId, tx);
337
365
  const mappedType = this.mapUiTypeToDbType(dto.tipo);
366
+ const normalizedStatus = this.normalizeProductionStatus(dto.statusProducao);
367
+ const shouldPublish = dto.published ?? false;
368
+
369
+ if (
370
+ shouldPublish &&
371
+ normalizedStatus !== 'publicada' &&
372
+ !dto.confirmarPublicacaoComStatus
373
+ ) {
374
+ throw new BadRequestException(
375
+ 'LESSON_PUBLISH_REQUIRES_STATUS_CONFIRMATION',
376
+ );
377
+ }
378
+
379
+ const statusProducao: LessonProductionStatus =
380
+ shouldPublish && dto.confirmarPublicacaoComStatus
381
+ ? 'publicada'
382
+ : normalizedStatus;
338
383
 
339
384
  const lesson = await tx.course_lesson.create({
340
385
  data: {
@@ -344,10 +389,15 @@ export class CourseStructureService {
344
389
  type: mappedType,
345
390
  duration_seconds: dto.duracao * 60,
346
391
  order: nextOrder,
347
- content: this.serializeContent(dto),
392
+ content: this.serializeContent({
393
+ ...dto,
394
+ statusProducao,
395
+ }),
348
396
  },
349
397
  });
350
398
 
399
+ await this.setLessonPublished(lesson.id, shouldPublish, tx);
400
+
351
401
  await this.syncLessonRelations(lesson.id, dto, tx);
352
402
 
353
403
  const projectLinks = await this.operationsIntegration.getCourseProjectLinks(
@@ -410,10 +460,36 @@ export class CourseStructureService {
410
460
  conteudoPost: dto.conteudoPost,
411
461
  exameVinculado: dto.exameVinculado,
412
462
  descricaoPrivada: dto.descricaoPrivada,
463
+ statusProducao: (() => {
464
+ const parsed = this.parseLessonContent(lesson.content);
465
+ const currentStatus = this.normalizeProductionStatus(
466
+ parsed?.statusProducao,
467
+ );
468
+ const nextStatus = this.normalizeProductionStatus(
469
+ dto.statusProducao ?? currentStatus,
470
+ );
471
+
472
+ const shouldPublish = dto.published;
473
+ if (shouldPublish === true && nextStatus !== 'publicada') {
474
+ if (!dto.confirmarPublicacaoComStatus) {
475
+ throw new BadRequestException(
476
+ 'LESSON_PUBLISH_REQUIRES_STATUS_CONFIRMATION',
477
+ );
478
+ }
479
+
480
+ return 'publicada';
481
+ }
482
+
483
+ return nextStatus;
484
+ })(),
413
485
  }),
414
486
  },
415
487
  });
416
488
 
489
+ if (dto.published !== undefined) {
490
+ await this.setLessonPublished(lessonId, dto.published);
491
+ }
492
+
417
493
  await this.syncLessonRelations(lessonId, dto);
418
494
  await this.operationsIntegration.syncTaskForLesson(lessonId);
419
495
 
@@ -756,6 +832,9 @@ export class CourseStructureService {
756
832
  },
757
833
  });
758
834
 
835
+ const sourcePublished = await this.getSessionPublished(sourceModule.id);
836
+ await this.setSessionPublished(newModule.id, sourcePublished);
837
+
759
838
  const newLessons = [];
760
839
  const projectLinks = await this.operationsIntegration.getCourseProjectLinks([
761
840
  courseId,
@@ -775,6 +854,9 @@ export class CourseStructureService {
775
854
  },
776
855
  });
777
856
 
857
+ const lessonPublished = await this.getLessonPublished(lesson.id);
858
+ await this.setLessonPublished(newLesson.id, lessonPublished);
859
+
778
860
  if (lesson.course_lesson_file.length > 0) {
779
861
  await (this.prisma as any).course_lesson_file.createMany({
780
862
  data: lesson.course_lesson_file.map((f) => ({
@@ -825,6 +907,7 @@ export class CourseStructureService {
825
907
  codigo: this.formatCode('S', newModule.order ?? nextOrder),
826
908
  titulo: newModule.title,
827
909
  duracao: newModule.duration_minutes ?? 0,
910
+ published: sourcePublished,
828
911
  collapsed: false,
829
912
  },
830
913
  lessons: newLessons,
@@ -877,6 +960,9 @@ export class CourseStructureService {
877
960
  },
878
961
  });
879
962
 
963
+ const sourcePublished = await this.getLessonPublished(sourceLesson.id);
964
+ await this.setLessonPublished(newLesson.id, sourcePublished);
965
+
880
966
  if (sourceLesson.course_lesson_file.length > 0) {
881
967
  await (this.prisma as any).course_lesson_file.createMany({
882
968
  data: sourceLesson.course_lesson_file.map((f) => ({
@@ -966,6 +1052,9 @@ export class CourseStructureService {
966
1052
  },
967
1053
  });
968
1054
 
1055
+ const sourcePublished = await this.getLessonPublished(lesson.id);
1056
+ await this.setLessonPublished(newLesson.id, sourcePublished);
1057
+
969
1058
  if (lesson.course_lesson_file.length > 0) {
970
1059
  await (this.prisma as any).course_lesson_file.createMany({
971
1060
  data: lesson.course_lesson_file.map((f) => ({
@@ -1182,6 +1271,7 @@ export class CourseStructureService {
1182
1271
  const taskLinks = await this.operationsIntegration.getLessonTaskLinks([
1183
1272
  lessonId,
1184
1273
  ]);
1274
+ const lessonPublished = await this.getLessonPublished(lessonId);
1185
1275
  const parsedContent = this.parseLessonContent(lesson.content);
1186
1276
  const tipo = this.mapDbTypeToUiType(lesson.type, parsedContent?.sourceType);
1187
1277
 
@@ -1205,6 +1295,8 @@ export class CourseStructureService {
1205
1295
  : lesson.course_lesson_question[0]?.question_id != null
1206
1296
  ? String(lesson.course_lesson_question[0].question_id)
1207
1297
  : undefined,
1298
+ statusProducao: this.normalizeProductionStatus(parsedContent?.statusProducao),
1299
+ published: lessonPublished,
1208
1300
  conteudoPost: parsedContent?.conteudoPost,
1209
1301
  recursos: lesson.course_lesson_file.map((fileLink) => ({
1210
1302
  id: String(fileLink.id),
@@ -1290,10 +1382,12 @@ export class CourseStructureService {
1290
1382
  conteudoPost?: string;
1291
1383
  exameVinculado?: number;
1292
1384
  descricaoPrivada?: string;
1385
+ statusProducao?: LessonProductionStatus;
1293
1386
  }) {
1294
1387
  const payload: Record<string, unknown> = {
1295
1388
  sourceType: data.tipo,
1296
1389
  descricaoPrivada: data.descricaoPrivada ?? '',
1390
+ statusProducao: this.normalizeProductionStatus(data.statusProducao),
1297
1391
  };
1298
1392
 
1299
1393
  if (data.tipo === 'video') {
@@ -1317,6 +1411,113 @@ export class CourseStructureService {
1317
1411
  return JSON.stringify(payload);
1318
1412
  }
1319
1413
 
1414
+ private normalizeProductionStatus(
1415
+ value?: string | null,
1416
+ ): LessonProductionStatus {
1417
+ if (!value) return 'preparada';
1418
+
1419
+ const normalized = String(value).trim().toLowerCase();
1420
+ if (normalized === 'preparada') return 'preparada';
1421
+ if (normalized === 'gravada') return 'gravada';
1422
+ if (normalized === 'editada') return 'editada';
1423
+ if (normalized === 'finalizada') return 'finalizada';
1424
+ if (normalized === 'publicada') return 'publicada';
1425
+
1426
+ return 'preparada';
1427
+ }
1428
+
1429
+ private normalizeIdList(ids: number[]) {
1430
+ return ids
1431
+ .map((id) => Number(id))
1432
+ .filter((id) => Number.isInteger(id) && id > 0);
1433
+ }
1434
+
1435
+ private buildIdCsv(ids: number[]) {
1436
+ return this.normalizeIdList(ids).join(',');
1437
+ }
1438
+
1439
+ private async getSessionPublishedByIds(sessionIds: number[]) {
1440
+ const publishedById = new Map<number, boolean>();
1441
+ const csv = this.buildIdCsv(sessionIds);
1442
+
1443
+ if (!csv) return publishedById;
1444
+
1445
+ try {
1446
+ const rows = await this.prisma.$queryRawUnsafe<
1447
+ Array<{ id: number; published: boolean }>
1448
+ >(
1449
+ `SELECT id, published FROM course_module WHERE id IN (${csv})`,
1450
+ );
1451
+
1452
+ for (const row of rows) {
1453
+ publishedById.set(row.id, Boolean(row.published));
1454
+ }
1455
+ } catch {
1456
+ return publishedById;
1457
+ }
1458
+
1459
+ return publishedById;
1460
+ }
1461
+
1462
+ private async getSessionPublished(sessionId: number) {
1463
+ const rows = await this.prisma.$queryRawUnsafe<
1464
+ Array<{ published: boolean }>
1465
+ >(
1466
+ `SELECT published FROM course_module WHERE id = ${Number(sessionId)} LIMIT 1`,
1467
+ );
1468
+
1469
+ return Boolean(rows[0]?.published);
1470
+ }
1471
+
1472
+ private async setSessionPublished(sessionId: number, published: boolean) {
1473
+ await this.prisma.$executeRawUnsafe(
1474
+ `UPDATE course_module SET published = ${published ? 'true' : 'false'} WHERE id = ${Number(sessionId)}`,
1475
+ );
1476
+ }
1477
+
1478
+ private async getLessonPublishedByIds(lessonIds: number[]) {
1479
+ const publishedById = new Map<number, boolean>();
1480
+ const csv = this.buildIdCsv(lessonIds);
1481
+
1482
+ if (!csv) return publishedById;
1483
+
1484
+ try {
1485
+ const rows = await this.prisma.$queryRawUnsafe<
1486
+ Array<{ id: number; published: boolean }>
1487
+ >(
1488
+ `SELECT id, published FROM course_lesson WHERE id IN (${csv})`,
1489
+ );
1490
+
1491
+ for (const row of rows) {
1492
+ publishedById.set(row.id, Boolean(row.published));
1493
+ }
1494
+ } catch {
1495
+ return publishedById;
1496
+ }
1497
+
1498
+ return publishedById;
1499
+ }
1500
+
1501
+ private async getLessonPublished(lessonId: number) {
1502
+ const rows = await this.prisma.$queryRawUnsafe<
1503
+ Array<{ published: boolean }>
1504
+ >(
1505
+ `SELECT published FROM course_lesson WHERE id = ${Number(lessonId)} LIMIT 1`,
1506
+ );
1507
+
1508
+ return Boolean(rows[0]?.published);
1509
+ }
1510
+
1511
+ private async setLessonPublished(
1512
+ lessonId: number,
1513
+ published: boolean,
1514
+ client: any = this.prisma,
1515
+ ) {
1516
+ await client.$executeRawUnsafe(
1517
+ `UPDATE course_lesson SET published = ${published ? 'true' : 'false'} WHERE id = ${Number(lessonId)}`,
1518
+ );
1519
+ }
1520
+
1320
1521
  private parseLessonContent(content?: string | null): any {
1321
1522
  if (!content) return null;
1322
1523
 
@@ -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
  }
@@ -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
  }