@hed-hog/lms 0.0.305 → 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.
Files changed (130) hide show
  1. package/dist/course/course-structure.controller.d.ts +60 -0
  2. package/dist/course/course-structure.controller.d.ts.map +1 -1
  3. package/dist/course/course-structure.controller.js +79 -0
  4. package/dist/course/course-structure.controller.js.map +1 -1
  5. package/dist/course/course-structure.service.d.ts +61 -1
  6. package/dist/course/course-structure.service.d.ts.map +1 -1
  7. package/dist/course/course-structure.service.js +326 -1
  8. package/dist/course/course-structure.service.js.map +1 -1
  9. package/dist/course/course.controller.d.ts +52 -4
  10. package/dist/course/course.controller.d.ts.map +1 -1
  11. package/dist/course/course.service.d.ts +52 -5
  12. package/dist/course/course.service.d.ts.map +1 -1
  13. package/dist/course/course.service.js +81 -60
  14. package/dist/course/course.service.js.map +1 -1
  15. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  16. package/dist/course/dto/create-course-structure-lesson.dto.js +5 -1
  17. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  18. package/dist/course/dto/create-course.dto.d.ts +1 -1
  19. package/dist/course/dto/create-course.dto.d.ts.map +1 -1
  20. package/dist/course/dto/create-course.dto.js +4 -1
  21. package/dist/course/dto/create-course.dto.js.map +1 -1
  22. package/dist/course/dto/move-lesson.dto.d.ts +10 -0
  23. package/dist/course/dto/move-lesson.dto.d.ts.map +1 -0
  24. package/dist/{enterprise/dto/add-enterprise-lead.dto.js → course/dto/move-lesson.dto.js} +12 -6
  25. package/dist/course/dto/move-lesson.dto.js.map +1 -0
  26. package/dist/course/dto/paste-lessons.dto.d.ts +4 -0
  27. package/dist/course/dto/paste-lessons.dto.d.ts.map +1 -0
  28. package/dist/course/dto/paste-lessons.dto.js +24 -0
  29. package/dist/course/dto/paste-lessons.dto.js.map +1 -0
  30. package/dist/course/dto/reorder-lessons.dto.d.ts +5 -0
  31. package/dist/course/dto/reorder-lessons.dto.d.ts.map +1 -0
  32. package/dist/course/dto/reorder-lessons.dto.js +24 -0
  33. package/dist/course/dto/reorder-lessons.dto.js.map +1 -0
  34. package/dist/course/dto/reorder-sessions.dto.d.ts +5 -0
  35. package/dist/course/dto/reorder-sessions.dto.d.ts.map +1 -0
  36. package/dist/course/dto/reorder-sessions.dto.js +24 -0
  37. package/dist/course/dto/reorder-sessions.dto.js.map +1 -0
  38. package/dist/training/training.controller.js +1 -1
  39. package/dist/training/training.controller.js.map +1 -1
  40. package/hedhog/data/dashboard_component.yaml +152 -152
  41. package/hedhog/data/dashboard_item.yaml +166 -166
  42. package/hedhog/data/image_type.yaml +20 -0
  43. package/hedhog/data/menu.yaml +2 -2
  44. package/hedhog/data/route.yaml +60 -6
  45. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +146 -165
  46. package/hedhog/frontend/app/_components/course-avatar.tsx.ejs +70 -0
  47. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +372 -22
  48. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +437 -77
  49. package/hedhog/frontend/app/classes/page.tsx.ejs +311 -289
  50. package/hedhog/frontend/app/courses/[id]/_components/CourseCertificateCard.tsx.ejs +10 -7
  51. package/hedhog/frontend/app/courses/[id]/_components/CourseClassificationCard.tsx.ejs +23 -32
  52. package/hedhog/frontend/app/courses/[id]/_components/CourseContentCard.tsx.ejs +3 -9
  53. package/hedhog/frontend/app/courses/[id]/_components/CourseDangerZoneCard.tsx.ejs +26 -16
  54. package/hedhog/frontend/app/courses/[id]/_components/CourseFlagsCard.tsx.ejs +19 -5
  55. package/hedhog/frontend/app/courses/[id]/_components/CourseMainInfoCard.tsx.ejs +10 -14
  56. package/hedhog/frontend/app/courses/[id]/_components/CourseMediaCard.tsx.ejs +131 -107
  57. package/hedhog/frontend/app/courses/[id]/_components/CourseRelationsCard.tsx.ejs +10 -7
  58. package/hedhog/frontend/app/courses/[id]/_components/CourseSectionCard.tsx.ejs +38 -19
  59. package/hedhog/frontend/app/courses/[id]/_components/CourseSummaryCard.tsx.ejs +1 -1
  60. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
  61. package/hedhog/frontend/app/courses/[id]/page.tsx.ejs +336 -1057
  62. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +45 -0
  63. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-dnd.tsx.ejs +362 -0
  64. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-panel.tsx.ejs +111 -0
  65. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree-skeleton.tsx.ejs +64 -0
  66. package/hedhog/frontend/app/courses/[id]/structure/_components/course-tree.tsx.ejs +134 -0
  67. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-course.tsx.ejs +113 -0
  68. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-lesson.tsx.ejs +314 -0
  69. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-panel.tsx.ejs +62 -0
  70. package/hedhog/frontend/app/courses/[id]/structure/_components/detail-session.tsx.ejs +174 -0
  71. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-handle.tsx.ejs +58 -0
  72. package/hedhog/frontend/app/courses/[id]/structure/_components/drag-overlay.tsx.ejs +52 -0
  73. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-bulk.tsx.ejs +276 -0
  74. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +1216 -0
  75. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +1827 -0
  76. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +443 -0
  77. package/hedhog/frontend/app/courses/[id]/structure/_components/highlighted-text.tsx.ejs +41 -0
  78. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +184 -0
  79. package/hedhog/frontend/app/courses/[id]/structure/_components/multi-select-bar.tsx.ejs +264 -0
  80. package/hedhog/frontend/app/courses/[id]/structure/_components/search-filter.tsx.ejs +96 -0
  81. package/hedhog/frontend/app/courses/[id]/structure/_components/session-picker-dialog.tsx.ejs +74 -0
  82. package/hedhog/frontend/app/courses/[id]/structure/_components/shortcuts-help.tsx.ejs +136 -0
  83. package/hedhog/frontend/app/courses/[id]/structure/_components/sortable-tree-row.tsx.ejs +80 -0
  84. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +948 -0
  85. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +525 -0
  86. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-display-settings-popover.tsx.ejs +150 -0
  87. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-helpers.ts.ejs +182 -0
  88. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-course.tsx.ejs +52 -0
  89. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +271 -0
  90. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +167 -0
  91. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row.tsx.ejs +108 -0
  92. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +122 -0
  93. package/hedhog/frontend/app/courses/[id]/structure/_components/use-course-structure-shortcuts.ts.ejs +318 -0
  94. package/hedhog/frontend/app/courses/[id]/structure/_components/use-tree-display-settings.ts.ejs +97 -0
  95. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +347 -0
  96. package/hedhog/frontend/app/courses/[id]/structure/_data/course-structure-contract.ts.ejs +195 -0
  97. package/hedhog/frontend/app/courses/[id]/structure/_data/services/course-structure.service.ts.ejs +420 -0
  98. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +254 -0
  99. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +987 -0
  100. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-query.ts.ejs +86 -0
  101. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure.ts.ejs +160 -0
  102. package/hedhog/frontend/app/courses/[id]/structure/page.tsx.ejs +10 -3212
  103. package/hedhog/frontend/app/courses/page.tsx.ejs +45 -26
  104. package/hedhog/frontend/app/enterprise/[id]/page.tsx.ejs +317 -317
  105. package/hedhog/frontend/app/enterprise/_components/enterprise-class-create-sheet.tsx.ejs +1 -0
  106. package/hedhog/frontend/app/enterprise/_components/enterprise-mocks.ts.ejs +12 -1
  107. package/hedhog/frontend/app/enterprise/_components/enterprise-related-tab.tsx.ejs +44 -7
  108. package/hedhog/frontend/app/enterprise/_components/enterprise-types.ts.ejs +96 -96
  109. package/hedhog/frontend/app/page.tsx.ejs +5 -5
  110. package/hedhog/frontend/app/{training → paths}/page.tsx.ejs +29 -7
  111. package/hedhog/frontend/messages/en.json +88 -10
  112. package/hedhog/frontend/messages/pt.json +88 -10
  113. package/hedhog/table/course.yaml +16 -16
  114. package/hedhog/table/image_type.yaml +14 -0
  115. package/package.json +6 -6
  116. package/src/class-group/class-group.service.ts +413 -413
  117. package/src/class-group/dto/create-class-group.dto.ts +77 -77
  118. package/src/course/course-structure.controller.ts +63 -0
  119. package/src/course/course-structure.service.ts +390 -3
  120. package/src/course/course.service.ts +214 -182
  121. package/src/course/dto/create-course-structure-lesson.dto.ts +3 -2
  122. package/src/course/dto/create-course.dto.ts +19 -16
  123. package/src/course/dto/move-lesson.dto.ts +17 -0
  124. package/src/course/dto/paste-lessons.dto.ts +9 -0
  125. package/src/course/dto/reorder-lessons.dto.ts +10 -0
  126. package/src/course/dto/reorder-sessions.dto.ts +10 -0
  127. package/src/training/training.controller.ts +1 -1
  128. package/dist/enterprise/dto/add-enterprise-lead.dto.d.ts +0 -4
  129. package/dist/enterprise/dto/add-enterprise-lead.dto.d.ts.map +0 -1
  130. package/dist/enterprise/dto/add-enterprise-lead.dto.js.map +0 -1
