musora-content-services 2.3.11 → 2.3.13

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 (134) hide show
  1. package/.coderabbit.yaml +0 -0
  2. package/.editorconfig +0 -0
  3. package/.github/workflows/node.js.yml +0 -0
  4. package/.prettierignore +0 -0
  5. package/.prettierrc +0 -0
  6. package/CHANGELOG.md +4 -0
  7. package/README.md +0 -0
  8. package/babel.config.cjs +0 -0
  9. package/docs/Content-Organization.html +0 -0
  10. package/docs/ContentOrganization.html +2 -2
  11. package/docs/Gamification.html +2 -2
  12. package/docs/UserManagement.html +0 -0
  13. package/docs/UserManagementSystem.html +2 -2
  14. package/docs/api_types.js.html +2 -2
  15. package/docs/config.js.html +2 -3
  16. package/docs/content-org_content-org.js.html +2 -2
  17. package/docs/content-org_playlists-types.js.html +2 -2
  18. package/docs/content-org_playlists.js.html +2 -2
  19. package/docs/content.js.html +2 -2
  20. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  21. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  23. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  24. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  25. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  26. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  27. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  28. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  34. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  35. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  36. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  37. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  38. package/docs/gamification_awards.js.html +2 -2
  39. package/docs/gamification_gamification.js.html +2 -2
  40. package/docs/gamification_types.js.html +2 -2
  41. package/docs/global.html +2 -2
  42. package/docs/global.html#User +0 -0
  43. package/docs/index.html +2 -2
  44. package/docs/module-Awards.html +2 -2
  45. package/docs/module-Config.html +2 -2
  46. package/docs/module-Content-Services-V2.html +2 -2
  47. package/docs/module-Permissions.html +2 -2
  48. package/docs/module-Playlists.html +2 -2
  49. package/docs/module-Railcontent-Services.html +222 -57
  50. package/docs/module-Sanity-Services.html +19 -19
  51. package/docs/module-Session-Management.html +0 -0
  52. package/docs/module-Sessions.html +2 -2
  53. package/docs/module-User-Activity.html +2849 -0
  54. package/docs/module-User-Management.html +0 -0
  55. package/docs/module-User-Permissions.html +0 -0
  56. package/docs/module-UserManagement.html +2 -2
  57. package/docs/railcontent.js.html +47 -20
  58. package/docs/sanity.js.html +3 -2
  59. package/docs/scripts/collapse.js +0 -0
  60. package/docs/scripts/commonNav.js +0 -0
  61. package/docs/scripts/linenumber.js +0 -0
  62. package/docs/scripts/nav.js +0 -0
  63. package/docs/scripts/polyfill.js +0 -0
  64. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  65. package/docs/scripts/prettify/lang-css.js +0 -0
  66. package/docs/scripts/prettify/prettify.js +0 -0
  67. package/docs/scripts/search.js +0 -0
  68. package/docs/styles/jsdoc.css +0 -0
  69. package/docs/styles/prettify.css +0 -0
  70. package/docs/types.js.html +0 -0
  71. package/docs/userActivity.js.html +742 -0
  72. package/docs/user_management.js.html +2 -2
  73. package/docs/user_permissions.js.html +2 -2
  74. package/docs/user_sessions.js.html +2 -2
  75. package/docs/user_types.js.html +2 -2
  76. package/docs/user_user-management-system.js.html +2 -2
  77. package/docs/user_user-management.js.html +0 -0
  78. package/jest.config.js +0 -0
  79. package/jsdoc.json +1 -0
  80. package/link_mcs.sh +0 -0
  81. package/package.json +1 -1
  82. package/src/contentMetaData.js +0 -0
  83. package/src/filterBuilder.js +0 -0
  84. package/src/index.d.ts +8 -0
  85. package/src/index.js +8 -0
  86. package/src/lib/httpHelper.js +0 -0
  87. package/src/lib/lastUpdated.js +0 -0
  88. package/src/services/api/types.js +0 -0
  89. package/src/services/config.js +0 -0
  90. package/src/services/content-org/content-org.js +0 -0
  91. package/src/services/content-org/playlists-types.js +0 -0
  92. package/src/services/content-org/playlists.js +0 -0
  93. package/src/services/content.js +0 -0
  94. package/src/services/contentProgress.js +0 -0
  95. package/src/services/dataContext.js +0 -0
  96. package/src/services/dateUtils.js +0 -0
  97. package/src/services/forum.js +0 -0
  98. package/src/services/gamification/awards.js +0 -0
  99. package/src/services/gamification/gamification.js +0 -0
  100. package/src/services/gamification/types.js +0 -0
  101. package/src/services/imageSRCBuilder.js +0 -0
  102. package/src/services/imageSRCVerify.js +0 -0
  103. package/src/services/railcontent.js +15 -0
  104. package/src/services/recommendations.js +9 -7
  105. package/src/services/sanity.js +4 -2
  106. package/src/services/types.js +0 -0
  107. package/src/services/user/management.js +0 -0
  108. package/src/services/user/permissions.js +0 -0
  109. package/src/services/user/sessions.js +0 -0
  110. package/src/services/user/types.js +0 -0
  111. package/src/services/user/user-management-system.js +0 -0
  112. package/src/services/userActivity.js +103 -6
  113. package/test/content.test.js +0 -0
  114. package/test/contentLikes.test.js +0 -0
  115. package/test/contentProgress.test.js +0 -0
  116. package/test/dataContext.test.js +0 -0
  117. package/test/forum.test.js +0 -0
  118. package/test/imageSRCBuilder.test.js +0 -0
  119. package/test/imageSRCVerify.test.js +0 -0
  120. package/test/initializeTests.js +0 -0
  121. package/test/lib/lastUpdated.test.js +0 -0
  122. package/test/live/contentProgressLive.test.js +0 -0
  123. package/test/live/railcontentLive.test.js +0 -0
  124. package/test/localStorageMock.js +0 -0
  125. package/test/log.js +0 -0
  126. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  127. package/test/mockData/mockData_user_practices.json +0 -0
  128. package/test/sanityQueryService.test.js +0 -0
  129. package/test/streakMessage.test.js +0 -0
  130. package/test/user/permissions.test.js +0 -0
  131. package/test/userActivity.test.js +0 -0
  132. package/tools/generate-index.cjs +0 -0
  133. package/.yarnrc.yml +0 -1
  134. package/docs/module-Content-Services.html +0 -763
