musora-content-services 1.3.2 → 1.3.5

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 (71) hide show
  1. package/.github/workflows/node.js.yml +0 -0
  2. package/.prettierignore +0 -0
  3. package/.prettierrc +0 -0
  4. package/CHANGELOG.md +6 -0
  5. package/README.md +0 -0
  6. package/babel.config.cjs +0 -0
  7. package/docs/config.js.html +0 -0
  8. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  9. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  10. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  11. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  12. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  13. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  14. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  15. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  16. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  17. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  18. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  19. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  20. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  21. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  22. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  23. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  24. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  25. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  26. package/docs/index.html +0 -0
  27. package/docs/module-Config.html +0 -0
  28. package/docs/module-Railcontent-Services.html +0 -0
  29. package/docs/module-Sanity-Services.html +0 -0
  30. package/docs/railcontent.js.html +0 -0
  31. package/docs/sanity.js.html +0 -0
  32. package/docs/scripts/collapse.js +0 -0
  33. package/docs/scripts/commonNav.js +0 -0
  34. package/docs/scripts/linenumber.js +0 -0
  35. package/docs/scripts/nav.js +0 -0
  36. package/docs/scripts/polyfill.js +0 -0
  37. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  38. package/docs/scripts/prettify/lang-css.js +0 -0
  39. package/docs/scripts/prettify/prettify.js +0 -0
  40. package/docs/scripts/search.js +0 -0
  41. package/docs/styles/jsdoc.css +0 -0
  42. package/docs/styles/prettify.css +0 -0
  43. package/jest.config.js +0 -0
  44. package/jsdoc.json +0 -0
  45. package/link_mcs.sh +0 -0
  46. package/package.json +1 -1
  47. package/src/contentMetaData.js +90 -0
  48. package/src/contentTypeConfig.js +27 -0
  49. package/src/filterBuilder.js +0 -0
  50. package/src/index.d.ts +0 -0
  51. package/src/index.js +0 -0
  52. package/src/services/config.js +0 -0
  53. package/src/services/contentLikes.js +0 -0
  54. package/src/services/contentProgress.js +0 -0
  55. package/src/services/dataContext.js +0 -0
  56. package/src/services/lastUpdated.js +0 -0
  57. package/src/services/railcontent.js +1 -1
  58. package/src/services/sanity.js +80 -10
  59. package/src/services/userPermissions.js +0 -0
  60. package/test/contentLikes.test.js +0 -0
  61. package/test/contentProgress.test.js +0 -0
  62. package/test/initializeTests.js +0 -0
  63. package/test/lastUpdated.test.js +0 -0
  64. package/test/live/contentProgressLive.test.js +0 -0
  65. package/test/live/railcontentLive.test.js +0 -0
  66. package/test/localStorageMock.js +0 -0
  67. package/test/log.js +0 -0
  68. package/test/sanityQueryService.test.js +13 -0
  69. package/test/userPermissions.test.js +0 -0
  70. package/tools/generate-index.cjs +0 -0
  71. package/.yarnrc.yml +0 -1
File without changes
package/.prettierignore CHANGED
File without changes
package/.prettierrc CHANGED
File without changes
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
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
+ ### [1.3.5](https://github.com/railroadmedia/musora-content-services/compare/v1.3.4...v1.3.5) (2025-02-14)
6
+
7
+ ### [1.3.4](https://github.com/railroadmedia/musora-content-services/compare/v1.3.2...v1.3.4) (2025-02-14)
8
+
9
+ ### [1.3.3](https://github.com/railroadmedia/musora-content-services/compare/v1.3.2...v1.3.3) (2025-01-31)
10
+
5
11
  ### [1.3.2](https://github.com/railroadmedia/musora-content-services/compare/v1.3.1...v1.3.2) (2025-01-30)
6
12
 
