musora-content-services 2.81.0 → 2.82.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 (220) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/.coderabbit.yaml +0 -0
  3. package/.editorconfig +0 -0
  4. package/.github/pull_request_template.md +0 -0
  5. package/.github/workflows/conventional-commits.yaml +0 -0
  6. package/.github/workflows/docs.js.yml +0 -0
  7. package/.github/workflows/node.js.yml +0 -0
  8. package/.prettierignore +0 -0
  9. package/.prettierrc +0 -0
  10. package/.yarnrc.yml +1 -0
  11. package/CHANGELOG.md +17 -0
  12. package/README.md +0 -0
  13. package/babel.config.cjs +0 -0
  14. package/docs/Content.html +0 -0
  15. package/docs/ContentOrganization.html +2 -2
  16. package/docs/Forums.html +2 -2
  17. package/docs/Gamification.html +2 -2
  18. package/docs/TestUser.html +2 -2
  19. package/docs/UserManagementSystem.html +2 -2
  20. package/docs/api_types.js.html +2 -2
  21. package/docs/config.js.html +5 -2
  22. package/docs/content-org_content-org.js.html +2 -2
  23. package/docs/content-org_guided-courses.ts.html +2 -2
  24. package/docs/content-org_learning-paths.ts.html +2 -2
  25. package/docs/content-org_playlists-types.js.html +2 -2
  26. package/docs/content-org_playlists.js.html +3 -2
  27. package/docs/content.js.html +88 -10
  28. package/docs/content_artist.ts.html +2 -2
  29. package/docs/content_content.ts.html +0 -0
  30. package/docs/content_genre.ts.html +2 -2
  31. package/docs/content_instructor.ts.html +2 -2
  32. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  33. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  34. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  35. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  36. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  37. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  38. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  39. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  40. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  41. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  42. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  43. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  44. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  45. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  46. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  47. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  48. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  49. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  50. package/docs/forums_categories.ts.html +2 -2
  51. package/docs/forums_discussions.js.html +0 -0
  52. package/docs/forums_forum.js.html +0 -0
  53. package/docs/forums_forums.ts.html +2 -2
  54. package/docs/forums_posts.ts.html +2 -2
  55. package/docs/forums_threads.ts.html +2 -2
  56. package/docs/gamification_awards.js.html +0 -0
  57. package/docs/gamification_awards.ts.html +2 -2
  58. package/docs/gamification_gamification.js.html +2 -2
  59. package/docs/gamification_types.js.html +0 -0
  60. package/docs/global.html +2 -2
  61. package/docs/index.html +2 -2
  62. package/docs/liveTesting.ts.html +2 -2
  63. package/docs/module-Accounts.html +2 -2
  64. package/docs/module-Artist.html +2 -2
  65. package/docs/module-Awards.html +2 -2
  66. package/docs/module-Categories.html +0 -0
  67. package/docs/module-Config.html +5 -4
  68. package/docs/module-Content-Services-V2.html +440 -9
  69. package/docs/module-ForumCategories.html +0 -0
  70. package/docs/module-ForumDiscussions.html +0 -0
  71. package/docs/module-Forums.html +2 -2
  72. package/docs/module-Genre.html +2 -2
  73. package/docs/module-GuidedCourses.html +2 -2
  74. package/docs/module-Instructor.html +2 -2
  75. package/docs/module-Interests.html +2 -2
  76. package/docs/module-LearningPaths.html +2 -2
  77. package/docs/module-Onboarding.html +2 -2
  78. package/docs/module-Payments.html +2 -2
  79. package/docs/module-Permissions.html +2 -2
  80. package/docs/module-Playlists.html +16 -16
  81. package/docs/module-ProgressRow.html +2 -2
  82. package/docs/module-Railcontent-Services.html +2 -2
  83. package/docs/module-Sanity-Services.html +1796 -55
  84. package/docs/module-Sessions.html +2 -2
  85. package/docs/module-Threads.html +0 -0
  86. package/docs/module-UserActivity.html +2 -2
  87. package/docs/module-UserChat.html +2 -2
  88. package/docs/module-UserManagement.html +2 -2
  89. package/docs/module-UserMemberships.html +2 -2
  90. package/docs/module-UserNotifications.html +2 -2
  91. package/docs/module-UserProfile.html +2 -2
  92. package/docs/progress-row_method-card.js.html +2 -2
  93. package/docs/railcontent.js.html +2 -2
  94. package/docs/sanity.js.html +130 -28
  95. package/docs/scripts/collapse.js +0 -0
  96. package/docs/scripts/commonNav.js +0 -0
  97. package/docs/scripts/linenumber.js +0 -0
  98. package/docs/scripts/nav.js +0 -0
  99. package/docs/scripts/polyfill.js +0 -0
  100. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  101. package/docs/scripts/prettify/lang-css.js +0 -0
  102. package/docs/scripts/prettify/prettify.js +0 -0
  103. package/docs/scripts/search.js +0 -0
  104. package/docs/styles/jsdoc.css +0 -0
  105. package/docs/styles/prettify.css +0 -0
  106. package/docs/userActivity.js.html +2 -2
  107. package/docs/user_account.ts.html +2 -2
  108. package/docs/user_chat.js.html +2 -2
  109. package/docs/user_interests.js.html +2 -2
  110. package/docs/user_management.js.html +2 -2
  111. package/docs/user_memberships.js.html +0 -0
  112. package/docs/user_memberships.ts.html +2 -2
  113. package/docs/user_notifications.js.html +2 -2
  114. package/docs/user_onboarding.ts.html +2 -2
  115. package/docs/user_payments.ts.html +2 -2
  116. package/docs/user_permissions.js.html +2 -2
  117. package/docs/user_profile.js.html +2 -2
  118. package/docs/user_sessions.js.html +2 -2
  119. package/docs/user_types.js.html +2 -2
  120. package/docs/user_user-management-system.js.html +2 -2
  121. package/jest.config.js +0 -0
  122. package/jsdoc.json +0 -0
  123. package/package.json +1 -1
  124. package/src/contentMetaData.js +0 -0
  125. package/src/contentTypeConfig.js +33 -1
  126. package/src/filterBuilder.js +22 -12
  127. package/src/index.d.ts +4 -0
  128. package/src/index.js +4 -0
  129. package/src/infrastructure/http/HttpClient.ts +0 -0
  130. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  131. package/src/infrastructure/http/index.ts +0 -0
  132. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  133. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  134. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  135. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  136. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  137. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  138. package/src/lib/httpHelper.js +0 -0
  139. package/src/lib/lastUpdated.js +4 -4
  140. package/src/services/api/types.js +0 -0
  141. package/src/services/api/types.ts +0 -0
  142. package/src/services/config.js +3 -0
  143. package/src/services/content/artist.ts +0 -0
  144. package/src/services/content/content.ts +0 -0
  145. package/src/services/content/genre.ts +0 -0
  146. package/src/services/content/instructor.ts +0 -0
  147. package/src/services/content-org/content-org.js +0 -0
  148. package/src/services/content-org/guided-courses.ts +0 -0
  149. package/src/services/content-org/learning-paths.ts +0 -0
  150. package/src/services/content-org/playlists-types.js +0 -0
  151. package/src/services/content-org/playlists.js +0 -0
  152. package/src/services/content.js +86 -8
  153. package/src/services/contentAggregator.js +4 -4
  154. package/src/services/contentLikes.js +0 -0
  155. package/src/services/contentProgress.js +16 -3
  156. package/src/services/dataContext.js +0 -0
  157. package/src/services/dateUtils.js +0 -0
  158. package/src/services/eventsAPI.js +0 -0
  159. package/src/services/forums/categories.ts +0 -0
  160. package/src/services/forums/forums.ts +0 -0
  161. package/src/services/forums/posts.ts +0 -0
  162. package/src/services/forums/threads.ts +0 -0
  163. package/src/services/forums/types.ts +0 -0
  164. package/src/services/gamification/awards.ts +0 -0
  165. package/src/services/gamification/gamification.js +0 -0
  166. package/src/services/imageSRCBuilder.js +0 -0
  167. package/src/services/imageSRCVerify.js +0 -0
  168. package/src/services/liveTesting.ts +0 -0
  169. package/src/services/permissions/PermissionsAdapter.ts +111 -0
  170. package/src/services/permissions/PermissionsAdapterFactory.ts +71 -0
  171. package/src/services/permissions/PermissionsV1Adapter.ts +232 -0
  172. package/src/services/permissions/PermissionsV2Adapter.ts +226 -0
  173. package/src/services/permissions/README.md +139 -0
  174. package/src/services/permissions/index.ts +65 -0
  175. package/src/services/progress-row/method-card.js +0 -0
  176. package/src/services/railcontent.js +0 -0
  177. package/src/services/recommendations.js +0 -0
  178. package/src/services/sanity.js +103 -40
  179. package/src/services/types.js +1 -0
  180. package/src/services/user/account.ts +0 -0
  181. package/src/services/user/chat.js +0 -0
  182. package/src/services/user/interests.js +0 -0
  183. package/src/services/user/management.js +0 -0
  184. package/src/services/user/memberships.ts +0 -0
  185. package/src/services/user/notifications.js +0 -0
  186. package/src/services/user/onboarding.ts +0 -0
  187. package/src/services/user/payments.ts +0 -0
  188. package/src/services/user/permissions.js +1 -1
  189. package/src/services/user/profile.js +0 -0
  190. package/src/services/user/sessions.js +0 -0
  191. package/src/services/user/types.d.ts +0 -0
  192. package/src/services/user/types.js +0 -0
  193. package/src/services/user/user-management-system.js +0 -0
  194. package/src/services/userActivity.js +0 -0
  195. package/test/HttpClient.test.js +0 -0
  196. package/test/content.test.js +5 -0
  197. package/test/contentLikes.test.js +0 -0
  198. package/test/contentProgress.test.js +0 -0
  199. package/test/dataContext.test.js +0 -0
  200. package/test/forum.test.js +1 -1
  201. package/test/imageSRCBuilder.test.js +0 -0
  202. package/test/imageSRCVerify.test.js +0 -0
  203. package/test/initializeTests.js +5 -3
  204. package/test/learningPaths.test.js +0 -0
  205. package/test/lib/lastUpdated.test.js +0 -0
  206. package/test/live/contentProgressLive.test.js +0 -0
  207. package/test/live/railcontentLive.test.js +0 -0
  208. package/test/localStorageMock.js +0 -0
  209. package/test/log.js +0 -0
  210. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  211. package/test/mockData/mockData_progress_content.json +0 -0
  212. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  213. package/test/mockData/mockData_user_practices.json +0 -0
  214. package/test/notifications.test.js +0 -0
  215. package/test/progressRows.test.js +0 -0
  216. package/test/sanityQueryService.test.js +0 -0
  217. package/test/streakMessage.test.js +0 -0
  218. package/test/user/permissions.test.js +0 -0
  219. package/test/userActivity.test.js +0 -0
  220. package/tools/generate-index.cjs +5 -0