@@ -0,0 +1,742 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>userActivity.js - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
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-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#.fetchUserPlaylists">fetchUserPlaylists</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#.countAssignmentsAndLessons">countAssignmentsAndLessons</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#.deletePlaylist">deletePlaylist</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.deletePlaylistItem">deletePlaylistItem</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.deletePlaylistLike">deletePlaylistLike</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.duplicatePlaylist">duplicatePlaylist</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#.fetchChallengeIndexMetadata">fetchChallengeIndexMetadata</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchChallengeLessonData">fetchChallengeLessonData</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchChallengeMetadata">fetchChallengeMetadata</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchChallengeUserActiveChallenges">fetchChallengeUserActiveChallenges</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#.fetchCompletedChallenges">fetchCompletedChallenges</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#.fetchOwnedChallenges">fetchOwnedChallenges</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchPinnedPlaylists">fetchPinnedPlaylists</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchPlaylist">fetchPlaylist</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchPlaylistItem">fetchPlaylistItem</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchPlaylistItems">fetchPlaylistItems</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#.fetchUserChallengeProgress">fetchUserChallengeProgress</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#.likePlaylist">likePlaylist</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#.pinPlaylist">pinPlaylist</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postChallengesCommunityNotification">postChallengesCommunityNotification</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postChallengesEnroll">postChallengesEnroll</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postChallengesEnrollmentNotification">postChallengesEnrollmentNotification</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postChallengesHideCompletedBanner">postChallengesHideCompletedBanner</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postChallengesLeave">postChallengesLeave</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postChallengesSetStartDate">postChallengesSetStartDate</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postChallengesSoloNotification">postChallengesSoloNotification</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.postChallengesUnlock">postChallengesUnlock</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#.reportPlaylist">reportPlaylist</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#.unpinPlaylist">unpinPlaylist</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.updatePlaylist">updatePlaylist</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.updatePlaylistItem">updatePlaylistItem</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#.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><li data-type='method'><a href="module-Sanity-Services.html#~handleCustomFetchAll">handleCustomFetchAll</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-User-Activity.html">User-Activity</a><ul class='methods'><li data-type='method'><a href="module-User-Activity.html#.createPracticeNotes">createPracticeNotes</a></li><li data-type='method'><a href="module-User-Activity.html#.deletePracticeSession">deletePracticeSession</a></li><li data-type='method'><a href="module-User-Activity.html#.getPracticeNotes">getPracticeNotes</a></li><li data-type='method'><a href="module-User-Activity.html#.getPracticeSessions">getPracticeSessions</a></li><li data-type='method'><a href="module-User-Activity.html#.getRecentActivity">getRecentActivity</a></li><li data-type='method'><a href="module-User-Activity.html#.getUserMonthlyStats">getUserMonthlyStats</a></li><li data-type='method'><a href="module-User-Activity.html#.getUserWeeklyStats">getUserWeeklyStats</a></li><li data-type='method'><a href="module-User-Activity.html#.recordUserPractice">recordUserPractice</a></li><li data-type='method'><a href="module-User-Activity.html#.removeUserPractice">removeUserPractice</a></li><li data-type='method'><a href="module-User-Activity.html#.restorePracticeSession">restorePracticeSession</a></li><li data-type='method'><a href="module-User-Activity.html#.restoreUserPractice">restoreUserPractice</a></li><li data-type='method'><a href="module-User-Activity.html#.updatePracticeNotes">updatePracticeNotes</a></li><li data-type='method'><a href="module-User-Activity.html#.updateUserPractice">updateUserPractice</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#.unblockUser">unblockUser</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
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">userActivity.js</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>/**
49
+ * @module User-Activity
50
+ */
51
+
52
+ import {fetchUserPractices, logUserPractice, fetchUserPracticeMeta, fetchUserPracticeNotes, fetchHandler} from './railcontent'
53
+ import { DataContext, UserActivityVersionKey } from './dataContext.js'
54
+ import {fetchByRailContentIds} from "./sanity";
55
+ import {lessonTypesMapping} from "../contentTypeConfig";
56
+ import { convertToTimeZone, getMonday, getWeekNumber, isSameDate, isNextDay } from './dateUtils.js';
57
+
58
+ const recentActivity = [
59
+ { id: 5,title: '3 Easy Classical Songs For Beginners', action: 'Comment', thumbnail: 'https://cdn.sanity.io/images/4032r8py/production/8a7fb4d7473306c5fa51ba2e8867e03d44342b18-1920x1080.jpg', summary: 'Just completed the advanced groove lesson! I’m finally feeling more confident with my fills. Thanks for the clear explanations and practice tips! ', date: '2025-03-25 10:09:48' },
60
+ { id:4, title: 'Piano Man by Billy Joel', action: 'Play', thumbnail:'https://cdn.sanity.io/images/4032r8py/production/107c258114540170399dfd72a50dae51575552f4-1000x1000.jpg', date: '2025-03-25 10:04:48' },
61
+ { id:3, title: 'General Piano Discussion', action: 'Post', thumbnail: 'https://cdn.sanity.io/images/4032r8py/production/2331571d237b42dbf72f0cf35fdf163d996c5c5a-1920x1080.jpg', summary: 'Just completed the advanced groove lesson! I’m finally feeling more confident with my fills. Thanks for the clear explanations and practice tips! ', date: '2025-03-25 09:49:48' },
62
+ { id:2, title: 'Welcome To Guitareo', action: 'Complete', thumbnail: 'https://cdn.sanity.io/images/4032r8py/production/2331571d237b42dbf72f0cf35fdf163d996c5c5a-1920x1080.jpg',date: '2025-03-25 09:34:48' },
63
+ { id:1, title: 'Welcome To Guitareo', action: 'Start', thumbnail: 'https://cdn.sanity.io/images/4032r8py/production/2331571d237b42dbf72f0cf35fdf163d996c5c5a-1920x1080.jpg',date: '2025-03-25 09:04:48' },
64
+ ]
65
+
66
+ const DATA_KEY_PRACTICES = 'practices'
67
+ const DATA_KEY_LAST_UPDATED_TIME = 'u'
68
+
69
+ const DAYS = ['M', 'T', 'W', 'T', 'F', 'S', 'S']
70
+
71
+ const streakMessages = {
72
+ startStreak: "Start your streak by taking any lesson!",
73
+ restartStreak: "Restart your streak by taking any lesson!",
74
+
75
+ // Messages when last active day is today
76
+ dailyStreak: (streak) => `Nice! You have ${getIndefiniteArticle(streak)} ${streak} day streak! Way to keep it going!`,
77
+ dailyStreakShort: (streak) => `Nice! You have ${getIndefiniteArticle(streak)} ${streak} day streak!`,
78
+ weeklyStreak: (streak) => `You have ${getIndefiniteArticle(streak)} ${streak} week streak! Way to keep up the momentum!`,
79
+ greatJobWeeklyStreak: (streak) => `Great job! You have ${getIndefiniteArticle(streak)} ${streak} week streak! Way to keep it going!`,
80
+
81
+ // Messages when last active day is NOT today
82
+ dailyStreakReminder: (streak) => `You have ${getIndefiniteArticle(streak)} ${streak} day streak! Keep it going with any lesson or song!`,
83
+ weeklyStreakKeepUp: (streak) => `You have ${getIndefiniteArticle(streak)} ${streak} week streak! Keep up the momentum!`,
84
+ weeklyStreakReminder: (streak) => `You have ${getIndefiniteArticle(streak)} ${streak} week streak! Keep it going with any lesson or song!`,
85
+ };
86
+
87
+ function getIndefiniteArticle(streak) {
88
+ return streak === 8 || (streak >= 80 &amp;&amp; streak &lt;= 89) || (streak >= 800 &amp;&amp; streak &lt;= 899) ? 'an' : 'a'
89
+ }
90
+
91
+
92
+ export let userActivityContext = new DataContext(UserActivityVersionKey, fetchUserPractices)
93
+
94
+ /**
95
+ * Retrieves user activity statistics for the current week, including daily activity and streak messages.
96
+ *
97
+ * @returns {Promise&lt;Object>} - A promise that resolves to an object containing weekly user activity statistics.
98
+ *
99
+ * @example
100
+ * // Retrieve user activity statistics for the current week
101
+ * getUserWeeklyStats()
102
+ * .then(stats => console.log(stats))
103
+ * .catch(error => console.error(error));
104
+ */
105
+ export async function getUserWeeklyStats() {
106
+ let data = await userActivityContext.getData()
107
+ let practices = data?.[DATA_KEY_PRACTICES] ?? {}
108
+ let sortedPracticeDays = Object.keys(practices)
109
+ .map(date => new Date(date))
110
+ .sort((a, b) => b - a);
111
+
112
+ let today = new Date();
113
+ today.setHours(0, 0, 0, 0);
114
+ let startOfWeek = getMonday(today) // Get last Monday
115
+ let dailyStats = []
116
+
117
+ for (let i = 0; i &lt; 7; i++) {
118
+ let day = new Date(startOfWeek)
119
+ day.setDate(startOfWeek.getDate() + i)
120
+ let hasPractice = sortedPracticeDays.some(practiceDate => isSameDate(practiceDate, day));
121
+ let isActive = isSameDate(today, day)
122
+ let type = (hasPractice ? 'tracked' : (isActive ? 'active' : 'none'))
123
+ dailyStats.push({ key: i, label: DAYS[i], isActive, inStreak: hasPractice, type })
124
+ }
125
+
126
+ let { streakMessage } = getStreaksAndMessage(practices);
127
+
128
+ return { data: { dailyActiveStats: dailyStats, streakMessage, practices } }
129
+ }
130
+
131
+ /**
132
+ * Retrieves user activity statistics for a specified month, including daily and weekly activity data.
133
+ *
134
+ * @param {number} [year=new Date().getFullYear()] - The year for which to retrieve the statistics.
135
+ * @param {number} [month=new Date().getMonth()] - The month (0-indexed) for which to retrieve the statistics.
136
+ * @returns {Promise&lt;Object>} - A promise that resolves to an object containing user activity statistics.
137
+ *
138
+ * @example
139
+ * // Retrieve user activity statistics for the current month
140
+ * getUserMonthlyStats()
141
+ * .then(stats => console.log(stats))
142
+ * .catch(error => console.error(error));
143
+ *
144
+ * @example
145
+ * // Retrieve user activity statistics for March 2024
146
+ * getUserMonthlyStats(2024, 2)
147
+ * .then(stats => console.log(stats))
148
+ * .catch(error => console.error(error));
149
+ */
150
+ export async function getUserMonthlyStats(year = new Date().getFullYear(), month = new Date().getMonth(), day = 1) {
151
+ let data = await userActivityContext.getData()
152
+ let practices = data?.[DATA_KEY_PRACTICES] ?? {}
153
+ let sortedPracticeDays = Object.keys(practices)
154
+ .map(dateStr => {
155
+ const [y, m, d] = dateStr.split('-').map(Number);
156
+ const newDate = new Date();
157
+ newDate.setFullYear(y, m - 1, d);
158
+ return newDate;
159
+ })
160
+ .sort((a, b) => a - b);
161
+
162
+ // Get the first day of the specified month and the number of days in that month
163
+ let firstDayOfMonth = new Date(year, month, 1)
164
+ let today = new Date()
165
+ today.setHours(0, 0, 0, 0);
166
+
167
+ let startOfGrid = getMonday(firstDayOfMonth)
168
+ let endOfMonth = new Date(year, month + 1, 0)
169
+ while (endOfMonth.getDay() !== 0) {
170
+ endOfMonth.setDate(endOfMonth.getDate() + 1)
171
+ }
172
+
173
+ let daysInMonth = Math.ceil((endOfMonth - startOfGrid) / (1000 * 60 * 60 * 24)) + 1;
174
+
175
+ let dailyStats = []
176
+ let practiceDuration = 0
177
+ let daysPracticed = 0
178
+ let weeklyStats = {}
179
+
180
+ for (let i = 0; i &lt; daysInMonth; i++) {
181
+ let day = new Date(startOfGrid)
182
+ day.setDate(startOfGrid.getDate() + i)
183
+ let dayKey = `${day.getFullYear()}-${String(day.getMonth() + 1).padStart(2, '0')}-${String(day.getDate()).padStart(2, '0')}`;
184
+
185
+ // Check if the user has activity for the day
186
+ let dayActivity = practices[dayKey] ?? null
187
+ let weekKey = getWeekNumber(day)
188
+
189
+ if (!weeklyStats[weekKey]) {
190
+ weeklyStats[weekKey] = { key: weekKey, inStreak: false };
191
+ }
192
+
193
+ if (dayActivity !== null) {
194
+ practiceDuration += dayActivity.reduce((sum, entry) => sum + entry.duration_seconds, 0)
195
+ daysPracticed++;
196
+ }
197
+
198
+ let isActive = isSameDate(today, day)
199
+ let type = ((dayActivity !== null) ? 'tracked' : (isActive ? 'active' : 'none'))
200
+ let isInStreak = dayActivity !== null;
201
+ if (isInStreak) {
202
+ weeklyStats[weekKey].inStreak = true;
203
+ }
204
+
205
+ dailyStats.push({
206
+ key: i,
207
+ label: dayKey,
208
+ isActive,
209
+ inStreak: dayActivity !== null,
210
+ type,
211
+ })
212
+ }
213
+
214
+ let filteredPractices = Object.keys(practices)
215
+ .filter((date) => new Date(date) &lt;= endOfMonth)
216
+ .reduce((obj, key) => {
217
+ obj[key] = practices[key]
218
+ return obj
219
+ }, {})
220
+
221
+ let { currentDailyStreak, currentWeeklyStreak } = calculateStreaks(filteredPractices);
222
+
223
+ return { data: {
224
+ dailyActiveStats: dailyStats,
225
+ weeklyActiveStats: Object.values(weeklyStats),
226
+ practiceDuration,
227
+ currentDailyStreak,
228
+ currentWeeklyStreak,
229
+ daysPracticed,
230
+ }
231
+ }
232
+ }
233
+
234
+ export async function getUserPractices() {
235
+ let data = await userActivityContext.getData()
236
+ return data?.[DATA_KEY_PRACTICES] ?? []
237
+ }
238
+ /**
239
+ * Records user practice data and updates both the remote and local activity context.
240
+ *
241
+ * @param {Object} practiceDetails - The details of the practice session.
242
+ * @param {number} practiceDetails.duration_seconds - The duration of the practice session in seconds.
243
+ * @param {boolean} [practiceDetails.auto=true] - Whether the session was automatically logged.
244
+ * @param {number} [practiceDetails.content_id] - The ID of the practiced content (if available).
245
+ * @param {number} [practiceDetails.category_id] - The ID of the associated category (if available).
246
+ * @param {string} [practiceDetails.title] - The title of the practice session (max 64 characters).
247
+ * @param {string} [practiceDetails.thumbnail_url] - The URL of the session's thumbnail (max 255 characters).
248
+ * @returns {Promise&lt;Object>} - A promise that resolves to the response from logging the user practice.
249
+ *
250
+ * @example
251
+ * // Record an auto practice session with content ID
252
+ * recordUserPractice({ content_id: 123, duration_seconds: 300 })
253
+ * .then(response => console.log(response))
254
+ * .catch(error => console.error(error));
255
+ *
256
+ * @example
257
+ * // Record a custom practice session with additional details
258
+ * recordUserPractice({
259
+ * duration_seconds: 600,
260
+ * auto: false,
261
+ * category_id: 5,
262
+ * title: "Guitar Warm-up",
263
+ * thumbnail_url: "https://example.com/thumbnail.jpg"
264
+ * })
265
+ * .then(response => console.log(response))
266
+ * .catch(error => console.error(error));
267
+ */
268
+ export async function recordUserPractice(practiceDetails) {
269
+ practiceDetails.auto = 0;
270
+ if (practiceDetails.content_id) {
271
+ practiceDetails.auto = 1;
272
+ }
273
+
274
+ await userActivityContext.update(
275
+ async function (localContext) {
276
+ let userData = localContext.data ?? { [DATA_KEY_PRACTICES]: {} };
277
+ localContext.data = userData;
278
+ },
279
+ async function () {
280
+ const response = await logUserPractice(practiceDetails);
281
+ if (response) {
282
+ await userActivityContext.updateLocal(async function (localContext) {
283
+ const newPractices = response.data ?? []
284
+ newPractices.forEach(newPractice => {
285
+ const { date } = newPractice;
286
+ if (!localContext.data[DATA_KEY_PRACTICES][date]) {
287
+ localContext.data[DATA_KEY_PRACTICES][date] = [];
288
+ }
289
+ localContext.data[DATA_KEY_PRACTICES][date][DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000)
290
+ localContext.data[DATA_KEY_PRACTICES][date].push({
291
+ id: newPractice.id,
292
+ duration_seconds: newPractice.duration_seconds // Add the new practice for this date
293
+ });
294
+ });
295
+ });
296
+ }
297
+ return response;
298
+ }
299
+ );
300
+ }
301
+ /**
302
+ * Updates a user's practice session with new details and syncs the changes remotely.
303
+ *
304
+ * @param {number} id - The unique identifier of the practice session to update.
305
+ * @param {Object} practiceDetails - The updated details of the practice session.
306
+ * @param {number} [practiceDetails.duration_seconds] - The duration of the practice session in seconds.
307
+ * @param {number} [practiceDetails.category_id] - The ID of the associated category (if available).
308
+ * @param {string} [practiceDetails.title] - The title of the practice session (max 64 characters).
309
+ * @param {string} [practiceDetails.thumbnail_url] - The URL of the session's thumbnail (max 255 characters).
310
+ * @returns {Promise&lt;Object>} - A promise that resolves to the response from updating the user practice.
311
+ *
312
+ * @example
313
+ * // Update a practice session's duration
314
+ * updateUserPractice(123, { duration_seconds: 600 })
315
+ * .then(response => console.log(response))
316
+ * .catch(error => console.error(error));
317
+ *
318
+ * @example
319
+ * // Change a practice session to manual and update its category
320
+ * updateUserPractice(456, { auto: false, category_id: 8 })
321
+ * .then(response => console.log(response))
322
+ * .catch(error => console.error(error));
323
+ */
324
+ export async function updateUserPractice(id, practiceDetails) {
325
+ const url = `/api/user/practices/v1/practices/${id}`
326
+ return await fetchHandler(url, 'PUT', null, practiceDetails)
327
+ }
328
+
329
+ /**
330
+ * Removes a user's practice session by ID, updating both the local and remote activity context.
331
+ *
332
+ * @param {number} id - The unique identifier of the practice session to be removed.
333
+ * @returns {Promise&lt;void>} - A promise that resolves once the practice session is removed.
334
+ *
335
+ * @example
336
+ * // Remove a practice session with ID 123
337
+ * removeUserPractice(123)
338
+ * .then(() => console.log("Practice session removed successfully"))
339
+ * .catch(error => console.error(error));
340
+ */
341
+ export async function removeUserPractice(id) {
342
+ let url = `/api/user/practices/v1/practices${buildQueryString([id])}`;
343
+ await userActivityContext.update(
344
+ async function (localContext) {
345
+ if (localContext.data?.[DATA_KEY_PRACTICES]) {
346
+ Object.keys(localContext.data[DATA_KEY_PRACTICES]).forEach(date => {
347
+ localContext.data[DATA_KEY_PRACTICES][date] = localContext.data[DATA_KEY_PRACTICES][date].filter(
348
+ practice => practice.id !== id
349
+ );
350
+ });
351
+ }
352
+ },
353
+ async function () {
354
+ return await fetchHandler(url, 'delete');
355
+ }
356
+ );
357
+ }
358
+
359
+ /**
360
+ * Restores a previously deleted user's practice session by ID, updating both the local and remote activity context.
361
+ *
362
+ * @param {number} id - The unique identifier of the practice session to be restored.
363
+ * @returns {Promise&lt;Object>} - A promise that resolves to the response containing the restored practice session data.
364
+ *
365
+ * @example
366
+ * // Restore a deleted practice session with ID 123
367
+ * restoreUserPractice(123)
368
+ * .then(response => console.log("Practice session restored:", response))
369
+ * .catch(error => console.error(error));
370
+ */
371
+ export async function restoreUserPractice(id) {
372
+ let url = `/api/user/practices/v1/practices/restore${buildQueryString([id])}`;
373
+ const response = await fetchHandler(url, 'put');
374
+ if (response?.data) {
375
+ await userActivityContext.updateLocal(async function (localContext) {
376
+ const restoredPractice = response.data;
377
+ const { date } = restoredPractice;
378
+ if (!localContext.data[DATA_KEY_PRACTICES][date]) {
379
+ localContext.data[DATA_KEY_PRACTICES][date] = [];
380
+ }
381
+ localContext.data[DATA_KEY_PRACTICES][date].push({
382
+ id: restoredPractice.id,
383
+ duration_seconds: restoredPractice.duration_seconds,
384
+ });
385
+ });
386
+ }
387
+ return response;
388
+ }
389
+
390
+ /**
391
+ * Deletes all practice sessions for a specific day.
392
+ *
393
+ * This function retrieves all user practice session IDs for a given day and sends a DELETE request
394
+ * to remove them from the server. It also updates the local context to reflect the deletion.
395
+ *
396
+ * @async
397
+ * @param {string} day - The day (in `YYYY-MM-DD` format) for which practice sessions should be deleted.
398
+ * @returns {Promise&lt;string[]>} - A promise that resolves once the practice session is removed.
399
+ *
400
+ * * @example
401
+ * // Delete practice sessions for April 10, 2025
402
+ * deletePracticeSession("2025-04-10")
403
+ * .then(deletedIds => console.log("Deleted sessions:", response))
404
+ * .catch(error => console.error("Delete failed:", error));
405
+ */
406
+ export async function deletePracticeSession(day) {
407
+ const userPracticesIds = await getUserPracticeIds(day);
408
+ if (!userPracticesIds.length) return [];
409
+
410
+ const url = `/api/user/practices/v1/practices${buildQueryString(userPracticesIds)}`;
411
+ await userActivityContext.update(
412
+ async function (localContext) {
413
+ if (localContext.data?.[DATA_KEY_PRACTICES]?.[day]) {
414
+ localContext.data[DATA_KEY_PRACTICES][day] = localContext.data[DATA_KEY_PRACTICES][day].filter(
415
+ practice => !userPracticesIds.includes(practice.id)
416
+ );
417
+ }
418
+ },
419
+ async function () {
420
+ return await fetchHandler(url, 'DELETE', null);
421
+ }
422
+ );
423
+ }
424
+
425
+ /**
426
+ * Restores deleted practice sessions for a specific date.
427
+ *
428
+ * Sends a PUT request to restore any previously deleted practices for a given date.
429
+ * If restored practices are returned, they are added back into the local context.
430
+ *
431
+ * @async
432
+ * @param {string} date - The date (in `YYYY-MM-DD` format) for which deleted practice sessions should be restored.
433
+ * @returns {Promise&lt;Object>} - The response object from the API, containing practices for selected date.
434
+ *
435
+ * @example
436
+ * // Restore practice sessions deleted on April 10, 2025
437
+ * restorePracticeSession("2025-04-10")
438
+ * .then(response => console.log("Practice session restored:", response))
439
+ * .catch(error => console.error("Restore failed:", error));
440
+ */
441
+ export async function restorePracticeSession(date) {
442
+ const url = `/api/user/practices/v1/practices/restore?date=${date}`;
443
+ const response = await fetchHandler(url, 'PUT', null);
444
+
445
+ if (response?.data) {
446
+ await userActivityContext.updateLocal(async function (localContext) {
447
+ if (!localContext.data[DATA_KEY_PRACTICES][date]) {
448
+ localContext.data[DATA_KEY_PRACTICES][date] = [];
449
+ }
450
+
451
+ response.data.forEach(restoredPractice => {
452
+ localContext.data[DATA_KEY_PRACTICES][date].push({
453
+ id: restoredPractice.id,
454
+ duration_seconds: restoredPractice.duration_seconds,
455
+ });
456
+ });
457
+ });
458
+ }
459
+
460
+ return response;
461
+ }
462
+
463
+ /**
464
+ * Retrieves and formats a user's practice sessions for a specific day.
465
+ *
466
+ * @param {string} day - The date for which practice sessions should be retrieved (format: YYYY-MM-DD).
467
+ * @returns {Promise&lt;Object>} - A promise that resolves to an object containing the practice sessions and total practice duration.
468
+ *
469
+ * @example
470
+ * // Get practice sessions for a specific day
471
+ * getPracticeSessions("2025-03-31")
472
+ * .then(response => console.log(response))
473
+ * .catch(error => console.error(error));
474
+ */
475
+ export async function getPracticeSessions(day) {
476
+ const userPracticesIds = await getUserPracticeIds(day);
477
+ if (!userPracticesIds.length) return { data: { practices: [], practiceDuration: 0} };
478
+
479
+ const meta = await fetchUserPracticeMeta(userPracticesIds);
480
+ if (!meta.data.length) return { data: { practices: [], practiceDuration: 0 } };
481
+ const practiceDuration = meta.data.reduce((total, practice) => total + (practice.duration_seconds || 0), 0);
482
+ const contentIds = meta.data.map(practice => practice.content_id).filter(id => id !== null);
483
+
484
+ const contents = await fetchByRailContentIds(contentIds);
485
+ const getFormattedType = (type) => {
486
+ for (const [key, values] of Object.entries(lessonTypesMapping)) {
487
+ if (values.includes(type)) {
488
+ return key.replace(/\b\w/g, char => char.toUpperCase());
489
+ }
490
+ }
491
+ return null;
492
+ };
493
+
494
+ const userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
495
+
496
+ const formattedMeta = meta.data.map(practice => {
497
+ const utcDate = new Date(practice.created_at);
498
+ const content = contents.find(c => c.id === practice.content_id) || {};
499
+ return {
500
+ id: practice.id,
501
+ auto: practice.auto,
502
+ thumbnail: (practice.content_id)? content.thumbnail : '',
503
+ duration: practice.duration_seconds || 0,
504
+ content_url: content.url || null,
505
+ title: (practice.content_id)? content.title : practice.title,
506
+ category_id: practice.category_id,
507
+ instrument_id: practice.instrument_id ,
508
+ content_type: getFormattedType(content.type || ''),
509
+ content_id: practice.content_id || null,
510
+ content_brand: content.brand || null,
511
+ created_at: convertToTimeZone(utcDate, userTimeZone)
512
+ };
513
+ });
514
+ return { data: { practices: formattedMeta, practiceDuration} };
515
+ }
516
+
517
+ /**
518
+ * Retrieves user practice notes for a specific day.
519
+ *
520
+ * @async
521
+ * @param {string} day - The day (in `YYYY-MM-DD` format) to fetch practice notes for.
522
+ * @returns {Promise&lt;{ data: Object[] }>} - A promise that resolves to an object containing the practice notes.
523
+ *
524
+ * @example
525
+ * // Get notes for April 10, 2025
526
+ * getPracticeNotes("2025-04-10")
527
+ * .then(({ data }) => console.log("Practice notes:", data))
528
+ * .catch(error => console.error("Failed to get notes:", error));
529
+ */
530
+ export async function getPracticeNotes(day) {
531
+ const notes = await fetchUserPracticeNotes(day);
532
+ return { data: notes };
533
+ }
534
+
535
+ /**
536
+ * Retrieves the user's recent activity.
537
+ *
538
+ * Returns an object containing recent practice activity.
539
+ *
540
+ * @async
541
+ * @returns {Promise&lt;{ data: Object[] }>} - A promise that resolves to an object containing recent activity items.
542
+ *
543
+ * @example
544
+ * // Fetch recent practice activity
545
+ * getRecentActivity()
546
+ * .then(({ data }) => console.log("Recent activity:", data))
547
+ * .catch(error => console.error("Failed to get recent activity:", error));
548
+ */
549
+ export async function getRecentActivity() {
550
+ return { data: recentActivity };
551
+ }
552
+
553
+ /**
554
+ * Creates practice notes for a specific date.
555
+ *
556
+ * @param {Object} payload - The data required to create practice notes.
557
+ * @param {string} payload.date - The date for which to create notes (format: YYYY-MM-DD).
558
+ * @param {string} payload.notes - The notes content to be saved.
559
+ * @returns {Promise&lt;Object>} - A promise that resolves to the API response after creating the notes.
560
+ *
561
+ * @example
562
+ * createPracticeNotes({ date: '2025-04-10', notes: 'Worked on scales and arpeggios' })
563
+ * .then(response => console.log(response))
564
+ * .catch(error => console.error(error));
565
+ */
566
+ export async function createPracticeNotes(payload) {
567
+ const url = `/api/user/practices/v1/notes`
568
+ return await fetchHandler(url, 'POST', null, payload)
569
+ }
570
+
571
+ /**
572
+ * Updates existing practice notes for a specific date.
573
+ *
574
+ * @param {Object} payload - The data required to update practice notes.
575
+ * @param {string} payload.date - The date for which to update notes (format: YYYY-MM-DD).
576
+ * @param {string} payload.notes - The updated notes content.
577
+ * @returns {Promise&lt;Object>} - A promise that resolves to the API response after updating the notes.
578
+ *
579
+ * @example
580
+ * updatePracticeNotes({ date: '2025-04-10', notes: 'Updated: Focused on technique and timing' })
581
+ * .then(response => console.log(response))
582
+ * .catch(error => console.error(error));
583
+ */
584
+ export async function updatePracticeNotes(payload) {
585
+ const url = `/api/user/practices/v1/notes`
586
+ return await fetchHandler(url, 'PUT', null, payload)
587
+ }
588
+
589
+ function getStreaksAndMessage(practices) {
590
+ let { currentDailyStreak, currentWeeklyStreak, streakMessage } = calculateStreaks(practices, true);
591
+
592
+ return {
593
+ currentDailyStreak,
594
+ currentWeeklyStreak,
595
+ streakMessage,
596
+ };
597
+ }
598
+
599
+
600
+ async function getUserPracticeIds(day = new Date().toISOString().split('T')[0]) {
601
+ let data = await userActivityContext.getData();
602
+ let practices = data?.[DATA_KEY_PRACTICES] ?? {};
603
+ let userPracticesIds = [];
604
+ Object.keys(practices).forEach(date => {
605
+ if (date === day) {
606
+ practices[date].forEach(practice => userPracticesIds.push(practice.id));
607
+ }
608
+ });
609
+
610
+ return userPracticesIds;
611
+ }
612
+
613
+ function buildQueryString(ids, paramName = 'practice_ids') {
614
+ if (!ids.length) return '';
615
+ return '?' + ids.map(id => `${paramName}[]=${id}`).join('&amp;');
616
+ }
617
+
618
+ // Helper: Calculate streaks
619
+ function calculateStreaks(practices, includeStreakMessage = false) {
620
+ let currentDailyStreak = 0;
621
+ let currentWeeklyStreak = 0;
622
+ let lastActiveDay = null;
623
+ let streakMessage = '';
624
+
625
+ let sortedPracticeDays = Object.keys(practices)
626
+ .map(dateStr => {
627
+ const [year, month, day] = dateStr.split('-').map(Number);
628
+ const newDate = new Date();
629
+ newDate.setFullYear(year, month - 1, day);
630
+ return newDate;
631
+ })
632
+ .sort((a, b) => a - b);
633
+ if (sortedPracticeDays.length === 0) {
634
+ return { currentDailyStreak: 0, currentWeeklyStreak: 0, streakMessage: streakMessages.startStreak };
635
+ }
636
+ lastActiveDay = sortedPracticeDays[sortedPracticeDays.length - 1];
637
+
638
+ let dailyStreak = 0;
639
+ let prevDay = null;
640
+ sortedPracticeDays.forEach((currentDay) => {
641
+ if (prevDay === null || isNextDay(prevDay, currentDay)) {
642
+ dailyStreak++;
643
+ } else {
644
+ dailyStreak = 1;
645
+ }
646
+ prevDay = currentDay;
647
+ });
648
+ currentDailyStreak = dailyStreak;
649
+
650
+ // Weekly streak calculation
651
+ let weekNumbers = new Set(sortedPracticeDays.map(date => getWeekNumber(date)));
652
+ let weeklyStreak = 0;
653
+ let lastWeek = null;
654
+ [...weekNumbers].sort((a, b) => b - a).forEach(week => {
655
+ if (lastWeek === null || week === lastWeek - 1) {
656
+ weeklyStreak++;
657
+ } else {
658
+ return;
659
+ }
660
+ lastWeek = week;
661
+ });
662
+ currentWeeklyStreak = weeklyStreak;
663
+
664
+ // Calculate streak message only if includeStreakMessage is true
665
+ if (includeStreakMessage) {
666
+ let today = new Date();
667
+ let yesterday = new Date(today);
668
+ yesterday.setDate(today.getDate() - 1);
669
+
670
+ let currentWeekStart = getMonday(today);
671
+ let lastWeekStart = new Date(currentWeekStart);
672
+ lastWeekStart.setDate(currentWeekStart.getDate() - 7);
673
+
674
+ let hasYesterdayPractice = sortedPracticeDays.some(date =>
675
+ isSameDate(date, yesterday)
676
+ );
677
+ let hasCurrentWeekPractice = sortedPracticeDays.some(date => date >= currentWeekStart);
678
+ let hasCurrentWeekPreviousPractice = sortedPracticeDays.some(date => date >= currentWeekStart &amp;&amp; date &lt; today);
679
+ let hasLastWeekPractice = sortedPracticeDays.some(date => date >= lastWeekStart &amp;&amp; date &lt; currentWeekStart);
680
+ let hasOlderPractice = sortedPracticeDays.some(date => date &lt; lastWeekStart );
681
+
682
+ if (isSameDate(lastActiveDay, today)) {
683
+ if (hasYesterdayPractice) {
684
+ streakMessage = streakMessages.dailyStreak(currentDailyStreak);
685
+ } else if (hasCurrentWeekPreviousPractice) {
686
+ streakMessage = streakMessages.weeklyStreak(currentWeeklyStreak);
687
+ } else if (hasLastWeekPractice) {
688
+ streakMessage = streakMessages.greatJobWeeklyStreak(currentWeeklyStreak);
689
+ } else {
690
+ streakMessage = streakMessages.dailyStreakShort(currentDailyStreak);
691
+ }
692
+ } else {
693
+ if ((hasYesterdayPractice &amp;&amp; currentDailyStreak >= 2) || (hasYesterdayPractice &amp;&amp; sortedPracticeDays.length == 1)
694
+ || (hasYesterdayPractice &amp;&amp; !hasLastWeekPractice &amp;&amp; hasOlderPractice)){
695
+ streakMessage = streakMessages.dailyStreakReminder(currentDailyStreak);
696
+ } else if (hasCurrentWeekPractice) {
697
+ streakMessage = streakMessages.weeklyStreakKeepUp(currentWeeklyStreak);
698
+ } else if (hasLastWeekPractice) {
699
+ streakMessage = streakMessages.weeklyStreakReminder(currentWeeklyStreak);
700
+ } else {
701
+ streakMessage = streakMessages.restartStreak;
702
+ }
703
+ }
704
+
705
+ }
706
+
707
+ return { currentDailyStreak, currentWeeklyStreak, streakMessage };
708
+ }
709
+
710
+
711
+
712
+
713
+
714
+
715
+
716
+
717
+
718
+ </code></pre>
719
+ </article>
720
+ </section>
721
+
722
+
723
+
724
+
725
+
726
+
727
+ </div>
728
+
729
+ <br class="clear">
730
+
731
+ <footer>
732
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.3</a> on Thu Apr 10 2025 11:52:29 GMT+0300 (Eastern European Summer Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
733
+ </footer>
734
+
735
+ <script>prettyPrint();</script>
736
+ <script src="scripts/polyfill.js"></script>
737
+ <script src="scripts/linenumber.js"></script>
738
+
739
+
740
+
741
+ </body>
742
+ </html>