musora-content-services 1.0.20 → 1.0.21

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 (71) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/docs/config.js.html +124 -0
  3. package/docs/index.html +2 -2
  4. package/docs/module-Config.html +669 -0
  5. package/docs/module-Railcontent-Services.html +751 -0
  6. package/docs/{global.html → module-Sanity-Services.html} +52 -501
  7. package/docs/railcontent.js.html +178 -0
  8. package/docs/sanity.js.html +994 -0
  9. package/jsdoc.json +1 -1
  10. package/package.json +1 -1
  11. package/publish.sh +0 -0
  12. package/src/index.d.ts +2 -2
  13. package/src/index.js +32 -909
  14. package/src/services/config.js +52 -0
  15. package/src/services/railcontent.js +106 -0
  16. package/src/services/sanity.js +922 -0
  17. package/test/sanityQueryService.test.js +2 -2
  18. package/docs/data/search.json +0 -1
  19. package/docs/fonts/Inconsolata-Regular.ttf +0 -0
  20. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  21. package/docs/fonts/OpenSans-Bold-webfont.svg +0 -1830
  22. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  23. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  24. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
  25. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  26. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  27. package/docs/fonts/OpenSans-Italic-webfont.svg +0 -1830
  28. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  29. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  30. package/docs/fonts/OpenSans-Light-webfont.svg +0 -1831
  31. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  32. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  33. package/docs/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
  34. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  35. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  36. package/docs/fonts/OpenSans-Regular-webfont.svg +0 -1831
  37. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  38. package/docs/fonts/OpenSans-Regular.ttf +0 -0
  39. package/docs/fonts/OpenSans-Semibold-webfont.eot +0 -0
  40. package/docs/fonts/OpenSans-Semibold-webfont.svg +0 -1830
  41. package/docs/fonts/OpenSans-Semibold-webfont.ttf +0 -0
  42. package/docs/fonts/OpenSans-Semibold-webfont.woff +0 -0
  43. package/docs/fonts/OpenSans-SemiboldItalic-webfont.eot +0 -0
  44. package/docs/fonts/OpenSans-SemiboldItalic-webfont.svg +0 -1830
  45. package/docs/fonts/OpenSans-SemiboldItalic-webfont.ttf +0 -0
  46. package/docs/fonts/OpenSans-SemiboldItalic-webfont.woff +0 -0
  47. package/docs/fonts/WorkSans-Bold.ttf +0 -0
  48. package/docs/index.js.html +0 -1122
  49. package/docs/scripts/core.js +0 -726
  50. package/docs/scripts/core.min.js +0 -23
  51. package/docs/scripts/resize.js +0 -90
  52. package/docs/scripts/search.min.js +0 -6
  53. package/docs/scripts/third-party/Apache-License-2.0.txt +0 -202
  54. package/docs/scripts/third-party/fuse.js +0 -9
  55. package/docs/scripts/third-party/hljs-line-num-original.js +0 -369
  56. package/docs/scripts/third-party/hljs-line-num.js +0 -1
  57. package/docs/scripts/third-party/hljs-original.js +0 -5171
  58. package/docs/scripts/third-party/hljs.js +0 -1
  59. package/docs/scripts/third-party/popper.js +0 -5
  60. package/docs/scripts/third-party/tippy.js +0 -1
  61. package/docs/scripts/third-party/tocbot.js +0 -672
  62. package/docs/scripts/third-party/tocbot.min.js +0 -1
  63. package/docs/styles/clean-jsdoc-theme-base.css +0 -1159
  64. package/docs/styles/clean-jsdoc-theme-dark.css +0 -412
  65. package/docs/styles/clean-jsdoc-theme-light.css +0 -482
  66. package/docs/styles/clean-jsdoc-theme-scrollbar.css +0 -30
  67. package/docs/styles/clean-jsdoc-theme-without-scrollbar.min.css +0 -1
  68. package/docs/styles/clean-jsdoc-theme.min.css +0 -1
  69. package/docs/styles/jsdoc-default.css +0 -692
  70. package/docs/styles/prettify-jsdoc.css +0 -111
  71. package/docs/styles/prettify-tomorrow.css +0 -132
