musora-content-services 2.71.0 → 2.72.1

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 (70) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/docs/ContentOrganization.html +2 -2
  3. package/docs/Forums.html +2 -2
  4. package/docs/Gamification.html +2 -2
  5. package/docs/TestUser.html +3 -3
  6. package/docs/UserManagementSystem.html +2 -2
  7. package/docs/api_types.js.html +2 -2
  8. package/docs/config.js.html +2 -2
  9. package/docs/content-org_content-org.js.html +2 -2
  10. package/docs/content-org_guided-courses.ts.html +2 -2
  11. package/docs/content-org_learning-paths.ts.html +94 -5
  12. package/docs/content-org_playlists-types.js.html +2 -2
  13. package/docs/content-org_playlists.js.html +2 -2
  14. package/docs/content.js.html +2 -2
  15. package/docs/forums_categories.ts.html +2 -2
  16. package/docs/forums_forums.ts.html +41 -41
  17. package/docs/forums_posts.ts.html +34 -27
  18. package/docs/forums_threads.ts.html +2 -2
  19. package/docs/gamification_awards.ts.html +2 -2
  20. package/docs/gamification_gamification.js.html +2 -2
  21. package/docs/global.html +3 -3
  22. package/docs/index.html +2 -2
  23. package/docs/liveTesting.ts.html +4 -3
  24. package/docs/module-Accounts.html +2 -2
  25. package/docs/module-Awards.html +2 -2
  26. package/docs/module-Config.html +2 -2
  27. package/docs/module-Content-Services-V2.html +2 -2
  28. package/docs/module-Forums.html +26 -26
  29. package/docs/module-GuidedCourses.html +2 -1198
  30. package/docs/module-Interests.html +2 -2
  31. package/docs/module-LearningPaths.html +1077 -0
  32. package/docs/module-Onboarding.html +2 -2
  33. package/docs/module-Payments.html +2 -2
  34. package/docs/module-Permissions.html +2 -2
  35. package/docs/module-Playlists.html +2 -2
  36. package/docs/module-Railcontent-Services.html +2 -2
  37. package/docs/module-Sanity-Services.html +32 -32
  38. package/docs/module-Sessions.html +2 -2
  39. package/docs/module-UserActivity.html +2 -2
  40. package/docs/module-UserChat.html +2 -2
  41. package/docs/module-UserManagement.html +2 -2
  42. package/docs/module-UserMemberships.html +2 -2
  43. package/docs/module-UserNotifications.html +2 -2
  44. package/docs/module-UserProfile.html +2 -2
  45. package/docs/railcontent.js.html +2 -2
  46. package/docs/sanity.js.html +5 -7
  47. package/docs/userActivity.js.html +2 -2
  48. package/docs/user_account.ts.html +2 -2
  49. package/docs/user_chat.js.html +2 -2
  50. package/docs/user_interests.js.html +2 -2
  51. package/docs/user_management.js.html +2 -2
  52. package/docs/user_memberships.ts.html +2 -2
  53. package/docs/user_notifications.js.html +2 -2
  54. package/docs/user_onboarding.ts.html +2 -2
  55. package/docs/user_payments.ts.html +2 -2
  56. package/docs/user_permissions.js.html +2 -2
  57. package/docs/user_profile.js.html +2 -2
  58. package/docs/user_sessions.js.html +2 -2
  59. package/docs/user_types.js.html +2 -2
  60. package/docs/user_user-management-system.js.html +2 -2
  61. package/package.json +1 -1
  62. package/src/contentTypeConfig.js +162 -105
  63. package/src/index.d.ts +2 -0
  64. package/src/index.js +2 -0
  65. package/src/lib/httpHelper.js +5 -0
  66. package/src/services/content-org/learning-paths.ts +92 -3
  67. package/src/services/forums/forums.ts +38 -38
  68. package/src/services/forums/posts.ts +32 -25
  69. package/test/initializeTests.js +21 -36
  70. package/test/learningPaths.test.js +19 -0
