musora-content-services 2.185.0 → 2.186.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/.agent/decisions/2026-10-01-agi-remove-limit-from-queries.md +60 -0
- package/.claude/settings.local.json +9 -14
- package/CHANGELOG.md +10 -0
- package/jest.config.js +0 -1
- package/package.json +1 -1
- package/src/index.d.ts +17 -2
- package/src/index.js +17 -2
- package/src/lib/ads/async-either.ts +148 -0
- package/src/lib/ads/coproduct.ts +36 -0
- package/src/lib/sanity/decorators/navigate-to.ts +20 -14
- package/src/lib/sanity/examples.ts +27 -31
- package/src/lib/sanity/filter.ts +62 -0
- package/src/lib/sanity/groq.ts +60 -19
- package/src/lib/sanity/runner.ts +3 -2
- package/src/lib/sanity/types/marketing.d.ts +33 -0
- package/src/services/content/artist.ts +4 -3
- package/src/services/content/genre.ts +4 -3
- package/src/services/content/instructor.ts +5 -4
- package/src/services/content.js +181 -191
- package/src/services/endScreen/endScreen.ts +1 -1
- package/src/services/marketing/promotions.ts +178 -0
- package/src/services/recommendations.ts +307 -0
- package/src/services/sanity.js +1 -1
- package/src/services/user/account.ts +23 -0
- package/src/version-info.js +1 -1
- package/.yarn/install-state.gz +0 -0
- package/dist/services/api/types.js +0 -2
- package/src/services/recommendations.js +0 -142
package/src/services/content.js
CHANGED
|
@@ -11,24 +11,29 @@ import {
|
|
|
11
11
|
fetchUpcomingEvents,
|
|
12
12
|
fetchScheduledReleases,
|
|
13
13
|
fetchReturning,
|
|
14
|
-
fetchLeaving,
|
|
14
|
+
fetchLeaving,
|
|
15
|
+
fetchScheduledAndNewReleases,
|
|
16
|
+
fetchContentRows,
|
|
17
|
+
fetchOwnedContent,
|
|
18
|
+
fetchCourseCollectionData,
|
|
15
19
|
} from './sanity.js'
|
|
16
|
-
import {TabResponseType, Tabs, capitalizeFirstLetter} from '../contentMetaData.js'
|
|
17
|
-
import {recommendations, rankCategories, rankItems} from
|
|
18
|
-
import {addContextToContent} from
|
|
19
|
-
import {getUserData} from
|
|
20
|
+
import { TabResponseType, Tabs, capitalizeFirstLetter } from '../contentMetaData.js'
|
|
21
|
+
import { recommendations, rankCategories, rankItems } from './recommendations'
|
|
22
|
+
import { addContextToContent } from './contentAggregator.js'
|
|
23
|
+
import { getUserData } from './user/management'
|
|
24
|
+
import { lessonTypesMapping, ownedContentTypes } from '../contentTypeConfig'
|
|
20
25
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
fetchUserPermissions,
|
|
27
|
+
getPermissionsAdapter,
|
|
28
|
+
isUserFreeTier,
|
|
29
|
+
doesUserHaveMembership,
|
|
30
|
+
} from './permissions/index.ts'
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
export async function getLessonContentRows (brand='drumeo', pageName = 'lessons') {
|
|
32
|
+
export async function getLessonContentRows(brand = 'drumeo', pageName = 'lessons') {
|
|
28
33
|
const [recentContentIds, rawContentRows, userData] = await Promise.all([
|
|
29
34
|
fetchRecent(brand, pageName, { progress: 'recent', limit: 10 }),
|
|
30
35
|
getContentRows(brand, pageName),
|
|
31
|
-
getUserData()
|
|
36
|
+
getUserData(),
|
|
32
37
|
])
|
|
33
38
|
|
|
34
39
|
const contentRows = Array.isArray(rawContentRows) ? rawContentRows : []
|
|
@@ -42,7 +47,7 @@ export async function getLessonContentRows (brand='drumeo', pageName = 'lessons'
|
|
|
42
47
|
contentRows.unshift({
|
|
43
48
|
id: 'owned',
|
|
44
49
|
title: 'Owned ' + capitalizeFirstLetter(pageName),
|
|
45
|
-
items: ownedContent.entity
|
|
50
|
+
items: ownedContent.entity,
|
|
46
51
|
})
|
|
47
52
|
}
|
|
48
53
|
}
|
|
@@ -51,12 +56,12 @@ export async function getLessonContentRows (brand='drumeo', pageName = 'lessons'
|
|
|
51
56
|
contentRows.unshift({
|
|
52
57
|
id: 'recent',
|
|
53
58
|
title: 'Recent ' + capitalizeFirstLetter(pageName),
|
|
54
|
-
items: recentContentIds || []
|
|
59
|
+
items: recentContentIds || [],
|
|
55
60
|
})
|
|
56
61
|
|
|
57
62
|
const results = await Promise.all(
|
|
58
63
|
contentRows.map(async (row) => {
|
|
59
|
-
return { id: row.id, title: row.title, items:
|
|
64
|
+
return { id: row.id, title: row.title, items: row.items }
|
|
60
65
|
})
|
|
61
66
|
)
|
|
62
67
|
|
|
@@ -85,50 +90,66 @@ export async function getLessonContentRows (brand='drumeo', pageName = 'lessons'
|
|
|
85
90
|
* .then(content => console.log(content))
|
|
86
91
|
* .catch(error => console.error(error));
|
|
87
92
|
*/
|
|
88
|
-
export async function getTabResults(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
selectedFilters = []
|
|
93
|
-
|
|
94
|
-
if (!tabName && ['lessons', 'songs'].includes(pageName))
|
|
93
|
+
export async function getTabResults(
|
|
94
|
+
brand,
|
|
95
|
+
pageName,
|
|
96
|
+
tabName,
|
|
97
|
+
{ page = 1, limit = 10, sort = 'recommended', selectedFilters = [] } = {}
|
|
98
|
+
) {
|
|
99
|
+
if (!tabName && ['lessons', 'songs'].includes(pageName))
|
|
100
|
+
return { type: TabResponseType.CATALOG, data: [], meta: { filters: [], sort: {} } }
|
|
95
101
|
|
|
96
102
|
const userPermissions = await fetchUserPermissions()
|
|
97
103
|
const permissions = userPermissions.permissions || []
|
|
98
104
|
const isFreeTier = isUserFreeTier(userPermissions)
|
|
99
105
|
|
|
100
106
|
// Extract and handle 'progress' filter separately
|
|
101
|
-
const progressFilter = selectedFilters.find(f => f.startsWith('progress,')) || 'progress,all'
|
|
102
|
-
const progressValue = progressFilter.split(',')[1].toLowerCase()
|
|
103
|
-
const filteredSelectedFilters = selectedFilters.filter(f => !f.startsWith('progress,'))
|
|
107
|
+
const progressFilter = selectedFilters.find((f) => f.startsWith('progress,')) || 'progress,all'
|
|
108
|
+
const progressValue = progressFilter.split(',')[1].toLowerCase()
|
|
109
|
+
const filteredSelectedFilters = selectedFilters.filter((f) => !f.startsWith('progress,'))
|
|
104
110
|
|
|
105
111
|
// Prepare included fields
|
|
106
112
|
const tabMatch = Object.values(Tabs).find(
|
|
107
|
-
tabObj => tabObj.name.toLowerCase() === tabName.toLowerCase()
|
|
113
|
+
(tabObj) => tabObj.name.toLowerCase() === tabName.toLowerCase()
|
|
108
114
|
)
|
|
109
115
|
const tabValue = tabMatch?.value || ''
|
|
110
116
|
const tabRecSysSection = tabMatch?.recSysSection || ''
|
|
111
|
-
const mergedIncludedFields = tabValue
|
|
117
|
+
const mergedIncludedFields = tabValue
|
|
118
|
+
? [...filteredSelectedFilters, tabValue]
|
|
119
|
+
: filteredSelectedFilters
|
|
112
120
|
|
|
113
121
|
// Fetch data
|
|
114
122
|
let results
|
|
115
|
-
if(
|
|
123
|
+
if (tabName === Tabs.ForYou.name) {
|
|
116
124
|
results = await addContextToContent(getLessonContentRows, brand, pageName, {
|
|
117
125
|
dataField: 'items',
|
|
118
126
|
addNextLesson: true,
|
|
119
127
|
addNavigateTo: true,
|
|
120
128
|
addProgressPercentage: true,
|
|
121
|
-
addProgressStatus: true
|
|
129
|
+
addProgressStatus: true,
|
|
122
130
|
})
|
|
123
|
-
} else if (
|
|
131
|
+
} else if (
|
|
132
|
+
sort === 'recommended' &&
|
|
133
|
+
tabName.toLowerCase() !== Tabs.ExploreAll.name.toLowerCase()
|
|
134
|
+
) {
|
|
124
135
|
const contentTypes = lessonTypesMapping[tabName.toLowerCase()] || []
|
|
125
|
-
const allRecommendations = await recommendations(brand, {
|
|
136
|
+
const allRecommendations = await recommendations(brand, {
|
|
137
|
+
contentTypes,
|
|
138
|
+
section: tabRecSysSection,
|
|
139
|
+
})
|
|
126
140
|
|
|
127
141
|
let contentToDisplay
|
|
128
142
|
if (allRecommendations.length > 0) {
|
|
129
143
|
// Fetch and sort recommended content
|
|
130
|
-
let recommendedContent = await fetchByRailContentIds(
|
|
131
|
-
|
|
144
|
+
let recommendedContent = await fetchByRailContentIds(
|
|
145
|
+
allRecommendations,
|
|
146
|
+
'tab-data',
|
|
147
|
+
brand,
|
|
148
|
+
true
|
|
149
|
+
)
|
|
150
|
+
recommendedContent.sort(
|
|
151
|
+
(a, b) => allRecommendations.indexOf(a.id) - allRecommendations.indexOf(b.id)
|
|
152
|
+
)
|
|
132
153
|
|
|
133
154
|
recommendedContent = filterCoursesInCourseCollections(recommendedContent)
|
|
134
155
|
|
|
@@ -148,16 +169,19 @@ export async function getTabResults(brand, pageName, tabName, {
|
|
|
148
169
|
sort: '-published_on',
|
|
149
170
|
includedFields: mergedIncludedFields,
|
|
150
171
|
progress: progressValue,
|
|
151
|
-
excludeIds: recommendedContent.map(c => c.id),
|
|
172
|
+
excludeIds: recommendedContent.map((c) => c.id),
|
|
152
173
|
sortPermissions: permissions,
|
|
153
174
|
})
|
|
154
175
|
|
|
155
176
|
// Filter out duplicates and combine
|
|
156
|
-
const recommendedIds = new Set(recommendedContent.map(c => c.id))
|
|
157
|
-
const additionalContent = tabData.entity.filter(c => !recommendedIds.has(c.id))
|
|
177
|
+
const recommendedIds = new Set(recommendedContent.map((c) => c.id))
|
|
178
|
+
const additionalContent = tabData.entity.filter((c) => !recommendedIds.has(c.id))
|
|
158
179
|
|
|
159
180
|
const recommendedContentToDisplay = recommendedContent.slice(start, end)
|
|
160
|
-
const additionalContentToDisplay = additionalContent.slice(
|
|
181
|
+
const additionalContentToDisplay = additionalContent.slice(
|
|
182
|
+
0,
|
|
183
|
+
limit - recommendedContentToDisplay.length
|
|
184
|
+
)
|
|
161
185
|
contentToDisplay = [...recommendedContentToDisplay, ...additionalContentToDisplay]
|
|
162
186
|
} else {
|
|
163
187
|
contentToDisplay = recommendedContent.slice(start, end)
|
|
@@ -179,69 +203,76 @@ export async function getTabResults(brand, pageName, tabName, {
|
|
|
179
203
|
addNextLesson: true,
|
|
180
204
|
addNavigateTo: true,
|
|
181
205
|
addProgressPercentage: true,
|
|
182
|
-
addProgressStatus: true
|
|
206
|
+
addProgressStatus: true,
|
|
183
207
|
})
|
|
184
208
|
} else {
|
|
185
|
-
let temp = await fetchTabData(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
progress: progressValue,
|
|
194
|
-
sortPermissions: permissions,
|
|
195
|
-
});
|
|
209
|
+
let temp = await fetchTabData(brand, pageName, {
|
|
210
|
+
page,
|
|
211
|
+
limit,
|
|
212
|
+
sort,
|
|
213
|
+
includedFields: mergedIncludedFields,
|
|
214
|
+
progress: progressValue,
|
|
215
|
+
sortPermissions: permissions,
|
|
216
|
+
})
|
|
196
217
|
const [ranking, contextResults] = await Promise.all([
|
|
197
|
-
sort === 'recommended'
|
|
218
|
+
sort === 'recommended'
|
|
219
|
+
? rankItems(
|
|
220
|
+
brand,
|
|
221
|
+
temp.entity.map((e) => e.id)
|
|
222
|
+
)
|
|
223
|
+
: [],
|
|
198
224
|
addContextToContent(() => temp.entity, {
|
|
199
225
|
addNextLesson: true,
|
|
200
226
|
addNavigateTo: true,
|
|
201
227
|
addProgressPercentage: true,
|
|
202
|
-
addProgressStatus: true
|
|
203
|
-
})
|
|
204
|
-
])
|
|
205
|
-
|
|
206
|
-
results =
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
228
|
+
addProgressStatus: true,
|
|
229
|
+
}),
|
|
230
|
+
])
|
|
231
|
+
|
|
232
|
+
results =
|
|
233
|
+
ranking.length === 0
|
|
234
|
+
? contextResults
|
|
235
|
+
: contextResults.sort((a, b) => {
|
|
236
|
+
const indexA = ranking.indexOf(a.id)
|
|
237
|
+
const indexB = ranking.indexOf(b.id)
|
|
238
|
+
return (indexA === -1 ? Infinity : indexA) - (indexB === -1 ? Infinity : indexB)
|
|
239
|
+
})
|
|
211
240
|
}
|
|
212
241
|
|
|
213
|
-
|
|
214
242
|
// Fetch metadata
|
|
215
|
-
const metaData = await fetchMetadata(brand, pageName, { skipTabFiltering: true })
|
|
243
|
+
const metaData = await fetchMetadata(brand, pageName, { skipTabFiltering: true })
|
|
216
244
|
|
|
217
245
|
// Process filters
|
|
218
|
-
const filters = (metaData.filters ?? []).map(filter => ({
|
|
246
|
+
const filters = (metaData.filters ?? []).map((filter) => ({
|
|
219
247
|
...filter,
|
|
220
|
-
items: filter.items.map(item => {
|
|
221
|
-
const value = item.value.split(',')[1]
|
|
248
|
+
items: filter.items.map((item) => {
|
|
249
|
+
const value = item.value.split(',')[1]
|
|
222
250
|
return {
|
|
223
251
|
...item,
|
|
224
|
-
selected:
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
252
|
+
selected:
|
|
253
|
+
selectedFilters.includes(`${filter.key},${value}`) ||
|
|
254
|
+
(filter.key === 'progress' &&
|
|
255
|
+
value === 'all' &&
|
|
256
|
+
!selectedFilters.some((f) => f.startsWith('progress,'))),
|
|
257
|
+
}
|
|
258
|
+
}),
|
|
259
|
+
}))
|
|
229
260
|
|
|
230
261
|
// Process sort options
|
|
231
262
|
const sortOptions = {
|
|
232
263
|
title: metaData.sort?.title ?? 'Sort By',
|
|
233
264
|
type: metaData.sort?.type ?? 'radio',
|
|
234
|
-
items: (metaData.sort?.items ?? []).map(option => ({
|
|
265
|
+
items: (metaData.sort?.items ?? []).map((option) => ({
|
|
235
266
|
...option,
|
|
236
|
-
selected: option.value === sort
|
|
237
|
-
}))
|
|
238
|
-
}
|
|
267
|
+
selected: option.value === sort,
|
|
268
|
+
})),
|
|
269
|
+
}
|
|
239
270
|
|
|
240
271
|
return {
|
|
241
272
|
type: tabName === Tabs.ForYou.name ? TabResponseType.SECTIONS : TabResponseType.CATALOG,
|
|
242
273
|
data: results,
|
|
243
|
-
meta: { filters, sort: sortOptions }
|
|
244
|
-
}
|
|
274
|
+
meta: { filters, sort: sortOptions },
|
|
275
|
+
}
|
|
245
276
|
}
|
|
246
277
|
|
|
247
278
|
/**
|
|
@@ -265,19 +296,24 @@ export async function getTabResults(brand, pageName, tabName, {
|
|
|
265
296
|
* .then(content => console.log(content))
|
|
266
297
|
* .catch(error => console.error(error));
|
|
267
298
|
*/
|
|
268
|
-
export async function getRecent(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
} = {}
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
const
|
|
299
|
+
export async function getRecent(
|
|
300
|
+
brand,
|
|
301
|
+
pageName,
|
|
302
|
+
tabName = 'all',
|
|
303
|
+
{ page = 1, limit = 10, sort = '-published_on' } = {}
|
|
304
|
+
) {
|
|
305
|
+
const progress = tabName.toLowerCase() == 'all' ? 'recent' : tabName.toLowerCase()
|
|
306
|
+
const recentContentIds = await fetchRecent(brand, pageName, {
|
|
307
|
+
page: page,
|
|
308
|
+
limit: limit,
|
|
309
|
+
progress: progress,
|
|
310
|
+
})
|
|
311
|
+
const metaData = await fetchMetadata(brand, 'recent')
|
|
276
312
|
return {
|
|
277
313
|
type: TabResponseType.CATALOG,
|
|
278
314
|
data: recentContentIds,
|
|
279
|
-
meta:
|
|
280
|
-
}
|
|
315
|
+
meta: { tabs: metaData.tabs },
|
|
316
|
+
}
|
|
281
317
|
}
|
|
282
318
|
|
|
283
319
|
/**
|
|
@@ -300,10 +336,12 @@ export async function getRecent(brand, pageName, tabName = 'all', {
|
|
|
300
336
|
* .then(content => console.log(content))
|
|
301
337
|
* .catch(error => console.error(error));
|
|
302
338
|
*/
|
|
303
|
-
export async function getContentRows(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
339
|
+
export async function getContentRows(
|
|
340
|
+
brand,
|
|
341
|
+
pageName,
|
|
342
|
+
contentRowSlug = null,
|
|
343
|
+
{ page = 1, limit = 10 } = {}
|
|
344
|
+
) {
|
|
307
345
|
const sanityData = await fetchContentRows(brand, pageName, contentRowSlug)
|
|
308
346
|
if (!sanityData) {
|
|
309
347
|
return []
|
|
@@ -312,7 +350,7 @@ export async function getContentRows(brand, pageName, contentRowSlug = null, {
|
|
|
312
350
|
let recData = {}
|
|
313
351
|
let slugNameMap = {}
|
|
314
352
|
for (const category of sanityData) {
|
|
315
|
-
recData[category.slug] = category.content.map(item => item.id)
|
|
353
|
+
recData[category.slug] = category.content.map((item) => item.id)
|
|
316
354
|
for (const content of category.content) {
|
|
317
355
|
contentMap[content.id] = content
|
|
318
356
|
}
|
|
@@ -324,18 +362,19 @@ export async function getContentRows(brand, pageName, contentRowSlug = null, {
|
|
|
324
362
|
const sortedData = await rankCategories(brand, recData)
|
|
325
363
|
let finalData = []
|
|
326
364
|
for (const category of sortedData) {
|
|
327
|
-
finalData.push(
|
|
365
|
+
finalData.push({
|
|
328
366
|
id: category.slug,
|
|
329
367
|
title: slugNameMap[category.slug],
|
|
330
|
-
items: category.items.slice(start, end).map(id => contentMap[id])
|
|
368
|
+
items: category.items.slice(start, end).map((id) => contentMap[id]),
|
|
369
|
+
})
|
|
331
370
|
}
|
|
332
371
|
|
|
333
|
-
return contentRowSlug
|
|
334
|
-
{
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
372
|
+
return contentRowSlug
|
|
373
|
+
? {
|
|
374
|
+
type: TabResponseType.CATALOG,
|
|
375
|
+
data: finalData[0].items,
|
|
376
|
+
meta: {},
|
|
377
|
+
}
|
|
339
378
|
: finalData
|
|
340
379
|
}
|
|
341
380
|
|
|
@@ -408,36 +447,40 @@ export async function getNewAndUpcoming(brand, { page = 1, limit = 10 } = {}) {
|
|
|
408
447
|
* .then(content => console.log(content))
|
|
409
448
|
* .catch(error => console.error(error));
|
|
410
449
|
*/
|
|
411
|
-
export async function getScheduleContentRows(
|
|
450
|
+
export async function getScheduleContentRows(
|
|
451
|
+
brand,
|
|
452
|
+
contentRowId = null,
|
|
453
|
+
{ page = 1, limit = 10 } = {}
|
|
454
|
+
) {
|
|
412
455
|
const sections = {
|
|
413
456
|
'New-Releases': {
|
|
414
457
|
title: 'New Releases',
|
|
415
|
-
fetchMethod: fetchNewReleases
|
|
458
|
+
fetchMethod: fetchNewReleases,
|
|
416
459
|
},
|
|
417
460
|
'Live-Streams': {
|
|
418
461
|
title: 'Live Streams',
|
|
419
|
-
fetchMethod: fetchUpcomingEvents
|
|
462
|
+
fetchMethod: fetchUpcomingEvents,
|
|
420
463
|
},
|
|
421
464
|
'Upcoming-Releases': {
|
|
422
465
|
title: 'Upcoming Releases',
|
|
423
|
-
fetchMethod: fetchScheduledReleases
|
|
466
|
+
fetchMethod: fetchScheduledReleases,
|
|
424
467
|
},
|
|
425
468
|
'Returning-Soon': {
|
|
426
469
|
title: 'Returning Soon',
|
|
427
|
-
fetchMethod: fetchReturning
|
|
470
|
+
fetchMethod: fetchReturning,
|
|
428
471
|
},
|
|
429
472
|
'Leaving-Soon': {
|
|
430
473
|
title: 'Leaving Soon',
|
|
431
|
-
fetchMethod: fetchLeaving
|
|
432
|
-
}
|
|
433
|
-
}
|
|
474
|
+
fetchMethod: fetchLeaving,
|
|
475
|
+
},
|
|
476
|
+
}
|
|
434
477
|
|
|
435
478
|
if (contentRowId) {
|
|
436
479
|
if (!sections[contentRowId]) {
|
|
437
|
-
return null
|
|
480
|
+
return null // Return null if the requested section does not exist
|
|
438
481
|
}
|
|
439
482
|
|
|
440
|
-
const items = await sections[contentRowId].fetchMethod(brand, { page, limit })
|
|
483
|
+
const items = await sections[contentRowId].fetchMethod(brand, { page, limit })
|
|
441
484
|
|
|
442
485
|
// Fetch only the requested section
|
|
443
486
|
const result = {
|
|
@@ -445,22 +488,24 @@ export async function getScheduleContentRows(brand, contentRowId = null, { page
|
|
|
445
488
|
title: sections[contentRowId].title,
|
|
446
489
|
// TODO: Remove content after FE/MA updates the existing code to use items
|
|
447
490
|
content: items,
|
|
448
|
-
items: items
|
|
449
|
-
}
|
|
491
|
+
items: items,
|
|
492
|
+
}
|
|
450
493
|
|
|
451
494
|
return {
|
|
452
495
|
type: TabResponseType.CATALOG,
|
|
453
496
|
data: result,
|
|
454
|
-
meta: {}
|
|
455
|
-
}
|
|
497
|
+
meta: {},
|
|
498
|
+
}
|
|
456
499
|
}
|
|
457
500
|
|
|
458
501
|
// If no specific contentRowId, fetch all sections
|
|
459
502
|
const results = await Promise.all(
|
|
460
503
|
Object.entries(sections).map(async ([id, section]) => {
|
|
461
504
|
// Apply special pagination rules
|
|
462
|
-
const isNewReleases = id === 'New-Releases'
|
|
463
|
-
const pagination = isNewReleases
|
|
505
|
+
const isNewReleases = id === 'New-Releases'
|
|
506
|
+
const pagination = isNewReleases
|
|
507
|
+
? { page: 1, limit: 30 }
|
|
508
|
+
: { page: 1, limit: Number.MAX_SAFE_INTEGER }
|
|
464
509
|
const items = await section.fetchMethod(brand, pagination)
|
|
465
510
|
|
|
466
511
|
const content = await addContextToContent(() => items, {
|
|
@@ -474,69 +519,18 @@ export async function getScheduleContentRows(brand, contentRowId = null, { page
|
|
|
474
519
|
title: section.title,
|
|
475
520
|
// TODO: Remove content after FE/MA updates the existing code to use items
|
|
476
521
|
content: content,
|
|
477
|
-
items: content
|
|
478
|
-
}
|
|
522
|
+
items: content,
|
|
523
|
+
}
|
|
479
524
|
})
|
|
480
|
-
)
|
|
525
|
+
)
|
|
481
526
|
|
|
482
527
|
return {
|
|
483
528
|
type: TabResponseType.SECTIONS,
|
|
484
529
|
data: results,
|
|
485
|
-
meta: {}
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Fetches recommended content for a given brand with pagination support.
|
|
491
|
-
*
|
|
492
|
-
* @param {string} brand - The brand for which to fetch recommended content.
|
|
493
|
-
* @param {Object} [params={}] - Pagination parameters.
|
|
494
|
-
* @param {number} [params.page=1] - The page number for pagination.
|
|
495
|
-
* @param {number} [params.limit=10] - The maximum number of recommended content items per page.
|
|
496
|
-
* @returns {Promise<Object>} - A promise that resolves to an object containing recommended content.
|
|
497
|
-
*
|
|
498
|
-
* @example
|
|
499
|
-
* // Fetch recommended content for a brand with default pagination
|
|
500
|
-
* getRecommendedForYou('drumeo')
|
|
501
|
-
* .then(content => console.log(content))
|
|
502
|
-
* .catch(error => console.error(error));
|
|
503
|
-
*
|
|
504
|
-
* @example
|
|
505
|
-
* // Fetch recommended content for a brand with custom pagination
|
|
506
|
-
* getRecommendedForYou('drumeo', { page: 2, limit: 5 })
|
|
507
|
-
* .then(content => console.log(content))
|
|
508
|
-
* .catch(error => console.error(error));
|
|
509
|
-
*/
|
|
510
|
-
export async function getRecommendedForYou(brand, rowId = null, {
|
|
511
|
-
page = 1,
|
|
512
|
-
limit = 10,
|
|
513
|
-
} = {}) {
|
|
514
|
-
const requiredItems = page * limit;
|
|
515
|
-
const data = await recommendations( brand, {limit: requiredItems})
|
|
516
|
-
const title = brand === 'playbass' ? "You Might Like" : "Recommended For You"
|
|
517
|
-
if (!data || !data.length) {
|
|
518
|
-
return { id: 'recommended', title: title, items: [] };
|
|
530
|
+
meta: {},
|
|
519
531
|
}
|
|
520
|
-
// Apply pagination before calling fetchByRailContentIds
|
|
521
|
-
const startIndex = (page - 1) * limit;
|
|
522
|
-
const paginatedData = data.slice(startIndex, startIndex + limit);
|
|
523
|
-
const contents = await addContextToContent(fetchByRailContentIds, paginatedData, 'tab-data', brand, true,
|
|
524
|
-
{
|
|
525
|
-
addNextLesson: true,
|
|
526
|
-
addNavigateTo: true,
|
|
527
|
-
})
|
|
528
|
-
if (rowId) {
|
|
529
|
-
return {
|
|
530
|
-
type: TabResponseType.CATALOG,
|
|
531
|
-
data: contents,
|
|
532
|
-
meta: {}
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
return { id: 'recommended', title: title, items: contents }
|
|
537
532
|
}
|
|
538
533
|
|
|
539
|
-
|
|
540
534
|
/**
|
|
541
535
|
* Fetches legacy methods for a given brand by permission.
|
|
542
536
|
*
|
|
@@ -549,19 +543,18 @@ export async function getRecommendedForYou(brand, rowId = null, {
|
|
|
549
543
|
* .then(content => console.log(content))
|
|
550
544
|
* .catch(error => console.error(error));
|
|
551
545
|
*/
|
|
552
|
-
export async function getLegacyMethods(brand)
|
|
553
|
-
{
|
|
546
|
+
export async function getLegacyMethods(brand) {
|
|
554
547
|
const brandMap = {
|
|
555
548
|
drumeo: [241247],
|
|
556
549
|
pianote: [
|
|
557
550
|
276693,
|
|
558
|
-
215952 //Foundations 2019
|
|
551
|
+
215952, //Foundations 2019
|
|
559
552
|
],
|
|
560
553
|
singeo: [308514],
|
|
561
554
|
guitareo: [333652],
|
|
562
555
|
}
|
|
563
|
-
const ids = brandMap[brand] ?? null
|
|
564
|
-
if (!ids) return []
|
|
556
|
+
const ids = brandMap[brand] ?? null
|
|
557
|
+
if (!ids) return []
|
|
565
558
|
const adapter = getPermissionsAdapter()
|
|
566
559
|
const userPermissionsData = await adapter.fetchUserPermissions()
|
|
567
560
|
const userPermissions = userPermissionsData.permissions
|
|
@@ -570,7 +563,7 @@ export async function getLegacyMethods(brand)
|
|
|
570
563
|
const hasMembership = doesUserHaveMembership(userPermissionsData)
|
|
571
564
|
const hasContentPermission = userPermissions.includes(100000000 + ids[0])
|
|
572
565
|
if (hasMembership && hasContentPermission) {
|
|
573
|
-
|
|
566
|
+
return Promise.all(ids.map((id) => fetchCourseCollectionData(id)))
|
|
574
567
|
} else {
|
|
575
568
|
return []
|
|
576
569
|
}
|
|
@@ -614,19 +607,17 @@ export async function getLegacyMethods(brand)
|
|
|
614
607
|
* .then(content => console.log(content))
|
|
615
608
|
* .catch(error => console.error(error));
|
|
616
609
|
*/
|
|
617
|
-
export async function getOwnedContent(
|
|
618
|
-
|
|
619
|
-
page = 1,
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
} = {}) {
|
|
623
|
-
const data = await fetchOwnedContent(brand, { type, page, limit, sort });
|
|
610
|
+
export async function getOwnedContent(
|
|
611
|
+
brand,
|
|
612
|
+
{ type = [], page = 1, limit = 10, sort = '-published_on' } = {}
|
|
613
|
+
) {
|
|
614
|
+
const data = await fetchOwnedContent(brand, { type, page, limit, sort })
|
|
624
615
|
|
|
625
616
|
if (!data) {
|
|
626
617
|
return {
|
|
627
618
|
entity: [],
|
|
628
|
-
total: 0
|
|
629
|
-
}
|
|
619
|
+
total: 0,
|
|
620
|
+
}
|
|
630
621
|
}
|
|
631
622
|
|
|
632
623
|
return await addContextToContent(() => data, {
|
|
@@ -634,20 +625,19 @@ export async function getOwnedContent(brand, {
|
|
|
634
625
|
addNavigateTo: true,
|
|
635
626
|
addProgressPercentage: true,
|
|
636
627
|
addProgressStatus: true,
|
|
637
|
-
})
|
|
628
|
+
})
|
|
638
629
|
}
|
|
639
630
|
|
|
640
631
|
export function filterCoursesInCourseCollections(data) {
|
|
641
|
-
return data.filter(c => !(c.type === 'course' && c.parent_id))
|
|
632
|
+
return data.filter((c) => !(c.type === 'course' && c.parent_id))
|
|
642
633
|
}
|
|
643
634
|
|
|
644
635
|
function applyPermissionsPostSort(contentList, permissionIds) {
|
|
645
636
|
contentList.sort((a, b) => {
|
|
646
|
-
const hasAccess = (item) =>
|
|
647
|
-
item.permission_id?.includes(permissionIds)
|
|
637
|
+
const hasAccess = (item) => item.permission_id?.includes(permissionIds)
|
|
648
638
|
|
|
649
|
-
return hasAccess(b) - hasAccess(a)
|
|
650
|
-
})
|
|
639
|
+
return hasAccess(b) - hasAccess(a)
|
|
640
|
+
})
|
|
651
641
|
|
|
652
|
-
return contentList
|
|
642
|
+
return contentList
|
|
653
643
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {fetchSimilarItems} from '../recommendations
|
|
1
|
+
import {fetchSimilarItems} from '../recommendations'
|
|
2
2
|
import {fetchByRailContentIds, fetchCourseCollectionData, fetchRelatedLessons} from '../sanity.js'
|
|
3
3
|
import { addContextToContent } from '../contentAggregator.js'
|
|
4
4
|
import { playAlongLessonTypes, jamTrackLessonTypes, lessonTypesMapping } from '../../contentTypeConfig.js'
|