musora-content-services 2.79.1 → 2.81.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 (85) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/docs/Content.html +269 -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 +2 -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 +24 -14
  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 +2 -2
  16. package/docs/content_artist.ts.html +211 -0
  17. package/docs/content_content.ts.html +77 -0
  18. package/docs/content_genre.ts.html +211 -0
  19. package/docs/content_instructor.ts.html +203 -0
  20. package/docs/forums_categories.ts.html +3 -3
  21. package/docs/forums_forums.ts.html +2 -2
  22. package/docs/forums_posts.ts.html +2 -2
  23. package/docs/forums_threads.ts.html +2 -2
  24. package/docs/gamification_awards.ts.html +2 -2
  25. package/docs/gamification_gamification.js.html +2 -2
  26. package/docs/global.html +2 -2
  27. package/docs/index.html +2 -2
  28. package/docs/liveTesting.ts.html +2 -2
  29. package/docs/module-Accounts.html +14 -14
  30. package/docs/module-Artist.html +991 -0
  31. package/docs/module-Awards.html +2 -2
  32. package/docs/module-Config.html +2 -2
  33. package/docs/module-Content-Services-V2.html +2 -2
  34. package/docs/module-Forums.html +2 -2
  35. package/docs/module-Genre.html +981 -0
  36. package/docs/module-GuidedCourses.html +2 -2
  37. package/docs/module-Instructor.html +929 -0
  38. package/docs/module-Interests.html +2 -2
  39. package/docs/module-LearningPaths.html +6 -6
  40. package/docs/module-Onboarding.html +2 -2
  41. package/docs/module-Payments.html +2 -2
  42. package/docs/module-Permissions.html +2 -2
  43. package/docs/module-Playlists.html +2 -2
  44. package/docs/module-ProgressRow.html +2 -2
  45. package/docs/module-Railcontent-Services.html +2 -2
  46. package/docs/module-Sanity-Services.html +326 -1854
  47. package/docs/module-Sessions.html +2 -2
  48. package/docs/module-UserActivity.html +4 -4
  49. package/docs/module-UserChat.html +2 -2
  50. package/docs/module-UserManagement.html +2 -2
  51. package/docs/module-UserMemberships.html +2 -2
  52. package/docs/module-UserNotifications.html +2 -2
  53. package/docs/module-UserProfile.html +2 -2
  54. package/docs/progress-row_method-card.js.html +2 -2
  55. package/docs/railcontent.js.html +2 -2
  56. package/docs/sanity.js.html +107 -268
  57. package/docs/userActivity.js.html +3 -2
  58. package/docs/user_account.ts.html +11 -4
  59. package/docs/user_chat.js.html +2 -2
  60. package/docs/user_interests.js.html +2 -2
  61. package/docs/user_management.js.html +2 -2
  62. package/docs/user_memberships.ts.html +2 -2
  63. package/docs/user_notifications.js.html +2 -2
  64. package/docs/user_onboarding.ts.html +2 -2
  65. package/docs/user_payments.ts.html +2 -2
  66. package/docs/user_permissions.js.html +2 -2
  67. package/docs/user_profile.js.html +2 -2
  68. package/docs/user_sessions.js.html +2 -2
  69. package/docs/user_types.js.html +2 -2
  70. package/docs/user_user-management-system.js.html +2 -2
  71. package/jsdoc.json +1 -0
  72. package/package.json +1 -1
  73. package/src/contentTypeConfig.js +2 -2
  74. package/src/index.d.ts +28 -5
  75. package/src/index.js +28 -5
  76. package/src/services/content/artist.ts +139 -0
  77. package/src/services/content/content.ts +38 -0
  78. package/src/services/content/genre.ts +139 -0
  79. package/src/services/content/instructor.ts +131 -0
  80. package/src/services/content-org/learning-paths.ts +3 -0
  81. package/src/services/sanity.js +105 -266
  82. package/src/services/user/account.ts +9 -2
  83. package/src/services/user/types.d.ts +133 -0
  84. package/src/services/userActivity.js +1 -0
  85. package/.claude/settings.local.json +0 -8
@@ -22,19 +22,16 @@ import {
22
22
  SONG_TYPES,
23
23
  SONG_TYPES_WITH_CHILDREN,
24
24
  } from '../contentTypeConfig.js'
25
- import {fetchSimilarItems, recommendations} from './recommendations.js'
25
+ import { fetchSimilarItems, recommendations } from './recommendations.js'
26
26
  import { processMetadata, typeWithSortOrder } from '../contentMetaData.js'
27
27
 
28
28
  import { globalConfig } from './config.js'
29
29
 
