musora-content-services 2.31.2 → 2.32.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 (48) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/docs/ContentOrganization.html +2 -2
  3. package/docs/Gamification.html +2 -2
  4. package/docs/UserManagementSystem.html +2 -2
  5. package/docs/api_types.js.html +2 -2
  6. package/docs/config.js.html +2 -8
  7. package/docs/content-org_content-org.js.html +2 -2
  8. package/docs/content-org_playlists-types.js.html +12 -2
  9. package/docs/content-org_playlists.js.html +16 -16
  10. package/docs/content.js.html +71 -39
  11. package/docs/gamification_awards.js.html +54 -35
  12. package/docs/gamification_gamification.js.html +2 -2
  13. package/docs/gamification_types.js.html +7 -25
  14. package/docs/global.html +298 -322
  15. package/docs/index.html +2 -2
  16. package/docs/module-Awards.html +2 -2
  17. package/docs/module-Config.html +4 -8
  18. package/docs/module-Content-Services-V2.html +455 -8
  19. package/docs/module-Interests.html +2 -2
  20. package/docs/module-Permissions.html +2 -2
  21. package/docs/module-Playlists.html +18 -17
  22. package/docs/module-Railcontent-Services.html +479 -35
  23. package/docs/module-Sanity-Services.html +45 -173
  24. package/docs/module-Sessions.html +2 -2
  25. package/docs/module-UserActivity.html +189 -21
  26. package/docs/module-UserChat.html +2 -2
  27. package/docs/module-UserManagement.html +410 -8
  28. package/docs/module-UserNotifications.html +203 -8
  29. package/docs/module-UserProfile.html +2 -2
  30. package/docs/railcontent.js.html +65 -37
  31. package/docs/sanity.js.html +120 -185
  32. package/docs/userActivity.js.html +448 -466
  33. package/docs/user_chat.js.html +2 -2
  34. package/docs/user_interests.js.html +2 -2
  35. package/docs/user_management.js.html +32 -2
  36. package/docs/user_notifications.js.html +27 -5
  37. package/docs/user_permissions.js.html +2 -2
  38. package/docs/user_profile.js.html +3 -9
  39. package/docs/user_sessions.js.html +2 -2
  40. package/docs/user_types.js.html +10 -2
  41. package/docs/user_user-management-system.js.html +2 -2
  42. package/package.json +1 -1
  43. package/src/contentTypeConfig.js +6 -1
  44. package/src/index.d.ts +6 -0
  45. package/src/index.js +6 -0
  46. package/src/services/sanity.js +14 -19
  47. package/src/services/user/management.js +30 -0
  48. package/src/services/user/types.js +8 -0
@@ -29,7 +29,7 @@
29
29
  <nav >
30
30
 
31
31
 
