musora-content-services 2.11.1 → 2.12.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 (162) hide show
  1. package/.coderabbit.yaml +0 -0
  2. package/.editorconfig +0 -0
  3. package/.github/pull_request_template.md +0 -0
  4. package/.github/workflows/conventional-commits.yaml +0 -0
  5. package/.github/workflows/node.js.yml +0 -0
  6. package/.prettierignore +0 -0
  7. package/.prettierrc +0 -0
  8. package/.yarnrc.yml +1 -0
  9. package/CHANGELOG.md +14 -0
  10. package/README.md +0 -0
  11. package/babel.config.cjs +0 -0
  12. package/docs/Content-Organization.html +0 -0
  13. package/docs/ContentOrganization.html +0 -0
  14. package/docs/Gamification.html +0 -0
  15. package/docs/UserManagement.html +0 -0
  16. package/docs/UserManagementSystem.html +0 -0
  17. package/docs/api_types.js.html +0 -0
  18. package/docs/config.js.html +0 -0
  19. package/docs/content-org_content-org.js.html +0 -0
  20. package/docs/content-org_playlists-types.js.html +0 -0
  21. package/docs/content-org_playlists.js.html +0 -0
  22. package/docs/content.js.html +0 -0
  23. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  24. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  25. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  26. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  27. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  28. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  29. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  30. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  33. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  34. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  35. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  36. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  37. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  38. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  39. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  40. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  41. package/docs/gamification_awards.js.html +0 -0
  42. package/docs/gamification_gamification.js.html +0 -0
  43. package/docs/gamification_types.js.html +0 -0
  44. package/docs/global.html +0 -0
  45. package/docs/global.html#User +0 -0
  46. package/docs/index.html +0 -0
  47. package/docs/module-Awards.html +0 -0
  48. package/docs/module-Config.html +0 -0
  49. package/docs/module-Content-Services-V2.html +0 -0
  50. package/docs/module-Interests.html +0 -0
  51. package/docs/module-Notifications.html +0 -0
  52. package/docs/module-Permissions.html +0 -0
  53. package/docs/module-Playlists.html +0 -0
  54. package/docs/module-Railcontent-Services.html +0 -0
  55. package/docs/module-Sanity-Services.html +0 -0
  56. package/docs/module-Session-Management.html +0 -0
  57. package/docs/module-Sessions.html +0 -0
  58. package/docs/module-User-Activity.html +0 -0
  59. package/docs/module-User-Management.html +0 -0
  60. package/docs/module-User-Permissions.html +0 -0
  61. package/docs/module-UserActivity.html +0 -0
  62. package/docs/module-UserManagement.html +0 -0
  63. package/docs/module-UserNotifications.html +0 -0
  64. package/docs/module-UserProfile.html +0 -0
  65. package/docs/railcontent.js.html +0 -0
  66. package/docs/sanity.js.html +0 -0
  67. package/docs/scripts/collapse.js +0 -0
  68. package/docs/scripts/commonNav.js +0 -0
  69. package/docs/scripts/linenumber.js +0 -0
  70. package/docs/scripts/nav.js +0 -0
  71. package/docs/scripts/polyfill.js +0 -0
  72. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  73. package/docs/scripts/prettify/lang-css.js +0 -0
  74. package/docs/scripts/prettify/prettify.js +0 -0
  75. package/docs/scripts/search.js +0 -0
  76. package/docs/styles/jsdoc.css +0 -0
  77. package/docs/styles/prettify.css +0 -0
  78. package/docs/types.js.html +0 -0
  79. package/docs/userActivity.js.html +0 -0
  80. package/docs/user_interests.js.html +0 -0
  81. package/docs/user_management.js.html +0 -0
  82. package/docs/user_notifications.js.html +0 -0
  83. package/docs/user_permissions.js.html +0 -0
  84. package/docs/user_profile.js.html +0 -0
  85. package/docs/user_sessions.js.html +0 -0
  86. package/docs/user_types.js.html +0 -0
  87. package/docs/user_user-management-system.js.html +0 -0
  88. package/docs/user_user-management.js.html +0 -0
  89. package/jest.config.js +0 -0
  90. package/jsdoc.json +0 -0
  91. package/link_mcs.sh +0 -0
  92. package/package.json +1 -1
  93. package/src/contentMetaData.js +0 -0
  94. package/src/filterBuilder.js +0 -0
  95. package/src/index.d.ts +2 -0
  96. package/src/index.js +2 -0
  97. package/src/infrastructure/http/HttpClient.ts +0 -0
  98. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  99. package/src/infrastructure/http/index.ts +0 -0
  100. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  101. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  102. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  103. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  104. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  105. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  106. package/src/lib/httpHelper.js +0 -0
  107. package/src/lib/lastUpdated.js +0 -0
  108. package/src/services/api/types.js +0 -0
  109. package/src/services/config.js +0 -0
  110. package/src/services/content-org/content-org.js +0 -0
  111. package/src/services/content-org/playlists-types.js +0 -0
  112. package/src/services/content-org/playlists.js +0 -0
  113. package/src/services/content.js +1 -1
  114. package/src/services/contentAggregator.js +0 -0
  115. package/src/services/contentLikes.js +0 -0
  116. package/src/services/contentProgress.js +35 -1
  117. package/src/services/dataContext.js +0 -0
  118. package/src/services/dateUtils.js +0 -0
  119. package/src/services/forum.js +0 -0
  120. package/src/services/gamification/awards.js +0 -0
  121. package/src/services/gamification/gamification.js +0 -0
  122. package/src/services/gamification/types.js +0 -0
  123. package/src/services/imageSRCBuilder.js +0 -0
  124. package/src/services/imageSRCVerify.js +0 -0
  125. package/src/services/railcontent.js +0 -0
  126. package/src/services/recommendations.js +0 -0
  127. package/src/services/sanity.js +40 -11
  128. package/src/services/types.js +0 -0
  129. package/src/services/user/chat.js +0 -0
  130. package/src/services/user/interests.js +0 -0
  131. package/src/services/user/management.js +0 -0
  132. package/src/services/user/notifications.js +4 -3
  133. package/src/services/user/permissions.js +0 -0
  134. package/src/services/user/profile.js +0 -0
  135. package/src/services/user/sessions.js +0 -0
  136. package/src/services/user/types.js +0 -0
  137. package/src/services/user/user-management-system.js +0 -0
  138. package/src/services/userActivity.js +0 -0
  139. package/test/HttpClient.test.js +0 -0
  140. package/test/content.test.js +0 -0
  141. package/test/contentLikes.test.js +0 -0
  142. package/test/contentProgress.test.js +0 -0
  143. package/test/dataContext.test.js +0 -0
  144. package/test/forum.test.js +0 -0
  145. package/test/imageSRCBuilder.test.js +0 -0
  146. package/test/imageSRCVerify.test.js +0 -0
  147. package/test/initializeTests.js +0 -0
  148. package/test/lib/lastUpdated.test.js +0 -0
  149. package/test/live/contentProgressLive.test.js +0 -0
  150. package/test/live/railcontentLive.test.js +0 -0
  151. package/test/localStorageMock.js +0 -0
  152. package/test/log.js +0 -0
  153. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  154. package/test/mockData/mockData_progress_content.json +0 -0
  155. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  156. package/test/mockData/mockData_user_practices.json +0 -0
  157. package/test/progressRows.test.js +0 -0
  158. package/test/sanityQueryService.test.js +0 -0
  159. package/test/streakMessage.test.js +0 -0
  160. package/test/user/permissions.test.js +0 -0
  161. package/test/userActivity.test.js +0 -0
  162. package/tools/generate-index.cjs +0 -0
