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.
Files changed (132) hide show
  1. package/.coderabbit.yaml +0 -0
  2. package/.editorconfig +0 -0
  3. package/.github/pull_request_template.md +0 -0
  4. package/.github/workflows/conventional-commits.yaml +0 -0
  5. package/.github/workflows/docs.js.yml +0 -0
  6. package/.github/workflows/node.js.yml +0 -0
  7. package/.prettierignore +0 -0
  8. package/.prettierrc +0 -0
  9. package/CHANGELOG.md +19 -0
  10. package/README.md +0 -0
  11. package/babel.config.cjs +0 -0
  12. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  13. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  14. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  15. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  16. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  17. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  18. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  19. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  20. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  21. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  22. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  23. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  24. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  25. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  26. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  27. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  28. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  30. package/docs/forums_discussions.js.html +0 -0
  31. package/docs/forums_forum.js.html +0 -0
  32. package/docs/gamification_awards.js.html +0 -0
  33. package/docs/gamification_types.js.html +0 -0
  34. package/docs/module-Categories.html +0 -0
  35. package/docs/module-ForumCategories.html +0 -0
  36. package/docs/module-ForumDiscussions.html +0 -0
  37. package/docs/module-Threads.html +0 -0
  38. package/docs/scripts/collapse.js +0 -0
  39. package/docs/scripts/commonNav.js +0 -0
  40. package/docs/scripts/linenumber.js +0 -0
  41. package/docs/scripts/nav.js +0 -0
  42. package/docs/scripts/polyfill.js +0 -0
  43. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  44. package/docs/scripts/prettify/lang-css.js +0 -0
  45. package/docs/scripts/prettify/prettify.js +0 -0
  46. package/docs/scripts/search.js +0 -0
  47. package/docs/styles/jsdoc.css +0 -0
  48. package/docs/styles/prettify.css +0 -0
  49. package/docs/user_memberships.js.html +0 -0
  50. package/jest.config.js +0 -0
  51. package/package.json +1 -1
  52. package/src/contentMetaData.js +16 -0
  53. package/src/contentTypeConfig.js +135 -120
  54. package/src/filterBuilder.js +0 -0
  55. package/src/infrastructure/http/HttpClient.ts +0 -0
  56. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  57. package/src/infrastructure/http/index.ts +0 -0
  58. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  59. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  60. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  61. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  62. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  63. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  64. package/src/lib/httpHelper.js +0 -0
  65. package/src/lib/lastUpdated.js +0 -0
  66. package/src/services/api/types.js +0 -0
  67. package/src/services/api/types.ts +0 -0
  68. package/src/services/config.js +0 -0
  69. package/src/services/content-org/content-org.js +0 -0
  70. package/src/services/content-org/guided-courses.ts +0 -0
  71. package/src/services/content-org/learning-paths.ts +0 -0
  72. package/src/services/content-org/playlists-types.js +0 -0
  73. package/src/services/content-org/playlists.js +0 -0
  74. package/src/services/content.js +5 -6
  75. package/src/services/contentAggregator.js +0 -0
  76. package/src/services/contentLikes.js +0 -0
  77. package/src/services/contentProgress.js +0 -0
  78. package/src/services/dataContext.js +0 -0
  79. package/src/services/dateUtils.js +0 -0
  80. package/src/services/eventsAPI.js +0 -0
  81. package/src/services/forums/categories.ts +0 -0
  82. package/src/services/forums/forums.ts +0 -0
  83. package/src/services/forums/posts.ts +0 -0
  84. package/src/services/forums/threads.ts +0 -0
  85. package/src/services/forums/types.ts +0 -0
  86. package/src/services/gamification/awards.ts +0 -0
  87. package/src/services/gamification/gamification.js +0 -0
  88. package/src/services/imageSRCBuilder.js +0 -0
  89. package/src/services/imageSRCVerify.js +0 -0
  90. package/src/services/railcontent.js +0 -0
  91. package/src/services/recommendations.js +0 -0
  92. package/src/services/sanity.js +22 -11
  93. package/src/services/types.js +0 -0
  94. package/src/services/user/account.ts +0 -0
  95. package/src/services/user/chat.js +0 -0
  96. package/src/services/user/interests.js +0 -0
  97. package/src/services/user/management.js +0 -0
  98. package/src/services/user/memberships.ts +0 -0
  99. package/src/services/user/notifications.js +0 -0
  100. package/src/services/user/onboarding.ts +0 -0
  101. package/src/services/user/payments.ts +0 -0
  102. package/src/services/user/permissions.js +0 -0
  103. package/src/services/user/profile.js +0 -0
  104. package/src/services/user/sessions.js +0 -0
  105. package/src/services/user/types.js +0 -0
  106. package/src/services/user/user-management-system.js +0 -0
  107. package/src/services/userActivity.js +0 -0
  108. package/test/HttpClient.test.js +0 -0
  109. package/test/content.test.js +0 -0
  110. package/test/contentLikes.test.js +0 -0
  111. package/test/contentProgress.test.js +0 -0
  112. package/test/dataContext.test.js +0 -0
  113. package/test/forum.test.js +0 -0
  114. package/test/imageSRCBuilder.test.js +0 -0
  115. package/test/imageSRCVerify.test.js +0 -0
  116. package/test/initializeTests.js +0 -0
  117. package/test/lib/lastUpdated.test.js +0 -0
  118. package/test/live/contentProgressLive.test.js +0 -0
  119. package/test/live/railcontentLive.test.js +0 -0
  120. package/test/localStorageMock.js +0 -0
  121. package/test/log.js +0 -0
  122. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  123. package/test/mockData/mockData_progress_content.json +0 -0
  124. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  125. package/test/mockData/mockData_user_practices.json +0 -0
  126. package/test/notifications.test.js +0 -0
  127. package/test/progressRows.test.js +0 -0
  128. package/test/sanityQueryService.test.js +0 -0
  129. package/test/streakMessage.test.js +0 -0
  130. package/test/user/permissions.test.js +0 -0
  131. package/test/userActivity.test.js +0 -0
  132. 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
