musora-content-services 1.0.128 → 1.0.130

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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
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.0.130](https://github.com/railroadmedia/musora-content-services/compare/v1.0.129...v1.0.130) (2024-10-09)
6
+
7
+ ### [1.0.129](https://github.com/railroadmedia/musora-content-services/compare/v1.0.127...v1.0.129) (2024-10-09)
8
+
5
9
  ### [1.0.128](https://github.com/railroadmedia/musora-content-services/compare/v1.0.127...v1.0.128) (2024-10-03)
6
10
 
7
11
  ### [1.0.127](https://github.com/railroadmedia/musora-content-services/compare/v1.0.123...v1.0.127) (2024-10-03)
package/README.md CHANGED
@@ -44,6 +44,15 @@ To install the package, use npm:
44
44
  npm install musora-content-services
45
45
  ```
46
46
 
47
+ ## Generating index.js and index.d.ts
48
+
49
+ The `index.js` and `index.d.ts` files provide all exported functions from this package, and are generated automatically.
50
+ Simply run `npm run build-index` to build these files. It works by running the `tools/generate-index.js` file, which simply
51
+ parses the files under the `src/services` directory and builds up the index files with any functions tagged with `export`.
52
+
53
+ If you want to exclude any of your exported functions from the generated index files, be sure to add the function name to
54
+ the `excludeFromGeneratedIndex` array inside the service file.
55
+
47
56
  ## Publishing Package Updates
48
57
 
49
58
  To publish a new version to NPM run,
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.0.128",
3
+ "version": "1.0.130",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -153,6 +153,7 @@ const commonMetadata ={
153
153
  {
154
154
  name: 'Genres',
155
155
  short_name: 'Genres',
156
+ is_group_by: true,
156
157
  value: 'genre',
157
158
  },
158
159
  ],
@@ -1066,6 +1067,7 @@ const contentMetadata = {
1066
1067
  }
1067
1068
  };
1068
1069
 
1070
+ const typeWithSortOrder = ['in-rhythm', 'diy-drum-experiments', 'rhythmic-adventures-of-captain-carson'];
1069
1071
  function processMetadata(brand, type, withFilters = false)
1070
1072
  {
1071
1073
  let brandMetaData = contentMetadata[brand]?.[type];
@@ -1099,4 +1101,5 @@ function processMetadata(brand, type, withFilters = false)
1099
1101
 
1100
1102
  module.exports = {
1101
1103
  processMetadata,
1104
+ typeWithSortOrder
1102
1105
  }
@@ -94,13 +94,26 @@ let contentTypeConfig = {
94
94
  'total_xp',
95
95
  'xp',
96
96
  '"instructors": instructor[]->name',
97
+ '"instructor_signature": instructor[0]->signature.asset->url',
97
98
  '"header_image_url": thumbnail.asset->url',
98
99
  '"logo_image_url": logo_image_url.asset->url',
99
100
  '"award": award.asset->url',
100
- `"award_template": award_template[]{
101
- streak_minimum,
102
- "template" : template.asset->url,
103
- }`,
101
+ 'award_custom_text',
102
+ '"gold_award": gold_award.asset->url',
103
+ '"silver_award": silver_award.asset->url',
104
+ '"bronze_award": bronze_award.asset->url',
105
+ `"lessons": child[]->{
106
+ "id": railcontent_id,
107
+ title,
108
+ "image": thumbnail.asset->url,
109
+ "instructors": instructor[]->name,
110
+ length_in_seconds,
111
+ difficulty_string,
112
+ difficulty,
113
+ "type": _type,
114
+ is_always_unlocked_for_challenge,
115
+ is_bonus_content_for_challenge,
116
+ }`,
104
117
  ]
105
118
  },
106
119
  'course': {
package/src/index.d.ts CHANGED
@@ -13,15 +13,20 @@ import {
13
13
 
14
14
  import {
15
15
  fetchAllCompletedStates,
16
+ fetchChallengeMetadata,
16
17
  fetchCompletedContent,
17
18
  fetchCompletedState,
18
19
  fetchContentInProgress,
19
20
  fetchContentPageUserData,
20
21
  fetchHandler,
21
22
  fetchSongsInProgress,
22
- fetchUserLikes,
23
+ fetchUserAward,
24
+ fetchUserChallengeProgress,
23
25
  fetchUserPermissions,
24
- fetchVimeoData
26
+ postChallengesEnroll,
27
+ postChallengesLeave,
28
+ postChallengesSetStartDate,
29
+ postChallengesUnlock
25
30
  } from './services/railcontent.js';
26
31
 
27
32
  import {
@@ -83,6 +88,7 @@ declare module 'musora-content-services' {
83
88
  fetchByRailContentIds,
84
89
  fetchByReference,
85
90
  fetchCatalogMetadata,
91
+ fetchChallengeMetadata,
86
92
  fetchChallengeOverview,
87
93
  fetchChildren,
88
94
  fetchCoachLessons,
@@ -120,15 +126,19 @@ declare module 'musora-content-services' {
120
126
  fetchSongFilterOptions,
121
127
  fetchSongsInProgress,
122
128
  fetchUpcomingEvents,
123
- fetchUserLikes,
129
+ fetchUserAward,
130
+ fetchUserChallengeProgress,
124
131
  fetchUserPermissions,
125
- fetchVimeoData,
126
132
  fetchWorkouts,
127
133
  getSortOrder,
128
134
  globalConfig,
129
135
  initializeService,
130
136
  isContentLiked,
131
137
  likeContent,
138
+ postChallengesEnroll,
139
+ postChallengesLeave,
140
+ postChallengesSetStartDate,
141
+ postChallengesUnlock,
132
142
  unlikeContent,
133
143
  }
134
144
  }
package/src/index.js CHANGED
@@ -13,15 +13,20 @@ import {
13
13
 
14
14
  import {
15
15
  fetchAllCompletedStates,
16
+ fetchChallengeMetadata,
16
17
  fetchCompletedContent,
17
18
  fetchCompletedState,
18
19
  fetchContentInProgress,
19
20
  fetchContentPageUserData,
20
21
  fetchHandler,
21
22
  fetchSongsInProgress,
22
- fetchUserLikes,
23
+ fetchUserAward,
24
+ fetchUserChallengeProgress,
23
25
  fetchUserPermissions,
24
- fetchVimeoData
26
+ postChallengesEnroll,
27
+ postChallengesLeave,
28
+ postChallengesSetStartDate,
29
+ postChallengesUnlock
25
30
  } from './services/railcontent.js';
26
31
 
27
32
  import {
@@ -82,6 +87,7 @@ export {
82
87
  fetchByRailContentIds,
83
88
  fetchByReference,
84
89
  fetchCatalogMetadata,
90
+ fetchChallengeMetadata,
85
91
  fetchChallengeOverview,
86
92
  fetchChildren,
87
93
  fetchCoachLessons,
@@ -119,14 +125,18 @@ export {
119
125
  fetchSongFilterOptions,
120
126
  fetchSongsInProgress,
121
127
  fetchUpcomingEvents,
122
- fetchUserLikes,
128
+ fetchUserAward,
129
+ fetchUserChallengeProgress,
123
130
  fetchUserPermissions,
124
- fetchVimeoData,
125
131
  fetchWorkouts,
126
132
  getSortOrder,
127
133
  globalConfig,
128
134
  initializeService,
129
135
  isContentLiked,
130
136
  likeContent,
137
+ postChallengesEnroll,
138
+ postChallengesLeave,
139
+ postChallengesSetStartDate,
140
+ postChallengesUnlock,
131
141
  unlikeContent,
132
142
  };
@@ -7,7 +7,14 @@ let globalConfig = {
7
7
  railcontentConfig: {},
8
8
  localStorage: null
9
9
  };
10
-
10
+
11
+ /**
12
+ * Exported functions that are excluded from index generation.
13
+ *
14
+ * @type {string[]}
15
+ */
16
+ const excludeFromGeneratedIndex = [];
17
+
11
18
  /**
12
19
  * Initializes the service with the given configuration.
13
20
  * This function must be called before using any other functions in this library.
@@ -1,6 +1,13 @@
1
1
  import {fetchUserLikes, postContentLiked, postContentUnliked} from "./railcontent";
2
2
  import {DataContext, ContentVersionKey} from "./dataContext";
3
3
 
4
+ /**
5
+ * Exported functions that are excluded from index generation.
6
+ *
7
+ * @type {string[]}
8
+ */
9
+ const excludeFromGeneratedIndex = [];
10
+
4
11
  export let dataContext = new DataContext(ContentVersionKey, fetchUserLikes);
5
12
 
6
13
  export async function isContentLiked(contentId) {
@@ -1,5 +1,12 @@
1
1
  import {globalConfig} from "./config";
2
2
 
3
+ /**
4
+ * Exported functions that are excluded from index generation.
5
+ *
6
+ * @type {string[]}
7
+ */
8
+ const excludeFromGeneratedIndex = [];
9
+
3
10
  //These constants need to match MWP UserDataVersionKeyEnum enum
4
11
  export const ContentVersionKey = 0;
5
12
 
@@ -4,6 +4,13 @@
4
4
 
5
5
  const {globalConfig} = require('./config');
6
6
 
7
+ /**
8
+ * Exported functions that are excluded from index generation.
9
+ *
10
+ * @type {string[]}
11
+ */
12
+ const excludeFromGeneratedIndex = ['fetchUserLikes', 'postContentLiked', 'postContentUnliked'];
13
+
7
14
 
8
15
  /**
9
16
  * Fetches the completion status of a specific lesson for the current user.
@@ -38,40 +45,6 @@ export async function fetchCompletedState(content_id) {
38
45
  }
39
46
  }
40
47
 
41
- /**
42
- * Fetches the vimeo meta-data
43
- *
44
- * @param {string} vimeo_id - The vimeo id, found in the <document>.video.external_id field for lessons
45
- * @returns {Promise<Object|null>} - Returns the
46
- * @example
47
- * fetchVimeoData('642900215')
48
- * .then(vimeoData => console.log(vimeoData))
49
- * .catch(error => console.error(error));
50
- */
51
- export async function fetchVimeoData(vimeo_id) {
52
- const url = `/content/vimeo-data/${vimeo_id}`;
53
-
54
- const headers = {
55
- 'Content-Type': 'application/json',
56
- 'X-CSRF-TOKEN': globalConfig.railcontentConfig.token
57
- };
58
-
59
- try {
60
- const response = await fetchAbsolute(url, {headers});
61
- const result = await response.json();
62
-
63
- if (result) {
64
- return result; // Return the correct object
65
- } else {
66
- console.log('Invalid result structure', result);
67
- return null; // Handle unexpected structure
68
- }
69
- } catch (error) {
70
- console.error('Fetch error:', error);
71
- return null;
72
- }
73
- }
74
-
75
48
 
76
49
  /**
77
50
  * Fetches the completion status for multiple songs for the current user.
@@ -300,6 +273,41 @@ export async function postContentUnliked(contentId) {
300
273
  return await fetchHandler(url, "post");
301
274
  }
302
275
 
276
+ export async function fetchChallengeMetadata(contentId) {
277
+ let url = `/challenges/${contentId}`;
278
+ return await fetchHandler(url, 'get');
279
+ }
280
+
281
+ export async function fetchUserChallengeProgress(contentId) {
282
+ let url = `/challenges/user_data/${contentId}`;
283
+ return await fetchHandler(url, 'get');
284
+ }
285
+
286
+ export async function fetchUserAward(contentId) {
287
+ let url = `/challenges/download_award/${contentId}`;
288
+ return await fetchHandler(url, 'get');
289
+ }
290
+
291
+ export async function postChallengesSetStartDate(contentId, startDate) {
292
+ let url = `/challenges/set_start_date/${contentId}?start_date=${startDate}`;
293
+ return await fetchHandler(url, 'post');
294
+ }
295
+
296
+ export async function postChallengesUnlock(contentId) {
297
+ let url = `/challenges/unlock/${contentId}`;
298
+ return await fetchHandler(url, 'post');
299
+ }
300
+
301
+ export async function postChallengesEnroll(contentId) {
302
+ let url = `/challenges/enroll/${contentId}`;
303
+ return await fetchHandler(url, 'post');
304
+ }
305
+
306
+ export async function postChallengesLeave(contentId) {
307
+ let url = `/challenges/leave/${contentId}`;
308
+ return await fetchHandler(url, 'post');
309
+ }
310
+
303
311
  function fetchAbsolute(url, params) {
304
312
  if (globalConfig.railcontentConfig.baseUrl) {
305
313
  if (url.startsWith('/')) {
@@ -18,6 +18,7 @@ import {
18
18
 
19
19
  import {
20
20
  processMetadata,
21
+ typeWithSortOrder
21
22
  } from "../contentMetaData";
22
23
 
23
24
  import {globalConfig} from "./config";
@@ -25,6 +26,12 @@ import {globalConfig} from "./config";
25
26
  import { fetchUserPermissions, fetchAllCompletedStates, fetchCurrentSongComplete } from './railcontent.js';
26
27
  import {arrayToStringRepresentation, FilterBuilder} from "../filterBuilder";
27
28
 
29
+ /**
30
+ * Exported functions that are excluded from index generation.
31
+ *
32
+ * @type {string[]}
33
+ */
34
+ const excludeFromGeneratedIndex = [];
28
35
  /**
29
36
  * Fetch a song by its document ID from Sanity.
30
37
  *
@@ -960,19 +967,17 @@ export async function fetchLessonContent(railContentId) {
960
967
  * @returns {Promise<Array<Object>|null>} - The fetched related lessons data or null if not found.
961
968
  */
962
969
  export async function fetchRelatedLessons(railContentId, brand) {
963
- // let sort = 'published_on'
964
- // if (type == 'rhythmic-adventures-of-captain-carson' ||
965
- // type == 'diy-drum-experiments' ||
966
- // type == 'in-rhythm') {
967
- // sort = 'sort';
968
- // }
969
- //TODO: Implement $this->contentService->getFiltered
970
- const query = `*[railcontent_id == ${railContentId} && brand == "${brand}" && references(*[_type=='permission']._id)]{
970
+ const query = `*[railcontent_id == ${railContentId} && brand == "${brand}" && references(*[_type=='permission']._id)]{
971
+ _type, parent_type, railcontent_id,
971
972
  "related_lessons" : array::unique([
972
- ...(*[_type=="song" && brand == "${brand}" && references(^.artist->_id)]{_id, "id":railcontent_id, published_on, title, "thumbnail_url":thumbnail.asset->url, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,}[0...11]),
973
- ...(*[_type=="song" && brand == "${brand}" && references(^.genre[]->_id)]{_id, "id":railcontent_id, published_on, title, "thumbnail_url":thumbnail.asset->url, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,}[0...11])
974
- ])|order(published_on, railcontent_id)[0...11]}`;
975
- return fetchSanity(query, false);
973
+ ...(*[references(^._id)][0].child[]->{_id, "id":railcontent_id, published_on, title, "thumbnail_url":thumbnail.asset->url, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}),
974
+ ...(*[_type=="song" && _type==^._type && brand == "${brand}" && references(^.artist->_id) && railcontent_id !=${railContentId}]{_id, "id":railcontent_id, published_on, title, "thumbnail_url":thumbnail.asset->url, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}|order(published_on desc, title asc)[0...11]),
975
+ ...(*[_type=="song" && _type==^._type && brand == "${brand}" && references(^.genre[]->_id) && railcontent_id !=${railContentId}]{_id, "id":railcontent_id, published_on, title, "thumbnail_url":thumbnail.asset->url, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}|order(published_on desc, title asc)[0...11]),
976
+ ...(*[_type==^._type && _type in ${JSON.stringify(typeWithSortOrder)} && brand == "${brand}" && railcontent_id !=${railContentId}]{_id, "id":railcontent_id, published_on, title, "thumbnail_url":thumbnail.asset->url, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type, sort}|order(sort asc, title asc)[0...11]),
977
+ ...(*[_type==^._type && !(_type in ${JSON.stringify(typeWithSortOrder)}) && brand == "${brand}" && railcontent_id !=${railContentId}]{_id, "id":railcontent_id, published_on, title, "thumbnail_url":thumbnail.asset->url, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}|order(published_on desc, title asc)[0...11]),
978
+ ])[0...11]}`;
979
+
980
+ return fetchSanity(query, false);
976
981
  }
977
982
 
978
983
  /**
@@ -1106,18 +1111,6 @@ export async function fetchChallengeOverview(id) {
1106
1111
  // WIP
1107
1112
  const query = `*[railcontent_id == ${id}]{
1108
1113
  ${getFieldsForContentType("challenge")}
1109
- "lessons": child[]->{
1110
- "id": railcontent_id,
1111
- title,
1112
- "image": thumbnail.asset->url,
1113
- "instructors": instructor[]->name,
1114
- length_in_seconds,
1115
- difficulty_string,
1116
- difficulty,
1117
- "type": _type,
1118
- is_always_unlocked,
1119
- is_bonus_content,
1120
- }
1121
1114
  } [0...1]`;
1122
1115
  return fetchSanity(query, false);
1123
1116
  }
@@ -250,6 +250,44 @@ describe('Sanity Queries', function () {
250
250
  expect(isMatch).toBeTruthy();
251
251
  });
252
252
 
253
+ test('fetchRelatedLessons-quick-tips', async () => {
254
+ const id = 406213;
255
+ const response = await fetchRelatedLessons(id, 'singeo');
256
+ log(response);
257
+ const relatedLessons = response.related_lessons;
258
+ expect(Array.isArray(relatedLessons)).toBe(true);
259
+ relatedLessons.forEach(lesson => {
260
+ expect(lesson._type).toBe('quick-tips');
261
+ });
262
+ });
263
+
264
+ test('fetchRelatedLessons-in-rhythm', async () => {
265
+ const id = 236677;
266
+ const response = await fetchRelatedLessons(id, 'drumeo');
267
+ log(response);
268
+ const relatedLessons = response.related_lessons;
269
+ let episode = 0;
270
+ expect(Array.isArray(relatedLessons)).toBe(true);
271
+ relatedLessons.forEach(lesson => {
272
+ expect(lesson._type).toBe('in-rhythm');
273
+ expect(lesson.sort).toBeGreaterThan(episode);
274
+ episode = lesson.sort;
275
+ });
276
+ });
277
+
278
+ test('fetchRelatedLessons-child', async () => {
279
+ const id = 362278;
280
+ const course = await fetchByRailContentId(362277, 'course');
281
+ const lessonIds = course.lessons.map((doc) => doc.id);
282
+ const response = await fetchRelatedLessons(id, 'drumeo');
283
+ log(response.related_lessons);
284
+ const relatedLessons = response.related_lessons;
285
+ expect(Array.isArray(relatedLessons)).toBe(true);
286
+ expect(relatedLessons.some(
287
+ lesson => lessonIds.includes(lesson.id)
288
+ )).toBe(true);
289
+ },10000);
290
+
253
291
  test('fetchChildren', async () => {
254
292
  // complement test to fetchParentByRailContentId
255
293
  const id = 191338; ////https://web-staging-one.musora.com/admin/studio/publishing/structure/play-along;play-along_191338
@@ -463,9 +501,9 @@ describe('Sanity Queries', function () {
463
501
  const response = await fetchChallengeOverview(id);
464
502
  log(response);
465
503
  expect(response.award).toBeDefined();
466
- expect(response.award_template).toBeDefined();
467
- expect(response.lessons[0].is_always_unlocked).toBeDefined();
468
- expect(response.lessons[0].is_bonus_content).toBeDefined();
504
+ expect(response.award_custom_text).toBeDefined();
505
+ expect(response.lessons[0].is_always_unlocked_for_challenge).toBeDefined();
506
+ expect(response.lessons[0].is_bonus_content_for_challenge).toBeDefined();
469
507
  });
470
508
 
471
509
  test('fetchShowsData-OddTimes', async () => {
@@ -11,7 +11,7 @@ const fileExports = {};
11
11
  /**
12
12
  * Helper function to extract function names from ES module and CommonJS exports
13
13
  *
14
- * @param filePath
14
+ * @param {string} filePath
15
15
  * @returns {string[]}
16
16
  */
17
17
  function extractExportedFunctions(filePath) {
@@ -30,9 +30,29 @@ function extractExportedFunctions(filePath) {
30
30
  matches = matches.concat(exportsList);
31
31
  }
32
32
 
33
+ const excludedFunctions = getExclusionList(fileContent);
34
+ matches = matches.filter(fn => !excludedFunctions.includes(fn));
35
+
33
36
  return matches.sort();
34
37
  }
35
38
 
39
+ /**
40
+ * Helper function to find the list of exclusions from the file's exports
41
+ *
42
+ * @param {string} fileContent
43
+ * @returns {string[]}
44
+ */
45
+ function getExclusionList(fileContent) {
46
+ const excludeRegex = /const\s+excludeFromGeneratedIndex\s*=\s*\[(.*?)\];/;
47
+ const excludeMatch = fileContent.match(excludeRegex);
48
+ let excludedFunctions = [];
49
+ if (excludeMatch) {
50
+ excludedFunctions = excludeMatch[1]
51
+ .split(',')
52
+ .map(name => name.trim().replace(/['"`]/g, ''));
53
+ }
54
+ return excludedFunctions;
55
+ }
36
56
 
37
57
  // get all files in the services directory
38
58
  const servicesDir = path.join(__dirname, '../src/services');