@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.
- package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
- package/dist/course/course-audio-transcription.service.js +15 -7
- package/dist/course/course-audio-transcription.service.js.map +1 -1
- package/dist/course/course-operations-integration.service.d.ts +31 -0
- package/dist/course/course-operations-integration.service.d.ts.map +1 -1
- package/dist/course/course-operations-integration.service.js +286 -22
- package/dist/course/course-operations-integration.service.js.map +1 -1
- package/dist/course/course-operations.controller.d.ts +10 -0
- package/dist/course/course-operations.controller.d.ts.map +1 -0
- package/dist/course/course-operations.controller.js +67 -0
- package/dist/course/course-operations.controller.js.map +1 -0
- package/dist/course/course-structure.controller.d.ts +15 -1
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.service.d.ts +25 -1
- package/dist/course/course-structure.service.d.ts.map +1 -1
- package/dist/course/course-structure.service.js +160 -24
- package/dist/course/course-structure.service.js.map +1 -1
- package/dist/course/course.module.d.ts.map +1 -1
- package/dist/course/course.module.js +4 -1
- package/dist/course/course.module.js.map +1 -1
- package/dist/course/course.service.d.ts +4 -2
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +61 -2
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts +3 -0
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course-structure-lesson.dto.js +15 -0
- package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
- package/dist/course/dto/create-course-structure-session.dto.d.ts +1 -0
- package/dist/course/dto/create-course-structure-session.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course-structure-session.dto.js +5 -0
- package/dist/course/dto/create-course-structure-session.dto.js.map +1 -1
- package/dist/course/dto/update-course-operations-config.dto.d.ts +6 -0
- package/dist/course/dto/update-course-operations-config.dto.d.ts.map +1 -0
- package/dist/course/dto/update-course-operations-config.dto.js +33 -0
- package/dist/course/dto/update-course-operations-config.dto.js.map +1 -0
- package/dist/course/lms-operations-task.subscriber.d.ts +13 -0
- package/dist/course/lms-operations-task.subscriber.d.ts.map +1 -0
- package/dist/course/lms-operations-task.subscriber.js +57 -0
- package/dist/course/lms-operations-task.subscriber.js.map +1 -0
- package/dist/enterprise/enterprise.service.js +1 -1
- package/dist/enterprise/enterprise.service.js.map +1 -1
- package/dist/enterprise/training/training-student.controller.d.ts +0 -95
- package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
- package/dist/enterprise/training/training-student.controller.js +1 -34
- package/dist/enterprise/training/training-student.controller.js.map +1 -1
- package/dist/enterprise/training/training-student.service.d.ts +63 -0
- package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
- package/dist/enterprise/training/training-student.service.js +320 -4
- package/dist/enterprise/training/training-student.service.js.map +1 -1
- package/dist/instructor/instructor.service.d.ts.map +1 -1
- package/dist/instructor/instructor.service.js +12 -3
- package/dist/instructor/instructor.service.js.map +1 -1
- package/dist/lms.module.d.ts.map +1 -1
- package/dist/lms.module.js +2 -0
- package/dist/lms.module.js.map +1 -1
- package/dist/platforma/platforma.controller.d.ts +287 -0
- package/dist/platforma/platforma.controller.d.ts.map +1 -0
- package/dist/platforma/platforma.controller.js +147 -0
- package/dist/platforma/platforma.controller.js.map +1 -0
- package/hedhog/data/route.yaml +102 -9
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -2
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -13
- package/hedhog/frontend/app/_lib/editor/templateSerializer.ts.ejs +26 -4
- package/hedhog/frontend/app/_lib/editor/types.ts.ejs +6 -0
- package/hedhog/frontend/app/certificates/models/CanvasStage.tsx.ejs +447 -31
- package/hedhog/frontend/app/certificates/models/LeftPanel.tsx.ejs +59 -47
- package/hedhog/frontend/app/certificates/models/RightPanel.tsx.ejs +201 -35
- package/hedhog/frontend/app/certificates/models/TemplateEditorPage.tsx.ejs +36 -5
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2 -2
- package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +17 -6
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-operations-tab.tsx.ejs +382 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +45 -8
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +177 -67
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +25 -60
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +11 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +22 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +79 -64
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +31 -14
- package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +4 -4
- package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +39 -27
- package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +24 -2
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +41 -6
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-query.ts.ejs +2 -2
- package/hedhog/frontend/app/courses/page.tsx.ejs +80 -103
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +1 -1
- package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +2 -2
- package/hedhog/frontend/app/enterprise/page.tsx.ejs +18 -6
- package/hedhog/frontend/app/exams/[id]/page.tsx.ejs +5 -4
- package/hedhog/frontend/app/instructors/_components/instructor-form-sheet.tsx.ejs +16 -10
- package/hedhog/frontend/app/paths/page.tsx.ejs +1 -1
- package/hedhog/frontend/app/training/page.tsx.ejs +1 -1
- package/hedhog/frontend/messages/en.json +7 -2
- package/hedhog/frontend/messages/pt.json +7 -2
- package/hedhog/table/course_lesson.yaml +2 -2
- package/hedhog/table/course_module.yaml +3 -0
- package/package.json +8 -8
- package/src/course/course-audio-transcription.service.ts +21 -8
- package/src/course/course-operations-integration.service.ts +460 -22
- package/src/course/course-operations.controller.ts +45 -0
- package/src/course/course-structure.service.ts +209 -4
- package/src/course/course.module.ts +4 -1
- package/src/course/course.service.ts +67 -1
- package/src/course/dto/create-course-structure-lesson.dto.ts +17 -0
- package/src/course/dto/create-course-structure-session.dto.ts +13 -1
- package/src/course/dto/update-course-operations-config.dto.ts +16 -0
- package/src/course/lms-operations-task.subscriber.ts +44 -0
- package/src/enterprise/enterprise.service.ts +1 -1
- package/src/enterprise/training/training-student.controller.ts +3 -27
- package/src/enterprise/training/training-student.service.ts +350 -2
- package/src/instructor/instructor.service.ts +12 -3
- package/src/lms.module.ts +2 -0
- 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
|
|
100
|
-
|
|
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
|
-
|
|
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(
|
|
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 {
|
|
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 {
|
|
2
|
-
import {
|
|
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
|
-
@
|
|
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
|
}
|