@@ -29,7 +29,7 @@ import { globalConfig } from './config.js'
29
29
 
30
30
  import { fetchNextContentDataForParent, fetchHandler } from './railcontent.js'
31
31
  import { arrayToStringRepresentation, FilterBuilder } from '../filterBuilder.js'
32
- import { fetchUserPermissions } from './user/permissions.js'
32
+ import { getPermissionsAdapter } from './permissions/index.ts'
33
33
  import { getAllCompleted, getAllStarted, getAllStartedOrCompleted } from './contentProgress.js'
34
34
  import { fetchRecentActivitiesActiveTabs } from './userActivity.js'
35
35
 
@@ -491,7 +491,7 @@ export async function fetchByRailContentIds(
491
491
  }
492
492
  return results.map(liveProcess)
493
493
  }
494
- const results = await fetchSanity(query, true, { customPostProcess: customPostProcess })
494
+ const results = await fetchSanity(query, true, { customPostProcess: customPostProcess, processNeedAccess: true })
495
495
 
496
496
  const sortFuction = function compare(a, b) {
497
497
  const indexA = ids.indexOf(a['id'])
@@ -511,10 +511,8 @@ export async function fetchContentRows(brand, pageName, contentRowSlug) {
511
511
  if (pageName === 'lessons') pageName = 'lesson'
512
512
  if (pageName === 'songs') pageName = 'song'
513
513
  const rowString = contentRowSlug ? ` && slug.current == "${contentRowSlug.toLowerCase()}"` : ''
514
- const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`, {
515
- pullFutureContent: true,
516
- }).buildFilter()
517
- const childFilter = await new FilterBuilder('', { isChildrenFilter: true }).buildFilter()
514
+ const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`, {pullFutureContent: true, showMembershipRestrictedContent: true}).buildFilter()
515
+ const childFilter = await new FilterBuilder('', {isChildrenFilter: true, showMembershipRestrictedContent: true}).buildFilter()
518
516
  const query = `*[_type == 'recommended-content-row' && brand == '${brand}' && type == '${pageName}'${rowString}]{
519
517
  brand,
520
518
  name,
@@ -527,7 +525,7 @@ export async function fetchContentRows(brand, pageName, contentRowSlug) {
527
525
  'lesson_count': coalesce(count(*[${lessonCountFilter}]), 0),
528
526
  },
529
527
  }`
530
- return fetchSanity(query, true)
528
+ return fetchSanity(query, true, {processNeedAccess: true})
531
529
  }
532
530
 
533
531
  /**
@@ -816,7 +814,9 @@ export async function fetchAllFilterOptions(
816
814
 
817
815
  const includedFieldsFilter = filters?.length ? filtersToGroq(filters) : undefined
818
816
  const progressFilter = progressIds ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
819
- const isAdmin = (await fetchUserPermissions()).isAdmin
817
+ const adapter = getPermissionsAdapter()
818
+ const userPermissionsData = await adapter.fetchUserPermissions()
819
+ const isAdmin = adapter.isAdmin(userPermissionsData)
820
820
 
821
821
  const constructCommonFilter = (excludeFilter) => {
822
822
  const filterWithoutOption = excludeFilter
@@ -1085,7 +1085,7 @@ export async function jumpToContinueContent(railcontentId) {
1085
1085
  * .catch(error => console.error(error));
1086
1086
  */
1087
1087
  export async function fetchLessonContent(railContentId, { addParent = false } = {}) {
1088
- const filterParams = { isSingle: true, pullFutureContent: true }
1088
+ const filterParams = { isSingle: true, pullFutureContent: true, showMembershipRestrictedContent: true }
1089
1089
 
1090
1090
  const parentQuery = addParent
1091
1091
  ? `"parent_content_data": *[railcontent_id in [...(^.parent_content_data[].id)]]{
@@ -1146,7 +1146,7 @@ export async function fetchLessonContent(railContentId, { addParent = false } =
1146
1146
  return result
1147
1147
  }
1148
1148
 
1149
- return fetchSanity(query, false, { customPostProcess: chapterProcess })
1149
+ return fetchSanity(query, false, { customPostProcess: chapterProcess, processNeedAccess: true })
1150
1150
  }
1151
1151
 
1152
1152
  /**
@@ -1237,16 +1237,21 @@ async function fetchRelatedByLicense(railcontentId, brand, onlyUseSongTypes, cou
1237
1237
  export async function fetchSiblingContent(railContentId, brand = null) {
1238
1238
  const filterGetParent = await new FilterBuilder(`references(^._id) && _type == ^.parent_type`, {
1239
1239
  pullFutureContent: true,
1240
+ showMembershipRestrictedContent: true // Show parent even without permissions
1240
1241
  }).buildFilter()
1241
1242
  const filterForParentList = await new FilterBuilder(
1242
1243
  `references(^._id) && _type == ^.parent_type`,
1243
1244
  {
1244
1245
  pullFutureContent: true,
1245
1246
  isParentFilter: true,
1247
+ showMembershipRestrictedContent: true // Show parent even without permissions
1246
1248
  }
1247
1249
  ).buildFilter()
1248
1250
 
1249
- const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
1251
+ const childrenFilter = await new FilterBuilder(``, {
1252
+ isChildrenFilter: true,
1253
+ showMembershipRestrictedContent: true // Show all lessons in sidebar, need_access applied on individual page
1254
+ }).buildFilter()
1250
1255
 
1251
1256
  const brandString = brand ? ` && brand == "${brand}"` : ''
1252
1257
  const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, difficulty_string, artist->, "permission_id": permission[]->railcontent_id, "genre": genre[]->name, "parent_id": parent_content_data[0].id`
@@ -1260,7 +1265,7 @@ export async function fetchSiblingContent(railContentId, brand = null) {
1260
1265
  "related_lessons" : *[${filterGetParent}][0].child[${childrenFilter}]->{${queryFields}}
1261
1266
  }`
1262
1267
 
1263
- let result = await fetchSanity(query, false)
1268
+ let result = await fetchSanity(query, false, { processNeedAccess: true })
1264
1269
 
1265
1270
  //there's no way in sanity to retrieve the index of an array, so we must calculate after fetch
1266
1271
  if (result['for-calculations'] && result['for-calculations']['parents-list']) {
@@ -1288,10 +1293,12 @@ export async function fetchRelatedLessons(railContentId) {
1288
1293
  const defaultFilterFields = `_type==^._type && brand == ^.brand && railcontent_id != ${railContentId}`
1289
1294
 
1290
1295
  const filterSameArtist = await new FilterBuilder(
1291
- `${defaultFilterFields} && references(^.artist->_id)`
1296
+ `${defaultFilterFields} && references(^.artist->_id)`,
1297
+ { showMembershipRestrictedContent: true }
1292
1298
  ).buildFilter()
1293
1299
  const filterSameGenre = await new FilterBuilder(
1294
- `${defaultFilterFields} && references(^.genre[]->_id)`
1300
+ `${defaultFilterFields} && references(^.genre[]->_id)`,
1301
+ { showMembershipRestrictedContent: true }
1295
1302
  ).buildFilter()
1296
1303
 
1297
1304
  const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type, "genre": genre[]->name`
@@ -1303,7 +1310,7 @@ export async function fetchRelatedLessons(railContentId) {
1303
1310
  ...(*[${filterSameGenre}]{${queryFields}}|order(published_on desc, title asc)[0...10]),
1304
1311
  ])[0...10]}`
1305
1312
 
1306
- return await fetchSanity(query, false)
1313
+ return await fetchSanity(query, false, { processNeedAccess: true })
1307
1314
  }
1308
1315
 
1309
1316
  /**
@@ -1633,13 +1640,13 @@ export async function fetchSanity(
1633
1640
  body: JSON.stringify({ query: query }),
1634
1641
  }
1635
1642
 
1643
+ const adapter = getPermissionsAdapter()
1636
1644
  let promisesResult = await Promise.all([
1637
1645
  fetch(url, options),
1638
- processNeedAccess ? fetchUserPermissions() : null,
1646
+ processNeedAccess ? adapter.fetchUserPermissions() : null,
1639
1647
  ])
1640
1648
  const response = promisesResult[0]
1641
- const userPermissions = promisesResult[1]?.permissions
1642
- const isAdmin = promisesResult[1]?.isAdmin
1649
+ const userPermissions = promisesResult[1]
1643
1650
 
1644
1651
  if (!response.ok) {
1645
1652
  throw new Error(`Sanity API error: ${response.status} - ${response.statusText}`)
@@ -1651,7 +1658,7 @@ export async function fetchSanity(
1651
1658
  throw new Error('No results found')
1652
1659
  }
1653
1660
  results = processNeedAccess
1654
- ? await needsAccessDecorator(results, userPermissions, isAdmin)
1661
+ ? await needsAccessDecorator(results, userPermissions)
1655
1662
  : results
1656
1663
  results = processPageType
1657
1664
  ? pageTypeDecorator(results)
@@ -1669,7 +1676,17 @@ export async function fetchSanity(
1669
1676
  function contentResultsDecorator(results, fieldName, callback) {
1670
1677
  if (Array.isArray(results)) {
1671
1678
  results.forEach((result) => {
1672
- result[fieldName] = callback(result)
1679
+ // Check if this is a content row structure
1680
+ if (result.content && Array.isArray(result.content)) {
1681
+ // Content rows structure: array of rows, each with a content array
1682
+ result.content.forEach((contentItem) => {
1683
+ if (contentItem) {
1684
+ contentItem[fieldName] = callback(contentItem)
1685
+ }
1686
+ })
1687
+ } else {
1688
+ result[fieldName] = callback(result)
1689
+ }
1673
1690
  })
1674
1691
  } else if (results.entity && Array.isArray(results.entity)) {
1675
1692
  // Group By
@@ -1699,28 +1716,20 @@ function pageTypeDecorator(results) {
1699
1716
  })
1700
1717
  }
1701
1718
 
1702
- function needsAccessDecorator(results, userPermissions, isAdmin) {
1719
+
1720
+ function needsAccessDecorator(results, userPermissions) {
1703
1721
  if (globalConfig.sanityConfig.useDummyRailContentMethods) return results
1704
- userPermissions = new Set(userPermissions)
1722
+ const adapter = getPermissionsAdapter()
1705
1723
  return contentResultsDecorator(results, 'need_access', function (content) {
1706
- return doesUserNeedAccessToContent(content, userPermissions, isAdmin)
1724
+ return adapter.doesUserNeedAccess(content, userPermissions)
1707
1725
  })
1708
1726
  }
1709
1727
 
1710
- function doesUserNeedAccessToContent(result, userPermissions, isAdmin) {
1711
- if (isAdmin ?? false) {
1712
- return false
1713
- }
1714
- const permissions = new Set(result?.permission_id ?? [])
1715
- if (permissions.size === 0) {
1716
- return false
1717
- }
1718
- for (let permission of permissions) {
1719
- if (userPermissions.has(permission)) {
1720
- return false
1721
- }
1722
- }
1723
- return true
1728
+ function doesUserNeedAccessToContent(result, userPermissions) {
1729
+ // Legacy function - now delegates to adapter
1730
+ // Kept for backwards compatibility if used elsewhere
1731
+ const adapter = getPermissionsAdapter()
1732
+ return adapter.doesUserNeedAccess(result, userPermissions)
1724
1733
  }
1725
1734
 
1726
1735
  /**
@@ -1992,6 +2001,7 @@ export async function fetchTabData(
1992
2001
  includedFields = [],
1993
2002
  progressIds = undefined,
1994
2003
  progress = 'all',
2004
+ showMembershipRestrictedContent = false,
1995
2005
  } = {}
1996
2006
  ) {
1997
2007
  const start = (page - 1) * limit
@@ -2028,7 +2038,7 @@ export async function fetchTabData(
2028
2038
  let filter = ''
2029
2039
 
2030
2040
  filter = `brand == "${brand}" && (defined(railcontent_id)) ${includedFieldsFilter} ${progressFilter}`
2031
- const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
2041
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true, showMembershipRestrictedContent: true }).buildFilter()
2032
2042
  const childrenFields = await getChildFieldsForContentType('tab-data')
2033
2043
  const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`).buildFilter()