30
- import {
31
- fetchNextContentDataForParent,
32
- fetchHandler,
33
- } from './railcontent.js'
30
+ import { fetchNextContentDataForParent, fetchHandler } from './railcontent.js'
34
31
  import { arrayToStringRepresentation, FilterBuilder } from '../filterBuilder.js'
35
32
  import { fetchUserPermissions } from './user/permissions.js'
36
33
  import { getAllCompleted, getAllStarted, getAllStartedOrCompleted } from './contentProgress.js'
37
- import {fetchRecentActivitiesActiveTabs} from "./userActivity.js";
34
+ import { fetchRecentActivitiesActiveTabs } from './userActivity.js'
38
35
 
39
36
  /**
40
37
  * Exported functions that are excluded from index generation.
@@ -162,30 +159,6 @@ function getQueryFromPage(pageNumber, contentPerPage) {
162
159
  return result
163
160
  }
164
161
 
165
- /**
166
- * Fetch all artists with lessons available for a specific brand.
167
- *
168
- * @param {string} brand - The brand for which to fetch artists.
169
- * @returns {Promise<Object|null>} - A promise that resolves to an array of artist objects or null if not found.
170
- *
171
- * @example
172
- * fetchArtists('drumeo')
173
- * .then(artists => console.log(artists))
174
- * .catch(error => console.error(error));
175
- */
176
- export async function fetchArtists(brand) {
177
- const filter = await new FilterBuilder(
178
- `_type == "song" && brand == "${brand}" && references(^._id)`,
179
- { bypassPermissions: true }
180
- ).buildFilter()
181
- const query = `
182
- *[_type == "artist"]{
183
- name,
184
- "lessonsCount": count(*[${filter}])
185
- }[lessonsCount > 0] |order(lower(name)) `
186
- return fetchSanity(query, true, { processNeedAccess: false })
187
- }
188
-
189
162
  /**
190
163
  * Fetch current number of artists for songs within a brand.
191
164
  * @param {string} brand - The current brand.
@@ -335,7 +308,7 @@ export async function fetchNewReleases(
335
308
  web_url_path,
336
309
  "permission_id": permission[]->railcontent_id,
337
310
  `
338
- const query = buildRawQuery(filter, fields, {sortOrder: sortOrder, start, end: end})
311
+ const query = buildRawQuery(filter, fields, { sortOrder: sortOrder, start, end: end })
339
312
  return fetchSanity(query, true)
340
313
  }
341
314
 
@@ -476,17 +449,26 @@ export async function fetchByRailContentId(id, contentType) {
476
449
  * .then(contents => console.log(contents))
477
450
  * .catch(error => console.error(error));
478
451
  */
479
- export async function fetchByRailContentIds(ids, contentType = undefined, brand = undefined, includePermissionsAndStatusFilter = false) {
452
+ export async function fetchByRailContentIds(
453
+ ids,
454
+ contentType = undefined,
455
+ brand = undefined,
456
+ includePermissionsAndStatusFilter = false
457
+ ) {
480
458
  if (!ids?.length) {
481
459
  return []
482
460
  }
483
- ids = [...new Set(ids.filter(item => item !== null && item !== undefined))];
461
+ ids = [...new Set(ids.filter((item) => item !== null && item !== undefined))]
484
462
  const idsString = ids.join(',')
485
463
  const brandFilter = brand ? ` && brand == "${brand}"` : ''
486
- const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`, {pullFutureContent: true}).buildFilter()
464
+ const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`, {
465
+ pullFutureContent: true,
466
+ }).buildFilter()
487
467
  const fields = await getFieldsForContentTypeWithFilteredChildren(contentType, true)
488
468
  const baseFilter = `railcontent_id in [${idsString}]${brandFilter}`
489
- const finalFilter = includePermissionsAndStatusFilter ? await new FilterBuilder(baseFilter).buildFilter() : baseFilter
469
+ const finalFilter = includePermissionsAndStatusFilter
470
+ ? await new FilterBuilder(baseFilter).buildFilter()
471
+ : baseFilter
490
472
  const query = `*[
491
473
  ${finalFilter}
492
474
  ]{
@@ -498,18 +480,16 @@ export async function fetchByRailContentIds(ids, contentType = undefined, brand
498
480
 
499
481
  console.log('ids query', query)
500
482
  const customPostProcess = (results) => {
501
- const now = getSanityDate(new Date(), false);
483
+ const now = getSanityDate(new Date(), false)
502
484
  const liveProcess = (result) => {
503
485
  if (result.live_event_start_time && result.live_event_end_time) {
504
- result.isLive =
505
- result.live_event_start_time <= now &&
506
- result.live_event_end_time >= now;
486
+ result.isLive = result.live_event_start_time <= now && result.live_event_end_time >= now
507
487
  } else {
508
- result.isLive = false;
488
+ result.isLive = false
509
489
  }
510
- return result;
511
- };
512
- return results.map(liveProcess);
490
+ return result
491
+ }
492
+ return results.map(liveProcess)
513
493
  }
514
494
  const results = await fetchSanity(query, true, { customPostProcess: customPostProcess })
515
495
 
@@ -527,13 +507,14 @@ export async function fetchByRailContentIds(ids, contentType = undefined, brand
527
507
  return sortedResults
528
508
  }
529
509
 
530
- export async function fetchContentRows(brand, pageName, contentRowSlug)
531
- {
510
+ export async function fetchContentRows(brand, pageName, contentRowSlug) {
532
511
  if (pageName === 'lessons') pageName = 'lesson'
533
512
  if (pageName === 'songs') pageName = 'song'
534
513
  const rowString = contentRowSlug ? ` && slug.current == "${contentRowSlug.toLowerCase()}"` : ''
535
- const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`, {pullFutureContent: true}).buildFilter()
536
- const childFilter = await new FilterBuilder('', {isChildrenFilter: true}).buildFilter()
514
+ const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`, {
515
+ pullFutureContent: true,
516
+ }).buildFilter()
517
+ const childFilter = await new FilterBuilder('', { isChildrenFilter: true }).buildFilter()
537
518
  const query = `*[_type == 'recommended-content-row' && brand == '${brand}' && type == '${pageName}'${rowString}]{
538
519
  brand,
539
520
  name,
@@ -549,8 +530,6 @@ export async function fetchContentRows(brand, pageName, contentRowSlug)
549
530
  return fetchSanity(query, true)
550
531
  }
551
532
 
552
-
553
-
554
533
  /**
555
534
  * Fetch all content for a specific brand and type with pagination, search, and grouping options.
556
535
  * @param {string} brand - The brand for which to fetch content.
@@ -619,9 +598,7 @@ export async function fetchAll(
619
598
  } else if (type === 'pack') {
620
599
  typeFilter = `&& (_type == 'pack' || _type == 'semester-pack')`
621
600
  } else {
622
- typeFilter = type
623
- ? `&& _type == '${type}'`
624
- : ''
601
+ typeFilter = type ? `&& _type == '${type}'` : ''
625
602
  }
626
603
 
627
604
  // Construct the search filter
@@ -740,7 +717,7 @@ async function getProgressFilter(progress, progressIds) {
740
717
  return `&& (railcontent_id in [${ids.join(',')}])`
741
718
  }
742
719
  case 'incomplete': {
743
- const ids = progressIds !== undefined ? progressIds :await getAllStarted()
720
+ const ids = progressIds !== undefined ? progressIds : await getAllStarted()
744
721
  return `&& railcontent_id in [${ids.join(',')}]`
745
722
  }
746
723
  default:
@@ -1248,7 +1225,7 @@ async function fetchRelatedByLicense(railcontentId, brand, onlyUseSongTypes, cou
1248
1225
  *[${filterSongTypesWithSameLicense}]->{${queryFields}}|order(published_on desc, title asc)[0...${count}],
1249
1226
  }[0...1]`
1250
1227
  const results = await fetchSanity(query, false)
1251
- return results ? results['related_by_license'] ?? [] : []
1228
+ return results ? (results['related_by_license'] ?? []) : []
1252
1229
  }
1253
1230
 
1254
1231
  /**
@@ -1257,15 +1234,17 @@ async function fetchRelatedByLicense(railcontentId, brand, onlyUseSongTypes, cou
1257
1234
  * @param {string} brand - The current brand.
1258
1235
  * @returns {Promise<Array<Object>|null>} - The fetched related lessons data or null if not found.
1259
1236
  */