@@ -1,6 +1,6 @@
1
1
  //import {AWSUrl, CloudFrontURl} from "./services/config";
2
- import {LengthFilterOptions, Tabs} from "./contentMetaData.js";
3
- import {FilterBuilder} from "./filterBuilder.js";
2
+ import { LengthFilterOptions, Tabs } from './contentMetaData.js'
3
+ import { FilterBuilder } from './filterBuilder.js'
4
4
 
5
5
  export const AWSUrl = 'https://s3.us-east-1.amazonaws.com/musora-web-platform'
6
6
  export const CloudFrontURl = 'https://d3fzm1tzeyr5n3.cloudfront.net'
@@ -9,8 +9,16 @@ export const CloudFrontURl = 'https://d3fzm1tzeyr5n3.cloudfront.net'
9
9
  export const SONG_TYPES = ['song', 'play-along', 'jam-track', 'song-tutorial-children']
10
10
  // Oct 2025: It turns out content-meta categories are not really clear
11
11
  // THis is used for the page_type field as a post processor so we include parents and children
12
- // Duplicated in SanityGateway.php if you update this, update that
13
- export const SONG_TYPES_WITH_CHILDREN = ['song', 'song-part', 'play-along', 'play-along-part', 'jam-track', 'song-tutorial', 'song-tutorial-children']
12
+ // Duplicated in SanityGateway.php if you update this, update that
13
+ export const SONG_TYPES_WITH_CHILDREN = [
14
+ 'song',
15
+ 'song-part',
16
+ 'play-along',
17
+ 'play-along-part',
18
+ 'jam-track',
19
+ 'song-tutorial',
20
+ 'song-tutorial-children',
21
+ ]
14
22
  // Single hierarchy refers to only one element in the hierarchy has video lessons, not that they have a single parent
15
23
  export const SINGLE_PARENT_TYPES = ['course-part', 'pack-bundle-lesson', 'song-tutorial-children']
16
24
 
@@ -172,40 +180,51 @@ export const coachLessonsTypes = [
172
180
  ]
173
181
 
174
182
  export const childContentTypeConfig = {
175
- 'song-tutorial': [
176
- `"genre": genre[]->name`,
177
- `difficulty_string`,
178
- `"type": _type`,
179
- ]
183
+ 'song-tutorial': [`"genre": genre[]->name`, `difficulty_string`, `"type": _type`],
180
184
  }
181
185
 
182
- export const singleLessonTypes = ['quick-tips', 'rudiment'];
183
- export const practiceAlongsLessonTypes = ['workout']; // challenges ->workouts
184
- export const performancesLessonTypes = ['performance'];
185
- export const documentariesLessonTypes = ['tama','sonor','history-of-electronic-drums','paiste-cymbals', 'backstage-secret'];
186
- export const liveArchivesLessonTypes = ['podcast', 'coach-stream', 'question-and-answer', 'live-streams', 'live'];
187
- export const studentArchivesLessonTypes = ['student-review', 'student-focus','student-collaboration'];
188
- export const tutorialsLessonTypes = ['song-tutorial'];
189
- export const transcriptionsLessonTypes = ['song'];
190
- export const playAlongLessonTypes = ['play-along'];
191
- export const jamTrackLessonTypes = ['jam-track'];
186
+ export const singleLessonTypes = ['quick-tips', 'rudiment']
187
+ export const practiceAlongsLessonTypes = ['workout'] // challenges ->workouts
188
+ export const performancesLessonTypes = ['performance']
189
+ export const documentariesLessonTypes = [
190
+ 'tama',
191
+ 'sonor',
192
+ 'history-of-electronic-drums',
193
+ 'paiste-cymbals',
194
+ 'backstage-secret',
195
+ ]
196
+ export const liveArchivesLessonTypes = [
197
+ 'podcast',
198
+ 'coach-stream',
199
+ 'question-and-answer',
200
+ 'live-streams',
201
+ 'live',
202
+ ]
203
+ export const studentArchivesLessonTypes = [
204
+ 'student-review',
205
+ 'student-focus',
206
+ 'student-collaboration',
207
+ ]
208
+ export const tutorialsLessonTypes = ['song-tutorial']
209
+ export const transcriptionsLessonTypes = ['song']
210
+ export const playAlongLessonTypes = ['play-along']
211
+ export const jamTrackLessonTypes = ['jam-track']
192
212
 
