musora-content-services 1.3.21 → 2.0.5

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 (96) hide show
  1. package/.editorconfig +16 -0
  2. package/.github/workflows/node.js.yml +0 -0
  3. package/.prettierignore +0 -0
  4. package/.prettierrc +0 -0
  5. package/CHANGELOG.md +4 -4
  6. package/README.md +0 -0
  7. package/babel.config.cjs +0 -0
  8. package/docs/Content-Organization.html +245 -0
  9. package/docs/Playlists.html +192 -0
  10. package/docs/config.js.html +14 -5
  11. package/docs/content-org_playlists-types.js.html +109 -0
  12. package/docs/content-org_playlists.js.html +194 -0
  13. package/docs/content-org_types.js.html +112 -0
  14. package/docs/content.js.html +443 -0
  15. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  16. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  17. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  18. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  19. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  20. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  21. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  23. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  24. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  25. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  26. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  27. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  28. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  33. package/docs/global.html +3878 -0
  34. package/docs/index.html +2 -2
  35. package/docs/module-Config.html +60 -7
  36. package/docs/module-Content-Organization-Playlists.html +194 -0
  37. package/docs/module-Content-Organization.html +976 -0
  38. package/docs/module-Content-Services-V2.html +2433 -0
  39. package/docs/module-Playlists.html +969 -0
  40. package/docs/module-Railcontent-Services.html +3052 -1991
  41. package/docs/module-Sanity-Services.html +57 -43
  42. package/docs/module-Session-Management.html +575 -0
  43. package/docs/module-User-Permissions.html +406 -0
  44. package/docs/module-playlists.html +1878 -0
  45. package/docs/module-playlists_.html +108 -0
  46. package/docs/railcontent.js.html +149 -112
  47. package/docs/sanity.js.html +297 -109
  48. package/docs/scripts/collapse.js +0 -0
  49. package/docs/scripts/commonNav.js +0 -0
  50. package/docs/scripts/linenumber.js +0 -0
  51. package/docs/scripts/nav.js +0 -0
  52. package/docs/scripts/polyfill.js +0 -0
  53. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  54. package/docs/scripts/prettify/lang-css.js +0 -0
  55. package/docs/scripts/prettify/prettify.js +0 -0
  56. package/docs/scripts/search.js +0 -0
  57. package/docs/styles/jsdoc.css +0 -0
  58. package/docs/styles/prettify.css +0 -0
  59. package/docs/user_permissions.js.html +110 -0
  60. package/docs/user_sessions.js.html +139 -0
  61. package/docs/user_types.js.html +188 -0
  62. package/jest.config.js +0 -0
  63. package/jsdoc.json +3 -0
  64. package/package.json +1 -1
  65. package/publish.sh +2 -2
  66. package/src/contentMetaData.js +307 -1088
  67. package/src/contentTypeConfig.js +108 -4
  68. package/src/filterBuilder.js +6 -6
  69. package/src/index.d.ts +67 -9
  70. package/src/index.js +67 -9
  71. package/src/{services → lib}/lastUpdated.js +17 -1
  72. package/src/services/content-org/playlists-types.js +37 -0
  73. package/src/services/content-org/playlists.js +122 -0
  74. package/src/services/content.js +371 -0
  75. package/src/services/contentLikes.js +0 -0
  76. package/src/services/contentProgress.js +0 -0
  77. package/src/services/forum.js +57 -0
  78. package/src/services/railcontent.js +122 -122
  79. package/src/services/recommendations.js +19 -0
  80. package/src/services/sanity.js +278 -104
  81. package/src/services/{userPermissions.js → user/permissions.js} +16 -2
  82. package/src/services/user/sessions.js +67 -0
  83. package/src/services/user/types.js +116 -0
  84. package/src/services/userActivity.js +32 -0
  85. package/test/content.test.js +116 -0
  86. package/test/contentProgress.test.js +83 -5
  87. package/test/forum.test.js +18 -0
  88. package/test/initializeTests.js +6 -1
  89. package/test/{lastUpdated.test.js → lib/lastUpdated.test.js} +2 -5
  90. package/test/live/contentProgressLive.test.js +0 -0
  91. package/test/live/railcontentLive.test.js +0 -0
  92. package/test/localStorageMock.js +0 -0
  93. package/test/log.js +0 -0
  94. package/test/sanityQueryService.test.js +66 -18
  95. package/test/{userPermissions.test.js → user/permissions.test.js} +3 -3
  96. package/tools/generate-index.cjs +16 -3
@@ -3,7 +3,6 @@
3
3
  */