@@ -0,0 +1,994 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>sanity.js - Documentation</title>
7
+
8
+
9
+ <script src="scripts/prettify/prettify.js"></script>
10
+ <script src="scripts/prettify/lang-css.js"></script>
11
+ <!--[if lt IE 9]>
12
+ <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
13
+ <![endif]-->
14
+ <link type="text/css" rel="stylesheet" href="styles/prettify.css">
15
+ <link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
16
+ <script src="scripts/nav.js" defer></script>
17
+
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ </head>
20
+ <body>
21
+
22
+ <input type="checkbox" id="nav-trigger" class="nav-trigger" />
23
+ <label for="nav-trigger" class="navicon-button x">
24
+ <div class="navicon"></div>
25
+ </label>
26
+
27
+ <label for="nav-trigger" class="overlay"></label>
28
+
29
+ <nav >
30
+
31
+
32
+ <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Config.html">Config</a><ul class='methods'><li data-type='method'><a href="module-Config.html#~initializeService">initializeService</a></li></ul></li><li><a href="module-Railcontent-Services.html">Railcontent-Services</a><ul class='methods'><li data-type='method'><a href="module-Railcontent-Services.html#.fetchAllCompletedStates">fetchAllCompletedStates</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchCurrentSongComplete">fetchCurrentSongComplete</a></li><li data-type='method'><a href="module-Railcontent-Services.html#.fetchSongsInProgress">fetchSongsInProgress</a></li></ul></li><li><a href="module-Sanity-Services.html">Sanity-Services</a><ul class='methods'><li data-type='method'><a href="module-Sanity-Services.html#.fetchAll">fetchAll</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchAllFilterOptions">fetchAllFilterOptions</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchAllSongs">fetchAllSongs</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchArtists">fetchArtists</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchByRailContentId">fetchByRailContentId</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchByRailContentIds">fetchByRailContentIds</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchChildren">fetchChildren</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchLessonContent">fetchLessonContent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchLiveEvent">fetchLiveEvent</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethodChildren">fetchMethodChildren</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchMethodNextLesson">fetchMethodNextLesson</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchNewReleases">fetchNewReleases</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchNextPreviousLesson">fetchNextPreviousLesson</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchPackAll">fetchPackAll</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchPackChildren">fetchPackChildren</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchRelatedLessons">fetchRelatedLessons</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchRelatedSongs">fetchRelatedSongs</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSanity">fetchSanity</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSongArtistCount">fetchSongArtistCount</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSongById">fetchSongById</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSongCount">fetchSongCount</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchSongFilterOptions">fetchSongFilterOptions</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchUpcomingEvents">fetchUpcomingEvents</a></li><li data-type='method'><a href="module-Sanity-Services.html#.fetchWorkouts">fetchWorkouts</a></li></ul></li></ul>
33
+
34
+ </nav>
35
+
36
+ <div id="main">
37
+
38
+ <h1 class="page-title">sanity.js</h1>
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ <section>
47
+ <article>
48
+ <pre class="prettyprint source linenums"><code>/**
49
+ * @module Sanity-Services
50
+ */
51
+
52
+ const { contentTypeConfig } = require('../contentTypeConfig.js');
53
+ const { globalConfig } = require('./config');
54
+
55
+ import { fetchAllCompletedStates, fetchCurrentSongComplete } from './railcontent.js';
56
+
57
+ /**
58
+ * Fetch a song by its document ID from Sanity.
59
+ *
60
+ * @param {string} documentId - The ID of the document to fetch.
61
+ * @returns {Promise&lt;Object|null>} - A promise that resolves to an object containing the song data or null if not found.
62
+ *
63
+ * @example
64
+ * fetchSongById('abc123')
65
+ * .then(song => console.log(song))
66
+ * .catch(error => console.error(error));
67
+ */
68
+ export async function fetchSongById(songId) {
69
+ const query = `
70
+ *[_type == "song" &amp;&amp; railcontent_id == ${songId}]{
71
+ title,
72
+ "thumbnail_url": thumbnail.asset->url,
73
+ "style": genre[0]->name,
74
+ "artist": artist->name,
75
+ album,
76
+ instrumentless,
77
+ soundslice,
78
+ railcontent_id,
79
+ "resources": resource[]{resource_url, resource_name},
80
+ }`;
81
+
82
+ const songData = await fetchSanity(query, false);
83
+
84
+ // Check if the necessary user information is available in globalConfig
85
+ if (!songData || !globalConfig.railcontentConfig.userId || !globalConfig.railcontentConfig.token) {
86
+ return songData;
87
+ }
88
+
89
+ // Fetch the completion state for the current song
90
+ const currentSongComplete = await fetchCurrentSongComplete(songId);
91
+
92
+ if (currentSongComplete) {
93
+ songData.completed = currentSongComplete.state !== "not started";
94
+ songData.progress_percent = currentSongComplete.percent.toString();
95
+ }
96
+
97
+ return songData;
98
+ }
99
+
100
+ /**
101
+ * Fetch all artists with lessons available for a specific brand.
102
+ *
103
+ * @param {string} brand - The brand for which to fetch artists.
104
+ * @returns {Promise&lt;Object|null>} - A promise that resolves to an array of artist objects or null if not found.
105
+ *
106
+ * @example
107
+ * fetchArtists('drumeo')
108
+ * .then(artists => console.log(artists))
109
+ * .catch(error => console.error(error));
110
+ */
111
+ export async function fetchArtists(brand) {
112
+ const query = `
113
+ *[_type == "artist"]{
114
+ name,
115
+ "lessonsCount": count(*[_type == "song" &amp;&amp; brand == "${brand}" &amp;&amp; references(^._id)])
116
+ }[lessonsCount > 0]`;
117
+ return fetchSanity(query, true);
118
+ }
119
+
120
+ /**
121
+ * Fetch current number of artists for songs within a brand.
122
+ * @param {string} brand - The current brand.
123
+ * @returns {Promise&lt;int|null>} - The fetched count of artists.
124
+ */
125
+ export async function fetchSongArtistCount(brand) {
126
+ const query = `count(*[_type == 'artist']{'lessonsCount': count(*[_type == 'song' &amp;&amp; brand == '${brand}' &amp;&amp; references(^._id)]._id)}[lessonsCount > 0])`;
127
+ return fetchSanity(query, true);
128
+ }
129
+
130
+ /**
131
+ * Fetch related songs for a specific brand and song ID.
132
+ *
133
+ * @param {string} brand - The brand for which to fetch related songs.
134
+ * @param {string} songId - The ID of the song to find related songs for.
135
+ * @returns {Promise&lt;Object|null>} - A promise that resolves to an array of related song objects or null if not found.
136
+ *
137
+ * @example
138
+ * fetchRelatedSongs('drumeo', '12345')
139
+ * .then(relatedSongs => console.log(relatedSongs))
140
+ * .catch(error => console.error(error));
141
+ */
142
+ export async function fetchRelatedSongs(brand, songId) {
143
+ const query = `
144
+ *[_type == "song" &amp;&amp; railcontent_id == ${songId}]{
145
+ "data": array::unique([
146
+ ...(*[_type == "song" &amp;&amp; brand == "${brand}" &amp;&amp; railcontent_id != ${songId} &amp;&amp; references(^.artist->_id)]{
147
+ "type": _type,
148
+ "id": railcontent_id,
149
+ "url": web_url_path,
150
+ "published_on": published_on,
151
+ status,
152
+ "fields": [
153
+ {
154
+ "key": "title",
155
+ "value": title
156
+ },
157
+ {
158
+ "key": "artist",
159
+ "value": artist->name
160
+ },
161
+ {
162
+ "key": "difficulty",
163
+ "value": difficulty
164
+ },
165
+ {
166
+ "key": "length_in_seconds",
167
+ "value": soundslice[0].soundslice_length_in_second
168
+ }
169
+ ],
170
+ "data": [{
171
+ "key": "thumbnail_url",
172
+ "value": thumbnail.asset->url
173
+ }]
174
+ }[0...10]),
175
+ ...(*[_type == "song" &amp;&amp; brand == "${brand}" &amp;&amp; railcontent_id != ${songId} &amp;&amp; references(^.genre[]->_id)]{
176
+ "type": _type,
177
+ "id": railcontent_id,
178
+ "url": web_url_path,
179
+ "published_on": published_on,
180
+ status,
181
+ "fields": [
182
+ {
183
+ "key": "title",
184
+ "value": title
185
+ },
186
+ {
187
+ "key": "artist",
188
+ "value": artist->name
189
+ },
190
+ {
191
+ "key": "difficulty",
192
+ "value": difficulty
193
+ },
194
+ {
195
+ "key": "length_in_seconds",
196
+ "value": soundslice[0].soundslice_length_in_second
197
+ }
198
+ ],
199
+ "data": [{
200
+ "key": "thumbnail_url",
201
+ "value": thumbnail.asset->url
202
+ }]
203
+ }[0...10])
204
+ ])[0...10]
205
+ }`;
206
+
207
+ // Fetch the related songs data
208
+ const relatedSongsData = await fetchSanity(query, true);
209
+
210
+ if (!relatedSongsData || !userId || !token) {
211
+ return relatedSongsData;
212
+ }
213
+
214
+ // Extract the IDs of related lessons
215
+ const relatedLessonIds = relatedSongsData.data.map(lesson => lesson.id);
216
+
217
+ // Fetch the completion states for the related lessons
218
+ const relatedLessonsCompletionStates = await fetchAllCompletedStates(relatedLessonIds);
219
+
220
+ // Map the completion states to the related lessons
221
+ relatedSongsData.data = relatedSongsData.data.map(lesson => {
222
+ const lessonCompletionState = relatedLessonsCompletionStates[lesson.id];
223
+ if (lessonCompletionState) {
224
+ return {
225
+ ...lesson,
226
+ completed: lessonCompletionState.state !== "not started",
227
+ lesson_progress: lessonCompletionState.percent.toString()
228
+ };
229
+ }
230
+ return lesson;
231
+ });
232
+
233
+ return relatedSongsData;
234
+ }
235
+
236
+ /**
237
+ * Fetch all songs for a specific brand with pagination and search options.
238
+ * @param {string} brand - The brand for which to fetch songs.
239
+ * @param {Object} params - Parameters for pagination, filtering, and sorting.
240
+ * @param {number} [params.page=1] - The page number for pagination.
241
+ * @param {number} [params.limit=10] - The number of songs per page.
242
+ * @param {string} [params.searchTerm=""] - The search term to filter songs by title or artist.
243
+ * @param {string} [params.sort="-published_on"] - The field to sort the songs by.
244
+ * @param {Array&lt;string>} [params.includedFields=[]] - The fields to include in the query.
245
+ * @param {string} [params.groupBy=""] - The field to group the results by.
246
+ * @returns {Promise&lt;Object|null>} - The fetched song data or null if not found.
247
+ *
248
+ * @example
249
+ * fetchAllSongs('drumeo', {
250
+ * page: 2,
251
+ * limit: 20,
252
+ * searchTerm: 'rock',
253
+ * sort: 'published_on',
254
+ * includedFields: ['difficulty', 'style'],
255
+ * groupBy: 'artist'
256
+ * })
257
+ * .then(result => console.log(result))
258
+ * .catch(error => console.error(error));
259
+ */
260
+ export async function fetchAllSongs(brand, {
261
+ page = 1,
262
+ limit = 10,
263
+ searchTerm = "",
264
+ sort = "-published_on",
265
+ includedFields = [],
266
+ groupBy = ""
267
+ }) {
268
+ return fetchAll(brand, 'song', {page, limit, searchTerm, sort, includedFields, groupBy});
269
+ }
270
+
271
+ /**
272
+ * Fetch filter options for a specific brand.
273
+ *
274
+ * @param {string} brand - The brand for which to fetch filter options.
275
+ * @returns {Promise&lt;Object|null>} - A promise that resolves to an object containing filter options or null if not found.
276
+ *
277
+ * @example
278
+ * fetchSongFilterOptions('drumeo')
279
+ * .then(options => console.log(options))
280
+ * .catch(error => console.error(error));
281
+ */
282
+ export async function fetchSongFilterOptions(brand) {
283
+ const query = `
284
+ {
285
+ "difficulty": [
286
+ {"type": "Introductory", "count": count(*[_type == 'song' &amp;&amp; brand == '${brand}' &amp;&amp; difficulty_string == "Introductory"]._id)},
287
+ {"type": "Beginner", "count": count(*[_type == 'song' &amp;&amp; brand == '${brand}' &amp;&amp; difficulty_string == "Beginner"]._id)},
288
+ {"type": "Intermediate", "count": count(*[_type == 'song' &amp;&amp; brand == '${brand}' &amp;&amp; difficulty_string == "Intermediate"]._id)},
289
+ {"type": "Advanced", "count": count(*[_type == 'song' &amp;&amp; brand == '${brand}' &amp;&amp; difficulty_string == "Advanced"]._id)},
290
+ {"type": "Expert", "count": count(*[_type == 'song' &amp;&amp; brand == '${brand}' &amp;&amp; difficulty_string == "Expert"]._id)}
291
+ ],
292
+ "genre": *[_type == 'genre' &amp;&amp; 'song' in filter_types] {
293
+ "type": name,
294
+ "count": count(*[_type == 'song' &amp;&amp; brand == '${brand}' &amp;&amp; references(^._id)]._id)
295
+ },
296
+ "instrumentless": [
297
+ {"type": "Full Song Only", "count": count(*[_type == 'song' &amp;&amp; brand == '${brand}' &amp;&amp; instrumentless == false]._id)},
298
+ {"type": "Instrument Removed", "count": count(*[_type == 'song' &amp;&amp; brand == '${brand}' &amp;&amp; instrumentless == true]._id)}
299
+ ]
300
+ }
301
+ `;
302
+
303
+ return fetchSanity(query, true);
304
+ }
305
+
306
+ /**
307
+ * Fetch the total count of songs for a specific brand.
308
+ * @param {string} brand - The brand for which to fetch the song count.
309
+ * @returns {Promise&lt;number|null>} - The total count of songs or null if an error occurs.
310
+ */
311
+ export async function fetchSongCount(brand) {
312
+ const query = `count(*[_type == 'song' &amp;&amp; brand == "${brand}"])`;
313
+ return fetchSanity(query, false);
314
+ }
315
+
316
+ /**
317
+ * Fetch the latest workouts for the home page of a specific brand.
318
+ * This function retrieves the latest workout content for a given brand, fetching up to five workouts. The workouts are sorted in descending order by their publication date.
319
+ * @param {string} brand - The brand for which to fetch workouts (e.g., 'drumeo', 'pianote').
320
+ * @returns {Promise&lt;Array&lt;Object>|null>} - A promise that resolves to an array of workout data objects or null if no workouts are found.
321
+ *
322
+ * @example
323
+ * fetchWorkouts('drumeo')
324
+ * .then(workouts => console.log(workouts))
325
+ * .catch(error => console.error(error));
326
+ */
327
+ export async function fetchWorkouts(brand) {
328
+ const query = `*[_type == 'workout' &amp;&amp; brand == '${brand}'] [0...5] {
329
+ "id": railcontent_id,
330
+ title,
331
+ "image": thumbnail.asset->url,
332
+ "artist_name": instructor[0]->name,
333
+ "artists": instructor[]->name,
334
+ difficulty,
335
+ difficulty_string,
336
+ length_in_seconds,
337
+ published_on,
338
+ "type": _type,
339
+ web_url_path,
340
+ } | order(published_on desc)[0...5]`
341
+ return fetchSanity(query, true);
342
+ }
343
+
344
+ /**
345
+ * Fetch the latest new releases for a specific brand.
346
+ * @param {string} brand - The brand for which to fetch new releases.
347
+ * @returns {Promise&lt;Object|null>} - The fetched new releases data or null if not found.
348
+ */
349
+ export async function fetchNewReleases(brand) {
350
+ const newTypes = {
351
+ 'drumeo': ["drum-fest-international-2022", "spotlight", "the-history-of-electronic-drums", "backstage-secrets", "quick-tips", "question-and-answer", "student-collaborations", "live-streams", "live", "podcasts", "solos", "boot-camps", "gear-guides", "performances", "in-rhythm", "challenges", "on-the-road", "diy-drum-experiments", "rhythmic-adventures-of-captain-carson", "study-the-greats", "rhythms-from-another-planet", "tama-drums", "paiste-cymbals", "behind-the-scenes", "exploring-beats", "sonor-drums", "course", "play-along", "student-focus", "coach-stream", "learning-path-level", "unit", "quick-tips", "live", "question-and-answer", "student-review", "boot-camps", "song", "chords-and-scales", "pack", "podcasts", "workout", "challenge", "challenge-part"],
352
+ 'pianote': ["student-review", "student-reviews", "question-and-answer", "course", "play-along", "student-focus", "coach-stream", "learning-path-level", "unit", "quick-tips", "live", "question-and-answer", "student-review", "boot-camps", "song", "chords-and-scales", "pack", "podcasts", "workout", "challenge", "challenge-part"],
353
+ 'guitareo': ["student-review", "student-reviews", "question-and-answer", "archives", "recording", "course", "play-along", "student-focus", "coach-stream", "learning-path-level", "unit", "quick-tips", "live", "question-and-answer", "student-review", "boot-camps", "song", "chords-and-scales", "pack", "podcasts", "workout", "challenge", "challenge-part"],
354
+ 'singeo': ["student-review", "student-reviews", "question-and-answer", "course", "play-along", "student-focus", "coach-stream", "learning-path-level", "unit", "quick-tips", "live", "question-and-answer", "student-review", "boot-camps", "song", "chords-and-scales", "pack", "podcasts", "workout", "challenge", "challenge-part"],
355
+ 'default': ["student-review", "student-reviews", "question-and-answer", "course", "play-along", "student-focus", "coach-stream", "learning-path-level", "unit", "quick-tips", "live", "question-and-answer", "student-review", "boot-camps", "song", "chords-and-scales", "pack", "podcasts", "workout", "challenge", "challenge-part"]
356
+ };
357
+ const typesString = arrayJoinWithQuotes(newTypes[brand] ?? newTypes['default']);
358
+ const query = `*[_type in [${typesString}] &amp;&amp; brand == '${brand}'] | order(releaseDate desc) [0...5] {
359
+ "id": railcontent_id,
360
+ title,
361
+ "image": thumbnail.asset->url,
362
+ "artist_name": instructor[0]->name,
363
+ "artists": instructor[]->name,
364
+ difficulty,
365
+ difficulty_string,
366
+ length_in_seconds,
367
+ published_on,
368
+ "type": _type,
369
+ web_url_path,
370
+ } | order(published_on desc)[0...5]`
371
+ return fetchSanity(query, true);
372
+ }
373
+
374
+ /**
375
+ * Fetch upcoming events for a specific brand.
376
+ *
377
+ * @param {string} brand - The brand for which to fetch upcoming events.
378
+ * @returns {Promise&lt;Object|null>} - A promise that resolves to an array of upcoming event objects or null if not found.
379
+ *
380
+ * @example
381
+ * fetchUpcomingEvents('drumeo')
382
+ * .then(events => console.log(events))
383
+ * .catch(error => console.error(error));
384
+ */
385
+ export async function fetchUpcomingEvents(brand) {
386
+ const baseLiveTypes = ["student-review", "student-reviews", "student-focus", "coach-stream", "live", "question-and-answer", "student-review", "boot-camps", "recording", "pack-bundle-lesson"];
387
+ const liveTypes = {
388
+ 'drumeo': [...baseLiveTypes, "drum-fest-international-2022", "spotlight", "the-history-of-electronic-drums", "backstage-secrets", "quick-tips", "student-collaborations", "live-streams", "podcasts", "solos", "gear-guides", "performances", "in-rhythm", "challenges", "on-the-road", "diy-drum-experiments", "rhythmic-adventures-of-captain-carson", "study-the-greats", "rhythms-from-another-planet", "tama-drums", "paiste-cymbals", "behind-the-scenes", "exploring-beats", "sonor-drums"],
389
+ 'pianote': baseLiveTypes,
390
+ 'guitareo': [...baseLiveTypes, "archives"],
391
+ 'singeo': baseLiveTypes,
392
+ 'default': baseLiveTypes
393
+ };
394
+ const typesString = arrayJoinWithQuotes(liveTypes[brand] ?? liveTypes['default']);
395
+ const now = getSanityDate(new Date());
396
+ //TODO: status = 'scheduled' is this handled in sanity?
397
+ const query = `*[_type in [${typesString}] &amp;&amp; brand == '${brand}' &amp;&amp; published_on > '${now}']{
398
+ "id": railcontent_id,
399
+ title,
400
+ "image": thumbnail.asset->url,
401
+ "artist_name": instructor[0]->name,
402
+ "artists": instructor[]->name,
403
+ difficulty,
404
+ difficulty_string,
405
+ length_in_seconds,
406
+ published_on,
407
+ "type": _type,
408
+ web_url_path,
409
+ } | order(published_on asc)[0...5]`;
410
+ return fetchSanity(query, true);
411
+ }
412
+
413
+ /**
414
+ * Fetch upcoming events for a specific brand that are within 48 hours before their `published_on` date
415
+ * and are currently ongoing based on their `length_in_seconds`.
416
+ *
417
+ * This function retrieves events that have a `published_on` date within the last 48 hours or are currently
418
+ * ongoing based on the event's duration (`length_in_seconds`).
419
+ *
420
+ * @param {string} brand - The brand for which to fetch upcoming events (e.g., 'drumeo', 'pianote', etc.).
421
+ * @returns {Promise&lt;Array&lt;Object>|null>} - A promise that resolves to an array of event objects or null if no events are found.
422
+ *
423
+ * @example
424
+ * // Example usage:
425
+ * fetchUpcomingEvents('drumeo')
426
+ * .then(events => console.log(events))
427
+ * .catch(error => console.error(error));
428
+ *
429
+ */
430
+ export async function fetchLiveEvent(brand) {
431
+ const baseLiveTypes = ["student-review", "student-reviews", "student-focus", "coach-stream", "live", "question-and-answer", "student-review", "boot-camps", "recording", "pack-bundle-lesson"];
432
+ const liveTypes = {
433
+ 'drumeo': [...baseLiveTypes, "drum-fest-international-2022", "spotlight", "the-history-of-electronic-drums", "backstage-secrets", "quick-tips", "student-collaborations", "live-streams", "podcasts", "solos", "gear-guides", "performances", "in-rhythm", "challenges", "on-the-road", "diy-drum-experiments", "rhythmic-adventures-of-captain-carson", "study-the-greats", "rhythms-from-another-planet", "tama-drums", "paiste-cymbals", "behind-the-scenes", "exploring-beats", "sonor-drums"],
434
+ 'pianote': baseLiveTypes,
435
+ 'guitareo': [...baseLiveTypes, "archives"],
436
+ 'singeo': baseLiveTypes,
437
+ 'default': baseLiveTypes
438
+ };
439
+
440
+ const typesString = arrayJoinWithQuotes(liveTypes[brand] ?? liveTypes['default']);
441
+ const now = getSanityDate(new Date());
442
+ const twoDaysAgo = getSanityDate(new Date(Date.now() - 48 * 60 * 60 * 1000)); // 48 hours ago
443
+
444
+ // Adjust the query to filter events based on the calculated time window
445
+ const query = `
446
+ *[_type in [${typesString}] &amp;&amp; brand == '${brand}'
447
+ &amp;&amp; published_on > '${twoDaysAgo}'
448
+ || (published_on &lt;= '${now}' &amp;&amp; dateTime(published_on) + length_in_seconds * 1000 > '${now}')
449
+ ] {
450
+ "id": railcontent_id,
451
+ title,
452
+ "image": thumbnail.asset->url,
453
+ "artist_name": instructor[0]->name,
454
+ "artists": instructor[]->name,
455
+ difficulty,
456
+ difficulty_string,
457
+ length_in_seconds,
458
+ published_on,
459
+ "type": _type,
460
+ web_url_path,
461
+ } | order(published_on asc)[0]`;
462
+
463
+ return fetchSanity(query, true);
464
+ }
465
+
466
+
467
+ /**
468
+ * Fetch content by a specific Railcontent ID.
469
+ *
470
+ * @param {string} id - The Railcontent ID of the content to fetch.
471
+ * @returns {Promise&lt;Object|null>} - A promise that resolves to the content object or null if not found.
472
+ *
473
+ * @example
474
+ * fetchByRailContentId('abc123')
475
+ * .then(content => console.log(content))
476
+ * .catch(error => console.error(error));
477
+ */
478
+ export async function fetchByRailContentId(id) {
479
+ const query = `*[railcontent_id == ${id}]{
480
+ railcontent_id,
481
+ title,
482
+ "image": thumbnail.asset->url,
483
+ "artist_name": artist->name,
484
+ artist,
485
+ difficulty,
486
+ difficulty_string,
487
+ web_url_path,
488
+ published_on
489
+ }`
490
+ return fetchSanity(query, false);
491
+ }
492
+
493
+ /**
494
+ * Fetch content by an array of Railcontent IDs.
495
+ *
496
+ * @param {Array&lt;string>} ids - The array of Railcontent IDs of the content to fetch.
497
+ * @returns {Promise&lt;Array&lt;Object>|null>} - A promise that resolves to an array of content objects or null if not found.
498
+ *
499
+ * @example
500
+ * fetchByRailContentIds(['abc123', 'def456', 'ghi789'])
501
+ * .then(contents => console.log(contents))
502
+ * .catch(error => console.error(error));
503
+ */
504
+ export async function fetchByRailContentIds(ids) {
505
+ const idsString = ids.join(',');
506
+ const query = `*[railcontent_id in [${idsString}]]{
507
+ railcontent_id,
508
+ title,
509
+ "image": thumbnail.asset->url,
510
+ "artist_name": artist->name,
511
+ artist,
512
+ difficulty,
513
+ difficulty_string,
514
+ web_url_path,
515
+ published_on
516
+ }`
517
+ return fetchSanity(query, true);
518
+ }
519
+
520
+ /**
521
+ * Fetch all content for a specific brand and type with pagination, search, and grouping options.
522
+ * @param {string} brand - The brand for which to fetch content.
523
+ * @param {string} type - The content type to fetch (e.g., 'song', 'artist').
524
+ * @param {Object} params - Parameters for pagination, filtering, sorting, and grouping.
525
+ * @param {number} [params.page=1] - The page number for pagination.
526
+ * @param {number} [params.limit=10] - The number of items per page.
527
+ * @param {string} [params.searchTerm=""] - The search term to filter content by title or artist.
528
+ * @param {string} [params.sort="-published_on"] - The field to sort the content by.
529
+ * @param {Array&lt;string>} [params.includedFields=[]] - The fields to include in the query.
530
+ * @param {string} [params.groupBy=""] - The field to group the results by (e.g., 'artist', 'genre').
531
+ * @returns {Promise&lt;Object|null>} - The fetched content data or null if not found.
532
+ *
533
+ * @example
534
+ * fetchAll('drumeo', 'song', {
535
+ * page: 2,
536
+ * limit: 20,
537
+ * searchTerm: 'jazz',
538
+ * sort: '-popularity',
539
+ * includedFields: ['difficulty,Intermediate'],
540
+ * groupBy: 'artist'
541
+ * })
542
+ * .then(content => console.log(content))
543
+ * .catch(error => console.error(error));
544
+ */
545
+ export async function fetchAll(brand, type, {
546
+ page = 1,
547
+ limit = 10,
548
+ searchTerm = "",
549
+ sort = "-published_on",
550
+ includedFields = [],
551
+ groupBy = ""
552
+ }) {
553
+ let config = contentTypeConfig[type] ?? {};
554
+ let additionalFields = config?.fields ?? [];
555
+ let isGroupByOneToOne = (groupBy ? config?.relationships[groupBy]?.isOneToOne : false) ?? false;
556
+ const start = (page - 1) * limit;
557
+ const end = start + limit;
558
+
559
+ // Construct the search filter
560
+ const searchFilter = searchTerm
561
+ ? `&amp;&amp; (artist->name match "${searchTerm}*" || title match "${searchTerm}*")`
562
+ : "";
563
+
564
+ // Construct the included fields filter, replacing 'difficulty' with 'difficulty_string'
565
+ const includedFieldsFilter = includedFields.length > 0
566
+ ? includedFields.map(field => {
567
+ let [key, value] = field.split(',');
568
+ if (key === 'difficulty') {
569
+ key = 'difficulty_string';
570
+ }
571
+ return `&amp;&amp; ${key} == "${value}"`;
572
+ }).join(' ')
573
+ : "";
574
+
575
+ // Determine the sort order
576
+ let sortOrder;
577
+ switch (sort) {
578
+ case "slug":
579
+ sortOrder = "artist->name asc";
580
+ break;
581
+ case "published_on":
582
+ sortOrder = "published_on desc";
583
+ break;
584
+ case "-published_on":
585
+ sortOrder = "published_on asc";
586
+ break;
587
+ case "-slug":
588
+ sortOrder = "artist->name desc";
589
+ break;
590
+ case "-popularity":
591
+ sortOrder = "popularity desc";
592
+ break;
593
+ default:
594
+ sortOrder = "published_on asc";
595
+ break;
596
+ }
597
+
598
+ let defaultFields = ['railcontent_id',
599
+ 'title',
600
+ '"image": thumbnail.asset->url',
601
+ 'difficulty',
602
+ 'difficulty_string',
603
+ 'web_url_path',
604
+ 'published_on'];
605
+
606
+ let fields = defaultFields.concat(additionalFields);
607
+ let fieldsString = fields.join(',');
608
+
609
+ // Determine the group by clause
610
+ let query = "";
611
+ if (groupBy !== "" &amp;&amp; isGroupByOneToOne) {
612
+ query = `
613
+ {
614
+ "total": count(*[_type == '${groupBy}' &amp;&amp; count(*[_type == '${type}' &amp;&amp; brand == '${brand}' &amp;&amp; ^._id == ${groupBy}._ref ]._id) > 0]),
615
+ "entity": *[_type == '${groupBy}' &amp;&amp; count(*[_type == '${type}' &amp;&amp; brand == '${brand}' &amp;&amp; ^._id == ${groupBy}._ref ]._id) > 0]
616
+ {
617
+ 'id': _id,
618
+ 'type': _type,
619
+ name,
620
+ 'head_shot_picture_url': thumbnail_url.asset->url,
621
+ 'all_lessons_count': count(*[_type == '${type}' &amp;&amp; brand == '${brand}' &amp;&amp; ^._id == ${groupBy}._ref ]._id),
622
+ 'lessons': *[_type == '${type}' &amp;&amp; brand == '${brand}' &amp;&amp; ^._id == ${groupBy}._ref ]{
623
+ ${fieldsString},
624
+ ${groupBy}
625
+ }[0...10]
626
+ }
627
+ |order(${sortOrder})
628
+ [${start}...${end}]
629
+ }`;
630
+ } else if (groupBy !== "") {
631
+ query = `
632
+ {
633
+ "total": count(*[_type == '${groupBy}' &amp;&amp; count(*[_type == '${type}' &amp;&amp; brand == '${brand}' &amp;&amp; ^._id in ${groupBy}[]._ref]._id)>0]),
634
+ "entity": *[_type == '${groupBy}' &amp;&amp; count(*[_type == '${type}' &amp;&amp; brand == '${brand}' &amp;&amp; ^._id in ${groupBy}[]._ref]._id) > 0]
635
+ {
636
+ 'id': _id,
637
+ 'type': _type,
638
+ name,
639
+ 'head_shot_picture_url': thumbnail_url.asset->url,
640
+ 'all_lessons_count': count(*[_type == '${type}' &amp;&amp; brand == '${brand}' &amp;&amp; ^._id in ${groupBy}[]._ref ]._id),
641
+ 'lessons': *[_type == '${type}' &amp;&amp; brand == '${brand}' &amp;&amp; ^._id in ${groupBy}[]._ref ]{
642
+ ${fieldsString},
643
+ ${groupBy}
644
+ }[0...10]
645
+ }
646
+ |order(${sortOrder})
647
+ [${start}...${end}]
648
+ }`;
649
+ } else {
650
+ query = `
651
+ {
652
+ "entity": *[_type == '${type}' &amp;&amp; brand == "${brand}" ${searchFilter} ${includedFieldsFilter}] | order(${sortOrder}) [${start}...${end}] {
653
+ ${fieldsString}
654
+ },
655
+ "total": count(*[_type == '${type}' &amp;&amp; brand == "${brand}" ${searchFilter} ${includedFieldsFilter}])
656
+ }
657
+ `;
658
+ }
659
+
660
+ return fetchSanity(query, true);
661
+ }
662
+
663
+ /**
664
+ * Fetches all available filter options based on various criteria such as brand, filters, style, artist, content type, and search term.
665
+ *
666
+ * This function constructs a query to retrieve the total number of results and filter options such as difficulty, instrument type, and genre.
667
+ * The filter options are dynamically generated based on the provided filters, style, artist, and content type.
668
+ *
669
+ * @param {string} brand - The brand for which to fetch the filter options.
670
+ * @param {string} filters - Additional filters to apply to the query, typically in the format of Sanity GROQ queries.
671
+ * @param {string} [style] - Optional style or genre to filter the results. If provided, the query will check if the style exists in the genre array.
672
+ * @param {string} [artist] - Optional artist name to filter the results. If provided, the query will check if the artist's name matches.
673
+ * @param {string} contentType - The content type to fetch (e.g., 'song', 'lesson').
674
+ * @param {string} [term] - Optional search term to match against various fields such as title, album, artist name, and genre.
675
+ *
676
+ * @returns {Promise&lt;Object|null>} - A promise that resolves to an object containing the total results and filter options, or null if the query fails.
677
+ *
678
+ * @example
679
+ * // Example usage:
680
+ * fetchAllFilterOptions('myBrand', '', 'Rock', 'John Doe', 'song', 'Love')
681
+ * .then(options => console.log(options))
682
+ * .catch(error => console.error(error));
683
+ */
684
+ export async function fetchAllFilterOptions(
685
+ brand,
686
+ filters,
687
+ style,
688
+ artist,
689
+ contentType,
690
+ term
691
+ ) {
692
+ const query = `
693
+ {
694
+ "meta": {
695
+ "totalResults": count(*[_type == '${contentType}' &amp;&amp; brand == "${brand}" &amp;&amp; ${style ? `'${style}' in genre[]->name` : `artist->name == '${artist}'`} ${filters}
696
+ ${term ? `&amp;&amp; (title match "${term}" || album match "${term}" || artist->name match "${term}" || genre[]->name match "${term}")` : ''}]),
697
+ "filterOptions": {
698
+ "difficulty": [
699
+ {"type": "Introductory", "count": count(*[_type == '${contentType}' &amp;&amp; brand == '${brand}' &amp;&amp; ${style ? `'${style}' in genre[]->name` : `artist->name == '${artist}'`} &amp;&amp; difficulty_string == "Introductory" ${filters}])},
700
+ {"type": "Beginner", "count": count(*[_type == '${contentType}' &amp;&amp; brand == '${brand}' &amp;&amp; ${style ? `'${style}' in genre[]->name` : `artist->name == '${artist}'`} &amp;&amp; difficulty_string == "Beginner" ${filters}])},
701
+ {"type": "Intermediate", "count": count(*[_type == '${contentType}' &amp;&amp; brand == '${brand}' &amp;&amp; ${style ? `'${style}' in genre[]->name` : `artist->name == '${artist}'`} &amp;&amp; difficulty_string == "Intermediate" ${filters}])},
702
+ {"type": "Advanced", "count": count(*[_type == '${contentType}' &amp;&amp; brand == '${brand}' &amp;&amp; ${style ? `'${style}' in genre[]->name` : `artist->name == '${artist}'`} &amp;&amp; difficulty_string == "Advanced" ${filters}])},
703
+ {"type": "Expert", "count": count(*[_type == '${contentType}' &amp;&amp; brand == '${brand}' &amp;&amp; ${style ? `'${style}' in genre[]->name` : `artist->name == '${artist}'`} &amp;&amp; difficulty_string == "Expert" ${filters}])}
704
+ ][count > 0],
705
+ "instrumentless": [
706
+ {"type": "Full Song Only", "count": count(*[_type == '${contentType}' &amp;&amp; brand == '${brand}' &amp;&amp; ${style ? `'${style}' in genre[]->name` : `artist->name == '${artist}'`} &amp;&amp; instrumentless == false ${filters}])},
707
+ {"type": "Instrument Removed", "count": count(*[_type == '${contentType}' &amp;&amp; brand == '${brand}' &amp;&amp; ${style ? `'${style}' in genre[]->name` : `artist->name == '${artist}'`} &amp;&amp; instrumentless == true ${filters}])}
708
+ ][count > 0],
709
+ "genre": *[_type == 'genre' &amp;&amp; '${contentType}' in filter_types] {
710
+ "type": name,
711
+ "count": count(*[_type == '${contentType}' &amp;&amp; brand == "${brand}" &amp;&amp; ${style ? `'${style}' in genre[]->name` : `artist->name == '${artist}'`} &amp;&amp; references(^._id)])
712
+ }[count > 0]
713
+ }
714
+ }
715
+ }
716
+ }
717
+ `;
718
+
719
+ return fetchSanity(query, false);
720
+ }
721
+
722
+ /**
723
+ * Fetch children content by Railcontent ID.
724
+ * @param {string} railcontentId - The Railcontent ID of the parent content.
725
+ * @returns {Promise&lt;Array&lt;Object>|null>} - The fetched children content data or null if not found.
726
+ */
727
+ export async function fetchChildren(railcontentId) {
728
+ //TODO: Implement getByParentId include sum XP
729
+ const query = `*[_railcontent_id == ${railcontentId}]{
730
+ railcontent_id,
731
+ title,
732
+ "image": thumbnail.asset->url,
733
+ "artist_name": artist->name,
734
+ artist,
735
+ difficulty,
736
+ difficulty_string,
737
+ web_url_path,
738
+ published_on
739
+ } | order(published_on asc)`
740
+ return fetchSanity(query, true);
741
+ }
742
+
743
+ /**
744
+ * Fetch the next lesson for a specific method by Railcontent ID.
745
+ * @param {string} railcontentId - The Railcontent ID of the current lesson.
746
+ * @returns {Promise&lt;Object|null>} - The fetched next lesson data or null if not found.
747
+ */
748
+ export async function fetchMethodNextLesson(railcontentId) {
749
+ //TODO: Implement getNextContentForParentContentForUser
750
+ const query = `*[_railcontent_id == ${railcontentId}]{
751
+ railcontent_id,
752
+ title,
753
+ "image": thumbnail.asset->url,
754
+ "artist_name": artist->name,
755
+ artist,
756
+ difficulty,
757
+ difficulty_string,
758
+ web_url_path,
759
+ published_on
760
+ }`
761
+ return fetchSanity(query, false);
762
+ }
763
+
764
+ /**
765
+ * Fetch all children of a specific method by Railcontent ID.
766
+ * @param {string} railcontentId - The Railcontent ID of the method.
767
+ * @returns {Promise&lt;Array&lt;Object>|null>} - The fetched children data or null if not found.
768
+ */
769
+ export async function fetchMethodChildren(railcontentId) {
770
+ //TODO: Implement getByParentId include sum XP
771
+ return fetchChildren(railcontentId);
772
+ }
773
+
774
+ /**
775
+ * Fetch the next and previous lessons for a specific lesson by Railcontent ID.
776
+ * @param {string} railcontentId - The Railcontent ID of the current lesson.
777
+ * @returns {Promise&lt;Object|null>} - The fetched next and previous lesson data or null if found.
778
+ */
779
+ export async function fetchNextPreviousLesson(railcontentId) {
780
+ //TODO: Implement getTypeNeighbouringSiblings/getNextAndPreviousLessons
781
+ const query = `*[_railcontent_id == ${railcontentId}]{
782
+ railcontent_id,
783
+ title,
784
+ "image": thumbnail.asset->url,
785
+ "artist_name": artist->name,
786
+ artist,
787
+ difficulty,
788
+ difficulty_string,
789
+ web_url_path,
790
+ published_on
791
+ }`
792
+ return fetchSanity(query, false);
793
+ }
794
+
795
+ /**
796
+ * Fetch the page data for a specific lesson by Railcontent ID.
797
+ * @param {string} railContentId - The Railcontent ID of the current lesson.
798
+ * @returns {Promise&lt;Object|null>} - The fetched page data or null if found.
799
+ *
800
+ * @example
801
+ * fetchLessonContent('lesson123')
802
+ * .then(data => console.log(data))
803
+ * .catch(error => console.error(error));
804
+ */
805
+ export async function fetchLessonContent(railContentId) {
806
+ const query = `*[railcontent_id == ${railContentId} ]
807
+ {title, published_on,"type":_type, "resources": resource, difficulty, difficulty_string, brand, soundslice, instrumentless, railcontent_id, "id":railcontent_id, slug, artist->,"thumbnail_url":thumbnail.asset->url, "url": web_url_path, soundslice_slug,description,
808
+ "chapters": chapter[]{
809
+ chapter_description,
810
+ chapter_timecode,
811
+ "chapter_thumbnail_url": chapter_thumbnail_url.asset->url
812
+ },
813
+ "coaches": instructor[]-> {
814
+ name,
815
+ "id":_id,
816
+ "coach_profile_image":thumbnail_url.asset->url
817
+ },
818
+ "instructors":instructor[]->name,
819
+ instructor[]->,
820
+ "assignments":assignment[]{
821
+ "id": railcontent_id,
822
+ "soundslice_slug": assignment_soundslice,
823
+ "title": assignment_title,
824
+ "sheet_music_image_url": assignment_sheet_music_image,
825
+ "timecode": assignment_timecode,
826
+ "description": assignment_description
827
+ },
828
+ video}`
829
+ return fetchSanity(query, false);
830
+ }
831
+
832
+ /**
833
+ * Fetch related lessons for a specific lesson by RailContent ID and type.
834
+ * @param {string} railContentId - The RailContent ID of the current lesson.
835
+ * @param {string} brand - The current brand.
836
+ * @returns {Promise&lt;Array&lt;Object>|null>} - The fetched related lessons data or null if not found.
837
+ */
838
+ export async function fetchRelatedLessons(railContentId, brand) {
839
+ // let sort = 'published_on'
840
+ // if (type == 'rhythmic-adventures-of-captain-carson' ||
841
+ // type == 'diy-drum-experiments' ||
842
+ // type == 'in-rhythm') {
843
+ // sort = 'sort';
844
+ // }
845
+ //TODO: Implement $this->contentService->getFiltered
846
+ const query = `*[railcontent_id == ${railContentId} &amp;&amp; brand == "${brand}" &amp;&amp; references(*[_type=='permission']._id)]{
847
+ "related_lessons" : array::unique([
848
+ ...(*[_type=="song" &amp;&amp; brand == "${brand}" &amp;&amp; references(^.artist->_id)]{_id, "id":railcontent_id, published_on, title, "thumbnail_url":thumbnail.asset->url, difficulty_string, railcontent_id, artist->}[0...11]),
849
+ ...(*[_type=="song" &amp;&amp; brand == "${brand}" &amp;&amp; references(^.genre[]->_id)]{_id, "id":railcontent_id, published_on, title, "thumbnail_url":thumbnail.asset->url, difficulty_string, railcontent_id, artist->}[0...11])
850
+ ])|order(published_on, railcontent_id)[0...11]}`;
851
+ return fetchSanity(query, false);
852
+ }
853
+
854
+ /**
855
+ * Fetch all content for a specific pack by Railcontent ID.
856
+ * @param {string} railcontentId - The Railcontent ID of the pack.
857
+ * @returns {Promise&lt;Array&lt;Object>|null>} - The fetched pack content data or null if not found.
858
+ */
859
+ export async function fetchPackAll(railcontentId) {
860
+ //TODO: Implement getPacks
861
+ const query = `*[_railcontent_id == ${railcontentId}]{
862
+ railcontent_id,
863
+ title,
864
+ "image": thumbnail.asset->url,
865
+ "artist_name": artist->name,
866
+ artist,
867
+ difficulty,
868
+ difficulty_string,
869
+ web_url_path,
870
+ published_on
871
+ } | order(published_on asc)[0...5]`
872
+ return fetchSanity(query, true);
873
+ }
874
+
875
+ /**
876
+ * Fetch all children of a specific pack by Railcontent ID.
877
+ * @param {string} railcontentId - The Railcontent ID of the pack.
878
+ * @returns {Promise&lt;Array&lt;Object>|null>} - The fetched pack children data or null if not found.
879
+ *
880
+ * @example
881
+ * fetchPackChildren('pack123')
882
+ * .then(children => console.log(children))
883
+ * .catch(error => console.error(error));
884
+ */
885
+ export async function fetchPackChildren(railcontentId) {
886
+ return fetchChildren(railcontentId, 'pack');
887
+ }
888
+
889
+ /**
890
+ * Fetch data from the Sanity API based on a provided query.
891
+ *
892
+ * @param {string} query - The GROQ query to execute against the Sanity API.
893
+ * @param {boolean} isList - Whether to return an array or a single result.
894
+ * @returns {Promise&lt;Object|null>} - A promise that resolves to the fetched data or null if an error occurs or no results are found.
895
+ *
896
+ * @example
897
+ * const query = `*[_type == "song"]{title, artist->name}`;
898
+ * fetchSanity(query, true)
899
+ * .then(data => console.log(data))
900
+ * .catch(error => console.error(error));
901
+ */
902
+ export async function fetchSanity(query, isList) {
903
+ // Check the config object before proceeding
904
+ if (!checkSanityConfig(globalConfig)) {
905
+ return null;
906
+ }
907
+
908
+ if (globalConfig.sanityConfig.debug) {
909
+ console.log("fetchSanity Query:", query);
910
+ }
911
+
912
+ const encodedQuery = encodeURIComponent(query);
913
+ const api = globalConfig.sanityConfig.useCachedAPI ? 'apicdn' : 'api';
914
+ const url = `https://${globalConfig.sanityConfig.projectId}.${api}.sanity.io/v${globalConfig.sanityConfig.version}/data/query/${globalConfig.sanityConfig.dataset}?query=${encodedQuery}`;
915
+ const headers = {
916
+ 'Authorization': `Bearer ${globalConfig.sanityConfig.token}`,
917
+ 'Content-Type': 'application/json'
918
+ };
919
+
920
+ try {
921
+ const response = await fetch(url, {headers});
922
+ if (!response.ok) {
923
+ throw new Error(`Sanity API error: ${response.status} - ${response.statusText}`);
924
+ }
925
+ const result = await response.json();
926
+ if (result.result) {
927
+ if (globalConfig.sanityConfig.debug) {
928
+ console.log("fetchSanity Results:", result);
929
+ }
930
+ return isList ? result.result : result.result[0];
931
+ } else {
932
+ throw new Error('No results found');
933
+ }
934
+ } catch (error) {
935
+ console.error('fetchSanity: Fetch error:', error);
936
+ return null;
937
+ }
938
+ }
939
+
940
+
941
+ //Helper Functions
942
+ function arrayJoinWithQuotes(array, delimiter = ',') {
943
+ const wrapped = array.map(value => `'${value}'`);
944
+ return wrapped.join(delimiter)
945
+ }
946
+
947
+ function getSanityDate(date) {
948
+ return date.toISOString();
949
+ }
950
+
951
+ function checkSanityConfig(config) {
952
+ if (!config.sanityConfig.token) {
953
+ console.warn('fetchSanity: The "token" property is missing in the config object.');
954
+ return false;
955
+ }
956
+ if (!config.sanityConfig.projectId) {
957
+ console.warn('fetchSanity: The "projectId" property is missing in the config object.');
958
+ return false;
959
+ }
960
+ if (!config.sanityConfig.dataset) {
961
+ console.warn('fetchSanity: The "dataset" property is missing in the config object.');
962
+ return false;
963
+ }
964
+ if (!config.sanityConfig.version) {
965
+ console.warn('fetchSanity: The "version" property is missing in the config object.');
966
+ return false;
967
+ }
968
+ return true;
969
+ }
970
+ </code></pre>
971
+ </article>
972
+ </section>
973
+
974
+
975
+
976
+
977
+
978
+
979
+ </div>
980
+
981
+ <br class="clear">
982
+
983
+ <footer>
984
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.3</a> on Wed Aug 14 2024 02:49:16 GMT+0000 (Coordinated Universal Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
985
+ </footer>
986
+
987
+ <script>prettyPrint();</script>
988
+ <script src="scripts/polyfill.js"></script>
989
+ <script src="scripts/linenumber.js"></script>
990
+
991
+
992
+
993
+ </body>
994
+ </html>