musora-content-services 2.140.7 → 2.140.8

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.140.8](https://github.com/railroadmedia/musora-content-services/compare/v2.140.7...v2.140.8) (2026-03-23)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * fix enriching learning paths ([#879](https://github.com/railroadmedia/musora-content-services/issues/879)) ([793c4e9](https://github.com/railroadmedia/musora-content-services/commit/793c4e90a77ad03d9f293bc6576317f4391b746d))
11
+
5
12
  ### [2.140.7](https://github.com/railroadmedia/musora-content-services/compare/v2.140.6...v2.140.7) (2026-03-19)
6
13
 
7
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.140.7",
3
+ "version": "2.140.8",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,10 +1,10 @@
1
1
  import {
2
+ extractFromRecordId,
2
3
  generateRecordId,
3
4
  getNavigateTo,
4
5
  getNavigateToForMethod,
5
6
  getProgressDataByIds,
6
7
  getProgressDataByRecordIds,
7
- getProgressStateByRecordIds,
8
8
  getResumeTimeSecondsByIds,
9
9
  getResumeTimeSecondsByRecordIds,
10
10
  } from './contentProgress.js'
@@ -65,7 +65,6 @@ export async function addContextToContent(dataPromise, ...dataArgs) {
65
65
 
66
66
  // todo: merge addProgressData with addResumeTimeSeconds to one watermelon call
67
67
  const {
68
- collection = null, // this is needed for different collection types like learning paths. has .id and .type
69
68
  dataField = null,
70
69
  dataField_includeParent = false,
71
70
  addProgressPercentage = false,
@@ -78,6 +77,8 @@ export async function addContextToContent(dataPromise, ...dataArgs) {
78
77
  addAwards = false,
79
78
  } = options
80
79
 
80
+ let dataFields = dataField ? [dataField] : []
81
+
81
82
  const dataParam = lastArg === options ? dataArgs.slice(0, -1) : dataArgs
82
83
 
83
84
  let data = await dataPromise(...dataParam)
@@ -95,12 +96,12 @@ export async function addContextToContent(dataPromise, ...dataArgs) {
95
96
  resumeTimeData,
96
97
  navigateToData,
97
98
  awards,
98
- ] = await Promise.all([ //for now assume these all return `collection = {type, id}`. it will be so when watermelon here
99
+ ] = await Promise.all([
99
100
  addProgressPercentage || addProgressStatus || addProgressTimestamp
100
- ? getProgressDataByIds(ids, collection) : Promise.resolve(null),
101
- addIsLiked ? isContentLikedByIds(ids, collection) : Promise.resolve(null),
102
- addResumeTimeSeconds ? getResumeTimeSecondsByIds(ids, collection) : Promise.resolve(null),
103
- addNavigateTo ? getNavigateTo(items, collection) : Promise.resolve(null),
101
+ ? getProgressDataByIds(ids) : Promise.resolve(null),
102
+ addIsLiked ? isContentLikedByIds(ids) : Promise.resolve(null),
103
+ addResumeTimeSeconds ? getResumeTimeSecondsByIds(ids) : Promise.resolve(null),
104
+ addNavigateTo ? getNavigateTo(items) : Promise.resolve(null),
104
105
  addAwards ? getContentAwardsByIds(ids) : Promise.resolve(null),
105
106
  ])
106
107
 
@@ -116,7 +117,7 @@ export async function addContextToContent(dataPromise, ...dataArgs) {
116
117
  ...(addAwards ? { awards: awards?.[item.id].awards || [] } : {}),
117
118
  })
118
119
 
119
- return await processItems(data, addContext, dataField, isDataAnArray, dataField_includeParent)
120
+ return await processItems(data, addContext, dataFields, isDataAnArray, dataField_includeParent)
120
121
  }
121
122
 
122
123
  /**
@@ -172,6 +173,11 @@ export async function addContextToLearningPaths(dataPromise, ...dataArgs) {
172
173
  addAwards = false,
173
174
  } = options
174
175
 
176
+ let dataFields = dataField ? [dataField] : []
177
+ if (dataField_includeIntroVideo) {
178
+ dataFields.push('intro_video')
179
+ }
180
+
175
181
  const dataParam = lastArg === options ? dataArgs.slice(0, -1) : dataArgs
176
182
 
177
183
  let data = await dataPromise(...dataParam)
@@ -179,12 +185,11 @@ export async function addContextToLearningPaths(dataPromise, ...dataArgs) {
179
185
  if (isDataAnArray && data.length === 0) return data
180
186
  if (!data) return false
181
187
 
182
- let items = extractItemsWithCollectionFromMethodData(data, dataField, isDataAnArray, dataField_includeParent, dataField_includeIntroVideo) ?? []
188
+ let items, recordIds
189
+ [data, items, recordIds] = addRecordIdsToData(data, dataField, isDataAnArray, dataField_includeParent, dataField_includeIntroVideo) ?? []
183
190
  if (items.length === 0) return data
184
191
 
185
- const recordIds = items.map((item) => generateRecordId(item.content?.id, item.collection))
186
-
187
- const ids = items.map(item => item.content?.id)
192
+ const ids = recordIds.map(item => extractFromRecordId(item).contentId)
188
193
 
189
194
  const [
190
195
  progressData,
@@ -203,37 +208,28 @@ export async function addContextToLearningPaths(dataPromise, ...dataArgs) {
203
208
 
204
209
  const addContext = async (item) => {
205
210
  const itemId = item.id || 0
206
- const enrichedItem = {
211
+ const itemRecordId = item.record_id || 0
212
+ delete item.record_id
213
+
214
+ return {
207
215
  ...item,
208
- ...(addProgressPercentage ? { progressPercentage: progressData?.[itemId]?.progress } : {}),
209
- ...(addProgressStatus ? { progressStatus: progressData?.[itemId]?.status } : {}),
210
- ...(addProgressTimestamp ? { progressTimestamp: progressData?.[itemId]?.last_update } : {}),
216
+ ...(addProgressPercentage ? { progressPercentage: progressData?.[itemRecordId]?.progress } : {}),
217
+ ...(addProgressStatus ? { progressStatus: progressData?.[itemRecordId]?.status } : {}),
218
+ ...(addProgressTimestamp ? { progressTimestamp: progressData?.[itemRecordId]?.last_update } : {}),
211
219
  ...(addIsLiked ? { isLiked: isLikedData?.[itemId] } : {}),
212
220
  ...(addLikeCount && ids.length === 1 ? { likeCount: await fetchLikeCount(itemId) } : {}),
213
- ...(addResumeTimeSeconds ? { resumeTime: resumeTimeData?.[itemId] } : {}),
221
+ ...(addResumeTimeSeconds ? { resumeTime: resumeTimeData?.[itemRecordId] } : {}),
214
222
  ...(addNavigateTo ? { navigateTo: navigateToData?.[itemId] } : {}),
215
223
  ...(addAwards ? { awards: awards?.[itemId].awards || [] } : {}),
216
224
  }
217
-
218
- // Enrich intro_video if it exists and flag is set
219
- if (dataField_includeIntroVideo && item?.intro_video?.id) {
220
- enrichedItem.intro_video = {
221
- ...item.intro_video,
222
- ...(addProgressPercentage ? { progressPercentage: progressData?.[item.intro_video.id]?.progress } : {}),
223
- ...(addProgressStatus ? { progressStatus: progressData?.[item.intro_video.id]?.status } : {}),
224
- ...(addProgressTimestamp ? { progressTimestamp: progressData?.[item.intro_video.id]?.last_update } : {}),
225
- ...(addIsLiked ? { isLiked: isLikedData?.[item.intro_video.id] } : {}),
226
- ...(addResumeTimeSeconds ? { resumeTime: resumeTimeData?.[item.intro_video.id] } : {}),
227
- }
228
- }
229
-
230
- return enrichedItem
231
225
  }
232
226
 
233
- return await processItems(data, addContext, dataField, isDataAnArray, dataField_includeParent)
227
+ return await processItems(data, addContext, dataFields, isDataAnArray, dataField_includeParent)
234
228
  }
235
229
 
236
230
  export async function getNavigateToForPlaylists(data, { dataField = null } = {}) {
231
+ let dataFields = dataField ? [dataField] : []
232
+
237
233
  let playlists = extractItemsFromData(data, dataField, false, false)
238
234
 
239
235
  const allIds = [...new Set(playlists.flatMap(playlist => playlist.items.map(item => item.content_id)))]
@@ -263,7 +259,7 @@ export async function getNavigateToForPlaylists(data, { dataField = null } = {})
263
259
  }
264
260
  return playlist
265
261
  }
266
- return await processItems(data, addContext, dataField, false, false)
262
+ return await processItems(data, addContext, dataFields, false, false)
267
263
  }
268
264
 
269
265
  function extractItemsFromData(data, dataField, isParentArray, includeParent) {
@@ -307,21 +303,30 @@ function extractItemsFromData(data, dataField, isParentArray, includeParent) {
307
303
  return items
308
304
  }
309
305
 
310
- function extractItemsWithCollectionFromMethodData(data, dataField, isDataAnArray, includeParent, includeIntroVideo) {
311
- let items = [] // array of tuples {}
306
+ function addRecordIdsToData(data, dataField, isDataAnArray, includeParent, includeIntroVideo) {
307
+ let items = []
308
+ let recordIds = []
312
309
 
313
- const extractLearningPathItems = (item) => {
314
- if (item.type === COLLECTION_TYPE.LEARNING_PATH) {
315
- const c = {type: COLLECTION_TYPE.LEARNING_PATH, id: item.id}
310
+ const extractLearningPathItems = (content) => {
311
+ if (content.type === COLLECTION_TYPE.LEARNING_PATH) {
312
+ const c = {type: COLLECTION_TYPE.LEARNING_PATH, id: content.id}
316
313
 
317
314
  if (!dataField || (dataField && includeParent)) {
318
- items.push(...getDataTuple([item], c))
315
+ content.record_id = generateRecordId(content.id, c)
316
+ items.push(content)
317
+ recordIds.push(content.record_id)
319
318
  }
320
319
  if (includeIntroVideo) {
321
- items.push(...getDataTuple([item.intro_video], null))
320
+ content.intro_video.record_id = generateRecordId(content.intro_video.id, null)
321
+ items.push(content.intro_video)
322
+ recordIds.push(content.intro_video.record_id)
322
323
  }
323
324
  if (dataField) {
324
- items.push(...getDataTuple(item[dataField], c))
325
+ for (const child of content[dataField] ?? []) {
326
+ child.record_id = generateRecordId(child.id, c)
327
+ items.push(child)
328
+ recordIds.push(child.record_id)
329
+ }
325
330
  }
326
331
  } else { // is a lesson id, cant determine which collection it belongs to
327
332
  // do not add it as we cant determine collection
@@ -330,41 +335,36 @@ function extractItemsWithCollectionFromMethodData(data, dataField, isDataAnArray
330
335
  }
331
336
 
332
337
  if (isDataAnArray) {
333
- for (const item of data) {
334
- extractLearningPathItems(item)
338
+ for (const content of data) {
339
+ extractLearningPathItems(content)
335
340
  }
336
341
  } else {
337
342
  extractLearningPathItems(data)
338
343
  }
339
- return items
340
-
341
- function getDataTuple(data, collection) {
342
- const tuples = []
343
- for (const item of data) {
344
- const coll = collection || null
345
- tuples.push({content: item, collection: coll})
346
- }
347
- return tuples
348
- }
344
+ return [data, items, recordIds]
349
345
  }
350
346
 
351
- async function processItems(data, addContext, dataField, isParentArray, includeParent) {
352
- if (dataField) {
347
+ async function processItems(data, addContext, dataFields, isParentArray, includeParent) {
348
+ if (dataFields.length > 0) {
353
349
  if (isParentArray) {
354
350
  for (let parent of data) {
355
- const fieldValue = parent[dataField]
356
- if (Array.isArray(fieldValue)) {
357
- parent[dataField] = await Promise.all(fieldValue.map(addContext))
358
- } else if (fieldValue && typeof fieldValue === 'object') {
359
- parent[dataField] = await addContext(fieldValue)
351
+ for (const field of dataFields) {
352
+ const fieldValue = parent[field]
353
+ if (Array.isArray(fieldValue)) {
354
+ parent[field] = await Promise.all(fieldValue.map(addContext))
355
+ } else if (fieldValue && typeof fieldValue === 'object') {
356
+ parent[field] = await addContext(fieldValue)
357
+ }
360
358
  }
361
359
  }
362
360
  } else {
363
- const fieldValue = data[dataField]
364
- if (Array.isArray(fieldValue)) {
365
- data[dataField] = await Promise.all(fieldValue.map(addContext))
366
- } else if (fieldValue && typeof fieldValue === 'object') {
367
- data[dataField] = await addContext(fieldValue)
361
+ for (const field of dataFields) {
362
+ const fieldValue = data[field]
363
+ if (Array.isArray(fieldValue)) {
364
+ data[field] = await Promise.all(fieldValue.map(addContext))
365
+ } else if (fieldValue && typeof fieldValue === 'object') {
366
+ data[field] = await addContext(fieldValue)
367
+ }
368
368
  }
369
369
  }
370
370
  if (includeParent) {
@@ -38,15 +38,15 @@ export async function getResumeTimeSecondsByRecordIds(ids) {
38
38
  export async function getNavigateToForMethod(data) {
39
39
  let navigateToData = {}
40
40
 
41
- const brand = data[0].content.brand || null
41
+ const brand = data[0].brand || null
42
42
  const dailySessionResponse = await getDailySession(brand, new Date())
43
43
  const dailySession = dailySessionResponse?.daily_session || null
44
44
  const activeLearningPathId = dailySessionResponse?.active_learning_path_id || null
45
45
 
46
- for (const tuple of data) {
47
- if (!tuple) continue
46
+ for (const content of data) {
47
+ if (!content) continue
48
48
 
49
- const {content, collection} = tuple
49
+ const { _, collection } = extractFromRecordId(content.record_id)
50
50
 
51
51
  const findFirstIncomplete = (ids, progresses) =>
52
52
  ids.find(id => progresses.get(id) !== STATE_COMPLETED) || ids[0]
@@ -93,8 +93,7 @@ export async function getNavigateToForMethod(data) {
93
93
  return navigateToData
94
94
  }
95
95
 
96
- export async function getNavigateTo(data, collection = null) {
97
- collection = normalizeCollection(collection)
96
+ export async function getNavigateTo(data) {
98
97
  let navigateToData = {}
99
98
 
100
99
  const twoDepthContentTypes = ['course-collection'] // not adding method because it has its own logic (with active path)
@@ -122,19 +121,18 @@ export async function getNavigateTo(data, collection = null) {
122
121
  children.set(child.id, child)
123
122
  })
124
123
  // return first child (or grand child) if parent-content is complete or no progress
125
- const contentState = await getProgressState(content.id, collection)
124
+ const contentState = await getProgressState(content.id)
126
125
  if (contentState !== STATE_STARTED) {
127
126
  const firstChild = validChildren[0]
128
- let lastInteractedChildNavToData = await getNavigateTo([firstChild], collection)
127
+ let lastInteractedChildNavToData = await getNavigateTo([firstChild])
129
128
  lastInteractedChildNavToData = lastInteractedChildNavToData[firstChild.id] ?? null
130
129
  navigateToData[content.id] = buildNavigateTo(
131
130
  firstChild,
132
131
  lastInteractedChildNavToData,
133
- collection
134
132
  ) //no G-child for LP
135
133
  } else {
136
- const childrenStates = await getProgressStateByIds(childrenIds, collection)
137
- const lastInteracted = await getLastInteractedOf(childrenIds, collection)
134
+ const childrenStates = await getProgressStateByIds(childrenIds)
135
+ const lastInteracted = await getLastInteractedOf(childrenIds)
138
136
  const lastInteractedStatus = childrenStates.get(lastInteracted)
139
137
 
140
138
  if (['course', 'skill-pack', 'song-tutorial'].includes(content.type)) {
@@ -143,7 +141,6 @@ export async function getNavigateTo(data, collection = null) {
143
141
  navigateToData[content.id] = buildNavigateTo(
144
142
  children.get(lastInteracted),
145
143
  null,
146
- collection
147
144
  )
148
145
  } else {
149
146
  // send to first incomplete after last interacted
@@ -151,7 +148,6 @@ export async function getNavigateTo(data, collection = null) {
151
148
  navigateToData[content.id] = buildNavigateTo(
152
149
  children.get(incompleteChild),
153
150
  null,
154
- collection
155
151
  )
156
152
  }
157
153
  } else if (
@@ -162,24 +158,21 @@ export async function getNavigateTo(data, collection = null) {
162
158
  navigateToData[content.id] = buildNavigateTo(
163
159
  children.get(incompleteChild),
164
160
  null,
165
- collection
166
161
  )
167
162
  } else if (twoDepthContentTypes.includes(content.type)) {
168
163
  // send to navigateTo child of last interacted child
169
164
  const firstChildren = content.children ?? []
170
165
  const lastInteractedChildId = await getLastInteractedOf(
171
166
  firstChildren.map((child) => child.id),
172
- collection
173
167
  )
174
168
  if (childrenStates.get(lastInteractedChildId) === STATE_COMPLETED) {
175
169
  // TODO: course collections have an extra situation where we need to jump to the next course if all lessons in the last engaged course are completed
176
170
  }
177
- let lastInteractedChildNavToData = await getNavigateTo(firstChildren, collection)
171
+ let lastInteractedChildNavToData = await getNavigateTo(firstChildren)
178
172
  lastInteractedChildNavToData = lastInteractedChildNavToData[lastInteractedChildId]
179
173
  navigateToData[content.id] = buildNavigateTo(
180
174
  children.get(lastInteractedChildId),
181
175
  lastInteractedChildNavToData,
182
- collection
183
176
  )
184
177
  }
185
178
  }
@@ -863,9 +856,25 @@ export async function getIdsWhereLastAccessedFromMethod(contentIds) {
863
856
  }
864
857
 
865
858
  export function generateRecordId(contentId, collection) {
866
- if (!contentId) return null
859
+ if (!contentId) return null
867
860
 
868
861
  contentId = normalizeContentId(contentId)
869
862
 
870
863
  return `${contentId}:${collection?.type || COLLECTION_TYPE.SELF}:${collection?.id || COLLECTION_ID_SELF}`
871
864
  }
865
+
866
+ export function extractFromRecordId(recordId) {
867
+ if (!recordId) return null
868
+
869
+ const contentId = Number(recordId.split(':')[0])
870
+ const collectionType = recordId.split(':')[1]
871
+ const collectionId = Number(recordId.split(':')[2])
872
+
873
+ return {
874
+ contentId,
875
+ collection: {
876
+ type: collectionType || COLLECTION_TYPE.SELF,
877
+ id: collectionId || COLLECTION_ID_SELF
878
+ }
879
+ }
880
+ }
@@ -2528,5 +2528,5 @@ export function fetchParentChildRelationshipsFor(childIds, parentType) {
2528
2528
  railcontent_id,
2529
2529
  "children": child[@->railcontent_id in [${stringIds}]]->railcontent_id
2530
2530
  }`
2531
- return fetchSanity(query, true)
2531
+ return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
2532
2532
  }