4
4
  import {
5
5
  artistOrInstructorName,
6
- artistOrInstructorNameAsArray,
7
6
  assignmentsField,
8
7
  descriptionField,
9
8
  resourcesField,
@@ -23,14 +22,13 @@ import { processMetadata, typeWithSortOrder } from '../contentMetaData.js'
23
22
  import { globalConfig } from './config.js'
24
23
 
25
24
  import {
26
- fetchAllCompletedStates,
27
25
  fetchCompletedChallenges,
28
26
  fetchOwnedChallenges,
29
27
  fetchNextContentDataForParent,
30
28
  fetchHandler,
31
29
  } from './railcontent.js'
32
30
  import { arrayToStringRepresentation, FilterBuilder } from '../filterBuilder.js'
33
- import { fetchUserPermissions } from './userPermissions.js'
31
+ import { fetchUserPermissions } from './user/permissions.js'
34
32
  import { getAllCompleted, getAllStarted, getAllStartedOrCompleted } from './contentProgress.js'
35
33
 
36
34
  /**
@@ -73,15 +71,22 @@ export async function fetchSongById(documentId) {
73
71
  * @number contentPerPage
74
72
  * @returns {Promise<Object|null>}
75
73
  */
76
- export async function fetchLeaving(
77
- brand,
78
- { pageNumber = 1, contentPerPage = 20 } = {}) {
79
- const nextQuarter = getNextAndPreviousQuarterDates()['next'];
74
+ export async function fetchLeaving(brand, { pageNumber = 1, contentPerPage = 20 } = {}) {
75
+ const nextQuarter = getNextAndPreviousQuarterDates()['next']
80
76
  const filterString = `brand == '${brand}' && quarter_removed == '${nextQuarter}'`
81
- const startEndOrder = getQueryFromPage(pageNumber, contentPerPage);
82
- const sortOrder = {sortOrder: "published_on desc, id desc", start: startEndOrder['start'], end: startEndOrder['end']};
83
- const query = await buildQuery(filterString, {pullFutureContent: false, availableContentStatuses: ["published"]}, getFieldsForContentType(), sortOrder);
84
- return fetchSanity(query, true);
77
+ const startEndOrder = getQueryFromPage(pageNumber, contentPerPage)
78
+ const sortOrder = {
79
+ sortOrder: 'published_on desc, id desc',
80
+ start: startEndOrder['start'],
81
+ end: startEndOrder['end'],
82
+ }
83
+ const query = await buildQuery(
84
+ filterString,
85
+ { pullFutureContent: false, availableContentStatuses: ['published'] },
86
+ getFieldsForContentType(),
87
+ sortOrder
88
+ )
89
+ return fetchSanity(query, true)
85
90
  }
86
91
 
87
92
  /**
@@ -92,16 +97,23 @@ export async function fetchLeaving(
92
97
  * @number contentPerPage
93
98
  * @returns {Promise<Object|null>}
94
99
  */
95
- export async function fetchReturning(
96
- brand,
97
- { pageNumber = 1, contentPerPage = 20 } = {}) {
98
- const nextQuarter = getNextAndPreviousQuarterDates()['next'];
99
- const filterString = `brand == '${brand}' && quarter_published == '${nextQuarter}'`;
100
- const startEndOrder = getQueryFromPage(pageNumber, contentPerPage);
101
- const sortOrder = {sortOrder: "published_on desc, id desc", start: startEndOrder['start'], end: startEndOrder['end']};
102
- const query = await buildQuery(filterString, {pullFutureContent: true, availableContentStatuses: ["draft"]}, getFieldsForContentType('returning'), sortOrder);
103
-
104
- return fetchSanity(query, true);
100
+ export async function fetchReturning(brand, { pageNumber = 1, contentPerPage = 20 } = {}) {
101
+ const nextQuarter = getNextAndPreviousQuarterDates()['next']
102
+ const filterString = `brand == '${brand}' && quarter_published == '${nextQuarter}'`
103
+ const startEndOrder = getQueryFromPage(pageNumber, contentPerPage)
104
+ const sortOrder = {
105
+ sortOrder: 'published_on desc, id desc',
106
+ start: startEndOrder['start'],
107
+ end: startEndOrder['end'],
108
+ }
109
+ const query = await buildQuery(
110
+ filterString,
111
+ { pullFutureContent: true, availableContentStatuses: ['draft'] },
112
+ getFieldsForContentType('returning'),
113
+ sortOrder
114
+ )
115
+
116
+ return fetchSanity(query, true)
105
117
  }
106
118
 
107
119
  /**
@@ -112,14 +124,21 @@ export async function fetchReturning(
112
124
  * @number contentPerPage
113
125
  * @returns {Promise<Object|null>}
114
126
  */
115
- export async function fetchComingSoon(
116
- brand,
117
- { pageNumber = 1, contentPerPage = 20 } = {}) {
118
- const filterString = `brand == '${brand}' && _type == 'song'`;
119
- const startEndOrder = getQueryFromPage(pageNumber, contentPerPage);
120
- const sortOrder = {sortOrder: "published_on desc, id desc", start: startEndOrder['start'], end: startEndOrder['end']};
121
- const query = await buildQuery(filterString, {getFutureContentOnly: true}, getFieldsForContentType(), sortOrder);
122
- return fetchSanity(query, true);
127
+ export async function fetchComingSoon(brand, { pageNumber = 1, contentPerPage = 20 } = {}) {
128
+ const filterString = `brand == '${brand}' && _type == 'song'`
129
+ const startEndOrder = getQueryFromPage(pageNumber, contentPerPage)
130
+ const sortOrder = {
131
+ sortOrder: 'published_on desc, id desc',
132
+ start: startEndOrder['start'],
133
+ end: startEndOrder['end'],
134
+ }
135
+ const query = await buildQuery(
136
+ filterString,
137
+ { getFutureContentOnly: true },
138
+ getFieldsForContentType(),
139
+ sortOrder
140
+ )
141
+ return fetchSanity(query, true)
123
142
  }
124
143
 
125
144
  /**
@@ -128,47 +147,47 @@ export async function fetchComingSoon(
128
147
  * @returns {number[]}
129
148
  */
130
149
  function getQueryFromPage(pageNumber, contentPerPage) {
131
- const start = contentPerPage*(pageNumber-1);
132
- const end = contentPerPage*pageNumber;
133
- let result = [];
134
- result['start'] = start;
135
- result['end'] = end;
136
- return result;
150
+ const start = contentPerPage * (pageNumber - 1)
151
+ const end = contentPerPage * pageNumber
152
+ let result = []
153
+ result['start'] = start
154
+ result['end'] = end
155
+ return result
137
156
  }
138
157
 
139
158
  /**
140
159
  * returns array of next and previous quarter dates as strings
141
160
  *
142
- * @returns {*[]}
161
+ * @returns {string[]}
143
162
  */
144
163
  function getNextAndPreviousQuarterDates() {
145
- const january = 1;
146
- const april = 4;
147
- const july = 7;
148
- const october = 10;
149
- const month = new Date().getMonth();
150
- let year = new Date().getFullYear();
151
- let nextQuarter = '';
152
- let prevQuarter = '';
164
+ const january = 1
165
+ const april = 4
166
+ const july = 7
167
+ const october = 10
168
+ const month = new Date().getMonth()
169
+ let year = new Date().getFullYear()
170
+ let nextQuarter = ''
171
+ let prevQuarter = ''
153
172
  if (month < april) {
154
- nextQuarter = `${year}-0${april}-01`;
155
- prevQuarter = `${year}-0${january}-01`;
173
+ nextQuarter = `${year}-0${april}-01`
174
+ prevQuarter = `${year}-0${january}-01`
156
175
  } else if (month < july) {
157
- nextQuarter = `${year}-0${july}-01`;
158
- prevQuarter = `${year}-0${april}-01`;
176
+ nextQuarter = `${year}-0${july}-01`
177
+ prevQuarter = `${year}-0${april}-01`
159
178
  } else if (month < october) {
160
- nextQuarter = `${year}-${october}-01`;
161
- prevQuarter = `${year}-0${july}-01`;
179
+ nextQuarter = `${year}-${october}-01`
180
+ prevQuarter = `${year}-0${july}-01`
162
181
  } else {
163
- prevQuarter = `${year}-${october}-01`;
164
- year++;
165
- nextQuarter = `${year}-0${january}-01`;
182
+ prevQuarter = `${year}-${october}-01`
183
+ year++
184
+ nextQuarter = `${year}-0${january}-01`
166
185
  }
167
186
 
168
- let result = [];
169
- result['next'] = nextQuarter;
170
- result['previous'] = prevQuarter;
171
- return result;
187
+ let result = []
188
+ result['next'] = nextQuarter
189
+ result['previous'] = prevQuarter
190
+ return result
172
191
  }
173
192
 
174
193
  /**
@@ -202,8 +221,8 @@ export async function fetchArtists(brand) {
202
221
  */
203
222
  export async function fetchSongArtistCount(brand) {
204
223
  const filter = await new FilterBuilder(
205
- `_type == "song" && brand == "${brand}" && references(^._id)`,
206
- { bypassPermissions: true }
224
+ `_type == "song" && brand == "${brand}" && references(^._id)`,
225
+ { bypassPermissions: true }
207
226
  ).buildFilter()
208
227
  const query = `
209
228
  count(*[_type == "artist"]{
@@ -213,13 +232,13 @@ export async function fetchSongArtistCount(brand) {
213
232
  return fetchSanity(query, true, { processNeedAccess: false })
214
233
  }
215
234
 
216
- export async function fetchPlayAlongsCount(brand, {
217
- searchTerm,
218
- includedFields,
219
- progressIds,
220
- progress,
221
- }) {
222
- const searchFilter = searchTerm ? `&& (artist->name match "${searchTerm}*" || instructor[]->name match "${searchTerm}*" || title match "${searchTerm}*" || name match "${searchTerm}*")` :'';
235
+ export async function fetchPlayAlongsCount(
236
+ brand,
237
+ { searchTerm, includedFields, progressIds, progress }
238
+ ) {
239
+ const searchFilter = searchTerm
240
+ ? `&& (artist->name match "${searchTerm}*" || instructor[]->name match "${searchTerm}*" || title match "${searchTerm}*" || name match "${searchTerm}*")`
241
+ : ''
223
242
 
224
243
  // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
225
244
  const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
@@ -327,7 +346,8 @@ export async function fetchNewReleases(
327
346
  const start = (page - 1) * limit
328
347
  const end = start + limit
329
348
  const sortOrder = getSortOrder(sort, brand)
330
- const filter = `_type in ${typesString} && brand == '${brand}' && show_in_new_feed == true`
349
+ const nextQuarter = getNextAndPreviousQuarterDates()['next']
350
+ const filter = `_type in ${typesString} && brand == '${brand}' && show_in_new_feed == true && (!defined(quarter_published) || quarter_published != '${nextQuarter}')`
331
351
  const fields = `
332
352
  "id": railcontent_id,
333
353
  title,
@@ -489,7 +509,7 @@ export async function fetchByRailContentId(id, contentType) {
489
509
  */
490
510
  export async function fetchByRailContentIds(ids, contentType = undefined) {
491
511
  if (!ids) {
492
- return [];
512
+ return []
493
513
  }
494
514
  const idsString = ids.join(',')
495
515
 
@@ -590,6 +610,8 @@ export async function fetchAll(
590
610
  if (type === 'archives') {
591
611
  typeFilter = `&& status == "archived"`
592
612
  bypassStatusAndPublishedValidation = true
613
+ } else if (type === 'lessons' || type === 'songs') {
614
+ typeFilter = ``
593
615
  } else if (type === 'pack') {
594
616
  typeFilter = `&& (_type == 'pack' || _type == 'semester-pack')`
595
617
  } else {
@@ -857,6 +879,14 @@ async function getProgressFilter(progress, progressIds) {
857
879
  const ids = await getAllStartedOrCompleted()
858
880
  return `&& !(railcontent_id in [${ids.join(',')}])`
859
881
  }
882
+ case 'recent': {
883
+ const ids = await getAllStartedOrCompleted()
884
+ return `&& (railcontent_id in [${ids.join(',')}])`
885
+ }
886
+ case 'incomplete': {
887
+ const ids = await getAllStarted()
888
+ return `&& railcontent_id in [${ids.join(',')}]`
889
+ }
860
890
  default:
861
891
  throw new Error(`'${progress}' progress option not implemented`)
862
892
  }
@@ -932,6 +962,13 @@ export async function fetchAllFilterOptions(
932
962
  coachId,
933
963
  includeTabs = false
934
964
  ) {
965
+ if (contentType == 'lessons' || contentType == 'songs') {
966
+ const metaData = processMetadata(brand, contentType, true)
967
+ return {
968
+ meta: metaData,
969
+ }
970
+ }
971
+
935
972
  if (coachId && contentType !== 'coach-lessons') {
936
973
  throw new Error(
937
974
  `Invalid contentType: '${contentType}' for coachId. It must be 'coach-lessons'.`
@@ -1210,26 +1247,26 @@ export async function fetchLessonContent(railContentId) {
1210
1247
  // Format changes made to the `fields` object may also need to be reflected in Musora-web-platform SanityGateway.php $fields object
1211
1248
  // Currently only for challenges and challenge lessons
1212
1249
  // If you're unsure, message Adrian, or just add them.
1213
- const fields = `title,
1250
+ const fields = `title,
1214
1251
  published_on,
1215
- "type":_type,
1252
+ "type":_type,
1216
1253
  "resources": ${resourcesField},
1217
- difficulty,
1218
- difficulty_string,
1219
- brand,
1254
+ difficulty,
1255
+ difficulty_string,
1256
+ brand,
1220
1257
  status,
1221
- soundslice,
1222
- instrumentless,
1223
- railcontent_id,
1224
- "id":railcontent_id,
1258
+ soundslice,
1259
+ instrumentless,
1260
+ railcontent_id,
1261
+ "id":railcontent_id,
1225
1262
  slug, artist->,
1226
- "thumbnail_url":thumbnail.asset->url,
1227
- "url": web_url_path,
1263
+ "thumbnail_url":thumbnail.asset->url,
1264
+ "url": web_url_path,
1228
1265
  soundslice_slug,
1229
1266
  "description": description[0].children[0].text,
1230
1267
  "chapters": chapter[]{
1231
1268
  chapter_description,
1232
- chapter_timecode,
1269
+ chapter_timecode,
1233
1270
  "chapter_thumbnail_url": chapter_thumbnail_url.asset->url
1234
1271
  },
1235
1272
  "instructors":instructor[]->name,
@@ -1237,7 +1274,7 @@ export async function fetchLessonContent(railContentId) {
1237
1274
  "id":railcontent_id,
1238
1275
  name,
1239
1276
  short_bio,
1240
- "biography": short_bio[0].children[0].text,
1277
+ "biography": short_bio[0].children[0].text,
1241
1278
  web_url_path,
1242
1279
  "coach_card_image": coach_card_image.asset->url,
1243
1280
  "coach_profile_image":thumbnail_url.asset->url
@@ -1264,16 +1301,16 @@ export async function fetchLessonContent(railContentId) {
1264
1301
  isSingle: true,
1265
1302
  })
1266
1303
  const chapterProcess = (result) => {
1267
- const chapters = result.chapters ?? [];
1268
- if(chapters.length == 0) return result
1304
+ const chapters = result.chapters ?? []
1305
+ if (chapters.length == 0) return result
1269
1306
  result.chapters = chapters.map((chapter, index) => ({
1270
1307
  ...chapter,
1271
- chapter_thumbnail_url: `https://musora-web-platform.s3.amazonaws.com/chapters/${result.brand}/Chapter${index + 1}.jpg`
1272
- }));
1308
+ chapter_thumbnail_url: `https://musora-web-platform.s3.amazonaws.com/chapters/${result.brand}/Chapter${index + 1}.jpg`,
1309
+ }))
1273
1310
  return result
1274
1311
  }
1275
1312
 
1276
- return fetchSanity(query, false, {customPostProcess:chapterProcess})
1313
+ return fetchSanity(query, false, { customPostProcess: chapterProcess })
1277
1314
  }
1278
1315
 
1279
1316
  /**
@@ -1353,7 +1390,7 @@ export async function fetchPackAll(railcontentId, type = 'pack') {
1353
1390
  return fetchByRailContentId(railcontentId, type)
1354
1391
  }
1355
1392
 
1356
- export async function fetchLiveEvent(brand) {
1393
+ export async function fetchLiveEvent(brand, forcedContentId = null) {
1357
1394
  //calendarIDs taken from addevent.php
1358
1395
  // TODO import instructor calendars to Sanity
1359
1396
  let defaultCalendarID = ''
@@ -1381,7 +1418,9 @@ export async function fetchLiveEvent(brand) {
1381
1418
  // See LiveStreamEventService.getCurrentOrNextLiveEvent for some nice complicated logic which I don't think is actually importart
1382
1419
  // this has some +- on times
1383
1420
  // But this query just finds the first scheduled event (sorted by start_time) that ends after now()
1384
- const query = `*[status == 'scheduled' && brand == '${brand}' && defined(live_event_start_time) && live_event_start_time <= '${getSanityDate(startDateTemp, false)}' && live_event_end_time >= '${getSanityDate(endDateTemp, false)}']{
1421
+ const query =
1422
+ forcedContentId !== null
1423
+ ? `*[railcontent_id == ${forcedContentId} ]{
1385
1424
  'slug': slug.current,
1386
1425
  'id': railcontent_id,
1387
1426
  live_event_start_time,
@@ -1392,13 +1431,36 @@ export async function fetchLiveEvent(brand) {
1392
1431
  'event_coach_url' : instructor[0]->web_url_path,
1393
1432
  'event_coach_calendar_id': coalesce(calendar_id, '${defaultCalendarID}'),
1394
1433
  title,
1395
- "image": thumbnail.asset->url,
1434
+ "thumbnail": thumbnail.asset->url,
1435
+ ${artistOrInstructorName()},
1436
+ difficulty_string,
1437
+ "instructors": instructor[]->{
1438
+ name,
1439
+ web_url_path,
1440
+ },
1441
+ 'videoId': coalesce(live_event_youtube_id, video.external_id),
1442
+ } | order(live_event_start_time)[0...1]`
1443
+ : `*[status == 'scheduled' && brand == '${brand}' && defined(live_event_start_time) && live_event_start_time <= '${getSanityDate(startDateTemp, false)}' && live_event_end_time >= '${getSanityDate(endDateTemp, false)}']{
1444
+ 'slug': slug.current,
1445
+ 'id': railcontent_id,
1446
+ live_event_start_time,
1447
+ live_event_end_time,
1448
+ live_event_youtube_id,
1449
+ railcontent_id,
1450
+ published_on,
1451
+ 'event_coach_url' : instructor[0]->web_url_path,
1452
+ 'event_coach_calendar_id': coalesce(calendar_id, '${defaultCalendarID}'),
1453
+ title,
1454
+ "thumbnail": thumbnail.asset->url,
1455
+ ${artistOrInstructorName()},
1456
+ difficulty_string,
1396
1457
  "instructors": instructor[]->{
1397
1458
  name,
1398
1459
  web_url_path,
1399
1460
  },
1400
1461
  'videoId': coalesce(live_event_youtube_id, video.external_id),
1401
1462
  } | order(live_event_start_time)[0...1]`
1463
+
1402
1464
  return await fetchSanity(query, false, { processNeedAccess: false })
1403
1465
  }
1404
1466
 
@@ -1561,10 +1623,10 @@ export async function fetchArtistLessons(
1561
1623
  progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
1562
1624
  const now = getSanityDate(new Date())
1563
1625
  const query = `{
1564
- "entity":
1626
+ "entity":
1565
1627
  *[_type == 'artist' && name == '${name}']
1566
- {'type': _type, name, 'thumbnail_url':thumbnail_url.asset->url,
1567
- 'lessons_count': count(*[${addType} brand == '${brand}' && references(^._id)]),
1628
+ {'type': _type, name, 'thumbnail_url':thumbnail_url.asset->url,
1629
+ 'lessons_count': count(*[${addType} brand == '${brand}' && references(^._id)]),
1568
1630
  'lessons': *[${addType} brand == '${brand}' && references(^._id) && (status in ['published'] || (status == 'scheduled' && defined(published_on) && published_on >= '${now}')) ${searchFilter} ${includedFieldsFilter} ${progressFilter}]{${fieldsString}}
1569
1631
  [${start}...${end}]}
1570
1632
  |order(${sortOrder})
@@ -1615,10 +1677,10 @@ export async function fetchGenreLessons(
1615
1677
  progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
1616
1678
  const now = getSanityDate(new Date())
1617
1679
  const query = `{
1618
- "entity":
1680
+ "entity":
1619
1681
  *[_type == 'genre' && name == '${name}']
1620
- {'type': _type, name, 'thumbnail_url':thumbnail_url.asset->url,
1621
- 'lessons_count': count(*[${addType} brand == '${brand}' && references(^._id)]),
1682
+ {'type': _type, name, 'thumbnail_url':thumbnail_url.asset->url,
1683
+ 'lessons_count': count(*[${addType} brand == '${brand}' && references(^._id)]),
1622
1684
  'lessons': *[${addType} brand == '${brand}' && references(^._id) && (status in ['published'] || (status == 'scheduled' && defined(published_on) && published_on >= '${now}')) ${searchFilter} ${includedFieldsFilter} ${progressFilter}]{${fieldsString}}
1623
1685
  [${start}...${end}]}
1624
1686
  |order(${sortOrder})
@@ -1638,8 +1700,8 @@ export async function fetchTopLevelParentId(railcontentId) {
1638
1700
  'parents': *[^._id in child[]._ref ${statusFilter}]{
1639
1701
  railcontent_id,
1640
1702
  'parents': *[^._id in child[]._ref ${statusFilter}]{
1641
- railcontent_id,
1642
- }
1703
+ railcontent_id,
1704
+ }
1643
1705
  }
1644
1706
  }
1645
1707
  }
@@ -1673,8 +1735,8 @@ export async function fetchHierarchy(railcontentId) {
1673
1735
  railcontent_id,
1674
1736
  'assignments': assignment[]{railcontent_id},
1675
1737
  'children': child[${childrenFilter}]->{
1676
- railcontent_id,
1677
- }
1738
+ railcontent_id,
1739
+ }
1678
1740
  }
1679
1741
  }
1680
1742
  },
@@ -1890,6 +1952,7 @@ export async function fetchShowsData(brand) {
1890
1952
 
1891
1953
  /**
1892
1954
  * Fetch metadata from the contentMetaData.js based on brand and type.
1955
+ * For v2 you need to provide page type('lessons' or 'songs') in type parameter
1893
1956
  *
1894
1957
  * @param {string} brand - The brand for which to fetch metadata.
1895
1958
  * @param {string} type - The type for which to fetch metadata.
@@ -1974,7 +2037,7 @@ function checkSanityConfig(config) {
1974
2037
  function buildRawQuery(
1975
2038
  filter = '',
1976
2039
  fields = '...',
1977
- { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false}
2040
+ { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false }
1978
2041
  ) {
1979
2042
  const sortString = sortOrder ? `order(${sortOrder})` : ''
1980
2043
  const countString = isSingle ? '[0...1]' : `[${start}...${end}]`
@@ -1988,10 +2051,10 @@ async function buildQuery(
1988
2051
  baseFilter = '',
1989
2052
  filterParams = { pullFutureContent: false },
1990
2053
  fields = '...',
1991
- { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false}
2054
+ { sortOrder = 'published_on desc', start = 0, end = 10, isSingle = false }
1992
2055
  ) {
1993
2056
  const filter = await new FilterBuilder(baseFilter, filterParams).buildFilter()
1994
- return buildRawQuery(filter, fields, { sortOrder, start, end, isSingle})
2057
+ return buildRawQuery(filter, fields, { sortOrder, start, end, isSingle })
1995
2058
  }
1996
2059
 
1997
2060
  function buildEntityAndTotalQuery(
@@ -2002,7 +2065,7 @@ function buildEntityAndTotalQuery(
2002
2065
  const sortString = sortOrder ? `order(${sortOrder})` : ''
2003
2066
  const countString = isSingle ? '[0...1]' : `[${start}...${end}]`
2004
2067
  const query = `{
2005
- "entity": *[${filter}] | ${sortString}${countString}
2068
+ "entity": *[${filter}] | ${sortString}${countString}
2006
2069
  {
2007
2070
  ${fields}
2008
2071
  },
@@ -2017,7 +2080,7 @@ function getFilterOptions(option, commonFilter, contentType, brand) {
2017
2080
 
2018
2081
  switch (option) {
2019
2082
  case 'difficulty':
2020
- filterGroq = `
2083
+ filterGroq = `
2021
2084
  "difficulty": [
2022
2085
  {"type": "All", "count": count(*[${commonFilter} && difficulty_string == "All"])},
2023
2086
  {"type": "Introductory", "count": count(*[${commonFilter} && (difficulty_string == "Novice" || difficulty_string == "Introductory")])},
@@ -2104,3 +2167,114 @@ function cleanUpGroq(query) {
2104
2167
 
2105
2168
  return cleanedQuery
2106
2169
  }
2170
+
2171
+ // V2 methods
2172
+
2173
+ export async function fetchTabData(
2174
+ brand,
2175
+ pageName,
2176
+ {
2177
+ page = 1,
2178
+ limit = 10,
2179
+ sort = '-published_on',
2180
+ includedFields = [],
2181
+ progressIds = undefined,
2182
+ progress = 'all',
2183
+ } = {}
2184
+ ) {
2185
+ const start = (page - 1) * limit
2186
+ const end = start + limit
2187
+
2188
+ // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
2189
+ const includedFieldsFilter =
2190
+ includedFields.length > 0 ? filtersToGroq(includedFields, [], pageName) : ''
2191
+
2192
+ // limits the results to supplied progressIds for started & completed filters
2193
+ const progressFilter = await getProgressFilter(progress, progressIds)
2194
+
2195
+ // Determine the sort order
2196
+ const sortOrder = getSortOrder(sort, brand, '')
2197
+
2198
+ let fields = DEFAULT_FIELDS
2199
+ let fieldsString = fields.join(',')
2200
+
2201
+ // Determine the group by clause
2202
+ let query = ''
2203
+ let entityFieldsString = ''
2204
+ let filter = ''
2205
+
2206
+ filter = `brand == "${brand}" ${includedFieldsFilter} ${progressFilter}`
2207
+ const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
2208
+ entityFieldsString = ` ${fieldsString},
2209
+ 'lesson_count': coalesce(count(child[${childrenFilter}]->), 0) ,
2210
+ 'length_in_seconds': coalesce(
2211
+ math::sum(
2212
+ select(
2213
+ child[${childrenFilter}]->length_in_seconds
2214
+ )
2215
+ ),
2216
+ length_in_seconds
2217
+ ),`
2218
+
2219
+ const filterWithRestrictions = await new FilterBuilder(filter, {}).buildFilter()
2220
+ query = buildEntityAndTotalQuery(filterWithRestrictions, entityFieldsString, {
2221
+ sortOrder: sortOrder,
2222
+ start: start,
2223
+ end: end,
2224
+ })
2225
+
2226
+ return fetchSanity(query, true)
2227
+ }
2228
+
2229
+ export async function fetchRecent(
2230
+ brand,
2231
+ pageName,
2232
+ { page = 1, limit = 10, sort = '-published_on', includedFields = [], progress = 'recent' } = {}
2233
+ ) {
2234
+ const mergedIncludedFields = [...includedFields, `tab,all`]
2235
+ const results = await fetchTabData(brand, pageName, {
2236
+ page,
2237
+ limit,
2238
+ sort,
2239
+ includedFields: mergedIncludedFields,
2240
+ progress: progress.toLowerCase(),
2241
+ })
2242
+ return results.entity
2243
+ }
2244
+
2245
+ export async function fetchScheduledAndNewReleases(
2246
+ brand,
2247
+ { page = 1, limit = 20, sort = '-published_on' } = {}
2248
+ ) {
2249
+ const upcomingTypes = getUpcomingEventsTypes(brand)
2250
+ const newTypes = getNewReleasesTypes(brand)
2251
+
2252
+ const scheduledTypes = merge(upcomingTypes, newTypes)
2253
+ const typesString = arrayJoinWithQuotes(scheduledTypes)
2254
+ const now = getSanityDate(new Date())
2255
+
2256
+ const start = (page - 1) * limit
2257
+ const end = start + limit
2258
+ const sortOrder = getSortOrder(sort, brand)
2259
+
2260
+ const query = `
2261
+ *[_type in [${typesString}] && brand == '${brand}' && ((status in ['published','scheduled'] && published_on > '${now}')||(show_in_new_feed == true)) ]
2262
+ [${start}...${end}]
2263
+ | order(published_on asc) {
2264
+ "id": railcontent_id,
2265
+ title,
2266
+ "image": thumbnail.asset->url,
2267
+ ${artistOrInstructorName()},
2268
+ "artists": instructor[]->name,
2269
+ difficulty,
2270
+ difficulty_string,
2271
+ length_in_seconds,
2272
+ published_on,
2273
+ "type": _type,
2274
+ show_in_new_feed,
2275
+ web_url_path,
2276
+ "permission_id": permission[]->railcontent_id
2277
+ }`
2278
+
2279
+ return fetchSanity(query, true)
2280
+ }
@@ -1,5 +1,9 @@
1
- import { fetchUserPermissionsData } from './railcontent.js'
2
- import { setLastUpdatedTime, wasLastUpdateOlderThanXSeconds } from './lastUpdated.js'
1
+ /**
2
+ * @module User-Permissions
3
+ */
4
+ import { setLastUpdatedTime, wasLastUpdateOlderThanXSeconds } from '../../lib/lastUpdated.js'
5
+ import { fetchUserPermissionsData } from '../railcontent.js'
6
+ import './types'
3
7
 
4
8
  /**
5
9
  * Exported functions that are excluded from index generation.
@@ -10,6 +14,11 @@ const excludeFromGeneratedIndex = []
10
14
  let userPermissionsPromise = null
11
15
  let lastUpdatedKey = `userPermissions_lastUpdated`
12
16
 
17
+ /**
18
+ * Fetches the user permissions data.
19
+ *
20
+ * @returns {Promise<UserPermissions>} - The user permissions data.
21
+ */
13
22
  export async function fetchUserPermissions() {
14
23
  if (!userPermissionsPromise || wasLastUpdateOlderThanXSeconds(10, lastUpdatedKey)) {
15
24
  userPermissionsPromise = fetchUserPermissionsData()
@@ -19,6 +28,11 @@ export async function fetchUserPermissions() {
19
28
  return await userPermissionsPromise
20
29
  }
21
30
 
31
+ /**
32
+ * Resets the user permissions data.
33
+ *
34
+ * @returns {Promise<void>}
35
+ */
22
36
  export async function reset() {
23
37
  userPermissionsPromise = null
24
38
  }