@hed-hog/lms 0.0.355 → 0.0.357

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/course/course-audio-transcription.service.d.ts.map +1 -1
  2. package/dist/course/course-audio-transcription.service.js +15 -7
  3. package/dist/course/course-audio-transcription.service.js.map +1 -1
  4. package/dist/course/course-structure.controller.d.ts +12 -0
  5. package/dist/course/course-structure.controller.d.ts.map +1 -1
  6. package/dist/course/course-structure.service.d.ts +22 -0
  7. package/dist/course/course-structure.service.d.ts.map +1 -1
  8. package/dist/course/course-structure.service.js +147 -18
  9. package/dist/course/course-structure.service.js.map +1 -1
  10. package/dist/course/course.service.d.ts +4 -2
  11. package/dist/course/course.service.d.ts.map +1 -1
  12. package/dist/course/course.service.js +61 -2
  13. package/dist/course/course.service.js.map +1 -1
  14. package/dist/course/dto/create-course-structure-lesson.dto.d.ts +3 -0
  15. package/dist/course/dto/create-course-structure-lesson.dto.d.ts.map +1 -1
  16. package/dist/course/dto/create-course-structure-lesson.dto.js +15 -0
  17. package/dist/course/dto/create-course-structure-lesson.dto.js.map +1 -1
  18. package/dist/course/dto/create-course-structure-session.dto.d.ts +1 -0
  19. package/dist/course/dto/create-course-structure-session.dto.d.ts.map +1 -1
  20. package/dist/course/dto/create-course-structure-session.dto.js +5 -0
  21. package/dist/course/dto/create-course-structure-session.dto.js.map +1 -1
  22. package/dist/enterprise/training/training-student.controller.d.ts +0 -95
  23. package/dist/enterprise/training/training-student.controller.d.ts.map +1 -1
  24. package/dist/enterprise/training/training-student.controller.js +1 -34
  25. package/dist/enterprise/training/training-student.controller.js.map +1 -1
  26. package/dist/enterprise/training/training-student.service.d.ts +63 -0
  27. package/dist/enterprise/training/training-student.service.d.ts.map +1 -1
  28. package/dist/enterprise/training/training-student.service.js +320 -4
  29. package/dist/enterprise/training/training-student.service.js.map +1 -1
  30. package/dist/lms.module.d.ts.map +1 -1
  31. package/dist/lms.module.js +2 -0
  32. package/dist/lms.module.js.map +1 -1
  33. package/dist/platforma/platforma.controller.d.ts +287 -0
  34. package/dist/platforma/platforma.controller.d.ts.map +1 -0
  35. package/dist/platforma/platforma.controller.js +147 -0
  36. package/dist/platforma/platforma.controller.js.map +1 -0
  37. package/hedhog/data/route.yaml +75 -9
  38. package/hedhog/frontend/app/_components/class-form-sheet.tsx.ejs +2 -2
  39. package/hedhog/frontend/app/_components/course-form-sheet.tsx.ejs +10 -13
  40. package/hedhog/frontend/app/classes/[id]/page.tsx.ejs +2 -2
  41. package/hedhog/frontend/app/courses/[id]/_components/course-edit-types.ts.ejs +1 -1
  42. package/hedhog/frontend/app/courses/[id]/structure/_components/confirm-dialog.tsx.ejs +17 -6
  43. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-course.tsx.ejs +14 -7
  44. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-lesson.tsx.ejs +92 -53
  45. package/hedhog/frontend/app/courses/[id]/structure/_components/editor-session.tsx.ejs +25 -60
  46. package/hedhog/frontend/app/courses/[id]/structure/_components/mock-data.ts.ejs +1 -0
  47. package/hedhog/frontend/app/courses/[id]/structure/_components/store.ts.ejs +11 -0
  48. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-context-menu.tsx.ejs +22 -0
  49. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-lesson.tsx.ejs +79 -64
  50. package/hedhog/frontend/app/courses/[id]/structure/_components/tree-row-session.tsx.ejs +31 -14
  51. package/hedhog/frontend/app/courses/[id]/structure/_components/types.ts.ejs +3 -4
  52. package/hedhog/frontend/app/courses/[id]/structure/_data/adapters/course-structure.adapter.ts.ejs +28 -24
  53. package/hedhog/frontend/app/courses/[id]/structure/_data/types/api-course.types.ts.ejs +20 -0
  54. package/hedhog/frontend/app/courses/[id]/structure/_data/use-course-structure-mutations.ts.ejs +41 -6
  55. package/hedhog/frontend/app/courses/page.tsx.ejs +59 -15
  56. package/hedhog/frontend/app/enterprise/_components/enterprise-course-create-sheet.tsx.ejs +1 -1
  57. package/hedhog/frontend/app/enterprise/_components/enterprise-course-edit-sheet.tsx.ejs +2 -2
  58. package/hedhog/frontend/app/paths/page.tsx.ejs +1 -1
  59. package/hedhog/frontend/app/training/page.tsx.ejs +1 -1
  60. package/hedhog/frontend/messages/en.json +7 -2
  61. package/hedhog/frontend/messages/pt.json +7 -2
  62. package/hedhog/table/course_lesson.yaml +2 -2
  63. package/hedhog/table/course_module.yaml +3 -0
  64. package/package.json +8 -8
  65. package/src/course/course-audio-transcription.service.ts +21 -8
  66. package/src/course/course-structure.service.ts +204 -3
  67. package/src/course/course.service.ts +67 -1
  68. package/src/course/dto/create-course-structure-lesson.dto.ts +17 -0
  69. package/src/course/dto/create-course-structure-session.dto.ts +13 -1
  70. package/src/enterprise/training/training-student.controller.ts +3 -27
  71. package/src/enterprise/training/training-student.service.ts +350 -2
  72. package/src/lms.module.ts +2 -0
  73. package/src/platforma/platforma.controller.ts +92 -0
