musora-content-services 2.21.4 → 2.22.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 +7 -0
- package/link_mcs.sh +0 -0
- package/package.json +1 -1
- package/src/contentTypeConfig.js +2 -1
- package/src/services/content.js +1 -1
- package/src/services/contentAggregator.js +89 -27
- package/src/services/contentProgress.js +10 -3
- package/src/services/imageSRCVerify.js +0 -0
- package/src/services/sanity.js +3 -3
- package/src/services/userActivity.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.22.0](https://github.com/railroadmedia/musora-content-services/compare/v2.21.4...v2.22.0) (2025-07-16)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **BEH-837:** update contentAggregator to process parent + children ([#346](https://github.com/railroadmedia/musora-content-services/issues/346)) ([20ffef3](https://github.com/railroadmedia/musora-content-services/commit/20ffef3826a7e56bee4c950d77b2cd57eff1dc08))
|
|
11
|
+
|
|
5
12
|
### [2.21.4](https://github.com/railroadmedia/musora-content-services/compare/v2.21.3...v2.21.4) (2025-07-15)
|
|
6
13
|
|
|
7
14
|
|
package/link_mcs.sh
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/src/contentTypeConfig.js
CHANGED
|
@@ -179,7 +179,7 @@ export const lessonTypesMapping = {
|
|
|
179
179
|
'jam tracks': ['jam-track'],
|
|
180
180
|
};
|
|
181
181
|
|
|
182
|
-
export const getNextLessonLessonParentTypes = ['course', 'guided-course', 'pack-bundle'];
|
|
182
|
+
export const getNextLessonLessonParentTypes = ['course', 'guided-course', 'pack', 'pack-bundle', 'song-tutorial'];
|
|
183
183
|
|
|
184
184
|
export const progressTypesMapping = {
|
|
185
185
|
'lesson': [...singleLessonTypes,...practiceAlongsLessonTypes, ...liveArchivesLessonTypes, ...performancesLessonTypes, ...studentArchivesLessonTypes, ...documentariesLessonTypes, 'live'],
|
|
@@ -450,6 +450,7 @@ export let contentTypeConfig = {
|
|
|
450
450
|
),
|
|
451
451
|
"children": child[]->{
|
|
452
452
|
"description": ${descriptionField},
|
|
453
|
+
"children": child[]->{"id": railcontent_id},
|
|
453
454
|
${getFieldsForContentType()}
|
|
454
455
|
},
|
|
455
456
|
${getFieldsForContentType()}
|
package/src/services/content.js
CHANGED
|
@@ -175,7 +175,7 @@ export async function getContentRows(brand, pageName, contentRowSlug = null, {
|
|
|
175
175
|
} = {}) {
|
|
176
176
|
const sanityData = await addContextToContent(fetchContentRows, brand, pageName, contentRowSlug, {
|
|
177
177
|
dataField: 'content',
|
|
178
|
-
|
|
178
|
+
dataField_parentIsArray: true,
|
|
179
179
|
addProgressStatus: true,
|
|
180
180
|
addProgressPercentage: true,
|
|
181
181
|
addNextLesson: true
|
|
@@ -8,6 +8,51 @@ import {
|
|
|
8
8
|
import {isContentLikedByIds} from "./contentLikes"
|
|
9
9
|
import {fetchLastInteractedChild, fetchLikeCount} from "./railcontent"
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Combine sanity data with BE contextual data.
|
|
13
|
+
*
|
|
14
|
+
* Supported dataStructures
|
|
15
|
+
* [{}, {}, {}] <- fetchRelatedLessons || on playback page (side bar)
|
|
16
|
+
* {} <- fetchLessonContent || on playback page (main window)
|
|
17
|
+
* in the examples below, dataField would be set to `children`
|
|
18
|
+
* [{id, children}, {id, children,}] <- getTabData || catalog Page
|
|
19
|
+
* {childen, } <- getPackData || pack index page
|
|
20
|
+
*
|
|
21
|
+
*
|
|
22
|
+
* @param dataPromise - promise or method that provides sanity data
|
|
23
|
+
* @param dataArgs - Arguments to pass to the dataPramise. The final parameter is expected to take the form of the options object
|
|
24
|
+
* @param options - Options has two categories of flags. two for defining the incoming data structure, and the rest of which data to add to the results. Unless otherwise specified the field flags use the format add<X> and add the <X> to the results
|
|
25
|
+
* @param options.dataField - the document field to process. (this is often 'children', 'entity', or 'lessons'
|
|
26
|
+
* @param options.dataField_includeParent - flag: if set with dataField, used to process the same contextual data for the parent object/array as well as the children
|
|
27
|
+
* @param options.addProgressPercentage - add progressPerecentage field
|
|
28
|
+
* @param options.addIsLiked - add isLikedField
|
|
29
|
+
* @param options.addLikeCount - add likeCount field
|
|
30
|
+
* @param options.addProgressStatus - add progressStatus field
|
|
31
|
+
* @param options.addResumeTimeSeconds - add resumeTimeSeconds field
|
|
32
|
+
* @param options.addLastInteractedChild - add lastInteractedChild field. This may be different from nextLesson
|
|
33
|
+
* @param options.addNextLesson - add nextLesson field. For collection type content. each collection has different logic for calculating this data
|
|
34
|
+
*
|
|
35
|
+
* @returns {Promise<{ data: Object[] } | false>} - A promise that resolves to the fetched content data + added data or `false` if no data is found.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* // GetLesson
|
|
39
|
+
* const response = await addContextToContent(fetchLessonContent, id, {
|
|
40
|
+
* addIsLiked: true,
|
|
41
|
+
* addProgressStatus: true,
|
|
42
|
+
* addLikeCount: true,
|
|
43
|
+
* addResumeTimeSeconds: true,
|
|
44
|
+
* })
|
|
45
|
+
*
|
|
46
|
+
* @example - addContextToContent retuns [{id, title, content}], so must be unpacked with dataField, and indicate the dataField_isParentArray
|
|
47
|
+
* const sanityData = await addContextToContent(fetchContentRows, brand, pageName, contentRowSlug, {
|
|
48
|
+
* dataField: 'content',
|
|
49
|
+
* dataField_parentIsArray: true,
|
|
50
|
+
* addProgressStatus: true,
|
|
51
|
+
* addProgressPercentage: true,
|
|
52
|
+
* addNextLesson: true
|
|
53
|
+
* })
|
|
54
|
+
*
|
|
55
|
+
*/
|
|
11
56
|
|
|
12
57
|
export async function addContextToContent(dataPromise, ...dataArgs)
|
|
13
58
|
{
|
|
@@ -16,7 +61,7 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
16
61
|
|
|
17
62
|
const {
|
|
18
63
|
dataField = null,
|
|
19
|
-
|
|
64
|
+
dataField_includeParent = false,
|
|
20
65
|
addProgressPercentage = false,
|
|
21
66
|
addIsLiked = false,
|
|
22
67
|
addLikeCount = false,
|
|
@@ -24,30 +69,14 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
24
69
|
addResumeTimeSeconds = false,
|
|
25
70
|
addLastInteractedChild = false,
|
|
26
71
|
addNextLesson = false,
|
|
27
|
-
addLastInteractedParent = false,
|
|
28
72
|
} = options
|
|
29
73
|
|
|
30
74
|
const dataParam = lastArg === options ? dataArgs.slice(0, -1) : dataArgs
|
|
31
75
|
|
|
32
|
-
|
|
76
|
+
let data = await dataPromise(...dataParam)
|
|
33
77
|
if(!data) return false
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (dataField && (data?.[dataField] || iterateDataFieldOnEachArrayElement)) {
|
|
38
|
-
if (iterateDataFieldOnEachArrayElement && Array.isArray(data)) {
|
|
39
|
-
for(const parent of data) {
|
|
40
|
-
items = [...items, ...parent[dataField]]
|
|
41
|
-
}
|
|
42
|
-
} else {
|
|
43
|
-
items = data[dataField]
|
|
44
|
-
}
|
|
45
|
-
} else if (Array.isArray(data)) {
|
|
46
|
-
items = data;
|
|
47
|
-
} else if (data?.id) {
|
|
48
|
-
items = [data]
|
|
49
|
-
}
|
|
50
|
-
|
|
78
|
+
const isDataAnArray = Array.isArray(data)
|
|
79
|
+
const items = extractItemsFromData(data, dataField, isDataAnArray, dataField_includeParent)
|
|
51
80
|
const ids = items.map(item => item?.id).filter(Boolean)
|
|
52
81
|
|
|
53
82
|
if(ids.length === 0) return false
|
|
@@ -58,7 +87,7 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
58
87
|
addIsLiked ? isContentLikedByIds(ids) : Promise.resolve(null),
|
|
59
88
|
addResumeTimeSeconds ? getResumeTimeSecondsByIds(ids) : Promise.resolve(null),
|
|
60
89
|
addLastInteractedChild ? fetchLastInteractedChild(ids) : Promise.resolve(null),
|
|
61
|
-
|
|
90
|
+
addNextLesson ? getNextLesson(items) : Promise.resolve(null),
|
|
62
91
|
])
|
|
63
92
|
|
|
64
93
|
const addContext = async (item) => ({
|
|
@@ -72,13 +101,41 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
72
101
|
...(addNextLesson ? { nextLesson: nextLessonData?.[item.id] } : {}),
|
|
73
102
|
})
|
|
74
103
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
104
|
+
return await processItems(data, addContext, dataField, isDataAnArray, dataField_includeParent)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function extractItemsFromData(data, dataField, isParentArray, includeParent)
|
|
108
|
+
{
|
|
109
|
+
let items = []
|
|
110
|
+
if (dataField) {
|
|
111
|
+
if (isParentArray) {
|
|
112
|
+
for (const parent of data) {
|
|
113
|
+
items = [...items, ...parent[dataField]]
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
items = data[dataField]
|
|
117
|
+
}
|
|
118
|
+
if (includeParent) {
|
|
119
|
+
if (isParentArray) {
|
|
120
|
+
for (const parent of data) {
|
|
121
|
+
items = [...items, ...parent]
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
items = [...items, data]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else if (Array.isArray(data)) {
|
|
128
|
+
items = data;
|
|
129
|
+
} else if (data?.id) {
|
|
130
|
+
items = [data]
|
|
78
131
|
}
|
|
132
|
+
return items
|
|
133
|
+
}
|
|
79
134
|
|
|
135
|
+
async function processItems(data, addContext, dataField, isParentArray, includeParent)
|
|
136
|
+
{
|
|
80
137
|
if (dataField) {
|
|
81
|
-
if (
|
|
138
|
+
if (isParentArray) {
|
|
82
139
|
for(let parent of data) {
|
|
83
140
|
parent[dataField] = Array.isArray(parent[dataField])
|
|
84
141
|
? await Promise.all(parent[dataField].map(addContext))
|
|
@@ -89,11 +146,16 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
89
146
|
? await Promise.all(data[dataField].map(addContext))
|
|
90
147
|
: await addContext(data[dataField])
|
|
91
148
|
}
|
|
149
|
+
if (includeParent) {
|
|
150
|
+
data = isParentArray
|
|
151
|
+
? await Promise.all(data.map(addContext))
|
|
152
|
+
: await addContext(data)
|
|
153
|
+
}
|
|
92
154
|
return data
|
|
93
155
|
} else {
|
|
94
156
|
return Array.isArray(data)
|
|
95
|
-
|
|
96
|
-
|
|
157
|
+
? await Promise.all(data.map(addContext))
|
|
158
|
+
: await addContext(data)
|
|
97
159
|
}
|
|
98
160
|
}
|
|
99
161
|
|
|
@@ -61,8 +61,6 @@ export async function getNextLesson(data)
|
|
|
61
61
|
nextLessonData[content.id] = children[0]
|
|
62
62
|
|
|
63
63
|
} else {
|
|
64
|
-
//if content in progress
|
|
65
|
-
|
|
66
64
|
const childrenStates = await getProgressStateByIds(children)
|
|
67
65
|
|
|
68
66
|
//calculate last_engaged
|
|
@@ -77,8 +75,17 @@ export async function getNextLesson(data)
|
|
|
77
75
|
nextLessonData[content.id] = findIncompleteLesson(childrenStates, lastInteracted, content.type)
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
} else if (content.type === 'guided-course') {
|
|
78
|
+
} else if (content.type === 'guided-course' || content.type === 'song-tutorial') {
|
|
81
79
|
nextLessonData[content.id] = findIncompleteLesson(childrenStates, lastInteracted, content.type)
|
|
80
|
+
} else if (content.type === 'pack') {
|
|
81
|
+
const packBundles = content.children ?? []
|
|
82
|
+
console.log('pack', content)
|
|
83
|
+
console.log('bundles', packBundles)
|
|
84
|
+
const packBundleProgressData = await getNextLesson(packBundles)
|
|
85
|
+
const parentId = await getLastInteractedOf(packBundles.map(bundle => bundle.id));
|
|
86
|
+
console.log('parentId', parentId)
|
|
87
|
+
console.log('bundleprogressData', packBundleProgressData)
|
|
88
|
+
nextLessonData[content.id] = packBundleProgressData[parentId];
|
|
82
89
|
}
|
|
83
90
|
}
|
|
84
91
|
}
|
|
File without changes
|
package/src/services/sanity.js
CHANGED
|
@@ -1979,9 +1979,9 @@ export async function fetchSanity(
|
|
|
1979
1979
|
return null
|
|
1980
1980
|
}
|
|
1981
1981
|
|
|
1982
|
-
if (globalConfig.sanityConfig.debug) {
|
|
1983
|
-
|
|
1984
|
-
}
|
|
1982
|
+
// if (globalConfig.sanityConfig.debug) {
|
|
1983
|
+
// console.log('fetchSanity Query:', query)
|
|
1984
|
+
// }
|
|
1985
1985
|
const perspective = globalConfig.sanityConfig.perspective ?? 'published'
|
|
1986
1986
|
const api = globalConfig.sanityConfig.useCachedAPI ? 'apicdn' : 'api'
|
|
1987
1987
|
const url = `https://${globalConfig.sanityConfig.projectId}.${api}.sanity.io/v${globalConfig.sanityConfig.version}/data/query/${globalConfig.sanityConfig.dataset}?perspective=${perspective}`
|
|
@@ -1074,7 +1074,7 @@ async function processContentItem(item) {
|
|
|
1074
1074
|
let ctaText = 'Continue';
|
|
1075
1075
|
if (contentType === 'transcription' || contentType === 'play-along' || contentType === 'jam-track') ctaText = 'Replay Song';
|
|
1076
1076
|
if (contentType === 'lesson') ctaText = status === 'completed' ? 'Revisit Lesson' : 'Continue';
|
|
1077
|
-
if ((contentType === 'song
|
|
1077
|
+
if ((contentType === 'song-tutorial' || collectionLessonTypes.includes(contentType)) && status === 'completed') ctaText = 'Revisit Lessons' ;
|
|
1078
1078
|
if (contentType === 'pack' && status === 'completed') {
|
|
1079
1079
|
ctaText = 'View Lessons';
|
|
1080
1080
|
}
|