musora-content-services 1.0.121 → 1.0.123

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 (52) hide show
  1. package/.github/workflows/node.js.yml +0 -0
  2. package/CHANGELOG.md +4 -0
  3. package/README.md +0 -0
  4. package/babel.config.js +0 -0
  5. package/docs/config.js.html +7 -3
  6. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  7. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  8. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  9. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  10. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  11. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  12. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  13. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  14. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  15. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  16. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  17. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  18. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  19. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  20. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  21. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  22. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  23. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  24. package/docs/index.html +1 -1
  25. package/docs/module-Config.html +28 -3
  26. package/docs/module-Railcontent-Services.html +7 -7
  27. package/docs/module-Sanity-Services.html +426 -48
  28. package/docs/railcontent.js.html +37 -47
  29. package/docs/sanity.js.html +69 -12
  30. package/docs/scripts/collapse.js +0 -0
  31. package/docs/scripts/commonNav.js +0 -0
  32. package/docs/scripts/linenumber.js +0 -0
  33. package/docs/scripts/nav.js +0 -0
  34. package/docs/scripts/polyfill.js +0 -0
  35. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  36. package/docs/scripts/prettify/lang-css.js +0 -0
  37. package/docs/scripts/prettify/prettify.js +0 -0
  38. package/docs/scripts/search.js +0 -0
  39. package/docs/styles/jsdoc.css +0 -0
  40. package/docs/styles/prettify.css +0 -0
  41. package/jest.config.js +0 -0
  42. package/jsdoc.json +0 -0
  43. package/link_mcs.sh +0 -0
  44. package/package.json +1 -1
  45. package/src/contentMetaData.js +11 -0
  46. package/src/contentTypeConfig.js +6 -2
  47. package/src/services/railcontent.js +0 -2
  48. package/src/services/sanity.js +66 -11
  49. package/test/localStorageMock.js +0 -0
  50. package/test/log.js +0 -0
  51. package/test/sanityQueryService.test.js +39 -0
  52. package/tools/generate-index.js +0 -0
@@ -13,6 +13,7 @@ import {
13
13
  getUpcomingEventsTypes,
14
14
  showsTypes,
15
15
  getNewReleasesTypes,
16
+ coachLessonsTypes
16
17
  } from "../contentTypeConfig";
17
18
 
18
19
  import {
@@ -576,6 +577,7 @@ export function getSortOrder(sort= '-published_on', groupBy)
576
577
  *
577
578
  * This function constructs a query to retrieve the total number of results and filter options such as difficulty, instrument type, and genre.
578
579
  * The filter options are dynamically generated based on the provided filters, style, artist, and content type.
580
+ * If a coachId is provided, the content type must be 'coach-lessons'.
579
581
  *
580
582
  * @param {string} brand - The brand for which to fetch the filter options.
581
583
  * @param {string[]} filters - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock']
@@ -584,13 +586,22 @@ export function getSortOrder(sort= '-published_on', groupBy)
584
586
  * @param {string} contentType - The content type to fetch (e.g., 'song', 'lesson').
585
587
  * @param {string} [term] - Optional search term to match against various fields such as title, album, artist name, and genre.
586
588
  * @param {Array<string>} [progressIds=undefined] - An array of railcontent IDs to filter the results by. Used for filtering by progress.
589
+ * @param {string} [coachId=undefined] - Optional coach ID to filter the results by a specific coach. If provided, contentType must be 'coach-lessons'.
587
590
  * @returns {Promise<Object|null>} - A promise that resolves to an object containing the total results and filter options, or null if the query fails.
588
591
  *
592
+ * @throws {Error} Will throw an error if coachId is provided but contentType is not 'coach-lessons'.
593
+ *
589
594
  * @example
590
595
  * // Example usage:
591
596
  * fetchAllFilterOptions('myBrand', '', 'Rock', 'John Doe', 'song', 'Love')
592
597
  * .then(options => console.log(options))
593
598
  * .catch(error => console.error(error));
599
+ *
600
+ * @example
601
+ * // Example usage with coachId:
602
+ * fetchAllFilterOptions('myBrand', '', 'Rock', 'John Doe', 'coach-lessons', 'Love', undefined, '123')
603
+ * .then(options => console.log(options))
604
+ * .catch(error => console.error(error));
594
605
  */
595
606
  export async function fetchAllFilterOptions(
596
607
  brand,
@@ -599,25 +610,48 @@ export async function fetchAllFilterOptions(
599
610
  artist,
600
611
  contentType,
601
612
  term,
602
- progressIds = undefined
613
+ progressIds = undefined,
614
+ coachId = undefined, // New parameter for coach ID
603
615
  ) {
616
+ if (coachId && contentType !== 'coach-lessons') {
617
+ throw new Error(`Invalid contentType: '${contentType}' for coachId. It must be 'coach-lessons'.`);
618
+ }
619
+
604
620
  filters = Array.isArray(filters) ? filters : [];
605
621
  const includedFieldsFilter = filters?.length > 0 ? filtersToGroq(filters) : undefined;
606
622
 
607
623
  const progressFilter = progressIds !== undefined ?
608
624
  `&& railcontent_id in [${progressIds.join(',')}]` : "";
609
625
 
610
- const commonFilter = `_type == '${contentType}' && brand == "${brand}"${style ? ` && '${style}' in genre[]->name` : ''}${artist ? ` && artist->name == '${artist}'` : ''} ${progressFilter} ${includedFieldsFilter ? includedFieldsFilter : ''}`;
626
+ // General common filter logic
627
+ let commonFilter;
628
+
629
+ if (coachId) {
630
+ // Coach-specific filtering
631
+ commonFilter = `brand == '${brand}' && references(*[_type=='instructor' && railcontent_id == ${coachId}]._id) ${includedFieldsFilter ? includedFieldsFilter : ''}`;
632
+ } else {
633
+ // Regular content filtering
634
+ commonFilter = `_type == '${contentType}' && brand == "${brand}"${style ? ` && '${style}' in genre[]->name` : ''}${artist ? ` && artist->name == '${artist}'` : ''} ${progressFilter} ${includedFieldsFilter ? includedFieldsFilter : ''}`;
635
+ }
636
+
637
+ // Determine metadata and allowable filters (handle coach lessons if coachId exists)
611
638
  const metaData = processMetadata(brand, contentType, true);
612
639
  const allowableFilters = metaData?.allowableFilters || [];
613
640
 
641
+ // Dynamic filter options construction
614
642
  const dynamicFilterOptions = allowableFilters.map(filter => {
615
- // Create a modified common filter for each allowable filter
616
643
  let includedFieldsFilterWithoutSelectedOption = filters?.length > 0 ? filtersToGroq(filters, filter) : undefined;
617
- const commonFilterWithoutSelectedOption = `_type == '${contentType}' && brand == "${brand}"${(style && filter !== "style") ? ` && '${style}' in genre[]->name` : ''}${(artist && filter !== "artist") ? ` && artist->name == '${artist}'` : ''} ${includedFieldsFilterWithoutSelectedOption ? includedFieldsFilterWithoutSelectedOption : ''}`;
644
+ let commonFilterWithoutSelectedOption;
645
+
646
+ if (coachId) {
647
+ commonFilterWithoutSelectedOption = `brand == '${brand}' && references(*[_type=='instructor' && railcontent_id == ${coachId}]._id) ${includedFieldsFilterWithoutSelectedOption ? includedFieldsFilterWithoutSelectedOption : ''}`;
648
+ } else {
649
+ // Regular content filter without the selected option
650
+ commonFilterWithoutSelectedOption = `_type == '${contentType}' && brand == "${brand}"${(style && filter !== "style") ? ` && '${style}' in genre[]->name` : ''}${(artist && filter !== "artist") ? ` && artist->name == '${artist}'` : ''} ${includedFieldsFilterWithoutSelectedOption ? includedFieldsFilterWithoutSelectedOption : ''}`;
651
+ }
618
652
 
619
653
  // Call getFilterOptions with the modified common filter
620
- return getFilterOptions(filter, commonFilterWithoutSelectedOption, contentType);
654
+ return getFilterOptions(filter, commonFilterWithoutSelectedOption, contentType, brand);
621
655
  }).join(' ');
622
656
 
623
657
  const query = `
@@ -937,7 +971,7 @@ export async function fetchRelatedLessons(railContentId, brand) {
937
971
  * Fetch related method lessons for a specific lesson by RailContent ID and type.
938
972
  * @param {string} railContentId - The RailContent ID of the current lesson.
939
973
  * @param {string} brand - The current brand.
940
- * @returns {Promise<Object>|null>} - The fetched related lessons
974
+ * @returns {Promise<Array<Object>|null>} - The fetched related lessons
941
975
  */
942
976
  export async function fetchRelatedMethodLessons(railContentId, brand) {
943
977
  const query = `*[railcontent_id == ${railContentId} && brand == "${brand}"]{
@@ -1082,8 +1116,15 @@ export async function fetchChallengeOverview(id) {
1082
1116
 
1083
1117
  /**
1084
1118
  * Fetch the data needed for the coach screen.
1119
+ * @param {string} brand - The brand for which to fetch coach lessons
1085
1120
  * @param {string} id - The Railcontent ID of the coach
1086
1121
  * @returns {Promise<Object|null>} - The lessons for the instructor or null if not found.
1122
+ * @param {Object} params - Parameters for pagination, filtering and sorting.
1123
+ * @param {string} [params.sortOrder="-published_on"] - The field to sort the lessons by.
1124
+ * @param {string} [params.searchTerm=""] - The search term to filter content by title.
1125
+ * @param {number} [params.page=1] - The page number for pagination.
1126
+ * @param {number} [params.limit=10] - The number of items per page.
1127
+ * @param {Array<string>} [params.includedFields=[]] - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock'].
1087
1128
  *
1088
1129
  * @example
1089
1130
  * fetchCoachLessons('coach123')
@@ -1095,12 +1136,17 @@ export async function fetchCoachLessons(brand, id, {
1095
1136
  searchTerm = '',
1096
1137
  page = 1,
1097
1138
  limit = 20,
1139
+ includedFields = [],
1098
1140
  } = {}) {
1099
1141
  const fieldsString = getFieldsForContentType();
1100
1142
  const start = (page - 1) * limit;
1101
1143
  const end = start + limit;
1102
1144
  const searchFilter = searchTerm ? `&& title match "${searchTerm}*"`: ''
1103
- const filter = `brand == '${brand}' ${searchFilter} && references(*[_type=='instructor' && railcontent_id == ${id}]._id)`;
1145
+ const includedFieldsFilter = includedFields.length > 0
1146
+ ? filtersToGroq(includedFields)
1147
+ : "";
1148
+ const filter = `brand == '${brand}' ${searchFilter} ${includedFieldsFilter} && references(*[_type=='instructor' && railcontent_id == ${id}]._id)`;
1149
+
1104
1150
  sortOrder = getSortOrder(sortOrder);
1105
1151
  const query = buildEntityAndTotalQuery(
1106
1152
  filter,
@@ -1269,7 +1315,8 @@ export async function fetchSanity(query,
1269
1315
  { customPostProcess = null,
1270
1316
  processNeedAccess = true,} = {}
1271
1317
  ) {
1272
-
1318
+ //TODO: Disable need_access decorator - should be deleted, but first should chck why the /content/user_data_permissions endpoint return 500 error
1319
+ processNeedAccess = false;
1273
1320
  // Check the config object before proceeding
1274
1321
  if (!checkSanityConfig(globalConfig)) {
1275
1322
  return null;
@@ -1374,7 +1421,7 @@ export async function fetchCatalogMetadata(contentType)
1374
1421
  * Fetch shows data for a brand.
1375
1422
  *
1376
1423
  * @param brand - The brand for which to fetch shows.
1377
- * @returns {Promise<[]>}
1424
+ * @returns {Promise<{name, description, type: *, thumbnailUrl}>}
1378
1425
  *
1379
1426
  * @example
1380
1427
  *
@@ -1508,8 +1555,10 @@ function buildEntityAndTotalQuery(
1508
1555
  }
1509
1556
 
1510
1557
 
1511
- function getFilterOptions(option, commonFilter,contentType){
1558
+ function getFilterOptions(option, commonFilter,contentType, brand){
1512
1559
  let filterGroq = '';
1560
+ const types = Array.from(new Set([...coachLessonsTypes,...showsTypes[brand]]));
1561
+
1513
1562
  switch (option) {
1514
1563
  case "difficulty":
1515
1564
  filterGroq = `
@@ -1521,6 +1570,12 @@ function getFilterOptions(option, commonFilter,contentType){
1521
1570
  {"type": "Expert", "count": count(*[${commonFilter} && difficulty_string == "Expert" ])}
1522
1571
  ][count > 0],`;
1523
1572
  break;
1573
+ case "type":
1574
+ const dynamicTypeOptions = types.map(filter => {
1575
+ return `{"type": "${filter}", "count": count(*[${commonFilter} && _type == "${filter}"])}`
1576
+ }).join(', ');
1577
+ filterGroq = `"type": [${dynamicTypeOptions}][count > 0],`;
1578
+ break;
1524
1579
  case "genre":
1525
1580
  case "essential":
1526
1581
  case "focus":
@@ -1529,7 +1584,7 @@ function getFilterOptions(option, commonFilter,contentType){
1529
1584
  case "lifestyle":
1530
1585
  case "creativity":
1531
1586
  filterGroq = `
1532
- "${option}": *[_type == '${option}' && '${contentType}' in filter_types] {
1587
+ "${option}": *[_type == '${option}' ${contentType ? ` && '${contentType}' in filter_types` : ''} ] {
1533
1588
  "type": name,
1534
1589
  "count": count(*[${commonFilter} && references(^._id)])
1535
1590
  }[count > 0],`;
File without changes
package/test/log.js CHANGED
File without changes
@@ -354,6 +354,31 @@ describe('Sanity Queries', function () {
354
354
  const response = await fetchCoachLessons('drumeo',411493, {});
355
355
  expect(response.entity.length).toBeGreaterThan(0);
356
356
  });
357
+ test('fetchCoachLessons-WithTypeFilters', async () => {
358
+ const response = await fetchAllFilterOptions('drumeo',['type,course','type,live'], '','','coach-lessons','',[],31880);
359
+ log(response);
360
+ expect(response.meta.filterOptions.difficulty).toBeDefined();
361
+ expect(response.meta.filterOptions.type).toBeDefined();
362
+ expect(response.meta.filterOptions.lifestyle).toBeDefined();
363
+ expect(response.meta.filterOptions.genre).toBeDefined();
364
+ });
365
+
366
+ test('fetchCoachLessons-WithTypeFilters-InvalidContentType', async () => {
367
+ const brand = 'drumeo';
368
+ const coachId = 31880;
369
+ const invalidContentType = 'course'; // Not 'coach-lessons'
370
+
371
+ await expect(fetchAllFilterOptions(brand, ['type,course', 'type,live'], '', '', invalidContentType, '', [], coachId))
372
+ .rejects
373
+ .toThrow("Invalid contentType: 'course' for coachId. It must be 'coach-lessons'.");
374
+ });
375
+
376
+ test('fetchCoachLessons-IncludedFields', async () => {
377
+ const response = await fetchCoachLessons('drumeo',31880, {includedFields: ['genre,Pop/Rock','difficulty,Beginner']});
378
+ log(response);
379
+ expect(response.entity.length).toBeGreaterThan(0);
380
+ });
381
+
357
382
 
358
383
  test('fetchAll-IncludedFields', async () => {
359
384
  let response = await fetchAll('drumeo', 'instructor',{includedFields: ['is_active']});
@@ -443,6 +468,20 @@ describe('Sanity Queries', function () {
443
468
  expect(response.lessons[0].is_bonus_content).toBeDefined();
444
469
  });
445
470
 
471
+ test('fetchShowsData-OddTimes', async () => {
472
+ const response = await fetchShowsData('drumeo');
473
+ log(response);
474
+ expect(response.length).toBeGreaterThan(0);
475
+ const showTypes = response.map((x) => x.type);
476
+ expect(showTypes).toContain('odd-times');
477
+ });
478
+
479
+ test('fetchMetadata-Coach-Lessons', async () => {
480
+ const response = await fetchMetadata('drumeo','coach-lessons');
481
+ log(response);
482
+ expect(response).toBeDefined();
483
+ });
484
+
446
485
  });
447
486
 
448
487
  describe('Filter Builder', function () {
File without changes