@@ -5,21 +5,21 @@ import { UpdateCourseDto } from './dto/update-course.dto';
5
5
 
6
6
  type CourseImageTypeSlug = 'course-logo' | 'course-banner';
7
7
 
8
- type CourseExtraFields = {
9
- code?: string | null;
10
- is_featured?: boolean | null;
11
- has_certificate?: boolean | null;
12
- is_listed?: boolean | null;
13
- offering_type?: 'scheduled' | 'on_demand' | 'blended' | null;
14
- };
15
-
16
- type PersistCourseExtrasInput = {
17
- code?: string;
18
- isFeatured?: boolean;
19
- hasCertificate?: boolean;
20
- isListed?: boolean;
21
- offeringType?: 'scheduled' | 'on_demand' | 'blended';
22
- };
8
+ type CourseExtraFields = {
9
+ name?: string | null;
10
+ is_featured?: boolean | null;
11
+ has_certificate?: boolean | null;
12
+ is_listed?: boolean | null;
13
+ offering_type?: 'scheduled' | 'on_demand' | 'blended' | null;
14
+ };
15
+
16
+ type PersistCourseExtrasInput = {
17
+ name?: string;
18
+ isFeatured?: boolean;
19
+ hasCertificate?: boolean;
20
+ isListed?: boolean;
21
+ offeringType?: 'scheduled' | 'on_demand' | 'blended';
22
+ };
23
23
 