193
213
  export const individualLessonsTypes = [
194
214
  ...singleLessonTypes,
195
215
  ...practiceAlongsLessonTypes,
196
216
  ...liveArchivesLessonTypes,
197
- ...studentArchivesLessonTypes
198
- ];
217
+ ...studentArchivesLessonTypes,
218
+ ]
199
219
 
200
220
  export const coursesLessonTypes = [
201
221
  'course',
202
222
  'tiered-course', // TODO: new content type
203
- 'guided-course'];
204
-
205
- export const skillLessonTypes = [
206
- 'skill-pack'
223
+ 'guided-course',
207
224
  ]
208
225
 
226
+ export const skillLessonTypes = ['skill-pack']
227
+
209
228
  export const showsLessonTypes = [
210
229
  'boot-camp',
211
230
  'diy-drum-experiment',
@@ -213,57 +232,75 @@ export const showsLessonTypes = [
213
232
  'in-rhythm',
214
233
  'rhythmic-adventures-of-captain-carson',
215
234
  'rhythms-from-another-planet',
216
- 'study-the-greats'];
235
+ 'study-the-greats',
236
+ ]
217
237
  export const entertainmentLessonTypes = [
218
238
  'specials', // TODO: new type
219
239
  ...documentariesLessonTypes,
220
- ...showsLessonTypes
221
- ];
222
- export const collectionLessonTypes = [
223
- ...coursesLessonTypes,
224
- ...showsLessonTypes
225
- ];
240
+ ...showsLessonTypes,
241
+ ]
242
+ export const collectionLessonTypes = [...coursesLessonTypes, ...showsLessonTypes]
226
243
 
227
244
  export const lessonTypesMapping = {
228
- 'lessons': singleLessonTypes,
245
+ lessons: singleLessonTypes,
229
246
  'practice alongs': practiceAlongsLessonTypes,
230
247
  'live archives': liveArchivesLessonTypes,
231
- 'performances': performancesLessonTypes,
248
+ performances: performancesLessonTypes,
232
249
  'student archives': studentArchivesLessonTypes,
233
- 'documentaries': documentariesLessonTypes,
234
- 'courses': ['course'],
250
+ documentaries: documentariesLessonTypes,
251
+ courses: ['course'],
235
252
  'guided courses': ['guided-course'],
236
- 'tiered courses': ['tiered-course' ],
253
+ 'tiered courses': ['tiered-course'],
237
254
  'skill packs': ['skill-pack'],
238
- 'specials': ['specials'],
239
- 'shows': showsLessonTypes,
240
- 'collections': collectionLessonTypes,
241
- 'individuals': individualLessonsTypes,
242
- 'tutorials': tutorialsLessonTypes,
243
- 'transcriptions': transcriptionsLessonTypes,
244
- 'tabs': transcriptionsLessonTypes,
255
+ specials: ['specials'],
256
+ shows: showsLessonTypes,
257
+ collections: collectionLessonTypes,
258
+ individuals: individualLessonsTypes,
259
+ tutorials: tutorialsLessonTypes,
260
+ transcriptions: transcriptionsLessonTypes,
261
+ tabs: transcriptionsLessonTypes,
245
262
  'sheet music': transcriptionsLessonTypes,
246
263
  'play-alongs': playAlongLessonTypes,
247
264
  'jam tracks': jamTrackLessonTypes,
248
- 'entertainment': entertainmentLessonTypes,
249
- 'single lessons': [...singleLessonTypes, ...liveArchivesLessonTypes, ...studentArchivesLessonTypes, ...practiceAlongsLessonTypes]
250
- };
265
+ entertainment: entertainmentLessonTypes,
266
+ 'single lessons': [
267
+ ...singleLessonTypes,
268
+ ...liveArchivesLessonTypes,
269
+ ...studentArchivesLessonTypes,
270
+ ...practiceAlongsLessonTypes,
271
+ ],
272
+ }
251
273
 