32
- <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Awards.html">Awards</a><ul class='methods'><li data-type='method'><a href="module-Awards.html#.fetchAwardsForUser">fetchAwardsForUser</a></li></ul></li><li><a href="module-Config.html">Config</a><ul class='methods'><li data-type='method'><a href="module-Config.html#.initializeService">initializeService</a></li></ul></li><li><a href="module-Content-Services-V2.html">Content-Services-V2</a><ul class='methods'><li data-type='method'><a href="module-Content-Services-V2.html#.getContentRows">getContentRows</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getNewAndUpcoming">getNewAndUpcoming</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getRecent">getRecent</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getRecommendedForYou">getRecommendedForYou</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getScheduleContentRows">getScheduleContentRows</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getTabResults">getTabResults</a></li></ul></li><li><a href="module-Interests.html">Interests</a><ul class='methods'><li data-type='method'><a href="module-Interests.html#.fetchInterests">fetchInterests</a></li><li data-type='method'><a href="module-Interests.html#.fetchUninterests">fetchUninterests</a></li><li data-type='method'><a href="module-Interests.html#.markContentAsInterested">markContentAsInterested</a></li><li data-type='method'><a href="module-Interests.html#.markContentAsNotInterested">markContentAsNotInterested</a></li><li data-type='method'><a href="module-Interests.html#.removeContentAsInterested">removeContentAsInterested</a></li><li data-type='method'><a href="module-Interests.html#.removeContentAsNotInterested">removeContentAsNotInterested</a></li></ul></li><li><a href="module-Permissions.html">Permissions</a><ul class='methods'><li data-type='method'><a href="module-Permissions.html#.fetchUserPermissions">fetchUserPermissions</a></li><li data-type='method'><a href="module-Permissions.html#.reset">reset</a></li></ul></li><li><a href="module-Playlists.html">Playlists</a><ul class='methods'><li data-type='method'><a href="module-Playlists.html#.addItemToPlaylist">addItemToPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.createPlaylist">createPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.deletePlaylist">deletePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.duplicatePlaylist">duplicatePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.fetchPlaylist">fetchPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.fetchPlaylistItems">fetchPlaylistItems</a></li><li data-type='method'><a href="module-Playlists.html#.fetchUserPlaylists">fetchUserPlaylists</a></li><li data-type='method'><a href="module-Playlists.html#.togglePlaylistPrivate">togglePlaylistPrivate</a></li><li data-type='method'><a href="module-Playlists.html#.undeletePlaylist">undeletePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.updatePlaylist">updatePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~deleteItemsFromPlaylist">deleteItemsFromPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~likePlaylist">likePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~reportPlaylist">reportPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~restoreItemFromPlaylist">restoreItemFromPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~unlikePlaylist">unlikePlaylist</a></li></ul></li><li><a href="module-Railcontent-Services.html">Railcontent-Services</a><ul class='methods'><li data-type='method'><a href="module-Railcontent-Services.html#.assignModeratorToComment">assignModeratorToComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.closeComment">closeComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.createComment">createComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.deleteComment">deleteComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.editComment">editComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchAllCompletedStates">fetchAllCompletedStates</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchCarouselCardData">fetchCarouselCardData</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchComment">fetchComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchCommentRelies">fetchCommentRelies</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchComments">fetchComments</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchCompletedContent">fetchCompletedContent</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchCompletedState">fetchCompletedState</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchContentInProgress">fetchContentInProgress</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchContentPageUserData">fetchContentPageUserData</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchNextContentDataForParent">fetchNextContentDataForParent</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchRecentUserActivities">fetchRecentUserActivities</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchSongsInProgress">fetchSongsInProgress</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchTopComment">fetchTopComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchUserAward">fetchUserAward</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchUserBadges">fetchUserBadges</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchUserPracticeNotes">fetchUserPracticeNotes</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.likeComment">likeComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.openComment">openComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.replyToComment">replyToComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.reportComment">reportComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.restoreComment">restoreComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.setStudentViewForUser">setStudentViewForUser</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.unassignModeratorToComment">unassignModeratorToComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.unlikeComment">unlikeComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#~fetchLastInteractedChild">fetchLastInteractedChild</a></li></ul></li><li><a href="module-Sanity-Services.html">Sanity-Services</a><ul class='methods'><li data-type='method'><a href="module-Sanity-Services.html#.fetchAll">fetchAll</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchAllFilterOptions">fetchAllFilterOptions</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchAllPacks">fetchAllPacks</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchArtistLessons">fetchArtistLessons</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchArtists">fetchArtists</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchByRailContentId">fetchByRailContentId</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchByRailContentIds">fetchByRailContentIds</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchByReference">fetchByReference</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchCoachLessons">fetchCoachLessons</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchComingSoon">fetchComingSoon</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchCommentModContentData">fetchCommentModContentData</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchFoundation">fetchFoundation</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchGenreLessons">fetchGenreLessons</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchLeaving">fetchLeaving</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchLessonContent">fetchLessonContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchLessonsFeaturingThisContent">fetchLessonsFeaturingThisContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMetadata">fetchMetadata</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethod">fetchMethod</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethodChildren">fetchMethodChildren</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethodChildrenIds">fetchMethodChildrenIds</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethodPreviousNextLesson">fetchMethodPreviousNextLesson</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchNewReleases">fetchNewReleases</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchNextPreviousLesson">fetchNextPreviousLesson</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchOtherSongVersions">fetchOtherSongVersions</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchPackAll">fetchPackAll</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchPackData">fetchPackData</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchParentForDownload">fetchParentForDownload</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchRelatedLessons">fetchRelatedLessons</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchRelatedRecommendedContent">fetchRelatedRecommendedContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchRelatedSongs">fetchRelatedSongs</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchReturning">fetchReturning</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSanity">fetchSanity</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchScheduledReleases">fetchScheduledReleases</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchShowsData">fetchShowsData</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSiblingContent">fetchSiblingContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSongArtistCount">fetchSongArtistCount</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSongById">fetchSongById</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchUpcomingEvents">fetchUpcomingEvents</a></li><li data-type='method'><a href="module-Sanity-Services.html#.jumpToContinueContent">jumpToContinueContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#~fetchRelatedByLicense">fetchRelatedByLicense</a></li><li data-type='method'><a href="module-Sanity-Services.html#~getNextAndPreviousQuarterDates">getNextAndPreviousQuarterDates</a></li><li data-type='method'><a href="module-Sanity-Services.html#~getQueryFromPage">getQueryFromPage</a></li></ul></li><li><a href="module-Sessions.html">Sessions</a><ul class='methods'><li data-type='method'><a href="module-Sessions.html#.login">login</a></li><li data-type='method'><a href="module-Sessions.html#.logout">logout</a></li></ul></li><li><a href="module-UserActivity.html">UserActivity</a><ul class='methods'><li data-type='method'><a href="module-UserActivity.html#.calculateLongestStreaks">calculateLongestStreaks</a></li><li data-type='method'><a href="module-UserActivity.html#.createPracticeNotes">createPracticeNotes</a></li><li data-type='method'><a href="module-UserActivity.html#.deletePracticeSession">deletePracticeSession</a></li><li data-type='method'><a href="module-UserActivity.html#.deleteUserActivity">deleteUserActivity</a></li><li data-type='method'><a href="module-UserActivity.html#.getPracticeNotes">getPracticeNotes</a></li><li data-type='method'><a href="module-UserActivity.html#.getPracticeSessions">getPracticeSessions</a></li><li data-type='method'><a href="module-UserActivity.html#.getProgressRows">getProgressRows</a></li><li data-type='method'><a href="module-UserActivity.html#.getRecentActivity">getRecentActivity</a></li><li data-type='method'><a href="module-UserActivity.html#.getUserMonthlyStats">getUserMonthlyStats</a></li><li data-type='method'><a href="module-UserActivity.html#.getUserWeeklyStats">getUserWeeklyStats</a></li><li data-type='method'><a href="module-UserActivity.html#.pinProgressRow">pinProgressRow</a></li><li data-type='method'><a href="module-UserActivity.html#.recordUserActivity">recordUserActivity</a></li><li data-type='method'><a href="module-UserActivity.html#.recordUserPractice">recordUserPractice</a></li><li data-type='method'><a href="module-UserActivity.html#.removeUserPractice">removeUserPractice</a></li><li data-type='method'><a href="module-UserActivity.html#.restorePracticeSession">restorePracticeSession</a></li><li data-type='method'><a href="module-UserActivity.html#.restoreUserPractice">restoreUserPractice</a></li><li data-type='method'><a href="module-UserActivity.html#.unpinProgressRow">unpinProgressRow</a></li><li data-type='method'><a href="module-UserActivity.html#.updatePracticeNotes">updatePracticeNotes</a></li><li data-type='method'><a href="module-UserActivity.html#.updateUserPractice">updateUserPractice</a></li></ul></li><li><a href="module-UserChat.html">UserChat</a><ul class='methods'><li data-type='method'><a href="module-UserChat.html#.fetchChatSettings">fetchChatSettings</a></li></ul></li><li><a href="module-UserManagement.html">UserManagement</a><ul class='methods'><li data-type='method'><a href="module-UserManagement.html#.blockUser">blockUser</a></li><li data-type='method'><a href="module-UserManagement.html#.deletePicture">deletePicture</a></li><li data-type='method'><a href="module-UserManagement.html#.getUserData">getUserData</a></li><li data-type='method'><a href="module-UserManagement.html#.unblockUser">unblockUser</a></li><li data-type='method'><a href="module-UserManagement.html#.uploadPicture">uploadPicture</a></li><li data-type='method'><a href="module-UserManagement.html#.uploadPictureFromS3">uploadPictureFromS3</a></li></ul></li><li><a href="module-UserNotifications.html">UserNotifications</a><ul class='methods'><li data-type='method'><a href="module-UserNotifications.html#.deleteNotification">deleteNotification</a></li><li data-type='method'><a href="module-UserNotifications.html#.fetchLiveEventPollingState">fetchLiveEventPollingState</a></li><li data-type='method'><a href="module-UserNotifications.html#.fetchNotificationSettings">fetchNotificationSettings</a></li><li data-type='method'><a href="module-UserNotifications.html#.fetchNotifications">fetchNotifications</a></li><li data-type='method'><a href="module-UserNotifications.html#.fetchUnreadCount">fetchUnreadCount</a></li><li data-type='method'><a href="module-UserNotifications.html#.markAllNotificationsAsRead">markAllNotificationsAsRead</a></li><li data-type='method'><a href="module-UserNotifications.html#.markNotificationAsRead">markNotificationAsRead</a></li><li data-type='method'><a href="module-UserNotifications.html#.markNotificationAsUnread">markNotificationAsUnread</a></li><li data-type='method'><a href="module-UserNotifications.html#.pauseLiveEventPolling">pauseLiveEventPolling</a></li><li data-type='method'><a href="module-UserNotifications.html#.startLiveEventPolling">startLiveEventPolling</a></li><li data-type='method'><a href="module-UserNotifications.html#.updateNotificationSetting">updateNotificationSetting</a></li></ul></li><li><a href="module-UserProfile.html">UserProfile</a><ul class='methods'><li data-type='method'><a href="module-UserProfile.html#.deleteProfilePicture">deleteProfilePicture</a></li><li data-type='method'><a href="module-UserProfile.html#.otherStats">otherStats</a></li></ul></li></ul><h3>Namespaces</h3><ul><li><a href="ContentOrganization.html">ContentOrganization</a></li><li><a href="Gamification.html">Gamification</a></li><li><a href="UserManagementSystem.html">UserManagementSystem</a></li></ul><h3><a href="global.html">Global</a></h3>
32
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Awards.html">Awards</a><ul class='methods'><li data-type='method'><a href="module-Awards.html#.fetchAwardsForUser">fetchAwardsForUser</a></li></ul></li><li><a href="module-Config.html">Config</a><ul class='methods'><li data-type='method'><a href="module-Config.html#.initializeService">initializeService</a></li></ul></li><li><a href="module-Content-Services-V2.html">Content-Services-V2</a><ul class='methods'><li data-type='method'><a href="module-Content-Services-V2.html#.getContentRows">getContentRows</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getNewAndUpcoming">getNewAndUpcoming</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getRecent">getRecent</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getRecentForTab">getRecentForTab</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getRecommendedForYou">getRecommendedForYou</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getScheduleContentRows">getScheduleContentRows</a></li><li data-type='method'><a href="module-Content-Services-V2.html#.getTabResults">getTabResults</a></li></ul></li><li><a href="module-Interests.html">Interests</a><ul class='methods'><li data-type='method'><a href="module-Interests.html#.fetchInterests">fetchInterests</a></li><li data-type='method'><a href="module-Interests.html#.fetchUninterests">fetchUninterests</a></li><li data-type='method'><a href="module-Interests.html#.markContentAsInterested">markContentAsInterested</a></li><li data-type='method'><a href="module-Interests.html#.markContentAsNotInterested">markContentAsNotInterested</a></li><li data-type='method'><a href="module-Interests.html#.removeContentAsInterested">removeContentAsInterested</a></li><li data-type='method'><a href="module-Interests.html#.removeContentAsNotInterested">removeContentAsNotInterested</a></li></ul></li><li><a href="module-Permissions.html">Permissions</a><ul class='methods'><li data-type='method'><a href="module-Permissions.html#.fetchUserPermissions">fetchUserPermissions</a></li><li data-type='method'><a href="module-Permissions.html#.reset">reset</a></li></ul></li><li><a href="module-Playlists.html">Playlists</a><ul class='methods'><li data-type='method'><a href="module-Playlists.html#.addItemToPlaylist">addItemToPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.createPlaylist">createPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.deletePlaylist">deletePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.duplicatePlaylist">duplicatePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.fetchPlaylist">fetchPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.fetchPlaylistItems">fetchPlaylistItems</a></li><li data-type='method'><a href="module-Playlists.html#.fetchUserPlaylists">fetchUserPlaylists</a></li><li data-type='method'><a href="module-Playlists.html#.togglePlaylistPrivate">togglePlaylistPrivate</a></li><li data-type='method'><a href="module-Playlists.html#.undeletePlaylist">undeletePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#.updatePlaylist">updatePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~deleteItemsFromPlaylist">deleteItemsFromPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~likePlaylist">likePlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~reportPlaylist">reportPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~restoreItemFromPlaylist">restoreItemFromPlaylist</a></li><li data-type='method'><a href="module-Playlists.html#~unlikePlaylist">unlikePlaylist</a></li></ul></li><li><a href="module-Railcontent-Services.html">Railcontent-Services</a><ul class='methods'><li data-type='method'><a href="module-Railcontent-Services.html#.assignModeratorToComment">assignModeratorToComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.closeComment">closeComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.createComment">createComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.deleteComment">deleteComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.editComment">editComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchAllCompletedStates">fetchAllCompletedStates</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchCarouselCardData">fetchCarouselCardData</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchComment">fetchComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchCommentRelies">fetchCommentRelies</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchComments">fetchComments</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchCompletedContent">fetchCompletedContent</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchCompletedState">fetchCompletedState</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchContentInProgress">fetchContentInProgress</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchContentPageUserData">fetchContentPageUserData</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchNextContentDataForParent">fetchNextContentDataForParent</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchRecentUserActivities">fetchRecentUserActivities</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchSongsInProgress">fetchSongsInProgress</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchTopComment">fetchTopComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchUserAward">fetchUserAward</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchUserBadges">fetchUserBadges</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchUserPracticeNotes">fetchUserPracticeNotes</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.likeComment">likeComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.openComment">openComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postContentComplete">postContentComplete</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postContentReset">postContentReset</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postContentRestore">postContentRestore</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.replyToComment">replyToComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.reportComment">reportComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.restoreComment">restoreComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.setStudentViewForUser">setStudentViewForUser</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.unassignModeratorToComment">unassignModeratorToComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.unlikeComment">unlikeComment</a></li><li data-type='method'><a href="module-Railcontent-Services.html#~fetchLastInteractedChild">fetchLastInteractedChild</a></li></ul></li><li><a href="module-Sanity-Services.html">Sanity-Services</a><ul class='methods'><li data-type='method'><a href="module-Sanity-Services.html#.fetchAll">fetchAll</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchAllFilterOptions">fetchAllFilterOptions</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchAllPacks">fetchAllPacks</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchArtistLessons">fetchArtistLessons</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchArtists">fetchArtists</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchByRailContentId">fetchByRailContentId</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchByRailContentIds">fetchByRailContentIds</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchByReference">fetchByReference</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchCoachLessons">fetchCoachLessons</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchComingSoon">fetchComingSoon</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchCommentModContentData">fetchCommentModContentData</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchFoundation">fetchFoundation</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchGenreLessons">fetchGenreLessons</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchLeaving">fetchLeaving</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchLessonContent">fetchLessonContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchLessonsFeaturingThisContent">fetchLessonsFeaturingThisContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMetadata">fetchMetadata</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethod">fetchMethod</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethodChildren">fetchMethodChildren</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethodChildrenIds">fetchMethodChildrenIds</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethodPreviousNextLesson">fetchMethodPreviousNextLesson</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchNewReleases">fetchNewReleases</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchNextPreviousLesson">fetchNextPreviousLesson</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchOtherSongVersions">fetchOtherSongVersions</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchPackAll">fetchPackAll</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchPackData">fetchPackData</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchParentForDownload">fetchParentForDownload</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchRelatedLessons">fetchRelatedLessons</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchRelatedRecommendedContent">fetchRelatedRecommendedContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchRelatedSongs">fetchRelatedSongs</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchReturning">fetchReturning</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSanity">fetchSanity</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchScheduledReleases">fetchScheduledReleases</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchShowsData">fetchShowsData</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSiblingContent">fetchSiblingContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSongArtistCount">fetchSongArtistCount</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSongById">fetchSongById</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchUpcomingEvents">fetchUpcomingEvents</a></li><li data-type='method'><a href="module-Sanity-Services.html#.jumpToContinueContent">jumpToContinueContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#~fetchRelatedByLicense">fetchRelatedByLicense</a></li><li data-type='method'><a href="module-Sanity-Services.html#~getQueryFromPage">getQueryFromPage</a></li></ul></li><li><a href="module-Sessions.html">Sessions</a><ul class='methods'><li data-type='method'><a href="module-Sessions.html#.login">login</a></li><li data-type='method'><a href="module-Sessions.html#.logout">logout</a></li></ul></li><li><a href="module-UserActivity.html">UserActivity</a><ul class='methods'><li data-type='method'><a href="module-UserActivity.html#.calculateLongestStreaks">calculateLongestStreaks</a></li><li data-type='method'><a href="module-UserActivity.html#.createPracticeNotes">createPracticeNotes</a></li><li data-type='method'><a href="module-UserActivity.html#.deletePracticeSession">deletePracticeSession</a></li><li data-type='method'><a href="module-UserActivity.html#.deleteUserActivity">deleteUserActivity</a></li><li data-type='method'><a href="module-UserActivity.html#.getPracticeNotes">getPracticeNotes</a></li><li data-type='method'><a href="module-UserActivity.html#.getPracticeSessions">getPracticeSessions</a></li><li data-type='method'><a href="module-UserActivity.html#.getProgressRows">getProgressRows</a></li><li data-type='method'><a href="module-UserActivity.html#.getRecentActivity">getRecentActivity</a></li><li data-type='method'><a href="module-UserActivity.html#.getUserMonthlyStats">getUserMonthlyStats</a></li><li data-type='method'><a href="module-UserActivity.html#.getUserWeeklyStats">getUserWeeklyStats</a></li><li data-type='method'><a href="module-UserActivity.html#.pinProgressRow">pinProgressRow</a></li><li data-type='method'><a href="module-UserActivity.html#.recordUserActivity">recordUserActivity</a></li><li data-type='method'><a href="module-UserActivity.html#.recordUserPractice">recordUserPractice</a></li><li data-type='method'><a href="module-UserActivity.html#.removeUserPractice">removeUserPractice</a></li><li data-type='method'><a href="module-UserActivity.html#.restorePracticeSession">restorePracticeSession</a></li><li data-type='method'><a href="module-UserActivity.html#.restoreUserActivity">restoreUserActivity</a></li><li data-type='method'><a href="module-UserActivity.html#.restoreUserPractice">restoreUserPractice</a></li><li data-type='method'><a href="module-UserActivity.html#.unpinProgressRow">unpinProgressRow</a></li><li data-type='method'><a href="module-UserActivity.html#.updatePracticeNotes">updatePracticeNotes</a></li><li data-type='method'><a href="module-UserActivity.html#.updateUserPractice">updateUserPractice</a></li></ul></li><li><a href="module-UserChat.html">UserChat</a><ul class='methods'><li data-type='method'><a href="module-UserChat.html#.fetchChatSettings">fetchChatSettings</a></li></ul></li><li><a href="module-UserManagement.html">UserManagement</a><ul class='methods'><li data-type='method'><a href="module-UserManagement.html#.blockUser">blockUser</a></li><li data-type='method'><a href="module-UserManagement.html#.blockedUsers">blockedUsers</a></li><li data-type='method'><a href="module-UserManagement.html#.deletePicture">deletePicture</a></li><li data-type='method'><a href="module-UserManagement.html#.getUserData">getUserData</a></li><li data-type='method'><a href="module-UserManagement.html#.isDisplayNameAvailable">isDisplayNameAvailable</a></li><li data-type='method'><a href="module-UserManagement.html#.unblockUser">unblockUser</a></li><li data-type='method'><a href="module-UserManagement.html#.updateDisplayName">updateDisplayName</a></li><li data-type='method'><a href="module-UserManagement.html#.uploadPicture">uploadPicture</a></li><li data-type='method'><a href="module-UserManagement.html#.uploadPictureFromS3">uploadPictureFromS3</a></li></ul></li><li><a href="module-UserNotifications.html">UserNotifications</a><ul class='methods'><li data-type='method'><a href="module-UserNotifications.html#.deleteNotification">deleteNotification</a></li><li data-type='method'><a href="module-UserNotifications.html#.fetchLiveEventPollingState">fetchLiveEventPollingState</a></li><li data-type='method'><a href="module-UserNotifications.html#.fetchNotificationSettings">fetchNotificationSettings</a></li><li data-type='method'><a href="module-UserNotifications.html#.fetchNotifications">fetchNotifications</a></li><li data-type='method'><a href="module-UserNotifications.html#.fetchUnreadCount">fetchUnreadCount</a></li><li data-type='method'><a href="module-UserNotifications.html#.markAllNotificationsAsRead">markAllNotificationsAsRead</a></li><li data-type='method'><a href="module-UserNotifications.html#.markNotificationAsRead">markNotificationAsRead</a></li><li data-type='method'><a href="module-UserNotifications.html#.markNotificationAsUnread">markNotificationAsUnread</a></li><li data-type='method'><a href="module-UserNotifications.html#.pauseLiveEventPolling">pauseLiveEventPolling</a></li><li data-type='method'><a href="module-UserNotifications.html#.restoreNotification">restoreNotification</a></li><li data-type='method'><a href="module-UserNotifications.html#.startLiveEventPolling">startLiveEventPolling</a></li><li data-type='method'><a href="module-UserNotifications.html#.updateNotificationSetting">updateNotificationSetting</a></li></ul></li><li><a href="module-UserProfile.html">UserProfile</a><ul class='methods'><li data-type='method'><a href="module-UserProfile.html#.deleteProfilePicture">deleteProfilePicture</a></li><li data-type='method'><a href="module-UserProfile.html#.otherStats">otherStats</a></li></ul></li></ul><h3>Namespaces</h3><ul><li><a href="ContentOrganization.html">ContentOrganization</a></li><li><a href="Gamification.html">Gamification</a></li><li><a href="UserManagementSystem.html">UserManagementSystem</a></li></ul><h3><a href="global.html">Global</a></h3>
33
33
 