package/.coderabbit.yaml CHANGED
File without changes
package/.editorconfig CHANGED
File without changes
File without changes
File without changes
File without changes
package/.prettierignore CHANGED
File without changes
package/.prettierrc CHANGED
File without changes
package/.yarnrc.yml ADDED
@@ -0,0 +1 @@
1
+ nodeLinker: node-modules
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.12.0](https://github.com/railroadmedia/musora-content-services/compare/v2.11.0...v2.12.0) (2025-06-25)
6
+
7
+
8
+ ### Features
9
+
10
+ * **MU2-478:** simplified version of getAllStartedOrCompleted method ([b58b97b](https://github.com/railroadmedia/musora-content-services/commit/b58b97bcf95db7ad71019848dbf5e5b508adeeb8))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **MU2-608:** Sort recent lessons by user progress ([e966ffc](https://github.com/railroadmedia/musora-content-services/commit/e966ffcd87847ee6b72d35bed84967ee8fd1db44))
16
+ * **MU2-730:** Pagination on fetchNotifications method ([37e95db](https://github.com/railroadmedia/musora-content-services/commit/37e95dbaacca06a1e408db8a5d6dead3dfb23550))
17
+ * Pagination on fetchNotifications method ([9e613b9](https://github.com/railroadmedia/musora-content-services/commit/9e613b9227bb188814ebc2d8c084c18e115fbcc3))
18
+
5
19
  ### [2.11.1](https://github.com/railroadmedia/musora-content-services/compare/v2.11.0...v2.11.1) (2025-06-24)
6
20
 
7
21
  ## [2.11.0](https://github.com/railroadmedia/musora-content-services/compare/v2.10.0...v2.11.0) (2025-06-24)
package/README.md CHANGED
File without changes
package/babel.config.cjs CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/docs/global.html CHANGED
File without changes
File without changes
package/docs/index.html CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/jest.config.js CHANGED
File without changes
package/jsdoc.json CHANGED
File without changes
package/link_mcs.sh CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.11.1",
3
+ "version": "2.12.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
File without changes
File without changes
package/src/index.d.ts CHANGED
@@ -59,6 +59,7 @@ import {
59
59
  getProgressStateByIds,
60
60
  getResumeTimeSeconds,
61
61
  getResumeTimeSecondsByIds,
62
+ getStartedOrCompletedProgressOnly,
62
63
  recordWatchSession
63
64
  } from './services/contentProgress.js';
64
65
 
@@ -419,6 +420,7 @@ declare module 'musora-content-services' {
419
420
  getResumeTimeSecondsByIds,
420
421
  getScheduleContentRows,
421
422
  getSortOrder,
423
+ getStartedOrCompletedProgressOnly,
422
424
  getTabResults,
423
425
  getTimeRemainingUntilLocal,
424
426
  getUserMonthlyStats,
package/src/index.js CHANGED
@@ -59,6 +59,7 @@ import {
59
59
  getProgressStateByIds,
60
60
  getResumeTimeSeconds,
61
61
  getResumeTimeSecondsByIds,
62
+ getStartedOrCompletedProgressOnly,
62
63
  recordWatchSession
63
64
  } from './services/contentProgress.js';
64
65
 
@@ -418,6 +419,7 @@ export {
418
419
  getResumeTimeSecondsByIds,
419
420
  getScheduleContentRows,
420
421
  getSortOrder,
422
+ getStartedOrCompletedProgressOnly,
421
423
  getTabResults,
422
424
  getTimeRemainingUntilLocal,
423
425
  getUserMonthlyStats,
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -19,7 +19,7 @@ import {recommendations} from "./recommendations";
19
19
 
20
20
 
21
21
  export async function getLessonContentRows (brand='drumeo', pageName = 'lessons') {
22
- let recentContentIds = await fetchRecent(brand, pageName, { progress: 'recent' });
22
+ let recentContentIds = await fetchRecent(brand, pageName, { progress: 'recent', limit: 10 });
23
23
 
24
24
  let contentRows = await getContentRows(brand, pageName);
25
25
  contentRows = Array.isArray(contentRows) ? contentRows : [];
File without changes
File without changes
@@ -118,7 +118,7 @@ export async function getAllStartedOrCompleted({ limit = null, onlyIds = true, b
118
118
  const isRelevantStatus =
119
119
  item[DATA_KEY_STATUS] === STATE_STARTED || item[DATA_KEY_STATUS] === STATE_COMPLETED
120
120
  const isRecent = item[DATA_KEY_LAST_UPDATED_TIME] >= oneMonthAgoInSeconds
121
- const isCorrectBrand = !brand || item.b === brand
121
+ const isCorrectBrand = !brand || !item.b || item.b === brand
122
122
  const isNotExcluded = !excludedSet.has(id)
123
123
  return isRelevantStatus && isRecent && isCorrectBrand && isNotExcluded
124
124
  })
@@ -151,6 +151,40 @@ export async function getAllStartedOrCompleted({ limit = null, onlyIds = true, b
151
151
  }
152
152
  }
153
153
 
154
+ /**
155
+ * Simplified version of `getAllStartedOrCompleted`.
156
+ *
157
+ * Fetches content IDs and progress percentages for items that were
158
+ * started or completed.
159
+ *
160
+ * @param {Object} [options={}] - Optional filtering options.
161
+ * @param {string|null} [options.brand=null] - Brand to filter by (e.g., 'drumeo').
162
+ * @returns {Promise<Object>} - A map of content ID to progress value:
163
+ * {
164
+ * [id]: progressPercentage
165
+ * }
166
+ *
167
+ * @example
168
+ * const progressMap = await getStartedOrCompletedProgressOnly({ brand: 'drumeo' });
169
+ * console.log(progressMap[123]); // => 52
170
+ */
171
+ export async function getStartedOrCompletedProgressOnly({ brand = null} = {}) {
172
+ const data = await dataContext.getData()
173
+ const result = {}
174
+
175
+ Object.entries(data).forEach(([key, item]) => {
176
+ const id = parseInt(key)
177
+ const isRelevantStatus = item[DATA_KEY_STATUS] === STATE_STARTED || item[DATA_KEY_STATUS] === STATE_COMPLETED
178
+ const isCorrectBrand = !brand || item.b === brand
179
+
180
+ if (isRelevantStatus && isCorrectBrand) {
181
+ result[id] = item?.[DATA_KEY_PROGRESS] ?? 0
182
+ }
183
+ })
184
+
185
+ return result
186
+ }
187
+
154
188
  export async function assignmentStatusCompleted(assignmentId, parentContentId) {
155
189
  await dataContext.update(
156
190
  async function (localContext) {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -888,11 +888,11 @@ async function getProgressFilter(progress, progressIds) {
888
888
  return `&& !(railcontent_id in [${ids.join(',')}])`
889
889
  }
890
890
  case 'recent': {
891
- const ids = await getAllStartedOrCompleted()
891
+ const ids = progressIds !== undefined ? progressIds : await getAllStartedOrCompleted()
892
892
  return `&& (railcontent_id in [${ids.join(',')}])`
893
893
  }
894
894
  case 'incomplete': {
895
- const ids = await getAllStarted()
895
+ const ids = progressIds !== undefined ? progressIds :await getAllStarted()
896
896
  return `&& railcontent_id in [${ids.join(',')}]`
897
897
  }
898
898
  default:
@@ -2187,12 +2187,12 @@ async function buildQuery(
2187
2187
  function buildEntityAndTotalQuery(
2188
2188
  filter = '',
2189
2189
  fields = '...',
2190
- { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false }
2190
+ { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false, withoutPagination = false }
2191
2191
  ) {
2192
- const sortString = sortOrder ? `order(${sortOrder})` : ''
2193
- const countString = isSingle ? '[0...1]' : `[${start}...${end}]`
2192
+ const sortString = sortOrder ? ` | order(${sortOrder})` : ''
2193
+ const countString = isSingle ? '[0...1]' : (withoutPagination ? ``: `[${start}...${end}]`)
2194
2194
  const query = `{
2195
- "entity": *[${filter}] | ${sortString}${countString}
2195
+ "entity": *[${filter}] ${sortString}${countString}
2196
2196
  {
2197
2197
  ${fields}
2198
2198
  },
@@ -2311,17 +2311,32 @@ export async function fetchTabData(
2311
2311
  ) {
2312
2312
  const start = (page - 1) * limit
2313
2313
  const end = start + limit
2314
-
2314
+ let withoutPagination = false
2315
2315
  // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
2316
2316
  const includedFieldsFilter =
2317
2317
  includedFields.length > 0 ? filtersToGroq(includedFields, [], pageName) : ''
2318
2318
 
2319
+ let sortOrder = getSortOrder(sort, brand, '')
2320
+
2321
+ switch (progress) {
2322
+ case 'recent':
2323
+ progressIds = await getAllStartedOrCompleted({ brand, onlyIds: true });
2324
+ sortOrder = null;
2325
+ withoutPagination = true;
2326
+ break;
2327
+ case 'incomplete':
2328
+ progressIds = await getAllStarted();
2329
+ sortOrder = null;
2330
+ break;
2331
+ case 'completed':
2332
+ progressIds = await getAllCompleted();
2333
+ sortOrder = null;
2334
+ break;
2335
+ }
2336
+
2319
2337
  // limits the results to supplied progressIds for started & completed filters
2320
2338
  const progressFilter = await getProgressFilter(progress, progressIds)
2321
2339
 
2322
- // Determine the sort order
2323
- const sortOrder = getSortOrder(sort, brand, '')
2324
-
2325
2340
  let fields = DEFAULT_FIELDS
2326
2341
  let fieldsString = fields.join(',')
2327
2342
 
@@ -2348,9 +2363,23 @@ export async function fetchTabData(
2348
2363
  sortOrder: sortOrder,
2349
2364
  start: start,
2350
2365
  end: end,
2366
+ withoutPagination: withoutPagination,
2351
2367
  })
2352
2368
 
2353
- return fetchSanity(query, true)
2369
+ let results = await fetchSanity(query, true);
2370
+
2371
+ if (['recent', 'incomplete', 'completed'].includes(progress) && results.entity.length > 1) {
2372
+ const orderMap = new Map(progressIds.map((id, index) => [id, index]))
2373
+ results.entity = results.entity
2374
+ .sort((a, b) => {
2375
+ const aIdx = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
2376
+ const bIdx = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
2377
+ return aIdx - bIdx || new Date(b.published_on) - new Date(a.published_on);
2378
+ })
2379
+ .slice(start, end);
2380
+ }
2381
+
2382
+ return results;
2354
2383
  }
2355
2384
 
2356
2385
  export async function fetchRecent(
File without changes
File without changes
File without changes
File without changes
@@ -12,6 +12,7 @@ const baseUrl = `/api/notifications`
12
12
  * @param {Object} [options={}] - Options for fetching notifications.
13
13
  * @param {string} options.brand - The brand to filter notifications by. (Required)
14
14
  * @param {number} [options.limit=10] - The maximum number of notifications to fetch.
15
+ * @param {number} [options.page=1] - The page number for pagination.
15
16
  * @param {boolean} [options.onlyUnread=false] - Whether to fetch only unread notifications. If true, adds `unread=1` to the query.
16
17
  *
17
18
  * @returns {Promise<Array<Object>>} - A promise that resolves to an array of notifications.
@@ -19,17 +20,17 @@ const baseUrl = `/api/notifications`
19
20
  * @throws {Error} - Throws an error if the brand is not provided.
20
21
  *
21
22
  * @example
22
- * fetchNotifications({ brand: 'drumeo', limit: 5, onlyUnread: true })
23
+ * fetchNotifications({ brand: 'drumeo', limit: 5, onlyUnread: true, page: 2 })
23
24
  * .then(notifications => console.log(notifications))
24
25
  * .catch(error => console.error(error));
25
26
  */
26
- export async function fetchNotifications({ brand = null, limit = 10, onlyUnread = false } = {}) {
27
+ export async function fetchNotifications({ brand = null, limit = 10, onlyUnread = false, page = 1 } = {}) {
27
28
  if (!brand) {
28
29
  throw new Error('brand is required')
29
30
  }
30
31
 
31
32
  const unreadParam = onlyUnread ? '&unread=1' : ''
32
- const url = `${baseUrl}/v1?brand=${brand}${unreadParam}&limit=${limit}`
33
+ const url = `${baseUrl}/v1?brand=${brand}${unreadParam}&limit=${limit}&page=${page}`
33
34
  return fetchHandler(url, 'get')
34
35
  }
35
36
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/test/log.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes