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 +7 -0
- package/package.json +1 -1
- package/src/services/contentAggregator.js +65 -65
- package/src/services/contentProgress.js +27 -18
- package/src/services/sanity.js +1 -1
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,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([
|
|
99
|
+
] = await Promise.all([
|
|
99
100
|
addProgressPercentage || addProgressStatus || addProgressTimestamp
|
|
100
|
-
? getProgressDataByIds(ids
|
|
101
|
-
addIsLiked ? isContentLikedByIds(ids
|
|
102
|
-
addResumeTimeSeconds ? getResumeTimeSecondsByIds(ids
|
|
103
|
-
addNavigateTo ? getNavigateTo(items
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
211
|
+
const itemRecordId = item.record_id || 0
|
|
212
|
+
delete item.record_id
|
|
213
|
+
|
|
214
|
+
return {
|
|
207
215
|
...item,
|
|
208
|
-
...(addProgressPercentage ? { progressPercentage: progressData?.[
|
|
209
|
-
...(addProgressStatus ? { progressStatus: progressData?.[
|
|
210
|
-
...(addProgressTimestamp ? { progressTimestamp: progressData?.[
|
|
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?.[
|
|
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,
|
|
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,
|
|
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
|
|
311
|
-
let items = []
|
|
306
|
+
function addRecordIdsToData(data, dataField, isDataAnArray, includeParent, includeIntroVideo) {
|
|
307
|
+
let items = []
|
|
308
|
+
let recordIds = []
|
|
312
309
|
|
|
313
|
-
const extractLearningPathItems = (
|
|
314
|
-
if (
|
|
315
|
-
const c = {type: COLLECTION_TYPE.LEARNING_PATH, 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
334
|
-
extractLearningPathItems(
|
|
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,
|
|
352
|
-
if (
|
|
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
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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].
|
|
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
|
|
47
|
-
if (!
|
|
46
|
+
for (const content of data) {
|
|
47
|
+
if (!content) continue
|
|
48
48
|
|
|
49
|
-
const {
|
|
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
|
|
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
|
|
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]
|
|
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
|
|
137
|
-
const lastInteracted = await getLastInteractedOf(childrenIds
|
|
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
|
|
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)
|
|
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
|
+
}
|
package/src/services/sanity.js
CHANGED
|
@@ -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
|
}
|