@hed-hog/lms 0.0.354 → 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.
- 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-structure.controller.d.ts +12 -0
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.service.d.ts +22 -0
- package/dist/course/course-structure.service.d.ts.map +1 -1
- package/dist/course/course-structure.service.js +147 -18
- package/dist/course/course-structure.service.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/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/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 +75 -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/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/editor-course.tsx.ejs +14 -7
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +92 -53
- 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 +3 -4
- package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +28 -24
- package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +20 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +41 -6
- package/hedhog/frontend/app/courses/page.tsx.ejs +59 -15
- 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/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 +9 -9
- package/src/course/course-audio-transcription.service.ts +21 -8
- package/src/course/course-structure.service.ts +204 -3
- 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/enterprise/training/training-student.controller.ts +3 -27
- package/src/enterprise/training/training-student.service.ts +350 -2
- package/src/lms.module.ts +2 -0
- 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-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
100
|
-
|
|
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(
|
|
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 {
|
|
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 {
|
|
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
|
}
|