252
- export const getNextLessonLessonParentTypes = ['course', 'guided-course', 'pack', 'pack-bundle', 'song-tutorial'];
274
+ export const getNextLessonLessonParentTypes = [
275
+ 'course',
276
+ 'guided-course',
277
+ 'pack',
278
+ 'pack-bundle',
279
+ 'song-tutorial',
280
+ ]
253
281
 
254
282
  export const progressTypesMapping = {
255
- 'lesson': [...singleLessonTypes,...practiceAlongsLessonTypes, ...liveArchivesLessonTypes, ...performancesLessonTypes, ...studentArchivesLessonTypes, ...documentariesLessonTypes, 'live', 'pack-bundle-lesson'],
256
- 'course': ['course'],
257
- 'show': showsLessonTypes,
283
+ lesson: [
284
+ ...singleLessonTypes,
285
+ ...practiceAlongsLessonTypes,
286
+ ...liveArchivesLessonTypes,
287
+ ...performancesLessonTypes,
288
+ ...studentArchivesLessonTypes,
289
+ ...documentariesLessonTypes,
290
+ 'live',
291
+ 'pack-bundle-lesson',
292
+ ],
293
+ course: ['course'],
294
+ show: showsLessonTypes,
258
295
  'song tutorial': [...tutorialsLessonTypes, 'song-tutorial-children'],
259
- 'songs': transcriptionsLessonTypes,
296
+ songs: transcriptionsLessonTypes,
260
297
  'play along': playAlongLessonTypes,
261
298
  'guided course': ['guided-course'],
262
- 'pack': ['pack', 'semester-pack'],
263
- 'method': ['learning-path'],
299
+ pack: ['pack', 'semester-pack'],
300
+ method: ['learning-path'],
264
301
  'jam track': jamTrackLessonTypes,
265
302
  'course video': ['course-part'],
266
- };
303
+ }
267
304
 