1260
- export async function fetchSiblingContent(railContentId, brand= null)
1261
- {
1237
+ export async function fetchSiblingContent(railContentId, brand = null) {
1262
1238
  const filterGetParent = await new FilterBuilder(`references(^._id) && _type == ^.parent_type`, {
1263
- pullFutureContent: true
1264
- }).buildFilter()
1265
- const filterForParentList = await new FilterBuilder(`references(^._id) && _type == ^.parent_type`, {
1266
1239
  pullFutureContent: true,
1267
- isParentFilter: true,
1268
1240
  }).buildFilter()
1241
+ const filterForParentList = await new FilterBuilder(
1242
+ `references(^._id) && _type == ^.parent_type`,
1243
+ {
1244
+ pullFutureContent: true,
1245
+ isParentFilter: true,
1246
+ }
1247
+ ).buildFilter()
1269
1248
 
1270
1249
  const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
1271
1250
 
@@ -1305,8 +1284,7 @@ export async function fetchSiblingContent(railContentId, brand= null)
1305
1284
  * @param {string} railContentId - The RailContent ID of the current lesson.
1306
1285
  * @returns {Promise<Array<Object>|null>} - The fetched related lessons data or null if not found.
1307
1286
  */
1308
- export async function fetchRelatedLessons(railContentId)
1309
- {
1287
+ export async function fetchRelatedLessons(railContentId) {
1310
1288
  const defaultFilterFields = `_type==^._type && brand == ^.brand && railcontent_id != ${railContentId}`
1311
1289
 
1312
1290
  const filterSameArtist = await new FilterBuilder(
@@ -1350,11 +1328,16 @@ export async function fetchAllPacks(
1350
1328
  const start = (page - 1) * limit
1351
1329
  const end = start + limit
1352
1330
 
1353
- const query = await buildQuery(filter, filterParams, await getFieldsForContentTypeWithFilteredChildren('pack'), {
1354
- sortOrder: sortOrder,
1355
- start,
1356
- end,
1357
- })
1331
+ const query = await buildQuery(
1332
+ filter,
1333
+ filterParams,
1334
+ await getFieldsForContentTypeWithFilteredChildren('pack'),
1335
+ {
1336
+ sortOrder: sortOrder,
1337
+ start,
1338
+ end,
1339
+ }
1340
+ )
1358
1341
  return fetchSanity(query, true)
1359
1342
  }
1360
1343
 
@@ -1460,45 +1443,6 @@ export async function fetchPackData(id) {
1460
1443
  return fetchSanity(query, false)
1461
1444
  }
1462
1445
 
1463
- /**
1464
- * Fetch the data needed for the coach screen.
1465
- * @param {string} brand - The brand for which to fetch coach lessons
1466
- * @param {string} id - The Railcontent ID of the coach
1467
- * @returns {Promise<Object|null>} - The lessons for the instructor or null if not found.
1468
- * @param {Object} params - Parameters for pagination, filtering and sorting.
1469
- * @param {string} [params.sortOrder="-published_on"] - The field to sort the lessons by.
1470
- * @param {string} [params.searchTerm=""] - The search term to filter content by title.
1471
- * @param {number} [params.page=1] - The page number for pagination.
1472
- * @param {number} [params.limit=10] - The number of items per page.
1473
- * @param {Array<string>} [params.includedFields=[]] - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock'].
1474
- *
1475
- * @example
1476
- * fetchCoachLessons('coach123')
1477
- * .then(lessons => console.log(lessons))
1478
- * .catch(error => console.error(error));
1479
- */
1480
- export async function fetchCoachLessons(
1481
- brand,
1482
- id,
1483
- { sortOrder = '-published_on', searchTerm = '', page = 1, limit = 20, includedFields = [] } = {}
1484
- ) {
1485
- const fieldsString = getFieldsForContentType()
1486
- const start = (page - 1) * limit
1487
- const end = start + limit
1488
- const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1489
- const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
1490
- const filter = `brand == '${brand}' ${searchFilter} ${includedFieldsFilter} && references(*[_type=='instructor' && railcontent_id == ${id}]._id)`
1491
- const filterWithRestrictions = await new FilterBuilder(filter).buildFilter()
1492
-
1493
- sortOrder = getSortOrder(sortOrder, brand)
1494
- const query = buildEntityAndTotalQuery(filterWithRestrictions, fieldsString, {
1495
- sortOrder: sortOrder,
1496
- start: start,
1497
- end: end,
1498
- })
1499
- return fetchSanity(query, true)
1500
- }
1501
-
1502
1446
  /**
1503
1447
  * Fetch the data needed for the coach screen.
1504
1448
  * @param {string} id - The Railcontent ID of the coach
@@ -1530,121 +1474,6 @@ export async function fetchByReference(
1530
1474
  return fetchSanity(query, true)
1531
1475
  }
1532
1476
 
1533
- /**
1534
- * Fetch the artist's lessons.
1535
- * @param {string} brand - The brand for which to fetch lessons.
1536
- * @param {string} name - The name of the artist
1537
- * @param {string} contentType - The type of the lessons we need to get from the artist. If not defined, groq will get lessons from all content types
1538
- * @param {Object} params - Parameters for sorting, searching, pagination and filtering.
1539
- * @param {string} [params.sort="-published_on"] - The field to sort the lessons by.
1540
- * @param {string} [params.searchTerm=""] - The search term to filter the lessons.
1541
- * @param {number} [params.page=1] - The page number for pagination.
1542
- * @param {number} [params.limit=10] - The number of items per page.
1543
- * @param {Array<string>} [params.includedFields=[]] - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock'].
1544
- * @param {Array<number>} [params.progressIds] - The ids of the lessons that are in progress or completed
1545
- * @returns {Promise<Object|null>} - The lessons for the artist and some details about the artist (name and thumbnail).
1546
- *
1547
- * @example
1548
- * fetchArtistLessons('drumeo', '10 Years', 'song', {'-published_on', '', 1, 10, ["difficulty,Intermediate"], [232168, 232824, 303375, 232194, 393125]})
1549
- * .then(lessons => console.log(lessons))
1550
- * .catch(error => console.error(error));
1551
- */
1552
- export async function fetchArtistLessons(
1553
- brand,
1554
- name,
1555
- contentType,
1556
- {
1557
- sort = '-published_on',
1558
- searchTerm = '',
1559
- page = 1,
1560
- limit = 10,
1561
- includedFields = [],
1562
- progressIds = undefined,
1563
- } = {}
1564
- ) {
1565
- const fieldsString = DEFAULT_FIELDS.join(',')
1566
- const start = (page - 1) * limit
1567
- const end = start + limit
1568
- const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1569
- const sortOrder = getSortOrder(sort, brand)
1570
- const addType =
1571
- contentType && Array.isArray(contentType)
1572
- ? `_type in ['${contentType.join("', '")}'] &&`
1573
- : contentType
1574
- ? `_type == '${contentType}' && `
1575
- : ''
1576
- const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
1577
-
1578
- // limits the results to supplied progressIds for started & completed filters
1579
- const progressFilter =
1580
- progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
1581
- const now = getSanityDate(new Date())
1582
- const query = `{
1583
- "entity":
1584
- *[_type == 'artist' && name == '${name}']
1585
- {'type': _type, name, 'thumbnail':thumbnail_url.asset->url,
1586
- 'lessons_count': count(*[${addType} brand == '${brand}' && references(^._id)]),
1587
- 'lessons': *[${addType} brand == '${brand}' && references(^._id) && (status in ['published'] || (status == 'scheduled' && defined(published_on) && published_on >= '${now}')) ${searchFilter} ${includedFieldsFilter} ${progressFilter}]{${fieldsString}}
1588
- [${start}...${end}]}
1589
- |order(${sortOrder})f
1590
- }`
1591
- return fetchSanity(query, true)
1592
- }
1593
-
1594
- /**
1595
- * Fetch the genre's lessons.
1596
- * @param {string} brand - The brand for which to fetch lessons.
1597
- * @param {string} name - The name of the genre
1598
- * @param {Object} params - Parameters for sorting, searching, pagination and filtering.
1599
- * @param {string} [params.sort="-published_on"] - The field to sort the lessons by.
1600
- * @param {string} [params.searchTerm=""] - The search term to filter the lessons.
1601
- * @param {number} [params.page=1] - The page number for pagination.
1602
- * @param {number} [params.limit=10] - The number of items per page.
1603
- * @param {Array<string>} [params.includedFields=[]] - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock'].
1604
- * @param {Array<number>} [params.progressIds] - The ids of the lessons that are in progress or completed
1605
- * @returns {Promise<Object|null>} - The lessons for the artist and some details about the artist (name and thumbnail).
1606
- *
1607
- * @example
1608
- * fetchGenreLessons('drumeo', 'Blues', 'song', {'-published_on', '', 1, 10, ["difficulty,Intermediate"], [232168, 232824, 303375, 232194, 393125]})
1609
- * .then(lessons => console.log(lessons))
1610
- * .catch(error => console.error(error));
1611
- */
1612
- export async function fetchGenreLessons(
1613
- brand,
1614
- name,
1615
- contentType,
1616
- {
1617
- sort = '-published_on',
1618
- searchTerm = '',
1619
- page = 1,
1620
- limit = 10,
1621
- includedFields = [],
1622
- progressIds = undefined,
1623
- } = {}
1624
- ) {
1625
- const fieldsString = DEFAULT_FIELDS.join(',')
1626
- const start = (page - 1) * limit
1627
- const end = start + limit
1628
- const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
1629
- const sortOrder = getSortOrder(sort, brand)
1630
- const addType = contentType ? `_type == '${contentType}' && ` : ''
1631
- const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
1632
- // limits the results to supplied progressIds for started & completed filters
1633
- const progressFilter =
1634
- progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
1635
- const now = getSanityDate(new Date())
1636
- const query = `{
1637
- "entity":
1638
- *[_type == 'genre' && name == '${name}']
1639
- {'type': _type, name, 'thumbnail':thumbnail_url.asset->url,
1640
- 'lessons_count': count(*[${addType} brand == '${brand}' && references(^._id)]),
1641
- 'lessons': *[${addType} brand == '${brand}' && references(^._id) && (status in ['published'] || (status == 'scheduled' && defined(published_on) && published_on >= '${now}')) ${searchFilter} ${includedFieldsFilter} ${progressFilter}]{${fieldsString}}
1642
- [${start}...${end}]}
1643
- |order(${sortOrder})
1644
- }`
1645
- return fetchSanity(query, true)
1646
- }
1647
-
1648
1477
  export async function fetchTopLevelParentId(railcontentId) {
1649
1478
  const statusFilter = "&& status in ['scheduled', 'published', 'archived', 'unlisted']"
1650
1479
 
@@ -1766,8 +1595,10 @@ export async function fetchCommentModContentData(ids) {
1766
1595
  *
1767
1596
  * @param {string} query - The GROQ query to execute against the Sanity API.
1768
1597
  * @param {boolean} isList - Whether to return an array or a single result.
1769
- * @param {Function} [customPostProcess=null] - custom post process callback
1770
- * @param {boolean} [processNeedAccess=true] - execute the needs_access callback
1598
+ * @param {Object} options - Additional options for fetching data.
1599
+ * @param {Function} [options.customPostProcess=null] - custom post process callback
1600
+ * @param {boolean} [options.processNeedAccess=true] - execute the needs_access callback
1601
+ * @param {boolean} [options.processPageType=true] - execute the page_type callback
1771
1602
  * @returns {Promise<*|null>} - A promise that resolves to the fetched data or null if an error occurs or no results are found.
1772
1603
  *
1773
1604
  * @example
@@ -1780,7 +1611,7 @@ export async function fetchCommentModContentData(ids) {
1780
1611
  export async function fetchSanity(
1781
1612
  query,
1782
1613
  isList,
1783
- { customPostProcess = null, processNeedAccess = true } = {}
1614
+ { customPostProcess = null, processNeedAccess = true, processPageType = true } = {}
1784
1615
  ) {
1785
1616
  // Check the config object before proceeding
1786
1617
  if (!checkSanityConfig(globalConfig)) {
@@ -1822,19 +1653,20 @@ export async function fetchSanity(
1822
1653
  results = processNeedAccess
1823
1654
  ? await needsAccessDecorator(results, userPermissions, isAdmin)
1824
1655
  : results
1825
- results = pageTypeDecorator(results)
1656
+ results = processPageType
1657
+ ? pageTypeDecorator(results)
1658
+ : results
1826
1659
  return customPostProcess ? customPostProcess(results) : results
1827
1660
  } else {
1828
1661
  throw new Error('No results found')
1829
1662
  }
1830
1663
  } catch (error) {
1831
- console.error('fetchSanity: Fetch error:', {error, query})
1664
+ console.error('fetchSanity: Fetch error:', { error, query })
1832
1665
  return null
1833
1666
  }
1834
1667
  }
1835
1668
 
1836
- function contentResultsDecorator(results, fieldName, callback)
1837
- {
1669
+ function contentResultsDecorator(results, fieldName, callback) {
1838
1670
  if (Array.isArray(results)) {
1839
1671
  results.forEach((result) => {
1840
1672
  result[fieldName] = callback(result)
@@ -1861,16 +1693,18 @@ function contentResultsDecorator(results, fieldName, callback)
1861
1693
  return results
1862
1694
  }
1863
1695
 
1864
- function pageTypeDecorator(results)
1865
- {
1866
- return contentResultsDecorator(results, 'page_type', function(content) { return SONG_TYPES_WITH_CHILDREN.includes(content['type']) ? 'song' : 'lesson'})
1696
+ function pageTypeDecorator(results) {
1697
+ return contentResultsDecorator(results, 'page_type', function (content) {
1698
+ return SONG_TYPES_WITH_CHILDREN.includes(content['type']) ? 'song' : 'lesson'
1699
+ })
1867
1700
  }
1868
1701
 
1869
-
1870
1702
  function needsAccessDecorator(results, userPermissions, isAdmin) {
1871
1703
  if (globalConfig.sanityConfig.useDummyRailContentMethods) return results
1872
1704
  userPermissions = new Set(userPermissions)
1873
- return contentResultsDecorator(results, 'need_access', function (content) { return doesUserNeedAccessToContent(content, userPermissions, isAdmin) })
1705
+ return contentResultsDecorator(results, 'need_access', function (content) {
1706
+ return doesUserNeedAccessToContent(content, userPermissions, isAdmin)
1707
+ })
1874
1708
  }
1875
1709
 
1876
1710
  function doesUserNeedAccessToContent(result, userPermissions, isAdmin) {
@@ -1928,8 +1762,8 @@ export async function fetchShowsData(brand) {
1928
1762
  * .catch(error => console.error(error));
1929
1763
  */
1930
1764
  export async function fetchMetadata(brand, type) {
1931
- let processedData = processMetadata(brand, type, true)
1932
- if(processedData?.onlyAvailableTabs === true) {
1765
+ let processedData = processMetadata(brand, type, true)
1766
+ if (processedData?.onlyAvailableTabs === true) {
1933
1767
  const activeTabs = await fetchRecentActivitiesActiveTabs()
1934
1768
  processedData.tabs = activeTabs
1935
1769
  }
@@ -1955,7 +1789,7 @@ function arrayJoinWithQuotes(array, delimiter = ',') {
1955
1789
  return wrapped.join(delimiter)
1956
1790
  }
1957
1791
 
1958
- function getSanityDate(date, roundToHourForCaching = true) {
1792
+ export function getSanityDate(date, roundToHourForCaching = true) {
1959
1793
  if (roundToHourForCaching) {
1960
1794
  // We need to set the published on filter date to be a round time so that it doesn't bypass the query cache
1961
1795
  // with every request by changing the filter date every second. I've set it to one minute past the current hour
@@ -2029,13 +1863,19 @@ async function buildQuery(
2029
1863
  return buildRawQuery(filter, fields, { sortOrder, start, end, isSingle })
2030
1864
  }
2031
1865
 
2032
- function buildEntityAndTotalQuery(
1866
+ export function buildEntityAndTotalQuery(
2033
1867
  filter = '',
2034
1868
  fields = '...',
2035
- { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false, withoutPagination = false }
1869
+ {
1870
+ sortOrder = 'published_on desc',
1871
+ start = 0,
1872
+ end = 10,
1873
+ isSingle = false,
1874
+ withoutPagination = false,
1875
+ }
2036
1876
  ) {
2037
1877
  const sortString = sortOrder ? ` | order(${sortOrder})` : ''
2038
- const countString = isSingle ? '[0...1]' : (withoutPagination ? ``: `[${start}...${end}]`)
1878
+ const countString = isSingle ? '[0...1]' : withoutPagination ? `` : `[${start}...${end}]`
2039
1879
  const query = `{
2040
1880
  "entity": *[${filter}] ${sortString}${countString}
2041
1881
  {
@@ -2164,22 +2004,22 @@ export async function fetchTabData(
2164
2004
 
2165
2005
  switch (progress) {
2166
2006
  case 'recent':
2167
- progressIds = await getAllStartedOrCompleted({ brand, onlyIds: true });
2168
- sortOrder = null;
2169
- break;
2007
+ progressIds = await getAllStartedOrCompleted({ brand, onlyIds: true })
2008
+ sortOrder = null
2009
+ break
2170
2010
  case 'incomplete':
2171
- progressIds = await getAllStarted();
2172
- sortOrder = null;
2173
- break;
2011
+ progressIds = await getAllStarted()
2012
+ sortOrder = null
2013
+ break
2174
2014
  case 'completed':
2175
- progressIds = await getAllCompleted();
2176
- sortOrder = null;
2177
- break;
2015
+ progressIds = await getAllCompleted()
2016
+ sortOrder = null
2017
+ break
2178
2018
  }
2179
2019
 
2180
2020
  // limits the results to supplied progressIds for started & completed filters
2181
2021
  const progressFilter = await getProgressFilter(progress, progressIds)
2182
- const fieldsString = getFieldsForContentType('tab-data');
2022
+ const fieldsString = getFieldsForContentType('tab-data')
2183
2023
  const now = getSanityDate(new Date())
2184
2024
 
2185
2025
  // Determine the group by clause
@@ -2191,8 +2031,7 @@ export async function fetchTabData(
2191
2031
  const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
2192
2032
  const childrenFields = await getChildFieldsForContentType('tab-data')
2193
2033
  const lessonCountFilter = await new FilterBuilder(`_id in ^.child[]._ref`).buildFilter()
2194
- entityFieldsString =
2195
- ` ${fieldsString}
2034
+ entityFieldsString = ` ${fieldsString}
2196
2035
  'children': child[${childrenFilter}]->{ ${childrenFields} 'children': child[${childrenFilter}]->{ ${childrenFields} }, },
2197
2036
  'isLive': live_event_start_time <= "${now}" && live_event_end_time >= "${now}",
2198
2037
  'lesson_count': coalesce(count(*[${lessonCountFilter}]), 0),
@@ -2208,23 +2047,23 @@ export async function fetchTabData(
2208
2047
  query = buildEntityAndTotalQuery(filterWithRestrictions, entityFieldsString, {
2209
2048
  sortOrder: sortOrder,
2210
2049
  start: start,
2211
- end: end
2050
+ end: end,
2212
2051
  })
2213
2052
 
2214
- let results = await fetchSanity(query, true);
2053
+ let results = await fetchSanity(query, true)
2215
2054
 
2216
2055
  if (['recent', 'incomplete', 'completed'].includes(progress) && results.entity.length > 1) {
2217
2056
  const orderMap = new Map(progressIds.map((id, index) => [id, index]))
2218
2057
  results.entity = results.entity
2219
2058
  .sort((a, b) => {
2220
- const aIdx = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER;
2221
- const bIdx = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER;
2222
- return aIdx - bIdx || new Date(b.published_on) - new Date(a.published_on);
2059
+ const aIdx = orderMap.get(a.id) ?? Number.MAX_SAFE_INTEGER
2060
+ const bIdx = orderMap.get(b.id) ?? Number.MAX_SAFE_INTEGER
2061
+ return aIdx - bIdx || new Date(b.published_on) - new Date(a.published_on)
2223
2062
  })
2224
- .slice(start, end);
2063
+ .slice(start, end)
2225
2064
  }
2226
2065
 
2227
- return results;
2066
+ return results
2228
2067
  }
2229
2068
 
2230
2069
  export async function fetchRecent(
@@ -2299,12 +2138,12 @@ export async function fetchShows(brand, type, sort = 'sort') {
2299
2138
  * @returns {Promise<*|null>}
2300
2139
  */
2301
2140
  export async function fetchMethodV2IntroVideo(brand) {
2302
- const type = "method-intro";
2303
- const filter = `_type == '${type}' && brand == '${brand}'`;
2304
- const fields = getIntroVideoFields('method-v2');
2141
+ const type = 'method-intro'
2142
+ const filter = `_type == '${type}' && brand == '${brand}'`
2143
+ const fields = getIntroVideoFields('method-v2')
2305
2144
 
2306
- const query = `*[${filter}] { ${fields.join(", ")} }`;
2307
- return fetchSanity(query, false);
2145
+ const query = `*[${filter}] { ${fields.join(', ')} }`
2146
+ return fetchSanity(query, false)
2308
2147
  }
2309
2148
 
2310
2149
  /**
@@ -2313,13 +2152,13 @@ export async function fetchMethodV2IntroVideo(brand) {
2313
2152
  * @returns {Promise<*|null>}
2314
2153
  */
2315
2154
  export async function fetchMethodV2Structure(brand) {
2316
- const _type = "method-v2";
2155
+ const _type = 'method-v2'
2317
2156
  const query = `*[_type == '${_type}' && brand == '${brand}'][0...1]{
2318
2157
  'sanity_id': _id,
2319
2158
  'learningPaths': child[]->{
2320
2159
  'id': railcontent_id,
2321
2160
  'children': child[]->railcontent_id
2322
2161
  }
2323
- }`;
2324
- return await fetchSanity(query, false);
2162
+ }`
2163
+ return await fetchSanity(query, false)
2325
2164
  }
@@ -4,6 +4,8 @@
4
4
  import { HttpClient } from '../../infrastructure/http/HttpClient'
5
5
  import { HttpError } from '../../infrastructure/http/interfaces/HttpError'
6
6
  import { globalConfig } from '../config.js'
7
+ import { Onboarding } from './onboarding'
8
+ import { AuthResponse } from './types'
7
9
 
8
10
  /**
9
11
  * @param {string} email - The email address to check the account status for.
@@ -42,6 +44,11 @@ export interface AccountSetupProps {
42
44
  from?: string
43
45
  }
44
46
 
47
+ export interface AccountSetupResponse {
48
+ auth: AuthResponse
49
+ onboarding: Onboarding
50
+ }
51
+
45
52
  /**
46
53
  * @param {Object} props - The parameters for setting up the account.
47
54
  * @property {string} email - The email address for the account.
@@ -51,11 +58,11 @@ export interface AccountSetupProps {
51
58
  * @property {string} [revenuecatAppUserId] - The RevenueCat App User ID for MA environments. Required for MA requests
52
59
  * @property {string} [deviceName] - The device name for MA environments. Required for MA requests
53
60
  *
54
- * @returns {Promise<void>} - A promise that resolves when the account setup is complete or an HttpError if the request fails.
61
+ * @returns {Promise<AccountSetupResponse>} - A promise that resolves when the account setup is complete or an HttpError if the request fails.
55
62
  * @throws {Error} - Throws an error if required parameters are missing based on the environment.
56
63
  * @throws {HttpError} - Throws an HttpError if the HTTP request fails.
57
64
  */
58
- export async function setupAccount(props: AccountSetupProps): Promise<void> {
65
+ export async function setupAccount(props: AccountSetupProps): Promise<AccountSetupResponse> {
59
66
  const httpClient = new HttpClient(globalConfig.baseUrl)
60
67
  if ((!globalConfig.isMA || props.from === 'mobile-ios-app') && !props.token) {
61
68
  throw new Error('Token is required for non-MA environments')