@hed-hog/lms 0.0.306 → 0.0.309
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-structure.controller.d.ts +60 -0
- package/dist/course/course-structure.controller.d.ts.map +1 -1
- package/dist/course/course-structure.controller.js +79 -0
- package/dist/course/course-structure.controller.js.map +1 -1
- package/dist/course/course-structure.service.d.ts +61 -1
- package/dist/course/course-structure.service.d.ts.map +1 -1
- package/dist/course/course-structure.service.js +326 -1
- package/dist/course/course-structure.service.js.map +1 -1
- package/dist/course/course.controller.d.ts +52 -4
- package/dist/course/course.controller.d.ts.map +1 -1
- package/dist/course/course.service.d.ts +52 -5
- package/dist/course/course.service.d.ts.map +1 -1
- package/dist/course/course.service.js +78 -57
- package/dist/course/course.service.js.map +1 -1
- package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course-structure-lesson.dto.js +5 -1
- package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
- package/dist/course/dto/create-course.dto.d.ts +1 -1
- package/dist/course/dto/create-course.dto.d.ts.map +1 -1
- package/dist/course/dto/create-course.dto.js +4 -1
- package/dist/course/dto/create-course.dto.js.map +1 -1
- package/dist/course/dto/move-lesson.dto.d.ts +10 -0
- package/dist/course/dto/move-lesson.dto.d.ts.map +1 -0
- package/dist/course/dto/move-lesson.dto.js +28 -0
- package/dist/course/dto/move-lesson.dto.js.map +1 -0
- package/dist/course/dto/paste-lessons.dto.d.ts +4 -0
- package/dist/course/dto/paste-lessons.dto.d.ts.map +1 -0
- package/dist/course/dto/paste-lessons.dto.js +24 -0
- package/dist/course/dto/paste-lessons.dto.js.map +1 -0
- package/dist/course/dto/reorder-lessons.dto.d.ts +5 -0
- package/dist/course/dto/reorder-lessons.dto.d.ts.map +1 -0
- package/dist/course/dto/reorder-lessons.dto.js +24 -0
- package/dist/course/dto/reorder-lessons.dto.js.map +1 -0
- package/dist/course/dto/reorder-sessions.dto.d.ts +5 -0
- package/dist/course/dto/reorder-sessions.dto.d.ts.map +1 -0
- package/dist/course/dto/reorder-sessions.dto.js +24 -0
- package/dist/course/dto/reorder-sessions.dto.js.map +1 -0
- package/dist/training/training.controller.js +1 -1
- package/dist/training/training.controller.js.map +1 -1
- package/hedhog/data/image_type.yaml +20 -0
- package/hedhog/data/menu.yaml +2 -2
- package/hedhog/data/route.yaml +60 -6
- package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +146 -165
- package/hedhog/frontend/app/_components/course-avatar.tsx.ejs +70 -0
- package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +372 -22
- package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +437 -77
- package/hedhog/frontend/app/classes/page.tsx.ejs +311 -289
- package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +10 -7
- package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +23 -32
- package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +3 -9
- package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +26 -16
- package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +19 -5
- package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +10 -14
- package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +131 -107
- package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +10 -7
- package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +38 -19
- package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
- package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +336 -1057
- package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +45 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +362 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +111 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +64 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +62 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +174 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +58 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +52 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +276 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +1216 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1827 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +443 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/highlighted-text.tsx.ejs +41 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +184 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +264 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +96 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/session-picker-dialog.tsx.ejs +74 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +136 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +80 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +948 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +525 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +150 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +182 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +52 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +271 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +167 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +108 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +122 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +318 -0
- package/hedhog/frontend/app/courses/[id]/structure/_components/use-tree-display-settings.ts.ejs +97 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +347 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/course-structure-contract.ts.ejs +195 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +420 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +254 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +987 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-query.ts.ejs +86 -0
- package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure.ts.ejs +160 -0
- package/hedhog/frontend/app/courses/[id]/structure/page.tsx.ejs +10 -3212
- package/hedhog/frontend/app/courses/page.tsx.ejs +45 -26
- package/hedhog/frontend/app/{training → paths}/page.tsx.ejs +29 -7
- package/hedhog/frontend/messages/en.json +88 -10
- package/hedhog/frontend/messages/pt.json +88 -10
- package/hedhog/table/course.yaml +1 -1
- package/hedhog/table/image_type.yaml +14 -0
- package/package.json +7 -7
- package/src/course/course-structure.controller.ts +63 -0
- package/src/course/course-structure.service.ts +390 -3
- package/src/course/course.service.ts +59 -27
- package/src/course/dto/create-course-structure-lesson.dto.ts +3 -2
- package/src/course/dto/create-course.dto.ts +4 -1
- package/src/course/dto/move-lesson.dto.ts +17 -0
- package/src/course/dto/paste-lessons.dto.ts +9 -0
- package/src/course/dto/reorder-lessons.dto.ts +10 -0
- package/src/course/dto/reorder-sessions.dto.ts +10 -0
- package/src/training/training.controller.ts +1 -1
|
@@ -12,6 +12,10 @@ import {
|
|
|
12
12
|
import { CourseStructureService } from './course-structure.service';
|
|
13
13
|
import { CreateCourseStructureLessonDto } from './dto/create-course-structure-lesson.dto';
|
|
14
14
|
import { CreateCourseStructureSessionDto } from './dto/create-course-structure-session.dto';
|
|
15
|
+
import { MoveLessonDto } from './dto/move-lesson.dto';
|
|
16
|
+
import { PasteLessonsDto } from './dto/paste-lessons.dto';
|
|
17
|
+
import { ReorderLessonsDto } from './dto/reorder-lessons.dto';
|
|
18
|
+
import { ReorderSessionsDto } from './dto/reorder-sessions.dto';
|
|
15
19
|
import { UpdateCourseStructureLessonDto } from './dto/update-course-structure-lesson.dto';
|
|
16
20
|
import { UpdateCourseStructureSessionDto } from './dto/update-course-structure-session.dto';
|
|
17
21
|
|
|
@@ -25,6 +29,8 @@ export class CourseStructureController {
|
|
|
25
29
|
return this.courseStructureService.getStructure(courseId);
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
// ── Sessions ──────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
28
34
|
@Post('sessions')
|
|
29
35
|
createSession(
|
|
30
36
|
@Param('id', ParseIntPipe) courseId: number,
|
|
@@ -33,6 +39,15 @@ export class CourseStructureController {
|
|
|
33
39
|
return this.courseStructureService.createSession(courseId, dto);
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
// Static PATCH must come before dynamic PATCH sessions/:sessionId
|
|
43
|
+
@Patch('sessions/reorder')
|
|
44
|
+
reorderSessions(
|
|
45
|
+
@Param('id', ParseIntPipe) courseId: number,
|
|
46
|
+
@Body() dto: ReorderSessionsDto,
|
|
47
|
+
) {
|
|
48
|
+
return this.courseStructureService.reorderSessions(courseId, dto);
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
@Patch('sessions/:sessionId')
|
|
37
52
|
updateSession(
|
|
38
53
|
@Param('id', ParseIntPipe) courseId: number,
|
|
@@ -50,6 +65,16 @@ export class CourseStructureController {
|
|
|
50
65
|
return this.courseStructureService.deleteSession(courseId, sessionId);
|
|
51
66
|
}
|
|
52
67
|
|
|
68
|
+
@Post('sessions/:sessionId/duplicate')
|
|
69
|
+
duplicateSession(
|
|
70
|
+
@Param('id', ParseIntPipe) courseId: number,
|
|
71
|
+
@Param('sessionId', ParseIntPipe) sessionId: number,
|
|
72
|
+
) {
|
|
73
|
+
return this.courseStructureService.duplicateSession(courseId, sessionId);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Lessons ───────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
53
78
|
@Post('sessions/:sessionId/lessons')
|
|
54
79
|
createLesson(
|
|
55
80
|
@Param('id', ParseIntPipe) courseId: number,
|
|
@@ -59,6 +84,25 @@ export class CourseStructureController {
|
|
|
59
84
|
return this.courseStructureService.createLesson(courseId, sessionId, dto);
|
|
60
85
|
}
|
|
61
86
|
|
|
87
|
+
@Post('sessions/:sessionId/lessons/paste')
|
|
88
|
+
pasteLessons(
|
|
89
|
+
@Param('id', ParseIntPipe) courseId: number,
|
|
90
|
+
@Param('sessionId', ParseIntPipe) sessionId: number,
|
|
91
|
+
@Body() dto: PasteLessonsDto,
|
|
92
|
+
) {
|
|
93
|
+
return this.courseStructureService.pasteLessons(courseId, sessionId, dto);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Static PATCH must come before dynamic PATCH sessions/:sessionId/lessons/:lessonId
|
|
97
|
+
@Patch('sessions/:sessionId/lessons/reorder')
|
|
98
|
+
reorderLessons(
|
|
99
|
+
@Param('id', ParseIntPipe) courseId: number,
|
|
100
|
+
@Param('sessionId', ParseIntPipe) sessionId: number,
|
|
101
|
+
@Body() dto: ReorderLessonsDto,
|
|
102
|
+
) {
|
|
103
|
+
return this.courseStructureService.reorderLessons(courseId, sessionId, dto);
|
|
104
|
+
}
|
|
105
|
+
|
|
62
106
|
@Patch('sessions/:sessionId/lessons/:lessonId')
|
|
63
107
|
updateLesson(
|
|
64
108
|
@Param('id', ParseIntPipe) courseId: number,
|
|
@@ -74,6 +118,16 @@ export class CourseStructureController {
|
|
|
74
118
|
);
|
|
75
119
|
}
|
|
76
120
|
|
|
121
|
+
@Patch('sessions/:sessionId/lessons/:lessonId/move')
|
|
122
|
+
moveLesson(
|
|
123
|
+
@Param('id', ParseIntPipe) courseId: number,
|
|
124
|
+
@Param('sessionId', ParseIntPipe) sessionId: number,
|
|
125
|
+
@Param('lessonId', ParseIntPipe) lessonId: number,
|
|
126
|
+
@Body() dto: MoveLessonDto,
|
|
127
|
+
) {
|
|
128
|
+
return this.courseStructureService.moveLesson(courseId, sessionId, lessonId, dto);
|
|
129
|
+
}
|
|
130
|
+
|
|
77
131
|
@Delete('sessions/:sessionId/lessons/:lessonId')
|
|
78
132
|
deleteLesson(
|
|
79
133
|
@Param('id', ParseIntPipe) courseId: number,
|
|
@@ -82,4 +136,13 @@ export class CourseStructureController {
|
|
|
82
136
|
) {
|
|
83
137
|
return this.courseStructureService.deleteLesson(courseId, sessionId, lessonId);
|
|
84
138
|
}
|
|
139
|
+
|
|
140
|
+
@Post('sessions/:sessionId/lessons/:lessonId/duplicate')
|
|
141
|
+
duplicateLesson(
|
|
142
|
+
@Param('id', ParseIntPipe) courseId: number,
|
|
143
|
+
@Param('sessionId', ParseIntPipe) sessionId: number,
|
|
144
|
+
@Param('lessonId', ParseIntPipe) lessonId: number,
|
|
145
|
+
) {
|
|
146
|
+
return this.courseStructureService.duplicateLesson(courseId, sessionId, lessonId);
|
|
147
|
+
}
|
|
85
148
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { PrismaService } from '@hed-hog/api-prisma';
|
|
2
2
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
|
3
|
+
import { InstructorService } from '../instructor/instructor.service';
|
|
3
4
|
import { CreateCourseStructureLessonDto } from './dto/create-course-structure-lesson.dto';
|
|
4
5
|
import { CreateCourseStructureSessionDto } from './dto/create-course-structure-session.dto';
|
|
6
|
+
import { MoveLessonDto } from './dto/move-lesson.dto';
|
|
7
|
+
import { PasteLessonsDto } from './dto/paste-lessons.dto';
|
|
8
|
+
import { ReorderLessonsDto } from './dto/reorder-lessons.dto';
|
|
9
|
+
import { ReorderSessionsDto } from './dto/reorder-sessions.dto';
|
|
5
10
|
import { UpdateCourseStructureLessonDto } from './dto/update-course-structure-lesson.dto';
|
|
6
11
|
import { UpdateCourseStructureSessionDto } from './dto/update-course-structure-session.dto';
|
|
7
|
-
import { InstructorService } from '../instructor/instructor.service';
|
|
8
12
|
|
|
9
13
|
type LessonUiType = 'video' | 'questao' | 'post' | 'exercicio';
|
|
10
14
|
type LessonDbType = 'video' | 'text' | 'quiz';
|
|
@@ -19,7 +23,7 @@ export class CourseStructureService {
|
|
|
19
23
|
async getStructure(courseId: number) {
|
|
20
24
|
await this.ensureCourseExists(courseId);
|
|
21
25
|
|
|
22
|
-
const [modules, instructors] = await Promise.all([
|
|
26
|
+
const [modules, instructors, courseRecord] = await Promise.all([
|
|
23
27
|
this.prisma.course_module.findMany({
|
|
24
28
|
where: { course_id: courseId },
|
|
25
29
|
orderBy: { order: 'asc' },
|
|
@@ -50,6 +54,10 @@ export class CourseStructureService {
|
|
|
50
54
|
},
|
|
51
55
|
}),
|
|
52
56
|
this.instructorService.listQualifiedInstructorOptions(['course-lessons']),
|
|
57
|
+
this.prisma.course.findUnique({
|
|
58
|
+
where: { id: courseId },
|
|
59
|
+
select: { id: true, slug: true, title: true, description: true },
|
|
60
|
+
}),
|
|
53
61
|
]);
|
|
54
62
|
|
|
55
63
|
const sessoes = modules.map((module, index) => ({
|
|
@@ -108,6 +116,14 @@ export class CourseStructureService {
|
|
|
108
116
|
id: instructor.id,
|
|
109
117
|
name: instructor.name,
|
|
110
118
|
})),
|
|
119
|
+
curso: courseRecord
|
|
120
|
+
? {
|
|
121
|
+
id: String(courseRecord.id),
|
|
122
|
+
slug: courseRecord.slug ?? '',
|
|
123
|
+
titulo: courseRecord.title,
|
|
124
|
+
descricao: courseRecord.description ?? '',
|
|
125
|
+
}
|
|
126
|
+
: null,
|
|
111
127
|
};
|
|
112
128
|
}
|
|
113
129
|
|
|
@@ -281,8 +297,379 @@ export class CourseStructureService {
|
|
|
281
297
|
return { success: true };
|
|
282
298
|
}
|
|
283
299
|
|
|
284
|
-
|
|
300
|
+
async reorderSessions(courseId: number, dto: ReorderSessionsDto) {
|
|
301
|
+
await this.ensureCourseExists(courseId);
|
|
302
|
+
|
|
303
|
+
// Validate that all provided IDs belong to the course
|
|
304
|
+
const existing = await this.prisma.course_module.findMany({
|
|
305
|
+
where: { course_id: courseId },
|
|
306
|
+
select: { id: true },
|
|
307
|
+
});
|
|
308
|
+
const existingIds = new Set(existing.map((m) => m.id));
|
|
309
|
+
for (const id of dto.sessionIds) {
|
|
310
|
+
if (!existingIds.has(id)) {
|
|
311
|
+
throw new BadRequestException(`Session ${id} does not belong to course ${courseId}`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
await this.prisma.$transaction(
|
|
316
|
+
dto.sessionIds.map((id, index) =>
|
|
317
|
+
this.prisma.course_module.update({
|
|
318
|
+
where: { id },
|
|
319
|
+
data: { order: index + 1 },
|
|
320
|
+
}),
|
|
321
|
+
),
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
return { success: true };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async reorderLessons(courseId: number, sessionId: number, dto: ReorderLessonsDto) {
|
|
328
|
+
const module = await this.prisma.course_module.findFirst({
|
|
329
|
+
where: { id: sessionId, course_id: courseId },
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (!module) {
|
|
333
|
+
throw new NotFoundException('Session not found for this course');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const existing = await this.prisma.course_lesson.findMany({
|
|
337
|
+
where: { course_module_id: sessionId },
|
|
338
|
+
select: { id: true },
|
|
339
|
+
});
|
|
340
|
+
const existingIds = new Set(existing.map((l) => l.id));
|
|
341
|
+
for (const id of dto.lessonIds) {
|
|
342
|
+
if (!existingIds.has(id)) {
|
|
343
|
+
throw new BadRequestException(`Lesson ${id} does not belong to session ${sessionId}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
await this.prisma.$transaction(
|
|
348
|
+
dto.lessonIds.map((id, index) =>
|
|
349
|
+
this.prisma.course_lesson.update({
|
|
350
|
+
where: { id },
|
|
351
|
+
data: { order: index + 1 },
|
|
352
|
+
}),
|
|
353
|
+
),
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
return { success: true };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async moveLesson(
|
|
360
|
+
courseId: number,
|
|
361
|
+
sessionId: number,
|
|
285
362
|
lessonId: number,
|
|
363
|
+
dto: MoveLessonDto,
|
|
364
|
+
) {
|
|
365
|
+
// Ensure the lesson belongs to the source session
|
|
366
|
+
const lesson = await this.prisma.course_lesson.findFirst({
|
|
367
|
+
where: {
|
|
368
|
+
id: lessonId,
|
|
369
|
+
course_module_id: sessionId,
|
|
370
|
+
course_module: { course_id: courseId },
|
|
371
|
+
},
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (!lesson) {
|
|
375
|
+
throw new NotFoundException('Lesson not found for this session');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Ensure the target session belongs to the same course
|
|
379
|
+
const targetModule = await this.prisma.course_module.findFirst({
|
|
380
|
+
where: { id: dto.toSessionId, course_id: courseId },
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
if (!targetModule) {
|
|
384
|
+
throw new NotFoundException('Target session not found for this course');
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (dto.toSessionId === sessionId) {
|
|
388
|
+
// Same session — just reorder
|
|
389
|
+
const siblings = await this.prisma.course_lesson.findMany({
|
|
390
|
+
where: { course_module_id: sessionId, id: { not: lessonId } },
|
|
391
|
+
orderBy: { order: 'asc' },
|
|
392
|
+
select: { id: true },
|
|
393
|
+
});
|
|
394
|
+
const insertAt = Math.min(dto.toIndex ?? siblings.length, siblings.length);
|
|
395
|
+
const orderedIds = [
|
|
396
|
+
...siblings.slice(0, insertAt).map((l) => l.id),
|
|
397
|
+
lessonId,
|
|
398
|
+
...siblings.slice(insertAt).map((l) => l.id),
|
|
399
|
+
];
|
|
400
|
+
await this.prisma.$transaction(
|
|
401
|
+
orderedIds.map((id, index) =>
|
|
402
|
+
this.prisma.course_lesson.update({
|
|
403
|
+
where: { id },
|
|
404
|
+
data: { order: index + 1 },
|
|
405
|
+
}),
|
|
406
|
+
),
|
|
407
|
+
);
|
|
408
|
+
} else {
|
|
409
|
+
// Move to a different session
|
|
410
|
+
const siblings = await this.prisma.course_lesson.findMany({
|
|
411
|
+
where: { course_module_id: dto.toSessionId },
|
|
412
|
+
orderBy: { order: 'asc' },
|
|
413
|
+
select: { id: true },
|
|
414
|
+
});
|
|
415
|
+
const insertAt = Math.min(dto.toIndex ?? siblings.length, siblings.length);
|
|
416
|
+
const orderedIds = [
|
|
417
|
+
...siblings.slice(0, insertAt).map((l) => l.id),
|
|
418
|
+
lessonId,
|
|
419
|
+
...siblings.slice(insertAt).map((l) => l.id),
|
|
420
|
+
];
|
|
421
|
+
|
|
422
|
+
await this.prisma.$transaction([
|
|
423
|
+
this.prisma.course_lesson.update({
|
|
424
|
+
where: { id: lessonId },
|
|
425
|
+
data: { course_module_id: dto.toSessionId },
|
|
426
|
+
}),
|
|
427
|
+
...orderedIds.map((id, index) =>
|
|
428
|
+
this.prisma.course_lesson.update({
|
|
429
|
+
where: { id },
|
|
430
|
+
data: { order: index + 1 },
|
|
431
|
+
}),
|
|
432
|
+
),
|
|
433
|
+
]);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return { success: true };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async duplicateSession(courseId: number, sessionId: number) {
|
|
440
|
+
const sourceModule = await this.prisma.course_module.findFirst({
|
|
441
|
+
where: { id: sessionId, course_id: courseId },
|
|
442
|
+
include: {
|
|
443
|
+
course_lesson: {
|
|
444
|
+
orderBy: { order: 'asc' },
|
|
445
|
+
include: {
|
|
446
|
+
course_lesson_file: true,
|
|
447
|
+
course_lesson_question: true,
|
|
448
|
+
course_lesson_instructor: true,
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
if (!sourceModule) {
|
|
455
|
+
throw new NotFoundException('Session not found for this course');
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const nextOrder = await this.nextModuleOrder(courseId);
|
|
459
|
+
|
|
460
|
+
const newModule = await this.prisma.course_module.create({
|
|
461
|
+
data: {
|
|
462
|
+
course_id: courseId,
|
|
463
|
+
title: `Cópia de ${sourceModule.title}`,
|
|
464
|
+
description: sourceModule.description,
|
|
465
|
+
duration_minutes: sourceModule.duration_minutes,
|
|
466
|
+
order: nextOrder,
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const newLessons = [];
|
|
471
|
+
for (const lesson of sourceModule.course_lesson) {
|
|
472
|
+
const lessonOrder = await this.nextLessonOrder(newModule.id);
|
|
473
|
+
const newLesson = await this.prisma.course_lesson.create({
|
|
474
|
+
data: {
|
|
475
|
+
course_module_id: newModule.id,
|
|
476
|
+
title: lesson.title,
|
|
477
|
+
type: lesson.type,
|
|
478
|
+
description: lesson.description,
|
|
479
|
+
duration_seconds: lesson.duration_seconds,
|
|
480
|
+
content: lesson.content,
|
|
481
|
+
order: lessonOrder,
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
if (lesson.course_lesson_file.length > 0) {
|
|
486
|
+
await this.prisma.course_lesson_file.createMany({
|
|
487
|
+
data: lesson.course_lesson_file.map((f) => ({
|
|
488
|
+
course_lesson_id: newLesson.id,
|
|
489
|
+
title: f.title,
|
|
490
|
+
file_id: f.file_id,
|
|
491
|
+
})),
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (lesson.course_lesson_instructor.length > 0) {
|
|
496
|
+
await this.prisma.course_lesson_instructor.createMany({
|
|
497
|
+
data: lesson.course_lesson_instructor.map((i) => ({
|
|
498
|
+
course_lesson_id: newLesson.id,
|
|
499
|
+
instructor_id: i.instructor_id,
|
|
500
|
+
role: i.role,
|
|
501
|
+
})),
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (lesson.course_lesson_question.length > 0) {
|
|
506
|
+
await this.prisma.course_lesson_question.createMany({
|
|
507
|
+
data: lesson.course_lesson_question.map((q) => ({
|
|
508
|
+
course_lesson_id: newLesson.id,
|
|
509
|
+
question_id: q.question_id,
|
|
510
|
+
order: q.order,
|
|
511
|
+
})),
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
newLessons.push(await this.getLessonById(newLesson.id));
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return {
|
|
519
|
+
session: {
|
|
520
|
+
id: String(newModule.id),
|
|
521
|
+
codigo: this.formatCode('S', newModule.order ?? nextOrder),
|
|
522
|
+
titulo: newModule.title,
|
|
523
|
+
duracao: newModule.duration_minutes ?? 0,
|
|
524
|
+
collapsed: false,
|
|
525
|
+
},
|
|
526
|
+
lessons: newLessons,
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async duplicateLesson(courseId: number, sessionId: number, lessonId: number) {
|
|
531
|
+
const sourceLesson = await this.prisma.course_lesson.findFirst({
|
|
532
|
+
where: {
|
|
533
|
+
id: lessonId,
|
|
534
|
+
course_module_id: sessionId,
|
|
535
|
+
course_module: { course_id: courseId },
|
|
536
|
+
},
|
|
537
|
+
include: {
|
|
538
|
+
course_lesson_file: true,
|
|
539
|
+
course_lesson_question: true,
|
|
540
|
+
course_lesson_instructor: true,
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
if (!sourceLesson) {
|
|
545
|
+
throw new NotFoundException('Lesson not found for this session');
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const nextOrder = await this.nextLessonOrder(sessionId);
|
|
549
|
+
|
|
550
|
+
const newLesson = await this.prisma.course_lesson.create({
|
|
551
|
+
data: {
|
|
552
|
+
course_module_id: sessionId,
|
|
553
|
+
title: `Cópia de ${sourceLesson.title}`,
|
|
554
|
+
type: sourceLesson.type,
|
|
555
|
+
description: sourceLesson.description,
|
|
556
|
+
duration_seconds: sourceLesson.duration_seconds,
|
|
557
|
+
content: sourceLesson.content,
|
|
558
|
+
order: nextOrder,
|
|
559
|
+
},
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
if (sourceLesson.course_lesson_file.length > 0) {
|
|
563
|
+
await this.prisma.course_lesson_file.createMany({
|
|
564
|
+
data: sourceLesson.course_lesson_file.map((f) => ({
|
|
565
|
+
course_lesson_id: newLesson.id,
|
|
566
|
+
title: f.title,
|
|
567
|
+
file_id: f.file_id,
|
|
568
|
+
})),
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (sourceLesson.course_lesson_instructor.length > 0) {
|
|
573
|
+
await this.prisma.course_lesson_instructor.createMany({
|
|
574
|
+
data: sourceLesson.course_lesson_instructor.map((i) => ({
|
|
575
|
+
course_lesson_id: newLesson.id,
|
|
576
|
+
instructor_id: i.instructor_id,
|
|
577
|
+
role: i.role,
|
|
578
|
+
})),
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (sourceLesson.course_lesson_question.length > 0) {
|
|
583
|
+
await this.prisma.course_lesson_question.createMany({
|
|
584
|
+
data: sourceLesson.course_lesson_question.map((q) => ({
|
|
585
|
+
course_lesson_id: newLesson.id,
|
|
586
|
+
question_id: q.question_id,
|
|
587
|
+
order: q.order,
|
|
588
|
+
})),
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return this.getLessonById(newLesson.id);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
async pasteLessons(courseId: number, targetSessionId: number, dto: PasteLessonsDto) {
|
|
596
|
+
const targetModule = await this.prisma.course_module.findFirst({
|
|
597
|
+
where: { id: targetSessionId, course_id: courseId },
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
if (!targetModule) {
|
|
601
|
+
throw new NotFoundException('Target session not found for this course');
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const sourceLessons = await this.prisma.course_lesson.findMany({
|
|
605
|
+
where: {
|
|
606
|
+
id: { in: dto.lessonIds },
|
|
607
|
+
course_module: { course_id: courseId },
|
|
608
|
+
},
|
|
609
|
+
orderBy: { order: 'asc' },
|
|
610
|
+
include: {
|
|
611
|
+
course_lesson_file: true,
|
|
612
|
+
course_lesson_question: true,
|
|
613
|
+
course_lesson_instructor: true,
|
|
614
|
+
},
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
if (sourceLessons.length !== dto.lessonIds.length) {
|
|
618
|
+
throw new BadRequestException('Some lessons do not belong to this course');
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const result = [];
|
|
622
|
+
for (const lesson of sourceLessons) {
|
|
623
|
+
const lessonOrder = await this.nextLessonOrder(targetSessionId);
|
|
624
|
+
const newLesson = await this.prisma.course_lesson.create({
|
|
625
|
+
data: {
|
|
626
|
+
course_module_id: targetSessionId,
|
|
627
|
+
title: `Cópia de ${lesson.title}`,
|
|
628
|
+
type: lesson.type,
|
|
629
|
+
description: lesson.description,
|
|
630
|
+
duration_seconds: lesson.duration_seconds,
|
|
631
|
+
content: lesson.content,
|
|
632
|
+
order: lessonOrder,
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
if (lesson.course_lesson_file.length > 0) {
|
|
637
|
+
await this.prisma.course_lesson_file.createMany({
|
|
638
|
+
data: lesson.course_lesson_file.map((f) => ({
|
|
639
|
+
course_lesson_id: newLesson.id,
|
|
640
|
+
title: f.title,
|
|
641
|
+
file_id: f.file_id,
|
|
642
|
+
})),
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (lesson.course_lesson_instructor.length > 0) {
|
|
647
|
+
await this.prisma.course_lesson_instructor.createMany({
|
|
648
|
+
data: lesson.course_lesson_instructor.map((i) => ({
|
|
649
|
+
course_lesson_id: newLesson.id,
|
|
650
|
+
instructor_id: i.instructor_id,
|
|
651
|
+
role: i.role,
|
|
652
|
+
})),
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (lesson.course_lesson_question.length > 0) {
|
|
657
|
+
await this.prisma.course_lesson_question.createMany({
|
|
658
|
+
data: lesson.course_lesson_question.map((q) => ({
|
|
659
|
+
course_lesson_id: newLesson.id,
|
|
660
|
+
question_id: q.question_id,
|
|
661
|
+
order: q.order,
|
|
662
|
+
})),
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
result.push(await this.getLessonById(newLesson.id));
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return { lessons: result };
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
private async syncLessonRelations( lessonId: number,
|
|
286
673
|
dto: Partial<
|
|
287
674
|
Pick<
|
|
288
675
|
CreateCourseStructureLessonDto,
|