34
34
  </nav>
35
35
 
@@ -56,26 +56,33 @@ import {
56
56
  fetchUserPracticeNotes,
57
57
  fetchHandler,
58
58
  fetchRecentUserActivities,
59
- fetchLastInteractedChild,
60
59
  } from './railcontent'
61
60
  import { DataContext, UserActivityVersionKey } from './dataContext.js'
62
- import { fetchByRailContentIds, fetchShows } from './sanity'
63
- import {fetchPlaylist, fetchUserPlaylists} from "./content-org/playlists"
64
- import {pinnedGuidedCourses} from "./content-org/guided-courses"
65
- import {convertToTimeZone, getMonday, getWeekNumber, isSameDate, isNextDay, getTimeRemainingUntilLocal, toDayjs} from './dateUtils.js'
61
+ import { fetchByRailContentId, fetchByRailContentIds, fetchShows } from './sanity'
62
+ import { fetchPlaylist, fetchUserPlaylists } from './content-org/playlists'
63
+ import { pinnedGuidedCourses } from './content-org/guided-courses'
64
+ import {
65
+ getMonday,
66
+ getWeekNumber,
67
+ isSameDate,
68
+ isNextDay,
69
+ getTimeRemainingUntilLocal,
70
+ toDayjs,
71
+ } from './dateUtils.js'
66
72
  import { globalConfig } from './config'
67
- import {collectionLessonTypes, lessonTypesMapping, progressTypesMapping, recentTypes, showsLessonTypes, songs} from "../contentTypeConfig";
68
73
  import {
69
- getAllStartedOrCompleted,
70
- getProgressPercentageByIds,
71
- getProgressStateByIds,
72
- getResumeTimeSecondsByIds
73
- } from "./contentProgress";
74
- import {TabResponseType} from "../contentMetaData";
75
- import {isContentLikedByIds} from "./contentLikes.js";
74
+ collectionLessonTypes,
75
+ progressTypesMapping,
76
+ recentTypes,
77
+ showsLessonTypes,
78
+ songs,
79
+ } from '../contentTypeConfig'
80
+ import { getAllStartedOrCompleted, getProgressStateByIds } from './contentProgress'
81
+ import { TabResponseType } from '../contentMetaData'
76
82
  import dayjs from 'dayjs'
77
83
  import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
78
84
  import weekOfYear from 'dayjs/plugin/weekOfYear'
85
+ import { addContextToContent } from './contentAggregator.js'
79
86
 
80
87
  const DATA_KEY_PRACTICES = 'practices'
81
88
  const DATA_KEY_LAST_UPDATED_TIME = 'u'