2034
2044
  entityFieldsString = ` ${fieldsString}
@@ -2043,14 +2053,14 @@ export async function fetchTabData(
2043
2053
  ),
2044
2054
  length_in_seconds
2045
2055
  ),`
2046
- const filterWithRestrictions = await new FilterBuilder(filter, {}).buildFilter()
2056
+ const filterWithRestrictions = await new FilterBuilder(filter, {showMembershipRestrictedContent: true}).buildFilter()
2047
2057
  query = buildEntityAndTotalQuery(filterWithRestrictions, entityFieldsString, {
2048
2058
  sortOrder: sortOrder,
2049
2059
  start: start,
2050
2060
  end: end,
2051
2061
  })
2052
2062
 
2053
- let results = await fetchSanity(query, true)
2063
+ let results = await fetchSanity(query, true, {processNeedAccess: true});
2054
2064
 
2055
2065
  if (['recent', 'incomplete', 'completed'].includes(progress) && results.entity.length > 1) {
2056
2066
  const orderMap = new Map(progressIds.map((id, index) => [id, index]))
@@ -2162,3 +2172,56 @@ export async function fetchMethodV2Structure(brand) {
2162
2172
  }`
2163
2173
  return await fetchSanity(query, false)
2164
2174
  }
2175
+
2176
+ /**
2177
+ * Fetch content owned by the user (excluding membership content).
2178
+ * Shows only content accessible through purchases/entitlements, not through membership.
2179
+ *
2180
+ * @param {string} brand - The brand to filter content by
2181
+ * @param {Object} options - Fetch options
2182
+ * @param {Array<string>} options.type - Content type(s) to filter (optional array, default: [])
2183
+ * @param {number} options.page - Page number (default: 1)
2184
+ * @param {number} options.limit - Items per page (default: 10)
2185
+ * @param {string} options.sort - Sort field and direction (default: '-published_on')
2186
+ * @returns {Promise<Object>} Object with 'entity' (content array) and 'total' (count)
2187
+ */
2188
+ export async function fetchOwnedContent(
2189
+ brand,
2190
+ {
2191
+ type = [],
2192
+ page = 1,
2193
+ limit = 10,
2194
+ sort = '-published_on',
2195
+ } = {}
2196
+ ) {
2197
+ const start = (page - 1) * limit
2198
+ const end = start + limit
2199
+
2200
+ // Determine the sort order
2201
+ const sortOrder = getSortOrder(sort, brand)
2202
+
2203
+ // Build the type filter
2204
+ let typeFilter = ''
2205
+ if (type.length > 0) {
2206
+ const typesString = type.map(t => `'${t}'`).join(', ')
2207
+ typeFilter = `&& _type in [${typesString}]`
2208
+ }
2209
+
2210
+ // Build the base filter
2211
+ const filter = `brand == "${brand}" ${typeFilter}`
2212
+
2213
+ // Apply owned content filter
2214
+ const filterWithRestrictions = await new FilterBuilder(filter, {
2215
+ showOnlyOwnedContent: true, // Key parameter: exclude membership content
2216
+ }).buildFilter()
2217
+
2218
+ const fieldsString = DEFAULT_FIELDS.join(',')
2219
+
2220
+ const query = buildEntityAndTotalQuery(filterWithRestrictions, fieldsString, {
2221
+ sortOrder: sortOrder,
2222
+ start: start,
2223
+ end: end,
2224
+ })
2225
+
2226
+ return fetchSanity(query, true)
2227
+ }
@@ -39,4 +39,5 @@
39
39
  * @property {Object} localStorage - Cache to use for localStorage
40
40
  * @property {boolean} isMA - Variable that tells if the library is used by MA or FEW
41
41
  * @property {string} localTimezoneString - The local timezone string in format: America/Vancouver
42
+ * @property {('v1'|'v2')} [permissionsVersion='v1'] - Permissions system version to use ('v1' or 'v2')
42
43
  */
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -20,7 +20,7 @@ let lastUpdatedKey = `userPermissions_lastUpdated`
20
20
  * @returns {Promise<UserPermissions>} - The user permissions data.
21
21
  */
22
22
  export async function fetchUserPermissions() {
23
- if (!userPermissionsPromise || wasLastUpdateOlderThanXSeconds(10, lastUpdatedKey)) {
23
+ if (!userPermissionsPromise || await wasLastUpdateOlderThanXSeconds(10, lastUpdatedKey)) {
24
24
  userPermissionsPromise = fetchUserPermissionsData()
25
25
  setLastUpdatedTime(lastUpdatedKey)
26
26
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,6 +1,11 @@
1
1
  import { initializeTestService } from './initializeTests.js'
2
2
  import {getContentRows, getNewAndUpcoming, getScheduleContentRows, getTabResults} from '../src/services/content.js'
3
3
 
4
+ // Mock fetchContentProgress before other modules load
5
+ jest.mock('../src/services/railcontent.js', () => ({
6
+ ...jest.requireActual('../src/services/railcontent.js'),
7
+ fetchContentProgress: jest.fn().mockResolvedValue({ version: 1, data: {} })
8
+ }))
4
9
 
5
10
  const railContentModule = require('../src/services/railcontent.js')
6
11
 
File without changes
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  import { initializeTestService } from './initializeTests.js'
2
2
  import { getLessonContentRows, getTabResults } from '../src/services/content.js'
3
- import {getActiveDiscussions} from "../src/services/forum";
3
+ import {getActiveDiscussions} from "../src/services/forums/forums";
4
4
 
5
5
  describe('forum', function () {
6
6
  beforeEach(() => {
File without changes
File without changes
@@ -45,7 +45,9 @@ export async function initializeTestService(useLive = false, isAdmin = false) {
45
45
  isMA: true,
46
46
  }
47
47
  initializeService(config)
48
- let mock = jest.spyOn(railContentModule, 'fetchUserPermissionsData')
49
- let testData = { permissions: [78, 91, 92], isAdmin: isAdmin }
50
- mock.mockImplementation(() => testData)
48
+
49
+ // Mock user permissions
50
+ let permissionsMock = jest.spyOn(railContentModule, 'fetchUserPermissionsData')
51
+ let permissionsData = { permissions: [108, 91, 92], isAdmin: isAdmin }
52
+ permissionsMock.mockImplementation(() => permissionsData)
51
53
  }
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
@@ -73,6 +73,11 @@ treeElements.forEach((treeNode) => {
73
73
  if (fs.lstatSync(filePath).isFile()) {
74
74
  addFunctionsToFileExports(filePath, treeNode)
75
75
  } else if (fs.lstatSync(filePath).isDirectory()) {
76
+ // Skip the permissions directory - it has its own index.ts barrel export
77
+ if (treeNode === 'permissions') {
78
+ return
79
+ }
80
+
76
81
  const subDir = fs.readdirSync(filePath)
77
82
  subDir.forEach((subFile) => {
78
83
  const filePath = path.join(servicesDir, treeNode, subFile)