268
305
  export const songs = {
269
306
  drumeo: 'transcription',
@@ -274,23 +311,48 @@ export const songs = {
274
311
  }
275
312
 
276
313
  export const filterTypes = {
277
- lessons: [...singleLessonTypes, ...practiceAlongsLessonTypes , ...liveArchivesLessonTypes, ...studentArchivesLessonTypes, ...coursesLessonTypes, ...skillLessonTypes , ...entertainmentLessonTypes],
278
- songs: [...tutorialsLessonTypes, ...transcriptionsLessonTypes, ...playAlongLessonTypes, ...jamTrackLessonTypes],
314
+ lessons: [
315
+ ...singleLessonTypes,
316
+ ...practiceAlongsLessonTypes,
317
+ ...liveArchivesLessonTypes,
318
+ ...studentArchivesLessonTypes,
319
+ ...coursesLessonTypes,
320
+ ...skillLessonTypes,
321
+ ...entertainmentLessonTypes,
322
+ ],
323
+ songs: [
324
+ ...tutorialsLessonTypes,
325
+ ...transcriptionsLessonTypes,
326
+ ...playAlongLessonTypes,
327
+ ...jamTrackLessonTypes,
328
+ ],
279
329
  }
280
330
 
281
331
  export const recentTypes = {
282
- lessons: [...individualLessonsTypes, 'course-part', 'pack-bundle-lesson', 'guided-course-part', 'quick-tips'],
332
+ lessons: [
333
+ ...individualLessonsTypes,
334
+ 'course-part',
335
+ 'pack-bundle-lesson',
336
+ 'guided-course-part',
337
+ 'quick-tips',
338
+ ],
283
339
  songs: [...SONG_TYPES],
284
- home: [...individualLessonsTypes, ...tutorialsLessonTypes, ...transcriptionsLessonTypes, ...playAlongLessonTypes,
285
- 'guided-course', 'learning-path', 'live', 'course', 'pack']
340
+ home: [
341
+ ...individualLessonsTypes,
342
+ ...tutorialsLessonTypes,
343
+ ...transcriptionsLessonTypes,
344
+ ...playAlongLessonTypes,
345
+ 'guided-course',
346
+ 'learning-path',
347
+ 'live',
348
+ 'course',
349
+ 'pack',
350
+ ],
286
351
  }
287
352
 
288
353
  export let contentTypeConfig = {
289
354
  'tab-data': {
290
- fields: [
291
- 'enrollment_start_time',
292
- 'enrollment_end_time',
293
- ],
355
+ fields: ['enrollment_start_time', 'enrollment_end_time'],
294
356
  includeChildFields: true,
295
357
  },
296
358
  'progress-tracker': {
@@ -353,7 +415,7 @@ export let contentTypeConfig = {
353
415
  ],
354
416
  slug: 'courses',
355
417
  },
356
- 'download': {
418
+ download: {
357
419
  fields: [
358
420
  `"resource": ${resourcesField}`,
359
421
  'soundslice',
@@ -372,8 +434,7 @@ export let contentTypeConfig = {
372
434
  "videoId": coalesce(live_event_stream_id, video.external_id),
373
435
  "live_event_is_global": live_global_event == true
374
436
  }
375
- )`
376
-
437
+ )`,
377
438
  ],
378
439
  childFields: [
379
440
  `"resource": ${resourcesField}`,
@@ -393,9 +454,8 @@ export let contentTypeConfig = {
393
454
  "videoId": coalesce(live_event_stream_id, video.external_id),
394
455
  "live_event_is_global": live_global_event == true
395
456
  }
396
- )`
397
-
398
- ]
457
+ )`,
458
+ ],
399
459
  },
400
460
  method: {
401
461
  fields: [
@@ -519,9 +579,7 @@ export let contentTypeConfig = {
519
579
  `"description": ${descriptionField}`,
520
580
  'total_xp',
521
581
  ],
522
- childFields: [
523
- `"description": ${descriptionField}`,
524
- ]
582
+ childFields: [`"description": ${descriptionField}`],
525
583
  },
526
584
  'pack-bundle-lesson': {
527
585
  fields: [`"resources": ${resourcesField}`],
@@ -609,23 +667,17 @@ export let contentTypeConfig = {
609
667
  'exploring-beats': contentWithSortField,
610
668
  sonor: contentWithSortField,
611
669
  returning: {
612
- fields: [
613
- `quarter_published`,
614
- '"thumbnail": thumbnail.asset->url',
615
- ]
670
+ fields: [`quarter_published`, '"thumbnail": thumbnail.asset->url'],
616
671
  },
617
672
  leaving: {
618
- fields: [
619
- `quarter_removed`,
620
- '"thumbnail": thumbnail.asset->url',
621
- ]
673
+ fields: [`quarter_removed`, '"thumbnail": thumbnail.asset->url'],
622
674
  },
623
- "method-v2": [
675
+ 'method-v2': [
624
676
  `"id":_id`,
625
677
  `"type":_type`,
626
- "title",
627
- "brand",
628
- `"intro_video": intro_video->{ ${getIntroVideoFields().join(", ")} }`,
678
+ 'title',
679
+ 'brand',
680
+ `"intro_video": intro_video->{ ${getIntroVideoFields().join(', ')} }`,
629
681
  `child[]->{
630
682
  "resource": ${resourcesField},
631
683
  total_skills,
@@ -643,22 +695,21 @@ export let contentTypeConfig = {
643
695
  }
644
696
  }`,
645
697
  ],
646
- "method-intro": getIntroVideoFields(),
698
+ 'method-intro': getIntroVideoFields(),
647
699
  }
648
700
 
649
701
  export function getIntroVideoFields() {
650
702
  return [
651
- "title",
652
- "brand",
703
+ 'title',
704
+ 'brand',
653
705
  `"description": ${descriptionField}`,
654
706
  `"thumbnail": thumbnail.asset->url`,
655
- "length_in_seconds",
656
- "video_desktop",
657
- "video_mobile",
658
- ];
707
+ 'length_in_seconds',
708
+ 'video_desktop',
709
+ 'video_mobile',
710
+ ]
659
711
  }
660
712
 
661
-
662
713
  export const plusMembershipPermissions = 92
663
714
 
664
715
  export function getNewReleasesTypes(brand) {
@@ -771,11 +822,14 @@ export function artistOrInstructorNameAsArray(key = 'artists') {
771
822
  return `'${key}': select(artist->name != null => [artist->name], instructor[]->name)`
772
823
  }
773
824
 
774
- export async function getFieldsForContentTypeWithFilteredChildren(contentType, asQueryString = true) {
825
+ export async function getFieldsForContentTypeWithFilteredChildren(
826
+ contentType,
827
+ asQueryString = true
828
+ ) {
775
829
  const childFields = getChildFieldsForContentType(contentType, true)
776
830
  const parentFields = getFieldsForContentType(contentType, false)
777
831
  if (childFields) {
778
- const childFilter = await new FilterBuilder('', {isChildrenFilter: true}).buildFilter()
832
+ const childFilter = await new FilterBuilder('', { isChildrenFilter: true }).buildFilter()
779
833
  parentFields.push(
780
834
  `"children": child[${childFilter}]->{
781
835
  ${childFields}
@@ -788,14 +842,16 @@ export async function getFieldsForContentTypeWithFilteredChildren(contentType, a
788
842
  return asQueryString ? parentFields.toString() + ',' : parentFields
789
843
  }
790
844
 
791
- export function getChildFieldsForContentType(contentType, asQueryString = true)
792
- {
845
+ export function getChildFieldsForContentType(contentType, asQueryString = true) {
793
846
  // When contentType is undefined/null, return DEFAULT_CHILD_FIELDS to support mixed-type queries (e.g., from Algolia)
794
847
  if (!contentType) {
795
848
  return asQueryString ? DEFAULT_CHILD_FIELDS.toString() + ',' : DEFAULT_CHILD_FIELDS
796
849
  }
797
850
 
798
- if (contentTypeConfig[contentType]?.childFields || contentTypeConfig[contentType]?.includeChildFields) {
851
+ if (
852
+ contentTypeConfig[contentType]?.childFields ||
853
+ contentTypeConfig[contentType]?.includeChildFields
854
+ ) {
799
855
  const childFields = contentType
800
856
  ? DEFAULT_CHILD_FIELDS.concat(contentTypeConfig?.[contentType]?.childFields ?? [])
801
857
  : DEFAULT_CHILD_FIELDS
@@ -817,7 +873,7 @@ export function getFieldsForContentType(contentType, asQueryString = true) {
817
873
  */
818
874
  function createTypeConditions(lessonTypes) {
819
875
  if (!lessonTypes || lessonTypes.length === 0) return ''
820
- const conditions = lessonTypes.map(type => `_type == '${type}'`).join(' || ')
876
+ const conditions = lessonTypes.map((type) => `_type == '${type}'`).join(' || ')
821
877
  return conditions ? `(${conditions})` : ''
822
878
  }
823
879
 
@@ -872,8 +928,9 @@ const filterHandlers = {
872
928
 
873
929
  length: (value) => {
874
930
  // Find the matching length option by name
875
- const lengthOption = Object.values(LengthFilterOptions)
876
- .find(opt => typeof opt === 'object' && opt.name === value)
931
+ const lengthOption = Object.values(LengthFilterOptions).find(
932
+ (opt) => typeof opt === 'object' && opt.name === value
933
+ )
877
934
 
878
935
  if (!lengthOption) return ''
879
936
 
@@ -912,12 +969,12 @@ const filterHandlers = {
912
969
  export function filtersToGroq(filters = [], selectedFilters = [], pageName = '') {
913
970
  // Handle railcontent_id filters separately (they use different syntax)
914
971
  const railcontentIdFilters = filters
915
- .filter(item => item.includes('railcontent_id in'))
916
- .map(item => ` && ${item}`)
972
+ .filter((item) => item.includes('railcontent_id in'))
973
+ .map((item) => ` && ${item}`)
917
974
  .join('')
918
975
 
919
976
  // Remove railcontent_id filters from main processing
920
- const regularFilters = filters.filter(item => !item.includes('railcontent_id in'))
977
+ const regularFilters = filters.filter((item) => !item.includes('railcontent_id in'))
921
978
 
922
979
  // Group filters by key
923
980
  const groupedFilters = groupFilters(regularFilters)
@@ -940,7 +997,7 @@ export function filtersToGroq(filters = [], selectedFilters = [], pageName = '')
940
997
 
941
998
  // Process each value with the appropriate handler
942
999
  const joinedValues = values
943
- .map(value => {
1000
+ .map((value) => {
944
1001
  const handler = filterHandlers[key]
945
1002
 
946
1003
  if (handler) {
package/src/index.d.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  } from './services/content-org/guided-courses.ts';
14
14
 
15
15
  import {
16
+ fetchLearningPathLessons,
16
17
  getActivePath,
17
18
  getDailySession,
18
19
  updateActivePath,
@@ -459,6 +460,7 @@ declare module 'musora-content-services' {
459
460
  fetchInterests,
460
461
  fetchLastInteractedChild,
461
462
  fetchLatestThreads,
463
+ fetchLearningPathLessons,
462
464
  fetchLeaving,
463
465
  fetchLessonContent,
464
466
  fetchLessonsFeaturingThisContent,
package/src/index.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  } from './services/content-org/guided-courses.ts';
14
14
 
15
15
  import {
16
+ fetchLearningPathLessons,
16
17
  getActivePath,
17
18
  getDailySession,
18
19
  updateActivePath,
@@ -458,6 +459,7 @@ export {
458
459
  fetchInterests,
459
460
  fetchLastInteractedChild,
460
461
  fetchLatestThreads,
462
+ fetchLearningPathLessons,
461
463
  fetchLeaving,
462
464
  fetchLessonContent,
463
465
  fetchLessonsFeaturingThisContent,
@@ -36,6 +36,11 @@ export async function fetchJSONHandler(
36
36
  } else {
37
37
  console.error(`Fetch error: ${method} ${url} ${response.status} ${response.statusText}`)
38
38
  console.log(response)
39
+ const contentType = response.headers.get('content-type')
40
+ if (contentType && contentType.indexOf('json') !== -1) {
41
+ const data = await response.json()
42
+ console.log(data)
43
+ }
39
44
  }
40
45
  } catch (error) {
41
46
  console.error('Fetch error:', error)
@@ -1,7 +1,9 @@
1
1
  import { fetchHandler } from '../railcontent.js'
2
-
2
+ import { fetchByRailContentId, fetchByRailContentIds, fetchMethodV2Structure } from '../sanity.js'
3
+ import { addContextToContent } from '../contentAggregator.js'
4
+ import { getProgressStateByIds } from '../contentProgress.js'
3
5
  /**
4
- * @module GuidedCourses
6
+ * @module LearningPaths
5
7
  */
6
8
  const BASE_PATH: string = `/api/content-org`
7
9
 
@@ -23,7 +25,11 @@ export async function getDailySession(brand: string, userDate: Date) {
23
25
  * @param userDate - format 2025-10-31
24
26
  * @param keepFirstLearningPath
25
27
  */
26
- export async function updateDailySession(brand: string, userDate: Date, keepFirstLearningPath: boolean) {
28
+ export async function updateDailySession(
29
+ brand: string,
30
+ userDate: Date,
31
+ keepFirstLearningPath: boolean
32
+ ) {
27
33
  const stringDate = userDate.toISOString().split('T')[0]
28
34
  const url: string = `${BASE_PATH}/v1/user/learning-paths/daily-session/update`
29
35
  const body = { brand: brand, userDate: stringDate, keepFirstLearningPath: keepFirstLearningPath }
@@ -49,3 +55,86 @@ export async function updateActivePath(brand: string) {
49
55
  const body = { brand: brand }
50
56
  return await fetchHandler(url, 'POST', null, body)
51
57
  }
58
+
59
+ /** Fetches and organizes learning path lessons.
60
+ *
61
+ * @param {number} learningPathId - The learning path ID.
62
+ * @param {string} brand
63
+ * @param {Date} userDate - Users local date - format 2025-10-31
64
+ * @returns {Promise<Object>} result - The result object.
65
+ * @returns {number} result.id - The learning path ID.
66
+ * @returns {string} result.thumbnail - Optional thumbnail URL for the learning path.
67
+ * @returns {string} result.title - The title of the learning path.
68
+ * @returns {boolean} result.is_active_learning_path - Whether the learning path is currently active.
69
+ * @returns {Array} result.all_lessons - Array of all lessons.
70
+ * @returns {Array} result.upcoming_lessons - Array of upcoming/additional lessons.
71
+ * @returns {Array} result.todays_lessons - Array of today's lessons (max 3).
72
+ * @returns {Array} result.next_learning_path_lessons - Array of next lessons to be taken.
73
+ * @returns {Array} result.completed_lessons - Array of completed lessons.
74
+ */
75
+ export async function fetchLearningPathLessons(
76
+ learningPathId: number,
77
+ brand: string,
78
+ userDate: Date
79
+ ) {
80
+ const [learningPath, dailySession] = await Promise.all([
81
+ fetchByRailContentId(learningPathId, 'learning-path-v2'),
82
+ getDailySession(brand, userDate),
83
+ ])
84
+
85
+ const addContextParameters = {
86
+ addProgressStatus: true,
87
+ addProgressPercentage: true,
88
+ }
89
+ const lessons = await addContextToContent(() => learningPath.children, addContextParameters)
90
+ const isActiveLearningPath = (dailySession?.active_learning_path_id || 0) == learningPathId
91
+ const todayContentIds = dailySession.daily_session[0]?.content_ids || []
92
+ const nextContentIds = dailySession.daily_session[1]?.content_ids || []
93
+ const completedLessons = []
94
+ let todaysLessons = []
95
+ let nextLPLessons = []
96
+ const upcomingLessons = []
97
+ const lessonIds = lessons.map((lesson: any) => lesson.id).filter(Boolean)
98
+
99
+ lessons.forEach((lesson: any) => {
100
+ if (todayContentIds.includes(lesson.id)) {
101
+ todaysLessons.push(lesson)
102
+ } else if (nextContentIds.includes(lesson.id)) {
103
+ nextLPLessons.push(lesson)
104
+ } else if (lesson.progressStatus === 'completed') {
105
+ completedLessons.push(lesson)
106
+ } else {
107
+ upcomingLessons.push(lesson)
108
+ }
109
+ })
110
+
111
+ if (todaysLessons.length == 0 && nextLPLessons.length > 0) {
112
+ // Daily sessions first lessons are not part of the active learning path, but next lessons are
113
+ // load todays lessons from previous learning path
114
+ todaysLessons = await addContextToContent(
115
+ fetchByRailContentIds,
116
+ todayContentIds,
117
+ addContextParameters
118
+ )
119
+ } else if (nextContentIds.length > 0 && nextLPLessons.length == 0 && todaysLessons.length > 0) {
120
+ // Daily sessions first lessons are the active learning path and the next lessons are not
121
+ // load next lessons from next learning path
122
+ nextLPLessons = await addContextToContent(
123
+ fetchByRailContentIds,
124
+ nextContentIds,
125
+ addContextParameters
126
+ )
127
+ }
128
+
129
+ return {
130
+ id: learningPathId,
131
+ thumbnail: learningPath.thumbnail,
132
+ title: learningPath.title || '',
133
+ is_active_learning_path: isActiveLearningPath,
134
+ all_lessons: lessons,
135
+ upcoming_lessons: upcomingLessons,
136
+ todays_lessons: todaysLessons,
137
+ next_learning_path_lessons: nextLPLessons,
138
+ completed_lessons: completedLessons,
139
+ }
140
+ }