musora-content-services 2.69.1 → 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 (131) 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 +12 -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 +130 -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/railcontent.js +0 -0
  90. package/src/services/recommendations.js +0 -0
  91. package/src/services/sanity.js +1 -1
  92. package/src/services/types.js +0 -0
  93. package/src/services/user/account.ts +0 -0
  94. package/src/services/user/chat.js +0 -0
  95. package/src/services/user/interests.js +0 -0
  96. package/src/services/user/management.js +0 -0
  97. package/src/services/user/memberships.ts +0 -0
  98. package/src/services/user/notifications.js +0 -0
  99. package/src/services/user/onboarding.ts +0 -0
  100. package/src/services/user/payments.ts +0 -0
  101. package/src/services/user/permissions.js +0 -0
  102. package/src/services/user/profile.js +0 -0
  103. package/src/services/user/sessions.js +0 -0
  104. package/src/services/user/types.js +0 -0
  105. package/src/services/user/user-management-system.js +0 -0
  106. package/src/services/userActivity.js +0 -0
  107. package/test/HttpClient.test.js +0 -0
  108. package/test/content.test.js +0 -0
  109. package/test/contentLikes.test.js +0 -0
  110. package/test/contentProgress.test.js +0 -0
  111. package/test/dataContext.test.js +0 -0
  112. package/test/forum.test.js +0 -0
  113. package/test/imageSRCBuilder.test.js +0 -0
  114. package/test/imageSRCVerify.test.js +0 -0
  115. package/test/initializeTests.js +0 -0
  116. package/test/lib/lastUpdated.test.js +0 -0
  117. package/test/live/contentProgressLive.test.js +0 -0
  118. package/test/live/railcontentLive.test.js +0 -0
  119. package/test/localStorageMock.js +0 -0
  120. package/test/log.js +0 -0
  121. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  122. package/test/mockData/mockData_progress_content.json +0 -0
  123. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  124. package/test/mockData/mockData_user_practices.json +0 -0
  125. package/test/notifications.test.js +0 -0
  126. package/test/progressRows.test.js +0 -0
  127. package/test/sanityQueryService.test.js +0 -0
  128. package/test/streakMessage.test.js +0 -0
  129. package/test/user/permissions.test.js +0 -0
  130. package/test/userActivity.test.js +0 -0
  131. 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,18 @@
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
+
5
17
  ### [2.69.1](https://github.com/railroadmedia/musora-content-services/compare/v2.69.0...v2.69.1) (2025-11-04)
6
18
 
7
19
 
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.1",
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,5 +1,5 @@
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'
@@ -823,136 +823,142 @@ export function getFieldsForContentType(contentType, asQueryString = true) {
823
823
  }
824
824
 
825
825
  /**
826
- * Takes the included fields array and returns a string that can be used in a groq query.
827
- * @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',
828
- * 'genre,rock']
829
- * @returns {string} - A string that can be used in a groq query
826
+ * Helper function to create type conditions from content type arrays
830
827
  */
