musora-content-services 2.93.1 → 2.94.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 (155) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/.coderabbit.yaml +0 -0
  3. package/.editorconfig +0 -0
  4. package/.github/pull_request_template.md +0 -0
  5. package/.github/workflows/conventional-commits.yaml +0 -0
  6. package/.github/workflows/node.js.yml +0 -0
  7. package/.prettierignore +0 -0
  8. package/.prettierrc +0 -0
  9. package/.yarnrc.yml +1 -0
  10. package/CHANGELOG.md +24 -0
  11. package/README.md +0 -0
  12. package/jest.config.js +0 -0
  13. package/jsdoc.json +0 -0
  14. package/package.json +1 -1
  15. package/src/contentMetaData.js +0 -0
  16. package/src/filterBuilder.js +0 -0
  17. package/src/index.d.ts +4 -0
  18. package/src/index.js +4 -0
  19. package/src/infrastructure/http/HttpClient.ts +0 -0
  20. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  21. package/src/infrastructure/http/index.ts +0 -0
  22. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  23. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  24. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  25. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  26. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  27. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  28. package/src/lib/httpHelper.js +0 -0
  29. package/src/lib/lastUpdated.js +0 -0
  30. package/src/lib/sanity/query.ts +0 -0
  31. package/src/services/api/types.js +0 -0
  32. package/src/services/api/types.ts +0 -0
  33. package/src/services/config.js +1 -1
  34. package/src/services/content/content.ts +0 -0
  35. package/src/services/content-org/content-org.js +0 -0
  36. package/src/services/content-org/guided-courses.ts +0 -0
  37. package/src/services/content-org/learning-paths.ts +31 -18
  38. package/src/services/content-org/playlists-types.js +0 -0
  39. package/src/services/content-org/playlists.js +0 -0
  40. package/src/services/content.js +0 -0
  41. package/src/services/contentAggregator.js +0 -0
  42. package/src/services/contentLikes.js +0 -0
  43. package/src/services/contentProgress.js +50 -13
  44. package/src/services/dataContext.js +0 -0
  45. package/src/services/dateUtils.js +0 -0
  46. package/src/services/eventsAPI.js +0 -0
  47. package/src/services/forums/categories.ts +0 -0
  48. package/src/services/forums/forums.ts +0 -0
  49. package/src/services/forums/posts.ts +0 -0
  50. package/src/services/forums/threads.ts +0 -0
  51. package/src/services/forums/types.ts +0 -0
  52. package/src/services/gamification/awards.ts +0 -0
  53. package/src/services/gamification/gamification.js +0 -0
  54. package/src/services/imageSRCBuilder.js +0 -0
  55. package/src/services/imageSRCVerify.js +0 -0
  56. package/src/services/liveTesting.ts +0 -0
  57. package/src/services/permissions/PermissionsAdapter.ts +0 -0
  58. package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
  59. package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
  60. package/src/services/permissions/README.md +0 -0
  61. package/src/services/permissions/index.ts +0 -0
  62. package/src/services/progress-row/method-card.js +2 -1
  63. package/src/services/railcontent.js +0 -0
  64. package/src/services/recommendations.js +0 -0
  65. package/src/services/reporting/README.md +0 -0
  66. package/src/services/reporting/types.ts +0 -0
  67. package/src/services/sanity.js +27 -3
  68. package/src/services/sentry/.indexignore +0 -0
  69. package/src/services/sentry/index.ts +0 -0
  70. package/src/services/sync/.indexignore +0 -0
  71. package/src/services/sync/adapters/factory.ts +0 -0
  72. package/src/services/sync/adapters/lokijs.ts +0 -0
  73. package/src/services/sync/adapters/sqlite.ts +0 -0
  74. package/src/services/sync/concurrency-safety.ts +0 -0
  75. package/src/services/sync/context/index.ts +0 -0
  76. package/src/services/sync/context/providers/base.ts +0 -0
  77. package/src/services/sync/context/providers/connectivity.ts +0 -0
  78. package/src/services/sync/context/providers/durability.ts +0 -0
  79. package/src/services/sync/context/providers/index.ts +0 -0
  80. package/src/services/sync/context/providers/session.ts +0 -0
  81. package/src/services/sync/context/providers/tabs.ts +0 -0
  82. package/src/services/sync/context/providers/visibility.ts +0 -0
  83. package/src/services/sync/database/factory.ts +0 -0
  84. package/src/services/sync/errors/index.ts +0 -0
  85. package/src/services/sync/index.ts +0 -0
  86. package/src/services/sync/models/Base.ts +0 -0
  87. package/src/services/sync/models/ContentLike.ts +0 -0
  88. package/src/services/sync/models/ContentProgress.ts +1 -1
  89. package/src/services/sync/models/Practice.ts +0 -0
  90. package/src/services/sync/models/PracticeDayNote.ts +0 -0
  91. package/src/services/sync/models/index.ts +0 -0
  92. package/src/services/sync/repositories/base.ts +0 -0
  93. package/src/services/sync/repositories/content-likes.ts +0 -0
  94. package/src/services/sync/repositories/content-progress.ts +34 -7
  95. package/src/services/sync/repositories/index.ts +0 -0
  96. package/src/services/sync/repositories/practice-day-notes.ts +0 -0
  97. package/src/services/sync/repositories/practices.ts +0 -0
  98. package/src/services/sync/resolver.ts +0 -0
  99. package/src/services/sync/run-scope.ts +0 -0
  100. package/src/services/sync/schema/index.ts +0 -0
  101. package/src/services/sync/serializers/index.ts +0 -0
  102. package/src/services/sync/serializers/model.ts +0 -0
  103. package/src/services/sync/serializers/raw.ts +0 -0
  104. package/src/services/sync/store/push-coalescer.ts +0 -0
  105. package/src/services/sync/store-configs.ts +0 -0
  106. package/src/services/sync/strategies/base.ts +0 -0
  107. package/src/services/sync/strategies/index.ts +0 -0
  108. package/src/services/sync/strategies/initial.ts +0 -0
  109. package/src/services/sync/strategies/polling.ts +0 -0
  110. package/src/services/sync/utils/event-emitter.ts +0 -0
  111. package/src/services/sync/utils/index.ts +0 -0
  112. package/src/services/sync/utils/throttle.ts +0 -0
  113. package/src/services/sync/utils/timers.ts +0 -0
  114. package/src/services/types.js +0 -0
  115. package/src/services/user/account.ts +0 -0
  116. package/src/services/user/chat.js +0 -0
  117. package/src/services/user/interests.js +0 -0
  118. package/src/services/user/management.js +0 -0
  119. package/src/services/user/memberships.ts +0 -0
  120. package/src/services/user/notifications.js +0 -0
  121. package/src/services/user/payments.ts +0 -0
  122. package/src/services/user/permissions.js +0 -0
  123. package/src/services/user/profile.js +0 -0
  124. package/src/services/user/sessions.js +0 -0
  125. package/src/services/user/types.d.ts +0 -0
  126. package/src/services/user/types.js +0 -0
  127. package/src/services/user/user-management-system.js +0 -0
  128. package/src/services/userActivity.js +3 -2
  129. package/test/HttpClient.test.js +0 -0
  130. package/test/content.test.js +0 -0
  131. package/test/contentLikes.test.js +0 -0
  132. package/test/contentProgress.test.js +0 -0
  133. package/test/dataContext.test.js +0 -0
  134. package/test/forum.test.js +0 -0
  135. package/test/imageSRCBuilder.test.js +0 -0
  136. package/test/imageSRCVerify.test.js +0 -0
  137. package/test/initializeTests.js +0 -0
  138. package/test/learningPaths.test.js +0 -0
  139. package/test/lib/lastUpdated.test.js +0 -0
  140. package/test/live/contentProgressLive.test.js +0 -0
  141. package/test/live/railcontentLive.test.js +0 -0
  142. package/test/localStorageMock.js +0 -0
  143. package/test/log.js +0 -0
  144. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  145. package/test/mockData/mockData_progress_content.json +0 -0
  146. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  147. package/test/mockData/mockData_user_practices.json +0 -0
  148. package/test/notifications.test.js +0 -0
  149. package/test/progressRows.test.js +0 -0
  150. package/test/reporting.test.js +132 -0
  151. package/test/sanityQueryService.test.js +0 -0
  152. package/test/streakMessage.test.js +0 -0
  153. package/test/user/permissions.test.js +0 -0
  154. package/test/userActivity.test.js +0 -0
  155. package/tools/generate-index.cjs +0 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Read(//app/musora-platform-backend/**)",
5
+ "Read(//app/musora-platform-frontend/**)",
6
+ "Bash(find:*)",
7
+ "Bash(sed:*)",
8
+ "Read(//app/**)",
9
+ "Bash(cat:*)"
10
+ ],
11
+ "deny": [],
12
+ "ask": []
13
+ }
14
+ }
package/.coderabbit.yaml CHANGED
File without changes
package/.editorconfig CHANGED
File without changes
File without changes
File without changes
File without changes
package/.prettierignore CHANGED
File without changes
package/.prettierrc CHANGED
File without changes
package/.yarnrc.yml ADDED
@@ -0,0 +1 @@
1
+ nodeLinker: node-modules
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
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.94.1](https://github.com/railroadmedia/musora-content-services/compare/v2.94.0...v2.94.1) (2025-12-03)
6
+
7
+ ## [2.94.0](https://github.com/railroadmedia/musora-content-services/compare/v2.92.6...v2.94.0) (2025-12-03)
8
+
9
+
10
+ ### Features
11
+
12
+ * **BEH-1192:** revise fetchLearningPathProgressCheckLessons ([#604](https://github.com/railroadmedia/musora-content-services/issues/604)) ([db8cb70](https://github.com/railroadmedia/musora-content-services/commit/db8cb70ed70f4341fdd4651cdd3fea2b8047dc21))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * **agi:** make brand parameter less strict for now ([#603](https://github.com/railroadmedia/musora-content-services/issues/603)) ([b440b92](https://github.com/railroadmedia/musora-content-services/commit/b440b92ae1b4aab80d7e5ac5238c1e386cf09db8))
18
+ * method progress card subtitle ([#608](https://github.com/railroadmedia/musora-content-services/issues/608)) ([3bbfb56](https://github.com/railroadmedia/musora-content-services/commit/3bbfb56fce08e0753e7708bdf2d1356000b1c398))
19
+ * progress fixes and features with watermelon ([#607](https://github.com/railroadmedia/musora-content-services/issues/607)) ([005403c](https://github.com/railroadmedia/musora-content-services/commit/005403c9bbaeebe8c5ecb5315754de4448bcbf43))
20
+ * Pull in permission_v2 ([#602](https://github.com/railroadmedia/musora-content-services/issues/602)) ([289e32d](https://github.com/railroadmedia/musora-content-services/commit/289e32d08bd3a6448191b9f6034801e4cf8d40e5))
21
+
22
+ ### [2.93.2](https://github.com/railroadmedia/musora-content-services/compare/v2.93.1...v2.93.2) (2025-12-02)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * progress fixes and features with watermelon ([#607](https://github.com/railroadmedia/musora-content-services/issues/607)) ([005403c](https://github.com/railroadmedia/musora-content-services/commit/005403c9bbaeebe8c5ecb5315754de4448bcbf43))
28
+
5
29
  ### [2.93.1](https://github.com/railroadmedia/musora-content-services/compare/v2.93.0...v2.93.1) (2025-12-02)
6
30
 
7
31
  ## [2.93.0](https://github.com/railroadmedia/musora-content-services/compare/v2.92.7...v2.93.0) (2025-12-02)
package/README.md CHANGED
File without changes
package/jest.config.js CHANGED
File without changes
package/jsdoc.json CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.93.1",
3
+ "version": "2.94.1",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
File without changes
File without changes
package/src/index.d.ts CHANGED
@@ -91,6 +91,7 @@ import {
91
91
  contentStatusCompleted,
92
92
  contentStatusReset,
93
93
  contentStatusStarted,
94
+ contentsStatusCompleted,
94
95
  getAllCompleted,
95
96
  getAllCompletedByIds,
96
97
  getAllStarted,
@@ -250,6 +251,7 @@ import {
250
251
  fetchContentRows,
251
252
  fetchFoundation,
252
253
  fetchHierarchy,
254
+ fetchLearningPathHierarchy,
253
255
  fetchLeaving,
254
256
  fetchLessonContent,
255
257
  fetchLessonsFeaturingThisContent,
@@ -428,6 +430,7 @@ declare module 'musora-content-services' {
428
430
  contentStatusCompleted,
429
431
  contentStatusReset,
430
432
  contentStatusStarted,
433
+ contentsStatusCompleted,
431
434
  convertToTimeZone,
432
435
  createComment,
433
436
  createForumCategory,
@@ -494,6 +497,7 @@ declare module 'musora-content-services' {
494
497
  fetchInterests,
495
498
  fetchLastInteractedChild,
496
499
  fetchLatestThreads,
500
+ fetchLearningPathHierarchy,
497
501
  fetchLearningPathLessons,
498
502
  fetchLearningPathProgressCheckLessons,
499
503
  fetchLeaving,
package/src/index.js CHANGED
@@ -95,6 +95,7 @@ import {
95
95
  contentStatusCompleted,
96
96
  contentStatusReset,
97
97
  contentStatusStarted,
98
+ contentsStatusCompleted,
98
99
  getAllCompleted,
99
100
  getAllCompletedByIds,
100
101
  getAllStarted,
@@ -254,6 +255,7 @@ import {
254
255
  fetchContentRows,
255
256
  fetchFoundation,
256
257
  fetchHierarchy,
258
+ fetchLearningPathHierarchy,
257
259
  fetchLeaving,
258
260
  fetchLessonContent,
259
261
  fetchLessonsFeaturingThisContent,
@@ -427,6 +429,7 @@ export {
427
429
  contentStatusCompleted,
428
430
  contentStatusReset,
429
431
  contentStatusStarted,
432
+ contentsStatusCompleted,
430
433
  convertToTimeZone,
431
434
  createComment,
432
435
  createForumCategory,
@@ -493,6 +496,7 @@ export {
493
496
  fetchInterests,
494
497
  fetchLastInteractedChild,
495
498
  fetchLatestThreads,
499
+ fetchLearningPathHierarchy,
496
500
  fetchLearningPathLessons,
497
501
  fetchLearningPathProgressCheckLessons,
498
502
  fetchLeaving,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -12,7 +12,7 @@ export let globalConfig = {
12
12
  localStorage: null,
13
13
  isMA: false,
14
14
  localTimezoneString: null, // In format: America/Vancouver
15
- permissionsVersion: 'v1', // 'v1' or 'v2'
15
+ permissionsVersion: 'v2', // 'v1' or 'v2'
16
16
  }
17
17
 
18
18
  /**
File without changes
File without changes
File without changes
@@ -7,11 +7,14 @@ import { fetchByRailContentId, fetchMethodV2Structure } from '../sanity.js'
7
7
  import { addContextToContent } from '../contentAggregator.js'
8
8
  import {
9
9
  contentStatusCompleted,
10
+ contentsStatusCompleted,
10
11
  contentStatusReset,
11
12
  getAllCompletedByIds,
12
13
  getProgressState,
13
14
  } from '../contentProgress.js'
14
- import { STATE } from '../sync/models/ContentProgress'
15
+ import { COLLECTION_TYPE, STATE } from "../sync/models/ContentProgress";
16
+ import { SyncWriteDTO } from "../sync";
17
+ import { ContentProgress } from "../sync/models";
15
18
 
16
19
  const BASE_PATH: string = `/api/content-org`
17
20
  const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
@@ -22,6 +25,20 @@ interface ActiveLearningPathResponse {
22
25
  active_learning_path_id: number,
23
26
  }
24
27
 
28
+ interface DailySessionResponse {
29
+ user_id: number,
30
+ brand: string,
31
+ user_date: string
32
+ daily_session: DailySession[],
33
+ active_learning_path_id: number,
34
+ active_learning_path_created_at: string,
35
+ }
36
+
37
+ interface DailySession {
38
+ content_ids: number[],
39
+ learning_path_id: number,
40
+ }
41
+
25
42
  /**
26
43
  * Gets today's daily session for the user.
27
44
  * @param brand
@@ -30,7 +47,7 @@ interface ActiveLearningPathResponse {
30
47
  export async function getDailySession(brand: string, userDate: Date) {
31
48
  const stringDate = userDate.toISOString().split('T')[0]
32
49
  const url: string = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${stringDate}`
33
- return await fetchHandler(url, 'GET', null, null)
50
+ return await fetchHandler(url, 'GET', null, null) as DailySessionResponse
34
51
  }
35
52
 
36
53
  /**
@@ -47,7 +64,7 @@ export async function updateDailySession(
47
64
  const stringDate = userDate.toISOString().split('T')[0]
48
65
  const url: string = `${LEARNING_PATHS_PATH}/daily-session/create`
49
66
  const body = { brand: brand, userDate: stringDate, keepFirstLearningPath: keepFirstLearningPath }
50
- return await fetchHandler(url, 'POST', null, body)
67
+ return await fetchHandler(url, 'POST', null, body) as DailySessionResponse
51
68
  }
52
69
 
53
70
  /**
@@ -56,7 +73,7 @@ export async function updateDailySession(
56
73
  */
57
74
  export async function getActivePath(brand: string) {
58
75
  const url: string = `${LEARNING_PATHS_PATH}/active-path/get?brand=${brand}`
59
- return await fetchHandler(url, 'GET', null, null)
76
+ return await fetchHandler(url, 'GET', null, null) as ActiveLearningPathResponse
60
77
  }
61
78
 
62
79
  /**
@@ -67,7 +84,7 @@ export async function getActivePath(brand: string) {
67
84
  export async function startLearningPath(brand: string, learningPathId: number) {
68
85
  const url: string = `${LEARNING_PATHS_PATH}/active-path/set`
69
86
  const body = { brand: brand, learning_path_id: learningPathId }
70
- return await fetchHandler(url, 'POST', null, body)
87
+ return await fetchHandler(url, 'POST', null, body) as ActiveLearningPathResponse
71
88
  }
72
89
 
73
90
  /**
@@ -87,9 +104,9 @@ export async function getEnrichedLearningPath(learningPathId) {
87
104
  const response = (await addContextToContent(
88
105
  fetchByRailContentId,
89
106
  learningPathId,
90
- 'learning-path-v2',
107
+ COLLECTION_TYPE.LEARNING_PATH,
91
108
  {
92
- collection: { id: learningPathId, type: 'learning-path-v2' },
109
+ collection: { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH },
93
110
  dataField: 'children',
94
111
  dataField_includeParent: true,
95
112
  addProgressStatus: true,
@@ -241,7 +258,7 @@ export async function fetchLearningPathProgressCheckLessons(contentIds: number[]
241
258
  }
242
259
 
243
260
  interface completeMethodIntroVideo {
244
- intro_video_response: Object | null,
261
+ intro_video_response: SyncWriteDTO<ContentProgress, any> | null,
245
262
  active_path_response: ActiveLearningPathResponse
246
263
  }
247
264
  /**
@@ -267,9 +284,9 @@ export async function completeMethodIntroVideo(introVideoId: number, brand: stri
267
284
  }
268
285
 
269
286
  interface completeLearningPathIntroVideo {
270
- intro_video_response: Object | null,
271
- learning_path_reset_response: void | null,
272
- lesson_import_response: Object | null
287
+ intro_video_response: SyncWriteDTO<ContentProgress, any> | null,
288
+ learning_path_reset_response: SyncWriteDTO<ContentProgress, any> | null,
289
+ lesson_import_response: SyncWriteDTO<ContentProgress, any> | null
273
290
  }
274
291
  /**
275
292
  * Handles completion of learning path intro video and other related actions.
@@ -286,24 +303,20 @@ export async function completeLearningPathIntroVideo(introVideoId: number, learn
286
303
 
287
304
  response.intro_video_response = await completeIfNotCompleted(introVideoId)
288
305
 
289
- const collection = { id: learningPathId, type: 'learning-path-v2' }
306
+ const collection = { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH }
290
307
 
291
308
  if (!lessonsToImport) {
292
309
  response.learning_path_reset_response = await contentStatusReset(learningPathId, collection)
293
310
 
294
311
  } else {
295
- response.lesson_import_response = {}
296
- for (const contentId of lessonsToImport) {
297
- // todo: create bulk complete endpoint with bubbling. and set up watermelon method bubbling
298
- response.lesson_import_response[contentId] = await contentStatusCompleted(contentId, collection)
299
- }
312
+ response.lesson_import_response = await contentsStatusCompleted(lessonsToImport, collection)
300
313
  }
301
314
 
302
315
  return response
303
316
  }
304
317
 
305
318
 
306
- async function completeIfNotCompleted(contentId: number): Promise<Object | null> {
319
+ async function completeIfNotCompleted(contentId: number): Promise<SyncWriteDTO<ContentProgress, any> | null> {
307
320
  const introVideoStatus = await getProgressState(contentId)
308
321
 
309
322
  return introVideoStatus !== 'completed' ? await contentStatusCompleted(contentId) : null
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,6 +1,6 @@
1
- import { fetchHierarchy } from './sanity.js'
1
+ import { fetchHierarchy, fetchLearningPathHierarchy } from './sanity.js'
2
2
  import { db } from './sync'
3
- import { STATE } from './sync/models/ContentProgress'
3
+ import {COLLECTION_TYPE, STATE} from './sync/models/ContentProgress'
4
4
  import { trackUserPractice, findIncompleteLesson } from './userActivity'
5
5
  import { getNextLessonLessonParentTypes } from '../contentTypeConfig.js'
6
6
 
@@ -66,7 +66,7 @@ export async function getNavigateTo(data, collection = null) {
66
66
  let incompleteChild = findIncompleteLesson(childrenStates, lastInteracted, content.type)
67
67
  navigateToData[content.id] = buildNavigateTo(children.get(incompleteChild), null, collection)
68
68
  }
69
- } else if (['song-tutorial', 'guided-course', 'learning-path-v2'].includes(content.type)) { // send to first incomplete
69
+ } else if (['song-tutorial', 'guided-course', COLLECTION_TYPE.LEARNING_PATH].includes(content.type)) { // send to first incomplete
70
70
  let incompleteChild = findIncompleteLesson(childrenStates, lastInteracted, content.type)
71
71
  navigateToData[content.id] = buildNavigateTo(children.get(incompleteChild), null, collection)
72
72
  } else if (twoDepthContentTypes.includes(content.type)) { // send to navigateTo child of last interacted child
@@ -268,6 +268,11 @@ async function trackProgress(contentId, collection, currentSeconds, mediaLengthS
268
268
  export async function contentStatusCompleted(contentId, collection = null) {
269
269
  return setStartedOrCompletedStatus(contentId, collection, true)
270
270
  }
271
+
272
+ export async function contentsStatusCompleted(contentIds, collection = null) {
273
+ return setStartedOrCompletedStatuses(contentIds, collection, true)
274
+ }
275
+
271
276
  export async function contentStatusStarted(contentId, collection = null) {
272
277
  return setStartedOrCompletedStatus(contentId, collection, false)
273
278
  }
@@ -276,12 +281,12 @@ export async function contentStatusReset(contentId, collection = null) {
276
281
  }
277
282
 
278
283
  async function saveContentProgress(contentId, collection, progress, currentSeconds) {
279
- const response = await db.contentProgress.recordProgressRemotely(contentId, collection, progress, currentSeconds)
284
+ const response = await db.contentProgress.recordProgress(contentId, collection, progress, currentSeconds)
280
285
 
281
286
  // note - previous implementation explicitly did not trickle progress to children here
282
287
  // (only to siblings/parents via le bubbles)
283
288
 
284
- const bubbledProgresses = bubbleProgress(await fetchHierarchy(contentId), contentId, collection)
289
+ const bubbledProgresses = bubbleProgress(await getHierarchy(contentId, collection), contentId, collection)
285
290
  await db.contentProgress.recordProgressesTentative(bubbledProgresses, collection)
286
291
 
287
292
  return response
@@ -292,23 +297,55 @@ async function setStartedOrCompletedStatus(contentId, collection, isCompleted) {
292
297
  // we explicitly pessimistically await a remote push here
293
298
  // because awards may be generated (on server) on completion
294
299
  // which we would want to toast the user about *in band*
295
- const response = await db.contentProgress.recordProgressRemotely(contentId, collection, progress)
300
+ const response = await db.contentProgress.recordProgress(contentId, collection, progress)
301
+
302
+ const hierarchy = await getHierarchy(contentId, collection)
303
+
304
+ await Promise.all([
305
+ db.contentProgress.recordProgressesTentative(trickleProgress(hierarchy, contentId, collection, progress), collection),
306
+ bubbleProgress(hierarchy, contentId, collection).then(bubbledProgresses => db.contentProgress.recordProgressesTentative(bubbledProgresses, collection))
307
+ ])
296
308
 
297
- if (response.pushStatus === 'success') {
298
- const hierarchy = await fetchHierarchy(contentId)
309
+ return response
310
+ }
299
311
 
300
- await Promise.all([
301
- db.contentProgress.recordProgressesTentative(trickleProgress(hierarchy, contentId, collection, progress), collection),
302
- bubbleProgress(hierarchy, contentId, collection).then(bubbledProgresses => db.contentProgress.recordProgressesTentative(bubbledProgresses, collection))
303
- ])
312
+ async function getHierarchy(contentId, collection) {
313
+ if (collection && collection.type === COLLECTION_TYPE.LEARNING_PATH) {
314
+ return await fetchLearningPathHierarchy(contentId, collection)
315
+ } else {
316
+ return await fetchHierarchy(contentId)
304
317
  }
318
+ }
319
+
320
+ async function setStartedOrCompletedStatuses(contentIds, collection, isCompleted) {
321
+ const progress = isCompleted ? 100 : 0
322
+ // we explicitly pessimistically await a remote push here
323
+ // because awards may be generated (on server) on completion
324
+ // which we would want to toast the user about *in band*
325
+ const response = await db.contentProgress.recordProgresses(contentIds, collection, progress)
326
+
327
+ // we assume this is used only for contents within the same hierarchy
328
+ const hierarchy = await getHierarchy(contentIds[0], collection)
329
+
330
+ let ids = {}
331
+ for (const contentId of contentIds) {
332
+ ids = {
333
+ ...ids,
334
+ ...trickleProgress(hierarchy, contentId, collection, progress),
335
+ ...await bubbleProgress(hierarchy, contentId, collection)
336
+ }
337
+ }
338
+
339
+ await Promise.all([
340
+ db.contentProgress.recordProgressesTentative(ids, collection),
341
+ ]);
305
342
 
306
343
  return response
307
344
  }
308
345
 
309
346
  async function resetStatus(contentId, collection = null) {
310
347
  const response = await db.contentProgress.eraseProgress(contentId, collection)
311
- const hierarchy = await fetchHierarchy(contentId)
348
+ const hierarchy = await getHierarchy(contentId, collection)
312
349
 
313
350
  await Promise.all([
314
351
  db.contentProgress.recordProgressesTentative(trickleProgress(hierarchy, contentId, collection, 0), collection),
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -6,6 +6,7 @@ import { getActivePath, fetchLearningPathLessons } from '../content-org/learning
6
6
  import { getToday } from '../dateUtils.js'
7
7
  import { fetchMethodV2IntroVideo } from '../sanity'
8
8
  import { getProgressState } from '../contentProgress'
9
+ import {COLLECTION_TYPE} from "../sync/models/ContentProgress";
9
10
 
10
11
  export async function getMethodCard(brand) {
11
12
  const introVideo = await fetchMethodV2IntroVideo(brand)
@@ -82,7 +83,7 @@ export async function getMethodCard(brand) {
82
83
 
83
84
  return {
84
85
  id: 1,
85
- type: 'learning-path-v2',
86
+ type: COLLECTION_TYPE.LEARNING_PATH,
86
87
  progressType: 'method',
87
88
  header: 'Method',
88
89
  body: learningPath,
File without changes
File without changes
File without changes
File without changes
@@ -299,6 +299,7 @@ export async function fetchNewReleases(
299
299
  "image": thumbnail.asset->url,
300
300
  "thumbnail": thumbnail.asset->url,
301
301
  ${artistOrInstructorName()},
302
+ "instructor": ${instructorField},
302
303
  "artists": instructor[]->name,
303
304
  difficulty,
304
305
  difficulty_string,
@@ -337,6 +338,7 @@ export async function fetchUpcomingEvents(brand, { page = 1, limit = 10 } = {})
337
338
  "thumbnail": thumbnail.asset->url,
338
339
  ${artistOrInstructorName()},
339
340
  "artists": instructor[]->name,
341
+ "instructor": ${instructorField},
340
342
  difficulty,
341
343
  difficulty_string,
342
344
  length_in_seconds,
@@ -388,6 +390,7 @@ export async function fetchScheduledReleases(brand, { page = 1, limit = 10 }) {
388
390
  "image": thumbnail.asset->url,
389
391
  "thumbnail": thumbnail.asset->url,
390
392
  ${artistOrInstructorName()},
393
+ "instructor": ${instructorField},
391
394
  "artists": instructor[]->name,
392
395
  difficulty,
393
396
  difficulty_string,
@@ -1523,6 +1526,25 @@ export async function fetchTopLevelParentId(railcontentId) {
1523
1526
  return response['railcontent_id']
1524
1527
  }
1525
1528
 
1529
+ export async function fetchLearningPathHierarchy(railcontentId, collection) {
1530
+ if (!collection) {
1531
+ return null
1532
+ }
1533
+
1534
+ const topLevelId = collection.id
1535
+
1536
+ let response = await fetchByRailContentId(topLevelId, collection.type)
1537
+ if (!response) return null
1538
+
1539
+ let data = {
1540
+ topLevelId: topLevelId,
1541
+ parents: {},
1542
+ children: {},
1543
+ }
1544
+ populateHierarchyLookups(response, data, null)
1545
+ return data
1546
+ }
1547
+
1526
1548
  export async function fetchHierarchy(railcontentId) {
1527
1549
  let topLevelId = await fetchTopLevelParentId(railcontentId)
1528
1550
  const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
@@ -1557,12 +1579,14 @@ export async function fetchHierarchy(railcontentId) {
1557
1579
  }
1558
1580
 
1559
1581
  function populateHierarchyLookups(currentLevel, data, parentId) {
1560
- let contentId = currentLevel['railcontent_id']
1582
+ const railcontentIdField = currentLevel.railcontent_id ? "railcontent_id" : "id";
1583
+
1584
+ let contentId = currentLevel[railcontentIdField]
1561
1585
  let children = currentLevel['children']
1562
1586
 
1563
1587
  data.parents[contentId] = parentId
1564
1588
  if (children) {
1565
- data.children[contentId] = children.map((child) => child['railcontent_id'])
1589
+ data.children[contentId] = children.map((child) => child[railcontentIdField])
1566
1590
  for (let i = 0; i < children.length; i++) {
1567
1591
  populateHierarchyLookups(children[i], data, contentId)
1568
1592
  }
@@ -1572,7 +1596,7 @@ function populateHierarchyLookups(currentLevel, data, parentId) {
1572
1596
 
1573
1597
  let assignments = currentLevel['assignments']
1574
1598
  if (assignments) {
1575
- let assignmentIds = assignments.map((assignment) => assignment['railcontent_id'])
1599
+ let assignmentIds = assignments.map((assignment) => assignment[railcontentIdField])
1576
1600
  data.children[contentId] = (data.children[contentId] ?? []).concat(assignmentIds)
1577
1601
  assignmentIds.forEach((assignmentId) => {
1578
1602
  data.parents[assignmentId] = contentId
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes