musora-content-services 2.158.3 → 2.159.0

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 (53) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.github/workflows/automated-testing.yml +21 -1
  3. package/CHANGELOG.md +8 -0
  4. package/README.md +21 -2
  5. package/jest.config.js +1 -4
  6. package/jest.integration.config.js +6 -0
  7. package/jest.live.config.js +1 -5
  8. package/package.json +5 -2
  9. package/src/contentTypeConfig.js +8 -5
  10. package/src/index.d.ts +2 -6
  11. package/src/index.js +2 -6
  12. package/src/services/content-org/learning-paths.ts +44 -39
  13. package/src/services/contentProgress.js +216 -207
  14. package/src/services/offline/progress.ts +107 -27
  15. package/src/services/sanity.js +55 -64
  16. package/src/services/sync/models/ContentProgress.ts +50 -34
  17. package/src/services/sync/repositories/content-progress.ts +105 -92
  18. package/test/{unit → integration}/awards/award-exclusion-handling.test.ts +2 -2
  19. package/test/integration/content-progress/__mocks__/mocks.ts +104 -0
  20. package/test/integration/content-progress/contentProgress.test.ts +335 -0
  21. package/test/integration/content-progress/e2eOfflineProgress.test.ts +352 -0
  22. package/test/integration/content-progress/e2eProgress.test.ts +612 -0
  23. package/test/integration/content-progress/getters.test.ts +334 -0
  24. package/test/integration/content-progress/helpers.test.ts +263 -0
  25. package/test/integration/content-progress/offlineContentProgress.test.ts +226 -0
  26. package/test/integration/forums.test.ts +209 -0
  27. package/test/integration/initializeTestDB.ts +80 -0
  28. package/test/{unit → integration}/sync/fetch.test.ts +1 -1
  29. package/test/{unit → integration}/sync/repositories/content-likes.test.ts +1 -1
  30. package/test/{unit → integration}/sync/repositories/practices.test.ts +1 -1
  31. package/test/{unit → integration}/sync/repositories/progress.test.ts +1 -1
  32. package/test/{unit → integration}/sync/repositories/user-award-progress.test.ts +1 -1
  33. package/test/{unit → integration}/sync/store/cross-user-protection.test.ts +2 -2
  34. package/test/{unit → integration}/sync/store/store-idb.test.ts +2 -2
  35. package/test/{unit → integration}/sync/store/store.test.ts +2 -2
  36. package/test/unit/content-progress/bubbleTrickle.test.ts +322 -0
  37. package/test/unit/content-progress/helpers.test.ts +329 -0
  38. package/test/unit/content-progress/navigateTo.test.ts +381 -0
  39. package/test/unit/contentMetaData.test.ts +58 -0
  40. package/tools/generate-index.cjs +6 -3
  41. package/test/SKIPPED_TESTS.md +0 -151
  42. package/test/integration/content.test.js +0 -107
  43. package/test/integration/contentProgress.test.js +0 -73
  44. package/test/integration/forum.test.js +0 -16
  45. package/test/integration/sanityQueryService.test.js +0 -681
  46. package/test/unit/contentProgress.test.ts +0 -81
  47. /package/test/{unit → integration}/awards/internal/image-utils.test.ts +0 -0
  48. /package/test/{unit → integration}/infrastructure/FetchRequestExecutor.test.ts +0 -0
  49. /package/test/{unit → integration}/notifications.test.ts +0 -0
  50. /package/test/{unit → integration}/sync/adapters/idb-errors.test.ts +0 -0
  51. /package/test/{unit → integration}/sync/adapters/sqlite-errors.test.ts +0 -0
  52. /package/test/{unit → integration}/sync/repositories/user-award-progress.static.test.ts +0 -0
  53. /package/test/{unit → integration}/userActivity.test.ts +0 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(rg:*)",
5
+ "Bash(npm run lint:*)",
6
+ "Bash(ls:*)",
7
+ "Bash(npm test *)",
8
+ "Bash(npx jest *)"
9
+ ],
10
+ "deny": []
11
+ }
12
+ }
@@ -12,7 +12,7 @@ jobs:
12
12
  - uses: actions/checkout@v4
13
13
  - uses: actions/setup-node@v4
14
14
  with:
15
- node-version: 20
15
+ node-version: 22
16
16
  cache: npm
17
17
  - name: Install dependencies
18
18
  run: npm ci
@@ -25,3 +25,23 @@ jobs:
25
25
  slug: railroadmedia/musora-content-services
26
26
  flags: unit
27
27
  fail_ci_if_error: true
28
+ integration-tests:
29
+ runs-on: ubuntu-latest
30
+ timeout-minutes: 5
31
+ steps:
32
+ - uses: actions/checkout@v4
33
+ - uses: actions/setup-node@v4
34
+ with:
35
+ node-version: 22
36
+ cache: npm
37
+ - name: Install dependencies
38
+ run: npm ci
39
+ - name: Run integration tests
40
+ run: npm run test:integration
41
+ - name: Upload coverage to Codecov
42
+ uses: codecov/codecov-action@v5
43
+ with:
44
+ token: ${{ secrets.CODECOV_TOKEN }}
45
+ slug: railroadmedia/musora-content-services
46
+ flags: integration
47
+ fail_ci_if_error: true
package/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.159.0](https://github.com/railroadmedia/musora-content-services/compare/v2.158.3...v2.159.0) (2026-05-12)
6
+
7
+
8
+ ### Features
9
+
10
+ * **BEHSTP-304:** LP lesson downloads & refactors ([#955](https://github.com/railroadmedia/musora-content-services/issues/955)) ([536e90f](https://github.com/railroadmedia/musora-content-services/commit/536e90f2b7e4d405e22b3de497191aa5c61b5776))
11
+ * **TP-1267:** audit live tests ([#962](https://github.com/railroadmedia/musora-content-services/issues/962)) ([aff540e](https://github.com/railroadmedia/musora-content-services/commit/aff540ec2e2f6f083e03406bc592eb54b4be8148))
12
+
5
13
  ### [2.158.3](https://github.com/railroadmedia/musora-content-services/compare/v2.158.2...v2.158.3) (2026-05-07)
6
14
 
7
15
 
package/README.md CHANGED
@@ -120,8 +120,27 @@ https://railroadmedia.github.io/musora-content-services/
120
120
  ## Run tests
121
121
  Ensure that the setup process has been completed, including copying .env file from 1Password "musora-content-services .env"
122
122
  and having jest installed (`npm install --save-dev jest`). To run the full test suite, simply run the following:
123
+
124
+ ### Unit tests only (fast, no external dependencies)
123
125
  ```
124
126
  npm test
125
127
  ```
126
- You can also filter down to specific tests with the `-- -t="..."` option. e.g. you can run the userContext test suite
127
- with `npm test -- -t="userContext"` or just the contentLiked test with `npm test -- -t="contentLiked"`
128
+ ### Integration tests only (mocked external boundaries, runs in CI)
129
+ ```
130
+ npm run test:integration
131
+ ```
132
+ ### Unit + integration (both suites)
133
+ ```
134
+ npm run test:all
135
+ ```
136
+ ### Live tests (requires real credentials in .env, never runs in CI)
137
+ ```
138
+ npm run test:live
139
+ ```
140
+ ### Filter to a specific test
141
+ ```
142
+ npm test -- -t="contentLiked"
143
+ ```
144
+
145
+ ### Without coverage
146
+ add flag `--coverage=false`
package/jest.config.js CHANGED
@@ -117,10 +117,7 @@ export default {
117
117
  },
118
118
 
119
119
  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
120
- modulePathIgnorePatterns: [
121
- '<rootDir>/test/live',
122
- '<rootDir>/test/integration'
123
- ],
120
+ testMatch: ['<rootDir>/test/unit/**/*.test.[jt]s?(x)'],
124
121
 
125
122
  // Activates notifications for test results
126
123
  // notify: false,
@@ -0,0 +1,6 @@
1
+ import baseConfig from './jest.config.js'
2
+ export default {
3
+ ...baseConfig,
4
+ testMatch: ['<rootDir>/test/integration/**/*.test.[jt]s?(x)'],
5
+ coverageThreshold: {},
6
+ }
@@ -1,10 +1,6 @@
1
- /** @type {import('jest').Config} */
2
1
  import baseConfig from './jest.config.js'
3
-
4
2
  export default {
5
3
  ...baseConfig,
6
- modulePathIgnorePatterns: [],
7
- setupFilesAfterEnv: ['dotenv/config'],
8
- testTimeout: 1000000,
4
+ testMatch: ['<rootDir>/test/live/**/*.test.[jt]s?(x)'],
9
5
  collectCoverage: false,
10
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.158.3",
3
+ "version": "2.159.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -8,7 +8,10 @@
8
8
  "build-index": "node tools/generate-index.cjs",
9
9
  "release": "standard-version",
10
10
  "doc": "jsdoc -c jsdoc.json --verbose",
11
- "test": "jest"
11
+ "test": "jest",
12
+ "test:integration": "jest --config=jest.integration.config.js",
13
+ "test:all": "jest --config=jest.config.js; jest --config=jest.integration.config.js",
14
+ "test:live": "jest --config=jest.live.config.js"
12
15
  },
13
16
  "repository": {
14
17
  "type": "git",
@@ -437,7 +437,7 @@ export let contentTypeConfig = {
437
437
  'railcontent_id',
438
438
  '"assignments": assignment[]{railcontent_id}',
439
439
  `"metadata": { brand, "type": _type, "parent_id": coalesce(${parentReferenceField}->railcontent_id, 0) }`,
440
- ],
440
+ ],
441
441
  childFields: [
442
442
  'railcontent_id',
443
443
  '"assignments": assignment[]{railcontent_id}',
@@ -498,6 +498,7 @@ export let contentTypeConfig = {
498
498
  'course-lesson': {
499
499
  fields: [`"resources": ${resourcesField}`],
500
500
  },
501
+
501
502
  download: {
502
503
  fields: [
503
504
  `"resources": ${resourcesField}`,
@@ -512,6 +513,7 @@ export let contentTypeConfig = {
512
513
  'video',
513
514
  ...playAlongMp3sFields,
514
515
  pcdForDownloadField,
516
+ '"learning_path_parent_id": *[_type == "learning-path-v2" && references(^._id)][0].railcontent_id',
515
517
  `...select(
516
518
  defined(live_event_start_time) => {
517
519
  ${getLiveFields(true).join(',')}
@@ -531,6 +533,7 @@ export let contentTypeConfig = {
531
533
  'video',
532
534
  ...playAlongMp3sFields,
533
535
  pcdForDownloadField,
536
+ '"learning_path_parent_id": *[_type == "learning-path-v2" && references(^._id)][0].railcontent_id',
534
537
  `...select(
535
538
  defined(live_event_start_time) => {
536
539
  ${getLiveFields(true).join(',')}
@@ -693,7 +696,7 @@ export let contentTypeConfig = {
693
696
  'new-and-scheduled': {
694
697
  fields: [
695
698
  'show_in_new_feed',
696
- isLiveField()
699
+ isLiveField(),
697
700
  ],
698
701
  },
699
702
  }
@@ -812,7 +815,7 @@ export function artistOrInstructorNameAsArray(key = 'artists') {
812
815
 
813
816
  export async function getFieldsForContentTypeWithFilteredChildren(
814
817
  contentType,
815
- asQueryString = true
818
+ asQueryString = true,
816
819
  ) {
817
820
  const childFields = getChildFieldsForContentType(contentType, true)
818
821
  const parentFields = getFieldsForContentType(contentType, false)
@@ -827,7 +830,7 @@ export async function getFieldsForContentTypeWithFilteredChildren(
827
830
  "children": child[${childFilter}]->{
828
831
  ${childFields}
829
832
  },
830
- }`
833
+ }`,
831
834
  )
832
835
  }
833
836
  return asQueryString ? parentFields.toString() + ',' : parentFields
@@ -917,7 +920,7 @@ const filterHandlers = {
917
920
  length: (value) => {
918
921
  // Find the matching length option by name
919
922
  const lengthOption = Object.values(LengthFilterOptions).find(
920
- (opt) => typeof opt === 'object' && opt.name === value
923
+ (opt) => typeof opt === 'object' && opt.name === value,
921
924
  )
922
925
 
923
926
  if (!lengthOption) return ''
package/src/index.d.ts CHANGED
@@ -57,9 +57,6 @@ import {
57
57
  getEnrichedLearningPaths,
58
58
  getLearningPathLessonsByIds,
59
59
  mapContentToParent,
60
- mapContentsThatWereLastProgressedFromMethod,
61
- mapLearningPathParentsTo,
62
- onContentCompletedLearningPathActions,
63
60
  resetAllLearningPaths,
64
61
  startLearningPath,
65
62
  updateDailySession
@@ -446,6 +443,7 @@ import {
446
443
 
447
444
  import {
448
445
  getOnboardingRecommendedContent,
446
+ initializeOnboardingFlow,
449
447
  startOnboarding,
450
448
  updateOnboarding,
451
449
  userOnboardingForBrand
@@ -727,6 +725,7 @@ declare module 'musora-content-services' {
727
725
  guidedCourses,
728
726
  hasAnyMethodV2IntroCompleted,
729
727
  initializeEnvVar,
728
+ initializeOnboardingFlow,
730
729
  initializeService,
731
730
  isBucketUrl,
732
731
  isContentLiked,
@@ -745,8 +744,6 @@ declare module 'musora-content-services' {
745
744
  login,
746
745
  logout,
747
746
  mapContentToParent,
748
- mapContentsThatWereLastProgressedFromMethod,
749
- mapLearningPathParentsTo,
750
747
  markAllNotificationsAsRead,
751
748
  markContentAsInterested,
752
749
  markContentAsNotInterested,
@@ -754,7 +751,6 @@ declare module 'musora-content-services' {
754
751
  markNotificationAsUnread,
755
752
  markThreadAsRead,
756
753
  numberOfActiveUsers,
757
- onContentCompletedLearningPathActions,
758
754
  onProgressSaved,
759
755
  openComment,
760
756
  otherStats,
package/src/index.js CHANGED
@@ -61,9 +61,6 @@ import {
61
61
  getEnrichedLearningPaths,
62
62
  getLearningPathLessonsByIds,
63
63
  mapContentToParent,
64
- mapContentsThatWereLastProgressedFromMethod,
65
- mapLearningPathParentsTo,
66
- onContentCompletedLearningPathActions,
67
64
  resetAllLearningPaths,
68
65
  startLearningPath,
69
66
  updateDailySession
@@ -450,6 +447,7 @@ import {
450
447
 
451
448
  import {
452
449
  getOnboardingRecommendedContent,
450
+ initializeOnboardingFlow,
453
451
  startOnboarding,
454
452
  updateOnboarding,
455
453
  userOnboardingForBrand
@@ -726,6 +724,7 @@ export {
726
724
  guidedCourses,
727
725
  hasAnyMethodV2IntroCompleted,
728
726
  initializeEnvVar,
727
+ initializeOnboardingFlow,
729
728
  initializeService,
730
729
  isBucketUrl,
731
730
  isContentLiked,
@@ -744,8 +743,6 @@ export {
744
743
  login,
745
744
  logout,
746
745
  mapContentToParent,
747
- mapContentsThatWereLastProgressedFromMethod,
748
- mapLearningPathParentsTo,
749
746
  markAllNotificationsAsRead,
750
747
  markContentAsInterested,
751
748
  markContentAsNotInterested,
@@ -753,7 +750,6 @@ export {
753
750
  markNotificationAsUnread,
754
751
  markThreadAsRead,
755
752
  numberOfActiveUsers,
756
- onContentCompletedLearningPathActions,
757
753
  onProgressSaved,
758
754
  openComment,
759
755
  otherStats,
@@ -26,10 +26,16 @@ import { ContentProgress } from '../sync/models'
26
26
  import dayjs from 'dayjs'
27
27
  import { LEARNING_PATH_LESSON } from '../../contentTypeConfig'
28
28
 
29
+ const excludeFromGeneratedIndex = [
30
+ 'onLearningPathCompletedActions',
31
+ 'mapContentsThatWereLastProgressedFromMethod',
32
+ 'mapLearningPathParentsTo',
33
+ ]
34
+
29
35
  const BASE_PATH: string = `/api/content-org`
30
36
  const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
31
- let dailySessionPromise: Promise<DailySessionResponse|""> | null = null
32
- let activePathPromise: Promise<ActiveLearningPathResponse|""> | null = null
37
+ let dailySessionPromise: Promise<DailySessionResponse | ''> | null = null
38
+ let activePathPromise: Promise<ActiveLearningPathResponse | ''> | null = null
33
39
 
34
40
  interface ActiveLearningPathResponse {
35
41
  user_id: number
@@ -66,7 +72,7 @@ interface CollectionObject {
66
72
  export async function getDailySession(
67
73
  brand: string,
68
74
  userDate: Date,
69
- forceRefresh: boolean = false
75
+ forceRefresh: boolean = false,
70
76
  ) {
71
77
  if (dailySessionPromise && !forceRefresh) {
72
78
  return dailySessionPromise
@@ -105,7 +111,7 @@ export async function getDailySession(
105
111
  export async function updateDailySession(
106
112
  brand: string,
107
113
  userDate: Date,
108
- keepFirstLearningPath: boolean = false
114
+ keepFirstLearningPath: boolean = false,
109
115
  ) {
110
116
  const dateWithTimezone = formatLocalDateTime(userDate)
111
117
  const url: string = `${LEARNING_PATHS_PATH}/daily-session/create`
@@ -115,7 +121,7 @@ export async function updateDailySession(
115
121
  keepFirstLearningPath: keepFirstLearningPath,
116
122
  }
117
123
  try {
118
- const response = (await POST(url, body)) as DailySessionResponse|''
124
+ const response = (await POST(url, body)) as DailySessionResponse | ''
119
125
 
120
126
  if (response || response === '') { // refresh cached value
121
127
  const urlGet: string = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${encodeURIComponent(dateWithTimezone)}`
@@ -173,8 +179,8 @@ export async function startLearningPath(brand: string, learningPathId: number) {
173
179
 
174
180
  async function dataPromiseGET(
175
181
  url: string,
176
- forceRefresh: boolean
177
- ): Promise<DailySessionResponse | ActiveLearningPathResponse | ""> {
182
+ forceRefresh: boolean,
183
+ ): Promise<DailySessionResponse | ActiveLearningPathResponse | ''> {
178
184
  if (url.includes('daily-session')) {
179
185
  if (!dailySessionPromise || forceRefresh) {
180
186
  dailySessionPromise = GET(url, {
@@ -203,8 +209,8 @@ export async function resetAllLearningPaths() {
203
209
  await Promise.all([
204
210
  db.contentProgress.eraseProgressMany(all.intros, null),
205
211
  ...all.learning_paths.map((id) =>
206
- db.contentProgress.eraseProgress(id, {id, type: COLLECTION_TYPE.LEARNING_PATH})
207
- )
212
+ db.contentProgress.eraseProgress(id, { id, type: COLLECTION_TYPE.LEARNING_PATH }),
213
+ ),
208
214
  ])
209
215
  }),
210
216
  POST(url, {}),
@@ -230,15 +236,15 @@ export async function getEnrichedLearningPath(learningPathId) {
230
236
  addProgressTimestamp: true,
231
237
  addResumeTimeSeconds: true,
232
238
  addNavigateTo: true,
233
- }
239
+ },
234
240
  )) as any
235
241
  // add awards to LP parents only
236
242
  response = await addContextToLearningPaths(() => response, { addAwards: true })
237
243
  if (!response) return response
238
244
 
239
245
  response.children = mapContentToParent(
240
- response.children,
241
- {lessonType: LEARNING_PATH_LESSON, parentContentId: learningPathId}
246
+ response.children,
247
+ { lessonType: LEARNING_PATH_LESSON, parentContentId: learningPathId },
242
248
  )
243
249
  return response
244
250
  }
@@ -262,7 +268,7 @@ export async function getEnrichedLearningPaths(learningPathIds: number[]) {
262
268
  addProgressTimestamp: true,
263
269
  addResumeTimeSeconds: true,
264
270
  addNavigateTo: true,
265
- }
271
+ },
266
272
  )) as any
267
273
  // add awards to LP parents only
268
274
  response = await addContextToLearningPaths(() => response, { addAwards: true })
@@ -271,8 +277,8 @@ export async function getEnrichedLearningPaths(learningPathIds: number[]) {
271
277
 
272
278
  response.forEach((learningPath) => {
273
279
  learningPath.children = mapContentToParent(
274
- learningPath.children,
275
- {lessonType: LEARNING_PATH_LESSON, parentContentId: learningPath.id}
280
+ learningPath.children,
281
+ { lessonType: LEARNING_PATH_LESSON, parentContentId: learningPath.id },
276
282
  )
277
283
  })
278
284
  return response
@@ -299,8 +305,8 @@ export async function getLearningPathLessonsByIds(contentIds, learningPathId) {
299
305
  * @param options.parentContentId
300
306
  */
301
307
  export function mapContentToParent(
302
- lessons: any,
303
- options?: { lessonType?: string; parentContentId?: number }
308
+ lessons: any,
309
+ options?: { lessonType?: string; parentContentId?: number },
304
310
  ) {
305
311
  if (!lessons || (Array.isArray(lessons) && lessons.length === 0)) return lessons
306
312
 
@@ -363,7 +369,7 @@ interface fetchLearningPathLessonsResponse {
363
369
  export async function fetchLearningPathLessons(
364
370
  learningPathId: number,
365
371
  brand: string,
366
- userDate: Date
372
+ userDate: Date,
367
373
  ) {
368
374
  const learningPath = await getEnrichedLearningPath(learningPathId)
369
375
  if (!learningPath || learningPath.children?.length === 0) return null
@@ -420,7 +426,7 @@ export async function fetchLearningPathLessons(
420
426
  if (previousContentIds.length !== 0) {
421
427
  previousLPDailies = await getLearningPathLessonsByIds(
422
428
  previousContentIds,
423
- previousLearningPathId
429
+ previousLearningPathId,
424
430
  )
425
431
  }
426
432
  if (nextContentIds.length !== 0) {
@@ -429,7 +435,7 @@ export async function fetchLearningPathLessons(
429
435
  lessons.map((lesson) => ({
430
436
  ...lesson,
431
437
  in_next_learning_path: learningPath.progressStatus === STATE.COMPLETED,
432
- }))
438
+ })),
433
439
  )
434
440
  }
435
441
 
@@ -456,7 +462,7 @@ export async function fetchLearningPathLessons(
456
462
  * @returns {Promise<number[]>} Array with completed content IDs
457
463
  */
458
464
  export async function fetchLearningPathProgressCheckLessons(
459
- contentIds: number[]
465
+ contentIds: number[],
460
466
  ): Promise<number[]> {
461
467
  let query = await getAllCompletedByIds(contentIds)
462
468
  let completedProgress = query.data.map((progress) => progress.content_id)
@@ -467,6 +473,7 @@ interface completeMethodIntroVideo {
467
473
  intro_video_response: SyncWriteDTO<ContentProgress, any> | null
468
474
  active_path_response: ActiveLearningPathResponse
469
475
  }
476
+
470
477
  /**
471
478
  * Handles completion of method intro video and other related actions.
472
479
  * @param introVideoId - The method intro video content ID. If not provided, does not `complete` intro video.
@@ -476,14 +483,14 @@ interface completeMethodIntroVideo {
476
483
  * @returns {Promise<Object>} response.active_path_response - The set active learning path response.
477
484
  */
478
485
  export async function completeMethodIntroVideo(
479
- introVideoId: number|null,
480
- brand: string
486
+ introVideoId: number | null,
487
+ brand: string,
481
488
  ): Promise<completeMethodIntroVideo> {
482
489
  let response = {} as completeMethodIntroVideo
483
490
 
484
491
  const [intro_video_response, methodStructure] = await Promise.all([
485
492
  introVideoId ? completeIfNotCompleted(introVideoId) : Promise.resolve(null),
486
- fetchMethodV2Structure(brand)
493
+ fetchMethodV2Structure(brand),
487
494
  ])
488
495
  response.intro_video_response = intro_video_response
489
496
 
@@ -492,7 +499,7 @@ export async function completeMethodIntroVideo(
492
499
  response.active_path_response = await methodIntroVideoCompleteActions(
493
500
  brand,
494
501
  firstLearningPathId,
495
- new Date()
502
+ new Date(),
496
503
  )
497
504
 
498
505
  return response
@@ -511,6 +518,7 @@ interface completeLearningPathIntroVideo {
511
518
  lesson_import_response: SyncWriteDTO<ContentProgress, any> | null
512
519
  update_dailies_response: DailySessionResponse | null
513
520
  }
521
+
514
522
  /**
515
523
  * Handles completion of learning path intro video and other related actions.
516
524
  * @param introVideoId - The learning path intro video content ID.
@@ -527,14 +535,14 @@ export async function completeLearningPathIntroVideo(
527
535
  introVideoId: number,
528
536
  learningPathId: number,
529
537
  lessonsToImport: number[] | null,
530
- brand: string
538
+ brand: string,
531
539
  ) {
532
540
  let response = {} as completeLearningPathIntroVideo
533
541
  const collection: CollectionObject = { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH }
534
542
 
535
543
  const [anyIntroComplete, activePath] = await Promise.all([
536
544
  hasAnyMethodV2IntroCompleted(),
537
- getActivePath(brand)
545
+ getActivePath(brand),
538
546
  ])
539
547
 
540
548
  let lateMethodSetup = false
@@ -561,7 +569,7 @@ export async function completeLearningPathIntroVideo(
561
569
  }
562
570
 
563
571
  async function completeIfNotCompleted(
564
- contentId: number
572
+ contentId: number,
565
573
  ): Promise<SyncWriteDTO<ContentProgress, any> | null> {
566
574
  const introVideoStatus = await getProgressState(contentId)
567
575
 
@@ -570,20 +578,14 @@ async function completeIfNotCompleted(
570
578
 
571
579
  async function resetIfPossible(
572
580
  contentId: number,
573
- collection: CollectionParameter = null
581
+ collection: CollectionParameter = null,
574
582
  ): Promise<SyncWriteDTO<ContentProgress, any> | null> {
575
583
  const status = await getProgressState(contentId, collection)
576
584
 
577
585
  return status !== '' ? await contentStatusReset(contentId, collection) : null
578
586
  }
579
587
 
580
- export async function onContentCompletedLearningPathActions(
581
- contentId: number,
582
- collection: CollectionObject | null
583
- ) {
584
- if (collection?.type !== COLLECTION_TYPE.LEARNING_PATH) return
585
- if (contentId !== collection?.id) return
586
-
588
+ export async function onLearningPathCompletedActions(contentId: number) {
587
589
  const learningPathId = contentId
588
590
  const learningPath = await getEnrichedLearningPath(learningPathId)
589
591
 
@@ -628,7 +630,7 @@ export async function mapContentsThatWereLastProgressedFromMethod(objects: any[]
628
630
 
629
631
  let filtered = objects.filter((obj) => trueIds.includes(obj.id))
630
632
 
631
- filtered = await mapLearningPathParentsTo(filtered, {type: true, parent_id: true})
633
+ filtered = await mapLearningPathParentsTo(filtered, { type: true, parent_id: true })
632
634
 
633
635
  // Map each filtered item back into the total contents object
634
636
  objects = objects.map((item) => {
@@ -639,7 +641,10 @@ export async function mapContentsThatWereLastProgressedFromMethod(objects: any[]
639
641
 
640
642
  }
641
643
 
642
- export async function mapLearningPathParentsTo(objects: any[], fieldsToMap?: {type?: boolean, parent_id?: boolean}): Promise<object[]> {
644
+ export async function mapLearningPathParentsTo(objects: any[], fieldsToMap?: {
645
+ type?: boolean,
646
+ parent_id?: boolean
647
+ }): Promise<object[]> {
643
648
  const ids = objects.map((obj: any) => obj.id) as number[]
644
649
  const hierarchy = await fetchParentChildRelationshipsFor(ids, COLLECTION_TYPE.LEARNING_PATH)
645
650
 
@@ -654,7 +659,7 @@ export async function mapLearningPathParentsTo(objects: any[], fieldsToMap?: {ty
654
659
  const parent_id = parentMap.get(obj.id) ?? undefined
655
660
  return mapContentToParent(obj, {
656
661
  lessonType: fieldsToMap.type ? LEARNING_PATH_LESSON : undefined,
657
- parentContentId: fieldsToMap.parent_id ? parent_id : undefined
662
+ parentContentId: fieldsToMap.parent_id ? parent_id : undefined,
658
663
  })
659
664
  })
660
665
  }