831
- export function filtersToGroq(filters, selectedFilters = [], pageName = '') {
832
- if (!filters) {
833
- filters = []
834
- }
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 ''
835
889
 
836
- //Account for multiple railcontent id's
837
- let multipleIdFilters = ''
838
- filters.forEach((item) => {
839
- if (item.includes('railcontent_id in')) {
840
- filters.pop(item)
841
- 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})`
842
901
  }
843
- })
844
902
 
845
- //Group All Other filters
846
- 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('')
847
928
 
848
- //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
849
936
  const filterClauses = Object.entries(groupedFilters)
850
937
  .map(([key, values]) => {
938
+ // Skip empty filters
851
939
  if (!key || values.length === 0) return ''
940
+
941
+ // Handle boolean flags (is_*)
852
942
  if (key.startsWith('is_')) {
853
943
  return `&& ${key} == true`
854
944
  }
855
- // 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
856
952
  const joinedValues = values
857
- .map((value) => {
858
- if (key === 'bpm' && !selectedFilters.includes('bpm')) {
859
- if (value.includes('-')) {
860
- const [min, max] = value.split('-').map(Number)
861
- return `(bpm > ${min} && bpm < ${max})`
862
- } else if (value.includes('+')) {
863
- const min = parseInt(value, 10)
864
- return `(bpm > ${min})`
865
- } else {
866
- return `bpm == ${value}`
867
- }
868
- } else if (
869
- ['creativity', 'essential', 'focus', 'genre', 'lifestyle', 'theory', 'topic'].includes(
870
- key
871
- ) &&
872
- !selectedFilters.includes(key)
873
- ) {
874
- return `"${value}" in ${key}[]->name`
875
- } else if (
876
- ['style'].includes(
877
- key
878
- ) &&
879
- !selectedFilters.includes(key)
880
- ) {
881
- return `"${value}" in genre[]->name`
882
- } else if (key === 'gear' && !selectedFilters.includes('gear')) {
883
- return `gear match "${value}"`
884
- } else if (key === 'instrumentless' && !selectedFilters.includes(key)) {
885
- if (value === 'Full Song Only') {
886
- return `(!instrumentless || instrumentless == null)`
887
- } else if (value === 'Instrument Removed') {
888
- return `instrumentless`
889
- } else {
890
- return `instrumentless == ${value}`
891
- }
892
- } else if (key === 'difficulty' && !selectedFilters.includes(key)) {
893
- if(value === 'Introductory'){
894
- return `(difficulty_string == "Novice" || difficulty_string == "Introductory" )`
895
- }
896
- return `difficulty_string == "${value}"`
897
- } else if (key === 'tab' && !selectedFilters.includes(key)) {
898
- if(value.toLowerCase() === Tabs.SingleLessons.name.toLowerCase()){
899
- const conditions = individualLessonsTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
900
- return ` (${conditions})`;
901
- } else if(value.toLowerCase() === Tabs.Courses.name.toLowerCase()){
902
- const conditions = coursesLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
903
- return ` (${conditions})`;
904
- } else if(value.toLowerCase() === Tabs.Entertainment.name.toLowerCase()){
905
- const conditions = entertainmentLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
906
- return ` (${conditions})`;
907
- } else if(value.toLowerCase() === Tabs.Tutorials.name.toLowerCase()){
908
- const conditions = tutorialsLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
909
- return ` (${conditions})`;
910
- } else if(value.toLowerCase() === Tabs.Transcriptions.name.toLowerCase()){
911
- const conditions = transcriptionsLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
912
- return ` (${conditions})`;
913
- } else if(value.toLowerCase() === Tabs.PlayAlongs.name.toLowerCase()){
914
- const conditions = playAlongLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
915
- return ` (${conditions})`;
916
- } else if(value.toLowerCase() === Tabs.JamTracks.name.toLowerCase()){
917
- const conditions = jamTrackLessonTypes.map(lessonType => `_type == '${lessonType}'`).join(' || ');
918
- return ` (${conditions})`;
919
- } else if(value.toLowerCase() === Tabs.ExploreAll.name.toLowerCase()){
920
- var allLessons = filterTypes[pageName] || [];
921
- const conditions = allLessons.map(lessonType => `_type == '${lessonType}'`).join(' || ');
922
- if (conditions === "") return '';
923
- return ` (${conditions})`;
924
- }else if(value.toLowerCase() === Tabs.RecentAll.name.toLowerCase()){
925
- var allLessons = recentTypes[pageName] || [];
926
- const conditions = allLessons.map(lessonType => `_type == '${lessonType}'`).join(' || ');
927
- if (conditions === "") return '';
928
- return ` (${conditions})`;
929
- }
930
- return `_type == "${value}"`
931
- } else if (key === 'type' && !selectedFilters.includes(key)) {
932
- const typeKey = value.toLowerCase();
933
- const lessonTypes = lessonTypesMapping[typeKey];
934
- if (lessonTypes) {
935
- const conditions = lessonTypes.map(
936
- (lessonType) => `_type == '${lessonType}'`
937
- ).join(' || ');
938
- return ` (${conditions})`;
939
- }
940
- return `_type == "${value}"`;
941
- } else if (key === 'length_in_seconds') {
942
- if (value.includes('-')) {
943
- const [min, max] = value.split('-').map(Number)
944
- return `(${key} > ${min} && ${key} < ${max})`
945
- } else if (value.includes('+')) {
946
- const min = parseInt(value, 10)
947
- return `(${key} > ${min})`
948
- } else {
949
- return `${key} == ${value}`
950
- }
951
- } else if (key === 'pageName') {
952
- return ` `
953
- } else if (!selectedFilters.includes(key)) {
954
- return ` ${key} == ${/^\d+$/.test(value) ? value : `"$${value}"`}`
953
+ .map(value => {
954
+ const handler = filterHandlers[key]
955
+
956
+ if (handler) {
957
+ return handler(value, pageName)
955
958
  }
959
+
960
+ // Default handler for unknown filters
961
+ return `${key} == ${/^\d+$/.test(value) ? value : `"${value}"`}`
956
962
  })
957
963
  .filter(Boolean)
958
964
  .join(' || ')
@@ -963,10 +969,14 @@ export function filtersToGroq(filters, selectedFilters = [], pageName = '') {
963
969
  .filter(Boolean)
964
970
  .join(' ')
965
971
 
966
- //Return
967
- return `${multipleIdFilters} ${filterClauses}`
972
+ return `${railcontentIdFilters} ${filterClauses}`.trim()
968
973
  }
969
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
+ */
970
980
  function groupFilters(filters) {
971
981
  if (filters.length === 0) return {}
972
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
@@ -2187,7 +2187,7 @@ export async function fetchTabData(
2187
2187
  let entityFieldsString = ''
2188
2188
  let filter = ''
2189
2189
 
2190
- filter = `brand == "${brand}" ${includedFieldsFilter} ${progressFilter}`
2190
+ filter = `brand == "${brand}" && (defined(railcontent_id)) ${includedFieldsFilter} ${progressFilter}`
2191
2191
  const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
2192
2192
  const childrenFields = await getChildFieldsForContentType('tab-data')
2193
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