7
13
  ### [1.3.1](https://github.com/railroadmedia/musora-content-services/compare/v1.3.0...v1.3.1) (2025-01-29)
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
package/docs/index.html 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
package/jest.config.js CHANGED
File without changes
package/jsdoc.json CHANGED
File without changes
package/link_mcs.sh CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "1.3.2",
3
+ "version": "1.3.5",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -266,6 +266,36 @@ const contentMetadata = {
266
266
  song: {
267
267
  sortBy: '-published_on',
268
268
  },
269
+ 'song-tutorial': {
270
+ name: 'Song Tutorials',
271
+ thumbnailUrl: 'https://dpwjbsxqtam5n.cloudfront.net/shows/pianote/songs.jpg',
272
+ allowableFilters: ['difficulty', 'genre'],
273
+ sortBy: '-published_on',
274
+ shortname: 'song tutorials',
275
+ icon: 'play-progress',
276
+ description: '',
277
+ amountOfFutureLessonsToShow: 3,
278
+ showFutureLessonAtTopOrBottom: 'bottom',
279
+ tabs: [
280
+ {
281
+ name: 'Lessons',
282
+ short_name: 'Lessons',
283
+ value: '',
284
+ },
285
+ {
286
+ name: 'Artists',
287
+ short_name: 'ARTISTS',
288
+ is_group_by: true,
289
+ value: 'artist',
290
+ },
291
+ {
292
+ name: 'Genres',
293
+ short_name: 'Genres',
294
+ is_group_by: true,
295
+ value: 'genre',
296
+ },
297
+ ],
298
+ },
269
299
  'student-review': null,
270
300
  'student-focus': {
271
301
  name: 'Student Focus',
@@ -1013,6 +1043,36 @@ const contentMetadata = {
1013
1043
  song: {
1014
1044
  allowableFilters: ['difficulty', 'genre', 'lifestyle', 'instrumentless'],
1015
1045
  },
1046
+ 'song-tutorial': {
1047
+ name: 'Song Tutorials',
1048
+ thumbnailUrl: 'https://dpwjbsxqtam5n.cloudfront.net/shows/pianote/songs.jpg',
1049
+ allowableFilters: ['difficulty', 'genre'],
1050
+ sortBy: '-published_on',
1051
+ shortname: 'song tutorials',
1052
+ icon: 'play-progress',
1053
+ description: '',
1054
+ amountOfFutureLessonsToShow: 3,
1055
+ showFutureLessonAtTopOrBottom: 'bottom',
1056
+ tabs: [
1057
+ {
1058
+ name: 'Lessons',
1059
+ short_name: 'Lessons',
1060
+ value: '',
1061
+ },
1062
+ {
1063
+ name: 'Artists',
1064
+ short_name: 'ARTISTS',
1065
+ is_group_by: true,
1066
+ value: 'artist',
1067
+ },
1068
+ {
1069
+ name: 'Genres',
1070
+ short_name: 'Genres',
1071
+ is_group_by: true,
1072
+ value: 'genre',
1073
+ },
1074
+ ],
1075
+ },
1016
1076
  'play-along': {
1017
1077
  name: 'Play Alongs',
1018
1078
  icon: 'icon-play-alongs',
@@ -1138,6 +1198,36 @@ const contentMetadata = {
1138
1198
  description:
1139
1199
  'Want feedback on your singing? Submit a video for student review. We will watch your submission and then provide helpful encouragement and feedback. This is a great way to build accountability and benefit from the expertise of our teachers.',
1140
1200
  },
1201
+ 'song-tutorial': {
1202
+ name: 'Song Tutorials',
1203
+ thumbnailUrl: 'https://dpwjbsxqtam5n.cloudfront.net/shows/pianote/songs.jpg',
1204
+ allowableFilters: ['difficulty', 'genre'],
1205
+ sortBy: '-published_on',
1206
+ shortname: 'song tutorials',
1207
+ icon: 'play-progress',
1208
+ description: '',
1209
+ amountOfFutureLessonsToShow: 3,
1210
+ showFutureLessonAtTopOrBottom: 'bottom',
1211
+ tabs: [
1212
+ {
1213
+ name: 'Lessons',
1214
+ short_name: 'Lessons',
1215
+ value: '',
1216
+ },
1217
+ {
1218
+ name: 'Artists',
1219
+ short_name: 'ARTISTS',
1220
+ is_group_by: true,
1221
+ value: 'artist',
1222
+ },
1223
+ {
1224
+ name: 'Genres',
1225
+ short_name: 'Genres',
1226
+ is_group_by: true,
1227
+ value: 'genre',
1228
+ },
1229
+ ],
1230
+ },
1141
1231
  routine: {
1142
1232
  name: 'Routines',
1143
1233
  icon: 'icon-shows',
@@ -26,6 +26,13 @@ export const DEFAULT_FIELDS = [
26
26
  'xp',
27
27
  'child_count',
28
28
  ]
29
+ export const DEFAULT_CHILD_FIELDS = [
30
+ `"id": railcontent_id`,
31
+ `title`,
32
+ `"image": thumbnail.asset->url`,
33
+ `"instructors": instructor[]->name`,
34
+ `length_in_seconds`,
35
+ ]
29
36
 
30
37
  export const descriptionField = 'description[0].children[0].text'
31
38
  // this pulls both any defined resources for the document as well as any resources in the parent document
@@ -111,6 +118,14 @@ export const coachLessonsTypes = [
111
118
  'workout',
112
119
  ]
113
120
 
121
+ export const childContentTypeConfig = {
122
+ 'song-tutorial': [
123
+ `"genre": genre[]->name`,
124
+ `difficulty_string`,
125
+ `"type": _type`,
126
+ ]
127
+ }
128
+
114
129
  export let contentTypeConfig = {
115
130
  song: {
116
131
  fields: ['album', 'soundslice', 'instrumentless', `"resources": ${resourcesField}`],
@@ -130,7 +145,9 @@ export let contentTypeConfig = {
130
145
  "image": thumbnail.asset->url,
131
146
  "instructors": instructor[]->name,
132
147
  length_in_seconds,
148
+ web_url_path,
133
149
  }`,
150
+ '"instructors": instructor[]->name',
134
151
  ],
135
152
  relationships: {
136
153
  artist: {
@@ -560,6 +577,13 @@ export function getFieldsForContentType(contentType, asQueryString = true) {
560
577
  return asQueryString ? fields.toString() + ',' : fields
561
578
  }
562
579
 
580
+ export function getChildFieldsForContentType(contentType, asQueryString = true) {
581
+ const fields = contentType
582
+ ? DEFAULT_CHILD_FIELDS.concat(childContentTypeConfig?.[contentType] ?? [])
583
+ : DEFAULT_CHILD_FIELDS
584
+ return asQueryString ? fields.toString() + ',' : fields
585
+ }
586
+
563
587
  /**
564
588
  * Takes the included fields array and returns a string that can be used in a groq query.
565
589
  * @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',
@@ -621,6 +645,9 @@ export function filtersToGroq(filters, selectedFilters = []) {
621
645
  return `instrumentless == ${value}`
622
646
  }
623
647
  } else if (key === 'difficulty' && !selectedFilters.includes(key)) {
648
+ if(value === 'Introductory'){
649
+ return `(difficulty_string == "Novice" || difficulty_string == "Introductory" )`
650
+ }
624
651
  return `difficulty_string == "${value}"`
625
652
  } else if (key === 'type' && !selectedFilters.includes(key)) {
626
653
  return `_type == "${value}"`
File without changes
package/src/index.d.ts CHANGED
File without changes
package/src/index.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -493,7 +493,7 @@ export async function fetchChallengeUserActiveChallenges(brand = null) {
493
493
  */
494
494
  export async function fetchCarouselCardData(brand = null) {
495
495
  const brandParam = brand ? `?brand=${brand}` : ''
496
- let url = `/api/v1/content/carousel${brandParam}`
496
+ let url = `/api/v2/content/carousel${brandParam}`
497
497
  return await fetchHandler(url, 'get')
498
498
  }
499
499
 
@@ -15,6 +15,7 @@ import {
15
15
  showsTypes,
16
16
  getNewReleasesTypes,
17
17
  coachLessonsTypes,
18
+ getChildFieldsForContentType,
18
19
  } from '../contentTypeConfig.js'
19
20
 
20
21
  import { processMetadata, typeWithSortOrder } from '../contentMetaData.js'
@@ -64,6 +65,68 @@ export async function fetchSongById(documentId) {
64
65
  return fetchSanity(query, false)
65
66
  }
66
67
 
68
+ /**
69
+ * fetches from Sanity all content marked for removal next quarter
70
+ *
71
+ * @string brand
72
+ * @returns {Promise<Object|null>}
73
+ */
74
+ export async function fetchQuarterRemoved(brand) {
75
+ const nextQuarter = getNextAndPreviousQuarterDates()['next'];
76
+ const filterString = `brand == '${brand}' && quarter_removed == '${nextQuarter}'`
77
+ const query = await buildQuery(filterString, {pullFutureContent: false, availableContentStatuses: ["published"]}, getFieldsForContentType(), {SortOrder: "published_on desc, id desc", end: 20});
78
+ return fetchSanity(query, false);
79
+ }
80
+
81
+ /**
82
+ * fetches from Sanity all content marked for publish next quarter
83
+ *
84
+ * @string brand
85
+ * @returns {Promise<Object|null>}
86
+ */
87
+ export async function fetchQuarterPublished(brand) {
88
+ const nextQuarter = getNextAndPreviousQuarterDates()['next'];
89
+ const filterString = `brand == '${brand}' && quarter_published == '${nextQuarter}'`;
90
+ const query = await buildQuery(filterString, {pullFutureContent: true, availableContentStatuses: ["draft"]}, getFieldsForContentType(), {SortOrder: "published_on desc, id desc", end: 20});
91
+
92
+ return fetchSanity(query, false);
93
+ }
94
+
95
+ /**
96
+ * returns array of next and previous quarter dates as strings
97
+ *
98
+ * @returns {*[]}
99
+ */
100
+ export function getNextAndPreviousQuarterDates() {
101
+ const january = 1;
102
+ const april = 4;
103
+ const july = 7;
104
+ const october = 10;
105
+ const month = new Date().getMonth();
106
+ let year = new Date().getFullYear();
107
+ let nextQuarter = '';
108
+ let prevQuarter = '';
109
+ if (month < april) {
110
+ nextQuarter = `${year}-0${april}-01`;
111
+ prevQuarter = `${year}-0${january}-01`;
112
+ } else if (month < july) {
113
+ nextQuarter = `${year}-0${july}-01`;
114
+ prevQuarter = `${year}-0${april}-01`;
115
+ } else if (month < october) {
116
+ nextQuarter = `${year}-${october}-01`;
117
+ prevQuarter = `${year}-0${july}-01`;
118
+ } else {
119
+ prevQuarter = `${year}-${october}-01`;
120
+ year++;
121
+ nextQuarter = `${year}-0${january}-01`;
122
+ }
123
+
124
+ let result = [];
125
+ result['next'] = nextQuarter;
126
+ result['previous'] = prevQuarter;
127
+ return result;
128
+ }
129
+
67
130
  /**
68
131
  * Fetch all artists with lessons available for a specific brand.
69
132
  *
@@ -106,8 +169,20 @@ export async function fetchSongArtistCount(brand) {
106
169
  return fetchSanity(query, true, { processNeedAccess: false })
107
170
  }
108
171
 
109
- export async function fetchPlayAlongsCount(brand) {
110
- const query = `count(*[brand == '${brand}' && _type == "play-along"]) `
172
+ export async function fetchPlayAlongsCount(brand, {
173
+ searchTerm,
174
+ includedFields,
175
+ progressIds,
176
+ progress,
177
+ }) {
178
+ const searchFilter = searchTerm ? `&& (artist->name match "${searchTerm}*" || instructor[]->name match "${searchTerm}*" || title match "${searchTerm}*" || name match "${searchTerm}*")` :'';
179
+
180
+ // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
181
+ const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
182
+
183
+ // limits the results to supplied progressIds for started & completed filters
184
+ const progressFilter = await getProgressFilter(progress, progressIds)
185
+ const query = `count(*[brand == '${brand}' && _type == "play-along" ${searchFilter} ${includedFieldsFilter} ${progressFilter} ]) `
111
186
  return fetchSanity(query, true, { processNeedAccess: false })
112
187
  }
113
188
 
@@ -329,16 +404,11 @@ export async function fetchScheduledReleases(brand, { page = 1, limit = 10 }) {
329
404
  */
330
405
  export async function fetchByRailContentId(id, contentType) {
331
406
  const fields = getFieldsForContentType(contentType)
407
+ const childFields = getChildFieldsForContentType(contentType)
332
408
  const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
333
409
  const entityFieldsString = ` ${fields}
334
410
  'child_count': coalesce(count(child[${childrenFilter}]->), 0) ,
335
- "lessons": child[${childrenFilter}]->{
336
- "id": railcontent_id,
337
- title,
338
- "image": thumbnail.asset->url,
339
- "instructors": instructor[]->name,
340
- length_in_seconds,
341
- },
411
+ "lessons": child[${childrenFilter}]->{${childFields}},
342
412
  'length_in_seconds': coalesce(
343
413
  math::sum(
344
414
  select(
@@ -1889,7 +1959,7 @@ function getFilterOptions(option, commonFilter, contentType, brand) {
1889
1959
  filterGroq = `
1890
1960
  "difficulty": [
1891
1961
  {"type": "All", "count": count(*[${commonFilter} && difficulty_string == "All"])},
1892
- {"type": "Introductory", "count": count(*[${commonFilter} && difficulty_string == "Introductory"])},
1962
+ {"type": "Introductory", "count": count(*[${commonFilter} && (difficulty_string == "Novice" || difficulty_string == "Introductory")])},
1893
1963
  {"type": "Beginner", "count": count(*[${commonFilter} && difficulty_string == "Beginner"])},
1894
1964
  {"type": "Intermediate", "count": count(*[${commonFilter} && difficulty_string == "Intermediate" ])},
1895
1965
  {"type": "Advanced", "count": count(*[${commonFilter} && difficulty_string == "Advanced" ])},
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
@@ -14,6 +14,8 @@ import { fetchOwnedChallenges } from '../src'
14
14
  const {
15
15
  fetchSongById,
16
16
  fetchArtists,
17
+ fetchQuarterPublished,
18
+ fetchQuarterRemoved,
17
19
  fetchSongArtistCount,
18
20
  fetchRelatedSongs,
19
21
  fetchNewReleases,
@@ -56,6 +58,17 @@ describe('Sanity Queries', function () {
56
58
  expect(response.id).toBe(id)
57
59
  })
58
60
 
61
+ test('fetchQuarterPublished', async () => {
62
+ const brand = 'guitareo'
63
+ const response = await fetchQuarterPublished(brand)
64
+ });
65
+
66
+ test('fetchQuarterRemoved', async () => {
67
+ const brand = 'guitareo'
68
+ const response = await fetchQuarterRemoved(brand)
69
+ });
70
+
71
+
59
72
  test('fetchArtists', async () => {
60
73
  const response = await fetchArtists('drumeo')
61
74
  const artistNames = response.map((x) => x.name)
File without changes
File without changes
package/.yarnrc.yml DELETED
@@ -1 +0,0 @@
1
- nodeLinker: node-modules