musora-content-services 1.2.0 → 1.2.2

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.2.2](https://github.com/railroadmedia/musora-content-services/compare/v1.2.1...v1.2.2) (2025-01-16)
6
+
7
+ ### [1.2.1](https://github.com/railroadmedia/musora-content-services/compare/v1.2.0...v1.2.1) (2025-01-16)
8
+
5
9
  ## [1.2.0](https://github.com/railroadmedia/musora-content-services/compare/v1.0.233...v1.2.0) (2025-01-14)
6
10
 
7
11
 
package/README.md CHANGED
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
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
- "build-index": "node tools/generate-index.js",
8
+ "build-index": "node tools/generate-index.cjs",
9
9
  "release": "standard-version",
10
10
  "doc": "jsdoc -c jsdoc.json --verbose",
11
11
  "test": "jest --setupFiles dotenv/config"
@@ -22,7 +22,8 @@ export class FilterBuilder {
22
22
  bypassStatuses = false,
23
23
  bypassPublishedDateRestriction = false,
24
24
  isSingle = false,
25
- allowsPullSongsContent = true
25
+ allowsPullSongsContent = true,
26
+ isChildrenFilter = false
26
27
  } = {}) {
27
28
  this.availableContentStatuses = availableContentStatuses;
28
29
  this.bypassPermissions = bypassPermissions;
@@ -36,6 +37,7 @@ export class FilterBuilder {
36
37
  this.filter = filter;
37
38
  // this.debug = process.env.DEBUG === 'true' || false;
38
39
  this.debug = false;
40
+ this.prefix = isChildrenFilter ? '@->' : '';
39
41
  }
40
42
 
41
43
 
@@ -81,10 +83,10 @@ export class FilterBuilder {
81
83
  const now = new Date().toISOString();
82
84
  let statuses = [...this.availableContentStatuses];
83
85
  statuses.splice(statuses.indexOf(this.STATUS_SCHEDULED), 1);
84
- this._andWhere(`(status in ${arrayToStringRepresentation(statuses)} || (status == '${this.STATUS_SCHEDULED}' && defined(published_on) && published_on >= '${now}'))`)
86
+ this._andWhere(`(${this.prefix}status in ${arrayToStringRepresentation(statuses)} || (${this.prefix}status == '${this.STATUS_SCHEDULED}' && defined(${this.prefix}published_on) && ${this.prefix}published_on >= '${now}'))`)
85
87
 
86
88
  } else {
87
- this._andWhere(`status in ${arrayToStringRepresentation(this.availableContentStatuses)}`);
89
+ this._andWhere(`${this.prefix}status in ${arrayToStringRepresentation(this.availableContentStatuses)}`);
88
90
  }
89
91
  return this;
90
92
  }
