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