musora-content-services 2.77.2 → 2.78.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
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.78.0](https://github.com/railroadmedia/musora-content-services/compare/v2.77.2...v2.78.0) (2025-11-15)
6
+
7
+
8
+ ### Features
9
+
10
+ * **BEH-1409:** complete intro video ([#566](https://github.com/railroadmedia/musora-content-services/issues/566)) ([fb2fa96](https://github.com/railroadmedia/musora-content-services/commit/fb2fa96f408948cf4c3501709dc2ec2c17f23163))
11
+
5
12
  ### [2.77.2](https://github.com/railroadmedia/musora-content-services/compare/v2.77.1...v2.77.2) (2025-11-14)
6
13
 
7
14
  ### [2.77.1](https://github.com/railroadmedia/musora-content-services/compare/v2.77.0...v2.77.1) (2025-11-13)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.77.2",
3
+ "version": "2.78.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.d.ts CHANGED
@@ -13,10 +13,12 @@ import {
13
13
  } from './services/content-org/guided-courses.ts';
14
14
 
15
15
  import {
16
+ completeLearningPathIntroVideo,
17
+ completeMethodIntroVideo,
16
18
  fetchLearningPathLessons,
17
19
  getActivePath,
18
20
  getDailySession,
19
- getLearningPath,
21
+ getEnrichedLearningPath,
20
22
  getLearningPathLessonsByIds,
21
23
  mapContentToParent,
22
24
  resetAllLearningPaths,
@@ -132,6 +134,7 @@ import {
132
134
  fetchThreads,
133
135
  followThread,
134
136
  lockThread,
137
+ markThreadAsRead,
135
138
  pinThread,
136
139
  unfollowThread,
137
140
  unlockThread,
@@ -406,6 +409,8 @@ declare module 'musora-content-services' {
406
409
  buildImageSRC,
407
410
  calculateLongestStreaks,
408
411
  closeComment,
412
+ completeLearningPathIntroVideo,
413
+ completeMethodIntroVideo,
409
414
  confirmEmailChange,
410
415
  contentStatusCompleted,
411
416
  contentStatusReset,
@@ -542,8 +547,8 @@ declare module 'musora-content-services' {
542
547
  getAwardDataForGuidedContent,
543
548
  getContentRows,
544
549
  getDailySession,
550
+ getEnrichedLearningPath,
545
551
  getLastInteractedOf,
546
- getLearningPath,
547
552
  getLearningPathLessonsByIds,
548
553
  getLegacyMethods,
549
554
  getLessonContentRows,
@@ -604,6 +609,7 @@ declare module 'musora-content-services' {
604
609
  markContentAsNotInterested,
605
610
  markNotificationAsRead,
606
611
  markNotificationAsUnread,
612
+ markThreadAsRead,
607
613
  numberOfActiveUsers,
608
614
  openComment,
609
615
  otherStats,
package/src/index.js CHANGED
@@ -13,10 +13,12 @@ import {
13
13
  } from './services/content-org/guided-courses.ts';
14
14
 
15
15
  import {
16
+ completeLearningPathIntroVideo,
17
+ completeMethodIntroVideo,
16
18
  fetchLearningPathLessons,
17
19
  getActivePath,
18
20
  getDailySession,
19
- getLearningPath,
21
+ getEnrichedLearningPath,
20
22
  getLearningPathLessonsByIds,
21
23
  mapContentToParent,
22
24
  resetAllLearningPaths,
@@ -132,6 +134,7 @@ import {
132
134
  fetchThreads,
133
135
  followThread,
134
136
  lockThread,
137
+ markThreadAsRead,
135
138
  pinThread,
136
139
  unfollowThread,
137
140
  unlockThread,
@@ -405,6 +408,8 @@ export {
405
408
  buildImageSRC,
406
409
  calculateLongestStreaks,
407
410
  closeComment,
411
+ completeLearningPathIntroVideo,
412
+ completeMethodIntroVideo,
408
413
  confirmEmailChange,
409
414
  contentStatusCompleted,
410
415
  contentStatusReset,
@@ -541,8 +546,8 @@ export {
541
546
  getAwardDataForGuidedContent,
542
547
  getContentRows,
543
548
  getDailySession,
549
+ getEnrichedLearningPath,
544
550
  getLastInteractedOf,
545
- getLearningPath,
546
551
  getLearningPathLessonsByIds,
547
552
  getLegacyMethods,
548
553
  getLessonContentRows,
@@ -603,6 +608,7 @@ export {
603
608
  markContentAsNotInterested,
604
609
  markNotificationAsRead,
605
610
  markNotificationAsUnread,
611
+ markThreadAsRead,
606
612
  numberOfActiveUsers,
607
613
  openComment,
608
614
  otherStats,
@@ -5,9 +5,25 @@
5
5
  import { fetchHandler } from '../railcontent.js'
6
6
  import { fetchByRailContentId, fetchByRailContentIds, fetchMethodV2Structure } from '../sanity.js'
7
7
  import { addContextToContent } from '../contentAggregator.js'
8
- import { getProgressStateByIds } from '../contentProgress.js'
8
+ import {
9
+ contentStatusCompleted,
10
+ contentStatusReset,
11
+ getProgressState,
12
+ getProgressStateByIds
13
+ } from '../contentProgress.js'
9
14
 
10
15
  const BASE_PATH: string = `/api/content-org`
16
+ const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
17
+
18
+ interface ActiveLearningPathResponse {
19
+ user_id: number,
20
+ brand: string,
21
+ active_learning_path_id: number,
22
+ }
23
+
24
+
25
+
26
+
11
27
 
12
28
  /**
13
29
  * Gets today's daily session for the user.
@@ -16,7 +32,7 @@ const BASE_PATH: string = `/api/content-org`
16
32
  */
17
33
  export async function getDailySession(brand: string, userDate: Date) {
18
34
  const stringDate = userDate.toISOString().split('T')[0]
19
- const url: string = `${BASE_PATH}/v1/user/learning-paths/daily-session/get-or-create`
35
+ const url: string = `${LEARNING_PATHS_PATH}/daily-session/get-or-create`
20
36
  const body = { brand: brand, userDate: stringDate }
21
37
  return await fetchHandler(url, 'POST', null, body)
22
38
  }
@@ -33,7 +49,7 @@ export async function updateDailySession(
33
49
  keepFirstLearningPath: boolean
34
50
  ) {
35
51
  const stringDate = userDate.toISOString().split('T')[0]
36
- const url: string = `${BASE_PATH}/v1/user/learning-paths/daily-session/update`
52
+ const url: string = `${LEARNING_PATHS_PATH}/daily-session/update`
37
53
  const body = { brand: brand, userDate: stringDate, keepFirstLearningPath: keepFirstLearningPath }
38
54
  return await fetchHandler(url, 'POST', null, body)
39
55
  }
@@ -43,17 +59,19 @@ export async function updateDailySession(
43
59
  * @param brand
44
60
  */
45
61
  export async function getActivePath(brand: string) {
46
- const url: string = `${BASE_PATH}/v1/user/learning-paths/active-path/get-or-create`
62
+ const url: string = `${LEARNING_PATHS_PATH}/active-path/get-or-create`
47
63
  const body = { brand: brand }
48
64
  return await fetchHandler(url, 'POST', null, body)
49
65
  }
50
66
 
67
+ // todo this should be removed once we handle active path gen only through
68
+ // finish method intro or complete current active path
51
69
  /**
52
70
  * Updates user's active learning path.
53
71
  * @param brand
54
72
  */
55
73
  export async function updateActivePath(brand: string) {
56
- const url: string = `${BASE_PATH}/v1/user/learning-paths/active-path/update`
74
+ const url: string = `${LEARNING_PATHS_PATH}/active-path/update`
57
75
  const body = { brand: brand }
58
76
  return await fetchHandler(url, 'POST', null, body)
59
77
  }
@@ -64,7 +82,7 @@ export async function updateActivePath(brand: string) {
64
82
  * @param learningPathId
65
83
  */
66
84
  export async function startLearningPath(brand: string, learningPathId: number) {
67
- const url: string = `${BASE_PATH}/v1/user/learning-paths/start`
85
+ const url: string = `${LEARNING_PATHS_PATH}/start`
68
86
  const body = { brand: brand, learning_path_id: learningPathId }
69
87
  return await fetchHandler(url, 'POST', null, body)
70
88
  }
@@ -73,7 +91,7 @@ export async function startLearningPath(brand: string, learningPathId: number) {
73
91
  * Resets the user's learning path.
74
92
  */
75
93
  export async function resetAllLearningPaths() {
76
- const url: string = `${BASE_PATH}/v1/user/learning-paths/reset`
94
+ const url: string = `${LEARNING_PATHS_PATH}/reset`
77
95
  return await fetchHandler(url, 'POST', null, {})
78
96
  }
79
97
 
@@ -82,8 +100,8 @@ export async function resetAllLearningPaths() {
82
100
  * @param {number} learningPathId - The learning path ID
83
101
  * @returns {Promise<Object>} Learning path with enriched lesson data
84
102
  */
85
- export async function getLearningPath(learningPathId) {
86
- //TODO: must be a cleaner way to do this
103
+ export async function getEnrichedLearningPath(learningPathId) {
104
+ //TODO BEH-1410: refactor/cleanup
87
105
  let learningPath = await fetchByRailContentId(learningPathId, 'learning-path-v2')
88
106
  learningPath.children = mapContentToParent(
89
107
  learningPath.children,
@@ -109,10 +127,16 @@ export async function getLearningPath(learningPathId) {
109
127
  export async function getLearningPathLessonsByIds(contentIds, learningPathId) {
110
128
  // It is more efficient to load the entire learning path than individual lessons
111
129
  // Also adds reliability check whether content is actually in the learning path
112
- const learningPath = await getLearningPath(learningPathId)
130
+ const learningPath = await getEnrichedLearningPath(learningPathId)
113
131
  return learningPath.children.filter((lesson) => contentIds.includes(lesson.id))
114
132
  }
115
133
 
134
+ /**
135
+ * Maps content to its parent learning path - fixes multi-parent problems for cta when lessons have a special collection.
136
+ * @param lessons
137
+ * @param parentContentType
138
+ * @param parentContentId
139
+ */
116
140
  export function mapContentToParent(lessons, parentContentType, parentContentId) {
117
141
  return lessons.map((lesson: any) => {
118
142
  return { ...lesson, type: parentContentType, parent_id: parentContentId }
@@ -142,7 +166,7 @@ export async function fetchLearningPathLessons(
142
166
  userDate: Date
143
167
  ) {
144
168
  const [learningPath, dailySession] = await Promise.all([
145
- getLearningPath(learningPathId),
169
+ getEnrichedLearningPath(learningPathId),
146
170
  getDailySession(brand, userDate),
147
171
  ])
148
172
 
@@ -203,3 +227,64 @@ export async function fetchLearningPathLessons(
203
227
  previous_learning_path_todays: previousLearningPathTodays,
204
228
  }
205
229
  }
230
+
231
+ interface completeMethodIntroVideo {
232
+ intro_video_response: Object | null,
233
+ active_path_response: ActiveLearningPathResponse
234
+ }
235
+ /**
236
+ * Handles completion of method intro video and other related actions.
237
+ * @param introVideoId - The intro video content ID.
238
+ * @param brand
239
+ * @returns {Promise<Array>} response - The response object.
240
+ * @returns {Promise<Object|null>} response.intro_video_response - The intro video completion response or null if already completed.
241
+ * @returns {Promise<Object>} response.active_path_response - The set active learning path response.
242
+ */
243
+ export async function completeMethodIntroVideo(introVideoId: number, brand: string): Promise<completeMethodIntroVideo> {
244
+ let response = {} as completeMethodIntroVideo
245
+
246
+ response.intro_video_response = await completeIfNotCompleted(introVideoId)
247
+
248
+ const url: string = `${LEARNING_PATHS_PATH}/start`
249
+ const body = { brand: brand }
250
+ response.active_path_response = await fetchHandler(url, 'POST', null, body)
251
+
252
+ return response
253
+ }
254
+
255
+ interface completeLearningPathIntroVideo {
256
+ intro_video_response: Object | null,
257
+ learning_path_reset_response: void | Object[] | Object
258
+ }
259
+ /**
260
+ * Handles completion of learning path intro video and other related actions.
261
+ * @param introVideoId
262
+ * @param learningPathId
263
+ * @param lessonsToImport
264
+ */
265
+ export async function completeLearningPathIntroVideo(introVideoId: number, learningPathId: number, lessonsToImport: number[] | null) {
266
+ let response = {} as completeLearningPathIntroVideo
267
+
268
+ response.intro_video_response = await completeIfNotCompleted(introVideoId)
269
+
270
+ if (!lessonsToImport) {
271
+ // reset progress within the learning path
272
+ response.learning_path_reset_response = await contentStatusReset(learningPathId)
273
+ } else {
274
+ response.learning_path_reset_response = null
275
+
276
+ // todo: add collection context + optimize with bulk calls with watermelon
277
+ for (const contentId of lessonsToImport) {
278
+ response.learning_path_reset_response[contentId] = await contentStatusCompleted(contentId)
279
+ }
280
+ }
281
+
282
+ return response
283
+ }
284
+
285
+
286
+ async function completeIfNotCompleted(contentId: number): Promise<Object | null> {
287
+ const introVideoStatus = await getProgressState(contentId)
288
+
289
+ return introVideoStatus !== 'completed' ? await contentStatusCompleted(contentId) : null
290
+ }
@@ -904,6 +904,7 @@ export async function fetchFoundation(slug) {
904
904
  * @param {string} slug - The slug of the method.
905
905
  * @returns {Promise<Object|null>} - The fetched methods data or null if not found.
906
906
  */
907
+ //todo BEH-1446 depreciated. remove all old method functions
907
908
  export async function fetchMethod(brand, slug) {
908
909
  const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
909
910