musora-content-services 2.85.1 → 2.86.1
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.
- package/.coderabbit.yaml +0 -0
- package/.editorconfig +0 -0
- package/.github/pull_request_template.md +0 -0
- package/.github/workflows/conventional-commits.yaml +0 -0
- package/.github/workflows/docs.js.yml +0 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/CHANGELOG.md +19 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/docs/Content.html +0 -0
- package/docs/ContentOrganization.html +2 -2
- package/docs/Forums.html +2 -2
- package/docs/Gamification.html +2 -2
- package/docs/TestUser.html +2 -2
- package/docs/UserManagementSystem.html +2 -2
- package/docs/api_types.js.html +2 -2
- package/docs/config.js.html +5 -2
- package/docs/content-org_content-org.js.html +2 -2
- package/docs/content-org_guided-courses.ts.html +2 -2
- package/docs/content-org_learning-paths.ts.html +126 -24
- package/docs/content-org_playlists-types.js.html +2 -2
- package/docs/content-org_playlists.js.html +2 -2
- package/docs/content.js.html +88 -10
- package/docs/content_artist.ts.html +36 -41
- package/docs/content_content.ts.html +0 -0
- package/docs/content_genre.ts.html +29 -34
- package/docs/content_instructor.ts.html +25 -27
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/forums_categories.ts.html +22 -3
- package/docs/forums_discussions.js.html +0 -0
- package/docs/forums_forum.js.html +0 -0
- package/docs/forums_forums.ts.html +2 -2
- package/docs/forums_posts.ts.html +2 -2
- package/docs/forums_threads.ts.html +9 -9
- package/docs/gamification_awards.js.html +0 -0
- package/docs/gamification_awards.ts.html +26 -12
- package/docs/gamification_gamification.js.html +2 -2
- package/docs/gamification_types.js.html +0 -0
- package/docs/global.html +2 -2
- package/docs/index.html +2 -2
- package/docs/liveTesting.ts.html +2 -2
- package/docs/module-Accounts.html +14 -14
- package/docs/module-Artist.html +12 -10
- package/docs/module-Awards.html +106 -6
- package/docs/module-Categories.html +0 -0
- package/docs/module-Config.html +5 -4
- package/docs/module-Content-Services-V2.html +440 -9
- package/docs/module-ForumCategories.html +0 -0
- package/docs/module-ForumDiscussions.html +0 -0
- package/docs/module-Forums.html +627 -63
- package/docs/module-Genre.html +6 -6
- package/docs/module-GuidedCourses.html +2 -2
- package/docs/module-Instructor.html +10 -10
- package/docs/module-Interests.html +2 -2
- package/docs/module-LearningPaths.html +640 -12
- package/docs/module-Onboarding.html +32 -8
- package/docs/module-Payments.html +2 -2
- package/docs/module-Permissions.html +2 -2
- package/docs/module-Playlists.html +2 -2
- package/docs/module-ProgressRow.html +2 -2
- package/docs/module-Railcontent-Services.html +2 -2
- package/docs/module-Sanity-Services.html +786 -1853
- package/docs/module-Sessions.html +2 -2
- package/docs/module-Threads.html +0 -0
- package/docs/module-UserActivity.html +4 -4
- package/docs/module-UserChat.html +2 -2
- package/docs/module-UserManagement.html +2 -2
- package/docs/module-UserMemberships.html +2 -2
- package/docs/module-UserNotifications.html +2 -2
- package/docs/module-UserProfile.html +2 -2
- package/docs/progress-row_method-card.js.html +3 -3
- package/docs/railcontent.js.html +2 -2
- package/docs/sanity.js.html +230 -321
- package/docs/scripts/collapse.js +0 -0
- package/docs/scripts/commonNav.js +0 -0
- package/docs/scripts/linenumber.js +0 -0
- package/docs/scripts/nav.js +0 -0
- package/docs/scripts/polyfill.js +0 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
- package/docs/scripts/prettify/lang-css.js +0 -0
- package/docs/scripts/prettify/prettify.js +0 -0
- package/docs/scripts/search.js +0 -0
- package/docs/styles/jsdoc.css +0 -0
- package/docs/styles/prettify.css +0 -0
- package/docs/userActivity.js.html +4 -3
- package/docs/user_account.ts.html +14 -13
- package/docs/user_chat.js.html +2 -2
- package/docs/user_interests.js.html +2 -2
- package/docs/user_management.js.html +2 -2
- package/docs/user_memberships.js.html +0 -0
- package/docs/user_memberships.ts.html +2 -2
- package/docs/user_notifications.js.html +2 -2
- package/docs/user_onboarding.ts.html +95 -4
- package/docs/user_payments.ts.html +2 -2
- package/docs/user_permissions.js.html +3 -3
- package/docs/user_profile.js.html +2 -2
- package/docs/user_sessions.js.html +2 -2
- package/docs/user_types.js.html +2 -2
- package/docs/user_user-management-system.js.html +2 -2
- package/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/package.json +1 -1
- package/src/contentMetaData.js +0 -0
- package/src/contentTypeConfig.js +24 -16
- package/src/filterBuilder.js +0 -0
- package/src/index.d.ts +0 -0
- package/src/index.js +0 -0
- package/src/infrastructure/http/HttpClient.ts +0 -0
- package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
- package/src/infrastructure/http/index.ts +0 -0
- package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
- package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
- package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
- package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
- package/src/lib/brands.ts +8 -0
- package/src/lib/httpHelper.js +0 -0
- package/src/lib/lastUpdated.js +0 -0
- package/src/lib/sanity/query.ts +30 -0
- package/src/services/api/types.js +0 -0
- package/src/services/api/types.ts +0 -0
- package/src/services/config.js +0 -0
- package/src/services/content/artist.ts +34 -39
- package/src/services/content/content.ts +7 -1
- package/src/services/content/genre.ts +27 -32
- package/src/services/content/instructor.ts +23 -25
- package/src/services/content-org/content-org.js +0 -0
- package/src/services/content-org/guided-courses.ts +0 -0
- package/src/services/content-org/learning-paths.ts +4 -4
- package/src/services/content-org/playlists-types.js +0 -0
- package/src/services/content-org/playlists.js +0 -0
- package/src/services/content.js +0 -0
- package/src/services/contentAggregator.js +5 -1
- package/src/services/contentLikes.js +0 -0
- package/src/services/contentProgress.js +6 -6
- package/src/services/dataContext.js +0 -0
- package/src/services/dateUtils.js +0 -0
- package/src/services/eventsAPI.js +0 -0
- package/src/services/forums/categories.ts +0 -0
- package/src/services/forums/forums.ts +0 -0
- package/src/services/forums/posts.ts +0 -0
- package/src/services/forums/threads.ts +0 -0
- package/src/services/forums/types.ts +0 -0
- package/src/services/gamification/awards.ts +0 -0
- package/src/services/gamification/gamification.js +0 -0
- package/src/services/imageSRCBuilder.js +0 -0
- package/src/services/imageSRCVerify.js +0 -0
- package/src/services/liveTesting.ts +0 -0
- package/src/services/permissions/PermissionsAdapter.ts +0 -0
- package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
- package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
- package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
- package/src/services/permissions/README.md +0 -0
- package/src/services/permissions/index.ts +0 -0
- package/src/services/progress-row/method-card.js +0 -0
- package/src/services/railcontent.js +0 -0
- package/src/services/recommendations.js +0 -0
- package/src/services/sanity.js +52 -45
- package/src/services/types.js +0 -0
- package/src/services/user/account.ts +0 -0
- package/src/services/user/chat.js +0 -0
- package/src/services/user/interests.js +0 -0
- package/src/services/user/management.js +0 -0
- package/src/services/user/memberships.ts +0 -0
- package/src/services/user/notifications.js +0 -0
- package/src/services/user/onboarding.ts +93 -2
- package/src/services/user/payments.ts +0 -0
- package/src/services/user/permissions.js +0 -0
- package/src/services/user/profile.js +0 -0
- package/src/services/user/sessions.js +0 -0
- package/src/services/user/types.d.ts +0 -0
- package/src/services/user/types.js +0 -0
- package/src/services/user/user-management-system.js +0 -0
- package/src/services/userActivity.js +1 -1
- package/test/HttpClient.test.js +0 -0
- package/test/content.test.js +0 -0
- package/test/contentLikes.test.js +0 -0
- package/test/contentProgress.test.js +0 -0
- package/test/dataContext.test.js +0 -0
- package/test/forum.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/initializeTests.js +0 -0
- package/test/learningPaths.test.js +0 -0
- package/test/lib/lastUpdated.test.js +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
- package/test/mockData/mockData_progress_content.json +0 -0
- package/test/mockData/mockData_sanity_progress_content.json +0 -0
- package/test/mockData/mockData_user_practices.json +0 -0
- package/test/notifications.test.js +0 -0
- package/test/progressRows.test.js +0 -0
- package/test/sanityQueryService.test.js +0 -0
- package/test/streakMessage.test.js +0 -0
- package/test/user/permissions.test.js +0 -0
- package/test/userActivity.test.js +0 -0
- package/tools/generate-index.cjs +0 -0
- package/.claude/settings.local.json +0 -14
- package/.yarnrc.yml +0 -1
package/package.json
CHANGED
package/src/contentMetaData.js
CHANGED
|
File without changes
|
package/src/contentTypeConfig.js
CHANGED
|
@@ -22,9 +22,25 @@ export const SONG_TYPES_WITH_CHILDREN = [
|
|
|
22
22
|
// Single hierarchy refers to only one element in the hierarchy has video lessons, not that they have a single parent
|
|
23
23
|
export const SINGLE_PARENT_TYPES = ['course-part', 'pack-bundle-lesson', 'song-tutorial-children']
|
|
24
24
|
|
|
25
|
+
export const genreField = `genre[]->{
|
|
26
|
+
name,
|
|
27
|
+
'slug': slug.current,
|
|
28
|
+
'thumbnail': thumbnail_url.asset->url,
|
|
29
|
+
}`
|
|
30
|
+
|
|
31
|
+
export const instructorField = `instructor[]->{
|
|
32
|
+
name,
|
|
33
|
+
'slug': slug.current,
|
|
34
|
+
short_bio,
|
|
35
|
+
'thumbnail': thumbnail_url.asset->url,
|
|
36
|
+
"biography": short_bio[0].children[0].text,
|
|
37
|
+
"coach_card_image": coach_card_image.asset->url,
|
|
38
|
+
"coach_profile_image": thumbnail_url.asset->url
|
|
39
|
+
}`
|
|
40
|
+
|
|
25
41
|
export const artistField = `select(
|
|
26
|
-
defined(artist) => artist->{ 'name': name, 'thumbnail': thumbnail_url.asset->url},
|
|
27
|
-
defined(parent_content_data) => *[_type == ^.parent_content_data[0].type && railcontent_id == ^.parent_content_data[0].id][0].artist->{ 'name': name, 'thumbnail': thumbnail_url.asset->url}
|
|
42
|
+
defined(artist) => artist->{ 'name': name, 'slug': slug.current, 'thumbnail': thumbnail_url.asset->url},
|
|
43
|
+
defined(parent_content_data) => *[_type == ^.parent_content_data[0].type && railcontent_id == ^.parent_content_data[0].id][0].artist->{ 'name': name, 'slug': slug.current, 'thumbnail': thumbnail_url.asset->url}
|
|
28
44
|
)`
|
|
29
45
|
|
|
30
46
|
export const DEFAULT_FIELDS = [
|
|
@@ -41,7 +57,8 @@ export const DEFAULT_FIELDS = [
|
|
|
41
57
|
"'type': _type",
|
|
42
58
|
"'length_in_seconds' : coalesce(length_in_seconds, soundslice[0].soundslice_length_in_second)",
|
|
43
59
|
'brand',
|
|
44
|
-
"
|
|
60
|
+
`"instructor": ${instructorField}`,
|
|
61
|
+
`'genre': ${genreField}`,
|
|
45
62
|
'status',
|
|
46
63
|
"'slug' : slug.current",
|
|
47
64
|
"'permission_id': permission[]->railcontent_id",
|
|
@@ -64,7 +81,7 @@ export const DEFAULT_CHILD_FIELDS = [
|
|
|
64
81
|
"'type': _type",
|
|
65
82
|
"'length_in_seconds' : coalesce(length_in_seconds, soundslice[0].soundslice_length_in_second)",
|
|
66
83
|
'brand',
|
|
67
|
-
|
|
84
|
+
`'genre': ${genreField}`,
|
|
68
85
|
'status',
|
|
69
86
|
"'slug' : slug.current",
|
|
70
87
|
"'permission_id': permission[]->railcontent_id",
|
|
@@ -80,15 +97,6 @@ export const playAlongMp3sField = `{
|
|
|
80
97
|
}
|
|
81
98
|
`
|
|
82
99
|
|
|
83
|
-
export const instructorField = `instructor[]->{
|
|
84
|
-
name,
|
|
85
|
-
slug,
|
|
86
|
-
short_bio,
|
|
87
|
-
"biography": short_bio[0].children[0].text,
|
|
88
|
-
"coach_card_image": coach_card_image.asset->url,
|
|
89
|
-
"coach_profile_image": thumbnail_url.asset->url
|
|
90
|
-
}`
|
|
91
|
-
|
|
92
100
|
export const chapterField = `chapter[]{
|
|
93
101
|
chapter_description,
|
|
94
102
|
chapter_timecode,
|
|
@@ -180,7 +188,7 @@ export const coachLessonsTypes = [
|
|
|
180
188
|
]
|
|
181
189
|
|
|
182
190
|
export const childContentTypeConfig = {
|
|
183
|
-
'song-tutorial': [`"genre":
|
|
191
|
+
'song-tutorial': [`"genre": ${genreField}`, `difficulty_string`, `"type": _type`],
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
export const singleLessonTypes = ['quick-tips', 'rudiment']
|
|
@@ -496,7 +504,7 @@ export let contentTypeConfig = {
|
|
|
496
504
|
},
|
|
497
505
|
'play-along': {
|
|
498
506
|
fields: [
|
|
499
|
-
|
|
507
|
+
`"style": ${genreField}`,
|
|
500
508
|
'mp3_no_drums_no_click_url',
|
|
501
509
|
'mp3_yes_drums_yes_click_url',
|
|
502
510
|
'mp3_no_drums_yes_click_url',
|
|
@@ -819,7 +827,7 @@ export async function getFieldsForContentTypeWithFilteredChildren(
|
|
|
819
827
|
if (childFields) {
|
|
820
828
|
const childFilter = await new FilterBuilder('', {
|
|
821
829
|
isChildrenFilter: true,
|
|
822
|
-
showMembershipRestrictedContent: true
|
|
830
|
+
showMembershipRestrictedContent: true, // Show all children in lists
|
|
823
831
|
}).buildFilter()
|
|
824
832
|
parentFields.push(
|
|
825
833
|
`"children": child[${childFilter}]->{
|
package/src/filterBuilder.js
CHANGED
|
File without changes
|
package/src/index.d.ts
CHANGED
|
File without changes
|
package/src/index.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/lib/httpHelper.js
CHANGED
|
File without changes
|
package/src/lib/lastUpdated.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface BuildQueryOptions {
|
|
2
|
+
sort: string
|
|
3
|
+
start: number
|
|
4
|
+
end: number
|
|
5
|
+
isSingle?: boolean
|
|
6
|
+
withoutPagination?: boolean
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function buildDataAndTotalQuery(
|
|
10
|
+
filter: string = '',
|
|
11
|
+
fields: string = '...',
|
|
12
|
+
{
|
|
13
|
+
sort = 'published_on desc',
|
|
14
|
+
start = 0,
|
|
15
|
+
end = 10,
|
|
16
|
+
isSingle = false,
|
|
17
|
+
withoutPagination = false,
|
|
18
|
+
}: BuildQueryOptions
|
|
19
|
+
): string {
|
|
20
|
+
const sortString = sort ? ` | order(${sort})` : ''
|
|
21
|
+
const countString = isSingle ? '[0...1]' : withoutPagination ? `` : `[${start}...${end}]`
|
|
22
|
+
const query = `{
|
|
23
|
+
"data": *[${filter}] ${sortString}${countString}
|
|
24
|
+
{
|
|
25
|
+
${fields}
|
|
26
|
+
},
|
|
27
|
+
"total": count(*[${filter}]),
|
|
28
|
+
}`
|
|
29
|
+
return query
|
|
30
|
+
}
|
|
File without changes
|
|
File without changes
|
package/src/services/config.js
CHANGED
|
File without changes
|
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module Artist
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
4
|
+
import { filtersToGroq, getFieldsForContentType } from '../../contentTypeConfig.js'
|
|
5
5
|
import { FilterBuilder } from '../../filterBuilder.js'
|
|
6
|
-
import {
|
|
6
|
+
import { buildDataAndTotalQuery } from '../../lib/sanity/query'
|
|
7
|
+
import { fetchSanity, getSortOrder } from '../sanity.js'
|
|
7
8
|
import { Lesson } from './content'
|
|
9
|
+
import { Brand } from '../../lib/brands'
|
|
8
10
|
|
|
9
11
|
export interface Artist {
|
|
10
12
|
slug: string
|
|
11
13
|
name: string
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
thumbnail: string
|
|
15
|
+
lessonCount: number
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Fetch all artists with lessons available for a specific brand.
|
|
18
20
|
*
|
|
19
|
-
* @param {
|
|
21
|
+
* @param {Brand} brand - The brand for which to fetch artists.
|
|
20
22
|
* @returns {Promise<Artist[]|null>} - A promise that resolves to an array of artist objects or null if not found.
|
|
21
23
|
*
|
|
22
24
|
* @example
|
|
@@ -24,7 +26,7 @@ export interface Artist {
|
|
|
24
26
|
* .then(artists => console.log(artists))
|
|
25
27
|
* .catch(error => console.error(error));
|
|
26
28
|
*/
|
|
27
|
-
export async function fetchArtists(brand:
|
|
29
|
+
export async function fetchArtists(brand: Brand): Promise<Artist[] | null> {
|
|
28
30
|
const filter = await new FilterBuilder(
|
|
29
31
|
`_type == "song" && brand == "${brand}" && references(^._id)`,
|
|
30
32
|
{ bypassPermissions: true }
|
|
@@ -33,8 +35,9 @@ export async function fetchArtists(brand: string): Promise<Artist[] | null> {
|
|
|
33
35
|
*[_type == "artist"]{
|
|
34
36
|
name,
|
|
35
37
|
"slug": slug.current,
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
'thumbnail': thumbnail_url.asset->url,
|
|
39
|
+
"lessonCount": count(*[${filter}])
|
|
40
|
+
}[lessonCount > 0] |order(lower(name)) `
|
|
38
41
|
return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
|
|
39
42
|
}
|
|
40
43
|
|
|
@@ -42,7 +45,7 @@ export async function fetchArtists(brand: string): Promise<Artist[] | null> {
|
|
|
42
45
|
* Fetch a single artist by their Sanity ID.
|
|
43
46
|
*
|
|
44
47
|
* @param {string} slug - The name of the artist to fetch.
|
|
45
|
-
* @param {
|
|
48
|
+
* @param {Brand} [brand] - The brand for which to fetch the artist.
|
|
46
49
|
* @returns {Promise<Artist|null>} - A promise that resolves to an artist objects or null if not found.
|
|
47
50
|
*
|
|
48
51
|
* @example
|
|
@@ -50,7 +53,7 @@ export async function fetchArtists(brand: string): Promise<Artist[] | null> {
|
|
|
50
53
|
* .then(artists => console.log(artists))
|
|
51
54
|
* .catch(error => console.error(error));
|
|
52
55
|
*/
|
|
53
|
-
export async function fetchArtistBySlug(slug: string, brand?:
|
|
56
|
+
export async function fetchArtistBySlug(slug: string, brand?: Brand): Promise<Artist | null> {
|
|
54
57
|
const brandFilter = brand ? `brand == "${brand}" && ` : ''
|
|
55
58
|
const filter = await new FilterBuilder(`${brandFilter} _type == "song" && references(^._id)`, {
|
|
56
59
|
bypassPermissions: true,
|
|
@@ -59,8 +62,8 @@ export async function fetchArtistBySlug(slug: string, brand?: string): Promise<A
|
|
|
59
62
|
*[_type == "artist" && slug.current == '${slug}']{
|
|
60
63
|
name,
|
|
61
64
|
"slug": slug.current,
|
|
62
|
-
"
|
|
63
|
-
}[
|
|
65
|
+
"lessonCount": count(*[${filter}])
|
|
66
|
+
}[lessonCount > 0] |order(lower(name)) `
|
|
64
67
|
return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -74,13 +77,14 @@ export interface ArtistLessonOptions {
|
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
export interface LessonsByArtistResponse {
|
|
77
|
-
data:
|
|
80
|
+
data: Lesson[]
|
|
81
|
+
total: number
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
/**
|
|
81
85
|
* Fetch the artist's lessons.
|
|
82
86
|
* @param {string} slug - The slug of the artist
|
|
83
|
-
* @param {
|
|
87
|
+
* @param {Brand} brand - The brand for which to fetch lessons.
|
|
84
88
|
* @param {string} contentType - The type of the lessons we need to get from the artist. If not defined, groq will get lessons from all content types
|
|
85
89
|
* @param {Object} params - Parameters for sorting, searching, pagination and filtering.
|
|
86
90
|
* @param {string} [params.sort="-published_on"] - The field to sort the lessons by.
|
|
@@ -88,8 +92,8 @@ export interface LessonsByArtistResponse {
|
|
|
88
92
|
* @param {number} [params.page=1] - The page number for pagination.
|
|
89
93
|
* @param {number} [params.limit=10] - The number of items per page.
|
|
90
94
|
* @param {Array<string>} [params.includedFields=[]] - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock'].
|
|
91
|
-
* @param {Array<number>} [params.
|
|
92
|
-
* @returns {Promise<LessonsByArtistResponse|null>} - The lessons for the artist
|
|
95
|
+
* @param {Array<number>} [params.progressId=[]] - The ids of the lessons that are in progress or completed
|
|
96
|
+
* @returns {Promise<LessonsByArtistResponse|null>} - The lessons for the artist
|
|
93
97
|
*
|
|
94
98
|
* @example
|
|
95
99
|
* fetchArtistLessons('10 Years', 'drumeo', 'song', {'-published_on', '', 1, 10, ["difficulty,Intermediate"], [232168, 232824, 303375, 232194, 393125]})
|
|
@@ -98,7 +102,7 @@ export interface LessonsByArtistResponse {
|
|
|
98
102
|
*/
|
|
99
103
|
export async function fetchArtistLessons(
|
|
100
104
|
slug: string,
|
|
101
|
-
brand:
|
|
105
|
+
brand: Brand,
|
|
102
106
|
contentType: string,
|
|
103
107
|
{
|
|
104
108
|
sort = '-published_on',
|
|
@@ -106,34 +110,25 @@ export async function fetchArtistLessons(
|
|
|
106
110
|
page = 1,
|
|
107
111
|
limit = 10,
|
|
108
112
|
includedFields = [],
|
|
109
|
-
progressIds =
|
|
113
|
+
progressIds = [],
|
|
110
114
|
}: ArtistLessonOptions = {}
|
|
111
115
|
): Promise<LessonsByArtistResponse | null> {
|
|
112
|
-
const fieldsString =
|
|
116
|
+
const fieldsString = getFieldsForContentType(contentType) as string
|
|
113
117
|
const start = (page - 1) * limit
|
|
114
118
|
const end = start + limit
|
|
115
119
|
const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
|
|
116
|
-
const sortOrder = getSortOrder(sort, brand)
|
|
117
|
-
const addType =
|
|
118
|
-
contentType && Array.isArray(contentType)
|
|
119
|
-
? `_type in ['${contentType.join("', '")}'] &&`
|
|
120
|
-
: contentType
|
|
121
|
-
? `_type == '${contentType}' && `
|
|
122
|
-
: ''
|
|
123
120
|
const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
|
|
124
|
-
|
|
125
|
-
// limits the results to supplied progressIds for started & completed filters
|
|
121
|
+
const addType = contentType ? `_type == '${contentType}' && ` : ''
|
|
126
122
|
const progressFilter =
|
|
127
|
-
progressIds
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}`
|
|
123
|
+
progressIds.length > 0 ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
|
|
124
|
+
const filter = `${addType} brand == '${brand}' ${searchFilter} ${includedFieldsFilter} && references(*[_type=='artist' && slug.current == '${slug}']._id) ${progressFilter}`
|
|
125
|
+
const filterWithRestrictions = await new FilterBuilder(filter).buildFilter()
|
|
126
|
+
|
|
127
|
+
sort = getSortOrder(sort, brand)
|
|
128
|
+
const query = buildDataAndTotalQuery(filterWithRestrictions, fieldsString, {
|
|
129
|
+
sort,
|
|
130
|
+
start: start,
|
|
131
|
+
end: end,
|
|
132
|
+
})
|
|
138
133
|
return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
|
|
139
134
|
}
|
|
@@ -13,6 +13,12 @@ export interface ArtistObject {
|
|
|
13
13
|
thumbnail: string | null
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export interface Genre {
|
|
17
|
+
name: string
|
|
18
|
+
slug: string
|
|
19
|
+
thumbnail: string | null
|
|
20
|
+
}
|
|
21
|
+
|
|
16
22
|
export interface Lesson {
|
|
17
23
|
artist: ArtistObject | string | null
|
|
18
24
|
artist_name: string
|
|
@@ -20,7 +26,7 @@ export interface Lesson {
|
|
|
20
26
|
child_count: number | null
|
|
21
27
|
difficulty: number | null
|
|
22
28
|
difficulty_string: string | null
|
|
23
|
-
genre:
|
|
29
|
+
genre: Genre | string | null
|
|
24
30
|
id: number
|
|
25
31
|
image: string
|
|
26
32
|
length_in_seconds: number
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module Genre
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
import { fetchSanity,
|
|
4
|
+
import { filtersToGroq, getFieldsForContentType } from '../../contentTypeConfig.js'
|
|
5
|
+
import { fetchSanity, getSortOrder } from '../sanity.js'
|
|
6
6
|
import { FilterBuilder } from '../../filterBuilder.js'
|
|
7
7
|
import { Lesson } from './content'
|
|
8
|
+
import { buildDataAndTotalQuery } from '../../lib/sanity/query'
|
|
9
|
+
import { Brand } from '../../lib/brands'
|
|
8
10
|
|
|
9
11
|
export interface Genre {
|
|
10
|
-
lessons?: Lesson[]
|
|
11
|
-
lessons_count: number
|
|
12
12
|
name: string
|
|
13
13
|
slug: string
|
|
14
|
+
lessons_count: number
|
|
14
15
|
thumbnail: string
|
|
15
|
-
type: 'genre'
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -26,7 +26,7 @@ export interface Genre {
|
|
|
26
26
|
* .then(genres => console.log(genres))
|
|
27
27
|
* .catch(error => console.error(error));
|
|
28
28
|
*/
|
|
29
|
-
export async function fetchGenres(brand:
|
|
29
|
+
export async function fetchGenres(brand: Brand): Promise<Genre[]> {
|
|
30
30
|
const filter = await new FilterBuilder(`brand == "${brand}" && references(^._id)`, {
|
|
31
31
|
bypassPermissions: true,
|
|
32
32
|
}).buildFilter()
|
|
@@ -46,7 +46,7 @@ export async function fetchGenres(brand: string): Promise<Genre[]> {
|
|
|
46
46
|
* Fetch a single genre by their slug and brand
|
|
47
47
|
*
|
|
48
48
|
* @param {string} slug - The slug of the genre to fetch.
|
|
49
|
-
* @param {
|
|
49
|
+
* @param {Brand} [brand] - The brand for which to fetch the genre. Lesson count will be filtered by this brand if provided.
|
|
50
50
|
* @returns {Promise<Genre[]|null>} - A promise that resolves to an genre object or null if not found.
|
|
51
51
|
*
|
|
52
52
|
* @example
|
|
@@ -54,7 +54,7 @@ export async function fetchGenres(brand: string): Promise<Genre[]> {
|
|
|
54
54
|
* .then(genres => console.log(genres))
|
|
55
55
|
* .catch(error => console.error(error));
|
|
56
56
|
*/
|
|
57
|
-
export async function fetchGenreBySlug(slug: string, brand?:
|
|
57
|
+
export async function fetchGenreBySlug(slug: string, brand?: Brand): Promise<Genre | null> {
|
|
58
58
|
const brandFilter = brand ? `brand == "${brand}" && ` : ''
|
|
59
59
|
const filter = await new FilterBuilder(`${brandFilter} references(^._id)`, {
|
|
60
60
|
bypassPermissions: true,
|
|
@@ -81,13 +81,14 @@ export interface FetchGenreLessonsOptions {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export interface LessonsByGenreResponse {
|
|
84
|
-
data:
|
|
84
|
+
data: Lesson[]
|
|
85
|
+
total: number
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
/**
|
|
88
89
|
* Fetch the genre's lessons.
|
|
89
90
|
* @param {string} slug - The slug of the genre
|
|
90
|
-
* @param {
|
|
91
|
+
* @param {Brand} brand - The brand for which to fetch lessons.
|
|
91
92
|
* @param {Object} params - Parameters for sorting, searching, pagination and filtering.
|
|
92
93
|
* @param {string} [params.sort="-published_on"] - The field to sort the lessons by.
|
|
93
94
|
* @param {string} [params.searchTerm=""] - The search term to filter the lessons.
|
|
@@ -95,7 +96,7 @@ export interface LessonsByGenreResponse {
|
|
|
95
96
|
* @param {number} [params.limit=10] - The number of items per page.
|
|
96
97
|
* @param {Array<string>} [params.includedFields=[]] - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock'].
|
|
97
98
|
* @param {Array<number>} [params.progressIds=[]] - The ids of the lessons that are in progress or completed
|
|
98
|
-
* @returns {Promise<LessonsByGenreResponse|null>} - The lessons for the
|
|
99
|
+
* @returns {Promise<LessonsByGenreResponse|null>} - The lessons for the genre
|
|
99
100
|
*
|
|
100
101
|
* @example
|
|
101
102
|
* fetchGenreLessons('Blues', 'drumeo', 'song', {'-published_on', '', 1, 10, ["difficulty,Intermediate"], [232168, 232824, 303375, 232194, 393125]})
|
|
@@ -104,8 +105,8 @@ export interface LessonsByGenreResponse {
|
|
|
104
105
|
*/
|
|
105
106
|
export async function fetchGenreLessons(
|
|
106
107
|
slug: string,
|
|
107
|
-
brand:
|
|
108
|
-
contentType
|
|
108
|
+
brand: Brand,
|
|
109
|
+
contentType?: string,
|
|
109
110
|
{
|
|
110
111
|
sort = '-published_on',
|
|
111
112
|
searchTerm = '',
|
|
@@ -115,28 +116,22 @@ export async function fetchGenreLessons(
|
|
|
115
116
|
progressIds = [],
|
|
116
117
|
}: FetchGenreLessonsOptions = {}
|
|
117
118
|
): Promise<LessonsByGenreResponse | null> {
|
|
118
|
-
const fieldsString =
|
|
119
|
+
const fieldsString = getFieldsForContentType(contentType) as string
|
|
119
120
|
const start = (page - 1) * limit
|
|
120
121
|
const end = start + limit
|
|
121
122
|
const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
|
|
122
|
-
const sortOrder = getSortOrder(sort, brand)
|
|
123
|
-
const addType = contentType ? `_type == '${contentType}' && ` : ''
|
|
124
123
|
const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
|
|
125
|
-
|
|
124
|
+
const addType = contentType ? `_type == '${contentType}' && ` : ''
|
|
126
125
|
const progressFilter =
|
|
127
|
-
progressIds
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
}
|
|
139
|
-
|order(${sortOrder})
|
|
140
|
-
}`
|
|
141
|
-
return fetchSanity(query, true)
|
|
126
|
+
progressIds.length > 0 ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
|
|
127
|
+
const filter = `${addType} brand == '${brand}' ${searchFilter} ${includedFieldsFilter} && references(*[_type=='genre' && slug.current == '${slug}']._id) ${progressFilter}`
|
|
128
|
+
const filterWithRestrictions = await new FilterBuilder(filter).buildFilter()
|
|
129
|
+
|
|
130
|
+
sort = getSortOrder(sort, brand)
|
|
131
|
+
const query = buildDataAndTotalQuery(filterWithRestrictions, fieldsString, {
|
|
132
|
+
sort: sort,
|
|
133
|
+
start: start,
|
|
134
|
+
end: end,
|
|
135
|
+
})
|
|
136
|
+
return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
|
|
142
137
|
}
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { FilterBuilder } from '../../filterBuilder.js'
|
|
5
5
|
import { filtersToGroq, getFieldsForContentType } from '../../contentTypeConfig.js'
|
|
6
|
-
import { fetchSanity,
|
|
6
|
+
import { fetchSanity, getSortOrder } from '../sanity.js'
|
|
7
7
|
import { Lesson } from './content'
|
|
8
|
+
import { buildDataAndTotalQuery } from '../../lib/sanity/query'
|
|
9
|
+
import { Brand } from '../../lib/brands'
|
|
8
10
|
|
|
9
11
|
export interface Instructor {
|
|
10
12
|
lessonCount: number
|
|
@@ -17,7 +19,7 @@ export interface Instructor {
|
|
|
17
19
|
/**
|
|
18
20
|
* Fetch all instructor with lessons available for a specific brand.
|
|
19
21
|
*
|
|
20
|
-
* @param {
|
|
22
|
+
* @param {Brand} brand - The brand for which to fetch instructors.
|
|
21
23
|
* @returns {Promise<Instructor[]>} - A promise that resolves to an array of instructor objects.
|
|
22
24
|
*
|
|
23
25
|
* @example
|
|
@@ -25,7 +27,7 @@ export interface Instructor {
|
|
|
25
27
|
* .then(instructors => console.log(instructors))
|
|
26
28
|
* .catch(error => console.error(error));
|
|
27
29
|
*/
|
|
28
|
-
export async function fetchInstructors(brand:
|
|
30
|
+
export async function fetchInstructors(brand: Brand): Promise<Instructor[]> {
|
|
29
31
|
const filter = await new FilterBuilder(`brand == "${brand}" && references(^._id)`, {
|
|
30
32
|
bypassPermissions: true,
|
|
31
33
|
}).buildFilter()
|
|
@@ -34,8 +36,9 @@ export async function fetchInstructors(brand: string): Promise<Instructor[]> {
|
|
|
34
36
|
*[_type == "instructor"] {
|
|
35
37
|
name,
|
|
36
38
|
"slug": slug.current,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
'thumbnail': thumbnail_url.asset->url,
|
|
40
|
+
"lessonCount": count(*[${filter}])
|
|
41
|
+
}[lessonCount > 0] |order(lower(name)) `
|
|
39
42
|
return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
|
|
40
43
|
}
|
|
41
44
|
|
|
@@ -43,7 +46,7 @@ export async function fetchInstructors(brand: string): Promise<Instructor[]> {
|
|
|
43
46
|
* Fetch a single instructor by their name
|
|
44
47
|
*
|
|
45
48
|
* @param {string} slug - The slug of the instructor to fetch.
|
|
46
|
-
* @param {
|
|
49
|
+
* @param {Brand} [brand] - The brand for which to fetch the instructor. Lesson count will be filtered by this brand if provided.
|
|
47
50
|
* @returns {Promise<Instructor[]>} - A promise that resolves to an instructor object or null if not found.
|
|
48
51
|
*
|
|
49
52
|
* @example
|
|
@@ -53,7 +56,7 @@ export async function fetchInstructors(brand: string): Promise<Instructor[]> {
|
|
|
53
56
|
*/
|
|
54
57
|
export async function fetchInstructorBySlug(
|
|
55
58
|
slug: string,
|
|
56
|
-
brand?:
|
|
59
|
+
brand?: Brand
|
|
57
60
|
): Promise<Instructor | null> {
|
|
58
61
|
const brandFilter = brand ? `brand == "${brand}" && ` : ''
|
|
59
62
|
const filter = await new FilterBuilder(`${brandFilter} references(^._id)`, {
|
|
@@ -66,7 +69,7 @@ export async function fetchInstructorBySlug(
|
|
|
66
69
|
"slug": slug.current,
|
|
67
70
|
short_bio,
|
|
68
71
|
'thumbnail': thumbnail_url.asset->url,
|
|
69
|
-
"
|
|
72
|
+
"lessonCount": count(*[${filter}])
|
|
70
73
|
}`
|
|
71
74
|
return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
|
|
72
75
|
}
|
|
@@ -81,6 +84,7 @@ export interface FetchInstructorLessonsOptions {
|
|
|
81
84
|
|
|
82
85
|
export interface InstructorLessonsResponse {
|
|
83
86
|
data: Lesson[]
|
|
87
|
+
total: number
|
|
84
88
|
}
|
|
85
89
|
|
|
86
90
|
/**
|
|
@@ -95,15 +99,15 @@ export interface InstructorLessonsResponse {
|
|
|
95
99
|
* @param {number} [options.limit=10] - The number of items per page.
|
|
96
100
|
* @param {Array<string>} [options.includedFields=[]] - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock'].
|
|
97
101
|
*
|
|
98
|
-
* @returns {Promise<InstructorLessonsResponse
|
|
102
|
+
* @returns {Promise<InstructorLessonsResponse>} - The lessons for the instructor or null if not found.
|
|
99
103
|
* @example
|
|
100
|
-
* fetchInstructorLessons('instructor123'
|
|
104
|
+
* fetchInstructorLessons('instructor123')
|
|
101
105
|
* .then(lessons => console.log(lessons))
|
|
102
106
|
* .catch(error => console.error(error));
|
|
103
107
|
*/
|
|
104
108
|
export async function fetchInstructorLessons(
|
|
105
109
|
slug: string,
|
|
106
|
-
brand:
|
|
110
|
+
brand: Brand,
|
|
107
111
|
{
|
|
108
112
|
sortOrder = '-published_on',
|
|
109
113
|
searchTerm = '',
|
|
@@ -111,26 +115,20 @@ export async function fetchInstructorLessons(
|
|
|
111
115
|
limit = 20,
|
|
112
116
|
includedFields = [],
|
|
113
117
|
}: FetchInstructorLessonsOptions = {}
|
|
114
|
-
): Promise<InstructorLessonsResponse
|
|
118
|
+
): Promise<InstructorLessonsResponse> {
|
|
115
119
|
const fieldsString = getFieldsForContentType() as string
|
|
116
120
|
const start = (page - 1) * limit
|
|
117
121
|
const end = start + limit
|
|
118
122
|
const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
|
|
119
123
|
const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
|
|
124
|
+
const filter = `brand == '${brand}' ${searchFilter} ${includedFieldsFilter} && references(*[_type=='instructor' && slug.current == '${slug}']._id)`
|
|
125
|
+
const filterWithRestrictions = await new FilterBuilder(filter).buildFilter()
|
|
120
126
|
|
|
121
127
|
sortOrder = getSortOrder(sortOrder, brand)
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
'type': _type,
|
|
128
|
-
name,
|
|
129
|
-
'thumbnail': thumbnail_url.asset->url,
|
|
130
|
-
'lessons_count': count(*[brand == '${brand}' && references(^._id)]),
|
|
131
|
-
'lessons': *[brand == '${brand}' && references(^._id) && (status in ['published'] || (status == 'scheduled' && defined(published_on) && published_on >= '${now}')) ${searchFilter} ${includedFieldsFilter}]{${fieldsString}} [${start}...${end}]
|
|
132
|
-
}
|
|
133
|
-
|order(${sortOrder})
|
|
134
|
-
}`
|
|
128
|
+
const query = buildDataAndTotalQuery(filterWithRestrictions, fieldsString, {
|
|
129
|
+
sort: sortOrder,
|
|
130
|
+
start: start,
|
|
131
|
+
end: end,
|
|
132
|
+
})
|
|
135
133
|
return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
|
|
136
134
|
}
|
|
File without changes
|
|
File without changes
|