musora-content-services 2.158.2 → 2.159.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.
Files changed (54) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.github/workflows/automated-testing.yml +21 -1
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +21 -2
  5. package/jest.config.js +1 -4
  6. package/jest.integration.config.js +6 -0
  7. package/jest.live.config.js +1 -5
  8. package/package.json +5 -2
  9. package/src/contentTypeConfig.js +8 -5
  10. package/src/index.d.ts +2 -6
  11. package/src/index.js +2 -6
  12. package/src/services/content-org/learning-paths.ts +44 -39
  13. package/src/services/contentAggregator.js +1 -1
  14. package/src/services/contentProgress.js +216 -207
  15. package/src/services/offline/progress.ts +107 -27
  16. package/src/services/sanity.js +55 -64
  17. package/src/services/sync/models/ContentProgress.ts +50 -34
  18. package/src/services/sync/repositories/content-progress.ts +105 -92
  19. package/test/{unit → integration}/awards/award-exclusion-handling.test.ts +2 -2
  20. package/test/integration/content-progress/__mocks__/mocks.ts +104 -0
  21. package/test/integration/content-progress/contentProgress.test.ts +335 -0
  22. package/test/integration/content-progress/e2eOfflineProgress.test.ts +352 -0
  23. package/test/integration/content-progress/e2eProgress.test.ts +612 -0
  24. package/test/integration/content-progress/getters.test.ts +334 -0
  25. package/test/integration/content-progress/helpers.test.ts +263 -0
  26. package/test/integration/content-progress/offlineContentProgress.test.ts +226 -0
  27. package/test/integration/forums.test.ts +209 -0
  28. package/test/integration/initializeTestDB.ts +80 -0
  29. package/test/{unit → integration}/sync/fetch.test.ts +1 -1
  30. package/test/{unit → integration}/sync/repositories/content-likes.test.ts +1 -1
  31. package/test/{unit → integration}/sync/repositories/practices.test.ts +1 -1
  32. package/test/{unit → integration}/sync/repositories/progress.test.ts +1 -1
  33. package/test/{unit → integration}/sync/repositories/user-award-progress.test.ts +1 -1
  34. package/test/{unit → integration}/sync/store/cross-user-protection.test.ts +2 -2
  35. package/test/{unit → integration}/sync/store/store-idb.test.ts +2 -2
  36. package/test/{unit → integration}/sync/store/store.test.ts +2 -2
  37. package/test/unit/content-progress/bubbleTrickle.test.ts +322 -0
  38. package/test/unit/content-progress/helpers.test.ts +329 -0
  39. package/test/unit/content-progress/navigateTo.test.ts +381 -0
  40. package/test/unit/contentMetaData.test.ts +58 -0
  41. package/tools/generate-index.cjs +6 -3
  42. package/test/SKIPPED_TESTS.md +0 -151
  43. package/test/integration/content.test.js +0 -107
  44. package/test/integration/contentProgress.test.js +0 -73
  45. package/test/integration/forum.test.js +0 -16
  46. package/test/integration/sanityQueryService.test.js +0 -681
  47. package/test/unit/contentProgress.test.ts +0 -81
  48. /package/test/{unit → integration}/awards/internal/image-utils.test.ts +0 -0
  49. /package/test/{unit → integration}/infrastructure/FetchRequestExecutor.test.ts +0 -0
  50. /package/test/{unit → integration}/notifications.test.ts +0 -0
  51. /package/test/{unit → integration}/sync/adapters/idb-errors.test.ts +0 -0
  52. /package/test/{unit → integration}/sync/adapters/sqlite-errors.test.ts +0 -0
  53. /package/test/{unit → integration}/sync/repositories/user-award-progress.static.test.ts +0 -0
  54. /package/test/{unit → integration}/userActivity.test.ts +0 -0
@@ -1,19 +1,29 @@
1
1
  import {
2
- _recordWatchSession, resetStatus,
3
- setStartedOrCompletedStatus,
4
- setStartedOrCompletedStatusMany,
5
- } from '../contentProgress.js'
6
- import { COLLECTION_ID_SELF, COLLECTION_TYPE, CollectionParameter, STATE } from '../sync/models/ContentProgress'
2
+ _recordWatchSession,
3
+ filterOutLearningPathsForDuplication,
4
+ filterOutNegativeProgress,
5
+ getProgressDataByIds,
6
+ normalizeCollection,
7
+ normalizeContentId,
8
+ } from '../../services/contentProgress.js'
9
+ import { COLLECTION_ID_SELF, COLLECTION_TYPE, CollectionParameter } from '../sync/models/ContentProgress'
10
+ import { db } from '../../services/sync'
11
+
12
+ const excludeFromGeneratedIndex = [
13
+ 'duplicateProgressToALaCarteOffline',
14
+ ]
7
15
 
8
16
  interface HierarchyParameter {
9
17
  topLevelId: number
10
- parents: { [contentId: number]: [parentId: number] }
11
- children: { [contentId: number]: [childId: number] }
12
- metadata: {
13
- brand: string
14
- parent_id: number
15
- type: string
16
- }
18
+ parents: Record<string, number>
19
+ children: Record<string, number[]>
20
+ metadata: Record<string, MetadataParameter>
21
+ }
22
+
23
+ interface MetadataParameter {
24
+ brand: string
25
+ parent_id: number
26
+ type: string
17
27
  }
18
28
 
19
29
  /**
@@ -37,10 +47,10 @@ export async function recordWatchSessionOffline(
37
47
  instrumentId = null,
38
48
  categoryId = null,
39
49
  }: {
40
- collection?: CollectionParameter|null,
41
- instrumentId?: number|null,
42
- categoryId?: number|null
43
- } = {}
50
+ collection?: CollectionParameter | null,
51
+ instrumentId?: number | null,
52
+ categoryId?: number | null
53
+ } = {},
44
54
  ) {
45
55
  return _recordWatchSession(
46
56
  contentId,
@@ -62,8 +72,8 @@ export async function recordWatchSessionOffline(
62
72
  * @param hierarchy - Content hierarchy used to update parent progress offline
63
73
  */
64
74
  export async function contentStatusCompletedOffline(contentId: number, collection: CollectionParameter = null, hierarchy: HierarchyParameter) {
65
- collection = collection ?? {id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF}
66
- return setStartedOrCompletedStatus(contentId, collection, true, {isOffline: true, hierarchy})
75
+ collection = collection ?? { id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF }
76
+ return setStartedOrCompletedStatusOffline(contentId, collection, true, hierarchy)
67
77
  }
68
78
 
69
79
  /**
@@ -72,8 +82,8 @@ export async function contentStatusCompletedOffline(contentId: number, collectio
72
82
  * @param hierarchy - Content hierarchy used to update parent progress offline
73
83
  */
74
84
  export async function contentStatusCompletedManyOffline(contentIds: number[], collection: CollectionParameter = null, hierarchy: HierarchyParameter) {
75
- collection = collection ?? {id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF}
76
- return setStartedOrCompletedStatusMany(contentIds, collection, true, {isOffline: true, hierarchy})
85
+ collection = collection ?? { id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF }
86
+ return setStartedOrCompletedStatusManyOffline(contentIds, collection, true, hierarchy)
77
87
  }
78
88
 
79
89
  /**
@@ -82,19 +92,89 @@ export async function contentStatusCompletedManyOffline(contentIds: number[], co
82
92
  * @param hierarchy - Content hierarchy used to update parent progress offline
83
93
  */
84
94
  export async function contentStatusStartedOffline(contentId: number, collection: CollectionParameter = null, hierarchy: HierarchyParameter) {
85
- collection = collection ?? {id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF}
86
- return setStartedOrCompletedStatus(contentId, collection, false, {isOffline: true, hierarchy})
95
+ collection = collection ?? { id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF }
96
+ return setStartedOrCompletedStatusOffline(contentId, collection, false, hierarchy)
87
97
  }
88
98
 
89
99
  /**
90
100
  * @param contentId
91
101
  * @param collection - Collection context; defaults to self
92
- * @param hierarchy - Content hierarchy used to update parent progress offline
93
- * @param options.skipPush - Skip queuing the reset for server sync (default false)
94
102
  */
95
- export async function contentStatusResetOffline(contentId: number, collection: CollectionParameter = null, hierarchy: HierarchyParameter, {skipPush = false} = {}) {
96
- collection = collection ?? {id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF}
97
- return resetStatus(contentId, collection, {hierarchy, skipPush})
103
+ export async function contentStatusResetOffline(contentId: number, collection: CollectionParameter = null) {
104
+ collection = collection ?? { id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF }
105
+ return resetStatusOffline(contentId, collection)
106
+ }
107
+
108
+ async function setStartedOrCompletedStatusOffline(contentId: number, collection: CollectionParameter, isCompleted: boolean, hierarchy: HierarchyParameter) {
109
+ const metadata = hierarchy.metadata || {}
110
+
111
+ const progress = isCompleted ? 100 : 0
112
+ const response = await db.contentProgress.recordProgress(
113
+ normalizeContentId(contentId),
114
+ normalizeCollection(collection),
115
+ progress,
116
+ metadata[contentId],
117
+ null,
118
+ { skipPush: true },
119
+ )
120
+
121
+ let allProgresses = { [contentId]: progress }
122
+
123
+ if (collection?.type === COLLECTION_TYPE.LEARNING_PATH) {
124
+ await duplicateProgressToALaCarteOffline(allProgresses, metadata, collection)
125
+ }
126
+
127
+ db.contentProgress.requestPushUnsynced('save-content-progress')
128
+ return response
98
129
  }
99
130
 
131
+ async function setStartedOrCompletedStatusManyOffline(contentIds: number[], collection: CollectionParameter, isCompleted: boolean, hierarchy: HierarchyParameter) {
132
+ const metadata = hierarchy.metadata || {}
133
+
134
+ const progress = isCompleted ? 100 : 0
135
+ let allProgresses = Object.fromEntries(contentIds.map(id => [id, progress]))
136
+
137
+ const response = await db.contentProgress.recordProgressMany(
138
+ allProgresses,
139
+ normalizeCollection(collection),
140
+ metadata,
141
+ { skipPush: true },
142
+ )
100
143
 
144
+ if (collection?.type === COLLECTION_TYPE.LEARNING_PATH) {
145
+ await duplicateProgressToALaCarteOffline(allProgresses, metadata, collection)
146
+ }
147
+
148
+ db.contentProgress.requestPushUnsynced('save-content-progress')
149
+ return response
150
+ }
151
+
152
+ async function resetStatusOffline(contentId: number, collection: CollectionParameter = null) {
153
+ contentId = normalizeContentId(contentId)
154
+ collection = normalizeCollection(collection)
155
+
156
+ const progress = 0
157
+ const response = await db.contentProgress.eraseProgress(contentId, collection, { skipPush: true })
158
+
159
+ let allProgresses = {}
160
+ allProgresses[contentId] = progress
161
+
162
+ db.contentProgress.requestPushUnsynced('reset-status')
163
+ return response
164
+ }
165
+
166
+ // todo: move getters and helper functions into separate file to unwind circular dependencies with ofline/progress.ts
167
+ export async function duplicateProgressToALaCarteOffline(progresses: Record<string, number>, metadata: Record<string, MetadataParameter>, collection: CollectionParameter) {
168
+ let filteredProgresses = filterOutLearningPathsForDuplication(progresses, collection)
169
+
170
+ const externalProgresses = await getProgressDataByIds(Object.keys(filteredProgresses), null)
171
+
172
+ filteredProgresses = filterOutNegativeProgress(filteredProgresses, externalProgresses)
173
+
174
+ await db.contentProgress.recordProgressMany(
175
+ filteredProgresses,
176
+ null,
177
+ metadata,
178
+ { skipPush: true },
179
+ )
180
+ }
@@ -5,58 +5,47 @@ import {
5
5
  artistOrInstructorName,
6
6
  coachLessonsTypes,
7
7
  contentTypeConfig,
8
+ coursesLessonTypes,
8
9
  DEFAULT_FIELDS,
10
+ entertainmentLessonTypes,
9
11
  filtersToGroq,
12
+ filterTypes,
10
13
  getChildFieldsForContentType,
11
14
  getFieldsForContentType,
12
15
  getFieldsForContentTypeWithFilteredChildren,
13
16
  getIntroVideoFields,
17
+ getLiveFields,
14
18
  getNewReleasesTypes,
15
19
  getUpcomingEventsTypes,
20
+ grandParentReferenceField,
21
+ individualLessonsTypes,
16
22
  instructorField,
23
+ jamTrackLessonTypes,
17
24
  lessonTypesMapping,
18
- individualLessonsTypes,
19
- coursesLessonTypes,
20
- skillLessonTypes,
21
- entertainmentLessonTypes,
22
- filterTypes,
23
- tutorialsLessonTypes,
24
- transcriptionsLessonTypes,
25
+ parentRecentTypes,
26
+ parentReferenceField,
25
27
  playAlongLessonTypes,
26
- jamTrackLessonTypes,
28
+ postProcessBadge,
27
29
  showsTypes,
30
+ skillLessonTypes,
28
31
  SONG_TYPES,
29
32
  SONG_TYPES_WITH_CHILDREN,
30
- postProcessBadge,
31
- parentRecentTypes,
32
- parentReferenceField,
33
- grandParentReferenceField,
34
- getLiveFields,
33
+ transcriptionsLessonTypes,
34
+ tutorialsLessonTypes,
35
35
  } from '../contentTypeConfig.js'
36
36
  import { fetchSimilarItems } from './recommendations.js'
37
- import {
38
- getSongType,
39
- processMetadata,
40
- ALWAYS_VISIBLE_TABS,
41
- CONTENT_STATUSES,
42
- } from '../contentMetaData.js'
37
+ import { ALWAYS_VISIBLE_TABS, CONTENT_STATUSES, getSongType, processMetadata } from '../contentMetaData.js'
43
38
  import { GET } from '../infrastructure/http/HttpClient.ts'
44
39
 
45
40
  import { globalConfig } from './config.js'
46
41
 
47
42
  import { arrayToStringRepresentation, FilterBuilder } from '../filterBuilder.js'
48
43
  import { getPermissionsAdapter } from './permissions/index.ts'
49
- import {
50
- getAllCompleted,
51
- getAllCompletedByIds,
52
- getAllStarted,
53
- getAllStartedOrCompleted,
54
- } from './contentProgress.js'
44
+ import { getAllCompleted, getAllCompletedByIds, getAllStarted, getAllStartedOrCompleted } from './contentProgress.js'
55
45
  import { fetchRecentActivitiesActiveTabs } from './userActivity.js'
56
46
  import { query } from '../lib/sanity/query'
57
47
  import { Filters as f } from '../lib/sanity/filter'
58
48
  import { COLLECTION_TYPE } from './sync/models/ContentProgress'
59
- import { MEMBERSHIP_PERMISSIONS } from '../constants/membership-permissions'
60
49
 
61
50
  /**
62
51
  * Exported functions that are excluded from index generation.
@@ -112,7 +101,7 @@ export async function fetchSongById(documentId) {
112
101
  fields,
113
102
  {
114
103
  isSingle: true,
115
- }
104
+ },
116
105
  )
117
106
  return fetchSanity(query, false)
118
107
  }
@@ -139,7 +128,7 @@ export async function fetchLeaving(brand, { pageNumber = 1, contentPerPage = 20
139
128
  filterString,
140
129
  { pullFutureContent: false, availableContentStatuses: CONTENT_STATUSES.PUBLISHED_ONLY },
141
130
  getFieldsForContentType('leaving'),
142
- sortOrder
131
+ sortOrder,
143
132
  )
144
133
  return fetchSanity(query, true)
145
134
  }
@@ -166,7 +155,7 @@ export async function fetchReturning(brand, { pageNumber = 1, contentPerPage = 2
166
155
  filterString,
167
156
  { pullFutureContent: true, availableContentStatuses: CONTENT_STATUSES.DRAFT_ONLY },
168
157
  getFieldsForContentType('returning'),
169
- sortOrder
158
+ sortOrder,
170
159
  )
171
160
 
172
161
  return fetchSanity(query, true)
@@ -192,7 +181,7 @@ export async function fetchComingSoon(brand, { pageNumber = 1, contentPerPage =
192
181
  filterString,
193
182
  { getFutureContentOnly: true },
194
183
  getFieldsForContentType(),
195
- sortOrder
184
+ sortOrder,
196
185
  )
197
186
  return fetchSanity(query, true)
198
187
  }
@@ -219,7 +208,7 @@ function getQueryFromPage(pageNumber, contentPerPage) {
219
208
  export async function fetchSongArtistCount(brand) {
220
209
  const filter = await new FilterBuilder(
221
210
  `_type == "song" && brand == "${brand}" && references(^._id)`,
222
- { bypassPermissions: true }
211
+ { bypassPermissions: true },
223
212
  ).buildFilter()
224
213
  const query = `
225
214
  count(*[_type == "artist"]{
@@ -231,7 +220,7 @@ export async function fetchSongArtistCount(brand) {
231
220
 
232
221
  export async function fetchPlayAlongsCount(
233
222
  brand,
234
- { searchTerm, includedFields, progressIds, progress }
223
+ { searchTerm, includedFields, progressIds, progress },
235
224
  ) {
236
225
  const searchFilter = searchTerm
237
226
  ? `&& (artist->name match "${searchTerm}*" || instructor[]->name match "${searchTerm}*" || title match "${searchTerm}*" || name match "${searchTerm}*")`
@@ -336,7 +325,7 @@ export async function fetchRelatedSongs(brand, songId) {
336
325
  */
337
326
  export async function fetchNewReleases(
338
327
  brand,
339
- { page = 1, limit = 20, sort = '-published_on' } = {}
328
+ { page = 1, limit = 20, sort = '-published_on' } = {},
340
329
  ) {
341
330
  const newTypes = getNewReleasesTypes(brand)
342
331
  const typesString = arrayToStringRepresentation(newTypes)
@@ -413,7 +402,7 @@ export async function fetchUpcomingEvents(brand, { page = 1, limit = 10 } = {})
413
402
  sortOrder: 'published_on asc',
414
403
  start: start,
415
404
  end: end,
416
- }
405
+ },
417
406
  )