@@ -135,7 +142,7 @@ export let userActivityContext = new DataContext(UserActivityVersionKey, fetchUs
135
142
  * .catch(error => console.error(error));
136
143
  */
137
144
  export async function getUserWeeklyStats() {
138
- const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
145
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
139
146
  let data = await userActivityContext.getData()
140
147
  let practices = data?.[DATA_KEY_PRACTICES] ?? {}
141
148
  let sortedPracticeDays = Object.keys(practices)
@@ -146,10 +153,19 @@ export async function getUserWeeklyStats() {
146
153
  let dailyStats = []
147
154
  for (let i = 0; i &lt; 7; i++) {
148
155
  const day = startOfWeek.add(i, 'day')
149
- let hasPractice = sortedPracticeDays.some((practiceDate) => isSameDate(practiceDate, day.format('YYYY-MM-DD')))
150
- let isActive = isSameDate(today, day)
156
+ let hasPractice = sortedPracticeDays.some((practiceDate) =>
157
+ isSameDate(practiceDate, day.format('YYYY-MM-DD'))
158
+ )
159
+ let isActive = isSameDate(today.format(), day.format())
151
160
  let type = hasPractice ? 'tracked' : isActive ? 'active' : 'none'
152
- dailyStats.push({ key: i, label: DAYS[i], isActive, inStreak: hasPractice, type, day: day.format('YYYY-MM-DD') })
161
+ dailyStats.push({
162
+ key: i,
163
+ label: DAYS[i],
164
+ isActive,
165
+ inStreak: hasPractice,
166
+ type,
167
+ day: day.format('YYYY-MM-DD'),
168
+ })
153
169
  }
154
170
 
155
171
  let { streakMessage } = getStreaksAndMessage(practices)
@@ -216,7 +232,7 @@ export async function getUserMonthlyStats(params = {}) {
216
232
  }
217
233
  }
218
234
 
219
- // let endOfMonth = new Date(year, month + 1, 0)
235
+ // let endOfMonth = new Date(year, month + 1, 0)
220
236
  let endOfGrid = endOfMonth.clone()
221
237
  while (endOfGrid.day() !== 0) {
222
238
  endOfGrid = endOfGrid.add(1, 'day')
@@ -268,7 +284,7 @@ export async function getUserMonthlyStats(params = {}) {
268
284
 
269
285
  // Filter past practices only
270
286
  let filteredPractices = Object.entries(practices)
271
- .filter(([date]) => dayjs.tz(date, timeZone).isSameOrBefore(endOfMonth))
287
+ .filter(([date]) => dayjs(date).isSameOrBefore(endOfMonth))
272
288
  .reduce((acc, [date, val]) => {
273
289
  acc[date] = val
274
290
  return acc
@@ -322,6 +338,7 @@ export async function getUserMonthlyStats(params = {}) {
322
338
  */
323
339
  export async function recordUserPractice(practiceDetails) {
324
340
  practiceDetails.auto = 0
341
+ practiceDetails.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
325
342
  if (practiceDetails.content_id) {
326
343
  practiceDetails.auto = 1
327
344
  }
@@ -437,13 +454,11 @@ export async function restoreUserPractice(id) {
437
454
  const restoredPractice = response.data.find((p) => p.id === id)
438
455
  if (restoredPractice) {
439
456
  await userActivityContext.updateLocal(async function (localContext) {
440
- const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
441
- const date = convertToTimeZone(restoredPractice.day, userTimeZone)
442
- if (localContext.data[DATA_KEY_PRACTICES][date]) {
443
- localContext.data[DATA_KEY_PRACTICES][date] = []
457
+ if (!localContext.data[DATA_KEY_PRACTICES][restoredPractice.day]) {
458
+ localContext.data[DATA_KEY_PRACTICES][restoredPractice.day] = []
444
459
  }
445
460
  response.data.forEach((restoredPractice) => {
446
- localContext.data[DATA_KEY_PRACTICES][date].push({
461
+ localContext.data[DATA_KEY_PRACTICES][restoredPractice.day].push({
447
462
  id: restoredPractice.id,
448
463
  duration_seconds: restoredPractice.duration_seconds,
449
464
  })
@@ -613,7 +628,21 @@ export async function getPracticeNotes(day) {
613
628
  * .catch(error => console.error("Failed to get recent activity:", error));
614
629
  */
615
630
  export async function getRecentActivity({ page = 1, limit = 5, tabName = null } = {}) {
616
- return await fetchRecentUserActivities({ page, limit, tabName })
631
+ const recentActivityData = await fetchRecentUserActivities({ page, limit, tabName })
632
+ const contentIds = recentActivityData.data.map((p) => p.contentId).filter((id) => id !== null)
633
+ const contents = await addContextToContent(fetchByRailContentIds, contentIds, {
634
+ addNavigateTo: true,
635
+ addNextLesson: true,
636
+ })
637
+ recentActivityData.data = recentActivityData.data.map((practice) => {
638
+ const content = contents?.find((c) => c.id === practice.contentId) || {}
639
+ return {
640
+ ...practice,
641
+ parent_id: content.parent_id || null,
642
+ navigateTo: content.navigateTo,
643
+ }
644
+ })
645
+ return recentActivityData
617
646
  }
618
647
 
619
648
  /**
@@ -692,7 +721,7 @@ function calculateStreaks(practices, includeStreakMessage = false) {
692
721
  let lastActiveDay = null
693
722
  let streakMessage = ''
694
723
 
695
- const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
724
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
696
725
  let sortedPracticeDays = Object.keys(practices)
697
726
  .map((dateStr) => {
698
727
  const [year, month, day] = dateStr.split('-').map(Number)
@@ -871,14 +900,15 @@ export async function calculateLongestStreaks(userId = globalConfig.sessionConfi
871
900
  }
872
901
  }
873
902
 
874
- async function formatPracticeMeta(practices) {
903
+ async function formatPracticeMeta(practices = []) {
875
904
  const contentIds = practices.map((p) => p.content_id).filter((id) => id !== null)
876
- const contents = await fetchByRailContentIds(contentIds)
877
-
878
- const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
905
+ const contents = await addContextToContent(fetchByRailContentIds, contentIds, {
906
+ addNavigateTo: true,
907
+ addNextLesson: true,
908
+ })
879
909
 
880
910
  return practices.map((practice) => {
881
- const content = contents.find((c) => c.id === practice.content_id) || {}
911
+ const content = contents &amp;&amp; contents.length > 0 ? contents.find((c) => c.id === practice.content_id) : {}
882
912
 
883
913
  return {
884
914
  id: practice.id,
@@ -887,21 +917,22 @@ async function formatPracticeMeta(practices) {
887
917
  thumbnail_url: practice.content_id ? content.thumbnail : practice.thumbnail_url || '',
888
918
  duration: practice.duration_seconds || 0,
889
919
  duration_seconds: practice.duration_seconds || 0,
890
- content_url: content.url || null,
920
+ content_url: content?.url || null,
891
921
  title: practice.content_id ? content.title : practice.title,
892
922
  category_id: practice.category_id,
893
923
  instrument_id: practice.instrument_id,
894
- content_type: getFormattedType(content.type || '', content.brand),
924
+ content_type: getFormattedType(content?.type || '', content?.brand || null),
895
925
  content_id: practice.content_id || null,
896
- content_brand: content.brand || null,
897
- created_at: convertToTimeZone(dayjs(practice.created_at), userTimeZone),
898
- sanity_type: content.type || null,
899
- content_slug: content.slug || null,
926
+ content_brand: content?.brand || null,
927
+ created_at: dayjs(practice.created_at),
928
+ sanity_type: content?.type || null,
929
+ content_slug: content?.slug || null,
930
+ parent_id: content?.parent_id || null,
931
+ navigateTo: content?.navigateTo || null,
900
932
  }
901
933
  })
902
934
  }
903
935
 
904
-
905
936
  /**
906
937
  * Records a new user activity in the system.
907
938
  *
@@ -945,6 +976,112 @@ export async function deleteUserActivity(id) {
945
976
  const url = `/api/user-management-system/v1/activities/${id}`
946
977
  return await fetchHandler(url, 'DELETE')
947
978
  }
979
+
980
+ /**
981
+ * Restores a specific user activity by its ID.
982
+ *
983
+ * @param {number|string} id - The ID of the user activity to restore.
984
+ * @returns {Promise&lt;Object>} - A promise that resolves to the API response after restoration.
985
+ *
986
+ * @example
987
+ * restoreUserActivity(789)
988
+ * .then(response => console.log('Restored:', response))
989
+ * .catch(error => console.error(error));
990
+ */
991
+ export async function restoreUserActivity(id) {
992
+ const url = `/api/user-management-system/v1/activities/${id}`
993
+ return await fetchHandler(url, 'POST')
994
+ }
995
+
996
+ async function extractPinnedItemsAndSortAllItems(
997
+ userPinnedItem,
998
+ contentsMap,
999
+ eligiblePlaylistItems,
1000
+ pinnedGuidedCourse,
1001
+ limit
1002
+ ) {
1003
+ let pinnedItem = await popPinnedItemFromContentsOrPlaylistMap(
1004
+ userPinnedItem,
1005
+ contentsMap,
1006
+ eligiblePlaylistItems
1007
+ )
1008
+
1009
+ const guidedCourseID = pinnedGuidedCourse?.content_id
1010
+ let combined = []
1011
+ if (pinnedGuidedCourse) {
1012
+ const guidedCourseContent =
1013
+ contentsMap.get(guidedCourseID) ??
1014
+ (await addContextToContent(fetchByRailContentId, guidedCourseID, 'guided-course', {
1015
+ addNextLesson: true,
1016
+ addNavigateTo: true,
1017
+ addProgressStatus: true,
1018
+ addProgressPercentage: true,
1019
+ addProgressTimestamp: true,
1020
+ }))
1021
+ contentsMap = popContentAndRemoveChildrenFromContentsMap(guidedCourseContent, contentsMap)
1022
+ guidedCourseContent.pinned = true
1023
+ combined.push(guidedCourseContent)
1024
+ }
1025
+ if (pinnedItem) {
1026
+ pinnedItem.pinned = true
1027
+ combined.push(pinnedItem)
1028
+ }
1029
+
1030
+ const progressList = Array.from(contentsMap.values())
1031
+ combined = [...combined, ...progressList, ...eligiblePlaylistItems]
1032
+ return mergeAndSortItems(combined, limit)
1033
+ }
1034
+
1035
+ function generateContentsMap(contents, playlistsContents) {
1036
+ const excludedTypes = new Set([
1037
+ 'pack-bundle',
1038
+ 'learning-path-course',
1039
+ 'learning-path-level',
1040
+ 'guided-course-part',
1041
+ ])
1042
+ const existingShows = new Set()
1043
+ const contentsMap = new Map()
1044
+ const childToParentMap = {}
1045
+ contents.forEach((content) => {
1046
+ if (Array.isArray(content.parent_content_data) &amp;&amp; content.parent_content_data.length > 0) {
1047
+ childToParentMap[content.id] =
1048
+ content.parent_content_data[content.parent_content_data.length - 1]
1049
+ }
1050
+ })
1051
+
1052
+ const allRecentTypeSet = new Set(Object.values(recentTypes).flat())
1053
+ contents.forEach((content) => {
1054
+ const id = content.id
1055
+ const type = content.type
1056
+ if (
1057
+ excludedTypes.has(type) ||
1058
+ (!allRecentTypeSet.has(type) &amp;&amp; !showsLessonTypes.includes(type))
1059
+ )
1060
+ return
1061
+ if (!childToParentMap[id]) {
1062
+ // Shows don't have a parent to link them, but need to be handled as if they're a set of children
1063
+ if (!existingShows.has(type)) {
1064
+ contentsMap.set(id, content)
1065
+ }
1066
+ if (showsLessonTypes.includes(type)) {
1067
+ existingShows.add(type)
1068
+ }
1069
+ }
1070
+ })
1071
+
1072
+ // TODO this doesn't work for guided courses as the GC card takes precedence over the playlist card
1073
+ // https://musora.atlassian.net/browse/BEH-812
1074
+ if (playlistsContents) {
1075
+ for (const item of playlistsContents) {
1076
+ const contentId = item.id
1077
+ contentsMap.delete(contentId)
1078
+ const parentIds = item.parent_content_data || []
1079
+ parentIds.forEach((id) => contentsMap.delete(id))
1080
+ }
1081
+ }
1082
+ return contentsMap
1083
+ }
1084
+
948
1085
  /**
949
1086
  * Fetches and combines recent user progress rows and playlists, excluding certain types and parents.
950
1087
  *
@@ -959,425 +1096,357 @@ export async function deleteUserActivity(id) {
959
1096
  * .catch(error => console.error(error));
960
1097
  */
961
1098
  export async function getProgressRows({ brand = null, limit = 8 } = {}) {
962
- const excludedTypes = new Set([
963
- 'pack-bundle',
964
- 'learning-path-course',
965
- 'learning-path-level'
966
- ]);
967
1099
  // TODO slice progress to a reasonable number, say 100
968
- const [recentPlaylists, progressContents, allPinnedGuidedCourse, userPinnedItem ] = await Promise.all([
969
- fetchUserPlaylists(brand, { sort: '-last_progress', limit: limit}),
970
- getAllStartedOrCompleted({onlyIds: false, brand: brand }),
971
- pinnedGuidedCourses(brand),
972
- getUserPinnedItem(brand),
973
- ])
974
-
1100
+ const [recentPlaylists, progressContents, allPinnedGuidedCourse, userPinnedItem] =
1101
+ await Promise.all([
1102
+ fetchUserPlaylists(brand, { sort: '-last_progress', limit: limit }),
1103
+ getAllStartedOrCompleted({ onlyIds: false, brand: brand }),
1104
+ pinnedGuidedCourses(brand),
1105
+ getUserPinnedItem(brand),
1106
+ ])
975
1107
  let pinnedGuidedCourse = allPinnedGuidedCourse?.[0] ?? null
976
1108
 
977
- const playlists = recentPlaylists?.data || [];
978
- const eligiblePlaylistItems = await getEligiblePlaylistItems(playlists);
979
- const playlistEngagedOnContents = eligiblePlaylistItems.map(item => item.last_engaged_on);
1109
+ const playlists = recentPlaylists?.data || []
1110
+ const eligiblePlaylistItems = await getEligiblePlaylistItems(playlists)
1111
+ const playlistEngagedOnContents = eligiblePlaylistItems.map(
1112
+ (item) => item.playlist.last_engaged_on
1113
+ )
980
1114
 
981
1115
  const nonPlaylistContentIds = Object.keys(progressContents)
982
1116
  if (pinnedGuidedCourse) {
983
- nonPlaylistContentIds.push(pinnedGuidedCourse.content_id);
1117
+ nonPlaylistContentIds.push(pinnedGuidedCourse.content_id)
984
1118
  }
985
1119
  if (userPinnedItem?.progressType === 'content') {
986
1120
  nonPlaylistContentIds.push(userPinnedItem.id)
987
1121
  }
988
- const [ playlistsContents, contents ] = await Promise.all([
989
- fetchByRailContentIds(playlistEngagedOnContents, 'progress-tracker'),
990
- fetchByRailContentIds(nonPlaylistContentIds, 'progress-tracker', brand),
991
- ]);
992
-
993
- const excludedParents = new Set();
994
- const existingShows = new Set();
995
- // TODO this doesn't work for guided courses as the GC card takes precedence over the playlist card
996
- // https://musora.atlassian.net/browse/BEH-812
997
- for (const item of playlistsContents) {
998
-
999
- const contentId = item.id ?? item.railcontent_id;
1000
- delete progressContents[contentId]
1001
- const parentIds = item.parent_content_data || [];
1002
- parentIds.forEach(id => delete progressContents[id] );
1003
- }
1004
-
1005
-
1006
- const contentsMap = {};
1007
- contents.forEach(content => {
1008
- contentsMap[content.railcontent_id] = content;
1009
- });
1010
- const childToParentMap = {};
1011
- Object.values(contentsMap).forEach(content => {
1012
- if (Array.isArray(content.parent_content_data) &amp;&amp; content.parent_content_data.length > 0) {
1013
- childToParentMap[content.id] = content.parent_content_data[content.parent_content_data.length - 1];
1014
- }
1015
- });
1016
-
1017
- const allRecentTypeSet = new Set(
1018
- Object.values(recentTypes).flat()
1019
- )
1020
- const progressMap = new Map();
1021
- for (const [idStr, progress] of Object.entries(progressContents)) {
1022
- const id = parseInt(idStr);
1023
- const content = contentsMap[id];
1024
- if (!content || excludedTypes.has(content.type) || !allRecentTypeSet.has(content.type) ) continue;
1025
- const parentId = childToParentMap[id];
1026
- // Handle children with parents
1027
- if (parentId) {
1028
- const parentContent = contentsMap[parentId];
1029
- if (!parentContent || excludedTypes.has(parentContent.type)) continue;
1030
- const existing = progressMap.get(parentId);
1031
- if (existing) {
1032
- // If childIndex isn't already set, set it
1033
- if (existing.childIndex === undefined) {
1034
- existing.childIndex = id;
1035
- }
1036
- } else {
1037
- progressMap.set(parentId, {
1038
- id: parentId,
1039
- raw: parentContent,
1040
- state: progress.status,
1041
- percent: progress.progress,
1042
- progressTimestamp: progress.last_update * 1000,
1043
- childIndex: id
1044
- });
1045
- }
1046
- continue;
1047
- }
1048
- // Handle standalone parents
1049
- if (!progressMap.has(id)) {
1050
- if(!existingShows.has(content.type)){
1051
- progressMap.set(id, {
1052
- id,
1053
- raw: content,
1054
- state: progress.status,
1055
- percent: progress.progress,
1056
- progressTimestamp: progress.last_update * 1000
1057
- });
1058
- }
1059
- if(showsLessonTypes.includes(content.type)) {
1060
- existingShows.add(content.type)
1061
- }
1062
- }
1063
- }
1064
- const pinnedItem = userPinnedItem ? await extractPinnedItem(
1122
+ const [playlistsContents, contents] = await Promise.all([
1123
+ addContextToContent(fetchByRailContentIds, playlistEngagedOnContents, 'progress-tracker', {
1124
+ addNextLesson: true,
1125
+ addNavigateTo: true,
1126
+ addProgressStatus: true,
1127
+ addProgressPercentage: true,
1128
+ addProgressTimestamp: true,
1129
+ }),
1130
+ addContextToContent(fetchByRailContentIds, nonPlaylistContentIds, 'progress-tracker', brand, {
1131
+ addNextLesson: true,
1132
+ addNavigateTo: true,
1133
+ addProgressStatus: true,
1134
+ addProgressPercentage: true,
1135
+ addProgressTimestamp: true,
1136
+ }),
1137
+ ])
1138
+ const contentsMap = generateContentsMap(contents, playlistsContents)
1139
+ let combined = await extractPinnedItemsAndSortAllItems(
1065
1140
  userPinnedItem,
1066
- progressMap,
1141
+ contentsMap,
1067
1142
  eligiblePlaylistItems,
1068
- ) : null
1069
-
1070
- const pinnedId = pinnedItem?.id
1071
- const guidedCourseID = pinnedGuidedCourse?.content_id
1072
- let combined = [];
1073
- if (pinnedGuidedCourse) {
1074
- const guidedCourseContent = contentsMap[guidedCourseID]
1075
- if (guidedCourseContent) {
1076
- const temp = await extractPinnedGuidedCourseItem(guidedCourseContent, progressMap)
1077
- temp.pinned = true
1078
- combined.push(temp)
1079
- }
1080
- }
1081
- if (pinnedItem) {
1082
- pinnedItem.pinned = true
1083
- combined.push(pinnedItem)
1084
- }
1085
- const progressList = Array.from(progressMap.values())
1086
-
1087
- const filteredProgressList = pinnedId
1088
- ? progressList.filter(item => !(item.id === pinnedId || item.id === guidedCourseID))
1089
- : progressList;
1090
- const filteredPlaylists = pinnedId
1091
- ? (eligiblePlaylistItems.filter(item => !(item.id === pinnedId || item.id === guidedCourseID)))
1092
- : eligiblePlaylistItems;
1093
-
1094
- combined = [...combined, ...filteredProgressList, ...filteredPlaylists]
1095
- const finalCombined = mergeAndSortItems(combined, limit)
1143
+ pinnedGuidedCourse,
1144
+ limit
1145
+ )
1096
1146
  const results = await Promise.all(
1097
- finalCombined.slice(0, limit).map(item =>
1098
- item.type === 'playlist'
1099
- ? processPlaylistItem(item)
1100
- : processContentItem(item)
1101
- )
1102
- );
1103
-
1147
+ combined
1148
+ .slice(0, limit)
1149
+ .map((item) =>
1150
+ item.type === 'playlist' ? processPlaylistItem(item) : processContentItem(item)
1151
+ )
1152
+ )
1153
+ console.log('HomePageProgressRows results: remove before merge', results)
1104
1154
  return {
1105
1155
  type: TabResponseType.PROGRESS_ROWS,
1106
1156
  displayBrowseAll: combined.length > limit,
1107
- data: results
1108
- };
1157
+ data: results,
1158
+ }
1109
1159
  }
1110
1160
 
1111
1161
  async function getUserPinnedItem(brand) {
1112
- const userRaw = await globalConfig.localStorage.getItem('user');
1113
- const user = userRaw ? JSON.parse(userRaw) : {};
1162
+ const userRaw = await globalConfig.localStorage.getItem('user')
1163
+ const user = userRaw ? JSON.parse(userRaw) : {}
1114
1164
  user.brand_pinned_progress = user.brand_pinned_progress || {}
1115
1165
  return user.brand_pinned_progress[brand] ?? null
1116
1166
  }
1117
1167
 
1118
- async function processContentItem(item) {
1119
- let data = item.raw;
1120
- const contentType = getFormattedType(data.type, data.brand);
1121
- const status = item.state;
1122
- const isLive = data.isLive ?? false
1123
- let ctaText = 'Continue';
1124
- if (contentType === 'transcription' || contentType === 'play-along' || contentType === 'jam-track') ctaText = 'Replay Song';
1125
- if (contentType === 'lesson') ctaText = status === 'completed' ? 'Revisit Lesson' : 'Continue';
1126
- if ((contentType === 'song-tutorial' || collectionLessonTypes.includes(contentType)) &amp;&amp; status === 'completed') ctaText = 'Revisit Lessons' ;
1127
- if (contentType === 'pack' &amp;&amp; status === 'completed') {
1128
- ctaText = 'View Lessons';
1129
- }
1130
-
1131
- if (data.lesson_count > 0) {
1132
- const lessonIds = extractLessonIds(item);
1133
- const progressOnItems = await getProgressStateByIds(lessonIds);
1134
- let completedCount = Object.values(progressOnItems).filter(value => value === 'completed').length;
1135
- data.completed_children = completedCount;
1136
-
1137
- if (item.childIndex) {
1138
- let nextId = item.childIndex
1139
- const nextByProgress = findIncompleteLesson(progressOnItems, item.childIndex, item.raw.type)
1140
- nextId = nextByProgress ? nextByProgress : nextId
1141
-
1142
- const nestedLessons = data.lessons
1143
- .filter(item => Array.isArray(item.lessons))
1144
- .flatMap(parent =>
1145
- parent.lessons.map(lesson => ({
1146
- ...lesson,
1147
- parent: {
1148
- id: parent.id,
1149
- slug: parent.slug,
1150
- title: parent.title,
1151
- type: parent.type
1152
- }
1153
- }))
1154
- );
1155
-
1156
- const lessons = (nestedLessons.length === 0) ? data.lessons : nestedLessons
1157
- const nextLesson = lessons.find(lesson => lesson.id === nextId)
1158
- data.first_incomplete_child = nextLesson?.parent ?? nextLesson
1159
- data.second_incomplete_child = (nextLesson?.parent) ? nextLesson : null
1160
- if(data.type === 'guided-course'){
1161
- let isLocked = new Date(nextLesson.published_on) > new Date()
1162
- data.thumbnail = nextLesson.thumbnail
1163
- // USHP-4 completed
1164
- if (status === 'completed') {
1165
- // duplicated code to above, but here for clarity
1166
- ctaText = 'Revisit Lessons'
1167
- // USHP-1 if lesson locked show unlock in X time
1168
- } else if (isLocked) {
1169
- data.is_locked = true
1170
- const timeRemaining = getTimeRemainingUntilLocal(nextLesson.published_on, {withTotalSeconds: true})
1171
- data.time_remaining_seconds = timeRemaining.totalSeconds
1172
- ctaText = 'Next lesson in ' + timeRemaining.formatted
1173
- }
1174
- // USHP-2 start course if not started
1175
- else if (status === 'not-started') {
1176
- ctaText = "Start Course"
1177
- }
1178
- // USHP-3 in progress for lesson
1179
- else {
1180
- ctaText = "Continue"
1181
- }
1182
- }
1168
+ async function processContentItem(content) {
1169
+ const contentType = getFormattedType(content.type, content.brand)
1170
+ const isLive = content.isLive ?? false
1171
+ let ctaText = getDefaultCTATextForContent(content, contentType)
1172
+
1173
+ content.completed_children = await getCompletedChildren(content, contentType)
1174
+
1175
+ if (content.type === 'guided-course') {
1176
+ const nextLessonPublishedOn = content.children.find(
1177
+ (child) => child.id === content.navigateTo.id
1178
+ )?.published_on
1179
+ let isLocked = new Date(nextLessonPublishedOn) > new Date()
1180
+ if (isLocked) {
1181
+ content.is_locked = true
1182
+ const timeRemaining = getTimeRemainingUntilLocal(nextLessonPublishedOn, {
1183
+ withTotalSeconds: true,
1184
+ })
1185
+ content.time_remaining_seconds = timeRemaining.totalSeconds
1186
+ ctaText = 'Next lesson in ' + timeRemaining.formatted
1187
+ } else if (!content.progressStatus || content.progressStatus === 'not-started') {
1188
+ ctaText = 'Start Course'
1183
1189
  }
1184
1190
  }
1185
1191
 
1186
- if(contentType == 'show'){
1187
- const shows = await fetchShows(data.brand, data.type)
1188
- const showIds = shows.map(item => item.id);
1189
- const progressOnItems = await getProgressStateByIds(showIds);
1190
- const completedCount = Object.values(progressOnItems).filter(value => value === 'completed').length;
1191
- if(status == 'completed') {
1192
- const nextByProgress = findIncompleteLesson(progressOnItems, data.id, data.type);
1193
- data = shows.find(lesson => lesson.id === nextByProgress);
1192
+ if (contentType === 'show') {
1193
+ const shows = await fetchShows(content.brand, content.type)
1194
+ const showIds = shows.map((item) => item.id)
1195
+ const progressOnItems = await getProgressStateByIds(showIds)
1196
+ const completedShows = content.completed_children
1197
+ const progressTimestamp = content.progressTimestamp
1198
+ const wasPinned = content.pinned ?? false
1199
+ if (content.progressStatus === 'completed') {
1200
+ // this could be handled more gracefully if their was a parent content type for shows
1201
+ const nextByProgress = findIncompleteLesson(progressOnItems, content.id, content.type)
1202
+ content = shows.find((lesson) => lesson.id === nextByProgress)
1203
+ content.completed_children = completedShows
1204
+ content.progressTimestamp = progressTimestamp
1205
+ content.pinned = wasPinned
1194
1206
  }
1195
- data.completed_children = completedCount;
1196
- data.child_count = shows.length;
1197
- item.percent = Math.round((completedCount / shows.length) * 100);
1198
- if(completedCount === shows.length) {
1199
- ctaText = 'Revisit Lessons';
1207
+ content.child_count = shows.length
1208
+ content.progressPercentage = Math.round((completedShows / shows.length) * 100)
1209
+ if (completedShows === shows.length) {
1210
+ ctaText = 'Revisit Show'
1200
1211
  }
1201
1212
  }
1202
1213
 
1203
1214
  return {
1204
- id: item.id,
1215
+ id: content.id,
1205
1216
  progressType: 'content',
1206
1217
  header: contentType,
1207
- pinned: item.pinned ?? false,
1218
+ pinned: content.pinned ?? false,
1219
+ content: content,
1208
1220
  body: {
1209
- progressPercent: isLive ? undefined: item.percent,
1210
- thumbnail: data.thumbnail,
1211
- title: data.title,
1221
+ progressPercent: isLive ? undefined : content.progressPercentage,
1222
+ thumbnail: content.thumbnail,
1223
+ title: content.title,
1212
1224
  isLive: isLive,
1213
- badge: data.badge ?? null,
1214
- isLocked: data.is_locked ?? false,
1215
- subtitle: !data.child_count || data.lesson_count === 1
1216
- ? (contentType === 'lesson' &amp;&amp; isLive === false) ? `${item.percent}% Complete`: `${data.difficulty_string} ${data.artist_name}`
1217
- : `${data.completed_children} of ${data.lesson_count ?? data.child_count} Lessons Complete`
1225
+ badge: content.badge ?? null,
1226
+ isLocked: content.is_locked ?? false,
1227
+ subtitle: collectionLessonTypes.includes(content.type) || content.lesson_count > 1
1228
+ ? `${content.completed_children} of ${content.lesson_count ?? content.child_count} Lessons Complete`
1229
+ : (contentType === 'lesson' &amp;&amp; isLive === false) ? `${content.progressPercentage}% Complete`: `${content.difficulty_string} ${content.artist_name}`
1218
1230
  },
1219
- cta: {
1220
- text: ctaText,
1221
- timeRemainingToUnlockSeconds: data.time_remaining_seconds ?? null,
1231
+ cta: {
1232
+ text: ctaText,
1233
+ timeRemainingToUnlockSeconds: content.time_remaining_seconds ?? null,
1222
1234
  action: {
1223
- type: data.type,
1224
- brand: data.brand,
1225
- id: data.id,
1226
- slug: data.slug,
1227
- child: data.first_incomplete_child
1228
- ? {
1229
- id: data.first_incomplete_child.id,
1230
- type: data.first_incomplete_child.type,
1231
- brand: data.first_incomplete_child.brand,
1232
- slug: data.first_incomplete_child.slug,
1233
- child: data.second_incomplete_child
1234
- ? {
1235
- id: data.second_incomplete_child.id,
1236
- type: data.second_incomplete_child.type,
1237
- brand: data.second_incomplete_child.brand,
1238
- slug: data.second_incomplete_child.slug
1239
- }
1240
- : null
1241
- }
1242
- : null
1243
- }
1235
+ type: content.type,
1236
+ brand: content.brand,
1237
+ id: content.id,
1238
+ slug: content.slug,
1239
+ child: content.navigateTo,
1240
+ },
1244
1241
  },
1245
- progressTimestamp: item.progressTimestamp
1246
- };
1242
+ // *1000 is to match playlists which are saved in millisecond accuracy
1243
+ progressTimestamp: content.progressTimestamp * 1000,
1244
+ }
1247
1245
  }
1248
1246
 
1249
- async function processPlaylistItem(item) {
1250
- const playlist = item.raw;
1251
- const progressOnItems = await getProgressStateByIds(playlist.items.map(a => a.content_id));
1252
- const allItemsCompleted = item.raw.items.every(i => {
1253
- const itemId = i.content_id;
1254
- const progress = progressOnItems[itemId];
1255
- return progress &amp;&amp; progress === 'completed';
1256
- });
1257
- let nextItem = playlist.items[0] ?? null;
1258
- if (!allItemsCompleted) {
1259
- const lastItemProgress = progressOnItems[playlist.last_engaged_on];
1260
- const index = playlist.items.findIndex(i => i.content_id === playlist.last_engaged_on);
1261
- if (lastItemProgress === 'completed') {
1262
- nextItem = playlist.items[index + 1] ?? nextItem;
1263
- } else {
1264
- nextItem = playlist.items[index] ?? nextItem;
1265
- }
1247
+ function getDefaultCTATextForContent(content, contentType) {
1248
+ let ctaText = 'Continue'
1249
+ if (content.progressStatus === 'completed') {
1250
+ if (
1251
+ contentType === songs[content.brand] ||
1252
+ contentType === 'play along' ||
1253
+ contentType === 'jam track'
1254
+ )
1255
+ ctaText = 'Replay Song'
1256
+ if (contentType === 'lesson') ctaText = 'Revisit Lesson'
1257
+ if (contentType === 'song tutorial' || collectionLessonTypes.includes(contentType))
1258
+ ctaText = 'Revisit Lessons'
1259
+ if (contentType === 'pack') ctaText = 'View Lessons'
1260
+ }
1261
+ return ctaText
1262
+ }
1263
+
1264
+ async function getCompletedChildren(content, contentType) {
1265
+ let completedChildren = null
1266
+ if (contentType === 'show') {
1267
+ const shows = await addContextToContent(fetchShows, content.brand, content.type, {
1268
+ addProgressStatus: true,
1269
+ })
1270
+ completedChildren = Object.values(shows).filter(
1271
+ (show) => show.progressStatus === 'completed'
1272
+ ).length
1273
+ } else if (content.lesson_count > 0) {
1274
+ const lessonIds = getLeafNodes(content)
1275
+ const progressOnItems = await getProgressStateByIds(lessonIds)
1276
+ completedChildren = Object.values(progressOnItems).filter(
1277
+ (value) => value === 'completed'
1278
+ ).length
1266
1279
  }
1280
+ return completedChildren
1281
+ }
1267
1282
 
1283
+ async function processPlaylistItem(item) {
1284
+ const playlist = item.playlist
1268
1285
  return {
1269
- id: playlist.id,
1270
- progressType: 'playlist',
1271
- header: 'playlist',
1272
- pinned: item.pinned ?? false,
1273
- body: {
1286
+ id: playlist.id,
1287
+ progressType: 'playlist',
1288
+ header: 'playlist',
1289
+ pinned: item.pinned ?? false,
1290
+ playlist: playlist,
1291
+ body: {
1274
1292
  first_items_thumbnail_url: playlist.first_items_thumbnail_url,
1275
- title: playlist.name,
1276
- subtitle: `${playlist.duration_formated} • ${playlist.total_items} items • ${playlist.likes} likes • ${playlist.user.display_name}`,
1277
- total_items: playlist.total_items,
1293
+ title: playlist.name,
1294
+ subtitle: `${playlist.duration_formated} • ${playlist.total_items} items • ${playlist.likes} likes • ${playlist.user.display_name}`,
1295
+ total_items: playlist.total_items,
1278
1296
  },
1279
1297
  progressTimestamp: item.progressTimestamp,
1280
- cta: {
1281
- text: 'Continue',
1298
+ cta: {
1299
+ text: 'Continue',
1282
1300
  action: {
1283
- brand: playlist.brand,
1284
- id: playlist.id,
1285
- itemId: nextItem.id,
1286
- lastEngagedOn: playlist.last_engaged_on,
1287
- lastEngagedOnItem: playlist.last_engaged_on_item,
1288
- type: 'playlists',
1289
- }
1290
- }
1301
+ brand: playlist.brand,
1302
+ item_id: playlist.navigateTo.id ?? null,
1303
+ content_id: playlist.navigateTo.content_id ?? null,
1304
+ type: 'playlists',
1305
+ // TODO depreciated, maintained to avoid breaking changes
1306
+ id: playlist.id,
1307
+ },
1308
+ },
1291
1309
  }
1292
1310
  }
1293
1311
 
1294
1312
  const getFormattedType = (type, brand) => {
1295
1313
  for (const [key, values] of Object.entries(progressTypesMapping)) {
1296
1314
  if (values.includes(type)) {
1297
- return key === 'songs' ? songs[brand] : key;
1315
+ return key === 'songs' ? songs[brand] : key
1298
1316
  }
1299
1317
  }
1300
1318
 
1301
- return null;
1302
- };
1303
-
1304
- function extractLessonIds(data) {
1305
- const ids = [];
1306
- function traverse(lessons) {
1307
- for (const item of lessons) {
1308
- if (item.lessons) {
1309
- traverse(item.lessons); // Recursively handle nested lessons
1310
- }else if (item.id) {
1311
- ids.push(item.id);
1319
+ return null
1320
+ }
1321
+
1322
+ function getLeafNodes(content) {
1323
+ const ids = []
1324
+ function traverse(children) {
1325
+ for (const item of children) {
1326
+ if (item.children) {
1327
+ traverse(item.children) // Recursively handle nested lessons
1328
+ } else if (item.id) {
1329
+ ids.push(item.id)
1312
1330
  }
1313
1331
  }
1314
1332
  }
1315
- if (data.raw &amp;&amp; Array.isArray(data.raw.lessons)) {
1316
- traverse(data.raw.lessons);
1333
+ if (content &amp;&amp; Array.isArray(content.children)) {
1334
+ traverse(content.children)
1317
1335
  }
1318
-
1319
- return ids;
1336
+ return ids
1320
1337
  }
1321
1338
 
1322
-
1323
1339
  async function getEligiblePlaylistItems(playlists) {
1324
- const eligible = playlists.filter(p => p.last_progress &amp;&amp; p.last_engaged_on);
1340
+ const eligible = playlists.filter((p) => p.last_progress &amp;&amp; p.last_engaged_on)
1325
1341
  return Promise.all(
1326
- eligible.map(async p => {
1327
- const utcDate = new Date(p.last_progress.replace(' ', 'T') + 'Z');
1328
- const timestamp = utcDate.getTime();
1342
+ eligible.map(async (p) => {
1343
+ const utcDate = new Date(p.last_progress.replace(' ', 'T') + 'Z')
1344
+ const timestamp = utcDate.getTime()
1329
1345
  return {
1330
1346
  type: 'playlist',
1331
- progressTimestamp: timestamp,
1332
- last_engaged_on: p.last_engaged_on,
1333
- raw: p
1334
- };
1347
+ // Content timestamps are millisecond accurate so for comparison we bring this to the same resolution
1348
+ progressTimestamp: timestamp / 1000,
1349
+ playlist: p,
1350
+ id: p.id,
1351
+ }
1335
1352
  })
1336
- );
1353
+ )
1337
1354
  }
1338
1355
 
1339
1356
  function mergeAndSortItems(items, limit) {
1340
- const seen = new Set();
1341
- const deduped = [];
1357
+ const seen = new Set()
1358
+ const deduped = []
1342
1359
 
1343
1360
  for (const item of items) {
1344
- const key = `${item.id}-${item.type || item.raw?.type}`;
1361
+ const key = `${item.id}-${item.type}`
1345
1362
  if (!seen.has(key)) {
1346
- seen.add(key);
1347
- deduped.push(item);
1363
+ seen.add(key)
1364
+ deduped.push(item)
1348
1365
  }
1349
1366
  }
1350
1367
 
1351
1368
  return deduped
1352
- .filter(item => typeof item.progressTimestamp === 'number' &amp;&amp; item.progressTimestamp > 0)
1369
+ .filter((item) => typeof item.progressTimestamp === 'number' &amp;&amp; item.progressTimestamp >= 0)
1353
1370
  .sort((a, b) => {
1354
- if (a.pinned &amp;&amp; !b.pinned) return -1;
1355
- if (!a.pinned &amp;&amp; b.pinned) return 1;
1356
- // TODO guided course should always be before user pinned item
1357
- return b.progressTimestamp - a.progressTimestamp;
1371
+ if (a.pinned &amp;&amp; !b.pinned) return -1
1372
+ if (!a.pinned &amp;&amp; b.pinned) return 1
1373
+ // TODO pinned guided course should always be before user pinned item
1374
+ return b.progressTimestamp - a.progressTimestamp
1358
1375
  })
1359
- .slice(0, limit + 5);
1376
+ .slice(0, limit + 5)
1360
1377
  }
1361
1378
 
1362
1379
  export function findIncompleteLesson(progressOnItems, currentContentId, contentType) {
1363
- const ids = Object.keys(progressOnItems).map(Number);
1380
+ const ids = Object.keys(progressOnItems).map(Number)
1364
1381
  if (contentType === 'guided-course') {
1365
1382
  // Return first incomplete lesson
1366
- return ids.find(id => progressOnItems[id] !== 'completed') || ids.at(0);
1383
+ return ids.find((id) => progressOnItems[id] !== 'completed') || ids.at(0)
1367
1384
  }
1368
1385
 
1369
1386
  // For other types, find next incomplete after current
1370
- const currentIndex = ids.indexOf(Number(currentContentId));
1371
- if (currentIndex === -1) return null;
1387
+ const currentIndex = ids.indexOf(Number(currentContentId))
1388
+ if (currentIndex === -1) return null
1372
1389
 
1373
1390
  for (let i = currentIndex + 1; i &lt; ids.length; i++) {
1374
- const id = ids[i];
1391
+ const id = ids[i]
1375
1392
  if (progressOnItems[id] !== 'completed') {
1376
- return id;
1393
+ return id
1377
1394
  }
1378
1395
  }
1379
1396
 
1380
- return ids[0];
1397
+ return ids[0]
1398
+ }
1399
+
1400
+ async function popPinnedItemFromContentsOrPlaylistMap(pinned, contentsMap, playlistItems) {
1401
+ if (!pinned) return null
1402
+ const { id, progressType, pinnedAt } = pinned
1403
+ let item = null
1404
+ if (progressType === 'content') {
1405
+ const pinnedId = parseInt(id)
1406
+ if (contentsMap.has(pinnedId)) {
1407
+ item = contentsMap.get(pinnedId)
1408
+ contentsMap.delete(pinnedId)
1409
+ } else {
1410
+ // we use fetchByRailContentIds so that we don't have the _type restriction in the query
1411
+ let data = await fetchByRailContentIds([id], 'progress-tracker')
1412
+ item = await addContextToContent(() => data[0] ?? null, {
1413
+ addNextLesson: true,
1414
+ addNavigateTo: true,
1415
+ addProgressStatus: true,
1416
+ addProgressPercentage: true,
1417
+ addProgressTimestamp: true,
1418
+ })
1419
+ }
1420
+ }
1421
+ if (progressType === 'playlist') {
1422
+ const pinnedPlaylist = playlistItems.find((p) => p.playlist.id === id)
1423
+ if (pinnedPlaylist) {
1424
+ playlistItems = playlistItems.filter((p) => p.playlist.id !== id)
1425
+ item = pinnedPlaylist
1426
+ } else {
1427
+ const playlist = await fetchPlaylist(id)
1428
+ item = {
1429
+ id: id,
1430
+ playlist: playlist,
1431
+ type: 'playlist',
1432
+ progressTimestamp: new Date(pinnedAt).getTime(),
1433
+ }
1434
+ }
1435
+ }
1436
+ return item
1437
+ }
1438
+
1439
+ function popContentAndRemoveChildrenFromContentsMap(content, contentsMap) {
1440
+ const children = content.children.map((child) => child.id)
1441
+ if (contentsMap.has(content.id)) {
1442
+ contentsMap.delete(content.id)
1443
+ }
1444
+ children.forEach((child) => {
1445
+ if (contentsMap.has(child)) {
1446
+ contentsMap.delete(child)
1447
+ }
1448
+ })
1449
+ return contentsMap
1381
1450
  }
1382
1451
 
1383
1452
  /**
@@ -1394,16 +1463,16 @@ export function findIncompleteLesson(progressOnItems, currentContentId, contentT
1394
1463
  * .catch(error => console.error(error));
1395
1464
  */
1396
1465
  export async function pinProgressRow(brand, id, progressType) {
1397
- const url = `/api/user-management-system/v1/progress/pin?brand=${brand}&amp;id=${id}&amp;progressType=${progressType}`;
1466
+ const url = `/api/user-management-system/v1/progress/pin?brand=${brand}&amp;id=${id}&amp;progressType=${progressType}`
1398
1467
  const response = await fetchHandler(url, 'PUT', null)
1399
1468
  if (response &amp;&amp; !response.error &amp;&amp; response['action'] === 'update_user_pin') {
1400
1469
  await updateUserPinnedProgressRow(brand, {
1401
1470
  id,
1402
1471
  progressType,
1403
1472
  pinnedAt: new Date().toISOString(),
1404
- });
1473
+ })
1405
1474
  }
1406
- return response;
1475
+ return response
1407
1476
  }
1408
1477
  /**
1409
1478
  * Unpins the current pinned progress row for a user, scoped by brand.
@@ -1428,116 +1497,29 @@ export async function unpinProgressRow(brand, id) {
1428
1497
  }
1429
1498
 
1430
1499
  async function updateUserPinnedProgressRow(brand, pinnedData) {
1431
- const userRaw = await globalConfig.localStorage.getItem('user');
1432
- const user = userRaw ? JSON.parse(userRaw) : {};
1500
+ const userRaw = await globalConfig.localStorage.getItem('user')
1501
+ const user = userRaw ? JSON.parse(userRaw) : {}
1433
1502
  user.brand_pinned_progress = user.brand_pinned_progress || {}
1434
1503
  user.brand_pinned_progress[brand] = pinnedData
1435
1504
  await globalConfig.localStorage.setItem('user', JSON.stringify(user))
1436
1505
  }
1437
1506
 
1438
- async function extractPinnedItem(pinned, progressMap, playlistItems) {
1439
- const {id, progressType, pinnedAt} = pinned
1440
-
1441
- if (progressType === 'content') {
1442
- const pinnedId = parseInt(id)
1443
- if (progressMap.has(pinnedId)) {
1444
- const item = progressMap.get(pinnedId)
1445
- progressMap.delete(pinnedId)
1446
- return item
1447
- } else {
1448
- const content = await fetchByRailContentIds([`${pinnedId}`], 'progress-tracker')
1449
- const firstLessonId = getFirstLeafLessonId(content[0])
1450
- return {
1451
- id: pinnedId,
1452
- state: 'started',
1453
- percent: 0,
1454
- raw: content[0],
1455
- progressTimestamp: new Date(pinnedAt).getTime(),
1456
- childIndex: firstLessonId
1457
- }
1458
- }
1459
- }
1460
- if (progressType === 'playlist') {
1461
- const pinnedPlaylist = playlistItems.find(p => p.raw.id === id)
1462
- if (pinnedPlaylist) {
1463
- return pinnedPlaylist
1464
- }else{
1465
- const playlist = await fetchPlaylist(id)
1466
- return {
1467
- id: id,
1468
- raw: playlist,
1469
- progressTimestamp: new Date(pinnedAt).getTime(),
1470
- type: 'playlist',
1471
- last_engaged_on: playlist.last_engaged_on,
1472
- }
1473
- }
1474
- }
1475
-
1476
- return null
1477
- }
1478
-
1479
- async function extractPinnedGuidedCourseItem(guidedCourse, progressMap) {
1480
- const children = guidedCourse.lessons.map(child => child.id)
1481
- let existingGuidedCourseProgress = null
1482
- if (progressMap.has(guidedCourse.id)) {
1483
- existingGuidedCourseProgress = progressMap.get(guidedCourse.id)
1484
- progressMap.delete(guidedCourse.id)
1485
- }
1486
- let lastChild = null
1487
- children.forEach(child => {
1488
- if (progressMap.has(child)) {
1489
- let childProgress = progressMap.get(child)
1490
- if (!lastChild &amp;&amp; childProgress.state !== 'completed') {
1491
- lastChild = childProgress
1492
- lastChild.id = child
1493
- }
1494
- progressMap.delete(child)
1495
- }
1496
- })
1497
- return existingGuidedCourseProgress ?? {
1498
- id: guidedCourse.id,
1499
- state: 'not-started',
1500
- percent: 0,
1501
- raw: guidedCourse,
1502
- pinned: true,
1503
- progressTimestamp: new Date().getTime(),
1504
- childIndex: guidedCourse.id,
1505
- }
1506
- }
1507
-
1508
- function getFirstLeafLessonId(data) {
1509
- function findFirstLeaf(lessons) {
1510
- for (const item of lessons) {
1511
- if (!item.lessons || item.lessons.length === 0) {
1512
- return item.id || null
1513
- }
1514
- const found = findFirstLeaf(item.lessons)
1515
- if (found) return found
1516
- }
1517
- return null
1518
- }
1519
-
1520
- return data.lessons ? findFirstLeaf(data.lessons) : null
1521
- }
1522
-
1523
1507
  export async function fetchRecentActivitiesActiveTabs() {
1524
1508
  const url = `/api/user-management-system/v1/activities/tabs`
1525
1509
  try {
1526
- const tabs = await fetchHandler(url, 'GET');
1527
- const activitiesTabs = [];
1510
+ const tabs = await fetchHandler(url, 'GET')
1511
+ const activitiesTabs = []
1528
1512
 
1529
- tabs.forEach(tab => {
1530
- activitiesTabs.push({ name: tab.label, short_name:tab.label });
1531
- });
1513
+ tabs.forEach((tab) => {
1514
+ activitiesTabs.push({ name: tab.label, short_name: tab.label })
1515
+ })
1532
1516
 
1533
- return activitiesTabs;
1517
+ return activitiesTabs
1534
1518
  } catch (error) {
1535
- console.error('Error fetching activity tabs:', error);
1536
- return [];
1519
+ console.error('Error fetching activity tabs:', error)
1520
+ return []
1537
1521
  }
1538
1522
  }
1539
-
1540
-
1541
1523
  </code></pre>
1542
1524
  </article>
1543
1525
  </section>
@@ -1552,7 +1534,7 @@ export async function fetchRecentActivitiesActiveTabs() {
1552
1534
  <br class="clear">
1553
1535
 
1554
1536
  <footer>
1555
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.3</a> on Fri Jul 25 2025 21:19:12 GMT+0000 (Coordinated Universal Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1537
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.3</a> on Tue Aug 19 2025 17:54:03 GMT+0000 (Coordinated Universal Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1556
1538
  </footer>
1557
1539
 
1558
1540
  <script>prettyPrint();</script>