24
24
  @Injectable()
25
25
  export class CourseService {
@@ -40,9 +40,9 @@ export class CourseService {
40
40
  return undefined;
41
41
  }
42
42
 
43
- private normalizeStatus(value?: string | null) {
44
- if (!value) return undefined;
45
- const normalized = String(value).trim().toLowerCase();
43
+ private normalizeStatus(value?: string | null) {
44
+ if (!value) return undefined;
45
+ const normalized = String(value).trim().toLowerCase();
46
46
  if (
47
47
  normalized === 'published' ||
48
48
  normalized === 'active' ||
@@ -55,39 +55,39 @@ export class CourseService {
55
55
  }
56
56
  if (normalized === 'archived' || normalized === 'arquivado') {
57
57
  return 'archived';
58
- }
59
- return undefined;
60
- }
61
-
62
- private normalizeOfferingType(value?: string | null) {
63
- if (!value) return undefined;
64
-
65
- const normalized = String(value).trim().toLowerCase();
66
- if (normalized === 'scheduled') {
67
- return 'scheduled';
68
- }
69
- if (
70
- normalized === 'on_demand' ||
71
- normalized === 'ondemand' ||
72
- normalized === 'on-demand'
73
- ) {
74
- return 'on_demand';
75
- }
76
- if (normalized === 'blended') {
77
- return 'blended';
78
- }
79
-
80
- return undefined;
81
- }
82
-
83
- private normalizeOptionalText(value?: string | null) {
84
- const normalized = value?.trim();
85
- return normalized ? normalized : undefined;
86
- }
87
-
88
- private resolveCourseTitle(title: string | undefined, slug: string) {
89
- return this.normalizeOptionalText(title) ?? slug;
90
- }
58
+ }
59
+ return undefined;
60
+ }
61
+
62
+ private normalizeOfferingType(value?: string | null) {
63
+ if (!value) return undefined;
64
+
65
+ const normalized = String(value).trim().toLowerCase();
66
+ if (normalized === 'scheduled') {
67
+ return 'scheduled';
68
+ }
69
+ if (
70
+ normalized === 'on_demand' ||
71
+ normalized === 'ondemand' ||
72
+ normalized === 'on-demand'
73
+ ) {
74
+ return 'on_demand';
75
+ }
76
+ if (normalized === 'blended') {
77
+ return 'blended';
78
+ }
79
+
80
+ return undefined;
81
+ }
82
+
83
+ private normalizeOptionalText(value?: string | null) {
84
+ const normalized = value?.trim();
85
+ return normalized ? normalized : undefined;
86
+ }
87
+
88
+ private resolveCourseTitle(title: string | undefined, slug: string) {
89
+ return this.normalizeOptionalText(title) ?? slug;
90
+ }
91
91
 
92
92
  async list(params: {
93
93
  page?: number;
@@ -154,11 +154,11 @@ export class CourseService {
154
154
  },
155
155
  orderBy: [{ is_primary: 'desc' }, { order: 'asc' }],
156
156
  },
157
- _count: {
158
- select: { course_enrollment: true, course_class_group: true },
159
- },
160
- },
161
- }),
157
+ _count: {
158
+ select: { course_enrollment: true, course_class_group: true },
159
+ },
160
+ },
161
+ }),
162
162
  this.prisma.course.count({ where }),