@@ -117,9 +119,9 @@ export class FilterBuilder {
117
119
  now = roundedDate.toISOString();
118
120
 
119
121
  if (this.getFutureContentOnly) {
120
- this._andWhere(`published_on >= '${now}'`);
122
+ this._andWhere(`${this.prefix}published_on >= '${now}'`);
121
123
  } else if (!this.pullFutureContent) {
122
- this._andWhere(`published_on <= '${now}'`);
124
+ this._andWhere(`${this.prefix}published_on <= '${now}'`);
123
125
  } else {
124
126
  // const date = new Date();
125
127
  // const theFuture = new Date(date.setMonth(date.getMonth() + 18));
package/src/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  /*** This file was generated automatically. To recreate, please run `npm run build-index`. ***/
2
2
 
3
3
  import {
4
- globalConfig,
5
4
  initializeService
6
5
  } from './services/config.js';
7
6
 
@@ -79,6 +78,7 @@ import {
79
78
  postContentUnliked,
80
79
  postRecordWatchSession,
81
80
  reportPlaylist,
81
+ setStudentViewForUser,
82
82
  unpinPlaylist,
83
83
  updatePlaylist,
84
84
  updatePlaylistItem
@@ -243,7 +243,6 @@ declare module 'musora-content-services' {
243
243
  getProgressStateByIds,
244
244
  getResumeTimeSeconds,
245
245
  getSortOrder,
246
- globalConfig,
247
246
  initializeService,
248
247
  isContentLiked,
249
248
  jumpToContinueContent,
@@ -268,6 +267,7 @@ declare module 'musora-content-services' {
268
267
  recordWatchSession,
269
268
  reportPlaylist,
270
269
  reset,
270
+ setStudentViewForUser,
271
271
  unlikeContent,
272
272
  unpinPlaylist,
273
273
  updatePlaylist,
package/src/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  /*** This file was generated automatically. To recreate, please run `npm run build-index`. ***/
2
2
 
3
3
  import {
4
- globalConfig,
5
4
  initializeService
6
5
  } from './services/config.js';
7
6
 
@@ -79,6 +78,7 @@ import {
79
78
  postContentUnliked,
80
79
  postRecordWatchSession,
81
80
  reportPlaylist,
81
+ setStudentViewForUser,
82
82
  unpinPlaylist,
83
83
  updatePlaylist,
84
84
  updatePlaylistItem
@@ -242,7 +242,6 @@ export {
242
242
  getProgressStateByIds,
243
243
  getResumeTimeSeconds,
244
244
  getSortOrder,
245
- globalConfig,
246
245
  initializeService,
247
246
  isContentLiked,
248
247
  jumpToContinueContent,
@@ -267,6 +266,7 @@ export {
267
266
  recordWatchSession,
268
267
  reportPlaylist,
269
268
  reset,
269
+ setStudentViewForUser,
270
270
  unlikeContent,
271
271
  unpinPlaylist,
272
272
  updatePlaylist,
@@ -289,6 +289,9 @@ async function postDataHandler(url, data) {
289
289
  return fetchHandler(url, 'post', null, data);
290
290
  }
291
291
 
292
+ async function patchDataHandler(url, data) {
293
+ return fetchHandler(url, 'patch', null, data);}
294
+
292
295
  export async function fetchHandler(url, method = "get", dataVersion = null, body = null) {
293
296
  let headers = {
294
297
  'Content-Type': 'application/json',
@@ -1178,6 +1181,19 @@ export async function playback(playlistId) {
1178
1181
  return await fetchHandler(url, "GET");
1179
1182
  }
1180
1183
 
1184
+ /**
1185
+ * Set a user's StudentView Flag
1186
+ *
1187
+ * @param {int|string} userId - id of the user (must be currently authenticated)
1188
+ * @param {bool} enable - truthsy value to enable student view
1189
+ * @returns {Promise<any|null>}
1190
+ */
1191
+ export async function setStudentViewForUser(userId, enable) {
1192
+ let url = `/user-management-system/user/update/${userId}`;
1193
+ let data = {'use_student_view' : enable ? 1 : 0};
1194
+ return await patchDataHandler(url, data);
1195
+ }
1196
+
1181
1197
  function fetchAbsolute(url, params) {
1182
1198
  if (globalConfig.railcontentConfig.authToken) {
1183
1199
  params.headers['Authorization'] = `Bearer ${globalConfig.railcontentConfig.authToken}`;
@@ -422,10 +422,29 @@ export async function fetchScheduledReleases(brand, {page = 1, limit = 10}) {
422
422
  * .catch(error => console.error(error));
423
423
  */
424
424
  export async function fetchByRailContentId(id, contentType) {
425
+ const fields = getFieldsForContentType(contentType);
426
+ const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
427
+ const entityFieldsString = ` ${fields}
428
+ 'child_count': coalesce(count(child[${childrenFilter}]->), 0) ,
429
+ "lessons": child[${childrenFilter}]->{
430
+ "id": railcontent_id,
431
+ title,
432
+ "image": thumbnail.asset->url,
433
+ "instructors": instructor[]->name,
434
+ length_in_seconds,
435
+ },
436
+ 'length_in_seconds': coalesce(
437
+ math::sum(
438
+ select(
439
+ child[${childrenFilter}]->length_in_seconds
440
+ )
441
+ ),
442
+ length_in_seconds
443
+ ),`;
425
444
 
426
445
  const query = buildRawQuery(
427
446
  `railcontent_id == ${id} && _type == '${contentType}'`,
428
- getFieldsForContentType(contentType),
447
+ entityFieldsString,
429
448
  {
430
449
  isSingle: true,
431
450
  },
@@ -595,6 +614,8 @@ export async function fetchAll(brand, type, {
595
614
  `;
596
615
  filter = `_type == '${groupBy}' && count(*[${lessonsFilterWithRestrictions}]._id) > 0`;
597
616
  } else if (groupBy !== "") {
617
+ const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
618
+
598
619
  const webUrlPath = (groupBy == 'genre') ? '/genres' : '';
599
620
  const lessonsFilter = `brand == '${brand}' && ^._id in ${groupBy}[]._ref ${typeFilter} ${searchFilter} ${includedFieldsFilter} ${progressFilter} ${customFilter}`;
600
621
  const lessonsFilterWithRestrictions = await new FilterBuilder(lessonsFilter).buildFilter();
@@ -608,12 +629,23 @@ export async function fetchAll(brand, type, {
608
629
  'all_lessons_count': count(*[${lessonsFilterWithRestrictions}]._id),
609
630
  'lessons': *[${lessonsFilterWithRestrictions}]{
610
631
  ${fieldsString},
632
+ 'lesson_count': coalesce(count(child[${childrenFilter}]->), 0) ,
611
633
  ${groupBy}
612
634
  }[0...20]`;
613
635
  filter = `_type == '${groupBy}' && count(*[${lessonsFilterWithRestrictions}]._id) > 0`;
614
636
  } else {
615
637
  filter = `brand == "${brand}" ${typeFilter} ${searchFilter} ${includedFieldsFilter} ${progressFilter} ${customFilter}`
616
- entityFieldsString = fieldsString;
638
+ const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
639
+ entityFieldsString = ` ${fieldsString},
640
+ 'lesson_count': coalesce(count(child[${childrenFilter}]->), 0) ,
641
+ 'length_in_seconds': coalesce(
642
+ math::sum(
643
+ select(
644
+ child[${childrenFilter}]->length_in_seconds
645
+ )
646
+ ),
647
+ length_in_seconds
648
+ ),`;
617
649
  }
618
650
 
619
651
  const filterWithRestrictions = await new FilterBuilder(filter, {
@@ -965,6 +997,8 @@ export async function fetchFoundation(slug) {
965
997
  * @returns {Promise<Object|null>} - The fetched methods data or null if not found.
966
998
  */
967
999
  export async function fetchMethod(brand, slug) {
1000
+ const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
1001
+
968
1002
  const query = `*[_type == 'learning-path' && brand == "${brand}" && slug.current == "${slug}"] {
969
1003
  "description": ${descriptionField},
970
1004
  "instructors":instructor[]->name,
@@ -984,7 +1018,7 @@ export async function fetchMethod(brand, slug) {
984
1018
  } | order(length(url)),
985
1019
  "type": _type,
986
1020
  "permission_id": permission[]->railcontent_id,
987
- "levels": child[]->
1021
+ "levels": child[${childrenFilter}]->
988
1022
  {
989
1023
  "id": railcontent_id,
990
1024
  published_on,
@@ -1011,8 +1045,10 @@ export async function fetchMethod(brand, slug) {
1011
1045
  * @returns {Promise<Object|null>} - The fetched next lesson data or null if not found.
1012
1046
  */
1013
1047
  export async function fetchMethodChildren(railcontentId) {
1048
+ const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
1049
+
1014
1050
  const query = `*[railcontent_id == ${railcontentId}]{
1015
- child_count,
1051
+ "child_count":coalesce(count(child[${childrenFilter}]->), 0),
1016
1052
  "id": railcontent_id,
1017
1053
  "description": ${descriptionField},
1018
1054
  "thumbnail_url": thumbnail.asset->url,
@@ -1026,7 +1062,7 @@ export async function fetchMethodChildren(railcontentId) {
1026
1062
  "title": *[railcontent_id == ^.id][0].title,
1027
1063
  "url": *[railcontent_id == ^.id][0].web_url_path
1028
1064
  } | order(length(url)),
1029
- 'children': child[]->{
1065
+ 'children': child[(${childrenFilter})]->{
1030
1066
  ${getFieldsForContentType('method')}
1031
1067
  },
1032
1068
  }[0..1]`;
@@ -1080,14 +1116,16 @@ export async function fetchMethodPreviousNextLesson(railcontentId, methodId) {
1080
1116
  * @returns {Promise<Array<Object>|null>} - The fetched children data or null if not found.
1081
1117
  */
1082
1118
  export async function fetchMethodChildrenIds(railcontentId) {
1119
+ const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
1120
+
1083
1121
  const query = `*[ railcontent_id == ${railcontentId}]{
1084
- 'children': child[]-> {
1122
+ 'children': child[${childrenFilter}]-> {
1085
1123
  'id': railcontent_id,
1086
1124
  'type' : _type,
1087
- 'children': child[]-> {
1125
+ 'children': child[${childrenFilter}]-> {
1088
1126
  'id': railcontent_id,
1089
1127
  'type' : _type,
1090
- 'children': child[]-> {
1128
+ 'children': child[${childrenFilter}]-> {
1091
1129
  'id': railcontent_id,
1092
1130
  'type' : _type,
1093
1131
  }
@@ -1249,11 +1287,12 @@ export async function fetchRelatedLessons(railContentId, brand) {
1249
1287
  const filterSongSameArtist = await new FilterBuilder(`_type=="song" && _type==^._type && brand == "${brand}" && references(^.artist->_id) && railcontent_id !=${railContentId}`).buildFilter();
1250
1288
  const filterSongSameGenre = await new FilterBuilder(`_type=="song" && _type==^._type && brand == "${brand}" && references(^.genre[]->_id) && railcontent_id !=${railContentId}`).buildFilter();
1251
1289
  const filterNeighbouringSiblings = await new FilterBuilder(`references(^._id)`).buildFilter();
1290
+ const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
1252
1291
 
1253
1292
  const query = `*[railcontent_id == ${railContentId} && brand == "${brand}" && (!defined(permission) || references(*[_type=='permission']._id))]{
1254
1293
  _type, parent_type, railcontent_id,
1255
1294
  "related_lessons" : array::unique([
1256
- ...(*[${filterNeighbouringSiblings}][0].child[]->{_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail_url":thumbnail.asset->url, length_in_seconds, web_url_path, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}),
1295
+ ...(*[${filterNeighbouringSiblings}][0].child[${childrenFilter}]->{_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail_url":thumbnail.asset->url, length_in_seconds, web_url_path, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}),
1257
1296
  ...(*[${filterSongSameArtist}]{_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail_url":thumbnail.asset->url, length_in_seconds, web_url_path, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}|order(published_on desc, title asc)[0...10]),
1258
1297
  ...(*[${filterSongSameGenre}]{_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail_url":thumbnail.asset->url, length_in_seconds, web_url_path, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type}|order(published_on desc, title asc)[0...10]),
1259
1298
  ...(*[${filterSameTypeAndSortOrder}]{_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail_url":thumbnail.asset->url, length_in_seconds, web_url_path, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type, sort}|order(sort asc, title asc)[0...10]),
@@ -1457,10 +1496,11 @@ export async function fetchCoachLessons(brand, id, {
1457
1496
  ? filtersToGroq(includedFields)
1458
1497
  : "";
1459
1498
  const filter = `brand == '${brand}' ${searchFilter} ${includedFieldsFilter} && references(*[_type=='instructor' && railcontent_id == ${id}]._id)`;
1499
+ const filterWithRestrictions = await new FilterBuilder(filter).buildFilter();
1460
1500
 
1461
1501
  sortOrder = getSortOrder(sortOrder, brand);
1462
1502
  const query = buildEntityAndTotalQuery(
1463
- filter,
1503
+ filterWithRestrictions,
1464
1504
  fieldsString,
1465
1505
  {
1466
1506
  sortOrder: sortOrder,
@@ -1683,19 +1723,20 @@ export async function fetchTopLevelParentId(railcontentId) {
1683
1723
 
1684
1724
  export async function fetchHierarchy(railcontentId) {
1685
1725
  let topLevelId = await fetchTopLevelParentId(railcontentId);
1726
+ const childrenFilter = await new FilterBuilder(``, {isChildrenFilter: true} ).buildFilter();
1686
1727
  const query = `*[railcontent_id == ${topLevelId}]{
1687
1728
  railcontent_id,
1688
1729
  'assignments': assignment[]{railcontent_id},
1689
- 'children': child[]->{
1730
+ 'children': child[${childrenFilter}]->{
1690
1731
  railcontent_id,
1691
1732
  'assignments': assignment[]{railcontent_id},
1692
- 'children': child[]->{
1733
+ 'children': child[${childrenFilter}]->{
1693
1734
  railcontent_id,
1694
1735
  'assignments': assignment[]{railcontent_id},
1695
- 'children': child[]->{
1736
+ 'children': child[${childrenFilter}]->{
1696
1737
  railcontent_id,
1697
1738
  'assignments': assignment[]{railcontent_id},
1698
- 'children': child[]->{
1739
+ 'children': child[${childrenFilter}]->{
1699
1740
  railcontent_id,
1700
1741
  }
1701
1742
  }
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
@@ -607,12 +607,12 @@ describe('Sanity Queries', function () {
607
607
  });
608
608
 
609
609
  test('fetchMethodNextPreviousLesson-Last', async () => {
610
- const id = 396234;
611
- const methodId = 396229;
610
+ const id = 260171;
611
+ const methodId = 259060;
612
612
  const response = await fetchMethodPreviousNextLesson(id, methodId);
613
613
  log(response);
614
614
  expect(response.prevLesson).toBeDefined();
615
- expect(response.prevLesson.id).toBe(396233);
615
+ expect(response.prevLesson.id).toBe(260170);
616
616
  expect(response.prevLesson.type).toBe('course-part');
617
617
  expect(response.nextLesson).not.toBeDefined();
618
618
  });
File without changes
File without changes
package/.yarnrc.yml DELETED
@@ -1 +0,0 @@
1
- nodeLinker: node-modules