musora-content-services 2.30.5 → 2.31.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 (158) 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/.yarnrc.yml +1 -0
  10. package/CHANGELOG.md +54 -0
  11. package/README.md +0 -0
  12. package/babel.config.cjs +0 -0
  13. package/docs/ContentOrganization.html +0 -0
  14. package/docs/Gamification.html +0 -0
  15. package/docs/UserManagementSystem.html +0 -0
  16. package/docs/api_types.js.html +0 -0
  17. package/docs/config.js.html +0 -0
  18. package/docs/content-org_content-org.js.html +0 -0
  19. package/docs/content-org_playlists-types.js.html +0 -0
  20. package/docs/content-org_playlists.js.html +0 -0
  21. package/docs/content.js.html +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  23. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  24. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  25. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  26. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  27. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  28. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  29. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  34. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  35. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  36. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  37. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  38. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  39. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  40. package/docs/gamification_awards.js.html +0 -0
  41. package/docs/gamification_gamification.js.html +0 -0
  42. package/docs/gamification_types.js.html +0 -0
  43. package/docs/global.html +0 -0
  44. package/docs/index.html +0 -0
  45. package/docs/module-Awards.html +0 -0
  46. package/docs/module-Config.html +0 -0
  47. package/docs/module-Content-Services-V2.html +0 -0
  48. package/docs/module-Interests.html +0 -0
  49. package/docs/module-Permissions.html +0 -0
  50. package/docs/module-Playlists.html +0 -0
  51. package/docs/module-Railcontent-Services.html +0 -0
  52. package/docs/module-Sanity-Services.html +0 -0
  53. package/docs/module-Sessions.html +0 -0
  54. package/docs/module-UserActivity.html +0 -0
  55. package/docs/module-UserChat.html +0 -0
  56. package/docs/module-UserManagement.html +0 -0
  57. package/docs/module-UserNotifications.html +0 -0
  58. package/docs/module-UserProfile.html +0 -0
  59. package/docs/railcontent.js.html +0 -0
  60. package/docs/sanity.js.html +0 -0
  61. package/docs/scripts/collapse.js +0 -0
  62. package/docs/scripts/commonNav.js +0 -0
  63. package/docs/scripts/linenumber.js +0 -0
  64. package/docs/scripts/nav.js +0 -0
  65. package/docs/scripts/polyfill.js +0 -0
  66. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  67. package/docs/scripts/prettify/lang-css.js +0 -0
  68. package/docs/scripts/prettify/prettify.js +0 -0
  69. package/docs/scripts/search.js +0 -0
  70. package/docs/styles/jsdoc.css +0 -0
  71. package/docs/styles/prettify.css +0 -0
  72. package/docs/userActivity.js.html +0 -0
  73. package/docs/user_chat.js.html +0 -0
  74. package/docs/user_interests.js.html +0 -0
  75. package/docs/user_management.js.html +0 -0
  76. package/docs/user_notifications.js.html +0 -0
  77. package/docs/user_permissions.js.html +0 -0
  78. package/docs/user_profile.js.html +0 -0
  79. package/docs/user_sessions.js.html +0 -0
  80. package/docs/user_types.js.html +0 -0
  81. package/docs/user_user-management-system.js.html +0 -0
  82. package/jest.config.js +0 -0
  83. package/jsdoc.json +0 -0
  84. package/package.json +1 -1
  85. package/src/contentMetaData.js +6 -0
  86. package/src/contentTypeConfig.js +7 -3
  87. package/src/filterBuilder.js +12 -1
  88. package/src/index.d.ts +11 -1
  89. package/src/index.js +12 -2
  90. package/src/infrastructure/http/HttpClient.ts +0 -0
  91. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  92. package/src/infrastructure/http/index.ts +0 -0
  93. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  94. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  95. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  96. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  97. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  98. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  99. package/src/lib/httpHelper.js +0 -0
  100. package/src/lib/lastUpdated.js +0 -0
  101. package/src/services/api/types.js +0 -0
  102. package/src/services/config.js +0 -0
  103. package/src/services/content-org/content-org.js +0 -0
  104. package/src/services/content-org/guided-courses.ts +0 -0
  105. package/src/services/content-org/playlists-types.js +0 -0
  106. package/src/services/content-org/playlists.js +0 -0
  107. package/src/services/content.js +18 -8
  108. package/src/services/contentAggregator.js +5 -4
  109. package/src/services/contentLikes.js +0 -0
  110. package/src/services/contentProgress.js +0 -0
  111. package/src/services/dataContext.js +0 -0
  112. package/src/services/dateUtils.js +0 -0
  113. package/src/services/forum.js +0 -0
  114. package/src/services/gamification/awards.js +0 -0
  115. package/src/services/gamification/gamification.js +0 -0
  116. package/src/services/gamification/types.js +0 -0
  117. package/src/services/imageSRCBuilder.js +0 -0
  118. package/src/services/imageSRCVerify.js +0 -0
  119. package/src/services/railcontent.js +20 -0
  120. package/src/services/recommendations.js +0 -0
  121. package/src/services/sanity.js +64 -56
  122. package/src/services/types.js +0 -0
  123. package/src/services/user/account.ts +0 -0
  124. package/src/services/user/chat.js +0 -0
  125. package/src/services/user/interests.js +0 -0
  126. package/src/services/user/management.js +0 -0
  127. package/src/services/user/notifications.js +25 -3
  128. package/src/services/user/permissions.js +0 -0
  129. package/src/services/user/profile.js +0 -0
  130. package/src/services/user/sessions.js +0 -0
  131. package/src/services/user/types.js +0 -0
  132. package/src/services/user/user-management-system.js +0 -0
  133. package/src/services/userActivity.js +31 -18
  134. package/test/HttpClient.test.js +0 -0
  135. package/test/content.test.js +0 -0
  136. package/test/contentLikes.test.js +0 -0
  137. package/test/contentProgress.test.js +0 -0
  138. package/test/dataContext.test.js +0 -0
  139. package/test/forum.test.js +0 -0
  140. package/test/imageSRCBuilder.test.js +0 -0
  141. package/test/imageSRCVerify.test.js +0 -0
  142. package/test/initializeTests.js +0 -0
  143. package/test/lib/lastUpdated.test.js +0 -0
  144. package/test/live/contentProgressLive.test.js +0 -0
  145. package/test/live/railcontentLive.test.js +0 -0
  146. package/test/localStorageMock.js +0 -0
  147. package/test/log.js +0 -0
  148. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  149. package/test/mockData/mockData_progress_content.json +0 -0
  150. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  151. package/test/mockData/mockData_user_practices.json +0 -0
  152. package/test/notifications.test.js +0 -0
  153. package/test/progressRows.test.js +0 -0
  154. package/test/sanityQueryService.test.js +0 -0
  155. package/test/streakMessage.test.js +0 -0
  156. package/test/user/permissions.test.js +0 -0
  157. package/test/userActivity.test.js +0 -0
  158. package/tools/generate-index.cjs +0 -0