163
163
  ]);
164
164
 
@@ -209,7 +209,13 @@ export class CourseService {
209
209
  },
210
210
  include: {
211
211
  image_type: {
212
- select: { slug: true },
212
+ select: {
213
+ slug: true,
214
+ suggested_width: true,
215
+ suggested_height: true,
216
+ allowed_extensions: true,
217
+ aspect_ratio: true,
218
+ },
213
219
  },
214
220
  file: {
215
221
  select: { id: true, filename: true },
@@ -306,14 +312,13 @@ export class CourseService {
306
312
  }, extrasById.get(id));
307
313
  }
308
314
 
309
- async create(dto: CreateCourseDto) {
310
- const { categorySlugs, logoFileId, bannerFileId, instructorIds, ...data } = dto;
311
- const normalizedSlug = data.slug.trim();
312
- const resolvedCode = this.normalizeCourseCode(data.code ?? normalizedSlug);
313
- const resolvedTitle = this.resolveCourseTitle(data.title, normalizedSlug);
314
-
315
- const categories = categorySlugs?.length
316
- ? await this.prisma.category.findMany({
315
+ async create(dto: CreateCourseDto) {
316
+ const { categorySlugs, logoFileId, bannerFileId, instructorIds, ...data } = dto;
317
+ const normalizedSlug = data.slug.trim();
318
+ const resolvedTitle = this.resolveCourseTitle(data.title, normalizedSlug);
319
+
320
+ const categories = categorySlugs?.length
321
+ ? await this.prisma.category.findMany({
317
322
  where: { slug: { in: categorySlugs } },
318
323
  select: { id: true },
319
324
  })
@@ -330,15 +335,16 @@ export class CourseService {
330
335
  validInstructorIds = instructorIds.filter((id) => validIds.has(id));
331
336
  }
332
337
 
333
- const c = await this.prisma.course.create({
334
- data: {
335
- slug: normalizedSlug,
336
- title: resolvedTitle,
337
- ...(data.description !== undefined && {
338
- description: data.description,
339
- }),
340
- level: this.normalizeLevel(data.level) ?? 'beginner',
341
- status: this.normalizeStatus(data.status) ?? 'draft',
338
+ const c = await this.prisma.course.create({
339
+ data: {
340
+ name: data.name,
341
+ slug: normalizedSlug,
342
+ title: resolvedTitle,
343
+ ...(data.description !== undefined && {
344
+ description: data.description,
345
+ }),
346
+ level: this.normalizeLevel(data.level) ?? 'beginner',
347
+ status: this.normalizeStatus(data.status) ?? 'draft',
342
348
  ...(data.requirements !== undefined && {
343
349
  requirements: data.requirements,
344
350
  }),
@@ -425,42 +431,41 @@ export class CourseService {
425
431
  bannerFileId,
426
432
  });
427
433
 
428
- await this.persistCourseExtras(c.id, {
429
- ...data,
430
- ...(resolvedCode && { code: resolvedCode }),
431
- offeringType: this.normalizeOfferingType(data.offeringType) ?? 'on_demand',
432
- });
434
+ await this.persistCourseExtras(c.id, {
435
+ ...data,
436
+ offeringType: this.normalizeOfferingType(data.offeringType) ?? 'on_demand',
437
+ });
433
438
 
434
439
  const extrasById = await this.getCourseExtras([c.id]);
435
440
 
436
441
  return this.mapCourse(c, undefined, extrasById.get(c.id));
437
442
  }
438
443
 
439
- async update(id: number, dto: UpdateCourseDto) {
440
- const { categorySlugs, logoFileId, bannerFileId, instructorIds, ...data } = dto;
441
- let existingSlug: string | undefined;
442
-
443
- if (
444
- data.title !== undefined &&
445
- this.normalizeOptionalText(data.title) === undefined &&
446
- data.slug === undefined
447
- ) {
448
- const currentCourse = await this.prisma.course.findUnique({
449
- where: { id },
450
- select: { slug: true },
451
- });
452
-
453
- existingSlug = currentCourse?.slug;
454
- }
455
-
456
- const resolvedSlug = data.slug !== undefined ? data.slug.trim() : undefined;
457
- const resolvedTitle =
458
- data.title !== undefined
459
- ? this.resolveCourseTitle(data.title, resolvedSlug ?? existingSlug ?? '')
460
- : undefined;
461
-
462
- const categories = categorySlugs?.length
463
- ? await this.prisma.category.findMany({
444
+ async update(id: number, dto: UpdateCourseDto) {
445
+ const { categorySlugs, logoFileId, bannerFileId, instructorIds, ...data } = dto;
446
+ let existingSlug: string | undefined;
447
+
448
+ if (
449
+ data.title !== undefined &&
450
+ this.normalizeOptionalText(data.title) === undefined &&
451
+ data.slug === undefined
452
+ ) {
453
+ const currentCourse = await this.prisma.course.findUnique({
454
+ where: { id },
455
+ select: { slug: true },
456
+ });
457
+
458
+ existingSlug = currentCourse?.slug;
459
+ }
460
+
461
+ const resolvedSlug = data.slug !== undefined ? data.slug.trim() : undefined;
462
+ const resolvedTitle =
463
+ data.title !== undefined
464
+ ? this.resolveCourseTitle(data.title, resolvedSlug ?? existingSlug ?? '')
465
+ : undefined;
466
+
467
+ const categories = categorySlugs?.length
468
+ ? await this.prisma.category.findMany({
464
469
  where: { slug: { in: categorySlugs } },
465
470
  select: { id: true },
466
471
  })
@@ -487,14 +492,15 @@ export class CourseService {
487
492
  });
488
493
  }
489
494
 
490
- const c = await this.prisma.course.update({
491
- where: { id },
492
- data: {
493
- ...(resolvedSlug !== undefined && { slug: resolvedSlug }),
494
- ...(resolvedTitle !== undefined && { title: resolvedTitle }),
495
- ...(data.description !== undefined && { description: data.description }),
496
- ...(data.level !== undefined && {
497
- level: this.normalizeLevel(data.level),
495
+ const c = await this.prisma.course.update({
496
+ where: { id },
497
+ data: {
498
+ ...(data.name !== undefined && { name: data.name }),
499
+ ...(resolvedSlug !== undefined && { slug: resolvedSlug }),
500
+ ...(resolvedTitle !== undefined && { title: resolvedTitle }),
501
+ ...(data.description !== undefined && { description: data.description }),
502
+ ...(data.level !== undefined && {
503
+ level: this.normalizeLevel(data.level),
498
504
  }),
499
505
  ...(data.status !== undefined && {
500
506
  status: this.normalizeStatus(data.status),
@@ -594,7 +600,7 @@ export class CourseService {
594
600
  bannerFileId,
595
601
  });
596
602
 
597
- await this.persistCourseExtras(id, data);
603
+ await this.persistCourseExtras(id, data);
598
604
 
599
605
  const extrasById = await this.getCourseExtras([id]);
600
606
 
@@ -606,7 +612,7 @@ export class CourseService {
606
612
  return { success: true };
607
613
  }
608
614
 
609
- private mapCourse(
615
+ private mapCourse(
610
616
  c: any,
611
617
  metrics?: {
612
618
  lessonCount?: number;
@@ -620,16 +626,16 @@ export class CourseService {
620
626
  }[];
621
627
  },
622
628
  extras?: CourseExtraFields,
623
- ) {
624
- const rawCode = extras?.code ?? c.code ?? c.slug;
625
- const resolvedTitle = this.normalizeOptionalText(c.title) ?? c.slug;
626
- const courseImages = Array.isArray(c.course_image) ? c.course_image : [];
627
- const classCount = c._count?.course_class_group ?? 0;
628
- const offeringType =
629
- this.normalizeOfferingType(extras?.offering_type) ??
630
- (classCount > 0 ? 'scheduled' : 'on_demand');
631
-
632
- const logoImage = courseImages.find(
629
+ ) {
630
+ const resolvedName = extras?.name ?? c.name ?? null;
631
+ const resolvedTitle = this.normalizeOptionalText(c.title) ?? c.slug;
632
+ const courseImages = Array.isArray(c.course_image) ? c.course_image : [];
633
+ const classCount = c._count?.course_class_group ?? 0;
634
+ const offeringType =
635
+ this.normalizeOfferingType(extras?.offering_type) ??
636
+ (classCount > 0 ? 'scheduled' : 'on_demand');
637
+
638
+ const logoImage = courseImages.find(
633
639
  (courseImage: any) => courseImage.image_type?.slug === 'course-logo',
634
640
  );
635
641
  const bannerImage = courseImages.find(
@@ -638,12 +644,10 @@ export class CourseService {
638
644
 
639
645
  return {
640
646
  id: c.id,
641
- code:
642
- rawCode?.toUpperCase().replace(/[^A-Z0-9-]+/g, '-').slice(0, 32) ??
643
- '',
644
- slug: c.slug,
645
- title: resolvedTitle,
646
- description: c.description ?? '',
647
+ name: resolvedName ?? '',
648
+ slug: c.slug,
649
+ title: resolvedTitle,
650
+ description: c.description ?? '',
647
651
  level: c.level,
648
652
  status: c.status,
649
653
  durationHours: c.duration_hours ?? 0,
@@ -657,8 +661,24 @@ export class CourseService {
657
661
  secondaryContrastColor: c.secondary_contrast_color ?? null,
658
662
  logoFileId: logoImage?.file?.id ?? logoImage?.file_id ?? null,
659
663
  logoFilename: logoImage?.file?.filename ?? null,
664
+ logoImageType: logoImage?.image_type
665
+ ? {
666
+ suggestedWidth: logoImage.image_type.suggested_width ?? null,
667
+ suggestedHeight: logoImage.image_type.suggested_height ?? null,
668
+ allowedExtensions: logoImage.image_type.allowed_extensions ?? null,
669
+ aspectRatio: logoImage.image_type.aspect_ratio ?? null,
670
+ }
671
+ : null,
660
672
  bannerFileId: bannerImage?.file?.id ?? bannerImage?.file_id ?? null,
661
673
  bannerFilename: bannerImage?.file?.filename ?? null,
674
+ bannerImageType: bannerImage?.image_type
675
+ ? {
676
+ suggestedWidth: bannerImage.image_type.suggested_width ?? null,
677
+ suggestedHeight: bannerImage.image_type.suggested_height ?? null,
678
+ allowedExtensions: bannerImage.image_type.allowed_extensions ?? null,
679
+ aspectRatio: bannerImage.image_type.aspect_ratio ?? null,
680
+ }
681
+ : null,
662
682
  categories: (c.course_category ?? []).map(
663
683
  (cc: any) => cc.category?.slug ?? '',
664
684
  ),
@@ -666,14 +686,14 @@ export class CourseService {
666
686
  (cc: any) => cc.category?.id ?? 0,
667
687
  ),
668
688
  isFeatured: extras?.is_featured ?? c.is_featured ?? false,
669
- hasCertificate: extras?.has_certificate ?? c.has_certificate ?? false,
670
- isListed: extras?.is_listed ?? c.is_listed ?? false,
671
- offeringType,
672
- enrollmentCount: c._count?.course_enrollment ?? 0,
673
- moduleCount: c._count?.course_module ?? 0,
674
- classCount,
675
- lessonCount: metrics?.lessonCount ?? 0,
676
- sessionCount: metrics?.sessionCount ?? 0,
689
+ hasCertificate: extras?.has_certificate ?? c.has_certificate ?? false,
690
+ isListed: extras?.is_listed ?? c.is_listed ?? false,
691
+ offeringType,
692
+ enrollmentCount: c._count?.course_enrollment ?? 0,
693
+ moduleCount: c._count?.course_module ?? 0,
694
+ classCount,
695
+ lessonCount: metrics?.lessonCount ?? 0,
696
+ sessionCount: metrics?.sessionCount ?? 0,
677
697
  averageCompletion: Math.round(metrics?.averageCompletion ?? 0),
678
698
  certificatesIssued:
679
699
  metrics?.certificatesIssued ?? c._count?.certificate ?? 0,
@@ -720,29 +740,29 @@ export class CourseService {
720
740
  try {
721
741
  const idsCsv = normalizedIds.join(',');
722
742
  const rows = (await this.prisma.$queryRawUnsafe(
723
- `
724
- SELECT id, code, is_featured, has_certificate, is_listed, offering_type
725
- FROM course
726
- WHERE id IN (${idsCsv})
727
- `,
743
+ `
744
+ SELECT id, name, is_featured, has_certificate, is_listed, offering_type
745
+ FROM course
746
+ WHERE id IN (${idsCsv})
747
+ `,
728
748
  )) as Array<{
729
749
  id: number;
730
- code: string | null;
750
+ name: string | null;
731
751
  is_featured: boolean | null;
732
- has_certificate: boolean | null;
733
- is_listed: boolean | null;
734
- offering_type: 'scheduled' | 'on_demand' | 'blended' | null;
735
- }>;
736
-
737
- for (const row of rows) {
738
- extrasById.set(row.id, {
739
- code: row.code,
740
- is_featured: row.is_featured,
741
- has_certificate: row.has_certificate,
742
- is_listed: row.is_listed,
743
- offering_type: row.offering_type,
744
- });
745
- }
752
+ has_certificate: boolean | null;
753
+ is_listed: boolean | null;
754
+ offering_type: 'scheduled' | 'on_demand' | 'blended' | null;
755
+ }>;
756
+
757
+ for (const row of rows) {
758
+ extrasById.set(row.id, {
759
+ name: row.name,
760
+ is_featured: row.is_featured,
761
+ has_certificate: row.has_certificate,
762
+ is_listed: row.is_listed,
763
+ offering_type: row.offering_type,
764
+ });
765
+ }
746
766
  } catch {
747
767
  return extrasById;
748
768
  }
@@ -757,9 +777,9 @@ export class CourseService {
757
777
  const sets: string[] = [];
758
778
  const params: Array<string | boolean | number> = [];
759
779
 
760
- if (data.code !== undefined) {
761
- params.push(data.code);
762
- sets.push(`code = $${params.length}`);
780
+ if (data.name !== undefined) {
781
+ params.push(data.name);
782
+ sets.push(`name = $${params.length}`);
763
783
  }
764
784
 
765
785
  if (data.isFeatured !== undefined) {
@@ -772,15 +792,15 @@ export class CourseService {
772
792
  sets.push(`has_certificate = $${params.length}`);
773
793
  }
774
794
 
775
- if (data.isListed !== undefined) {
776
- params.push(data.isListed);
777
- sets.push(`is_listed = $${params.length}`);
778
- }
779
-
780
- if (data.offeringType !== undefined) {
781
- params.push(data.offeringType);
782
- sets.push(`offering_type = $${params.length}`);
783
- }
795
+ if (data.isListed !== undefined) {
796
+ params.push(data.isListed);
797
+ sets.push(`is_listed = $${params.length}`);
798
+ }
799
+
800
+ if (data.offeringType !== undefined) {
801
+ params.push(data.offeringType);
802
+ sets.push(`offering_type = $${params.length}`);
803
+ }
784
804
 
785
805
  if (sets.length === 0) {
786
806
  return;
@@ -802,18 +822,6 @@ export class CourseService {
802
822
  }
803
823
  }
804
824
 
805
- private normalizeCourseCode(value?: string | null) {
806
- const normalized = String(value ?? '')
807
- .trim()
808
- .toUpperCase()
809
- .replace(/[^A-Z0-9-]+/g, '-')
810
- .replace(/-+/g, '-')
811
- .replace(/^-+|-+$/g, '')
812
- .slice(0, 32);
813
-
814
- return normalized || undefined;
815
- }
816
-
817
825
  private async syncCourseImages(
818
826
  courseId: number,
819
827
  data: {
@@ -908,11 +916,35 @@ export class CourseService {
908
916
  return existing;
909
917
  }
910
918
 
919
+ const specsBySlug: Record<
920
+ CourseImageTypeSlug,
921
+ {
922
+ suggested_width: number;
923
+ suggested_height: number;
924
+ allowed_extensions: string;
925
+ aspect_ratio: string;
926
+ }
927
+ > = {
928
+ 'course-logo': {
929
+ suggested_width: 500,
930
+ suggested_height: 500,
931
+ allowed_extensions: 'png,webp',
932
+ aspect_ratio: '1:1',
933
+ },
934
+ 'course-banner': {
935
+ suggested_width: 1920,
936
+ suggested_height: 540,
937
+ allowed_extensions: 'jpg,png,webp',
938
+ aspect_ratio: '16:9',
939
+ },
940
+ };
941
+
911
942
  return this.prisma.image_type.create({
912
943
  data: {
913
944
  name: imageTypeSlug === 'course-logo' ? 'Course Logo' : 'Course Banner',
914
945
  slug: imageTypeSlug,
915
946
  applies_to_course: true,
947
+ ...specsBySlug[imageTypeSlug],
916
948
  },
917
949
  select: { id: true },
918
950
  });
@@ -56,8 +56,9 @@ export class CreateCourseStructureLessonDto {
56
56
  tipo: 'video' | 'questao' | 'post' | 'exercicio';
57
57
 
58
58
  @IsInt()
59
- @Min(1)
60
- duracao: number;
59
+ @Min(0)
60
+ @IsOptional()
61
+ duracao: number = 0;
61
62
 
62
63
  @IsEnum(['youtube', 'vimeo', 'bunny', 'custom'])
63
64
  @IsOptional()
@@ -14,17 +14,20 @@ export class CreateCourseDto {
14
14
  @IsString()
15
15
  @MaxLength(32)
16
16
  @IsOptional()
17
- code?: string;
17
+ name?: string;
18
18
 
19
19
  @IsString()
20
20
  @IsNotEmpty()
21
21
  @MaxLength(255)
22
+ @Matches(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, {
23
+ message: 'slug deve conter apenas letras minúsculas, números e hífens, sem espaços',
24
+ })
22
25
  slug: string;
23
26
 
24
- @IsString()
25
- @MaxLength(255)
26
- @IsOptional()
27
- title?: string;
27
+ @IsString()
28
+ @MaxLength(255)
29
+ @IsOptional()
30
+ title?: string;
28
31
 
29
32
  @IsString()
30
33
  @IsOptional()
@@ -52,17 +55,17 @@ export class CreateCourseDto {
52
55
  @IsOptional()
53
56
  level?: 'beginner' | 'intermediate' | 'advanced';
54
57
 
55
- @IsEnum(['draft', 'published', 'archived'])
56
- @IsOptional()
57
- status?: 'draft' | 'published' | 'archived';
58
-
59
- @IsEnum(['scheduled', 'on_demand', 'blended'])
60
- @IsOptional()
61
- offeringType?: 'scheduled' | 'on_demand' | 'blended';
62
-
63
- @IsBoolean()
64
- @IsOptional()
65
- isFeatured?: boolean;
58
+ @IsEnum(['draft', 'published', 'archived'])
59
+ @IsOptional()
60
+ status?: 'draft' | 'published' | 'archived';
61
+
62
+ @IsEnum(['scheduled', 'on_demand', 'blended'])
63
+ @IsOptional()
64
+ offeringType?: 'scheduled' | 'on_demand' | 'blended';
65
+
66
+ @IsBoolean()
67
+ @IsOptional()
68
+ isFeatured?: boolean;
66
69
 
67
70
  @IsBoolean()
68
71
  @IsOptional()
@@ -0,0 +1,17 @@
1
+ import { IsInt, IsOptional, IsPositive, Min } from 'class-validator';
2
+
3
+ export class MoveLessonDto {
4
+ /** ID of the destination session (course_module). */
5
+ @IsInt()
6
+ @IsPositive()
7
+ toSessionId: number;
8
+
9
+ /**
10
+ * Zero-based index within the destination session.
11
+ * If omitted, the lesson is appended at the end.
12
+ */
13
+ @IsOptional()
14
+ @IsInt()
15
+ @Min(0)
16
+ toIndex?: number;
17
+ }
@@ -0,0 +1,9 @@
1
+ import { ArrayMinSize, IsArray, IsInt, IsPositive } from 'class-validator';
2
+
3
+ export class PasteLessonsDto {
4
+ @IsArray()
5
+ @ArrayMinSize(1)
6
+ @IsInt({ each: true })
7
+ @IsPositive({ each: true })
8
+ lessonIds: number[];
9
+ }
@@ -0,0 +1,10 @@
1
+ import { ArrayMinSize, IsArray, IsInt, IsPositive } from 'class-validator';
2
+
3
+ export class ReorderLessonsDto {
4
+ /** Ordered array of lesson IDs in their new positions within the session. */
5
+ @IsArray()
6
+ @ArrayMinSize(1)
7
+ @IsInt({ each: true })
8
+ @IsPositive({ each: true })
9
+ lessonIds: number[];
10
+ }
@@ -0,0 +1,10 @@
1
+ import { ArrayMinSize, IsArray, IsInt, IsPositive } from 'class-validator';
2
+
3
+ export class ReorderSessionsDto {
4
+ /** Ordered array of session IDs reflecting the new visual order. */
5
+ @IsArray()
6
+ @ArrayMinSize(1)
7
+ @IsInt({ each: true })
8
+ @IsPositive({ each: true })
9
+ sessionIds: number[];
10
+ }
@@ -15,7 +15,7 @@ import { UpdateTrainingDto } from './dto/update-training.dto';
15
15
  import { TrainingService } from './training.service';
16
16
 
17
17
  @Role()
18
- @Controller('lms/training')
18
+ @Controller('lms/paths')
19
19
  export class TrainingController {
20
20
  constructor(private readonly trainingService: TrainingService) {}
21
21
 
@@ -1,4 +0,0 @@
1
- export declare class AddEnterpriseLeadDto {
2
- person_id: number;
3
- }
4
- //# sourceMappingURL=add-enterprise-lead.dto.d.ts.map