musora-content-services 2.86.0 → 2.87.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 (82) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/CHANGELOG.md +14 -0
  3. package/docs/ContentOrganization.html +2 -2
  4. package/docs/Forums.html +2 -2
  5. package/docs/Gamification.html +2 -2
  6. package/docs/TestUser.html +2 -2
  7. package/docs/UserManagementSystem.html +2 -2
  8. package/docs/api_types.js.html +2 -2
  9. package/docs/config.js.html +5 -2
  10. package/docs/content-org_content-org.js.html +2 -2
  11. package/docs/content-org_guided-courses.ts.html +2 -2
  12. package/docs/content-org_learning-paths.ts.html +126 -24
  13. package/docs/content-org_playlists-types.js.html +2 -2
  14. package/docs/content-org_playlists.js.html +2 -2
  15. package/docs/content.js.html +88 -10
  16. package/docs/content_artist.ts.html +36 -41
  17. package/docs/content_genre.ts.html +29 -34
  18. package/docs/content_instructor.ts.html +25 -27
  19. package/docs/forums_categories.ts.html +22 -3
  20. package/docs/forums_forums.ts.html +2 -2
  21. package/docs/forums_posts.ts.html +2 -2
  22. package/docs/forums_threads.ts.html +9 -9
  23. package/docs/gamification_awards.ts.html +26 -12
  24. package/docs/gamification_gamification.js.html +2 -2
  25. package/docs/global.html +2 -2
  26. package/docs/index.html +2 -2
  27. package/docs/liveTesting.ts.html +2 -2
  28. package/docs/module-Accounts.html +14 -14
  29. package/docs/module-Artist.html +12 -10
  30. package/docs/module-Awards.html +106 -6
  31. package/docs/module-Config.html +5 -4
  32. package/docs/module-Content-Services-V2.html +440 -9
  33. package/docs/module-Forums.html +627 -63
  34. package/docs/module-Genre.html +6 -6
  35. package/docs/module-GuidedCourses.html +2 -2
  36. package/docs/module-Instructor.html +10 -10
  37. package/docs/module-Interests.html +2 -2
  38. package/docs/module-LearningPaths.html +640 -12
  39. package/docs/module-Onboarding.html +32 -8
  40. package/docs/module-Payments.html +2 -2
  41. package/docs/module-Permissions.html +2 -2
  42. package/docs/module-Playlists.html +2 -2
  43. package/docs/module-ProgressRow.html +2 -2
  44. package/docs/module-Railcontent-Services.html +2 -2
  45. package/docs/module-Sanity-Services.html +786 -1853
  46. package/docs/module-Sessions.html +2 -2
  47. package/docs/module-UserActivity.html +4 -4
  48. package/docs/module-UserChat.html +2 -2
  49. package/docs/module-UserManagement.html +2 -2
  50. package/docs/module-UserMemberships.html +2 -2
  51. package/docs/module-UserNotifications.html +2 -2
  52. package/docs/module-UserProfile.html +2 -2
  53. package/docs/progress-row_method-card.js.html +3 -3
  54. package/docs/railcontent.js.html +2 -2
  55. package/docs/sanity.js.html +230 -321
  56. package/docs/userActivity.js.html +4 -3
  57. package/docs/user_account.ts.html +14 -13
  58. package/docs/user_chat.js.html +2 -2
  59. package/docs/user_interests.js.html +2 -2
  60. package/docs/user_management.js.html +2 -2
  61. package/docs/user_memberships.ts.html +2 -2
  62. package/docs/user_notifications.js.html +2 -2
  63. package/docs/user_onboarding.ts.html +95 -4
  64. package/docs/user_payments.ts.html +2 -2
  65. package/docs/user_permissions.js.html +3 -3
  66. package/docs/user_profile.js.html +2 -2
  67. package/docs/user_sessions.js.html +2 -2
  68. package/docs/user_types.js.html +2 -2
  69. package/docs/user_user-management-system.js.html +2 -2
  70. package/package.json +1 -1
  71. package/src/contentTypeConfig.js +24 -16
  72. package/src/index.d.ts +1 -3
  73. package/src/index.js +1 -3
  74. package/src/lib/brands.ts +8 -0
  75. package/src/lib/sanity/query.ts +30 -0
  76. package/src/services/content/artist.ts +34 -39
  77. package/src/services/content/content.ts +7 -1
  78. package/src/services/content/genre.ts +27 -32
  79. package/src/services/content/instructor.ts +23 -25
  80. package/src/services/content-org/learning-paths.ts +12 -23
  81. package/src/services/sanity.js +34 -26
  82. package/src/services/user/onboarding.ts +93 -2