@@ -77,12 +77,13 @@ export async function addContextToContent(dataPromise, ...dataArgs)
77
77
  const dataParam = lastArg === options ? dataArgs.slice(0, -1) : dataArgs
78
78
 
79
79
  let data = await dataPromise(...dataParam)
80
- if(!data) return false
81
80
  const isDataAnArray = Array.isArray(data)
82
- const items = extractItemsFromData(data, dataField, isDataAnArray, dataField_includeParent)
83
- const ids = items.map(item => item?.id).filter(Boolean)
81
+ if(isDataAnArray && data.length === 0) return data
82
+ if(!data) return false
84
83
 
85
- if(ids.length === 0) return false
84
+ const items = extractItemsFromData(data, dataField, isDataAnArray, dataField_includeParent) ?? []
85
+ const ids = items.map(item => item?.id).filter(Boolean)
86
+ if(ids.length === 0) return data
86
87
 
87
88
  const [progressData, isLikedData, resumeTimeData, lastInteractedChildData, nextLessonData, navigateToData] = await Promise.all([
88
89
  addProgressPercentage || addProgressStatus || addProgressTimestamp ? getProgressDateByIds(ids) : Promise.resolve(null),
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
@@ -378,16 +378,36 @@ export async function fetchUserBadges(brand = null) {
378
378
  return await fetchHandler(url, 'get')
379
379
  }
380
380
 
381
+ /**
382
+ * complete a content's progress for a given user
383
+ * @param contentId
384
+ * @returns {Promise<any|string|null>}
385
+ */
381
386
  export async function postContentComplete(contentId) {
382
387
  let url = `/api/content/v1/user/progress/complete/${contentId}`
383
388
  return postDataHandler(url)
384
389
  }
385
390
 
391
+ /**
392
+ * resets the user's progress on a content
393
+ * @param contentId
394
+ * @returns {Promise<any|string|null>}
395
+ */
386
396
  export async function postContentReset(contentId) {
387
397
  let url = `/api/content/v1/user/progress/reset/${contentId}`
388
398
  return postDataHandler(url)
389
399
  }
390
400
 
401
+ /**
402
+ * restores the user's progress on a content
403
+ * @param contentId
404
+ * @returns {Promise<any|string|null>}
405
+ */
406
+ export async function postContentRestore(contentId) {
407
+ let url = `/api/content/v1/user/progress/restore/${contentId}`
408
+ return postDataHandler(url)
409
+ }
410
+
391
411
  /**
392
412
  * Set a user's StudentView Flag
393
413
  *
File without changes
@@ -476,7 +476,7 @@ export async function fetchByRailContentId(id, contentType) {
476
476
  * .then(contents => console.log(contents))
477
477
  * .catch(error => console.error(error));
478
478
  */
479
- export async function fetchByRailContentIds(ids, contentType = undefined, brand = undefined) {
479
+ export async function fetchByRailContentIds(ids, contentType = undefined, brand = undefined, includePermissionsAndStatusFilter = false) {
480
480
  if (!ids?.length) {
481
481
  return []
482
482
  }
@@ -485,8 +485,10 @@ export async function fetchByRailContentIds(ids, contentType = undefined, brand
485
485
  const brandFilter = brand ? ` && brand == "${brand}"` : ''
486
486
  const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`, {pullFutureContent: true}).buildFilter()
487
487
  const fields = await getFieldsForContentTypeWithFilteredChildren(contentType, true)
488
+ const baseFilter = `railcontent_id in [${idsString}]${brandFilter}`
489
+ const finalFilter = includePermissionsAndStatusFilter ? await new FilterBuilder(baseFilter).buildFilter() : baseFilter
488
490
  const query = `*[
489
- railcontent_id in [${idsString}]${brandFilter}
491
+ ${finalFilter}
490
492
  ]{
491
493
  ${fields}
492
494
  'lesson_count': coalesce(count(*[${lessonCountFilter}]), 0),
@@ -535,7 +537,9 @@ export async function fetchContentRows(brand, pageName, contentRowSlug)
535
537
  name,
536
538
  'slug': slug.current,
537
539
  'content': content[${childFilter}]->{
538
- 'children': child[${childFilter}]->{ 'id': railcontent_id, 'children': child[${childFilter}]->{'id': railcontent_id}, },
540
+ 'children': child[${childFilter}]->{ 'id': railcontent_id,
541
+ 'type': _type, brand, 'thumbnail': thumbnail.asset->url,
542
+ 'children': child[${childFilter}]->{'id': railcontent_id}, },
539
543
  ${getFieldsForContentType('tab-data')}
540
544
  'lesson_count': coalesce(count(*[${lessonCountFilter}]), 0),
541
545
  },
@@ -749,7 +753,7 @@ export function getSortOrder(sort = '-published_on', brand, groupBy) {
749
753
  sort = isDesc ? sort.substring(1) : sort
750
754
  switch (sort) {
751
755
  case 'slug':
752
- sortOrder = groupBy ? 'name' : 'title'
756
+ sortOrder = groupBy ? 'name' : '!defined(title), lower(title)'
753
757
  break
754
758
  case 'name':
755
759
  sortOrder = sort
@@ -1089,6 +1093,7 @@ export async function jumpToContinueContent(railcontentId) {
1089
1093
  /**
1090
1094
  * Fetch the page data for a specific lesson by Railcontent ID.
1091
1095
  * @param {string} railContentId - The Railcontent ID of the current lesson.
1096
+ * @parent {boolean} addParent - Whether to include parent content data in the response.
1092
1097
  * @returns {Promise<Object|null>} - The fetched page data or null if found.
1093
1098
  *
1094
1099
  * @example
@@ -1096,44 +1101,51 @@ export async function jumpToContinueContent(railcontentId) {
1096
1101
  * .then(data => console.log(data))
1097
1102
  * .catch(error => console.error(error));
1098
1103
  */
1099
- export async function fetchLessonContent(railContentId) {
1104
+ export async function fetchLessonContent(railContentId, { addParent = false } = {}) {
1100
1105
  const filterParams = { isSingle: true, pullFutureContent: true }
1101
1106
 
1107
+ const parentQuery = addParent
1108
+ ? `"parent_content_data": *[railcontent_id in [...(^.parent_content_data[].id)]]{
1109
+ "id": railcontent_id,
1110
+ title,
1111
+ slug,
1112
+ "type": _type,
1113
+ "logo" : logo_image_url.asset->url,
1114
+ "dark_mode_logo": dark_mode_logo_url.asset->url,
1115
+ "light_mode_logo": light_mode_logo_url.asset->url,
1116
+ "badge": badge[0]->badge.asset->url,
1117
+ },`
1118
+ : ''
1119
+
1102
1120
  const fields = `${getFieldsForContentType()}
1103
- "resources": ${resourcesField},
1104
- soundslice,
1105
- instrumentless,
1106
- soundslice_slug,
1107
- "description": ${descriptionField},
1108
- "chapters": ${chapterField},
1109
- "instructors":instructor[]->name,
1110
- "instructor": ${instructorField},
1111
- ${assignmentsField}
1112
- video,
1113
- length_in_seconds,
1114
- mp3_no_drums_no_click_url,
1115
- mp3_no_drums_yes_click_url,
1116
- mp3_yes_drums_no_click_url,
1117
- mp3_yes_drums_yes_click_url,
1118
- "permission_id": permission[]->railcontent_id,
1119
- "parent_content_data": parent_content_data[]{
1120
- "id": id,
1121
- "title": *[railcontent_id == ^.id][0].title,
1122
- "slug":*[railcontent_id == ^.id][0].slug,
1123
- "type": *[railcontent_id == ^.id][0]._type,
1124
- "logo" : *[railcontent_id == ^.id][0].logo_image_url.asset->url,
1125
- "dark_mode_logo": *[railcontent_id == ^.id][0].dark_mode_logo_url.asset->url,
1126
- "light_mode_logo": *[railcontent_id == ^.id][0].light_mode_logo_url.asset->url,
1127
- },
1128
- ...select(
1129
- defined(live_event_start_time) => {
1130
- "live_event_start_time": live_event_start_time,
1131
- "live_event_end_time": live_event_end_time,
1132
- "live_event_youtube_id": live_event_youtube_id,
1133
- "videoId": coalesce(live_event_youtube_id, video.external_id),
1134
- "live_event_is_global": live_global_event == true
1135
- }
1136
- )`
1121
+ "resources": ${resourcesField},
1122
+ soundslice,
1123
+ instrumentless,
1124
+ soundslice_slug,
1125
+ "description": ${descriptionField},
1126
+ "chapters": ${chapterField},
1127
+ "instructors":instructor[]->name,
1128
+ "instructor": ${instructorField},
1129
+ ${assignmentsField}
1130
+ video,
1131
+ length_in_seconds,
1132
+ mp3_no_drums_no_click_url,
1133
+ mp3_no_drums_yes_click_url,
1134
+ mp3_yes_drums_no_click_url,
1135
+ mp3_yes_drums_yes_click_url,
1136
+ "permission_id": permission[]->railcontent_id,
1137
+ ${parentQuery}
1138
+ ...select(
1139
+ defined(live_event_start_time) => {
1140
+ "live_event_start_time": live_event_start_time,
1141
+ "live_event_end_time": live_event_end_time,
1142
+ "live_event_youtube_id": live_event_youtube_id,
1143
+ "videoId": coalesce(live_event_youtube_id, video.external_id),
1144
+ "live_event_is_global": live_global_event == true
1145
+ }
1146
+ )
1147
+ `
1148
+
1137
1149
  const query = await buildQuery(`railcontent_id == ${railContentId}`, filterParams, fields, {
1138
1150
  isSingle: true,
1139
1151
  })
@@ -1166,7 +1178,7 @@ export async function fetchLessonContent(railContentId) {
1166
1178
  export async function fetchRelatedRecommendedContent(railContentId, brand, count = 10) {
1167
1179
  const recommendedItems = await fetchSimilarItems(railContentId, brand, count)
1168
1180
  if (recommendedItems && recommendedItems.length > 0) {
1169
- return fetchByRailContentIds(recommendedItems)
1181
+ return fetchByRailContentIds(recommendedItems, 'tab-data', brand, true)
1170
1182
  }
1171
1183
 
1172
1184
  return await fetchRelatedLessons(railContentId, brand).then((result) =>
@@ -1239,20 +1251,26 @@ async function fetchRelatedByLicense(railcontentId, brand, onlyUseSongTypes, cou
1239
1251
  * @param {string} brand - The current brand.
1240
1252
  * @returns {Promise<Array<Object>|null>} - The fetched related lessons data or null if not found.
1241
1253
  */
1242
- export async function fetchSiblingContent(railContentId, brand)
1254
+ export async function fetchSiblingContent(railContentId, brand= null)
1243
1255
  {
1244
1256
  const filterGetParent = await new FilterBuilder(`references(^._id) && _type == ^.parent_type`, {
1257
+ pullFutureContent: true
1258
+ }).buildFilter()
1259
+ const filterForParentList = await new FilterBuilder(`references(^._id) && _type == ^.parent_type`, {
1245
1260
  pullFutureContent: true,
1261
+ isParentFilter: true,
1246
1262
  }).buildFilter()
1263
+
1247
1264
  const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
1248
1265
 
1266
+ const brandString = brand ? ` && brand == "${brand}"` : ''
1249
1267
  const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, difficulty_string, artist->, "permission_id": permission[]->railcontent_id, "genre": genre[]->name, "parent_id": parent_content_data[0].id`
1250
1268
 
1251
- const query = `*[railcontent_id == ${railContentId} && brand == "${brand}"]{
1269
+ const query = `*[railcontent_id == ${railContentId}${brandString}]{
1252
1270
  _type, parent_type, 'parent_id': parent_content_data[0].id, railcontent_id,
1253
1271
  'for-calculations': *[${filterGetParent}][0]{
1254
1272
  'siblings-list': child[]->railcontent_id,
1255
- 'parents-list': *[${filterGetParent}][0].child[]->railcontent_id
1273
+ 'parents-list': *[${filterForParentList}][0].child[]->railcontent_id
1256
1274
  },
1257
1275
  "related_lessons" : *[${filterGetParent}][0].child[${childrenFilter}]->{${queryFields}}
1258
1276
  }`
@@ -2157,7 +2175,6 @@ export async function fetchTabData(
2157
2175
  ) {
2158
2176
  const start = (page - 1) * limit
2159
2177
  const end = start + limit
2160
- let withoutPagination = false
2161
2178
  // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
2162
2179
  const includedFieldsFilter =
2163
2180
  includedFields.length > 0 ? filtersToGroq(includedFields, [], pageName) : ''
@@ -2168,27 +2185,19 @@ export async function fetchTabData(
2168
2185
  case 'recent':
2169
2186
  progressIds = await getAllStartedOrCompleted({ brand, onlyIds: true });
2170
2187
  sortOrder = null;
2171
- withoutPagination = true;
2172
2188
  break;
2173
2189
  case 'incomplete':
2174
2190
  progressIds = await getAllStarted();
2175
2191
  sortOrder = null;
2176
- withoutPagination = true;
2177
2192
  break;
2178
2193
  case 'completed':
2179
2194
  progressIds = await getAllCompleted();
2180
2195
  sortOrder = null;
2181
- withoutPagination = true;
2182
2196
  break;
2183
2197
  }
2184
2198
 
2185
2199
  // limits the results to supplied progressIds for started & completed filters
2186
2200
  const progressFilter = await getProgressFilter(progress, progressIds)
2187
- if (sort === "recommended"){
2188
- progressIds = await recommendations(brand);
2189
- withoutPagination = true;
2190
- }
2191
-
2192
2201
  const fieldsString = getFieldsForContentType('tab-data');
2193
2202
  const now = getSanityDate(new Date())
2194
2203
 
@@ -2202,7 +2211,7 @@ export async function fetchTabData(
2202
2211
  const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`).buildFilter()
2203
2212
  entityFieldsString =
2204
2213
  ` ${fieldsString}
2205
- 'children': child[${childrenFilter}]->{'id': railcontent_id},
2214
+ 'children': child[${childrenFilter}]->{'id': railcontent_id, 'type': _type, brand, 'thumbnail': thumbnail.asset->url},
2206
2215
  'isLive': live_event_start_time <= "${now}" && live_event_end_time >= "${now}",
2207
2216
  'lesson_count': coalesce(count(*[${lessonCountFilter}]), 0),
2208
2217
  'length_in_seconds': coalesce(
@@ -2217,13 +2226,12 @@ export async function fetchTabData(
2217
2226
  query = buildEntityAndTotalQuery(filterWithRestrictions, entityFieldsString, {
2218
2227
  sortOrder: sortOrder,
2219
2228
  start: start,
2220
- end: end,
2221
- withoutPagination: withoutPagination,
2229
+ end: end
2222
2230
  })
2223
2231
 
2224
2232
  let results = await fetchSanity(query, true);
2225
2233
 
2226
- if ((['recent', 'incomplete', 'completed'].includes(progress) || sort == "recommended" )&& results.entity.length > 1) {
2234
+ if (['recent', 'incomplete', 'completed'].includes(progress) && results.entity.length > 1) {
2227
2235
  const orderMap = new Map(progressIds.map((id, index) => [id, index]))
2228
2236
  results.entity = results.entity
2229
2237
  .sort((a, b) => {
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -114,11 +114,33 @@ export async function deleteNotification(notificationId) {
114
114
  if (!notificationId) {
115
115
  throw new Error('notificationId is required')
116
116
  }
117
-
118
117
  const url = `${baseUrl}/v1/${notificationId}`
119
118
  return fetchHandler(url, 'delete')
120
119
  }
121
120
 
121
+ /**
122
+ * Restores a specific notification.
123
+ *
124
+ * @param {number} notificationId - The ID of the notification to restore.
125
+ *
126
+ * @returns {Promise<any>} - A promise that resolves when the notification is successfully restored.
127
+ *
128
+ * @throws {Error} - Throws an error if notificationId is not provided.
129
+ *
130
+ * @example
131
+ * restoreNotification(123)
132
+ * .then(response => console.log(response))
133
+ * .catch(error => console.error(error));
134
+ */
135
+ export async function restoreNotification(notificationId) {
136
+ if (!notificationId) {
137
+ throw new Error('notificationId is required')
138
+ }
139
+
140
+ const url = `${baseUrl}/v1/${notificationId}`
141
+ return fetchHandler(url, 'put')
142
+ }
143
+
122
144
  /**
123
145
  * Fetches the count of unread notifications for the current user in a given brand context.
124
146
  *
@@ -140,7 +162,7 @@ export async function deleteNotification(notificationId) {
140
162
  export async function fetchUnreadCount({ brand = 'drumeo'} = {}) {
141
163
  const url = `${baseUrl}/v1/unread-count`
142
164
  const notifUnread = await fetchHandler(url, 'get')
143
- if (notifUnread.data > 0) {
165
+ if (notifUnread && notifUnread.data > 0) {
144
166
  return notifUnread// Return early if unread notifications exist
145
167
  }
146
168
  const liveEventPollingState = await fetchLiveEventPollingState()
@@ -148,7 +170,7 @@ export async function fetchUnreadCount({ brand = 'drumeo'} = {}) {
148
170
  const liveEvent = await fetchLiveEvent(brand)
149
171
  return { data: liveEvent ? 1 : 0}
150
172
  }
151
- return notifUnread
173
+ return { data: 0}
152
174
  }
153
175
 
154
176
  /**
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -11,7 +11,7 @@ import {
11
11
  fetchRecentUserActivities,
12
12
  } from './railcontent'
13
13
  import { DataContext, UserActivityVersionKey } from './dataContext.js'
14
- import { fetchByRailContentIds, fetchShows } from './sanity'
14
+ import { fetchByRailContentId, fetchByRailContentIds, fetchShows } from './sanity'
15
15
  import { fetchPlaylist, fetchUserPlaylists } from './content-org/playlists'
16
16
  import { pinnedGuidedCourses } from './content-org/guided-courses'
17
17
  import {
@@ -930,6 +930,22 @@ export async function deleteUserActivity(id) {
930
930
  return await fetchHandler(url, 'DELETE')
931
931
  }
932
932
 
933
+ /**
934
+ * Restores a specific user activity by its ID.
935
+ *
936
+ * @param {number|string} id - The ID of the user activity to restore.
937
+ * @returns {Promise<Object>} - A promise that resolves to the API response after restoration.
938
+ *
939
+ * @example
940
+ * restoreUserActivity(789)
941
+ * .then(response => console.log('Restored:', response))
942
+ * .catch(error => console.error(error));
943
+ */
944
+ export async function restoreUserActivity(id) {
945
+ const url = `/api/user-management-system/v1/activities/${id}`
946
+ return await fetchHandler(url, 'POST')
947
+ }
948
+
933
949
  async function extractPinnedItemsAndSortAllItems(
934
950
  userPinnedItem,
935
951
  contentsMap,
@@ -1149,24 +1165,21 @@ async function processContentItem(content) {
1149
1165
  }
1150
1166
 
1151
1167
  return {
1152
- id: content.id,
1153
- progressType: 'content',
1154
- header: contentType,
1155
- pinned: content.pinned ?? false,
1156
- content: content,
1157
- body: {
1168
+ id: content.id,
1169
+ progressType: 'content',
1170
+ header: contentType,
1171
+ pinned: content.pinned ?? false,
1172
+ content: content,
1173
+ body: {
1158
1174
  progressPercent: isLive ? undefined : content.progressPercentage,
1159
- thumbnail: content.thumbnail,
1160
- title: content.title,
1161
- isLive: isLive,
1162
- badge: content.badge ?? null,
1163
- isLocked: content.is_locked ?? false,
1164
- subtitle:
1165
- !content.child_count || content.lesson_count === 1
1166
- ? contentType === 'lesson' && isLive === false
1167
- ? `${content.progressPercentage}% Complete`
1168
- : `${content.difficulty_string} • ${content.artist_name}`
1169
- : `${content.completed_children} of ${content.lesson_count ?? content.child_count} Lessons Complete`,
1175
+ thumbnail: content.thumbnail,
1176
+ title: content.title,
1177
+ isLive: isLive,
1178
+ badge: content.badge ?? null,
1179
+ isLocked: content.is_locked ?? false,
1180
+ subtitle: collectionLessonTypes.includes(content.type) || content.lesson_count > 1
1181
+ ? `${content.completed_children} of ${content.lesson_count ?? content.child_count} Lessons Complete`
1182
+ : (contentType === 'lesson' && isLive === false) ? `${content.progressPercentage}% Complete`: `${content.difficulty_string} • ${content.artist_name}`
1170
1183
  },
1171
1184
  cta: {
1172
1185
  text: ctaText,
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