File without changes
File without changes
File without changes
package/jest.config.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.69.0",
3
+ "version": "2.70.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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,
@@ -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
- * Takes the included fields array and returns a string that can be used in a groq query.
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
- export function filtersToGroq(filters, selectedFilters = [], pageName = '') {
827
- if (!filters) {
828
- filters = []
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
- //Account for multiple railcontent id's
832
- let multipleIdFilters = ''
833
- filters.forEach((item) => {
834
- if (item.includes('railcontent_id in')) {
835
- filters.pop(item)
836
- multipleIdFilters += ` && ${item} `
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
- //Group All Other filters
841
- const groupedFilters = groupFilters(filters)
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
- //Format groupFilter itemsss
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
- // Filter out values that exist in selectedFilters
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((value) => {
853
- if (key === 'bpm' && !selectedFilters.includes('bpm')) {
854
- if (value.includes('-')) {
855
- const [min, max] = value.split('-').map(Number)
856
- return `(bpm > ${min} && bpm < ${max})`
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
- //Return
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
 
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
@@ -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.railcontent_id)) : [],
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.railcontent_id);
104
- const indexB = ranking.indexOf(b.railcontent_id);
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
@@ -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 needsAccessDecorator(results, userPermissions, isAdmin) {
1835
- if (globalConfig.sanityConfig.useDummyRailContentMethods) return results
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['need_access'] = doesUserNeedAccessToContent(result, userPermissions, isAdmin)
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['need_access'] = doesUserNeedAccessToContent(lesson, userPermissions, isAdmin) // Updated to check lesson access
1847
+ lesson[fieldName] = callback(lesson) // Updated to check lesson access
1849
1848
  })
1850
1849
  } else {
1851
- result['need_access'] = doesUserNeedAccessToContent(result, userPermissions, isAdmin)
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['need_access'] = doesUserNeedAccessToContent(result, userPermissions, isAdmin)
1855
+ result[fieldName] = callback(result)
1857
1856
  })
1858
1857
  } else {
1859
- results['need_access'] = doesUserNeedAccessToContent(results, userPermissions, isAdmin)
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()
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
File without changes
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