musora-content-services 1.2.5 → 1.3.2

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 (40) hide show
  1. package/.prettierignore +5 -0
  2. package/.prettierrc +8 -0
  3. package/.yarnrc.yml +1 -0
  4. package/CHANGELOG.md +11 -0
  5. package/README.md +0 -0
  6. package/babel.config.cjs +1 -1
  7. package/docs/config.js.html +0 -0
  8. package/docs/index.html +0 -0
  9. package/docs/module-Config.html +0 -0
  10. package/docs/module-Railcontent-Services.html +0 -0
  11. package/docs/module-Sanity-Services.html +0 -0
  12. package/docs/railcontent.js.html +0 -0
  13. package/docs/sanity.js.html +0 -0
  14. package/jest.config.js +9 -10
  15. package/jsdoc.json +17 -12
  16. package/package.json +2 -1
  17. package/src/contentMetaData.js +1190 -1131
  18. package/src/contentTypeConfig.js +492 -387
  19. package/src/filterBuilder.js +163 -145
  20. package/src/index.d.ts +227 -237
  21. package/src/index.js +226 -236
  22. package/src/services/config.js +12 -12
  23. package/src/services/contentLikes.js +33 -32
  24. package/src/services/contentProgress.js +233 -200
  25. package/src/services/dataContext.js +99 -93
  26. package/src/services/lastUpdated.js +7 -7
  27. package/src/services/railcontent.js +368 -364
  28. package/src/services/sanity.js +983 -955
  29. package/src/services/userPermissions.js +12 -14
  30. package/test/contentLikes.test.js +89 -86
  31. package/test/contentProgress.test.js +229 -236
  32. package/test/initializeTests.js +54 -51
  33. package/test/lastUpdated.test.js +20 -18
  34. package/test/live/contentProgressLive.test.js +135 -137
  35. package/test/live/railcontentLive.test.js +12 -14
  36. package/test/localStorageMock.js +16 -16
  37. package/test/log.js +5 -5
  38. package/test/sanityQueryService.test.js +857 -821
  39. package/test/userPermissions.test.js +15 -15
  40. package/tools/generate-index.cjs +108 -111
@@ -2,45 +2,42 @@
2
2
  * @module Sanity-Services
3
3
  */
4
4
  import {
5
- artistOrInstructorName,
6
- artistOrInstructorNameAsArray,
7
- assignmentsField,
8
- descriptionField,
9
- resourcesField,
10
- contentTypeConfig,
11
- DEFAULT_FIELDS,
12
- getFieldsForContentType,
13
- filtersToGroq,
14
- getUpcomingEventsTypes,
15
- showsTypes,
16
- getNewReleasesTypes,
17
- coachLessonsTypes
18
- } from "../contentTypeConfig.js";
5
+ artistOrInstructorName,
6
+ artistOrInstructorNameAsArray,
7
+ assignmentsField,
8
+ descriptionField,
9
+ resourcesField,
10
+ contentTypeConfig,
11
+ DEFAULT_FIELDS,
12
+ getFieldsForContentType,
13
+ filtersToGroq,
14
+ getUpcomingEventsTypes,
15
+ showsTypes,
16
+ getNewReleasesTypes,
17
+ coachLessonsTypes,
18
+ } from '../contentTypeConfig.js'
19
+
20
+ import { processMetadata, typeWithSortOrder } from '../contentMetaData.js'
21
+
22
+ import { globalConfig } from './config.js'
19
23
 
20
24
  import {
21
- processMetadata,
22
- typeWithSortOrder
23
- } from "../contentMetaData.js";
24
-
25
- import {globalConfig} from "./config.js";
26
-
27
- import {
28
- fetchAllCompletedStates,
29
- fetchCompletedChallenges,
30
- fetchOwnedChallenges,
31
- fetchNextContentDataForParent,
32
- fetchHandler,
33
- } from './railcontent.js';
34
- import {arrayToStringRepresentation, FilterBuilder} from "../filterBuilder.js";
35
- import {fetchUserPermissions} from "./userPermissions.js";
36
- import {getAllCompleted, getAllStarted, getAllStartedOrCompleted} from "./contentProgress.js";
25
+ fetchAllCompletedStates,
26
+ fetchCompletedChallenges,
27
+ fetchOwnedChallenges,
28
+ fetchNextContentDataForParent,
29
+ fetchHandler,
30
+ } from './railcontent.js'
31
+ import { arrayToStringRepresentation, FilterBuilder } from '../filterBuilder.js'
32
+ import { fetchUserPermissions } from './userPermissions.js'
33
+ import { getAllCompleted, getAllStarted, getAllStartedOrCompleted } from './contentProgress.js'
37
34
 
38
35
  /**
39
36
  * Exported functions that are excluded from index generation.
40
37
  *
41
38
  * @type {string[]}
42
39
  */
43
- const excludeFromGeneratedIndex = ['handleCustomFetchAll'];
40
+ const excludeFromGeneratedIndex = ['handleCustomFetchAll']
44
41
 
45
42
  /**
46
43
  * Fetch a song by its document ID from Sanity.
@@ -54,16 +51,17 @@ const excludeFromGeneratedIndex = ['handleCustomFetchAll'];
54
51
  * .catch(error => console.error(error));
55
52
  */
56
53
  export async function fetchSongById(documentId) {
57
- const fields = getFieldsForContentType('song');
58
- const filterParams = {};
59
- const query = await buildQuery(
60
- `_type == "song" && railcontent_id == ${documentId}`,
61
- filterParams,
62
- fields,
63
- {
64
- isSingle: true,
65
- });
66
- return fetchSanity(query, false);
54
+ const fields = getFieldsForContentType('song')
55
+ const filterParams = {}
56
+ const query = await buildQuery(
57
+ `_type == "song" && railcontent_id == ${documentId}`,
58
+ filterParams,
59
+ fields,
60
+ {
61
+ isSingle: true,
62
+ }
63
+ )
64
+ return fetchSanity(query, false)
67
65
  }
68
66
 
69
67
  /**
@@ -78,13 +76,16 @@ export async function fetchSongById(documentId) {
78
76
  * .catch(error => console.error(error));
79
77
  */
80
78
  export async function fetchArtists(brand) {
81
- const filter = await new FilterBuilder(`_type == "song" && brand == "${brand}" && references(^._id)`, {bypassPermissions: true}).buildFilter();
82
- const query = `
79
+ const filter = await new FilterBuilder(
80
+ `_type == "song" && brand == "${brand}" && references(^._id)`,
81
+ { bypassPermissions: true }
82
+ ).buildFilter()
83
+ const query = `
83
84
  *[_type == "artist"]{
84
85
  name,
85
86
  "lessonsCount": count(*[${filter}])
86
- }[lessonsCount > 0]`;
87
- return fetchSanity(query, true, {processNeedAccess: false});
87
+ }[lessonsCount > 0] |order(lower(name)) `
88
+ return fetchSanity(query, true, { processNeedAccess: false })
88
89
  }
89
90
 
90
91
  /**
@@ -93,13 +94,21 @@ export async function fetchArtists(brand) {
93
94
  * @returns {Promise<int|null>} - The fetched count of artists.
94
95
  */
95
96
  export async function fetchSongArtistCount(brand) {
96
- const query = `count(*[_type == 'artist']{'lessonsCount': count(*[_type == 'song' && brand == '${brand}' && references(^._id)]._id)}[lessonsCount > 0])`;
97
- return fetchSanity(query, true, {processNeedAccess: false});
97
+ const filter = await new FilterBuilder(
98
+ `_type == "song" && brand == "${brand}" && references(^._id)`,
99
+ { bypassPermissions: true }
100
+ ).buildFilter()
101
+ const query = `
102
+ count(*[_type == "artist"]{
103
+ name,
104
+ "lessonsCount": count(*[${filter}])
105
+ }[lessonsCount > 0])`
106
+ return fetchSanity(query, true, { processNeedAccess: false })
98
107
  }
99
108
 
100
109
  export async function fetchPlayAlongsCount(brand) {
101
- const query = `count(*[brand == '${brand}' && _type == "play-along"]) `
102
- return fetchSanity(query, true, {processNeedAccess: false});
110
+ const query = `count(*[brand == '${brand}' && _type == "play-along"]) `
111
+ return fetchSanity(query, true, { processNeedAccess: false })
103
112
  }
104
113
 
105
114
  /**
@@ -115,8 +124,8 @@ export async function fetchPlayAlongsCount(brand) {
115
124
  * .catch(error => console.error(error));
116
125
  */
117
126
  export async function fetchRelatedSongs(brand, songId) {
118
- const now = getSanityDate(new Date());
119
- const query = `
127
+ const now = getSanityDate(new Date())
128
+ const query = `
120
129
  *[_type == "song" && railcontent_id == ${songId}]{
121
130
  "entity": array::unique([
122
131
  ...(*[_type == "song" && brand == "${brand}" && railcontent_id != ${songId} && references(^.artist->_id)
@@ -177,10 +186,10 @@ export async function fetchRelatedSongs(brand, songId) {
177
186
  }]
178
187
  }[0...10])
179
188
  ])[0...10]
180
- }`;
189
+ }`
181
190
 
182
- // Fetch the related songs data
183
- return fetchSanity(query, false);
191
+ // Fetch the related songs data
192
+ return fetchSanity(query, false)
184
193
  }
185
194
 
186
195
  /**
@@ -188,14 +197,17 @@ export async function fetchRelatedSongs(brand, songId) {
188
197
  * @param {string} brand - The brand for which to fetch new releases.
189
198
  * @returns {Promise<Object|null>} - The fetched new releases data or null if not found.
190
199
  */
191
- export async function fetchNewReleases(brand, {page = 1, limit = 20, sort = "-published_on"} = {}) {
192
- const newTypes = getNewReleasesTypes(brand);
193
- const typesString = arrayToStringRepresentation(newTypes);
194
- const start = (page - 1) * limit;
195
- const end = start + limit;
196
- const sortOrder = getSortOrder(sort, brand);
197
- const filter = `_type in ${typesString} && brand == '${brand}' && show_in_new_feed == true`;
198
- const fields = `
200
+ export async function fetchNewReleases(
201
+ brand,
202
+ { page = 1, limit = 20, sort = '-published_on' } = {}
203
+ ) {
204
+ const newTypes = getNewReleasesTypes(brand)
205
+ const typesString = arrayToStringRepresentation(newTypes)
206
+ const start = (page - 1) * limit
207
+ const end = start + limit
208
+ const sortOrder = getSortOrder(sort, brand)
209
+ const filter = `_type in ${typesString} && brand == '${brand}' && show_in_new_feed == true`
210
+ const fields = `
199
211
  "id": railcontent_id,
200
212
  title,
201
213
  "image": thumbnail.asset->url,
@@ -208,21 +220,16 @@ export async function fetchNewReleases(brand, {page = 1, limit = 20, sort = "-pu
208
220
  "type": _type,
209
221
  web_url_path,
210
222
  "permission_id": permission[]->railcontent_id,
211
- `;
212
- const filterParams = {allowsPullSongsContent: false};
213
- const query = await buildQuery(
214
- filter,
215
- filterParams,
216
- fields,
217
- {
218
- sortOrder: sortOrder,
219
- start,
220
- end: end,
221
- });
222
- return fetchSanity(query, true);
223
+ `
224
+ const filterParams = { allowsPullSongsContent: false }
225
+ const query = await buildQuery(filter, filterParams, fields, {
226
+ sortOrder: sortOrder,
227
+ start,
228
+ end: end,
229
+ })
230
+ return fetchSanity(query, true)
223
231
  }
224
232
 
225
-
226
233
  /**
227
234
  * Fetch upcoming events for a specific brand.
228
235
  *
@@ -237,13 +244,13 @@ export async function fetchNewReleases(brand, {page = 1, limit = 20, sort = "-pu
237
244
  * .then(events => console.log(events))
238
245
  * .catch(error => console.error(error));
239
246
  */
240
- export async function fetchUpcomingEvents(brand, {page = 1, limit = 10} = {}) {
241
- const liveTypes = getUpcomingEventsTypes(brand);
242
- const typesString = arrayToStringRepresentation(liveTypes);
243
- const now = getSanityDate(new Date());
244
- const start = (page - 1) * limit;
245
- const end = start + limit;
246
- const fields = `
247
+ export async function fetchUpcomingEvents(brand, { page = 1, limit = 10 } = {}) {
248
+ const liveTypes = getUpcomingEventsTypes(brand)
249
+ const typesString = arrayToStringRepresentation(liveTypes)
250
+ const now = getSanityDate(new Date())
251
+ const start = (page - 1) * limit
252
+ const end = start + limit
253
+ const fields = `
247
254
  "id": railcontent_id,
248
255
  title,
249
256
  "image": thumbnail.asset->url,
@@ -255,17 +262,17 @@ export async function fetchUpcomingEvents(brand, {page = 1, limit = 10} = {}) {
255
262
  published_on,
256
263
  "type": _type,
257
264
  web_url_path,
258
- "permission_id": permission[]->railcontent_id,`;
259
- const query = buildRawQuery(
260
- `_type in ${typesString} && brand == '${brand}' && published_on > '${now}' && status == 'scheduled'`,
261
- fields,
262
- {
263
- sortOrder: 'published_on asc',
264
- start: start,
265
- end: end,
266
- },
267
- );
268
- return fetchSanity(query, true);
265
+ "permission_id": permission[]->railcontent_id,`
266
+ const query = buildRawQuery(
267
+ `_type in ${typesString} && brand == '${brand}' && published_on > '${now}' && status == 'scheduled'`,
268
+ fields,
269
+ {
270
+ sortOrder: 'published_on asc',
271
+ start: start,
272
+ end: end,
273
+ }
274
+ )
275
+ return fetchSanity(query, true)
269
276
  }
270
277
 
271
278
  /**
@@ -282,16 +289,16 @@ export async function fetchUpcomingEvents(brand, {page = 1, limit = 10} = {}) {
282
289
  * .then(content => console.log(content))
283
290
  * .catch(error => console.error(error));
284
291
  */
285
- export async function fetchScheduledReleases(brand, {page = 1, limit = 10}) {
286
- const upcomingTypes = getUpcomingEventsTypes(brand);
287
- const newTypes = getNewReleasesTypes(brand);
288
-
289
- const scheduledTypes = merge(upcomingTypes, newTypes)
290
- const typesString = arrayJoinWithQuotes(scheduledTypes);
291
- const now = getSanityDate(new Date());
292
- const start = (page - 1) * limit;
293
- const end = start + limit;
294
- const query = `*[_type in [${typesString}] && brand == '${brand}' && status in ['published','scheduled'] && published_on > '${now}']{
292
+ export async function fetchScheduledReleases(brand, { page = 1, limit = 10 }) {
293
+ const upcomingTypes = getUpcomingEventsTypes(brand)
294
+ const newTypes = getNewReleasesTypes(brand)
295
+
296
+ const scheduledTypes = merge(upcomingTypes, newTypes)
297
+ const typesString = arrayJoinWithQuotes(scheduledTypes)
298
+ const now = getSanityDate(new Date())
299
+ const start = (page - 1) * limit
300
+ const end = start + limit
301
+ const query = `*[_type in [${typesString}] && brand == '${brand}' && status in ['published','scheduled'] && published_on > '${now}']{
295
302
  "id": railcontent_id,
296
303
  title,
297
304
  "image": thumbnail.asset->url,
@@ -304,8 +311,8 @@ export async function fetchScheduledReleases(brand, {page = 1, limit = 10}) {
304
311
  "type": _type,
305
312
  web_url_path,
306
313
  "permission_id": permission[]->railcontent_id,
307
- } | order(published_on asc)[${start}...${end}]`;
308
- return fetchSanity(query, true);
314
+ } | order(published_on asc)[${start}...${end}]`
315
+ return fetchSanity(query, true)
309
316
  }
310
317
 
311
318
  /**
@@ -321,9 +328,9 @@ export async function fetchScheduledReleases(brand, {page = 1, limit = 10}) {
321
328
  * .catch(error => console.error(error));
322
329
  */
323
330
  export async function fetchByRailContentId(id, contentType) {
324
- const fields = getFieldsForContentType(contentType);
325
- const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
326
- const entityFieldsString = ` ${fields}
331
+ const fields = getFieldsForContentType(contentType)
332
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
333
+ const entityFieldsString = ` ${fields}
327
334
  'child_count': coalesce(count(child[${childrenFilter}]->), 0) ,
328
335
  "lessons": child[${childrenFilter}]->{
329
336
  "id": railcontent_id,
@@ -339,17 +346,17 @@ export async function fetchByRailContentId(id, contentType) {
339
346
  )
340
347
  ),
341
348
  length_in_seconds
342
- ),`;
349
+ ),`
343
350
 
344
- const query = buildRawQuery(
345
- `railcontent_id == ${id} && _type == '${contentType}'`,
346
- entityFieldsString,
347
- {
348
- isSingle: true,
349
- },
350
- );
351
+ const query = buildRawQuery(
352
+ `railcontent_id == ${id} && _type == '${contentType}'`,
353
+ entityFieldsString,
354
+ {
355
+ isSingle: true,
356
+ }
357
+ )
351
358
 
352
- return fetchSanity(query, false);
359
+ return fetchSanity(query, false)
353
360
  }
354
361
 
355
362
  /**
@@ -365,25 +372,25 @@ export async function fetchByRailContentId(id, contentType) {
365
372
  * .catch(error => console.error(error));
366
373
  */
367
374
  export async function fetchByRailContentIds(ids, contentType = undefined) {
368
- const idsString = ids.join(',');
375
+ const idsString = ids.join(',')
369
376
 
370
- const query = `*[railcontent_id in [${idsString}]]{
377
+ const query = `*[railcontent_id in [${idsString}]]{
371
378
  ${getFieldsForContentType(contentType)}
372
379
  }`
373
- const results = await fetchSanity(query, true);
374
-
375
- const sortFuction = function compare(a,b){
376
- const indexA = ids.indexOf(a['id']);
377
- const indexB = ids.indexOf(b['id'])
378
- if(indexA === indexB) return 0;
379
- if(indexA > indexB) return 1;
380
- return -1;
381
- }
380
+ const results = await fetchSanity(query, true)
382
381
 
383
- // Sort results to match the order of the input IDs
384
- const sortedResults = results.sort(sortFuction);
382
+ const sortFuction = function compare(a, b) {
383
+ const indexA = ids.indexOf(a['id'])
384
+ const indexB = ids.indexOf(b['id'])
385
+ if (indexA === indexB) return 0
386
+ if (indexA > indexB) return 1
387
+ return -1
388
+ }
385
389
 
386
- return sortedResults;
390
+ // Sort results to match the order of the input IDs
391
+ const sortedResults = results.sort(sortFuction)
392
+
393
+ return sortedResults
387
394
  }
388
395
 
389
396
  /**
@@ -418,87 +425,96 @@ export async function fetchByRailContentIds(ids, contentType = undefined) {
418
425
  * .then(content => console.log(content))
419
426
  * .catch(error => console.error(error));
420
427
  */
421
- export async function fetchAll(brand, type, {
428
+ export async function fetchAll(
429
+ brand,
430
+ type,
431
+ {
422
432
  page = 1,
423
433
  limit = 10,
424
- searchTerm = "",
425
- sort = "-published_on",
434
+ searchTerm = '',
435
+ sort = '-published_on',
426
436
  includedFields = [],
427
- groupBy = "",
437
+ groupBy = '',
428
438
  progressIds = undefined,
429
439
  useDefaultFields = true,
430
440
  customFields = [],
431
- progress = "all"
432
- } = {}) {
433
- let customResults = await handleCustomFetchAll(brand, type, {
434
- page,
435
- limit,
436
- searchTerm,
437
- sort,
438
- includedFields,
439
- groupBy,
440
- progressIds,
441
- useDefaultFields,
442
- customFields,
443
- progress
444
- });
445
- if (customResults) {
446
- return customResults;
447
- }
448
- let config = contentTypeConfig[type] ?? {};
449
- let additionalFields = config?.fields ?? [];
450
- let isGroupByOneToOne = (groupBy ? config?.relationships?.[groupBy]?.isOneToOne : false) ?? false;
451
- let webUrlPathType = config?.slug ?? type;
452
- const start = (page - 1) * limit;
453
- const end = start + limit;
454
- let bypassStatusAndPublishedValidation = (type == 'instructor' || groupBy == 'artist' || groupBy == 'genre' || groupBy == 'instructor');
455
- let bypassPermissions = bypassStatusAndPublishedValidation;
456
- // Construct the type filter
457
- let typeFilter;
458
-
459
- if (type === 'archives') {
460
- typeFilter = `&& status == "archived"`;
461
- bypassStatusAndPublishedValidation = true;
462
- } else if(type === 'pack'){
463
- typeFilter = `&& (_type == 'pack' || _type == 'semester-pack')`;
464
- } else {
465
- typeFilter = type ? `&& _type == '${type}'` : progress === 'in progress' || progress === 'completed' ? " && (_type != 'challenge-part' && _type != 'challenge')" : "";
466
- }
467
-
468
- // Construct the search filter
469
- const searchFilter = searchTerm
470
- ? groupBy !== "" ?
471
- `&& (^.name match "${searchTerm}*" || title match "${searchTerm}*")`
472
- : `&& (artist->name match "${searchTerm}*" || instructor[]->name match "${searchTerm}*" || title match "${searchTerm}*" || name match "${searchTerm}*")`
473
- : "";
474
-
475
- // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
476
- const includedFieldsFilter = includedFields.length > 0
477
- ? filtersToGroq(includedFields)
478
- : "";
479
-
480
- // limits the results to supplied progressIds for started & completed filters
481
- const progressFilter = await getProgressFilter(progress, progressIds);
482
-
483
- // Determine the sort order
484
- const sortOrder = getSortOrder(sort, brand, groupBy);
485
-
486
- let fields = useDefaultFields ? customFields.concat(DEFAULT_FIELDS, additionalFields) : customFields;
487
- let fieldsString = fields.join(',');
488
-
489
- let customFilter = '';
490
- if (type == 'instructor') {
491
- customFilter = '&& coach_card_image != null'
492
- }
493
- // Determine the group by clause
494
- let query = "";
495
- let entityFieldsString = "";
496
- let filter = "";
497
- if (groupBy !== "" && isGroupByOneToOne) {
498
- const webUrlPath = 'artists';
499
- const lessonsFilter = `_type == '${type}' && brand == '${brand}' && ^._id == ${groupBy}._ref ${searchFilter} ${includedFieldsFilter} ${progressFilter} ${customFilter}`;
500
- const lessonsFilterWithRestrictions = await new FilterBuilder(lessonsFilter).buildFilter();
501
- entityFieldsString = `
441
+ progress = 'all',
442
+ } = {}
443
+ ) {
444
+ let customResults = await handleCustomFetchAll(brand, type, {
445
+ page,
446
+ limit,
447
+ searchTerm,
448
+ sort,
449
+ includedFields,
450
+ groupBy,
451
+ progressIds,
452
+ useDefaultFields,
453
+ customFields,
454
+ progress,
455
+ })
456
+ if (customResults) {
457
+ return customResults
458
+ }
459
+ let config = contentTypeConfig[type] ?? {}
460
+ let additionalFields = config?.fields ?? []
461
+ let isGroupByOneToOne = (groupBy ? config?.relationships?.[groupBy]?.isOneToOne : false) ?? false
462
+ let webUrlPathType = config?.slug ?? type
463
+ const start = (page - 1) * limit
464
+ const end = start + limit
465
+ let bypassStatusAndPublishedValidation =
466
+ type == 'instructor' || groupBy == 'artist' || groupBy == 'genre' || groupBy == 'instructor'
467
+ let bypassPermissions = bypassStatusAndPublishedValidation
468
+ // Construct the type filter
469
+ let typeFilter
470
+
471
+ if (type === 'archives') {
472
+ typeFilter = `&& status == "archived"`
473
+ bypassStatusAndPublishedValidation = true
474
+ } else if (type === 'pack') {
475
+ typeFilter = `&& (_type == 'pack' || _type == 'semester-pack')`
476
+ } else {
477
+ typeFilter = type
478
+ ? `&& _type == '${type}'`
479
+ : progress === 'in progress' || progress === 'completed'
480
+ ? " && (_type != 'challenge-part' && _type != 'challenge')"
481
+ : ''
482
+ }
483
+
484
+ // Construct the search filter
485
+ const searchFilter = searchTerm
486
+ ? groupBy !== ''
487
+ ? `&& (^.name match "${searchTerm}*" || title match "${searchTerm}*")`
488
+ : `&& (artist->name match "${searchTerm}*" || instructor[]->name match "${searchTerm}*" || title match "${searchTerm}*" || name match "${searchTerm}*")`
489
+ : ''
490
+
491
+ // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
492
+ const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
493
+
494
+ // limits the results to supplied progressIds for started & completed filters
495
+ const progressFilter = await getProgressFilter(progress, progressIds)
496
+
497
+ // Determine the sort order
498
+ const sortOrder = getSortOrder(sort, brand, groupBy)
499
+
500
+ let fields = useDefaultFields
501
+ ? customFields.concat(DEFAULT_FIELDS, additionalFields)
502
+ : customFields
503
+ let fieldsString = fields.join(',')
504
+
505
+ let customFilter = ''
506
+ if (type == 'instructor') {
507
+ customFilter = '&& coach_card_image != null'
508
+ }
509
+ // Determine the group by clause
510
+ let query = ''
511
+ let entityFieldsString = ''
512
+ let filter = ''
513
+ if (groupBy !== '' && isGroupByOneToOne) {
514
+ const webUrlPath = 'artists'
515
+ const lessonsFilter = `_type == '${type}' && brand == '${brand}' && ^._id == ${groupBy}._ref ${searchFilter} ${includedFieldsFilter} ${progressFilter} ${customFilter}`
516
+ const lessonsFilterWithRestrictions = await new FilterBuilder(lessonsFilter).buildFilter()
517
+ entityFieldsString = `
502
518
  'id': railcontent_id,
503
519
  'type': _type,
504
520
  name,
@@ -509,16 +525,16 @@ export async function fetchAll(brand, type, {
509
525
  ${fieldsString},
510
526
  ${groupBy}
511
527
  }[0...20]
512
- `;
513
- filter = `_type == '${groupBy}' && count(*[${lessonsFilterWithRestrictions}]._id) > 0`;
514
- } else if (groupBy !== "") {
515
- const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
528
+ `
529
+ filter = `_type == '${groupBy}' && count(*[${lessonsFilterWithRestrictions}]._id) > 0`
530
+ } else if (groupBy !== '') {
531
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
516
532
 
517
- const webUrlPath = (groupBy == 'genre') ? '/genres' : '';
518
- const lessonsFilter = `brand == '${brand}' && ^._id in ${groupBy}[]._ref ${typeFilter} ${searchFilter} ${includedFieldsFilter} ${progressFilter} ${customFilter}`;
519
- const lessonsFilterWithRestrictions = await new FilterBuilder(lessonsFilter).buildFilter();
533
+ const webUrlPath = groupBy == 'genre' ? '/genres' : ''
534
+ const lessonsFilter = `brand == '${brand}' && ^._id in ${groupBy}[]._ref ${typeFilter} ${searchFilter} ${includedFieldsFilter} ${progressFilter} ${customFilter}`
535
+ const lessonsFilterWithRestrictions = await new FilterBuilder(lessonsFilter).buildFilter()
520
536
 
521
- entityFieldsString = `
537
+ entityFieldsString = `
522
538
  'id': railcontent_id,
523
539
  'type': _type,
524
540
  name,
@@ -529,12 +545,12 @@ export async function fetchAll(brand, type, {
529
545
  ${fieldsString},
530
546
  'lesson_count': coalesce(count(child[${childrenFilter}]->), 0) ,
531
547
  ${groupBy}
532
- }[0...20]`;
533
- filter = `_type == '${groupBy}' && count(*[${lessonsFilterWithRestrictions}]._id) > 0`;
534
- } else {
535
- filter = `brand == "${brand}" ${typeFilter} ${searchFilter} ${includedFieldsFilter} ${progressFilter} ${customFilter}`
536
- const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
537
- entityFieldsString = ` ${fieldsString},
548
+ }[0...20]`
549
+ filter = `_type == '${groupBy}' && count(*[${lessonsFilterWithRestrictions}]._id) > 0`
550
+ } else {
551
+ filter = `brand == "${brand}" ${typeFilter} ${searchFilter} ${includedFieldsFilter} ${progressFilter} ${customFilter}`
552
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
553
+ entityFieldsString = ` ${fieldsString},
538
554
  'lesson_count': coalesce(count(child[${childrenFilter}]->), 0) ,
539
555
  'length_in_seconds': coalesce(
540
556
  math::sum(
@@ -543,24 +559,21 @@ export async function fetchAll(brand, type, {
543
559
  )
544
560
  ),
545
561
  length_in_seconds
546
- ),`;
547
- }
548
-
549
- const filterWithRestrictions = await new FilterBuilder(filter, {
550
- bypassStatuses: bypassStatusAndPublishedValidation,
551
- bypassPermissions: bypassPermissions,
552
- bypassPublishedDateRestriction: bypassStatusAndPublishedValidation
553
- }).buildFilter();
554
- query = buildEntityAndTotalQuery(
555
- filterWithRestrictions,
556
- entityFieldsString,
557
- {
558
- sortOrder: sortOrder,
559
- start: start,
560
- end: end,
561
- });
562
-
563
- return fetchSanity(query, true);
562
+ ),`
563
+ }
564
+
565
+ const filterWithRestrictions = await new FilterBuilder(filter, {
566
+ bypassStatuses: bypassStatusAndPublishedValidation,
567
+ bypassPermissions: bypassPermissions,
568
+ bypassPublishedDateRestriction: bypassStatusAndPublishedValidation,
569
+ }).buildFilter()
570
+ query = buildEntityAndTotalQuery(filterWithRestrictions, entityFieldsString, {
571
+ sortOrder: sortOrder,
572
+ start: start,
573
+ end: end,
574
+ })
575
+
576
+ return fetchSanity(query, true)
564
577
  }
565
578
 
566
579
  /**
@@ -580,83 +593,110 @@ export async function fetchAll(brand, type, {
580
593
  * @param {string} [params.progress="all"] - An string representing which progress filter to use ("all", "in progress", "complete", "not started").
581
594
  * @returns {Promise<Object|null>} - The fetched content data or null if not found.
582
595
  */
583
- async function handleCustomFetchAll(brand, type, {
596
+ async function handleCustomFetchAll(
597
+ brand,
598
+ type,
599
+ {
584
600
  page = 1,
585
601
  limit = 10,
586
- searchTerm = "",
587
- sort = "-published_on",
602
+ searchTerm = '',
603
+ sort = '-published_on',
588
604
  includedFields = [],
589
- groupBy = "",
605
+ groupBy = '',
590
606
  progressIds = undefined,
591
607
  useDefaultFields = true,
592
608
  customFields = [],
593
- progress = "all"
594
- } = {}) {
595
- if (type === 'challenge') {
596
- if (groupBy === 'completed') {
597
- const completedIds = await fetchCompletedChallenges(brand, page, limit);
598
- return fetchAll(brand, type,
599
- {
600
- page,
601
- limit,
602
- searchTerm,
603
- sort,
604
- includedFields,
605
- groupBy: '',
606
- progressIds: completedIds,
607
- useDefaultFields,
608
- customFields,
609
- progress
610
- });
611
- } else if (groupBy === 'owned') {
612
- const ownedIds = await fetchOwnedChallenges(brand, page, limit);
613
- return fetchAll(brand, type,
614
- {
615
- page,
616
- limit,
617
- searchTerm,
618
- sort,
619
- includedFields,
620
- groupBy: '',
621
- progressIds: ownedIds,
622
- useDefaultFields,
623
- customFields,
624
- progress
625
- });
626
- } else if (groupBy === 'difficulty_string') {
627
- return fetchChallengesByDifficulty(brand, type, page, limit, searchTerm, sort, includedFields, groupBy, progressIds, useDefaultFields, customFields, progress);
628
- }
609
+ progress = 'all',
610
+ } = {}
611
+ ) {
612
+ if (type === 'challenge') {
613
+ if (groupBy === 'completed') {
614
+ const completedIds = await fetchCompletedChallenges(brand, page, limit)
615
+ return fetchAll(brand, type, {
616
+ page,
617
+ limit,
618
+ searchTerm,
619
+ sort,
620
+ includedFields,
621
+ groupBy: '',
622
+ progressIds: completedIds,
623
+ useDefaultFields,
624
+ customFields,
625
+ progress,
626
+ })
627
+ } else if (groupBy === 'owned') {
628
+ const ownedIds = await fetchOwnedChallenges(brand, page, limit)
629
+ return fetchAll(brand, type, {
630
+ page,
631
+ limit,
632
+ searchTerm,
633
+ sort,
634
+ includedFields,
635
+ groupBy: '',
636
+ progressIds: ownedIds,
637
+ useDefaultFields,
638
+ customFields,
639
+ progress,
640
+ })
641
+ } else if (groupBy === 'difficulty_string') {
642
+ return fetchChallengesByDifficulty(
643
+ brand,
644
+ type,
645
+ page,
646
+ limit,
647
+ searchTerm,
648
+ sort,
649
+ includedFields,
650
+ groupBy,
651
+ progressIds,
652
+ useDefaultFields,
653
+ customFields,
654
+ progress
655
+ )
629
656
  }
630
- return null;
657
+ }
658
+ return null
631
659
  }
632
660
 
633
- async function fetchChallengesByDifficulty(brand, type, page, limit, searchTerm, sort, includedFields, groupBy, progressIds, useDefaultFields, customFields, progress) {
634
- let config = contentTypeConfig['challenge'] ?? {};
635
- let additionalFields = config?.fields ?? [];
636
-
637
- // Construct the search filter
638
- const searchFilter = searchTerm
639
- ? groupBy !== "" ?
640
- `&& (^.name match "${searchTerm}*" || title match "${searchTerm}*")`
641
- : `&& (artist->name match "${searchTerm}*" || instructor[]->name match "${searchTerm}*" || title match "${searchTerm}*" || name match "${searchTerm}*")`
642
- : "";
661
+ async function fetchChallengesByDifficulty(
662
+ brand,
663
+ type,
664
+ page,
665
+ limit,
666
+ searchTerm,
667
+ sort,
668
+ includedFields,
669
+ groupBy,
670
+ progressIds,
671
+ useDefaultFields,
672
+ customFields,
673
+ progress
674
+ ) {
675
+ let config = contentTypeConfig['challenge'] ?? {}
676
+ let additionalFields = config?.fields ?? []
643
677
 
644
- // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
645
- const includedFieldsFilter = includedFields.length > 0
646
- ? filtersToGroq(includedFields)
647
- : "";
678
+ // Construct the search filter
679
+ const searchFilter = searchTerm
680
+ ? groupBy !== ''
681
+ ? `&& (^.name match "${searchTerm}*" || title match "${searchTerm}*")`
682
+ : `&& (artist->name match "${searchTerm}*" || instructor[]->name match "${searchTerm}*" || title match "${searchTerm}*" || name match "${searchTerm}*")`
683
+ : ''
648
684
 
649
- // limits the results to supplied progressIds for started & completed filters
650
- const progressFilter = await getProgressFilter(progress, progressIds);
685
+ // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
686
+ const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
651
687
 
652
- let fields = useDefaultFields ? customFields.concat(DEFAULT_FIELDS, additionalFields) : customFields;
653
- let fieldsString = fields.join(',');
688
+ // limits the results to supplied progressIds for started & completed filters
689
+ const progressFilter = await getProgressFilter(progress, progressIds)
654
690
 
691
+ let fields = useDefaultFields
692
+ ? customFields.concat(DEFAULT_FIELDS, additionalFields)
693
+ : customFields
694
+ let fieldsString = fields.join(',')
655
695
 
656
- const lessonsFilter = `_type == 'challenge' && brand == '${brand}' && ^.name == difficulty_string ${searchFilter} ${includedFieldsFilter} ${progressFilter}`;
657
- const lessonsFilterWithRestrictions = await new FilterBuilder(lessonsFilter).buildFilter();
696
+ const lessonsFilter = `_type == 'challenge' && brand == '${brand}' && ^.name == difficulty_string ${searchFilter} ${includedFieldsFilter} ${progressFilter}`
697
+ const lessonsFilterWithRestrictions = await new FilterBuilder(lessonsFilter).buildFilter()
658
698
 
659
- const query = `{
699
+ const query = `{
660
700
  "entity": [
661
701
  {"name": "All"},
662
702
  {"name": "Novice"},
@@ -674,62 +714,61 @@ async function fetchChallengesByDifficulty(brand, type, page, limit, searchTerm,
674
714
  }[0...20]
675
715
  },
676
716
  "total": 0
677
- }`;
678
- let data = await fetchSanity(query, true);
679
- data.entity = data.entity.filter(function (difficulty) {
680
- return difficulty.lessons.length > 0;
681
- });
682
- return data;
717
+ }`
718
+ let data = await fetchSanity(query, true)
719
+ data.entity = data.entity.filter(function (difficulty) {
720
+ return difficulty.lessons.length > 0
721
+ })
722
+ return data
683
723
  }
684
724
 
685
725
  async function getProgressFilter(progress, progressIds) {
686
- switch (progress) {
687
- case "all":
688
- return progressIds !== undefined ?
689
- `&& railcontent_id in [${progressIds.join(',')}]` : "";
690
- case "in progress": {
691
- const ids = await getAllStarted();
692
- return `&& railcontent_id in [${ids.join(',')}]`;
693
- }
694
- case "completed": {
695
- const ids = await getAllCompleted();
696
- return `&& railcontent_id in [${ids.join(',')}]`;
697
- }
698
- case "not started": {
699
- const ids = await getAllStartedOrCompleted();
700
- return `&& !(railcontent_id in [${ids.join(',')}])`;
701
- }
702
- default:
703
- throw new Error(`'${progress}' progress option not implemented`);
726
+ switch (progress) {
727
+ case 'all':
728
+ return progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
729
+ case 'in progress': {
730
+ const ids = await getAllStarted()
731
+ return `&& railcontent_id in [${ids.join(',')}]`
732
+ }
733
+ case 'completed': {
734
+ const ids = await getAllCompleted()
735
+ return `&& railcontent_id in [${ids.join(',')}]`
704
736
  }
737
+ case 'not started': {
738
+ const ids = await getAllStartedOrCompleted()
739
+ return `&& !(railcontent_id in [${ids.join(',')}])`
740
+ }
741
+ default:
742
+ throw new Error(`'${progress}' progress option not implemented`)
743
+ }
705
744
  }
706
745
 
707
746
  export function getSortOrder(sort = '-published_on', brand, groupBy) {
708
- // Determine the sort order
709
- let sortOrder = '';
710
- const isDesc = sort.startsWith('-');
711
- sort = isDesc ? sort.substring(1) : sort;
712
- switch (sort) {
713
- case "slug":
714
- sortOrder = groupBy ? 'name' : "title";
715
- break;
716
- case "name":
717
- sortOrder = sort;
718
- break;
719
- case "popularity":
720
- if (groupBy == "artist" || groupBy == "genre") {
721
- sortOrder = isDesc ? `coalesce(popularity.${brand}, -1)` : "popularity";
722
- } else {
723
- sortOrder = isDesc ? "coalesce(popularity, -1)" : "popularity";
724
- }
725
- break;
726
- case "published_on":
727
- default:
728
- sortOrder = "published_on";
729
- break;
730
- }
731
- sortOrder += isDesc ? ' desc' : ' asc';
732
- return sortOrder;
747
+ // Determine the sort order
748
+ let sortOrder = ''
749
+ const isDesc = sort.startsWith('-')
750
+ sort = isDesc ? sort.substring(1) : sort
751
+ switch (sort) {
752
+ case 'slug':
753
+ sortOrder = groupBy ? 'name' : 'title'
754
+ break
755
+ case 'name':
756
+ sortOrder = sort
757
+ break
758
+ case 'popularity':
759
+ if (groupBy == 'artist' || groupBy == 'genre') {
760
+ sortOrder = isDesc ? `coalesce(popularity.${brand}, -1)` : 'popularity'
761
+ } else {
762
+ sortOrder = isDesc ? 'coalesce(popularity, -1)' : 'popularity'
763
+ }
764
+ break
765
+ case 'published_on':
766
+ default:
767
+ sortOrder = 'published_on'
768
+ break
769
+ }
770
+ sortOrder += isDesc ? ' desc' : ' asc'
771
+ return sortOrder
733
772
  }
734
773
 
735
774
  /**
@@ -764,42 +803,48 @@ export function getSortOrder(sort = '-published_on', brand, groupBy) {
764
803
  * .catch(error => console.error(error));
765
804
  */
766
805
  export async function fetchAllFilterOptions(
767
- brand,
768
- filters = [],
769
- style,
770
- artist,
771
- contentType,
772
- term,
773
- progressIds,
774
- coachId,
775
- includeTabs = false,
806
+ brand,
807
+ filters = [],
808
+ style,
809
+ artist,
810
+ contentType,
811
+ term,
812
+ progressIds,
813
+ coachId,
814
+ includeTabs = false
776
815
  ) {
777
- if (coachId && contentType !== 'coach-lessons') {
778
- throw new Error(`Invalid contentType: '${contentType}' for coachId. It must be 'coach-lessons'.`);
779
- }
780
-
781
- const includedFieldsFilter = filters?.length ? filtersToGroq(filters) : undefined;
782
- const progressFilter = progressIds ? `&& railcontent_id in [${progressIds.join(',')}]` : "";
783
- const isAdmin = (await fetchUserPermissions()).isAdmin;
784
-
785
- const constructCommonFilter = (excludeFilter) => {
786
- const filterWithoutOption = excludeFilter ? filtersToGroq(filters, excludeFilter) : includedFieldsFilter;
787
- const statusFilter = ' && status == "published"';
788
- const includeStatusFilter = !isAdmin && !['instructor', 'artist', 'genre'].includes(contentType);
789
-
790
- return coachId
791
- ? `brand == '${brand}' && status == "published" && references(*[_type=='instructor' && railcontent_id == ${coachId}]._id) ${filterWithoutOption || ''} ${term ? ` && (title match "${term}" || album match "${term}" || artist->name match "${term}" || genre[]->name match "${term}")` : ''}`
792
- : `_type == '${contentType}' && brand == "${brand}"${includeStatusFilter ? statusFilter : ''}${style && excludeFilter !== "style" ? ` && '${style}' in genre[]->name` : ''}${artist && excludeFilter !== "artist" ? ` && artist->name == '${artist}'` : ''} ${progressFilter} ${filterWithoutOption || ''} ${term ? ` && (title match "${term}" || album match "${term}" || artist->name match "${term}" || genre[]->name match "${term}")` : ''}`;
793
- };
794
-
795
- const metaData = processMetadata(brand, contentType, true);
796
- const allowableFilters = metaData?.allowableFilters || [];
797
- const tabs = metaData?.tabs || [];
798
- const catalogName = metaData?.shortname || metaData?.name;
799
-
800
- const dynamicFilterOptions = allowableFilters.map(filter => getFilterOptions(filter, constructCommonFilter(filter), contentType, brand)).join(' ');
801
-
802
- const query = `
816
+ if (coachId && contentType !== 'coach-lessons') {
817
+ throw new Error(
818
+ `Invalid contentType: '${contentType}' for coachId. It must be 'coach-lessons'.`
819
+ )
820
+ }
821
+
822
+ const includedFieldsFilter = filters?.length ? filtersToGroq(filters) : undefined
823
+ const progressFilter = progressIds ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
824
+ const isAdmin = (await fetchUserPermissions()).isAdmin
825
+
826
+ const constructCommonFilter = (excludeFilter) => {
827
+ const filterWithoutOption = excludeFilter
828
+ ? filtersToGroq(filters, excludeFilter)
829
+ : includedFieldsFilter
830
+ const statusFilter = ' && status == "published"'
831
+ const includeStatusFilter = !isAdmin && !['instructor', 'artist', 'genre'].includes(contentType)
832
+
833
+ return coachId
834
+ ? `brand == '${brand}' && status == "published" && references(*[_type=='instructor' && railcontent_id == ${coachId}]._id) ${filterWithoutOption || ''} ${term ? ` && (title match "${term}" || album match "${term}" || artist->name match "${term}" || genre[]->name match "${term}")` : ''}`
835
+ : `_type == '${contentType}' && brand == "${brand}"${includeStatusFilter ? statusFilter : ''}${style && excludeFilter !== 'style' ? ` && '${style}' in genre[]->name` : ''}${artist && excludeFilter !== 'artist' ? ` && artist->name == '${artist}'` : ''} ${progressFilter} ${filterWithoutOption || ''} ${term ? ` && (title match "${term}" || album match "${term}" || artist->name match "${term}" || genre[]->name match "${term}")` : ''}`
836
+ }
837
+
838
+ const metaData = processMetadata(brand, contentType, true)
839
+ const allowableFilters = metaData?.allowableFilters || []
840
+ const tabs = metaData?.tabs || []
841
+ const catalogName = metaData?.shortname || metaData?.name
842
+
843
+ const dynamicFilterOptions = allowableFilters
844
+ .map((filter) => getFilterOptions(filter, constructCommonFilter(filter), contentType, brand))
845
+ .join(' ')
846
+
847
+ const query = `
803
848
  {
804
849
  "meta": {
805
850
  "totalResults": count(*[${constructCommonFilter()}
@@ -808,11 +853,11 @@ export async function fetchAllFilterOptions(
808
853
  ${dynamicFilterOptions}
809
854
  }
810
855
  }
811
- }`;
856
+ }`
812
857
 
813
- const results = await fetchSanity(query, true, {processNeedAccess: false});
858
+ const results = await fetchSanity(query, true, { processNeedAccess: false })
814
859
 
815
- return includeTabs ? {...results, tabs, catalogName} : results;
860
+ return includeTabs ? { ...results, tabs, catalogName } : results
816
861
  }
817
862
 
818
863
  /**
@@ -821,17 +866,17 @@ export async function fetchAllFilterOptions(
821
866
  * @returns {Promise<Object|null>} - The fetched foundation data or null if not found.
822
867
  */
823
868
  export async function fetchFoundation(slug) {
824
- const filterParams = {};
825
- const query = await buildQuery(
826
- `_type == 'foundation' && slug.current == "${slug}"`,
827
- filterParams,
828
- getFieldsForContentType('foundation'),
829
- {
830
- sortOrder: 'published_on asc',
831
- isSingle: true,
832
- }
833
- );
834
- return fetchSanity(query, false);
869
+ const filterParams = {}
870
+ const query = await buildQuery(
871
+ `_type == 'foundation' && slug.current == "${slug}"`,
872
+ filterParams,
873
+ getFieldsForContentType('foundation'),
874
+ {
875
+ sortOrder: 'published_on asc',
876
+ isSingle: true,
877
+ }
878
+ )
879
+ return fetchSanity(query, false)
835
880
  }
836
881
 
837
882
  /**
@@ -841,9 +886,9 @@ export async function fetchFoundation(slug) {
841
886
  * @returns {Promise<Object|null>} - The fetched methods data or null if not found.
842
887
  */
843
888
  export async function fetchMethod(brand, slug) {
844
- const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
889
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
845
890
 
846
- const query = `*[_type == 'learning-path' && brand == "${brand}" && slug.current == "${slug}"] {
891
+ const query = `*[_type == 'learning-path' && brand == "${brand}" && slug.current == "${slug}"] {
847
892
  "description": ${descriptionField},
848
893
  "instructors":instructor[]->name,
849
894
  published_on,
@@ -880,7 +925,7 @@ export async function fetchMethod(brand, slug) {
880
925
  total_xp
881
926
  }
882
927
  } | order(published_on asc)`
883
- return fetchSanity(query, false);
928
+ return fetchSanity(query, false)
884
929
  }
885
930
 
886
931
  /**
@@ -889,9 +934,9 @@ export async function fetchMethod(brand, slug) {
889
934
  * @returns {Promise<Object|null>} - The fetched next lesson data or null if not found.
890
935
  */
891
936
  export async function fetchMethodChildren(railcontentId) {
892
- const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
937
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
893
938
 
894
- const query = `*[railcontent_id == ${railcontentId}]{
939
+ const query = `*[railcontent_id == ${railcontentId}]{
895
940
  "child_count":coalesce(count(child[${childrenFilter}]->), 0),
896
941
  "id": railcontent_id,
897
942
  "description": ${descriptionField},
@@ -909,8 +954,8 @@ export async function fetchMethodChildren(railcontentId) {
909
954
  'children': child[(${childrenFilter})]->{
910
955
  ${getFieldsForContentType('method')}
911
956
  },
912
- }[0..1]`;
913
- return fetchSanity(query, true);
957
+ }[0..1]`
958
+ return fetchSanity(query, true)
914
959
  }
915
960
 
916
961
  /**
@@ -924,21 +969,21 @@ export async function fetchMethodChildren(railcontentId) {
924
969
  * .catch(error => console.error(error));
925
970
  */
926
971
  export async function fetchMethodPreviousNextLesson(railcontentId, methodId) {
927
- const sortedChildren = await fetchMethodChildrenIds(methodId);
928
- const index = sortedChildren.indexOf(Number(railcontentId));
929
- let nextId = sortedChildren[index + 1];
930
- let previousId = sortedChildren[index - 1];
931
- let ids = [];
932
- if (nextId) ids.push(nextId);
933
- if (previousId) ids.push(previousId);
934
- let nextPrev = await fetchByRailContentIds(ids);
935
- const nextLesson = nextPrev.find((elem) => {
936
- return elem['id'] === nextId
937
- });
938
- const prevLesson = nextPrev.find((elem) => {
939
- return elem['id'] === previousId
940
- });
941
- return {nextLesson, prevLesson};
972
+ const sortedChildren = await fetchMethodChildrenIds(methodId)
973
+ const index = sortedChildren.indexOf(Number(railcontentId))
974
+ let nextId = sortedChildren[index + 1]
975
+ let previousId = sortedChildren[index - 1]
976
+ let ids = []
977
+ if (nextId) ids.push(nextId)
978
+ if (previousId) ids.push(previousId)
979
+ let nextPrev = await fetchByRailContentIds(ids)
980
+ const nextLesson = nextPrev.find((elem) => {
981
+ return elem['id'] === nextId
982
+ })
983
+ const prevLesson = nextPrev.find((elem) => {
984
+ return elem['id'] === previousId
985
+ })
986
+ return { nextLesson, prevLesson }
942
987
  }
943
988
 
944
989
  /**
@@ -947,9 +992,9 @@ export async function fetchMethodPreviousNextLesson(railcontentId, methodId) {
947
992
  * @returns {Promise<Array<Object>|null>} - The fetched children data or null if not found.
948
993
  */
949
994
  export async function fetchMethodChildrenIds(railcontentId) {
950
- const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
995
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
951
996
 
952
- const query = `*[ railcontent_id == ${railcontentId}]{
997
+ const query = `*[ railcontent_id == ${railcontentId}]{
953
998
  'children': child[${childrenFilter}]-> {
954
999
  'id': railcontent_id,
955
1000
  'type' : _type,
@@ -962,22 +1007,22 @@ export async function fetchMethodChildrenIds(railcontentId) {
962
1007
  }
963
1008
  }
964
1009
  }
965
- }`;
966
- let allChildren = await fetchSanity(query, false);
967
- return getChildrenToDepth(allChildren, 4);
1010
+ }`
1011
+ let allChildren = await fetchSanity(query, false)
1012
+ return getChildrenToDepth(allChildren, 4)
968
1013
  }
969
1014
 
970
1015
  function getChildrenToDepth(parent, depth = 1) {
971
- let allChildrenIds = [];
972
- if (parent && parent['children'] && depth > 0) {
973
- parent['children'].forEach((child) => {
974
- if (!child['children']) {
975
- allChildrenIds.push(child['id']);
976
- }
977
- allChildrenIds = allChildrenIds.concat(getChildrenToDepth(child, depth - 1));
978
- })
979
- }
980
- return allChildrenIds;
1016
+ let allChildrenIds = []
1017
+ if (parent && parent['children'] && depth > 0) {
1018
+ parent['children'].forEach((child) => {
1019
+ if (!child['children']) {
1020
+ allChildrenIds.push(child['id'])
1021
+ }
1022
+ allChildrenIds = allChildrenIds.concat(getChildrenToDepth(child, depth - 1))
1023
+ })
1024
+ }
1025
+ return allChildrenIds
981
1026
  }
982
1027
 
983
1028
  /**
@@ -986,31 +1031,31 @@ function getChildrenToDepth(parent, depth = 1) {
986
1031
  * @returns {Promise<Object|null>} - The fetched next and previous lesson data or null if found.
987
1032
  */
988
1033
  export async function fetchNextPreviousLesson(railcontentId) {
989
- const document = await fetchLessonContent(railcontentId);
990
- if (document.parent_content_data && document.parent_content_data.length > 0) {
991
- const lastElement = document.parent_content_data[document.parent_content_data.length - 1];
992
- const results = await fetchMethodPreviousNextLesson(railcontentId, lastElement.id);
993
- return results;
994
- }
995
- const processedData = processMetadata(document.brand, document.type, true);
996
- let sortBy = processedData?.sortBy ?? 'published_on';
997
- const isDesc = sortBy.startsWith('-');
998
- sortBy = isDesc ? sortBy.substring(1) : sortBy;
999
- let sortValue = document[sortBy];
1000
- if (sortValue == null) {
1001
- sortBy = 'railcontent_id';
1002
- sortValue = document['railcontent_id'];
1003
- }
1004
- const isNumeric = !isNaN(sortValue);
1005
- let prevComparison = isNumeric ? `${sortBy} <= ${sortValue}` : `${sortBy} <= "${sortValue}"`;
1006
- let nextComparison = isNumeric ? `${sortBy} >= ${sortValue}` : `${sortBy} >= "${sortValue}"`;
1007
- const fields = getFieldsForContentType(document.type);
1008
- const query = `{
1034
+ const document = await fetchLessonContent(railcontentId)
1035
+ if (document.parent_content_data && document.parent_content_data.length > 0) {
1036
+ const lastElement = document.parent_content_data[document.parent_content_data.length - 1]
1037
+ const results = await fetchMethodPreviousNextLesson(railcontentId, lastElement.id)
1038
+ return results
1039
+ }
1040
+ const processedData = processMetadata(document.brand, document.type, true)
1041
+ let sortBy = processedData?.sortBy ?? 'published_on'
1042
+ const isDesc = sortBy.startsWith('-')
1043
+ sortBy = isDesc ? sortBy.substring(1) : sortBy
1044
+ let sortValue = document[sortBy]
1045
+ if (sortValue == null) {
1046
+ sortBy = 'railcontent_id'
1047
+ sortValue = document['railcontent_id']
1048
+ }
1049
+ const isNumeric = !isNaN(sortValue)
1050
+ let prevComparison = isNumeric ? `${sortBy} <= ${sortValue}` : `${sortBy} <= "${sortValue}"`
1051
+ let nextComparison = isNumeric ? `${sortBy} >= ${sortValue}` : `${sortBy} >= "${sortValue}"`
1052
+ const fields = getFieldsForContentType(document.type)
1053
+ const query = `{
1009
1054
  "prevLesson": *[brand == "${document.brand}" && status == "${document.status}" && _type == "${document.type}" && ${prevComparison} && railcontent_id != ${railcontentId}] | order(${sortBy} desc){${fields}}[0...1][0],
1010
1055
  "nextLesson": *[brand == "${document.brand}" && status == "${document.status}" && _type == "${document.type}" && ${nextComparison} && railcontent_id != ${railcontentId}] | order(${sortBy} asc){${fields}}[0...1][0]
1011
- }`;
1056
+ }`
1012
1057
 
1013
- return await fetchSanity(query, true);
1058
+ return await fetchSanity(query, true)
1014
1059
  }
1015
1060
 
1016
1061
  /**
@@ -1023,12 +1068,12 @@ export async function fetchNextPreviousLesson(railcontentId) {
1023
1068
  * .catch(error => console.error(error));
1024
1069
  */
1025
1070
  export async function jumpToContinueContent(railcontentId) {
1026
- const nextContent = await fetchNextContentDataForParent(railcontentId);
1027
- if (!nextContent || !nextContent.id) {
1028
- return null;
1029
- }
1030
- let next = await fetchByRailContentId(nextContent.id, nextContent.type);
1031
- return {next};
1071
+ const nextContent = await fetchNextContentDataForParent(railcontentId)
1072
+ if (!nextContent || !nextContent.id) {
1073
+ return null
1074
+ }
1075
+ let next = await fetchByRailContentId(nextContent.id, nextContent.type)
1076
+ return { next }
1032
1077
  }
1033
1078
 
1034
1079
  /**
@@ -1042,11 +1087,11 @@ export async function jumpToContinueContent(railcontentId) {
1042
1087
  * .catch(error => console.error(error));
1043
1088
  */
1044
1089
  export async function fetchLessonContent(railContentId) {
1045
- const filterParams = {isSingle: true, pullFutureContent: true};
1046
- // Format changes made to the `fields` object may also need to be reflected in Musora-web-platform SanityGateway.php $fields object
1047
- // Currently only for challenges and challenge lessons
1048
- // If you're unsure, message Adrian, or just add them.
1049
- const fields = `title,
1090
+ const filterParams = { isSingle: true, pullFutureContent: true }
1091
+ // Format changes made to the `fields` object may also need to be reflected in Musora-web-platform SanityGateway.php $fields object
1092
+ // Currently only for challenges and challenge lessons
1093
+ // If you're unsure, message Adrian, or just add them.
1094
+ const fields = `title,
1050
1095
  published_on,
1051
1096
  "type":_type,
1052
1097
  "resources": ${resourcesField},
@@ -1094,16 +1139,11 @@ export async function fetchLessonContent(railContentId) {
1094
1139
  "type": *[railcontent_id == ^.id][0]._type,
1095
1140
  },
1096
1141
  sort,
1097
- xp`;
1098
- const query = await buildQuery(
1099
- `railcontent_id == ${railContentId}`,
1100
- filterParams,
1101
- fields,
1102
- {
1103
- isSingle: true,
1104
- }
1105
- );
1106
- return fetchSanity(query, false);
1142
+ xp`
1143
+ const query = await buildQuery(`railcontent_id == ${railContentId}`, filterParams, fields, {
1144
+ isSingle: true,
1145
+ })
1146
+ return fetchSanity(query, false)
1107
1147
  }
1108
1148
 
1109
1149
  /**
@@ -1113,14 +1153,22 @@ export async function fetchLessonContent(railContentId) {
1113
1153
  * @returns {Promise<Array<Object>|null>} - The fetched related lessons data or null if not found.
1114
1154
  */
1115
1155
  export async function fetchRelatedLessons(railContentId, brand) {
1116
- const filterSameTypeAndSortOrder = await new FilterBuilder(`_type==^._type && _type in ${JSON.stringify(typeWithSortOrder)} && brand == "${brand}" && railcontent_id !=${railContentId}`).buildFilter();
1117
- const filterSameType = await new FilterBuilder(`_type==^._type && !(_type in ${JSON.stringify(typeWithSortOrder)}) && !(defined(parent_type)) && brand == "${brand}" && railcontent_id !=${railContentId}`).buildFilter();
1118
- const filterSongSameArtist = await new FilterBuilder(`_type=="song" && _type==^._type && brand == "${brand}" && references(^.artist->_id) && railcontent_id !=${railContentId}`).buildFilter();
1119
- const filterSongSameGenre = await new FilterBuilder(`_type=="song" && _type==^._type && brand == "${brand}" && references(^.genre[]->_id) && railcontent_id !=${railContentId}`).buildFilter();
1120
- const filterNeighbouringSiblings = await new FilterBuilder(`references(^._id)`).buildFilter();
1121
- const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
1122
-
1123
- const query = `*[railcontent_id == ${railContentId} && brand == "${brand}" && (!defined(permission) || references(*[_type=='permission']._id))]{
1156
+ const filterSameTypeAndSortOrder = await new FilterBuilder(
1157
+ `_type==^._type && _type in ${JSON.stringify(typeWithSortOrder)} && brand == "${brand}" && railcontent_id !=${railContentId}`
1158
+ ).buildFilter()
1159
+ const filterSameType = await new FilterBuilder(
1160
+ `_type==^._type && !(_type in ${JSON.stringify(typeWithSortOrder)}) && !(defined(parent_type)) && brand == "${brand}" && railcontent_id !=${railContentId}`
1161
+ ).buildFilter()
1162
+ const filterSongSameArtist = await new FilterBuilder(
1163
+ `_type=="song" && _type==^._type && brand == "${brand}" && references(^.artist->_id) && railcontent_id !=${railContentId}`
1164
+ ).buildFilter()
1165
+ const filterSongSameGenre = await new FilterBuilder(
1166
+ `_type=="song" && _type==^._type && brand == "${brand}" && references(^.genre[]->_id) && railcontent_id !=${railContentId}`
1167
+ ).buildFilter()
1168
+ const filterNeighbouringSiblings = await new FilterBuilder(`references(^._id)`).buildFilter()
1169
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
1170
+
1171
+ const query = `*[railcontent_id == ${railContentId} && brand == "${brand}" && (!defined(permission) || references(*[_type=='permission']._id))]{
1124
1172
  _type, parent_type, railcontent_id,
1125
1173
  "related_lessons" : array::unique([
1126
1174
  ...(*[${filterNeighbouringSiblings}][0].child[${childrenFilter}]->{_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail_url":thumbnail.asset->url, length_in_seconds, web_url_path, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}),
@@ -1129,8 +1177,8 @@ export async function fetchRelatedLessons(railContentId, brand) {
1129
1177
  ...(*[${filterSameTypeAndSortOrder}]{_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail_url":thumbnail.asset->url, length_in_seconds, web_url_path, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type, sort}|order(sort asc, title asc)[0...10]),
1130
1178
  ...(*[${filterSameType}]{_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail_url":thumbnail.asset->url, length_in_seconds, web_url_path, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}|order(published_on desc, title asc)[0...10])
1131
1179
  ,
1132
- ])[0...10]}`;
1133
- return fetchSanity(query, false);
1180
+ ])[0...10]}`
1181
+ return fetchSanity(query, false)
1134
1182
  }
1135
1183
 
1136
1184
  /**
@@ -1142,26 +1190,27 @@ export async function fetchRelatedLessons(railContentId, brand) {
1142
1190
  * @param {number} [params.limit=10] - The number of items per page.
1143
1191
  * @returns {Promise<Array<Object>|null>} - The fetched pack content data or null if not found.
1144
1192
  */
1145
- export async function fetchAllPacks(brand, sort = "-published_on", searchTerm = "", page = 1, limit = 10) {
1146
- const sortOrder = getSortOrder(sort, brand);
1147
- const filter = `(_type == 'pack' || _type == 'semester-pack') && brand == '${brand}' && title match "${searchTerm}*"`
1148
- const filterParams = {};
1149
- const fields = getFieldsForContentType('pack');
1150
- const start = (page - 1) * limit;
1151
- const end = start + limit;
1152
-
1153
- const query = await buildQuery(
1154
- filter,
1155
- filterParams,
1156
- getFieldsForContentType('pack'),
1157
- {
1158
- logo_image_url: 'logo_image_url.asset->url',
1159
- sortOrder: sortOrder,
1160
- start,
1161
- end
1162
- }
1163
- );
1164
- return fetchSanity(query, true);
1193
+ export async function fetchAllPacks(
1194
+ brand,
1195
+ sort = '-published_on',
1196
+ searchTerm = '',
1197
+ page = 1,
1198
+ limit = 10
1199
+ ) {
1200
+ const sortOrder = getSortOrder(sort, brand)
1201
+ const filter = `(_type == 'pack' || _type == 'semester-pack') && brand == '${brand}' && title match "${searchTerm}*"`
1202
+ const filterParams = {}
1203
+ const fields = getFieldsForContentType('pack')
1204
+ const start = (page - 1) * limit
1205
+ const end = start + limit
1206
+
1207
+ const query = await buildQuery(filter, filterParams, getFieldsForContentType('pack'), {
1208
+ logo_image_url: 'logo_image_url.asset->url',
1209
+ sortOrder: sortOrder,
1210
+ start,
1211
+ end,
1212
+ })
1213
+ return fetchSanity(query, true)
1165
1214
  }
1166
1215
 
1167
1216
  /**
@@ -1170,38 +1219,38 @@ export async function fetchAllPacks(brand, sort = "-published_on", searchTerm =
1170
1219
  * @returns {Promise<Array<Object>|null>} - The fetched pack content data or null if not found.
1171
1220
  */
1172
1221
  export async function fetchPackAll(railcontentId, type = 'pack') {
1173
- return fetchByRailContentId(railcontentId, type);
1222
+ return fetchByRailContentId(railcontentId, type)
1174
1223
  }
1175
1224
 
1176
1225
  export async function fetchLiveEvent(brand) {
1177
- //calendarIDs taken from addevent.php
1178
- // TODO import instructor calendars to Sanity
1179
- let defaultCalendarID = '';
1180
- switch (brand) {
1181
- case ('drumeo'):
1182
- defaultCalendarID = 'GP142387';
1183
- break;
1184
- case ('pianote'):
1185
- defaultCalendarID = 'be142408';
1186
- break;
1187
- case ('guitareo'):
1188
- defaultCalendarID = 'IJ142407';
1189
- break;
1190
- case ('singeo'):
1191
- defaultCalendarID = 'bk354284';
1192
- break;
1193
- default:
1194
- break;
1195
- }
1196
- let startDateTemp = new Date();
1197
- let endDateTemp = new Date();
1198
- startDateTemp= new Date (startDateTemp.setMinutes(startDateTemp.getMinutes() + 15));
1199
- endDateTemp = new Date(endDateTemp.setMinutes(endDateTemp.getMinutes() - 15));
1200
-
1201
- // See LiveStreamEventService.getCurrentOrNextLiveEvent for some nice complicated logic which I don't think is actually importart
1202
- // this has some +- on times
1203
- // But this query just finds the first scheduled event (sorted by start_time) that ends after now()
1204
- const query = `*[status == 'scheduled' && brand == '${brand}' && defined(live_event_start_time) && live_event_start_time <= '${getSanityDate(startDateTemp, false)}' && live_event_end_time >= '${getSanityDate(endDateTemp, false)}']{
1226
+ //calendarIDs taken from addevent.php
1227
+ // TODO import instructor calendars to Sanity
1228
+ let defaultCalendarID = ''
1229
+ switch (brand) {
1230
+ case 'drumeo':
1231
+ defaultCalendarID = 'GP142387'
1232
+ break
1233
+ case 'pianote':
1234
+ defaultCalendarID = 'be142408'
1235
+ break
1236
+ case 'guitareo':
1237
+ defaultCalendarID = 'IJ142407'
1238
+ break
1239
+ case 'singeo':
1240
+ defaultCalendarID = 'bk354284'
1241
+ break
1242
+ default:
1243
+ break
1244
+ }
1245
+ let startDateTemp = new Date()
1246
+ let endDateTemp = new Date()
1247
+ startDateTemp = new Date(startDateTemp.setMinutes(startDateTemp.getMinutes() + 15))
1248
+ endDateTemp = new Date(endDateTemp.setMinutes(endDateTemp.getMinutes() - 15))
1249
+
1250
+ // See LiveStreamEventService.getCurrentOrNextLiveEvent for some nice complicated logic which I don't think is actually importart
1251
+ // this has some +- on times
1252
+ // But this query just finds the first scheduled event (sorted by start_time) that ends after now()
1253
+ const query = `*[status == 'scheduled' && brand == '${brand}' && defined(live_event_start_time) && live_event_start_time <= '${getSanityDate(startDateTemp, false)}' && live_event_end_time >= '${getSanityDate(endDateTemp, false)}']{
1205
1254
  'slug': slug.current,
1206
1255
  'id': railcontent_id,
1207
1256
  live_event_start_time,
@@ -1218,8 +1267,8 @@ export async function fetchLiveEvent(brand) {
1218
1267
  web_url_path,
1219
1268
  },
1220
1269
  'videoId': coalesce(live_event_youtube_id, video.external_id),
1221
- } | order(live_event_start_time)[0...1]`;
1222
- return await fetchSanity(query, false, {processNeedAccess: false});
1270
+ } | order(live_event_start_time)[0...1]`
1271
+ return await fetchSanity(query, false, { processNeedAccess: false })
1223
1272
  }
1224
1273
 
1225
1274
  /**
@@ -1233,10 +1282,10 @@ export async function fetchLiveEvent(brand) {
1233
1282
  * .catch(error => console.error(error));
1234
1283
  */
1235
1284
  export async function fetchPackData(id) {
1236
- const query = `*[railcontent_id == ${id}]{
1237
- ${getFieldsForContentType("pack")}
1238
- } [0...1]`;
1239
- return fetchSanity(query, false);
1285
+ const query = `*[railcontent_id == ${id}]{
1286
+ ${getFieldsForContentType('pack')}
1287
+ } [0...1]`
1288
+ return fetchSanity(query, false)
1240
1289
  }
1241
1290
 
1242
1291
  /**
@@ -1256,34 +1305,26 @@ export async function fetchPackData(id) {
1256
1305
  * .then(lessons => console.log(lessons))
1257
1306
  * .catch(error => console.error(error));
1258
1307
  */
1259
- export async function fetchCoachLessons(brand, id, {
1260
- sortOrder = '-published_on',
1261
- searchTerm = '',
1262
- page = 1,
1263
- limit = 20,
1264
- includedFields = [],
1265
- } = {}) {
1266
- const fieldsString = getFieldsForContentType();
1267
- const start = (page - 1) * limit;
1268
- const end = start + limit;
1269
- const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1270
- const includedFieldsFilter = includedFields.length > 0
1271
- ? filtersToGroq(includedFields)
1272
- : "";
1273
- const filter = `brand == '${brand}' ${searchFilter} ${includedFieldsFilter} && references(*[_type=='instructor' && railcontent_id == ${id}]._id)`;
1274
- const filterWithRestrictions = await new FilterBuilder(filter).buildFilter();
1275
-
1276
- sortOrder = getSortOrder(sortOrder, brand);
1277
- const query = buildEntityAndTotalQuery(
1278
- filterWithRestrictions,
1279
- fieldsString,
1280
- {
1281
- sortOrder: sortOrder,
1282
- start: start,
1283
- end: end,
1284
- },
1285
- );
1286
- return fetchSanity(query, true);
1308
+ export async function fetchCoachLessons(
1309
+ brand,
1310
+ id,
1311
+ { sortOrder = '-published_on', searchTerm = '', page = 1, limit = 20, includedFields = [] } = {}
1312
+ ) {
1313
+ const fieldsString = getFieldsForContentType()
1314
+ const start = (page - 1) * limit
1315
+ const end = start + limit
1316
+ const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1317
+ const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
1318
+ const filter = `brand == '${brand}' ${searchFilter} ${includedFieldsFilter} && references(*[_type=='instructor' && railcontent_id == ${id}]._id)`
1319
+ const filterWithRestrictions = await new FilterBuilder(filter).buildFilter()
1320
+
1321
+ sortOrder = getSortOrder(sortOrder, brand)
1322
+ const query = buildEntityAndTotalQuery(filterWithRestrictions, fieldsString, {
1323
+ sortOrder: sortOrder,
1324
+ start: start,
1325
+ end: end,
1326
+ })
1327
+ return fetchSanity(query, true)
1287
1328
  }
1288
1329
 
1289
1330
  /**
@@ -1297,15 +1338,15 @@ export async function fetchCoachLessons(brand, id, {
1297
1338
  * .catch(error => console.error(error));
1298
1339
  */
1299
1340
  export async function fetchParentForDownload(id) {
1300
- const query = buildRawQuery(
1301
- `railcontent_id == ${id}`,
1302
- getFieldsForContentType('parent-download'),
1303
- {
1304
- isSingle: true,
1305
- },
1306
- );
1307
-
1308
- return fetchSanity(query, false);
1341
+ const query = buildRawQuery(
1342
+ `railcontent_id == ${id}`,
1343
+ getFieldsForContentType('parent-download'),
1344
+ {
1345
+ isSingle: true,
1346
+ }
1347
+ )
1348
+
1349
+ return fetchSanity(query, false)
1309
1350
  }
1310
1351
 
1311
1352
  /**
@@ -1319,33 +1360,24 @@ export async function fetchParentForDownload(id) {
1319
1360
  * .then(lessons => console.log(lessons))
1320
1361
  * .catch(error => console.error(error));
1321
1362
  */
1322
- export async function fetchByReference(brand, {
1323
- sortOrder = '-published_on',
1324
- searchTerm = '',
1325
- page = 1,
1326
- limit = 20,
1327
- includedFields = [],
1328
- } = {}) {
1329
- const fieldsString = getFieldsForContentType();
1330
- const start = (page - 1) * limit;
1331
- const end = start + limit;
1332
- const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : '';
1333
- const includedFieldsFilter = includedFields.length > 0
1334
- ? includedFields.join(' && ')
1335
- : "";
1336
-
1337
- const filter = `brand == '${brand}' ${searchFilter} && references(*[${includedFieldsFilter}]._id)`;
1338
- const filterWithRestrictions = await new FilterBuilder(filter).buildFilter();
1339
- const query = buildEntityAndTotalQuery(
1340
- filterWithRestrictions,
1341
- fieldsString,
1342
- {
1343
- sortOrder: getSortOrder(sortOrder, brand),
1344
- start: start,
1345
- end: end,
1346
- },
1347
- );
1348
- return fetchSanity(query, true);
1363
+ export async function fetchByReference(
1364
+ brand,
1365
+ { sortOrder = '-published_on', searchTerm = '', page = 1, limit = 20, includedFields = [] } = {}
1366
+ ) {
1367
+ const fieldsString = getFieldsForContentType()
1368
+ const start = (page - 1) * limit
1369
+ const end = start + limit
1370
+ const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1371
+ const includedFieldsFilter = includedFields.length > 0 ? includedFields.join(' && ') : ''
1372
+
1373
+ const filter = `brand == '${brand}' ${searchFilter} && references(*[${includedFieldsFilter}]._id)`
1374
+ const filterWithRestrictions = await new FilterBuilder(filter).buildFilter()
1375
+ const query = buildEntityAndTotalQuery(filterWithRestrictions, fieldsString, {
1376
+ sortOrder: getSortOrder(sortOrder, brand),
1377
+ start: start,
1378
+ end: end,
1379
+ })
1380
+ return fetchSanity(query, true)
1349
1381
  }
1350
1382
 
1351
1383
  /**
@@ -1367,29 +1399,37 @@ export async function fetchByReference(brand, {
1367
1399
  * .then(lessons => console.log(lessons))
1368
1400
  * .catch(error => console.error(error));
1369
1401
  */
1370
- export async function fetchArtistLessons(brand, name, contentType, {
1402
+ export async function fetchArtistLessons(
1403
+ brand,
1404
+ name,
1405
+ contentType,
1406
+ {
1371
1407
  sort = '-published_on',
1372
1408
  searchTerm = '',
1373
1409
  page = 1,
1374
1410
  limit = 10,
1375
1411
  includedFields = [],
1376
1412
  progressIds = undefined,
1377
- } = {}) {
1378
-
1379
- const fieldsString = DEFAULT_FIELDS.join(',');
1380
- const start = (page - 1) * limit;
1381
- const end = start + limit;
1382
- const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1383
- const sortOrder = getSortOrder(sort, brand);
1384
- const addType = contentType && Array.isArray(contentType) ? `_type in ['${contentType.join("', '")}'] &&` : contentType ? `_type == '${contentType}' && ` : ''
1385
- const includedFieldsFilter = includedFields.length > 0
1386
- ? filtersToGroq(includedFields)
1387
- : "";
1388
-
1389
- // limits the results to supplied progressIds for started & completed filters
1390
- const progressFilter = progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : "";
1391
- const now = getSanityDate(new Date());
1392
- const query = `{
1413
+ } = {}
1414
+ ) {
1415
+ const fieldsString = DEFAULT_FIELDS.join(',')
1416
+ const start = (page - 1) * limit
1417
+ const end = start + limit
1418
+ const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1419
+ const sortOrder = getSortOrder(sort, brand)
1420
+ const addType =
1421
+ contentType && Array.isArray(contentType)
1422
+ ? `_type in ['${contentType.join("', '")}'] &&`
1423
+ : contentType
1424
+ ? `_type == '${contentType}' && `
1425
+ : ''
1426
+ const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
1427
+
1428
+ // limits the results to supplied progressIds for started & completed filters
1429
+ const progressFilter =
1430
+ progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
1431
+ const now = getSanityDate(new Date())
1432
+ const query = `{
1393
1433
  "entity":
1394
1434
  *[_type == 'artist' && name == '${name}']
1395
1435
  {'type': _type, name, 'thumbnail_url':thumbnail_url.asset->url,
@@ -1397,8 +1437,8 @@ export async function fetchArtistLessons(brand, name, contentType, {
1397
1437
  'lessons': *[${addType} brand == '${brand}' && references(^._id) && (status in ['published'] || (status == 'scheduled' && defined(published_on) && published_on >= '${now}')) ${searchFilter} ${includedFieldsFilter} ${progressFilter}]{${fieldsString}}
1398
1438
  [${start}...${end}]}
1399
1439
  |order(${sortOrder})
1400
- }`;
1401
- return fetchSanity(query, true);
1440
+ }`
1441
+ return fetchSanity(query, true)
1402
1442
  }
1403
1443
 
1404
1444
  /**
@@ -1419,27 +1459,31 @@ export async function fetchArtistLessons(brand, name, contentType, {
1419
1459
  * .then(lessons => console.log(lessons))
1420
1460
  * .catch(error => console.error(error));
1421
1461
  */
1422
- export async function fetchGenreLessons(brand, name, contentType, {
1462
+ export async function fetchGenreLessons(
1463
+ brand,
1464
+ name,
1465
+ contentType,
1466
+ {
1423
1467
  sort = '-published_on',
1424
1468
  searchTerm = '',
1425
1469
  page = 1,
1426
1470
  limit = 10,
1427
1471
  includedFields = [],
1428
1472
  progressIds = undefined,
1429
- } = {}) {
1430
- const fieldsString = DEFAULT_FIELDS.join(',');
1431
- const start = (page - 1) * limit;
1432
- const end = start + limit;
1433
- const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1434
- const sortOrder = getSortOrder(sort, brand);
1435
- const addType = contentType ? `_type == '${contentType}' && ` : ''
1436
- const includedFieldsFilter = includedFields.length > 0
1437
- ? filtersToGroq(includedFields)
1438
- : "";
1439
- // limits the results to supplied progressIds for started & completed filters
1440
- const progressFilter = progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : "";
1441
- const now = getSanityDate(new Date());
1442
- const query = `{
1473
+ } = {}
1474
+ ) {
1475
+ const fieldsString = DEFAULT_FIELDS.join(',')
1476
+ const start = (page - 1) * limit
1477
+ const end = start + limit
1478
+ const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1479
+ const sortOrder = getSortOrder(sort, brand)
1480
+ const addType = contentType ? `_type == '${contentType}' && ` : ''
1481
+ const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
1482
+ // limits the results to supplied progressIds for started & completed filters
1483
+ const progressFilter =
1484
+ progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
1485
+ const now = getSanityDate(new Date())
1486
+ const query = `{
1443
1487
  "entity":
1444
1488
  *[_type == 'genre' && name == '${name}']
1445
1489
  {'type': _type, name, 'thumbnail_url':thumbnail_url.asset->url,
@@ -1447,14 +1491,14 @@ export async function fetchGenreLessons(brand, name, contentType, {
1447
1491
  'lessons': *[${addType} brand == '${brand}' && references(^._id) && (status in ['published'] || (status == 'scheduled' && defined(published_on) && published_on >= '${now}')) ${searchFilter} ${includedFieldsFilter} ${progressFilter}]{${fieldsString}}
1448
1492
  [${start}...${end}]}
1449
1493
  |order(${sortOrder})
1450
- }`;
1451
- return fetchSanity(query, true);
1494
+ }`
1495
+ return fetchSanity(query, true)
1452
1496
  }
1453
1497
 
1454
1498
  export async function fetchTopLevelParentId(railcontentId) {
1455
- const statusFilter = "&& status in ['scheduled', 'published', 'archived', 'unlisted']";
1499
+ const statusFilter = "&& status in ['scheduled', 'published', 'archived', 'unlisted']"
1456
1500
 
1457
- const query = `*[railcontent_id == ${railcontentId}]{
1501
+ const query = `*[railcontent_id == ${railcontentId}]{
1458
1502
  railcontent_id,
1459
1503
  'parents': *[^._id in child[]._ref ${statusFilter}]{
1460
1504
  railcontent_id,
@@ -1468,24 +1512,24 @@ export async function fetchTopLevelParentId(railcontentId) {
1468
1512
  }
1469
1513
  }
1470
1514
  }
1471
- }`;
1472
- let response = await fetchSanity(query, false, {processNeedAccess: false});
1473
- if (!response) return null;
1474
- let currentLevel = response;
1475
- for (let i = 0; i < 4; i++) {
1476
- if (currentLevel['parents'].length > 0) {
1477
- currentLevel = currentLevel['parents'][0];
1478
- } else {
1479
- return currentLevel['railcontent_id'];
1480
- }
1515
+ }`
1516
+ let response = await fetchSanity(query, false, { processNeedAccess: false })
1517
+ if (!response) return null
1518
+ let currentLevel = response
1519
+ for (let i = 0; i < 4; i++) {
1520
+ if (currentLevel['parents'].length > 0) {
1521
+ currentLevel = currentLevel['parents'][0]
1522
+ } else {
1523
+ return currentLevel['railcontent_id']
1481
1524
  }
1482
- return null;
1525
+ }
1526
+ return null
1483
1527
  }
1484
1528
 
1485
1529
  export async function fetchHierarchy(railcontentId) {
1486
- let topLevelId = await fetchTopLevelParentId(railcontentId);
1487
- const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
1488
- const query = `*[railcontent_id == ${topLevelId}]{
1530
+ let topLevelId = await fetchTopLevelParentId(railcontentId)
1531
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
1532
+ const query = `*[railcontent_id == ${topLevelId}]{
1489
1533
  railcontent_id,
1490
1534
  'assignments': assignment[]{railcontent_id},
1491
1535
  'children': child[${childrenFilter}]->{
@@ -1503,42 +1547,40 @@ export async function fetchHierarchy(railcontentId) {
1503
1547
  }
1504
1548
  }
1505
1549
  },
1506
- }`;
1507
- let response = await fetchSanity(query, false, {processNeedAccess: false});
1508
- if (!response) return null;
1509
- let data = {
1510
- topLevelId: topLevelId,
1511
- parents: {},
1512
- children: {}
1513
- };
1514
- populateHierarchyLookups(response, data, null);
1515
- return data;
1550
+ }`
1551
+ let response = await fetchSanity(query, false, { processNeedAccess: false })
1552
+ if (!response) return null
1553
+ let data = {
1554
+ topLevelId: topLevelId,
1555
+ parents: {},
1556
+ children: {},
1557
+ }
1558
+ populateHierarchyLookups(response, data, null)
1559
+ return data
1516
1560
  }
1517
1561
 
1518
-
1519
1562
  function populateHierarchyLookups(currentLevel, data, parentId) {
1520
- let contentId = currentLevel['railcontent_id'];
1521
- let children = currentLevel['children'];
1522
-
1523
- data.parents[contentId] = parentId;
1524
- if (children) {
1525
- data.children[contentId] = children.map(child => child['railcontent_id']);
1526
- for (let i = 0; i < children.length; i++) {
1527
- populateHierarchyLookups(children[i], data, contentId);
1528
- }
1529
- } else {
1530
- data.children[contentId] = [];
1531
- }
1532
-
1533
- let assignments = currentLevel['assignments'];
1534
- if (assignments) {
1535
- let assignmentIds = assignments.map(assignment => assignment['railcontent_id']);
1536
- data.children[contentId] = (data.children[contentId] ?? []).concat(assignmentIds);
1537
- assignmentIds.forEach(assignmentId => {
1538
- data.parents[assignmentId] = contentId;
1539
- });
1563
+ let contentId = currentLevel['railcontent_id']
1564
+ let children = currentLevel['children']
1565
+
1566
+ data.parents[contentId] = parentId
1567
+ if (children) {
1568
+ data.children[contentId] = children.map((child) => child['railcontent_id'])
1569
+ for (let i = 0; i < children.length; i++) {
1570
+ populateHierarchyLookups(children[i], data, contentId)
1540
1571
  }
1541
-
1572
+ } else {
1573
+ data.children[contentId] = []
1574
+ }
1575
+
1576
+ let assignments = currentLevel['assignments']
1577
+ if (assignments) {
1578
+ let assignmentIds = assignments.map((assignment) => assignment['railcontent_id'])
1579
+ data.children[contentId] = (data.children[contentId] ?? []).concat(assignmentIds)
1580
+ assignmentIds.forEach((assignmentId) => {
1581
+ data.parents[assignmentId] = contentId
1582
+ })
1583
+ }
1542
1584
  }
1543
1585
 
1544
1586
  /**
@@ -1548,27 +1590,28 @@ function populateHierarchyLookups(currentLevel, data, parentId) {
1548
1590
  * @returns {Promise<Object|null>} - A promise that resolves to an object containing the data
1549
1591
  */
1550
1592
  export async function fetchCommentModContentData(ids) {
1551
- const idsString = ids.join(',');
1552
- const fields = `"id": railcontent_id, "type": _type, title, "url": web_url_path, "parent": *[^._id in child[]._ref]{"id": railcontent_id, title}`;
1553
- const query = await buildQuery(`railcontent_id in [${idsString}]`,
1554
- {bypassPermissions: true},
1555
- fields,
1556
- {end: 50});
1557
- let data = await fetchSanity(query, true);
1558
- let mapped = {};
1559
- data.forEach(function (content) {
1560
- mapped[content.id] = {
1561
- "id": content.id,
1562
- "type": content.type,
1563
- "title": content.title,
1564
- "url": content.url,
1565
- "parentTitle": content.parent[0]?.title ?? null
1566
- };
1567
- });
1568
- return mapped;
1593
+ const idsString = ids.join(',')
1594
+ const fields = `"id": railcontent_id, "type": _type, title, "url": web_url_path, "parent": *[^._id in child[]._ref]{"id": railcontent_id, title}`
1595
+ const query = await buildQuery(
1596
+ `railcontent_id in [${idsString}]`,
1597
+ { bypassPermissions: true },
1598
+ fields,
1599
+ { end: 50 }
1600
+ )
1601
+ let data = await fetchSanity(query, true)
1602
+ let mapped = {}
1603
+ data.forEach(function (content) {
1604
+ mapped[content.id] = {
1605
+ id: content.id,
1606
+ type: content.type,
1607
+ title: content.title,
1608
+ url: content.url,
1609
+ parentTitle: content.parent[0]?.title ?? null,
1610
+ }
1611
+ })
1612
+ return mapped
1569
1613
  }
1570
1614
 
1571
-
1572
1615
  /**
1573
1616
  *
1574
1617
  * @param {string} query - The GROQ query to execute against the Sanity API.
@@ -1584,110 +1627,110 @@ export async function fetchCommentModContentData(ids) {
1584
1627
  * .catch(error => console.error(error));
1585
1628
  */
1586
1629
 
1587
- export async function fetchSanity(query,
1588
- isList,
1589
- {
1590
- customPostProcess = null,
1591
- processNeedAccess = true,
1592
- } = {}
1630
+ export async function fetchSanity(
1631
+ query,
1632
+ isList,
1633
+ { customPostProcess = null, processNeedAccess = true } = {}
1593
1634
  ) {
1594
- // Check the config object before proceeding
1595
- if (!checkSanityConfig(globalConfig)) {
1596
- return null;
1635
+ // Check the config object before proceeding
1636
+ if (!checkSanityConfig(globalConfig)) {
1637
+ return null
1638
+ }
1639
+
1640
+ if (globalConfig.sanityConfig.debug) {
1641
+ console.log('fetchSanity Query:', query)
1642
+ }
1643
+ const perspective = globalConfig.sanityConfig.perspective ?? 'published'
1644
+ const api = globalConfig.sanityConfig.useCachedAPI ? 'apicdn' : 'api'
1645
+ const url = `https://${globalConfig.sanityConfig.projectId}.${api}.sanity.io/v${globalConfig.sanityConfig.version}/data/query/${globalConfig.sanityConfig.dataset}?perspective=${perspective}`
1646
+ const headers = {
1647
+ Authorization: `Bearer ${globalConfig.sanityConfig.token}`,
1648
+ 'Content-Type': 'application/json',
1649
+ }
1650
+
1651
+ try {
1652
+ const method = 'post'
1653
+ const options = {
1654
+ method,
1655
+ headers,
1656
+ body: JSON.stringify({ query: query }),
1597
1657
  }
1598
1658
 
1599
- if (globalConfig.sanityConfig.debug) {
1600
- console.log("fetchSanity Query:", query);
1659
+ let promisesResult = await Promise.all([
1660
+ fetch(url, options),
1661
+ processNeedAccess ? fetchUserPermissions() : null,
1662
+ ])
1663
+ const response = promisesResult[0]
1664
+ const userPermissions = promisesResult[1]?.permissions
1665
+ const isAdmin = promisesResult[1]?.isAdmin
1666
+
1667
+ if (!response.ok) {
1668
+ throw new Error(`Sanity API error: ${response.status} - ${response.statusText}`)
1601
1669
  }
1602
- const perspective = globalConfig.sanityConfig.perspective ?? 'published';
1603
- const api = globalConfig.sanityConfig.useCachedAPI ? 'apicdn' : 'api';
1604
- const url = `https://${globalConfig.sanityConfig.projectId}.${api}.sanity.io/v${globalConfig.sanityConfig.version}/data/query/${globalConfig.sanityConfig.dataset}?perspective=${perspective}`;
1605
- const headers = {
1606
- 'Authorization': `Bearer ${globalConfig.sanityConfig.token}`,
1607
- 'Content-Type': 'application/json'
1608
- };
1609
-
1610
- try {
1611
- const method = 'post';
1612
- const options = {
1613
- method,
1614
- headers,
1615
- body: JSON.stringify({'query': query})
1616
- };
1617
-
1618
- let promisesResult = await Promise.all([
1619
- fetch(url, options),
1620
- processNeedAccess ? fetchUserPermissions() : null
1621
- ]);
1622
- const response = promisesResult[0];
1623
- const userPermissions = promisesResult[1]?.permissions;
1624
- const isAdmin = promisesResult[1]?.isAdmin;
1625
-
1626
- if (!response.ok) {
1627
- throw new Error(`Sanity API error: ${response.status} - ${response.statusText}`);
1628
- }
1629
- const result = await response.json();
1630
- if (result.result) {
1631
- if (globalConfig.sanityConfig.debug) {
1632
- console.log("fetchSanity Results:", result);
1633
- }
1634
- let results = isList ? result.result : result.result[0];
1635
- results = processNeedAccess ? await needsAccessDecorator(results, userPermissions, isAdmin) : results;
1636
- return customPostProcess ? customPostProcess(results) : results;
1637
- } else {
1638
- throw new Error('No results found');
1639
- }
1640
- } catch (error) {
1641
- console.error('fetchSanity: Fetch error:', error);
1642
- return null;
1670
+ const result = await response.json()
1671
+ if (result.result) {
1672
+ if (globalConfig.sanityConfig.debug) {
1673
+ console.log('fetchSanity Results:', result)
1674
+ }
1675
+ let results = isList ? result.result : result.result[0]
1676
+ results = processNeedAccess
1677
+ ? await needsAccessDecorator(results, userPermissions, isAdmin)
1678
+ : results
1679
+ return customPostProcess ? customPostProcess(results) : results
1680
+ } else {
1681
+ throw new Error('No results found')
1643
1682
  }
1683
+ } catch (error) {
1684
+ console.error('fetchSanity: Fetch error:', error)
1685
+ return null
1686
+ }
1644
1687
  }
1645
1688
 
1646
1689
  function needsAccessDecorator(results, userPermissions, isAdmin) {
1647
- if (globalConfig.sanityConfig.useDummyRailContentMethods) return results;
1648
-
1649
- userPermissions = new Set(userPermissions);
1650
-
1651
- if (Array.isArray(results)) {
1652
- results.forEach((result) => {
1653
- result['need_access'] = doesUserNeedAccessToContent(result, userPermissions, isAdmin);
1654
- });
1655
- } else if (results.entity && Array.isArray(results.entity)) {
1656
- // Group By
1657
- results.entity.forEach((result) => {
1658
- if (result.lessons) {
1659
- result.lessons.forEach((lesson) => {
1660
- lesson['need_access'] = doesUserNeedAccessToContent(lesson, userPermissions, isAdmin); // Updated to check lesson access
1661
- });
1662
- } else {
1663
- result['need_access'] = doesUserNeedAccessToContent(result, userPermissions, isAdmin);
1664
- }
1665
- });
1666
- } else if (results.related_lessons && Array.isArray(results.related_lessons)) {
1667
- results.related_lessons.forEach((result) => {
1668
- result['need_access'] = doesUserNeedAccessToContent(result, userPermissions, isAdmin);
1690
+ if (globalConfig.sanityConfig.useDummyRailContentMethods) return results
1691
+
1692
+ userPermissions = new Set(userPermissions)
1693
+
1694
+ if (Array.isArray(results)) {
1695
+ results.forEach((result) => {
1696
+ result['need_access'] = doesUserNeedAccessToContent(result, userPermissions, isAdmin)
1697
+ })
1698
+ } else if (results.entity && Array.isArray(results.entity)) {
1699
+ // Group By
1700
+ results.entity.forEach((result) => {
1701
+ if (result.lessons) {
1702
+ result.lessons.forEach((lesson) => {
1703
+ lesson['need_access'] = doesUserNeedAccessToContent(lesson, userPermissions, isAdmin) // Updated to check lesson access
1669
1704
  })
1670
- } else {
1671
- results['need_access'] = doesUserNeedAccessToContent(results, userPermissions, isAdmin);
1672
- }
1673
-
1674
- return results;
1705
+ } else {
1706
+ result['need_access'] = doesUserNeedAccessToContent(result, userPermissions, isAdmin)
1707
+ }
1708
+ })
1709
+ } else if (results.related_lessons && Array.isArray(results.related_lessons)) {
1710
+ results.related_lessons.forEach((result) => {
1711
+ result['need_access'] = doesUserNeedAccessToContent(result, userPermissions, isAdmin)
1712
+ })
1713
+ } else {
1714
+ results['need_access'] = doesUserNeedAccessToContent(results, userPermissions, isAdmin)
1715
+ }
1716
+
1717
+ return results
1675
1718
  }
1676
1719
 
1677
1720
  function doesUserNeedAccessToContent(result, userPermissions, isAdmin) {
1678
- if (isAdmin ?? false) {
1679
- return false;
1680
- }
1681
- const permissions = new Set(result?.permission_id ?? []);
1682
- if (permissions.size === 0) {
1683
- return false;
1721
+ if (isAdmin ?? false) {
1722
+ return false
1723
+ }
1724
+ const permissions = new Set(result?.permission_id ?? [])
1725
+ if (permissions.size === 0) {
1726
+ return false
1727
+ }
1728
+ for (let permission of permissions) {
1729
+ if (userPermissions.has(permission)) {
1730
+ return false
1684
1731
  }
1685
- for (let permission of permissions) {
1686
- if (userPermissions.has(permission)) {
1687
- return false;
1688
- }
1689
- }
1690
- return true;
1732
+ }
1733
+ return true
1691
1734
  }
1692
1735
 
1693
1736
  /**
@@ -1703,15 +1746,15 @@ function doesUserNeedAccessToContent(result, userPermissions, isAdmin) {
1703
1746
  * .catch(error => console.error(error));
1704
1747
  */
1705
1748
  export async function fetchShowsData(brand) {
1706
- let shows = showsTypes[brand] ?? [];
1707
- const showsInfo = [];
1749
+ let shows = showsTypes[brand] ?? []
1750
+ const showsInfo = []
1708
1751
 
1709
- shows.forEach(type => {
1710
- const processedData = processMetadata(brand, type);
1711
- if (processedData) showsInfo.push(processedData)
1712
- });
1752
+ shows.forEach((type) => {
1753
+ const processedData = processMetadata(brand, type)
1754
+ if (processedData) showsInfo.push(processedData)
1755
+ })
1713
1756
 
1714
- return showsInfo;
1757
+ return showsInfo
1715
1758
  }
1716
1759
 
1717
1760
  /**
@@ -1728,135 +1771,122 @@ export async function fetchShowsData(brand) {
1728
1771
  * .catch(error => console.error(error));
1729
1772
  */
1730
1773
  export async function fetchMetadata(brand, type) {
1731
- const processedData = processMetadata(brand, type, true);
1732
- return processedData ? processedData : {};
1774
+ const processedData = processMetadata(brand, type, true)
1775
+ return processedData ? processedData : {}
1733
1776
  }
1734
1777
 
1735
1778
  export async function fetchChatAndLiveEnvent(brand, forcedId = null) {
1736
- const liveEvent = (forcedId !== null) ? await fetchByRailContentIds([forcedId]): [await fetchLiveEvent(brand)];
1737
- if (liveEvent.length === 0 || (liveEvent.length === 1 && liveEvent[0] === undefined)) {
1738
- return null;
1739
- }
1740
- let url = `/content/live-chat?brand=${brand}`;
1741
- const chatData = await fetchHandler(url);
1742
- const mergedData = { ...chatData, ...liveEvent[0] };
1743
- return mergedData;
1779
+ const liveEvent =
1780
+ forcedId !== null ? await fetchByRailContentIds([forcedId]) : [await fetchLiveEvent(brand)]
1781
+ if (liveEvent.length === 0 || (liveEvent.length === 1 && liveEvent[0] === undefined)) {
1782
+ return null
1783
+ }
1784
+ let url = `/content/live-chat?brand=${brand}`
1785
+ const chatData = await fetchHandler(url)
1786
+ const mergedData = { ...chatData, ...liveEvent[0] }
1787
+ return mergedData
1744
1788
  }
1745
1789
 
1746
-
1747
1790
  //Helper Functions
1748
1791
  function arrayJoinWithQuotes(array, delimiter = ',') {
1749
- const wrapped = array.map(value => `'${value}'`);
1750
- return wrapped.join(delimiter)
1792
+ const wrapped = array.map((value) => `'${value}'`)
1793
+ return wrapped.join(delimiter)
1751
1794
  }
1752
1795
 
1753
1796
  function getSanityDate(date, roundToHourForCaching = true) {
1754
- if (roundToHourForCaching) {
1755
- // We need to set the published on filter date to be a round time so that it doesn't bypass the query cache
1756
- // with every request by changing the filter date every second. I've set it to one minute past the current hour
1757
- // because publishing usually publishes content on the hour exactly which means it should still skip the cache
1758
- // when the new content is available.
1759
- // Round to the start of the current hour
1760
- const roundedDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours());
1761
-
1762
- return roundedDate.toISOString();
1763
- }
1764
-
1765
- return date.toISOString();
1797
+ if (roundToHourForCaching) {
1798
+ // We need to set the published on filter date to be a round time so that it doesn't bypass the query cache
1799
+ // with every request by changing the filter date every second. I've set it to one minute past the current hour
1800
+ // because publishing usually publishes content on the hour exactly which means it should still skip the cache
1801
+ // when the new content is available.
1802
+ // Round to the start of the current hour
1803
+ const roundedDate = new Date(
1804
+ date.getFullYear(),
1805
+ date.getMonth(),
1806
+ date.getDate(),
1807
+ date.getHours()
1808
+ )
1809
+
1810
+ return roundedDate.toISOString()
1811
+ }
1812
+
1813
+ return date.toISOString()
1766
1814
  }
1767
1815
 
1768
1816
  const merge = (a, b, predicate = (a, b) => a === b) => {
1769
- const c = [...a]; // copy to avoid side effects
1770
- // add all items from B to copy C if they're not already present
1771
- b.forEach((bItem) => (c.some((cItem) => predicate(bItem, cItem)) ? null : c.push(bItem)))
1772
- return c;
1817
+ const c = [...a] // copy to avoid side effects
1818
+ // add all items from B to copy C if they're not already present
1819
+ b.forEach((bItem) => (c.some((cItem) => predicate(bItem, cItem)) ? null : c.push(bItem)))
1820
+ return c
1773
1821
  }
1774
1822
 
1775
1823
  function checkSanityConfig(config) {
1776
- if (!config.sanityConfig.token) {
1777
- console.warn('fetchSanity: The "token" property is missing in the config object.');
1778
- return false;
1779
- }
1780
- if (!config.sanityConfig.projectId) {
1781
- console.warn('fetchSanity: The "projectId" property is missing in the config object.');
1782
- return false;
1783
- }
1784
- if (!config.sanityConfig.dataset) {
1785
- console.warn('fetchSanity: The "dataset" property is missing in the config object.');
1786
- return false;
1787
- }
1788
- if (!config.sanityConfig.version) {
1789
- console.warn('fetchSanity: The "version" property is missing in the config object.');
1790
- return false;
1791
- }
1792
- return true;
1824
+ if (!config.sanityConfig.token) {
1825
+ console.warn('fetchSanity: The "token" property is missing in the config object.')
1826
+ return false
1827
+ }
1828
+ if (!config.sanityConfig.projectId) {
1829
+ console.warn('fetchSanity: The "projectId" property is missing in the config object.')
1830
+ return false
1831
+ }
1832
+ if (!config.sanityConfig.dataset) {
1833
+ console.warn('fetchSanity: The "dataset" property is missing in the config object.')
1834
+ return false
1835
+ }
1836
+ if (!config.sanityConfig.version) {
1837
+ console.warn('fetchSanity: The "version" property is missing in the config object.')
1838
+ return false
1839
+ }
1840
+ return true
1793
1841
  }
1794
1842
 
1795
-
1796
1843
  function buildRawQuery(
1797
- filter = '',
1798
- fields = '...',
1799
- {
1800
- sortOrder = 'published_on desc',
1801
- start = 0,
1802
- end = 10,
1803
- isSingle = false,
1804
- }
1844
+ filter = '',
1845
+ fields = '...',
1846
+ { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false }
1805
1847
  ) {
1806
- const sortString = sortOrder ? `order(${sortOrder})` : '';
1807
- const countString = isSingle ? '[0...1]' : `[${start}...${end}]`;
1808
- const query = `*[${filter}]{
1848
+ const sortString = sortOrder ? `order(${sortOrder})` : ''
1849
+ const countString = isSingle ? '[0...1]' : `[${start}...${end}]`
1850
+ const query = `*[${filter}]{
1809
1851
  ${fields}
1810
1852
  } | ${sortString}${countString}`
1811
- return query;
1853
+ return query
1812
1854
  }
1813
1855
 
1814
-
1815
1856
  async function buildQuery(
1816
- baseFilter = '',
1817
- filterParams = {pullFutureContent: false},
1818
- fields = '...',
1819
- {
1820
- sortOrder = 'published_on desc',
1821
- start = 0,
1822
- end = 10,
1823
- isSingle = false,
1824
- },
1857
+ baseFilter = '',
1858
+ filterParams = { pullFutureContent: false },
1859
+ fields = '...',
1860
+ { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false }
1825
1861
  ) {
1826
- const filter = await new FilterBuilder(baseFilter, filterParams).buildFilter();
1827
- return buildRawQuery(filter, fields, {sortOrder, start, end, isSingle});
1862
+ const filter = await new FilterBuilder(baseFilter, filterParams).buildFilter()
1863
+ return buildRawQuery(filter, fields, { sortOrder, start, end, isSingle })
1828
1864
  }
1829
1865
 
1830
1866
  function buildEntityAndTotalQuery(
1831
- filter = '',
1832
- fields = '...',
1833
- {
1834
- sortOrder = 'published_on desc',
1835
- start = 0,
1836
- end = 10,
1837
- isSingle = false,
1838
- },
1867
+ filter = '',
1868
+ fields = '...',
1869
+ { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false }
1839
1870
  ) {
1840
- const sortString = sortOrder ? `order(${sortOrder})` : '';
1841
- const countString = isSingle ? '[0...1]' : `[${start}...${end}]`;
1842
- const query = `{
1871
+ const sortString = sortOrder ? `order(${sortOrder})` : ''
1872
+ const countString = isSingle ? '[0...1]' : `[${start}...${end}]`
1873
+ const query = `{
1843
1874
  "entity": *[${filter}] | ${sortString}${countString}
1844
1875
  {
1845
1876
  ${fields}
1846
1877
  },
1847
1878
  "total": 0
1848
- }`;
1849
- return query;
1879
+ }`
1880
+ return query
1850
1881
  }
1851
1882
 
1852
-
1853
1883
  function getFilterOptions(option, commonFilter, contentType, brand) {
1854
- let filterGroq = '';
1855
- const types = Array.from(new Set([...coachLessonsTypes, ...showsTypes[brand]]));
1884
+ let filterGroq = ''
1885
+ const types = Array.from(new Set([...coachLessonsTypes, ...showsTypes[brand]]))
1856
1886
 
1857
- switch (option) {
1858
- case "difficulty":
1859
- filterGroq = `
1887
+ switch (option) {
1888
+ case 'difficulty':
1889
+ filterGroq = `
1860
1890
  "difficulty": [
1861
1891
  {"type": "All", "count": count(*[${commonFilter} && difficulty_string == "All"])},
1862
1892
  {"type": "Introductory", "count": count(*[${commonFilter} && difficulty_string == "Introductory"])},
@@ -1864,84 +1894,82 @@ function getFilterOptions(option, commonFilter, contentType, brand) {
1864
1894
  {"type": "Intermediate", "count": count(*[${commonFilter} && difficulty_string == "Intermediate" ])},
1865
1895
  {"type": "Advanced", "count": count(*[${commonFilter} && difficulty_string == "Advanced" ])},
1866
1896
  {"type": "Expert", "count": count(*[${commonFilter} && difficulty_string == "Expert" ])}
1867
- ][count > 0],`;
1868
- break;
1869
- case "type":
1870
- const typesString = types.map(t => {
1871
- return `{"type": "${t}"}`
1872
- }).join(', ');
1873
- filterGroq = `"type": [${typesString}]{type, 'count': count(*[_type == ^.type && ${commonFilter}])}[count > 0],`;
1874
- break;
1875
- case "genre":
1876
- case "essential":
1877
- case "focus":
1878
- case "theory":
1879
- case "topic":
1880
- case "lifestyle":
1881
- case "creativity":
1882
- filterGroq = `
1897
+ ][count > 0],`
1898
+ break
1899
+ case 'type':
1900
+ const typesString = types
1901
+ .map((t) => {
1902
+ return `{"type": "${t}"}`
1903
+ })
1904
+ .join(', ')
1905
+ filterGroq = `"type": [${typesString}]{type, 'count': count(*[_type == ^.type && ${commonFilter}])}[count > 0],`
1906
+ break
1907
+ case 'genre':
1908
+ case 'essential':
1909
+ case 'focus':
1910
+ case 'theory':
1911
+ case 'topic':
1912
+ case 'lifestyle':
1913
+ case 'creativity':
1914
+ filterGroq = `
1883
1915
  "${option}": *[_type == '${option}' ${contentType ? ` && '${contentType}' in filter_types` : ''} ] {
1884
1916
  "type": name,
1885
1917
  "count": count(*[${commonFilter} && references(^._id)])
1886
- }[count > 0],`;
1887
- break;
1888
- case "instrumentless":
1889
- filterGroq = `
1918
+ }[count > 0],`
1919
+ break
1920
+ case 'instrumentless':
1921
+ filterGroq = `
1890
1922
  "${option}": [
1891
1923
  {"type": "Full Song Only", "count": count(*[${commonFilter} && instrumentless == false ])},
1892
1924
  {"type": "Instrument Removed", "count": count(*[${commonFilter} && instrumentless == true ])}
1893
- ][count > 0],`;
1894
- break;
1895
- case "gear":
1896
- filterGroq = `
1925
+ ][count > 0],`
1926
+ break
1927
+ case 'gear':
1928
+ filterGroq = `
1897
1929
  "${option}": [
1898
1930
  {"type": "Practice Pad", "count": count(*[${commonFilter} && gear match 'Practice Pad' ])},
1899
1931
  {"type": "Drum-Set", "count": count(*[${commonFilter} && gear match 'Drum-Set'])}
1900
- ][count > 0],`;
1901
- break;
1902
- case "bpm":
1903
- filterGroq = `
1932
+ ][count > 0],`
1933
+ break
1934
+ case 'bpm':
1935
+ filterGroq = `
1904
1936
  "${option}": [
1905
1937
  {"type": "50-90", "count": count(*[${commonFilter} && bpm > 50 && bpm < 91])},
1906
1938
  {"type": "91-120", "count": count(*[${commonFilter} && bpm > 90 && bpm < 121])},
1907
1939
  {"type": "121-150", "count": count(*[${commonFilter} && bpm > 120 && bpm < 151])},
1908
1940
  {"type": "151-180", "count": count(*[${commonFilter} && bpm > 150 && bpm < 181])},
1909
1941
  {"type": "180+", "count": count(*[${commonFilter} && bpm > 180])},
1910
- ][count > 0],`;
1911
- break;
1912
- default:
1913
- filterGroq = "";
1914
- break;
1915
- }
1916
-
1917
- return filterGroq;
1942
+ ][count > 0],`
1943
+ break
1944
+ default:
1945
+ filterGroq = ''
1946
+ break
1947
+ }
1948
+
1949
+ return filterGroq
1918
1950
  }
1919
1951
 
1920
1952
  function cleanUpGroq(query) {
1921
- // Split the query into clauses based on the logical operators
1922
- const clauses = query.split(/(\s*&&|\s*\|\|)/).map(clause => clause.trim());
1953
+ // Split the query into clauses based on the logical operators
1954
+ const clauses = query.split(/(\s*&&|\s*\|\|)/).map((clause) => clause.trim())
1923
1955
 
1924
- // Filter out empty clauses
1925
- const filteredClauses = clauses.filter(clause => clause.length > 0);
1956
+ // Filter out empty clauses
1957
+ const filteredClauses = clauses.filter((clause) => clause.length > 0)
1926
1958
 
1927
- // Check if there are valid conditions in the clauses
1928
- const hasConditions = filteredClauses.some(clause => !clause.match(/^\s*&&\s*|\s*\|\|\s*$/));
1959
+ // Check if there are valid conditions in the clauses
1960
+ const hasConditions = filteredClauses.some((clause) => !clause.match(/^\s*&&\s*|\s*\|\|\s*$/))
1929
1961
 
1930
- if (!hasConditions) {
1931
- // If no valid conditions, return an empty string or the original query
1932
- return '';
1933
- }
1962
+ if (!hasConditions) {
1963
+ // If no valid conditions, return an empty string or the original query
1964
+ return ''
1965
+ }
1934
1966
 
1935
- // Remove occurrences of '&& ()'
1936
- const cleanedQuery = filteredClauses.join(' ')
1937
- .replace(/&&\s*\(\)/g, '')
1938
- .replace(/(\s*&&|\s*\|\|)(?=\s*[\s()]*$|(?=\s*&&|\s*\|\|))/g, '')
1939
- .trim();
1967
+ // Remove occurrences of '&& ()'
1968
+ const cleanedQuery = filteredClauses
1969
+ .join(' ')
1970
+ .replace(/&&\s*\(\)/g, '')
1971
+ .replace(/(\s*&&|\s*\|\|)(?=\s*[\s()]*$|(?=\s*&&|\s*\|\|))/g, '')
1972
+ .trim()
1940
1973
 
1941
- return cleanedQuery;
1974
+ return cleanedQuery
1942
1975
  }
1943
-
1944
-
1945
-
1946
-
1947
-