musora-content-services 2.96.1 → 2.97.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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(rg:*)",
5
+ "Bash(npm run lint:*)"
6
+ ],
7
+ "deny": []
8
+ }
9
+ }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
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.97.0](https://github.com/railroadmedia/musora-content-services/compare/v2.96.2...v2.97.0) (2025-12-09)
6
+
7
+
8
+ ### Features
9
+
10
+ * **BEH-1458:** method addcontexttocontent ([#633](https://github.com/railroadmedia/musora-content-services/issues/633)) ([95a2b0a](https://github.com/railroadmedia/musora-content-services/commit/95a2b0ae3f011e17f92b78ad4fcd1ebe7a2342ca))
11
+
12
+ ### [2.96.2](https://github.com/railroadmedia/musora-content-services/compare/v2.96.1...v2.96.2) (2025-12-09)
13
+
5
14
  ### [2.96.1](https://github.com/railroadmedia/musora-content-services/compare/v2.96.0...v2.96.1) (2025-12-09)
6
15
 
7
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.96.1",
3
+ "version": "2.97.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
@@ -90,6 +90,7 @@ import {
90
90
 
91
91
  import {
92
92
  addContextToContent,
93
+ addContextToLearningPaths,
93
94
  getNavigateToForPlaylists
94
95
  } from './services/contentAggregator.js';
95
96
 
@@ -111,10 +112,13 @@ import {
111
112
  getAllStartedOrCompleted,
112
113
  getLastInteractedOf,
113
114
  getNavigateTo,
115
+ getNavigateToForMethod,
114
116
  getProgressDataByIds,
117
+ getProgressDataByIdsAndCollections,
115
118
  getProgressState,
116
119
  getProgressStateByIds,
117
120
  getResumeTimeSecondsByIds,
121
+ getResumeTimeSecondsByIdsAndCollections,
118
122
  getStartedOrCompletedProgressOnly,
119
123
  recordWatchSession
120
124
  } from './services/contentProgress.js';
@@ -267,7 +271,6 @@ import {
267
271
  fetchComingSoon,
268
272
  fetchCommentModContentData,
269
273
  fetchContentRows,
270
- fetchFoundation,
271
274
  fetchHierarchy,
272
275
  fetchLearningPathHierarchy,
273
276
  fetchLeaving,
@@ -275,15 +278,10 @@ import {
275
278
  fetchLessonsFeaturingThisContent,
276
279
  fetchLiveEvent,
277
280
  fetchMetadata,
278
- fetchMethod,
279
- fetchMethodChildren,
280
- fetchMethodChildrenIds,
281
- fetchMethodPreviousNextLesson,
282
281
  fetchMethodV2IntroVideo,
283
282
  fetchMethodV2Structure,
284
283
  fetchMethodV2StructureFromId,
285
284
  fetchNewReleases,
286
- fetchNextPreviousLesson,
287
285
  fetchOtherSongVersions,
288
286
  fetchOwnedContent,
289
287
  fetchPackAll,
@@ -432,6 +430,7 @@ import {
432
430
  declare module 'musora-content-services' {
433
431
  export {
434
432
  addContextToContent,
433
+ addContextToLearningPaths,
435
434
  addItemToPlaylist,
436
435
  applyCloudflareWrapper,
437
436
  applySanityTransformations,
@@ -504,7 +503,6 @@ declare module 'musora-content-services' {
504
503
  fetchEnrollmentPageMetadata,
505
504
  fetchFollowedThreads,
506
505
  fetchForumCategories,
507
- fetchFoundation,
508
506
  fetchGenreBySlug,
509
507
  fetchGenreLessons,
510
508
  fetchGenres,
@@ -527,16 +525,11 @@ declare module 'musora-content-services' {
527
525
  fetchLiveEventPollingState,
528
526
  fetchMemberships,
529
527
  fetchMetadata,
530
- fetchMethod,
531
- fetchMethodChildren,
532
- fetchMethodChildrenIds,
533
- fetchMethodPreviousNextLesson,
534
528
  fetchMethodV2IntroVideo,
535
529
  fetchMethodV2Structure,
536
530
  fetchMethodV2StructureFromId,
537
531
  fetchNewReleases,
538
532
  fetchNextContentDataForParent,
539
- fetchNextPreviousLesson,
540
533
  fetchNotificationSettings,
541
534
  fetchNotifications,
542
535
  fetchOtherSongVersions,
@@ -602,6 +595,7 @@ declare module 'musora-content-services' {
602
595
  getMethodCard,
603
596
  getMonday,
604
597
  getNavigateTo,
598
+ getNavigateToForMethod,
605
599
  getNavigateToForPlaylists,
606
600
  getNewAndUpcoming,
607
601
  getOnboardingRecommendedContent,
@@ -609,6 +603,7 @@ declare module 'musora-content-services' {
609
603
  getPracticeNotes,
610
604
  getPracticeSessions,
611
605
  getProgressDataByIds,
606
+ getProgressDataByIdsAndCollections,
612
607
  getProgressRows,
613
608
  getProgressState,
614
609
  getProgressStateByIds,
@@ -617,6 +612,7 @@ declare module 'musora-content-services' {
617
612
  getRecommendedForYou,
618
613
  getReportIssueOptions,
619
614
  getResumeTimeSecondsByIds,
615
+ getResumeTimeSecondsByIdsAndCollections,
620
616
  getSanityDate,
621
617
  getScheduleContentRows,
622
618
  getSortOrder,
package/src/index.js CHANGED
@@ -94,6 +94,7 @@ import {
94
94
 
95
95
  import {
96
96
  addContextToContent,
97
+ addContextToLearningPaths,
97
98
  getNavigateToForPlaylists
98
99
  } from './services/contentAggregator.js';
99
100
 
@@ -115,10 +116,13 @@ import {
115
116
  getAllStartedOrCompleted,
116
117
  getLastInteractedOf,
117
118
  getNavigateTo,
119
+ getNavigateToForMethod,
118
120
  getProgressDataByIds,
121
+ getProgressDataByIdsAndCollections,
119
122
  getProgressState,
120
123
  getProgressStateByIds,
121
124
  getResumeTimeSecondsByIds,
125
+ getResumeTimeSecondsByIdsAndCollections,
122
126
  getStartedOrCompletedProgressOnly,
123
127
  recordWatchSession
124
128
  } from './services/contentProgress.js';
@@ -271,7 +275,6 @@ import {
271
275
  fetchComingSoon,
272
276
  fetchCommentModContentData,
273
277
  fetchContentRows,
274
- fetchFoundation,
275
278
  fetchHierarchy,
276
279
  fetchLearningPathHierarchy,
277
280
  fetchLeaving,
@@ -279,15 +282,10 @@ import {
279
282
  fetchLessonsFeaturingThisContent,
280
283
  fetchLiveEvent,
281
284
  fetchMetadata,
282
- fetchMethod,
283
- fetchMethodChildren,
284
- fetchMethodChildrenIds,
285
- fetchMethodPreviousNextLesson,
286
285
  fetchMethodV2IntroVideo,
287
286
  fetchMethodV2Structure,
288
287
  fetchMethodV2StructureFromId,
289
288
  fetchNewReleases,
290
- fetchNextPreviousLesson,
291
289
  fetchOtherSongVersions,
292
290
  fetchOwnedContent,
293
291
  fetchPackAll,
@@ -431,6 +429,7 @@ import {
431
429
 
432
430
  export {
433
431
  addContextToContent,
432
+ addContextToLearningPaths,
434
433
  addItemToPlaylist,
435
434
  applyCloudflareWrapper,
436
435
  applySanityTransformations,
@@ -503,7 +502,6 @@ export {
503
502
  fetchEnrollmentPageMetadata,
504
503
  fetchFollowedThreads,
505
504
  fetchForumCategories,
506
- fetchFoundation,
507
505
  fetchGenreBySlug,
508
506
  fetchGenreLessons,
509
507
  fetchGenres,
@@ -526,16 +524,11 @@ export {
526
524
  fetchLiveEventPollingState,
527
525
  fetchMemberships,
528
526
  fetchMetadata,
529
- fetchMethod,
530
- fetchMethodChildren,
531
- fetchMethodChildrenIds,
532
- fetchMethodPreviousNextLesson,
533
527
  fetchMethodV2IntroVideo,
534
528
  fetchMethodV2Structure,
535
529
  fetchMethodV2StructureFromId,
536
530
  fetchNewReleases,
537
531
  fetchNextContentDataForParent,
538
- fetchNextPreviousLesson,
539
532
  fetchNotificationSettings,
540
533
  fetchNotifications,
541
534
  fetchOtherSongVersions,
@@ -601,6 +594,7 @@ export {
601
594
  getMethodCard,
602
595
  getMonday,
603
596
  getNavigateTo,
597
+ getNavigateToForMethod,
604
598
  getNavigateToForPlaylists,
605
599
  getNewAndUpcoming,
606
600
  getOnboardingRecommendedContent,
@@ -608,6 +602,7 @@ export {
608
602
  getPracticeNotes,
609
603
  getPracticeSessions,
610
604
  getProgressDataByIds,
605
+ getProgressDataByIdsAndCollections,
611
606
  getProgressRows,
612
607
  getProgressState,
613
608
  getProgressStateByIds,
@@ -616,6 +611,7 @@ export {
616
611
  getRecommendedForYou,
617
612
  getReportIssueOptions,
618
613
  getResumeTimeSecondsByIds,
614
+ getResumeTimeSecondsByIdsAndCollections,
619
615
  getSanityDate,
620
616
  getScheduleContentRows,
621
617
  getSortOrder,
@@ -15,6 +15,7 @@ import {
15
15
  import { COLLECTION_TYPE, STATE } from '../sync/models/ContentProgress'
16
16
  import { SyncWriteDTO } from '../sync'
17
17
  import { ContentProgress } from '../sync/models'
18
+ import { CollectionParameter } from '../sync/repositories/content-progress'
18
19
 
19
20
  const BASE_PATH: string = `/api/content-org`
20
21
  const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
@@ -39,6 +40,11 @@ interface DailySession {
39
40
  learning_path_id: number
40
41
  }
41
42
 
43
+ interface CollectionObject {
44
+ id: number
45
+ type: COLLECTION_TYPE.LEARNING_PATH
46
+ }
47
+
42
48
  /**
43
49
  * Gets today's daily session for the user.
44
50
  * @param brand
@@ -308,10 +314,11 @@ export async function completeLearningPathIntroVideo(
308
314
 
309
315
  response.intro_video_response = await completeIfNotCompleted(introVideoId)
310
316
 
311
- const collection = { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH }
317
+ const collection: CollectionObject = { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH }
312
318
 
313
319
  if (!lessonsToImport) {
314
- response.learning_path_reset_response = await contentStatusReset(learningPathId, collection)
320
+ response.learning_path_reset_response = await resetIfPossible(learningPathId, collection)
321
+
315
322
  } else {
316
323
  response.lesson_import_response = await contentsStatusCompleted(lessonsToImport, collection)
317
324
  }
@@ -327,6 +334,12 @@ async function completeIfNotCompleted(
327
334
  return introVideoStatus !== 'completed' ? await contentStatusCompleted(contentId) : null
328
335
  }
329
336
 
337
+ async function resetIfPossible(contentId: number, collection: CollectionParameter = null): Promise<SyncWriteDTO<ContentProgress, any> | null> {
338
+ const status = await getProgressState(contentId, collection)
339
+
340
+ return status !== '' ? await contentStatusReset(contentId, collection) : null
341
+ }
342
+
330
343
  export async function onContentCompletedLearningPathListener(event) {
331
344
  console.log('if')
332
345
  if (event?.collection?.type !== 'learning-path-v2') return
@@ -1,11 +1,15 @@
1
1
  import {
2
2
  getNavigateTo,
3
+ getNavigateToForMethod,
3
4
  getProgressDataByIds,
5
+ getProgressDataByIdsAndCollections,
4
6
  getProgressStateByIds,
5
7
  getResumeTimeSecondsByIds,
8
+ getResumeTimeSecondsByIdsAndCollections,
6
9
  } from './contentProgress'
7
10
  import { isContentLikedByIds } from './contentLikes'
8
11
  import { fetchLastInteractedChild, fetchLikeCount } from './railcontent'
12
+ import {COLLECTION_TYPE} from "./sync/models/ContentProgress";
9
13
 
10
14
  /**
11
15
  * Combine sanity data with BE contextual data.
@@ -54,12 +58,11 @@ import { fetchLastInteractedChild, fetchLikeCount } from './railcontent'
54
58
  *
55
59
  */
56
60
 
57
- // need to add method support.
58
- // this means returning collection_type and collection_id
59
61
  export async function addContextToContent(dataPromise, ...dataArgs) {
60
62
  const lastArg = dataArgs[dataArgs.length - 1]
61
63
  const options = typeof lastArg === 'object' && !Array.isArray(lastArg) ? lastArg : {}
62
64
 
65
+ // todo: merge addProgressData with addResumeTimeSeconds to one watermelon call
63
66
  const {
64
67
  collection = null, // this is needed for different collection types like learning paths. has .id and .type
65
68
  dataField = null,
@@ -115,6 +118,121 @@ export async function addContextToContent(dataPromise, ...dataArgs) {
115
118
  return await processItems(data, addContext, dataField, isDataAnArray, dataField_includeParent)
116
119
  }
117
120
 
121
+ /**
122
+ * Enriches method content (learning paths) with contextual data.
123
+ *
124
+ * Key behaviors:
125
+ * 1. Enriches all learning paths in a method structure
126
+ * 2. Auto-sets collection for learning-path-v2 items when no collection specified
127
+ * 3. Enriches intro videos when dataField_includeIntroVideo is true
128
+ *
129
+ * @param dataPromise - promise or method that provides sanity data
130
+ * @param dataArgs - Arguments to pass to the dataPromise
131
+ * @param options - Same as addContextToContent, plus:
132
+ * @param options.dataField_includeIntroVideo - If true, adds progress to intro_video field where it exists
133
+ *
134
+ * @returns {Promise<Object | false>} - Enriched data or false if no data found
135
+ *
136
+ * @example
137
+ * // Enrich method structure with all learning paths
138
+ * const method = await addContextToMethodContent(fetchMethodV2Structure, brand, {
139
+ * dataField: 'learningPaths',
140
+ * dataField_includeIntroVideo: true,
141
+ * addProgressStatus: true,
142
+ * addProgressPercentage: true,
143
+ * })
144
+ *
145
+ * @example
146
+ * // Enrich single learning path with intro video
147
+ * const lp = await addContextToMethodContent(fetchByRailContentId, lpId, 'learning-path-v2', {
148
+ * collection: { id: lpId, type: 'learning-path-v2' },
149
+ * dataField: 'children',
150
+ * dataField_includeParent: true,
151
+ * dataField_includeIntroVideo: true,
152
+ * addProgressStatus: true,
153
+ * })
154
+ */
155
+ export async function addContextToLearningPaths(dataPromise, ...dataArgs) {
156
+ const lastArg = dataArgs[dataArgs.length - 1]
157
+ const options = typeof lastArg === 'object' && !Array.isArray(lastArg) ? lastArg : {}
158
+
159
+ // todo: merge addProgressData with addResumeTimeSeconds to one watermelon call
160
+ const {
161
+ dataField = null,
162
+ dataField_includeParent = false,
163
+ dataField_includeIntroVideo = false,
164
+ addProgressPercentage = false,
165
+ addProgressStatus = false,
166
+ addProgressTimestamp = false,
167
+ addIsLiked = false,
168
+ addLikeCount = false,
169
+ addResumeTimeSeconds = false,
170
+ addNavigateTo = false,
171
+ } = options
172
+
173
+ const dataParam = lastArg === options ? dataArgs.slice(0, -1) : dataArgs
174
+
175
+ let data = await dataPromise(...dataParam)
176
+ const isDataAnArray = Array.isArray(data)
177
+ if (isDataAnArray && data.length === 0) return data
178
+ if (!data) return false
179
+
180
+ let items = extractItemsWithCollectionFromMethodData(data, dataField, isDataAnArray, dataField_includeParent, dataField_includeIntroVideo) ?? []
181
+ if (items.length === 0) return data
182
+
183
+ let ids = items.map((item) => (
184
+ {
185
+ contentId: item.content?.id,
186
+ collection: item.collection
187
+ })
188
+ ).filter(obj => obj.contentId)
189
+
190
+ const justIds = ids.map(obj => obj.contentId)
191
+
192
+ const [
193
+ progressData,
194
+ isLikedData,
195
+ resumeTimeData,
196
+ navigateToData,
197
+ ] = await Promise.all([
198
+ addProgressPercentage || addProgressStatus || addProgressTimestamp
199
+ ? getProgressDataByIdsAndCollections(ids) : Promise.resolve(null),
200
+ addIsLiked ? isContentLikedByIds(justIds) : Promise.resolve(null),
201
+ addResumeTimeSeconds ? getResumeTimeSecondsByIdsAndCollections(ids) : Promise.resolve(null),
202
+ addNavigateTo ? getNavigateToForMethod(items) : Promise.resolve(null),
203
+ ])
204
+
205
+ const addContext = async (item) => {
206
+ const itemId = item.id || 0
207
+ const enrichedItem = {
208
+ ...item,
209
+ ...(addProgressPercentage ? { progressPercentage: progressData?.[itemId]?.progress } : {}),
210
+ ...(addProgressStatus ? { progressStatus: progressData?.[itemId]?.status } : {}),
211
+ ...(addProgressTimestamp ? { progressTimestamp: progressData?.[itemId]?.last_update } : {}),
212
+ ...(addIsLiked ? { isLiked: isLikedData?.[itemId] } : {}),
213
+ ...(addLikeCount && ids.length === 1 ? { likeCount: await fetchLikeCount(itemId) } : {}),
214
+ ...(addResumeTimeSeconds ? { resumeTime: resumeTimeData?.[itemId] } : {}),
215
+ ...(addNavigateTo ? { navigateTo: navigateToData?.[itemId] } : {}),
216
+ }
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
+ }
232
+
233
+ return await processItems(data, addContext, dataField, isDataAnArray, dataField_includeParent)
234
+ }
235
+
118
236
  export async function getNavigateToForPlaylists(data, { dataField = null } = {}) {
119
237
  let playlists = extractItemsFromData(data, dataField, false, false)
120
238
  let allIds = []
@@ -191,6 +309,47 @@ function extractItemsFromData(data, dataField, isParentArray, includeParent) {
191
309
  return items
192
310
  }
193
311
 
312
+ function extractItemsWithCollectionFromMethodData(data, dataField, isDataAnArray, includeParent, includeIntroVideo) {
313
+ let items = [] // array of tuples {}
314
+
315
+ const extractLearningPathItems = (item) => {
316
+ if (item.type === COLLECTION_TYPE.LEARNING_PATH) {
317
+ const c = {type: COLLECTION_TYPE.LEARNING_PATH, id: item.id}
318
+
319
+ if (!dataField || (dataField && includeParent)) {
320
+ items.push(...getDataTuple([item], c))
321
+ }
322
+ if (includeIntroVideo) {
323
+ items.push(...getDataTuple([item.intro_video], null))
324
+ }
325
+ if (dataField) {
326
+ items.push(...getDataTuple(item[dataField], c))
327
+ }
328
+ } else { // is a lesson id, cant determine which collection it belongs to
329
+ // do not add it as we cant determine collection
330
+ // items.push(...getDataTuple([data], collection))
331
+ }
332
+ }
333
+
334
+ if (isDataAnArray) {
335
+ for (const item of data) {
336
+ extractLearningPathItems(item)
337
+ }
338
+ } else {
339
+ extractLearningPathItems(data)
340
+ }
341
+ return items
342
+
343
+ function getDataTuple(data, collection) {
344
+ const tuples = []
345
+ for (const item of data) {
346
+ const coll = collection || null
347
+ tuples.push({content: item, collection: coll})
348
+ }
349
+ return tuples
350
+ }
351
+ }
352
+
194
353
  async function processItems(data, addContext, dataField, isParentArray, includeParent) {
195
354
  if (dataField) {
196
355
  if (isParentArray) {
@@ -4,13 +4,15 @@ import { COLLECTION_TYPE, STATE } from './sync/models/ContentProgress'
4
4
  import { trackUserPractice, findIncompleteLesson } from './userActivity'
5
5
  import { getNextLessonLessonParentTypes } from '../contentTypeConfig.js'
6
6
  import { emitContentCompleted } from './progress-events'
7
+ import {getDailySession} from "./content-org/learning-paths.js";
8
+ import {getToday} from "./dateUtils.js";
7
9
 
8
10
  const STATE_STARTED = STATE.STARTED
9
11
  const STATE_COMPLETED = STATE.COMPLETED
10
12
  const MAX_DEPTH = 3
11
13
 
12
- export async function getProgressState(contentId) {
13
- return getById(contentId, 'state', '')
14
+ export async function getProgressState(contentId, collection = null) {
15
+ return getById(contentId, collection, 'state', '')
14
16
  }
15
17
 
16
18
  export async function getProgressStateByIds(contentIds, collection = null) {
@@ -26,6 +28,68 @@ export async function getResumeTimeSecondsByIds(contentIds, collection = null) {
26
28
  )
27
29
  }
28
30
 
31
+ export async function getResumeTimeSecondsByIdsAndCollections(tuples) {
32
+ return getByIdsAndCollections(tuples, 'resume_time_seconds', 0)
33
+ }
34
+
35
+ export async function getNavigateToForMethod(data) {
36
+ let navigateToData = {}
37
+
38
+ const brand = data[0].content.brand || null
39
+ const dailySessionResponse = await getDailySession(brand, getToday())
40
+ const dailySession = dailySessionResponse?.daily_session || null
41
+ const activeLearningPathId = dailySessionResponse?.active_learning_path_id || null
42
+
43
+ for (const tuple of data) {
44
+ if (!tuple) continue
45
+
46
+ const {content, collection} = tuple
47
+
48
+ const findFirstIncomplete = (progresses) =>
49
+ Object.keys(progresses).find(id => progresses[id] !== STATE_COMPLETED) || null
50
+
51
+ const findChildById = (children, id) =>
52
+ children?.find(child => child.id === Number(id)) || null
53
+
54
+ const getFirstOrIncompleteChild = async (content, collection) => {
55
+ const childrenIds = content?.children.map(child => child.id) || []
56
+ if (childrenIds.length === 0) return null
57
+
58
+ const progresses = await getProgressStateByIds(childrenIds, collection)
59
+ const incompleteId = findFirstIncomplete(progresses)
60
+
61
+ return incompleteId ? findChildById(content.children, incompleteId) : content.children[0]
62
+ }
63
+
64
+ const getDailySessionNavigateTo = async (content, dailySession, collection) => {
65
+ const dailiesIds = dailySession?.map(item => item.content_ids).flat() || []
66
+ const progresses = await getProgressStateByIds(dailiesIds, collection)
67
+ const incompleteId = findFirstIncomplete(progresses)
68
+
69
+ return incompleteId ? findChildById(content.children, incompleteId) : null
70
+ }
71
+
72
+ // does not support passing in 'method-v2' type yet
73
+ if (content.type === COLLECTION_TYPE.LEARNING_PATH) {
74
+ let navigateTo = null
75
+
76
+ if (content.id === activeLearningPathId) {
77
+ navigateTo = await getDailySessionNavigateTo(content, dailySession, collection)
78
+ }
79
+
80
+ if (!navigateTo) {
81
+ navigateTo = await getFirstOrIncompleteChild(content, collection)
82
+ }
83
+
84
+ navigateToData[content.id] =buildNavigateTo(navigateTo, null, collection)
85
+
86
+ } else {
87
+ navigateToData[content.id] = null
88
+ }
89
+ }
90
+ return navigateToData
91
+ }
92
+
29
93
  export async function getNavigateTo(data, collection = null) {
30
94
  collection = normalizeCollection(collection)
31
95
  let navigateToData = {}
@@ -178,10 +242,52 @@ export async function getProgressDataByIds(contentIds, collection) {
178
242
  return progress
179
243
  }
180
244
 
181
- async function getById(contentId, dataKey, defaultValue) {
245
+ /**
246
+ * Get progress data for multiple content IDs, each with their own collection context.
247
+ * Useful when fetching progress for tuples that belong to different collections.
248
+ *
249
+ * @param {Array<{contentId: number, collection: {type: string, id: number}|null}>} tuples - Array of objects with contentId and collection
250
+ * @returns {Promise<Object>} - Object mapping content IDs to progress data
251
+ *
252
+ * @example
253
+ * const tuples = [
254
+ * { contentId: 123, collection: { id: 456, type: 'learning-path-v2' } },
255
+ * { contentId: 789, collection: { id: 101, type: 'learning-path-v2' } },
256
+ * { contentId: 111, collection: null }
257
+ * ]
258
+ * const progress = await getProgressDataByIdsAndCollections(tuples)
259
+ * // Returns: { 123: { progress: 50, status: 'started', last_update: 123456 }, ... }
260
+ */
261
+
262
+ // todo: warning: this doesnt work with having 2 items with same contentId but different collection, because
263
+ // of the response structure here with contentId as key
264
+ export async function getProgressDataByIdsAndCollections(tuples) {
265
+ tuples = tuples.map(t => ({contentId: normalizeContentId(t.contentId), collection: normalizeCollection(t.collection)}))
266
+ const progress = Object.fromEntries(tuples.map(item => [item.contentId, {
267
+ last_update: 0,
268
+ progress: 0,
269
+ status: '',
270
+ collection: {},
271
+ }]))
272
+
273
+ await db.contentProgress.getSomeProgressByContentIdsAndCollection(tuples).then(r => {
274
+ r.data.forEach(p => {
275
+ progress[p.content_id] = {
276
+ last_update: p.updated_at,
277
+ progress: p.progress_percent,
278
+ status: p.state,
279
+ collection: (p.collection_type && p.collection_id) ? {type: p.collection_type, id: p.collection_id} : null
280
+ }
281
+ })
282
+ })
283
+
284
+ return progress
285
+ }
286
+
287
+ async function getById(contentId, collection, dataKey, defaultValue) {
182
288
  if (!contentId) return defaultValue
183
289
  return db.contentProgress
184
- .getOneProgressByContentId(contentId)
290
+ .getOneProgressByContentId(contentId, collection)
185
291
  .then((r) => r.data?.[dataKey] ?? defaultValue)
186
292
  }
187
293
 
@@ -197,6 +303,18 @@ async function getByIds(contentIds, collection, dataKey, defaultValue) {
197
303
  return progress
198
304
  }
199
305
 
306
+ async function getByIdsAndCollections(tuples, dataKey, defaultValue) {
307
+ tuples = tuples.map(t => ({contentId: normalizeContentId(t.contentId), collection: normalizeCollection(t.collection)}))
308
+ const progress = Object.fromEntries(tuples.map(tuple => [tuple.contentId, defaultValue]))
309
+
310
+ await db.contentProgress.getSomeProgressByContentIdsAndCollection(tuples).then(r => {
311
+ r.data.forEach(p => {
312
+ progress[p.content_id] = p[dataKey] ?? defaultValue
313
+ })
314
+ })
315
+ return progress
316
+ }
317
+
200
318
  export async function getAllStarted(limit = null) {
201
319
  return db.contentProgress.startedIds(limit).then((r) => r.data.map((id) => parseInt(id)))
202
320
  }
@@ -866,206 +866,6 @@ export async function fetchAllFilterOptions(
866
866
  return includeTabs ? { ...results, tabs, catalogName } : results
867
867
  }
868
868
 
869
- //Daniel Nov 14 2025 note - keeping this for when we migrate foundations to packs, so we know what fields to use.
870
- /**
871
- * Fetch the Foundations 2019.
872
- * @param {string} slug - The slug of the method.
873
- * @returns {Promise<Object|null>} - The fetched foundation data or null if not found.
874
- */
875
- export async function fetchFoundation(slug) {
876
- const filterParams = {}
877
- const query = await buildQuery(
878
- `_type == 'foundation' && slug.current == "${slug}"`,
879
- filterParams,
880
- getFieldsForContentType('foundation'),
881
- {
882
- sortOrder: 'published_on asc',
883
- isSingle: true,
884
- }
885
- )
886
- return fetchSanity(query, false)
887
- }
888
-
889
- /**
890
- * Fetch the Method (learning-paths) for a specific brand.
891
- * @param {string} brand - The brand for which to fetch methods.
892
- * @param {string} slug - The slug of the method.
893
- * @returns {Promise<Object|null>} - The fetched methods data or null if not found.
894
- */
895
- //todo BEH-1446 depreciated. remove all old method functions
896
- export async function fetchMethod(brand, slug) {
897
- const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
898
-
899
- const query = `*[_type == 'learning-path' && brand == "${brand}" && slug.current == "${slug}"] {
900
- "description": ${descriptionField},
901
- "instructors":instructor[]->name,
902
- published_on,
903
- "id": railcontent_id,
904
- railcontent_id,
905
- "slug": slug.current,
906
- status,
907
- title,
908
- video,
909
- length_in_seconds,
910
- parent_content_data,
911
- "breadcrumbs_data": parent_content_data[] {
912
- "id": id,
913
- "title": *[railcontent_id == ^.id][0].title,
914
- "url": *[railcontent_id == ^.id][0].web_url_path
915
- } | order(length(url)),
916
- "type": _type,
917
- "permission_id": permission_v2,
918
- "levels": child[${childrenFilter}]->
919
- {
920
- "id": railcontent_id,
921
- published_on,
922
- child_count,
923
- difficulty,
924
- difficulty_string,
925
- "thumbnail": thumbnail.asset->url,
926
- "instructor": instructor[]->{name},
927
- title,
928
- "type": _type,
929
- "description": ${descriptionField},
930
- "url": web_url_path,
931
- web_url_path,
932
- xp,
933
- total_xp
934
- }
935
- } | order(published_on asc)`
936
- return fetchSanity(query, false)
937
- }
938
-
939
- /**
940
- * Fetch the child courses for a specific method by Railcontent ID.
941
- * @param {string} railcontentId - The Railcontent ID of the current lesson.
942
- * @returns {Promise<Object|null>} - The fetched next lesson data or null if not found.
943
- */
944
- export async function fetchMethodChildren(railcontentId) {
945
- const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
946
-
947
- const query = `*[railcontent_id == ${railcontentId}]{
948
- "child_count":coalesce(count(child[${childrenFilter}]->), 0),
949
- "id": railcontent_id,
950
- "description": ${descriptionField},
951
- "thumbnail": thumbnail.asset->url,
952
- title,
953
- xp,
954
- total_xp,
955
- parent_content_data,
956
- "resources": ${resourcesField},
957
- "breadcrumbs_data": parent_content_data[] {
958
- "id": id,
959
- "title": *[railcontent_id == ^.id][0].title,
960
- "url": *[railcontent_id == ^.id][0].web_url_path
961
- } | order(length(url)),
962
- 'children': child[(${childrenFilter})]->{
963
- ${getFieldsForContentType('method')}
964
- },
965
- }[0..1]`
966
- return fetchSanity(query, true)
967
- }
968
-
969
- /**
970
- * Fetch the next lesson for a specific method by Railcontent ID.
971
- * @param {string} railcontentId - The Railcontent ID of the current lesson.
972
- * @param {string} methodId - The RailcontentID of the method
973
- * @returns {Promise<Object|null>} - object with `nextLesson` and `previousLesson` attributes
974
- * @example
975
- * fetchMethodPreviousNextLesson(241284, 241247)
976
- * .then(data => { console.log('nextLesson', data.nextLesson); console.log('prevlesson', data.prevLesson);})
977
- * .catch(error => console.error(error));
978
- */
979
- export async function fetchMethodPreviousNextLesson(railcontentId, methodId) {
980
- const sortedChildren = await fetchMethodChildrenIds(methodId)
981
- const index = sortedChildren.indexOf(Number(railcontentId))
982
- let nextId = sortedChildren[index + 1]
983
- let previousId = sortedChildren[index - 1]
984
- let ids = []
985
- if (nextId) ids.push(nextId)
986
- if (previousId) ids.push(previousId)
987
- let nextPrev = await fetchByRailContentIds(ids)
988
- const nextLesson = nextPrev.find((elem) => {
989
- return elem['id'] === nextId
990
- })
991
- const prevLesson = nextPrev.find((elem) => {
992
- return elem['id'] === previousId
993
- })
994
- return { nextLesson, prevLesson }
995
- }
996
-
997
- /**
998
- * Fetch all children of a specific method by Railcontent ID.
999
- * @param {string} railcontentId - The Railcontent ID of the method.
1000
- * @returns {Promise<Array<Object>|null>} - The fetched children data or null if not found.
1001
- */
1002
- export async function fetchMethodChildrenIds(railcontentId) {
1003
- const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
1004
-
1005
- const query = `*[ railcontent_id == ${railcontentId}]{
1006
- 'children': child[${childrenFilter}]-> {
1007
- 'id': railcontent_id,
1008
- 'type' : _type,
1009
- 'children': child[${childrenFilter}]-> {
1010
- 'id': railcontent_id,
1011
- 'type' : _type,
1012
- 'children': child[${childrenFilter}]-> {
1013
- 'id': railcontent_id,
1014
- 'type' : _type,
1015
- }
1016
- }
1017
- }
1018
- }`
1019
- let allChildren = await fetchSanity(query, false)
1020
- return getChildrenToDepth(allChildren, 4)
1021
- }
1022
-
1023
- function getChildrenToDepth(parent, depth = 1) {
1024
- let allChildrenIds = []
1025
- if (parent && parent['children'] && depth > 0) {
1026
- parent['children'].forEach((child) => {
1027
- if (!child['children']) {
1028
- allChildrenIds.push(child['id'])
1029
- }
1030
- allChildrenIds = allChildrenIds.concat(getChildrenToDepth(child, depth - 1))
1031
- })
1032
- }
1033
- return allChildrenIds
1034
- }
1035
-
1036
- /**
1037
- * Fetch the next and previous lessons for a specific lesson by Railcontent ID.
1038
- * @param {string} railcontentId - The Railcontent ID of the current lesson.
1039
- * @returns {Promise<Object|null>} - The fetched next and previous lesson data or null if found.
1040
- */
1041
- export async function fetchNextPreviousLesson(railcontentId) {
1042
- const document = await fetchLessonContent(railcontentId)
1043
- if (document.parent_content_data && document.parent_content_data.length > 0) {
1044
- const lastElement = document.parent_content_data[document.parent_content_data.length - 1]
1045
- const results = await fetchMethodPreviousNextLesson(railcontentId, lastElement.id)
1046
- return results
1047
- }
1048
- const processedData = processMetadata(document.brand, document.type, true)
1049
- let sortBy = processedData?.sortBy ?? 'published_on'
1050
- const isDesc = sortBy.startsWith('-')
1051
- sortBy = isDesc ? sortBy.substring(1) : sortBy
1052
- let sortValue = document[sortBy]
1053
- if (sortValue == null) {
1054
- sortBy = 'railcontent_id'
1055
- sortValue = document['railcontent_id']
1056
- }
1057
- const isNumeric = !isNaN(sortValue)
1058
- let prevComparison = isNumeric ? `${sortBy} <= ${sortValue}` : `${sortBy} <= "${sortValue}"`
1059
- let nextComparison = isNumeric ? `${sortBy} >= ${sortValue}` : `${sortBy} >= "${sortValue}"`
1060
- const fields = getFieldsForContentType(document.type)
1061
- const query = `{
1062
- "prevLesson": *[brand == "${document.brand}" && status == "${document.status}" && _type == "${document.type}" && ${prevComparison} && railcontent_id != ${railcontentId}] | order(${sortBy} desc){${fields}}[0...1][0],
1063
- "nextLesson": *[brand == "${document.brand}" && status == "${document.status}" && _type == "${document.type}" && ${nextComparison} && railcontent_id != ${railcontentId}] | order(${sortBy} asc){${fields}}[0...1][0]
1064
- }`
1065
-
1066
- return await fetchSanity(query, true)
1067
- }
1068
-
1069
869
  /**
1070
870
  * Fetch the next piece of content under a parent by Railcontent ID
1071
871
  * @param {int} railcontentId - The Railcontent ID of the parent content
@@ -2200,8 +2000,11 @@ export async function fetchMethodV2Structure(brand) {
2200
2000
  const _type = 'method-v2'
2201
2001
  const query = `*[_type == '${_type}' && brand == '${brand}'][0...1]{
2202
2002
  'sanity_id': _id,
2003
+ brand,
2004
+ 'intro_video_id': intro_video->railcontent_id,
2203
2005
  'learning_paths': child[]->{
2204
2006
  'id': railcontent_id,
2007
+ 'intro_video_id': intro_video->railcontent_id,
2205
2008
  'children': child[]->railcontent_id
2206
2009
  }
2207
2010
  }`
@@ -2217,8 +2020,11 @@ export async function fetchMethodV2StructureFromId(contentId) {
2217
2020
  const _type = "method-v2";
2218
2021
  const query = `*[_type == '${_type}' && brand == *[railcontent_id == ${contentId}][0].brand][0...1]{
2219
2022
  'sanity_id': _id,
2023
+ brand,
2024
+ 'intro_video_id': intro_video->railcontent_id,
2220
2025
  'learning_paths': child[]->{
2221
2026
  'id': railcontent_id,
2027
+ 'intro_video_id': intro_video->railcontent_id,
2222
2028
  'children': child[]->railcontent_id
2223
2029
  }
2224
2030
  }`
@@ -1,6 +1,15 @@
1
- import SyncRepository, { Q } from './base'
2
- import ContentProgress, { COLLECTION_TYPE, COLLECTION_ID_SELF, STATE } from '../models/ContentProgress'
1
+ import SyncRepository, {Q} from './base'
2
+ import ContentProgress, {COLLECTION_ID_SELF, COLLECTION_TYPE, STATE} from '../models/ContentProgress'
3
3
 
4
+ interface ContentIdCollectionTuple {
5
+ contentId: number,
6
+ collection: CollectionParameter | null,
7
+ }
8
+
9
+ export interface CollectionParameter {
10
+ type: COLLECTION_TYPE,
11
+ id: number,
12
+ }
4
13
  export default class ProgressRepository extends SyncRepository<ContentProgress> {
5
14
  // null collection only
6
15
  async startedIds(limit?: number) {
@@ -77,7 +86,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
77
86
  return clauses
78
87
  }
79
88
 
80
- async mostRecentlyUpdatedId(contentIds: number[], collection: { type: COLLECTION_TYPE; id: number } | null = null) {
89
+ async mostRecentlyUpdatedId(contentIds: number[], collection: CollectionParameter | null = null) {
81
90
  return this.queryOneId(
82
91
  Q.where('content_id', Q.oneOf(contentIds)),
83
92
  Q.where('collection_type', collection?.type ?? COLLECTION_TYPE.SELF),
@@ -89,7 +98,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
89
98
 
90
99
  async getOneProgressByContentId(
91
100
  contentId: number,
92
- { collection }: { collection?: { type: COLLECTION_TYPE; id: number } | null } = {}
101
+ collection: CollectionParameter | null = null
93
102
  ) {
94
103
  const clauses = [
95
104
  Q.where('content_id', contentId),
@@ -102,7 +111,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
102
111
 
103
112
  async getSomeProgressByContentIds(
104
113
  contentIds: number[],
105
- collection: { type: COLLECTION_TYPE; id: number } | null = null
114
+ collection: CollectionParameter | null = null
106
115
  ) {
107
116
  const clauses = [
108
117
  Q.where('content_id', Q.oneOf(contentIds)),
@@ -113,7 +122,23 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
113
122
  return await this.queryAll(...clauses)
114
123
  }
115
124
 
116
- recordProgress(contentId: number, collection: { type: COLLECTION_TYPE; id: number } | null, progressPct: number, resumeTime?: number) {
125
+ async getSomeProgressByContentIdsAndCollection(tuples: ContentIdCollectionTuple[]) {
126
+ const clauses = []
127
+
128
+ clauses.push(...tuples.map(tuple => Q.and(...tupleClauses(tuple))))
129
+
130
+ return await this.queryAll(Q.or(...clauses))
131
+
132
+ function tupleClauses(tuple: ContentIdCollectionTuple) {
133
+ return [
134
+ Q.where('content_id', tuple.contentId),
135
+ Q.where('collection_type', tuple.collection?.type ?? COLLECTION_TYPE.SELF),
136
+ Q.where('collection_id', tuple.collection?.id ?? COLLECTION_ID_SELF)
137
+ ]
138
+ }
139
+ }
140
+
141
+ recordProgress(contentId: number, collection: CollectionParameter | null, progressPct: number, resumeTime?: number) {
117
142
  const id = ProgressRepository.generateId(contentId, collection)
118
143
 
119
144
  const result = this.upsertOne(id, (r) => {
@@ -156,7 +181,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
156
181
 
157
182
  recordProgresses(
158
183
  contentIds: number[],
159
- collection: { type: COLLECTION_TYPE; id: number } | null,
184
+ collection: CollectionParameter | null,
160
185
  progressPct: number
161
186
  ) {
162
187
  return this.upsertSome(
@@ -178,7 +203,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
178
203
 
179
204
  recordProgressesTentative(
180
205
  contentProgresses: Record<string, number>, // Accept plain object
181
- collection: { type: COLLECTION_TYPE; id: number } | null
206
+ collection: CollectionParameter | null
182
207
  ) {
183
208
  const data = Object.fromEntries(
184
209
  Object.entries(contentProgresses).map(([contentId, progressPct]) => [
@@ -196,13 +221,13 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
196
221
  return this.upsertSomeTentative(data)
197
222
  }
198
223
 
199
- eraseProgress(contentId: number, collection: { type: COLLECTION_TYPE; id: number } | null) {
224
+ eraseProgress(contentId: number, collection: CollectionParameter | null) {
200
225
  return this.deleteOne(ProgressRepository.generateId(contentId, collection))
201
226
  }
202
227
 
203
228
  private static generateId(
204
229
  contentId: number,
205
- collection: { type: COLLECTION_TYPE; id: number } | null
230
+ collection: CollectionParameter | null
206
231
  ) {
207
232
  return `${contentId}:${collection?.type || COLLECTION_TYPE.SELF}:${collection?.id || COLLECTION_ID_SELF}`
208
233
  }
@@ -10,7 +10,6 @@ import { fetchLessonsFeaturingThisContent } from '../src/services/sanity.js'
10
10
 
11
11
  const {
12
12
  fetchSongById,
13
- fetchArtists,
14
13
  fetchReturning,
15
14
  fetchLeaving,
16
15
  fetchComingSoon,
@@ -21,25 +20,20 @@ const {
21
20
  fetchByRailContentIds,
22
21
  fetchAll,
23
22
  fetchAllFilterOptions,
24
- fetchFoundation,
25
- fetchMethod,
26
23
  fetchRelatedLessons,
27
24
  fetchAllPacks,
28
25
  fetchPackAll,
29
26
  fetchLessonContent,
30
27
  fetchLiveEvent,
31
- fetchCoachLessons,
32
28
  fetchByReference,
33
29
  fetchScheduledReleases,
34
30
  getSortOrder,
35
31
  fetchShowsData,
36
32
  fetchMetadata,
37
- fetchNextPreviousLesson,
38
33
  fetchHierarchy,
39
34
  fetchTopLevelParentId,
40
35
  fetchOtherSongVersions,
41
36
  fetchCommentModContentData,
42
- fetchMethodPreviousNextLesson,
43
37
  fetchSanity,
44
38
  } = require('../src/services/sanity.js')
45
39
 
@@ -77,13 +71,6 @@ describe('Sanity Queries', function() {
77
71
  expect(response).toBeDefined()
78
72
  })
79
73
 
80
-
81
- test('fetchArtists', async () => {
82
- const response = await fetchArtists('drumeo')
83
- const artistNames = response.map((x) => x.name)
84
- expect(artistNames).toContain('Audioslave')
85
- }, 10000)
86
-
87
74
  test('fetchSongArtistCount', async () => {
88
75
  const response = await fetchSongArtistCount('drumeo')
89
76
  log(response)
@@ -301,13 +288,6 @@ describe('Sanity Queries', function() {
301
288
  expect(sort).toBe('published_on asc')
302
289
  })
303
290
 
304
- test('fetchMethod', async () => {
305
- const response = await fetchMethod('drumeo', 'drumeo-method')
306
- log(response)
307
- expect(response).toBeDefined()
308
- expect(response.levels.length).toBeGreaterThan(0)
309
- })
310
-
311
291
  test('fetchAll-WithProgress', async () => {
312
292
  const ids = [410213, 410215]
313
293
  let response = await fetchAll('drumeo', 'song', {
@@ -332,13 +312,6 @@ describe('Sanity Queries', function() {
332
312
  expect(response.meta.totalResults).toBe(0)
333
313
  })
334
314
 
335
- test('fetchFoundation', async () => {
336
- const response = await fetchFoundation('foundations-2019')
337
- log(response)
338
- expect(response.units.length).toBeGreaterThan(0)
339
- expect(response.type).toBe('foundation')
340
- })
341
-
342
315
  test('fetchPackAll', async () => {
343
316
  const response = await fetchPackAll(212899) //https://web-staging-one.musora.com/admin/studio/publishing/structure/pack;pack_212899%2Cinspect%3Don
344
317
  log(response)
@@ -357,35 +330,6 @@ describe('Sanity Queries', function() {
357
330
  expect(response[0].id).toBe(212899)
358
331
  })
359
332
 
360
- test('fetchCoachLessons', async () => {
361
- const response = await fetchCoachLessons('drumeo', 411493, {})
362
- expect(response.entity.length).toBeGreaterThan(0)
363
- })
364
- test('fetchCoachLessons-WithTypeFilters', async () => {
365
- const response = await fetchAllFilterOptions('drumeo', ['type,course', 'type,live'], '', '', 'coach-lessons', '', [], 31880)
366
- log(response)
367
- expect(response.meta.filterOptions.difficulty).toBeDefined()
368
- expect(response.meta.filterOptions.type).toBeDefined()
369
- expect(response.meta.filterOptions.lifestyle).toBeDefined()
370
- expect(response.meta.filterOptions.genre).toBeDefined()
371
- })
372
-
373
- test('fetchCoachLessons-WithTypeFilters-InvalidContentType', async () => {
374
- const brand = 'drumeo'
375
- const coachId = 31880
376
- const invalidContentType = 'course' // Not 'coach-lessons'
377
-
378
- await expect(fetchAllFilterOptions(brand, ['type,course', 'type,live'], '', '', invalidContentType, '', [], coachId)).rejects.toThrow(`Invalid contentType: 'course' for coachId. It must be 'coach-lessons'.`)
379
- })
380
-
381
- test('fetchCoachLessons-IncludedFields', async () => {
382
- const response = await fetchCoachLessons('drumeo', 31880, {
383
- includedFields: ['genre,Pop/Rock', 'difficulty,Beginner'],
384
- })
385
- log(response)
386
- expect(response.entity.length).toBeGreaterThan(0)
387
- })
388
-
389
333
  test('fetchAll-IncludedFields', async () => {
390
334
  let response = await fetchAll('drumeo', 'instructor', { includedFields: ['is_active'] })
391
335
  console.log(response)
@@ -488,66 +432,6 @@ describe('Sanity Queries', function() {
488
432
  expect(response).toBeDefined()
489
433
  })
490
434
 
491
- test('fetchNextPreviousLesson-Show-With-Episodes', async () => {
492
- const id = 227136
493
- const document = await fetchByRailContentId(id, 'behind-the-scenes')
494
- const response = await fetchNextPreviousLesson(id)
495
- log(response)
496
- expect(response.prevLesson).toBeDefined()
497
- expect(response.prevLesson.sort).toBeLessThanOrEqual(document.sort)
498
- expect(response.nextLesson).toBeDefined()
499
- expect(response.nextLesson.sort).toBeGreaterThanOrEqual(document.sort)
500
- })
501
-
502
- test('fetchMethodNextPreviousLesson-Last', async () => {
503
- const id = 260171
504
- const methodId = 259060
505
- const response = await fetchMethodPreviousNextLesson(id, methodId)
506
- log(response)
507
- expect(response.prevLesson).toBeDefined()
508
- expect(response.prevLesson.id).toBe(260170)
509
- expect(response.prevLesson.type).toBe('course-part')
510
- expect(response.nextLesson).not.toBeDefined()
511
- })
512
-
513
- test('fetchNextPreviousLesson-Method-Lesson', async () => {
514
- const id = 241265
515
- const response = await fetchNextPreviousLesson(id)
516
- log(response)
517
- expect(response.prevLesson).toBeDefined()
518
- expect(response.prevLesson.id).toBe(241264)
519
- expect(response.prevLesson.type).toBe('learning-path-lesson')
520
- expect(response.nextLesson).toBeDefined()
521
- expect(response.nextLesson.id).toBe(241267)
522
- expect(response.nextLesson.type).toBe('learning-path-lesson')
523
- })
524
-
525
- test('fetchNextPreviousLesson-Quick-Tips', async () => {
526
- const id = 412277
527
- const response = await fetchNextPreviousLesson(id)
528
- const document = await fetchByRailContentId(id, 'quick-tips')
529
- const documentPublishedOn = new Date(document.published_on)
530
- const prevDocumentPublishedOn = new Date(response.prevLesson.published_on)
531
- const nextDocumentPublishedOn = new Date(response.nextLesson.published_on)
532
- expect(response.prevLesson).toBeDefined()
533
- expect(prevDocumentPublishedOn.getTime()).toBeLessThan(documentPublishedOn.getTime())
534
- expect(response.nextLesson).toBeDefined()
535
- expect(documentPublishedOn.getTime()).toBeLessThan(nextDocumentPublishedOn.getTime())
536
- })
537
-
538
- test('fetchNextPreviousLesson-Song', async () => {
539
- const id = 414041
540
- const response = await fetchNextPreviousLesson(id)
541
- const document = await fetchByRailContentId(id, 'song')
542
- const documentPublishedOn = new Date(document.published_on)
543
- const prevDocumentPublishedOn = new Date(response.prevLesson.published_on)
544
- const nextDocumentPublishedOn = new Date(response.nextLesson.published_on)
545
- expect(response.prevLesson).toBeDefined()
546
- expect(prevDocumentPublishedOn.getTime()).toBeLessThanOrEqual(documentPublishedOn.getTime())
547
- expect(response.nextLesson).toBeDefined()
548
- expect(documentPublishedOn.getTime()).toBeLessThanOrEqual(nextDocumentPublishedOn.getTime())
549
- })
550
-
551
435
  test('fetchTopLevelParentId', async () => {
552
436
  let contentId = await fetchTopLevelParentId(241250)
553
437
  expect(contentId).toBe(241247)