@@ -5,6 +5,8 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm
5
5
  export class TrainingStudentService {
6
6
  constructor(private readonly prisma: PrismaService) {}
7
7
 
8
+ private readonly courseCardsLimit = 4;
9
+
8
10
  async registerStreakActivity(
9
11
  userId: number,
10
12
  payload?: { activityType?: string; source?: string; activityDateKey?: string },
@@ -68,6 +70,150 @@ export class TrainingStudentService {
68
70
  };
69
71
  }
70
72
 
73
+ async getPublishedCourses(params: {
74
+ page?: number;
75
+ pageSize?: number;
76
+ search?: string;
77
+ category?: string;
78
+ sort?: string;
79
+ }) {
80
+ const page = Math.max(Number(params.page) || 1, 1);
81
+ const pageSize = Math.max(Number(params.pageSize) || 12, 1);
82
+ const skip = (page - 1) * pageSize;
83
+
84
+ const where: any = {
85
+ status: 'published',
86
+ is_listed: true,
87
+ };
88
+
89
+ const search = params.search?.trim();
90
+ const category = params.category?.trim();
91
+ const sort = params.sort?.trim();
92
+
93
+ if (search) {
94
+ where.OR = [
95
+ { title: { contains: search, mode: 'insensitive' } },
96
+ { description: { contains: search, mode: 'insensitive' } },
97
+ { slug: { contains: search, mode: 'insensitive' } },
98
+ ];
99
+ }
100
+
101
+ if (category && category !== 'all') {
102
+ where.course_category = {
103
+ some: {
104
+ category: {
105
+ slug: category,
106
+ },
107
+ },
108
+ };
109
+ }
110
+
111
+ const orderBy = this.resolvePublishedCourseOrder(sort);
112
+
113
+ const [rows, total, categories] = await Promise.all([
114
+ this.prisma.course.findMany({
115
+ where,
116
+ skip,
117
+ take: pageSize,
118
+ orderBy,
119
+ select: {
120
+ id: true,
121
+ slug: true,
122
+ title: true,
123
+ description: true,
124
+ duration_hours: true,
125
+ level: true,
126
+ updated_at: true,
127
+ course_category: {
128
+ select: {
129
+ category: {
130
+ select: {
131
+ slug: true,
132
+ },
133
+ },
134
+ },
135
+ },
136
+ course_image: {
137
+ where: { image_type: { slug: { in: ['course-banner', 'course-logo'] } } },
138
+ orderBy: [{ is_primary: 'desc' }, { order: 'asc' }],
139
+ select: {
140
+ image_type: { select: { slug: true } },
141
+ file: { select: { location: true } },
142
+ },
143
+ },
144
+ course_module: {
145
+ select: {
146
+ _count: {
147
+ select: {
148
+ course_lesson: true,
149
+ },
150
+ },
151
+ },
152
+ },
153
+ _count: {
154
+ select: {
155
+ course_enrollment: true,
156
+ },
157
+ },
158
+ },
159
+ }),
160
+ this.prisma.course.count({ where }),
161
+ this.prisma.category.findMany({
162
+ where: {
163
+ course_category: {
164
+ some: {
165
+ course: {
166
+ status: 'published',
167
+ is_listed: true,
168
+ },
169
+ },
170
+ },
171
+ },
172
+ select: {
173
+ slug: true,
174
+ },
175
+ orderBy: {
176
+ slug: 'asc',
177
+ },
178
+ }),
179
+ ]);
180
+
181
+ return {
182
+ data: rows.map((row) => {
183
+ const banner = row.course_image.find((image) => image.image_type?.slug === 'course-banner');
184
+ const logo = row.course_image.find((image) => image.image_type?.slug === 'course-logo');
185
+ const lessons = (row.course_module ?? []).reduce(
186
+ (sum, module) => sum + (module._count?.course_lesson ?? 0),
187
+ 0,
188
+ );
189
+ const categorySlug = row.course_category[0]?.category?.slug ?? null;
190
+
191
+ return {
192
+ id: row.id,
193
+ slug: row.slug,
194
+ title: row.title,
195
+ description: row.description ?? '',
196
+ durationHours: row.duration_hours ?? 0,
197
+ level: row.level,
198
+ lessons,
199
+ image: banner?.file?.location ?? logo?.file?.location ?? null,
200
+ categoryKey: categorySlug,
201
+ categoryLabel: this.formatCategoryLabel(categorySlug),
202
+ enrollmentCount: row._count?.course_enrollment ?? 0,
203
+ updatedAt: row.updated_at,
204
+ };
205
+ }),
206
+ categories: categories.map((item) => ({
207
+ value: item.slug,
208
+ label: this.formatCategoryLabel(item.slug),
209
+ })),
210
+ total,
211
+ page,
212
+ pageSize,
213
+ lastPage: Math.max(1, Math.ceil(total / pageSize)),
214
+ };
215
+ }
216
+
71
217
  async getDashboard(userId: number, enterpriseId?: number) {
72
218
  const personUser = await this.prisma.person_user.findFirst({
73
219
  where: { user_id: userId },
@@ -83,6 +229,37 @@ export class TrainingStudentService {
83
229
  const weekStart = this.startOfWeek(now);
84
230
  const weekEnd = this.endOfDay(this.addDays(weekStart, 6));
85
231
 
232
+ const profile = await this.prisma.person.findUnique({
233
+ where: { id: personId },
234
+ select: {
235
+ id: true,
236
+ name: true,
237
+ avatar_id: true,
238
+ person_user: {
239
+ where: { user_id: userId },
240
+ take: 1,
241
+ select: {
242
+ user: {
243
+ select: {
244
+ id: true,
245
+ name: true,
246
+ photo_id: true,
247
+ user_identifier: {
248
+ where: { type: 'email' },
249
+ orderBy: [{ verified_at: 'desc' }, { created_at: 'asc' }],
250
+ take: 1,
251
+ select: { value: true },
252
+ },
253
+ },
254
+ },
255
+ },
256
+ },
257
+ },
258
+ });
259
+
260
+ const profileUser = profile?.person_user?.[0]?.user ?? null;
261
+ const profileEmail = profileUser?.user_identifier?.[0]?.value ?? null;
262
+
86
263
  // Fetch all active enrollments for this student
87
264
  const enrollments = await this.prisma.course_enrollment.findMany({
88
265
  where: { person_id: personId, status: { not: 'cancelled' } },
@@ -116,21 +293,29 @@ export class TrainingStudentService {
116
293
  .filter((id): id is number => id != null);
117
294
  const enrollmentIds = enrollments.map((e) => e.id);
118
295
 
119
- const [kpis, myClasses, onDemandCourses, weeklySchedule, ranking, gamification] = await Promise.all([
296
+ const [kpis, myClasses, onDemandCourses, weeklySchedule, ranking, gamification, courseCards] = await Promise.all([
120
297
  this.getKpis(personId, enrollments, enrollmentIds),
121
298
  this.getMyClasses(classEnrollments, now),
122
299
  this.getOnDemandCourses(onDemandEnrollments),
123
300
  this.getWeeklySchedule(cgIds, weekStart, weekEnd),
124
301
  this.getRanking(personId, cgIds),
125
302
  this.getGamification(personId, enrollmentIds, weekStart, weekEnd),
303
+ this.getDashboardCourseCards(personId),
126
304
  ]);
127
305
 
128
306
  const myRanking = ranking.find((r) => r.isMe);
129
307
 
130
308
  return {
309
+ user: {
310
+ id: profileUser?.id ?? userId,
311
+ name: profile?.name ?? profileUser?.name ?? null,
312
+ email: profileEmail,
313
+ avatarId: profile?.avatar_id ?? profileUser?.photo_id ?? null,
314
+ },
131
315
  kpis,
132
316
  myClasses,
133
317
  onDemandCourses,
318
+ courseCards,
134
319
  weeklySchedule,
135
320
  ranking,
136
321
  gamification: {
@@ -447,7 +632,11 @@ export class TrainingStudentService {
447
632
 
448
633
  const courseIds = onDemandEnrollments.map((e) => e.course_id);
449
634
  const courses = await this.prisma.course.findMany({
450
- where: { id: { in: courseIds } },
635
+ where: {
636
+ id: { in: courseIds },
637
+ status: 'published',
638
+ is_listed: true,
639
+ },
451
640
  select: {
452
641
  id: true,
453
642
  title: true,
@@ -465,6 +654,10 @@ export class TrainingStudentService {
465
654
 
466
655
  return onDemandEnrollments.map((enrollment) => {
467
656
  const course = courseById.get(enrollment.course_id);
657
+ if (!course) {
658
+ return null;
659
+ }
660
+
468
661
  return {
469
662
  enrollmentId: enrollment.id,
470
663
  courseId: enrollment.course_id,
@@ -473,7 +666,162 @@ export class TrainingStudentService {
473
666
  durationHours: (course as any)?.duration_hours ?? null,
474
667
  progress: enrollment.progress_percent ?? 0,
475
668
  };
669
+ }).filter((course): course is NonNullable<typeof course> => !!course);
670
+ }
671
+
672
+ private async getDashboardCourseCards(personId: number) {
673
+ const startedEnrollments = await this.prisma.course_enrollment.findMany({
674
+ where: {
675
+ person_id: personId,
676
+ status: { not: 'cancelled' },
677
+ progress_percent: { gt: 0 },
678
+ course: {
679
+ status: 'published',
680
+ is_listed: true,
681
+ },
682
+ },
683
+ orderBy: [{ updated_at: 'desc' }, { enrolled_at: 'desc' }, { id: 'desc' }],
684
+ take: this.courseCardsLimit,
685
+ select: {
686
+ id: true,
687
+ progress_percent: true,
688
+ course: {
689
+ select: {
690
+ id: true,
691
+ slug: true,
692
+ title: true,
693
+ description: true,
694
+ duration_hours: true,
695
+ course_module: {
696
+ select: {
697
+ _count: {
698
+ select: {
699
+ course_lesson: true,
700
+ },
701
+ },
702
+ },
703
+ },
704
+ course_image: {
705
+ where: { image_type: { slug: { in: ['course-banner', 'course-logo'] } } },
706
+ orderBy: [{ is_primary: 'desc' }, { order: 'asc' }],
707
+ select: {
708
+ image_type: { select: { slug: true } },
709
+ file: { select: { location: true } },
710
+ },
711
+ },
712
+ },
713
+ },
714
+ },
476
715
  });
716
+
717
+ if (startedEnrollments.length > 0) {
718
+ return startedEnrollments.map((enrollment) => {
719
+ const course = enrollment.course;
720
+ const lessons = (course.course_module ?? []).reduce(
721
+ (sum, module) => sum + (module._count?.course_lesson ?? 0),
722
+ 0,
723
+ );
724
+ const banner = course.course_image.find((image) => image.image_type?.slug === 'course-banner');
725
+ const logo = course.course_image.find((image) => image.image_type?.slug === 'course-logo');
726
+
727
+ return {
728
+ id: course.id,
729
+ slug: course.slug,
730
+ title: course.title,
731
+ description: course.description ?? 'Continue de onde parou.',
732
+ image: banner?.file?.location ?? logo?.file?.location ?? null,
733
+ progress: enrollment.progress_percent ?? 0,
734
+ lessons,
735
+ durationHours: course.duration_hours ?? 0,
736
+ inProgress: true,
737
+ source: 'started' as const,
738
+ };
739
+ });
740
+ }
741
+
742
+ const fallbackCourses = await this.prisma.course.findMany({
743
+ where: {
744
+ status: 'published',
745
+ is_listed: true,
746
+ offering_type: 'on_demand',
747
+ },
748
+ orderBy: [{ updated_at: 'desc' }, { created_at: 'desc' }, { id: 'desc' }],
749
+ take: this.courseCardsLimit,
750
+ select: {
751
+ id: true,
752
+ slug: true,
753
+ title: true,
754
+ description: true,
755
+ duration_hours: true,
756
+ course_module: {
757
+ select: {
758
+ _count: {
759
+ select: {
760
+ course_lesson: true,
761
+ },
762
+ },
763
+ },
764
+ },
765
+ course_image: {
766
+ where: { image_type: { slug: { in: ['course-banner', 'course-logo'] } } },
767
+ orderBy: [{ is_primary: 'desc' }, { order: 'asc' }],
768
+ select: {
769
+ image_type: { select: { slug: true } },
770
+ file: { select: { location: true } },
771
+ },
772
+ },
773
+ },
774
+ });
775
+
776
+ return fallbackCourses.map((course) => {
777
+ const lessons = (course.course_module ?? []).reduce(
778
+ (sum, module) => sum + (module._count?.course_lesson ?? 0),
779
+ 0,
780
+ );
781
+ const banner = course.course_image.find((image) => image.image_type?.slug === 'course-banner');
782
+ const logo = course.course_image.find((image) => image.image_type?.slug === 'course-logo');
783
+
784
+ return {
785
+ id: course.id,
786
+ slug: course.slug,
787
+ title: course.title,
788
+ description: course.description ?? 'Curso disponível para começar agora.',
789
+ image: banner?.file?.location ?? logo?.file?.location ?? null,
790
+ progress: 0,
791
+ lessons,
792
+ durationHours: course.duration_hours ?? 0,
793
+ inProgress: false,
794
+ source: 'on_demand' as const,
795
+ };
796
+ });
797
+ }
798
+
799
+ private resolvePublishedCourseOrder(sort?: string) {
800
+ switch (sort) {
801
+ case 'duracao':
802
+ return [{ duration_hours: 'asc' as const }, { updated_at: 'desc' as const }];
803
+ case 'populares':
804
+ return [{ course_enrollment: { _count: 'desc' as const } }, { updated_at: 'desc' as const }];
805
+ case 'recentes':
806
+ return [{ updated_at: 'desc' as const }, { created_at: 'desc' as const }];
807
+ case 'relevantes':
808
+ default:
809
+ return [
810
+ { is_featured: 'desc' as const },
811
+ { course_enrollment: { _count: 'desc' as const } },
812
+ { updated_at: 'desc' as const },
813
+ ];
814
+ }
815
+ }
816
+
817
+ private formatCategoryLabel(slug?: string | null) {
818
+ if (!slug) return 'Sem categoria';
819
+
820
+ return slug
821
+ .split(/[-_\s]+/)
822
+ .filter(Boolean)
823
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
824
+ .join(' ');
477
825
  }
478
826
 
479
827
  private async getWeeklySchedule(cgIds: number[], weekStart: Date, weekEnd: Date) {
package/src/lms.module.ts CHANGED
@@ -21,6 +21,7 @@ import { ExamModule } from './exam/exam.module';
21
21
  import { InstructorModule } from './instructor/instructor.module';
22
22
  import { LmsRealtimeModule } from './realtime/lms-realtime.module';
23
23
  import { LmsReportsModule } from './reports/reports.module';
24
+ import { PlataformaController } from './platforma/platforma.controller';
24
25
  import { TrainingModule } from './training/training.module';
25
26
  import { VideoResolutionProfileModule } from './video-resolution-profile/video-resolution-profile.module';
26
27
 
@@ -50,6 +51,7 @@ import { VideoResolutionProfileModule } from './video-resolution-profile/video-r
50
51
  forwardRef(() => TrainingModule),
51
52
  forwardRef(() => VideoResolutionProfileModule),
52
53
  ],
54
+ controllers: [PlataformaController],
53
55
  providers: [CourseAudioTranscriptionService],
54
56
  exports: [
55
57
  forwardRef(() => AchievementModule),
@@ -0,0 +1,92 @@
1
+ import { NoRole, User } from '@hed-hog/api';
2
+ import { NotificationService } from '@hed-hog/core';
3
+ import { Body, Controller, Get, Param, ParseIntPipe, Patch, Post, Query } from '@nestjs/common';
4
+ import { TrainingStudentService } from '../enterprise/training/training-student.service';
5
+
6
+ @NoRole()
7
+ @Controller('lms/platforma')
8
+ export class PlataformaController {
9
+ constructor(
10
+ private readonly trainingStudentService: TrainingStudentService,
11
+ private readonly notificationService: NotificationService,
12
+ ) {}
13
+
14
+ @Get()
15
+ getHome(@User('id') userId: number) {
16
+ return this.trainingStudentService.getDashboard(userId);
17
+ }
18
+
19
+ @Get('dashboard')
20
+ getDashboard(@User('id') userId: number) {
21
+ return this.trainingStudentService.getDashboard(userId);
22
+ }
23
+
24
+ @Get('courses')
25
+ getCourses(
26
+ @Query('page') page?: string,
27
+ @Query('pageSize') pageSize?: string,
28
+ @Query('search') search?: string,
29
+ @Query('category') category?: string,
30
+ @Query('sort') sort?: string,
31
+ ) {
32
+ return this.trainingStudentService.getPublishedCourses({
33
+ page: page ? Number(page) : 1,
34
+ pageSize: pageSize ? Number(pageSize) : 12,
35
+ search,
36
+ category,
37
+ sort,
38
+ });
39
+ }
40
+
41
+ @Get('notifications')
42
+ listNotifications(
43
+ @User('id') userId: number,
44
+ @Query('page') page?: string,
45
+ @Query('pageSize') pageSize?: string,
46
+ @Query('search') search?: string,
47
+ @Query('sortField') sortField?: string,
48
+ @Query('sortOrder') sortOrder?: 'asc' | 'desc',
49
+ ) {
50
+ return this.notificationService.list(userId, {
51
+ page: page ? Number(page) : 1,
52
+ pageSize: pageSize ? Number(pageSize) : 10,
53
+ search,
54
+ sortField,
55
+ sortOrder,
56
+ });
57
+ }
58
+
59
+ @Get('notifications/unread-count')
60
+ unreadNotificationsCount(@User('id') userId: number) {
61
+ return this.notificationService.unreadCount(userId);
62
+ }
63
+
64
+ @Patch('notifications/read-all')
65
+ markAllNotificationsRead(@User('id') userId: number) {
66
+ return this.notificationService.markAllRead(userId);
67
+ }
68
+
69
+ @Patch('notifications/:id/read')
70
+ markNotificationRead(
71
+ @User('id') userId: number,
72
+ @Param('id', ParseIntPipe) id: number,
73
+ ) {
74
+ return this.notificationService.markRead(userId, id);
75
+ }
76
+
77
+ @Get('streak')
78
+ getStreak(
79
+ @User('id') userId: number,
80
+ @Query('days', new ParseIntPipe({ optional: true })) days?: number,
81
+ ) {
82
+ return this.trainingStudentService.getStreak(userId, days);
83
+ }
84
+
85
+ @Post('streak/activities')
86
+ registerStreakActivity(
87
+ @User('id') userId: number,
88
+ @Body() body?: { activityType?: string; source?: string; activityDateKey?: string },
89
+ ) {
90
+ return this.trainingStudentService.registerStreakActivity(userId, body);
91
+ }
92
+ }