@@ -32,9 +32,9 @@ interface ActiveLearningPathResponse {
32
32
  */
33
33
  export async function getDailySession(brand: string, userDate: Date) {
34
34
  const stringDate = userDate.toISOString().split('T')[0]
35
- const url: string = `${LEARNING_PATHS_PATH}/daily-session/get-or-create`
35
+ const url: string = `${LEARNING_PATHS_PATH}/daily-session/get`
36
36
  const body = { brand: brand, userDate: stringDate }
37
- return await fetchHandler(url, 'POST', null, body)
37
+ return await fetchHandler(url, 'GET', null, body)
38
38
  }
39
39
 
40
40
  /**
@@ -49,7 +49,7 @@ export async function updateDailySession(
49
49
  keepFirstLearningPath: boolean
50
50
  ) {
51
51
  const stringDate = userDate.toISOString().split('T')[0]
52
- const url: string = `${LEARNING_PATHS_PATH}/daily-session/update`
52
+ const url: string = `${LEARNING_PATHS_PATH}/daily-session/create`
53
53
  const body = { brand: brand, userDate: stringDate, keepFirstLearningPath: keepFirstLearningPath }
54
54
  return await fetchHandler(url, 'POST', null, body)
55
55
  }
@@ -59,30 +59,18 @@ export async function updateDailySession(
59
59
  * @param brand
60
60
  */
61
61
  export async function getActivePath(brand: string) {
62
- const url: string = `${LEARNING_PATHS_PATH}/active-path/get-or-create`
63
- const body = { brand: brand }
64
- return await fetchHandler(url, 'POST', null, body)
65
- }
66
-
67
- // todo this should be removed once we handle active path gen only through
68
- // finish method intro or complete current active path
69
- /**
70
- * Updates user's active learning path.
71
- * @param brand
72
- */
73
- export async function updateActivePath(brand: string) {
74
- const url: string = `${LEARNING_PATHS_PATH}/active-path/update`
62
+ const url: string = `${LEARNING_PATHS_PATH}/active-path/get`
75
63
  const body = { brand: brand }
76
- return await fetchHandler(url, 'POST', null, body)
64
+ return await fetchHandler(url, 'GET', null, body)
77
65
  }
78
66
 
79
67
  /**
80
- * Starts a new learning path for the user.
68
+ * Sets a new learning path as the user's active learning path.
81
69
  * @param brand
82
70
  * @param learningPathId
83
71
  */
84
72
  export async function startLearningPath(brand: string, learningPathId: number) {
85
- const url: string = `${LEARNING_PATHS_PATH}/start`
73
+ const url: string = `${LEARNING_PATHS_PATH}/active-path/set`
86
74
  const body = { brand: brand, learning_path_id: learningPathId }
87
75
  return await fetchHandler(url, 'POST', null, body)
88
76
  }
@@ -172,10 +160,11 @@ export async function fetchLearningPathLessons(
172
160
  brand: string,
173
161
  userDate: Date
174
162
  ) {
175
- const [learningPath, dailySession] = await Promise.all([
176
- getEnrichedLearningPath(learningPathId),
177
- getDailySession(brand, userDate),
178
- ])
163
+ const learningPath = await getEnrichedLearningPath(learningPathId)
164
+ let dailySession = await getDailySession(brand, userDate)
165
+ if (!dailySession) {
166
+ dailySession = await updateDailySession(brand, userDate, false)
167
+ }
179
168
 
180
169
  const isActiveLearningPath = (dailySession?.active_learning_path_id || 0) == learningPathId
181
170
  if (!isActiveLearningPath) {
@@ -490,7 +490,10 @@ export async function fetchByRailContentIds(
490
490
  }
491
491
  return results.map(liveProcess)
492
492
  }
493
- const results = await fetchSanity(query, true, { customPostProcess: customPostProcess, processNeedAccess: true })
493
+ const results = await fetchSanity(query, true, {
494
+ customPostProcess: customPostProcess,
495
+ processNeedAccess: true,
496
+ })
494
497
 
495
498
  const sortFuction = function compare(a, b) {
496
499
  const indexA = ids.indexOf(a['id'])
@@ -510,8 +513,14 @@ export async function fetchContentRows(brand, pageName, contentRowSlug) {
510
513
  if (pageName === 'lessons') pageName = 'lesson'
511
514
  if (pageName === 'songs') pageName = 'song'
512
515
  const rowString = contentRowSlug ? ` && slug.current == "${contentRowSlug.toLowerCase()}"` : ''
513
- const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`, {pullFutureContent: true, showMembershipRestrictedContent: true}).buildFilter()
514
- const childFilter = await new FilterBuilder('', {isChildrenFilter: true, showMembershipRestrictedContent: true}).buildFilter()
516
+ const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`, {
517
+ pullFutureContent: true,
518
+ showMembershipRestrictedContent: true,
519
+ }).buildFilter()
520
+ const childFilter = await new FilterBuilder('', {
521
+ isChildrenFilter: true,
522
+ showMembershipRestrictedContent: true,
523
+ }).buildFilter()
515
524
  const query = `*[_type == 'recommended-content-row' && brand == '${brand}' && type == '${pageName}'${rowString}]{
516
525
  brand,
517
526
  name,
@@ -524,7 +533,7 @@ export async function fetchContentRows(brand, pageName, contentRowSlug) {
524
533
  'lesson_count': coalesce(count(*[${lessonCountFilter}]), 0),
525
534
  },
526
535
  }`
527
- return fetchSanity(query, true, {processNeedAccess: true})
536
+ return fetchSanity(query, true, { processNeedAccess: true })
528
537
  }
529
538
 
530
539
  /**
@@ -1084,7 +1093,11 @@ export async function jumpToContinueContent(railcontentId) {
1084
1093
  * .catch(error => console.error(error));
1085
1094
  */
1086
1095
  export async function fetchLessonContent(railContentId, { addParent = false } = {}) {
1087
- const filterParams = { isSingle: true, pullFutureContent: true, showMembershipRestrictedContent: true }
1096
+ const filterParams = {
1097
+ isSingle: true,
1098
+ pullFutureContent: true,
1099
+ showMembershipRestrictedContent: true,
1100
+ }
1088
1101
 
1089
1102
  const parentQuery = addParent
1090
1103
  ? `"parent_content_data": *[railcontent_id in [...(^.parent_content_data[].id)]]{
@@ -1236,20 +1249,20 @@ async function fetchRelatedByLicense(railcontentId, brand, onlyUseSongTypes, cou
1236
1249
  export async function fetchSiblingContent(railContentId, brand = null) {
1237
1250
  const filterGetParent = await new FilterBuilder(`references(^._id) && _type == ^.parent_type`, {
1238
1251
  pullFutureContent: true,
1239
- showMembershipRestrictedContent: true // Show parent even without permissions
1252
+ showMembershipRestrictedContent: true, // Show parent even without permissions
1240
1253
  }).buildFilter()
1241
1254
  const filterForParentList = await new FilterBuilder(
1242
1255
  `references(^._id) && _type == ^.parent_type`,
1243
1256
  {
1244
1257
  pullFutureContent: true,
1245
1258
  isParentFilter: true,
1246
- showMembershipRestrictedContent: true // Show parent even without permissions
1259
+ showMembershipRestrictedContent: true, // Show parent even without permissions
1247
1260
  }
1248
1261
  ).buildFilter()
1249
1262
 
1250
1263
  const childrenFilter = await new FilterBuilder(``, {
1251
1264
  isChildrenFilter: true,
1252
- showMembershipRestrictedContent: true // Show all lessons in sidebar, need_access applied on individual page
1265
+ showMembershipRestrictedContent: true, // Show all lessons in sidebar, need_access applied on individual page
1253
1266
  }).buildFilter()
1254
1267
 
1255
1268
  const brandString = brand ? ` && brand == "${brand}"` : ''
@@ -1655,12 +1668,8 @@ export async function fetchSanity(
1655
1668
  if (!results) {
1656
1669
  throw new Error('No results found')
1657
1670
  }
1658
- results = processNeedAccess
1659
- ? await needsAccessDecorator(results, userPermissions)
1660
- : results
1661
- results = processPageType
1662
- ? pageTypeDecorator(results)
1663
- : results
1671
+ results = processNeedAccess ? await needsAccessDecorator(results, userPermissions) : results
1672
+ results = processPageType ? pageTypeDecorator(results) : results
1664
1673
  return customPostProcess ? customPostProcess(results) : results
1665
1674
  } else {
1666
1675
  throw new Error('No results found')
@@ -1714,7 +1723,6 @@ function pageTypeDecorator(results) {
1714
1723
  })
1715
1724
  }
1716
1725
 
1717
-
1718
1726
  function needsAccessDecorator(results, userPermissions) {
1719
1727
  if (globalConfig.sanityConfig.useDummyRailContentMethods) return results
1720
1728
  const adapter = getPermissionsAdapter()
@@ -2036,7 +2044,10 @@ export async function fetchTabData(
2036
2044
  let filter = ''
2037
2045
 
2038
2046
  filter = `brand == "${brand}" && (defined(railcontent_id)) ${includedFieldsFilter} ${progressFilter}`
2039
- const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true, showMembershipRestrictedContent: true }).buildFilter()
2047
+ const childrenFilter = await new FilterBuilder(``, {
2048
+ isChildrenFilter: true,
2049
+ showMembershipRestrictedContent: true,
2050
+ }).buildFilter()
2040
2051
  const childrenFields = await getChildFieldsForContentType('tab-data')
2041
2052
  const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`).buildFilter()
2042
2053
  entityFieldsString = ` ${fieldsString}
@@ -2051,14 +2062,16 @@ export async function fetchTabData(
2051
2062
  ),
2052
2063
  length_in_seconds
2053
2064
  ),`
2054
- const filterWithRestrictions = await new FilterBuilder(filter, {showMembershipRestrictedContent: true}).buildFilter()
2065
+ const filterWithRestrictions = await new FilterBuilder(filter, {
2066
+ showMembershipRestrictedContent: true,
2067
+ }).buildFilter()
2055
2068
  query = buildEntityAndTotalQuery(filterWithRestrictions, entityFieldsString, {
2056
2069
  sortOrder: sortOrder,
2057
2070
  start: start,
2058
2071
  end: end,
2059
2072
  })
2060
2073
 
2061
- let results = await fetchSanity(query, true, {processNeedAccess: true});
2074
+ let results = await fetchSanity(query, true, { processNeedAccess: true })
2062
2075
 
2063
2076
  if (['recent', 'incomplete', 'completed'].includes(progress) && results.entity.length > 1) {
2064
2077
  const orderMap = new Map(progressIds.map((id, index) => [id, index]))
@@ -2202,12 +2215,7 @@ export async function fetchMethodV2StructureFromId(contentId) {
2202
2215
  */
2203
2216
  export async function fetchOwnedContent(
2204
2217
  brand,
2205
- {
2206
- type = [],
2207
- page = 1,
2208
- limit = 10,
2209
- sort = '-published_on',
2210
- } = {}
2218
+ { type = [], page = 1, limit = 10, sort = '-published_on' } = {}
2211
2219
  ) {
2212
2220
  const start = (page - 1) * limit
2213
2221
  const end = start + limit
@@ -2218,7 +2226,7 @@ export async function fetchOwnedContent(
2218
2226
  // Build the type filter
2219
2227
  let typeFilter = ''
2220
2228
  if (type.length > 0) {
2221
- const typesString = type.map(t => `'${t}'`).join(', ')
2229
+ const typesString = type.map((t) => `'${t}'`).join(', ')
2222
2230
  typeFilter = `&& _type in [${typesString}]`
2223
2231
  }
2224
2232
 
@@ -2227,7 +2235,7 @@ export async function fetchOwnedContent(
2227
2235
 
2228
2236
  // Apply owned content filter
2229
2237
  const filterWithRestrictions = await new FilterBuilder(filter, {
2230
- showOnlyOwnedContent: true, // Key parameter: exclude membership content
2238
+ showOnlyOwnedContent: true, // Key parameter: exclude membership content
2231
2239
  }).buildFilter()
2232
2240
 
2233
2241
  const fieldsString = DEFAULT_FIELDS.join(',')
@@ -2,6 +2,7 @@
2
2
  * @module Onboarding
3
3
  */
4
4
  import { HttpClient } from '../../infrastructure/http/HttpClient'
5
+ import { Brand } from '../../lib/brands'
5
6
  import { globalConfig } from '../config.js'
6
7
 
7
8
  export interface OnboardingSteps {
@@ -131,17 +132,107 @@ export interface OnboardingRecommendedContent {
131
132
  }
132
133
  }
133
134
 
135
+ const recommendedContentCache: { [brand: string]: OnboardingRecommendedContent } = {
136
+ drumeo: {
137
+ id: 391995,
138
+ title: 'Getting Started On The Drums',
139
+ difficulty: 'Beginner',
140
+ lesson_count: 12,
141
+ skill_count: 1,
142
+ badge: 'https://cdn.sanity.io/files/4032r8py/staging/drumeo_badge.png',
143
+ description:
144
+ 'Start your drumming journey with essential techniques and rhythms to get you playing quickly.',
145
+ video: {
146
+ external_id: '1002267396',
147
+ hlsManifestUrl:
148
+ 'https://player.vimeo.com/external/1002267396.m3u8?s=abcdef1234567890abcdef1234567890abcdef12&oauth2_token_id=1284792284',
149
+ type: 'vimeo-video',
150
+ },
151
+ },
152
+ pianote: {
153
+ id: 412405,
154
+ title: 'Getting Started On The Piano',
155
+ difficulty: 'Beginner',
156
+ lesson_count: 4,
157
+ skill_count: 3,
158
+ badge:
159
+ 'https://cdn.sanity.io/files/4032r8py/staging/9470587f03479b7c1f8019c3cbcbdfe12aa267f3.png',
160
+ description:
161
+ 'The goal of this course is to introduce you to the keys, and get you playing a song as fast as possible. ',
162
+ video: {
163
+ external_id: '1001267395',
164
+ hlsManifestUrl:
165
+ 'https://player.vimeo.com/external/1001267395.m3u8?s=8f8d8a8a762f688058e6e6fd6704c402baf1b797&oauth2_token_id=1284792283',
166
+ type: 'vimeo-video',
167
+ },
168
+ },
169
+ guitareo: {
170
+ id: 294635,
171
+ title: 'Getting Started On The Acoustic Guitar',
172
+ difficulty: 'Beginner',
173
+ lesson_count: 6,
174
+ skill_count: 5,
175
+ badge: 'https://cdn.sanity.io/files/4032r8py/staging/guitareo_badge.png',
176
+ description:
177
+ 'New to the acoustic guitar? Then this Course is for you! Learn everything you need to get started on the acoustic guitar, and start playing music as fast as possible!',
178
+ video: {
179
+ external_id: '1003267397',
180
+ hlsManifestUrl:
181
+ 'https://player.vimeo.com/external/1003267397.m3u8?s=1234567890abcdef1234567890abcdef12345678&oauth2_token_id=1284792285',
182
+ type: 'vimeo-video',
183
+ },
184
+ },
185
+ singeo: {
186
+ id: 712408,
187
+ title: 'Singing Starter Kit',
188
+ difficulty: 'Beginner',
189
+ lesson_count: 5,
190
+ skill_count: 4,
191
+ badge: 'https://cdn.sanity.io/files/4032r8py/staging/singeo_badge.png',
192
+ description:
193
+ 'Welcome to the Singing Starter Kit! This course will teach you everything you need to know to sound better when you sing! You will learn how your unique voice works so that you can develop vocal strength, accurate pitch, and find confidence singing your favorite songs. You can sing, and the Singing Starter Kit is the perfect way to start your singing journey.',
194
+ video: {
195
+ external_id: '1004267398',
196
+ hlsManifestUrl:
197
+ 'https://player.vimeo.com/external/1004267398.m3u8?s=fedcba0987654321fedcba0987654321fedcba09&oauth2_token_id=1284792286',
198
+ type: 'vimeo-video',
199
+ },
200
+ },
201
+ playbass: {
202
+ id: 294635,
203
+ title: 'Getting Started On The Acoustic Guitar',
204
+ difficulty: 'Beginner',
205
+ lesson_count: 6,
206
+ skill_count: 5,
207
+ badge: 'https://cdn.sanity.io/files/4032r8py/staging/guitareo_badge.png',
208
+ description:
209
+ 'New to the acoustic guitar? Then this Course is for you! Learn everything you need to get started on the acoustic guitar, and start playing music as fast as possible!',
210
+ video: {
211
+ external_id: '1003267397',
212
+ hlsManifestUrl:
213
+ 'https://player.vimeo.com/external/1003267397.m3u8?s=1234567890abcdef1234567890abcdef12345678&oauth2_token_id=1284792285',
214
+ type: 'vimeo-video',
215
+ },
216
+ },
217
+ }
218
+
134
219
  /**
135
220
  * Fetches recommended content for onboarding based on the specified brand.
136
221
  *
137
- * @param {string} brand - The brand identifier.
222
+ * @param {string} email - The user's email address.
223
+ * @param {Brand} brand - The brand identifier.
138
224
  * @returns {Promise<OnboardingRecommendedContent>} - A promise that resolves with the recommended content.
139
225
  * @throws {HttpError} - If the HTTP request fails.
140
226
  */
141
227
  export async function getOnboardingRecommendedContent(
142
- brand: string
228
+ email: string,
229
+ brand: Brand
143
230
  ): Promise<OnboardingRecommendedContent> {
144
231
  // TODO: Replace with real API call when available
232
+ if (recommendedContentCache[brand]) {
233
+ return recommendedContentCache[brand]
234
+ }
235
+
145
236
  return {
146
237
  id: 412405,
147
238
  title: 'Getting Started On The Piano',