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.
- package/.github/workflows/node.js.yml +0 -0
- package/CHANGELOG.md +4 -0
- package/README.md +0 -0
- package/babel.config.js +0 -0
- package/docs/config.js.html +7 -3
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/index.html +1 -1
- package/docs/module-Config.html +28 -3
- package/docs/module-Railcontent-Services.html +7 -7
- package/docs/module-Sanity-Services.html +426 -48
- package/docs/railcontent.js.html +37 -47
- package/docs/sanity.js.html +69 -12
- package/docs/scripts/collapse.js +0 -0
- package/docs/scripts/commonNav.js +0 -0
- package/docs/scripts/linenumber.js +0 -0
- package/docs/scripts/nav.js +0 -0
- package/docs/scripts/polyfill.js +0 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
- package/docs/scripts/prettify/lang-css.js +0 -0
- package/docs/scripts/prettify/prettify.js +0 -0
- package/docs/scripts/search.js +0 -0
- package/docs/styles/jsdoc.css +0 -0
- package/docs/styles/prettify.css +0 -0
- package/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/link_mcs.sh +0 -0
- package/package.json +1 -1
- package/src/contentMetaData.js +11 -0
- package/src/contentTypeConfig.js +6 -2
- package/src/services/railcontent.js +0 -2
- package/src/services/sanity.js +66 -11
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/sanityQueryService.test.js +39 -0
- package/tools/generate-index.js +0 -0
package/src/services/sanity.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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],`;
|
package/test/localStorageMock.js
CHANGED
|
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 () {
|
package/tools/generate-index.js
CHANGED
|
File without changes
|