musora-content-services 2.69.0 → 2.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.coderabbit.yaml +0 -0
- package/.editorconfig +0 -0
- package/.github/pull_request_template.md +0 -0
- package/.github/workflows/conventional-commits.yaml +0 -0
- package/.github/workflows/docs.js.yml +0 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/CHANGELOG.md +19 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- 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/forums_discussions.js.html +0 -0
- package/docs/forums_forum.js.html +0 -0
- package/docs/gamification_awards.js.html +0 -0
- package/docs/gamification_types.js.html +0 -0
- package/docs/module-Categories.html +0 -0
- package/docs/module-ForumCategories.html +0 -0
- package/docs/module-ForumDiscussions.html +0 -0
- package/docs/module-Threads.html +0 -0
- 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/docs/user_memberships.js.html +0 -0
- package/jest.config.js +0 -0
- package/package.json +1 -1
- package/src/contentMetaData.js +16 -0
- package/src/contentTypeConfig.js +135 -120
- package/src/filterBuilder.js +0 -0
- package/src/infrastructure/http/HttpClient.ts +0 -0
- package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
- package/src/infrastructure/http/index.ts +0 -0
- package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
- package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
- package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
- package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
- package/src/lib/httpHelper.js +0 -0
- package/src/lib/lastUpdated.js +0 -0
- package/src/services/api/types.js +0 -0
- package/src/services/api/types.ts +0 -0
- package/src/services/config.js +0 -0
- package/src/services/content-org/content-org.js +0 -0
- package/src/services/content-org/guided-courses.ts +0 -0
- package/src/services/content-org/learning-paths.ts +0 -0
- package/src/services/content-org/playlists-types.js +0 -0
- package/src/services/content-org/playlists.js +0 -0
- package/src/services/content.js +5 -6
- package/src/services/contentAggregator.js +0 -0
- package/src/services/contentLikes.js +0 -0
- package/src/services/contentProgress.js +0 -0
- package/src/services/dataContext.js +0 -0
- package/src/services/dateUtils.js +0 -0
- package/src/services/eventsAPI.js +0 -0
- package/src/services/forums/categories.ts +0 -0
- package/src/services/forums/forums.ts +0 -0
- package/src/services/forums/posts.ts +0 -0
- package/src/services/forums/threads.ts +0 -0
- package/src/services/forums/types.ts +0 -0
- package/src/services/gamification/awards.ts +0 -0
- package/src/services/gamification/gamification.js +0 -0
- package/src/services/imageSRCBuilder.js +0 -0
- package/src/services/imageSRCVerify.js +0 -0
- package/src/services/railcontent.js +0 -0
- package/src/services/recommendations.js +0 -0
- package/src/services/sanity.js +22 -11
- package/src/services/types.js +0 -0
- package/src/services/user/account.ts +0 -0
- package/src/services/user/chat.js +0 -0
- package/src/services/user/interests.js +0 -0
- package/src/services/user/management.js +0 -0
- package/src/services/user/memberships.ts +0 -0
- package/src/services/user/notifications.js +0 -0
- package/src/services/user/onboarding.ts +0 -0
- package/src/services/user/payments.ts +0 -0
- package/src/services/user/permissions.js +0 -0
- package/src/services/user/profile.js +0 -0
- package/src/services/user/sessions.js +0 -0
- package/src/services/user/types.js +0 -0
- package/src/services/user/user-management-system.js +0 -0
- package/src/services/userActivity.js +0 -0
- package/test/HttpClient.test.js +0 -0
- package/test/content.test.js +0 -0
- package/test/contentLikes.test.js +0 -0
- package/test/contentProgress.test.js +0 -0
- package/test/dataContext.test.js +0 -0
- package/test/forum.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/initializeTests.js +0 -0
- package/test/lib/lastUpdated.test.js +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
- package/test/mockData/mockData_progress_content.json +0 -0
- package/test/mockData/mockData_sanity_progress_content.json +0 -0
- package/test/mockData/mockData_user_practices.json +0 -0
- package/test/notifications.test.js +0 -0
- package/test/progressRows.test.js +0 -0
- package/test/sanityQueryService.test.js +0 -0
- package/test/streakMessage.test.js +0 -0
- package/test/user/permissions.test.js +0 -0
- package/test/userActivity.test.js +0 -0
- package/tools/generate-index.cjs +0 -0
package/.coderabbit.yaml
CHANGED
|
File without changes
|
package/.editorconfig
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/.prettierignore
CHANGED
|
File without changes
|
package/.prettierrc
CHANGED
|
File without changes
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
## [2.70.0](https://github.com/railroadmedia/musora-content-services/compare/v2.67.2...v2.70.0) (2025-11-05)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* add fields for learning path v2 ([#537](https://github.com/railroadmedia/musora-content-services/issues/537)) ([b2bcbd2](https://github.com/railroadmedia/musora-content-services/commit/b2bcbd2306afa730e5c61d6ee34fe082212dfdbb))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* Add is_content and page_type to all content ([#507](https://github.com/railroadmedia/musora-content-services/issues/507)) ([174706b](https://github.com/railroadmedia/musora-content-services/commit/174706b736540b9d24ceaee11539df9203d2d135))
|
|
16
|
+
|
|
17
|
+
### [2.69.1](https://github.com/railroadmedia/musora-content-services/compare/v2.69.0...v2.69.1) (2025-11-04)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* Add is_content and page_type to all content ([#507](https://github.com/railroadmedia/musora-content-services/issues/507)) ([174706b](https://github.com/railroadmedia/musora-content-services/commit/174706b736540b9d24ceaee11539df9203d2d135))
|
|
23
|
+
|
|
5
24
|
## [2.69.0](https://github.com/railroadmedia/musora-content-services/compare/v2.67.2...v2.69.0) (2025-11-04)
|
|
6
25
|
|
|
7
26
|
|
package/README.md
CHANGED
|
File without changes
|
package/babel.config.cjs
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/docs/module-Threads.html
CHANGED
|
File without changes
|
package/docs/scripts/collapse.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/docs/scripts/nav.js
CHANGED
|
File without changes
|
package/docs/scripts/polyfill.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/docs/scripts/search.js
CHANGED
|
File without changes
|
package/docs/styles/jsdoc.css
CHANGED
|
File without changes
|
package/docs/styles/prettify.css
CHANGED
|
File without changes
|
|
File without changes
|
package/jest.config.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/src/contentMetaData.js
CHANGED
|
@@ -39,6 +39,19 @@ class SortingOptions {
|
|
|
39
39
|
]
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
export class LengthFilterOptions {
|
|
43
|
+
static UpTo7 = { value: '<420', name: 'Up to 7 Minutes' }
|
|
44
|
+
static From7To15 = { value: '420-900', name: '7 to 15 Minutes' }
|
|
45
|
+
static From15To30 = { value: '901-1800', name: '15 to 30 Minutes' }
|
|
46
|
+
static More30 = { value: '>1801', name: '30+ Minutes' }
|
|
47
|
+
static AllOptions = [
|
|
48
|
+
this.UpTo7.name,
|
|
49
|
+
this.From7To15.name,
|
|
50
|
+
this.From15To30.name,
|
|
51
|
+
this.More30.name,
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
|
|
42
55
|
export class Tabs {
|
|
43
56
|
static ForYou = { name: 'For You', short_name: 'For You' }
|
|
44
57
|
static Individuals = { name: 'Individuals', short_name: 'Individuals', value: 'type,individuals', cardType: 'big' }
|
|
@@ -82,6 +95,7 @@ const commonMetadata = {
|
|
|
82
95
|
name: 'Lessons',
|
|
83
96
|
filterOptions: {
|
|
84
97
|
difficulty: DIFFICULTY_STRINGS,
|
|
98
|
+
length: LengthFilterOptions.AllOptions,
|
|
85
99
|
style: ['Country/Folk', 'Funk/Disco', 'Hard Rock/Metal', 'Hip-Hop/Rap/EDM', 'Holiday/Soundtrack', 'Jazz/Blues', 'Latin/World', 'Pop/Rock', 'R&B/Soul', 'Worship/Gospel'],
|
|
86
100
|
type: LESSON_TYPE_FILTER,
|
|
87
101
|
progress: PROGRESS_NAMES,
|
|
@@ -157,6 +171,7 @@ const contentMetadata = {
|
|
|
157
171
|
name: 'Lessons',
|
|
158
172
|
filterOptions: {
|
|
159
173
|
difficulty: DIFFICULTY_STRINGS,
|
|
174
|
+
length: LengthFilterOptions.AllOptions,
|
|
160
175
|
style: ['Country/Folk', 'Funk/Disco', 'Hard Rock/Metal', 'Hip-Hop/Rap/EDM', 'Holiday/Soundtrack', 'Jazz/Blues', 'Latin/World', 'Pop/Rock', 'R&B/Soul', 'Worship/Gospel'],
|
|
161
176
|
type: LESSON_TYPE_FILTER,
|
|
162
177
|
progress: PROGRESS_NAMES,
|
|
@@ -182,6 +197,7 @@ const contentMetadata = {
|
|
|
182
197
|
name: 'Lessons',
|
|
183
198
|
filterOptions: {
|
|
184
199
|
difficulty: DIFFICULTY_STRINGS,
|
|
200
|
+
length: LengthFilterOptions.AllOptions,
|
|
185
201
|
style: ['Classical', 'Country/Folk', 'Funk/Disco', 'Hip-Hop/Rap/EDM', 'Holiday/Soundtrack', 'Jazz/Blues', 'Latin/World', 'Pop/Rock', 'R&B/Soul', 'Worship/Gospel'],
|
|
186
202
|
type: LESSON_TYPE_FILTER,
|
|
187
203
|
progress: PROGRESS_NAMES,
|
package/src/contentTypeConfig.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
//import {AWSUrl, CloudFrontURl} from "./services/config";
|
|
2
|
-
import {Tabs} from "./contentMetaData.js";
|
|
2
|
+
import {LengthFilterOptions, Tabs} from "./contentMetaData.js";
|
|
3
3
|
import {FilterBuilder} from "./filterBuilder.js";
|
|
4
4
|
|
|
5
5
|
export const AWSUrl = 'https://s3.us-east-1.amazonaws.com/musora-web-platform'
|
|
6
6
|
export const CloudFrontURl = 'https://d3fzm1tzeyr5n3.cloudfront.net'
|
|
7
7
|
|
|
8
|
+
// This is used to pull related content by license, so we only show "consumable" content
|
|
8
9
|
export const SONG_TYPES = ['song', 'play-along', 'jam-track', 'song-tutorial-children']
|
|
10
|
+
// Oct 2025: It turns out content-meta categories are not really clear
|
|
11
|
+
// THis is used for the page_type field as a post processor so we include parents and children
|
|
12
|
+
// Duplicated in SanityGateway.php if you update this, update that
|
|
13
|
+
export const SONG_TYPES_WITH_CHILDREN = ['song', 'song-part', 'play-along', 'play-along-part', 'jam-track', 'song-tutorial', 'song-tutorial-children']
|
|
9
14
|
// Single hierarchy refers to only one element in the hierarchy has video lessons, not that they have a single parent
|
|
10
15
|
export const SINGLE_PARENT_TYPES = ['course-part', 'pack-bundle-lesson', 'song-tutorial-children']
|
|
11
16
|
|
|
@@ -818,136 +823,142 @@ export function getFieldsForContentType(contentType, asQueryString = true) {
|
|
|
818
823
|
}
|
|
819
824
|
|
|
820
825
|
/**
|
|
821
|
-
*
|
|
822
|
-
* @param {Array<string>} filters - An array of strings that represent applied filters. This should be in the format of a key,value array. eg. ['difficulty,Intermediate',
|
|
823
|
-
* 'genre,rock']
|
|
824
|
-
* @returns {string} - A string that can be used in a groq query
|
|
826
|
+
* Helper function to create type conditions from content type arrays
|
|
825
827
|
*/
|
|
826
|
-
|
|
827
|
-
if (!
|
|
828
|
-
|
|
829
|
-
}
|
|
828
|
+
function createTypeConditions(lessonTypes) {
|
|
829
|
+
if (!lessonTypes || lessonTypes.length === 0) return ''
|
|
830
|
+
const conditions = lessonTypes.map(type => `_type == '${type}'`).join(' || ')
|
|
831
|
+
return conditions ? `(${conditions})` : ''
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Filter handler registry - maps filter keys to their handler functions
|
|
836
|
+
*/
|
|
837
|
+
const filterHandlers = {
|
|
838
|
+
style: (value) => `"${value}" in genre[]->name`,
|
|
839
|
+
|
|
840
|
+
difficulty: (value) => {
|
|
841
|
+
if (value === 'Introductory') {
|
|
842
|
+
return `(difficulty_string == "Novice" || difficulty_string == "Introductory")`
|
|
843
|
+
}
|
|
844
|
+
return `difficulty_string == "${value}"`
|
|
845
|
+
},
|
|
846
|
+
|
|
847
|
+
tab: (value, pageName) => {
|
|
848
|
+
const valueLower = value.toLowerCase()
|
|
849
|
+
const tabMappings = {
|
|
850
|
+
[Tabs.Individuals.name.toLowerCase()]: individualLessonsTypes,
|
|
851
|
+
[Tabs.Collections.name.toLowerCase()]: collectionLessonTypes,
|
|
852
|
+
[Tabs.Tutorials.name.toLowerCase()]: tutorialsLessonTypes,
|
|
853
|
+
[Tabs.Transcriptions.name.toLowerCase()]: transcriptionsLessonTypes,
|
|
854
|
+
[Tabs.PlayAlongs.name.toLowerCase()]: playAlongLessonTypes,
|
|
855
|
+
[Tabs.JamTracks.name.toLowerCase()]: jamTrackLessonTypes,
|
|
856
|
+
[Tabs.ExploreAll.name.toLowerCase()]: filterTypes[pageName] || [],
|
|
857
|
+
[Tabs.RecentAll.name.toLowerCase()]: recentTypes[pageName] || [],
|
|
858
|
+
[Tabs.SingleLessons.name.toLowerCase()]: individualLessonsTypes,
|
|
859
|
+
[Tabs.Courses.name.toLowerCase()]: coursesLessonTypes,
|
|
860
|
+
[Tabs.SkillPacks.name.toLowerCase()]: skillLessonTypes,
|
|
861
|
+
[Tabs.Entertainment.name.toLowerCase()]: entertainmentLessonTypes,
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
const lessonTypes = tabMappings[valueLower]
|
|
865
|
+
if (lessonTypes) {
|
|
866
|
+
return createTypeConditions(lessonTypes)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return `_type == "${value}"`
|
|
870
|
+
},
|
|
871
|
+
|
|
872
|
+
type: (value) => {
|
|
873
|
+
const typeKey = value.toLowerCase()
|
|
874
|
+
const lessonTypes = lessonTypesMapping[typeKey]
|
|
875
|
+
|
|
876
|
+
if (lessonTypes) {
|
|
877
|
+
return createTypeConditions(lessonTypes)
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return `_type == "${value}"`
|
|
881
|
+
},
|
|
882
|
+
|
|
883
|
+
length: (value) => {
|
|
884
|
+
// Find the matching length option by name
|
|
885
|
+
const lengthOption = Object.values(LengthFilterOptions)
|
|
886
|
+
.find(opt => typeof opt === 'object' && opt.name === value)
|
|
887
|
+
|
|
888
|
+
if (!lengthOption) return ''
|
|
830
889
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (
|
|
835
|
-
|
|
836
|
-
|
|
890
|
+
const optionValue = lengthOption.value
|
|
891
|
+
|
|
892
|
+
// Parse the value format: '<420', '420-900', '>1801'
|
|
893
|
+
if (optionValue.startsWith('<')) {
|
|
894
|
+
const max = parseInt(optionValue.substring(1), 10)
|
|
895
|
+
return `(length_in_seconds < ${max})`
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (optionValue.startsWith('>')) {
|
|
899
|
+
const min = parseInt(optionValue.substring(1), 10)
|
|
900
|
+
return `(length_in_seconds > ${min})`
|
|
837
901
|
}
|
|
838
|
-
})
|
|
839
902
|
|
|
840
|
-
|
|
841
|
-
|
|
903
|
+
if (optionValue.includes('-')) {
|
|
904
|
+
const [min, max] = optionValue.split('-').map(Number)
|
|
905
|
+
return `(length_in_seconds >= ${min} && length_in_seconds <= ${max})`
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
return ''
|
|
909
|
+
},
|
|
910
|
+
|
|
911
|
+
pageName: () => '', // pageName is meta, doesn't generate a query
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* Takes the included fields array and returns a string that can be used in the groq query.
|
|
916
|
+
* @param {Array<string>} filters - An array of strings that represent applied filters.
|
|
917
|
+
* Format: ['difficulty,Intermediate', 'genre,rock']
|
|
918
|
+
* @param {Array<string>} selectedFilters - Filters to exclude from processing
|
|
919
|
+
* @param {string} pageName - Current page name for context-specific filtering
|
|
920
|
+
* @returns {string} - A GROQ query filter string
|
|
921
|
+
*/
|
|
922
|
+
export function filtersToGroq(filters = [], selectedFilters = [], pageName = '') {
|
|
923
|
+
// Handle railcontent_id filters separately (they use different syntax)
|
|
924
|
+
const railcontentIdFilters = filters
|
|
925
|
+
.filter(item => item.includes('railcontent_id in'))
|
|
926
|
+
.map(item => ` && ${item}`)
|
|
927
|
+
.join('')
|
|
842
928
|
|
|
843
|
-
//
|
|
929
|
+
// Remove railcontent_id filters from main processing
|
|
930
|
+
const regularFilters = filters.filter(item => !item.includes('railcontent_id in'))
|
|
931
|
+
|
|
932
|
+
// Group filters by key
|
|
933
|
+
const groupedFilters = groupFilters(regularFilters)
|
|
934
|
+
|
|
935
|
+
// Process each filter group
|
|
844
936
|
const filterClauses = Object.entries(groupedFilters)
|
|
845
937
|
.map(([key, values]) => {
|
|
938
|
+
// Skip empty filters
|
|
846
939
|
if (!key || values.length === 0) return ''
|
|
940
|
+
|
|
941
|
+
// Handle boolean flags (is_*)
|
|
847
942
|
if (key.startsWith('is_')) {
|
|
848
943
|
return `&& ${key} == true`
|
|
849
944
|
}
|
|
850
|
-
|
|
945
|
+
|
|
946
|
+
// Skip if in selectedFilters
|
|
947
|
+
if (selectedFilters.includes(key)) {
|
|
948
|
+
return ''
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Process each value with the appropriate handler
|
|
851
952
|
const joinedValues = values
|
|
852
|
-
.map(
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
} else if (value.includes('+')) {
|
|
858
|
-
const min = parseInt(value, 10)
|
|
859
|
-
return `(bpm > ${min})`
|
|
860
|
-
} else {
|
|
861
|
-
return `bpm == ${value}`
|
|
862
|
-
}
|
|
863
|
-
} else if (
|
|
864
|
-
['creativity', 'essential', 'focus', 'genre', 'lifestyle', 'theory', 'topic'].includes(
|
|
865
|
-
key
|
|
866
|
-
) &&
|
|
867
|
-
!selectedFilters.includes(key)
|
|
868
|
-
) {
|
|
869
|
-
return `"${value}" in ${key}[]->name`
|
|
870
|
-
} else if (
|
|
871
|
-
['style'].includes(
|
|
872
|
-
key
|
|
873
|
-
) &&
|
|
874
|
-
!selectedFilters.includes(key)
|
|
875
|
-
) {
|
|
876
|
-
return `"${value}" in genre[]->name`
|
|
877
|
-
} else if (key === 'gear' && !selectedFilters.includes('gear')) {
|
|
878
|
-
return `gear match "${value}"`
|
|
879
|
-
} else if (key === 'instrumentless' && !selectedFilters.includes(key)) {
|
|
880
|
-
if (value === 'Full Song Only') {
|
|
881
|
-
return `(!instrumentless || instrumentless == null)`
|
|
882
|
-
} else if (value === 'Instrument Removed') {
|
|
883
|
-
return `instrumentless`
|
|
884
|
-
} else {
|
|
885
|
-
return `instrumentless == ${value}`
|
|
886
|
-
}
|
|
887
|
-
} else if (key === 'difficulty' && !selectedFilters.includes(key)) {
|
|
888
|
-
if(value === 'Introductory'){
|
|
889
|
-
return `(difficulty_string == "Novice" || difficulty_string == "Introductory" )`
|
|
890
|
-
}
|
|
891
|
-
return `difficulty_string == "${value}"`
|
|
892
|
-
} else if (key === 'tab' && !selectedFilters.includes(key)) {
|
|
893
|
-
if(value.toLowerCase() === Tabs.SingleLessons.name.toLowerCase()){
|
|
894
|
-
const conditions = individualLessonsTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
|
|
895
|
-
return ` (${conditions})`;
|
|
896
|
-
} else if(value.toLowerCase() === Tabs.Courses.name.toLowerCase()){
|
|
897
|
-
const conditions = coursesLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
|
|
898
|
-
return ` (${conditions})`;
|
|
899
|
-
} else if(value.toLowerCase() === Tabs.Entertainment.name.toLowerCase()){
|
|
900
|
-
const conditions = entertainmentLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
|
|
901
|
-
return ` (${conditions})`;
|
|
902
|
-
} else if(value.toLowerCase() === Tabs.Tutorials.name.toLowerCase()){
|
|
903
|
-
const conditions = tutorialsLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
|
|
904
|
-
return ` (${conditions})`;
|
|
905
|
-
} else if(value.toLowerCase() === Tabs.Transcriptions.name.toLowerCase()){
|
|
906
|
-
const conditions = transcriptionsLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
|
|
907
|
-
return ` (${conditions})`;
|
|
908
|
-
} else if(value.toLowerCase() === Tabs.PlayAlongs.name.toLowerCase()){
|
|
909
|
-
const conditions = playAlongLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
|
|
910
|
-
return ` (${conditions})`;
|
|
911
|
-
} else if(value.toLowerCase() === Tabs.JamTracks.name.toLowerCase()){
|
|
912
|
-
const conditions = jamTrackLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
|
|
913
|
-
return ` (${conditions})`;
|
|
914
|
-
} else if(value.toLowerCase() === Tabs.ExploreAll.name.toLowerCase()){
|
|
915
|
-
var allLessons = filterTypes[pageName] || [];
|
|
916
|
-
const conditions = allLessons.map(lessonType => `_type == '${lessonType}'`).join(' || ');
|
|
917
|
-
if (conditions === "") return '';
|
|
918
|
-
return ` (${conditions})`;
|
|
919
|
-
}else if(value.toLowerCase() === Tabs.RecentAll.name.toLowerCase()){
|
|
920
|
-
var allLessons = recentTypes[pageName] || [];
|
|
921
|
-
const conditions = allLessons.map(lessonType => `_type == '${lessonType}'`).join(' || ');
|
|
922
|
-
if (conditions === "") return '';
|
|
923
|
-
return ` (${conditions})`;
|
|
924
|
-
}
|
|
925
|
-
return `_type == "${value}"`
|
|
926
|
-
} else if (key === 'type' && !selectedFilters.includes(key)) {
|
|
927
|
-
const typeKey = value.toLowerCase();
|
|
928
|
-
const lessonTypes = lessonTypesMapping[typeKey];
|
|
929
|
-
if (lessonTypes) {
|
|
930
|
-
const conditions = lessonTypes.map(
|
|
931
|
-
(lessonType) => `_type == '${lessonType}'`
|
|
932
|
-
).join(' || ');
|
|
933
|
-
return ` (${conditions})`;
|
|
934
|
-
}
|
|
935
|
-
return `_type == "${value}"`;
|
|
936
|
-
} else if (key === 'length_in_seconds') {
|
|
937
|
-
if (value.includes('-')) {
|
|
938
|
-
const [min, max] = value.split('-').map(Number)
|
|
939
|
-
return `(${key} > ${min} && ${key} < ${max})`
|
|
940
|
-
} else if (value.includes('+')) {
|
|
941
|
-
const min = parseInt(value, 10)
|
|
942
|
-
return `(${key} > ${min})`
|
|
943
|
-
} else {
|
|
944
|
-
return `${key} == ${value}`
|
|
945
|
-
}
|
|
946
|
-
} else if (key === 'pageName') {
|
|
947
|
-
return ` `
|
|
948
|
-
} else if (!selectedFilters.includes(key)) {
|
|
949
|
-
return ` ${key} == ${/^\d+$/.test(value) ? value : `"$${value}"`}`
|
|
953
|
+
.map(value => {
|
|
954
|
+
const handler = filterHandlers[key]
|
|
955
|
+
|
|
956
|
+
if (handler) {
|
|
957
|
+
return handler(value, pageName)
|
|
950
958
|
}
|
|
959
|
+
|
|
960
|
+
// Default handler for unknown filters
|
|
961
|
+
return `${key} == ${/^\d+$/.test(value) ? value : `"${value}"`}`
|
|
951
962
|
})
|
|
952
963
|
.filter(Boolean)
|
|
953
964
|
.join(' || ')
|
|
@@ -958,10 +969,14 @@ export function filtersToGroq(filters, selectedFilters = [], pageName = '') {
|
|
|
958
969
|
.filter(Boolean)
|
|
959
970
|
.join(' ')
|
|
960
971
|
|
|
961
|
-
|
|
962
|
-
return `${multipleIdFilters} ${filterClauses}`
|
|
972
|
+
return `${railcontentIdFilters} ${filterClauses}`.trim()
|
|
963
973
|
}
|
|
964
974
|
|
|
975
|
+
/**
|
|
976
|
+
* Groups filters by category
|
|
977
|
+
* @param {Array<string>} filters - Array of 'key,value' strings
|
|
978
|
+
* @returns {Object} - Object with keys as categories and values as arrays
|
|
979
|
+
*/
|
|
965
980
|
function groupFilters(filters) {
|
|
966
981
|
if (filters.length === 0) return {}
|
|
967
982
|
|
package/src/filterBuilder.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/lib/httpHelper.js
CHANGED
|
File without changes
|
package/src/lib/lastUpdated.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/services/config.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/services/content.js
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
fetchLeaving, fetchScheduledAndNewReleases, fetchContentRows
|
|
15
15
|
} from './sanity.js'
|
|
16
16
|
import {TabResponseType, Tabs, capitalizeFirstLetter} from '../contentMetaData.js'
|
|
17
|
-
import {fetchHandler} from "./railcontent";
|
|
18
17
|
import {recommendations, rankCategories, rankItems} from "./recommendations";
|
|
19
18
|
import {addContextToContent} from "./contentAggregator.js";
|
|
20
19
|
|
|
@@ -90,7 +89,7 @@ export async function getTabResults(brand, pageName, tabName, {
|
|
|
90
89
|
let temp = await fetchTabData(brand, pageName, { page, limit, sort, includedFields: mergedIncludedFields, progress: progressValue });
|
|
91
90
|
|
|
92
91
|
const [ranking, contextResults] = await Promise.all([
|
|
93
|
-
sort === 'recommended' ? rankItems(brand, temp.entity.map(e => e.
|
|
92
|
+
sort === 'recommended' ? rankItems(brand, temp.entity.map(e => e.id)) : [],
|
|
94
93
|
addContextToContent(() => temp.entity, {
|
|
95
94
|
addNextLesson: true,
|
|
96
95
|
addNavigateTo: true,
|
|
@@ -100,8 +99,8 @@ export async function getTabResults(brand, pageName, tabName, {
|
|
|
100
99
|
]);
|
|
101
100
|
|
|
102
101
|
results = ranking.length === 0 ? contextResults : contextResults.sort((a, b) => {
|
|
103
|
-
const indexA = ranking.indexOf(a.
|
|
104
|
-
const indexB = ranking.indexOf(b.
|
|
102
|
+
const indexA = ranking.indexOf(a.id);
|
|
103
|
+
const indexB = ranking.indexOf(b.id);
|
|
105
104
|
return (indexA === -1 ? Infinity : indexA) - (indexB === -1 ? Infinity : indexB);
|
|
106
105
|
})
|
|
107
106
|
}
|
|
@@ -436,7 +435,7 @@ export async function getRecommendedForYou(brand, rowId = null, {
|
|
|
436
435
|
export async function getLegacyMethods(brand) {
|
|
437
436
|
|
|
438
437
|
// TODO: Replace with real data from Sanity when available with permissions
|
|
439
|
-
|
|
438
|
+
|
|
440
439
|
return [
|
|
441
440
|
{
|
|
442
441
|
id: 1,
|
|
@@ -451,4 +450,4 @@ export async function getLegacyMethods(brand) {
|
|
|
451
450
|
child_count: 12,
|
|
452
451
|
},
|
|
453
452
|
]
|
|
454
|
-
}
|
|
453
|
+
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/services/sanity.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
getFieldsForContentTypeWithFilteredChildren,
|
|
21
21
|
getChildFieldsForContentType,
|
|
22
22
|
SONG_TYPES,
|
|
23
|
+
SONG_TYPES_WITH_CHILDREN,
|
|
23
24
|
} from '../contentTypeConfig.js'
|
|
24
25
|
import {fetchSimilarItems, recommendations} from './recommendations.js'
|
|
25
26
|
import { processMetadata, typeWithSortOrder } from '../contentMetaData.js'
|
|
@@ -1821,6 +1822,7 @@ export async function fetchSanity(
|
|
|
1821
1822
|
results = processNeedAccess
|
|
1822
1823
|
? await needsAccessDecorator(results, userPermissions, isAdmin)
|
|
1823
1824
|
: results
|
|
1825
|
+
results = pageTypeDecorator(results)
|
|
1824
1826
|
return customPostProcess ? customPostProcess(results) : results
|
|
1825
1827
|
} else {
|
|
1826
1828
|
throw new Error('No results found')
|
|
@@ -1831,37 +1833,46 @@ export async function fetchSanity(
|
|
|
1831
1833
|
}
|
|
1832
1834
|
}
|
|
1833
1835
|
|
|
1834
|
-
function
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
userPermissions = new Set(userPermissions)
|
|
1838
|
-
|
|
1836
|
+
function contentResultsDecorator(results, fieldName, callback)
|
|
1837
|
+
{
|
|
1839
1838
|
if (Array.isArray(results)) {
|
|
1840
1839
|
results.forEach((result) => {
|
|
1841
|
-
result[
|
|
1840
|
+
result[fieldName] = callback(result)
|
|
1842
1841
|
})
|
|
1843
1842
|
} else if (results.entity && Array.isArray(results.entity)) {
|
|
1844
1843
|
// Group By
|
|
1845
1844
|
results.entity.forEach((result) => {
|
|
1846
1845
|
if (result.lessons) {
|
|
1847
1846
|
result.lessons.forEach((lesson) => {
|
|
1848
|
-
lesson[
|
|
1847
|
+
lesson[fieldName] = callback(lesson) // Updated to check lesson access
|
|
1849
1848
|
})
|
|
1850
1849
|
} else {
|
|
1851
|
-
result[
|
|
1850
|
+
result[fieldName] = callback(result)
|
|
1852
1851
|
}
|
|
1853
1852
|
})
|
|
1854
1853
|
} else if (results.related_lessons && Array.isArray(results.related_lessons)) {
|
|
1855
1854
|
results.related_lessons.forEach((result) => {
|
|
1856
|
-
result[
|
|
1855
|
+
result[fieldName] = callback(result)
|
|
1857
1856
|
})
|
|
1858
1857
|
} else {
|
|
1859
|
-
results[
|
|
1858
|
+
results[fieldName] = callback(results)
|
|
1860
1859
|
}
|
|
1861
1860
|
|
|
1862
1861
|
return results
|
|
1863
1862
|
}
|
|
1864
1863
|
|
|
1864
|
+
function pageTypeDecorator(results)
|
|
1865
|
+
{
|
|
1866
|
+
return contentResultsDecorator(results, 'page_type', function(content) { return SONG_TYPES_WITH_CHILDREN.includes(content['type']) ? 'song' : 'lesson'})
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
|
|
1870
|
+
function needsAccessDecorator(results, userPermissions, isAdmin) {
|
|
1871
|
+
if (globalConfig.sanityConfig.useDummyRailContentMethods) return results
|
|
1872
|
+
userPermissions = new Set(userPermissions)
|
|
1873
|
+
return contentResultsDecorator(results, 'need_access', function (content) { return doesUserNeedAccessToContent(content, userPermissions, isAdmin) })
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1865
1876
|
function doesUserNeedAccessToContent(result, userPermissions, isAdmin) {
|
|
1866
1877
|
if (isAdmin ?? false) {
|
|
1867
1878
|
return false
|
|
@@ -2176,7 +2187,7 @@ export async function fetchTabData(
|
|
|
2176
2187
|
let entityFieldsString = ''
|
|
2177
2188
|
let filter = ''
|
|
2178
2189
|
|
|
2179
|
-
filter = `brand == "${brand}" ${includedFieldsFilter} ${progressFilter}`
|
|
2190
|
+
filter = `brand == "${brand}" && (defined(railcontent_id)) ${includedFieldsFilter} ${progressFilter}`
|
|
2180
2191
|
const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
|
|
2181
2192
|
const childrenFields = await getChildFieldsForContentType('tab-data')
|
|
2182
2193
|
const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`).buildFilter()
|
package/src/services/types.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/HttpClient.test.js
CHANGED
|
File without changes
|
package/test/content.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/dataContext.test.js
CHANGED
|
File without changes
|
package/test/forum.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/initializeTests.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/localStorageMock.js
CHANGED
|
File without changes
|
package/test/log.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/tools/generate-index.cjs
CHANGED
|
File without changes
|