418
407
  return fetchSanity(query, true)
419
408
  }
@@ -492,7 +481,7 @@ export async function fetchByRailContentId(id, contentType) {
492
481
  entityFieldsString,
493
482
  {
494
483
  isSingle: true,
495
- }
484
+ },
496
485
  )
497
486
 
498
487
  return fetchSanity(query, false)
@@ -515,7 +504,7 @@ export async function fetchByRailContentIds(
515
504
  contentType = undefined,
516
505
  brand = undefined,
517
506
  includePermissionsAndStatusFilter = false,
518
- filterOptions = {}
507
+ filterOptions = {},
519
508
  ) {
520
509
  if (!ids?.length) {
521
510
  return []
@@ -663,7 +652,7 @@ export async function fetchAll(
663
652
  customFields = [],
664
653
  progress = 'all',
665
654
  onlyPublished = true,
666
- } = {}
655
+ } = {},
667
656
  ) {
668
657
  let config = contentTypeConfig[type] ?? {}
669
658
  let additionalFields = config?.fields ?? []
@@ -881,7 +870,7 @@ export async function fetchAllFilterOptions(
881
870
  term,
882
871
  progressIds,
883
872
  coachId,
884
- includeTabs = false
873
+ includeTabs = false,
885
874
  ) {
886
875
  if (contentType == 'lessons' || contentType == 'songs') {
887
876
  const metaData = processMetadata(brand, contentType, true)
@@ -892,7 +881,7 @@ export async function fetchAllFilterOptions(
892
881
 
893
882
  if (coachId && contentType !== 'coach-lessons') {
894
883
  throw new Error(
895
- `Invalid contentType: '${contentType}' for coachId. It must be 'coach-lessons'.`
884
+ `Invalid contentType: '${contentType}' for coachId. It must be 'coach-lessons'.`,
896
885
  )
897
886
  }
898
887
 
@@ -976,7 +965,7 @@ export async function fetchLessonContent(railContentId, { forDownload = false }
976
965
  showMembershipRestrictedContent: true,
977
966
  }
978
967
 
979
- const fields = getFieldsForContentType('download')
968
+ const fields = getFieldsForContentType('download') // todo(BEHTP-7): a later refactor can make this 'playback'.
980
969
 
981
970
  const query = await buildQuery(`railcontent_id == ${railContentId}`, filterParams, fields, {
982
971
  isSingle: true,
@@ -1027,7 +1016,7 @@ export async function fetchRelatedRecommendedContent(railContentId, brand, count
1027
1016
  }
1028
1017
 
1029
1018
  return await fetchRelatedLessons(railContentId, brand).then((result) =>
1030
- result.related_lessons?.splice(0, count)
1019
+ result.related_lessons?.splice(0, count),
1031
1020
  )
1032
1021
  }
1033
1022
 
@@ -1152,15 +1141,15 @@ export async function fetchRelatedLessons(railContentId) {
1152
1141
  }
1153
1142
  const filterSameArtist = await new FilterBuilder(
1154
1143
  `${defaultFilterFields} && references(^.artist->_id)`,
1155
- params
1144
+ params,
1156
1145
  ).buildFilter()
1157
1146
  const filterSameGenre = await new FilterBuilder(
1158
1147
  `${defaultFilterFields} && references(^.genre[]->_id)`,
1159
- params
1148
+ params,
1160
1149
  ).buildFilter()
1161
1150
  const filterSameDifficulty = await new FilterBuilder(
1162
1151
  `${defaultFilterFields} && difficulty == ^.difficulty`,
1163
- params
1152
+ params,
1164
1153
  ).buildFilter()
1165
1154
  const queryFields = getFieldsForContentType()
1166
1155
 
@@ -1199,12 +1188,12 @@ export async function fetchLiveEvent(brand, forcedContentId = null) {
1199
1188
  let endDateTemp = new Date()
1200
1189
 
1201
1190
  startDateTemp = new Date(
1202
- startDateTemp.setMinutes(startDateTemp.getMinutes() + LIVE_EXTRA_MINUTES)
1191
+ startDateTemp.setMinutes(startDateTemp.getMinutes() + LIVE_EXTRA_MINUTES),
1203
1192
  )
1204
1193
  endDateTemp = new Date(endDateTemp.setMinutes(endDateTemp.getMinutes() - LIVE_EXTRA_MINUTES))
1205
1194
 
1206
1195
  const liveEventFields = getLiveFields().concat(
1207
- `'event_coach_calendar_id': coalesce(calendar_id, '${defaultCalendarID}')`
1196
+ `'event_coach_calendar_id': coalesce(calendar_id, '${defaultCalendarID}')`,
1208
1197
  )
1209
1198
  const fieldsString = liveEventFields.join(',')
1210
1199
 
@@ -1271,7 +1260,7 @@ export async function fetchPackData(id) {
1271
1260
  */
1272
1261
  export async function fetchByReference(
1273
1262
  brand,
1274
- { sortOrder = '-published_on', searchTerm = '', page = 1, limit = 20, includedFields = [] } = {}
1263
+ { sortOrder = '-published_on', searchTerm = '', page = 1, limit = 20, includedFields = [] } = {},
1275
1264
  ) {
1276
1265
  const fieldsString = getFieldsForContentType()
1277
1266
  const start = (page - 1) * limit
@@ -1411,6 +1400,7 @@ function mapHierarchyDataToContentIds(hierarchyData, contentIds) {
1411
1400
 
1412
1401
  function extractMetadataFromHierarchy(hierarchyData) {
1413
1402
  let metadata = {}
1403
+
1414
1404
  function recursiveExtract(currentLevel, parentMetadata = {}) {
1415
1405
  const railcontentIdField = currentLevel.railcontent_id ? 'railcontent_id' : 'id'
1416
1406
  let contentId = currentLevel[railcontentIdField]
@@ -1432,6 +1422,7 @@ function extractMetadataFromHierarchy(hierarchyData) {
1432
1422
  }
1433
1423
  }
1434
1424
  }
1425
+
1435
1426
  recursiveExtract(hierarchyData)
1436
1427
  return metadata
1437
1428
  }
@@ -1545,11 +1536,11 @@ export async function fetchCommentModContentData(ids) {
1545
1536
  `railcontent_id in [${idsString}]`,
1546
1537
  { bypassPermissions: true },
1547
1538
  fields,
1548
- { end: 50 }
1539
+ { end: 50 },
1549
1540
  )
1550
1541
  let data = await fetchSanity(query, true)
1551
1542
  let mapped = {}
1552
- data.forEach(function (content) {
1543
+ data.forEach(function(content) {
1553
1544
  mapped[content.id] = {
1554
1545
  id: content.id,
1555
1546
  type: content.type,
@@ -1581,7 +1572,7 @@ export async function fetchCommentModContentData(ids) {
1581
1572
  export async function fetchSanity(
1582
1573
  query,
1583
1574
  isList,
1584
- { customPostProcess = null, processNeedAccess = true, processPageType = true } = {}
1575
+ { customPostProcess = null, processNeedAccess = true, processPageType = true } = {},
1585
1576
  ) {
1586
1577
  // Check the config object before proceeding
1587
1578
  if (!checkSanityConfig(globalConfig)) {
@@ -1713,7 +1704,7 @@ function contentResultsDecorator(results, fieldName, callback) {
1713
1704
  }
1714
1705
 
1715
1706
  function pageTypeDecorator(results) {
1716
- return contentResultsDecorator(results, 'page_type', function (content) {
1707
+ return contentResultsDecorator(results, 'page_type', function(content) {
1717
1708
  return SONG_TYPES_WITH_CHILDREN.includes(content['type']) ? 'song' : 'lesson'
1718
1709
  })
1719
1710
  }
@@ -1721,7 +1712,7 @@ function pageTypeDecorator(results) {
1721
1712
  function needsAccessDecorator(results, userPermissions) {
1722
1713
  if (globalConfig.sanityConfig.useDummyRailContentMethods) return results
1723
1714
  const adapter = getPermissionsAdapter()
1724
- return contentResultsDecorator(results, 'need_access', function (content) {
1715
+ return contentResultsDecorator(results, 'need_access', function(content) {
1725
1716
  return adapter.doesUserNeedAccess(content, userPermissions)
1726
1717
  })
1727
1718
  }
@@ -1798,7 +1789,7 @@ export async function fetchMetadata(brand, type, options = {}) {
1798
1789
  if (processedData.filters) {
1799
1790
  processedData.filters = filterTypeOptionsByContentCounts(
1800
1791
  processedData.filters,
1801
- contentTypeCounts
1792
+ contentTypeCounts,
1802
1793
  )
1803
1794
  }
1804
1795
  } catch (error) {
@@ -1838,7 +1829,7 @@ export function getSanityDate(date, roundToHourForCaching = true) {
1838
1829
  date.getFullYear(),
1839
1830
  date.getMonth(),
1840
1831
  date.getDate(),
1841
- date.getHours()
1832
+ date.getHours(),
1842
1833
  )
1843
1834
 
1844
1835
  return roundedDate.toISOString()
@@ -1881,7 +1872,7 @@ function checkSanityConfig(config) {
1881
1872
  function buildRawQuery(
1882
1873
  filter = '',
1883
1874
  fields = '...',
1884
- { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false }
1875
+ { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false },
1885
1876
  ) {
1886
1877
  const sortString = sortOrder ? `order(${sortOrder})` : ''
1887
1878
  const countString = isSingle ? '[0...1]' : `[${start}...${end}]`
@@ -1895,7 +1886,7 @@ async function buildQuery(
1895
1886
  baseFilter = '',
1896
1887
  filterParams = { pullFutureContent: false },
1897
1888
  fields = '...',
1898
- { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false }
1889
+ { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false },
1899
1890
  ) {
1900
1891
  const filter = await new FilterBuilder(baseFilter, filterParams).buildFilter()
1901
1892
  return buildRawQuery(filter, fields, { sortOrder, start, end, isSingle })
@@ -1910,7 +1901,7 @@ export function buildEntityAndTotalQuery(
1910
1901
  end = 10,
1911
1902
  isSingle = false,
1912
1903
  withoutPagination = false,
1913
- }
1904
+ },
1914
1905
  ) {
1915
1906
  const sortString = sortOrder ? ` | order(${sortOrder})` : ''
1916
1907
  const countString = isSingle ? '[0...1]' : withoutPagination ? `` : `[${start}...${end}]`
@@ -2032,7 +2023,7 @@ export async function fetchTabData(
2032
2023
  progressIds = undefined,
2033
2024
  progress = 'all',
2034
2025
  excludeIds = [],
2035
- } = {}
2026
+ } = {},
2036
2027
  ) {
2037
2028
  const start = (page - 1) * limit
2038
2029
  const end = start + limit
@@ -2135,7 +2126,7 @@ export async function fetchTabData(
2135
2126
  export async function fetchRecent(
2136
2127
  brand,
2137
2128
  pageName,
2138
- { page = 1, limit = 10, sort = '-published_on', includedFields = [], progress = 'recent' } = {}
2129
+ { page = 1, limit = 10, sort = '-published_on', includedFields = [], progress = 'recent' } = {},
2139
2130
  ) {
2140
2131
  const mergedIncludedFields = [...includedFields, `tab,all`]
2141
2132
  const results = await fetchTabData(brand, pageName, {
@@ -2152,7 +2143,7 @@ export async function fetchScheduledAndNewReleases(
2152
2143
  brand,
2153
2144
  // page param deprecated, doesnt have 1-1 affect on this functionality
2154
2145
  // if we want to allow pagination, this requires a revisit
2155
- { limit = 10 } = {}
2146
+ { limit = 10 } = {},
2156
2147
  ) {
2157
2148
  const maxLessons = 3
2158
2149
  const maxSongs = 5
@@ -2172,7 +2163,7 @@ export async function fetchScheduledAndNewReleases(
2172
2163
  f.typeIn(parentsWithoutSong),
2173
2164
  f.statusIn(['published']),
2174
2165
  f.publishedBefore(now),
2175
- f.publishedAfter(fifteenDaysAgo)
2166
+ f.publishedAfter(fifteenDaysAgo),
2176
2167
  )
2177
2168
 
2178
2169
  const songFilter = f.combine(
@@ -2181,14 +2172,14 @@ export async function fetchScheduledAndNewReleases(
2181
2172
  f.type('song'),
2182
2173
  f.statusIn(['published']),
2183
2174
  f.publishedBefore(now),
2184
- f.publishedAfter(fifteenDaysAgo)
2175
+ f.publishedAfter(fifteenDaysAgo),
2185
2176
  )
2186
2177
 
2187
2178
  const livestreamFilter = f.combine(
2188
2179
  'show_in_new_feed == true',
2189
2180
  f.combineOr(f.brand(brand), 'live_global_event == true'),
2190
2181
  f.statusIn(['scheduled']),
2191
- `live_event_start_time >= '${now}'`
2182
+ `live_event_start_time >= '${now}'`,
2192
2183
  )
2193
2184
 
2194
2185
  const lessonQuery = query()
@@ -2349,7 +2340,7 @@ export async function fetchMethodV2StructureFromId(contentId) {
2349
2340
  */
2350
2341
  export async function fetchOwnedContent(
2351
2342
  brand,
2352
- { type = [], page = 1, limit = 10, sort = '-published_on' } = {}
2343
+ { type = [], page = 1, limit = 10, sort = '-published_on' } = {},
2353
2344
  ) {
2354
2345
  const start = (page - 1) * limit
2355
2346
  const end = start + limit