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.
Files changed (222) hide show
  1. package/.coderabbit.yaml +0 -0
  2. package/.editorconfig +0 -0
  3. package/.github/pull_request_template.md +0 -0
  4. package/.github/workflows/conventional-commits.yaml +0 -0
  5. package/.github/workflows/docs.js.yml +0 -0
  6. package/.github/workflows/node.js.yml +0 -0
  7. package/.prettierignore +0 -0
  8. package/.prettierrc +0 -0
  9. package/CHANGELOG.md +19 -0
  10. package/README.md +0 -0
  11. package/babel.config.cjs +0 -0
  12. package/docs/Content.html +0 -0
  13. package/docs/ContentOrganization.html +2 -2
  14. package/docs/Forums.html +2 -2
  15. package/docs/Gamification.html +2 -2
  16. package/docs/TestUser.html +2 -2
  17. package/docs/UserManagementSystem.html +2 -2
  18. package/docs/api_types.js.html +2 -2
  19. package/docs/config.js.html +5 -2
  20. package/docs/content-org_content-org.js.html +2 -2
  21. package/docs/content-org_guided-courses.ts.html +2 -2
  22. package/docs/content-org_learning-paths.ts.html +126 -24
  23. package/docs/content-org_playlists-types.js.html +2 -2
  24. package/docs/content-org_playlists.js.html +2 -2
  25. package/docs/content.js.html +88 -10
  26. package/docs/content_artist.ts.html +36 -41
  27. package/docs/content_content.ts.html +0 -0
  28. package/docs/content_genre.ts.html +29 -34
  29. package/docs/content_instructor.ts.html +25 -27
  30. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  31. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  32. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  33. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  34. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  35. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  36. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  37. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  38. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  39. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  40. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  41. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  42. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  43. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  44. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  45. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  46. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  47. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  48. package/docs/forums_categories.ts.html +22 -3
  49. package/docs/forums_discussions.js.html +0 -0
  50. package/docs/forums_forum.js.html +0 -0
  51. package/docs/forums_forums.ts.html +2 -2
  52. package/docs/forums_posts.ts.html +2 -2
  53. package/docs/forums_threads.ts.html +9 -9
  54. package/docs/gamification_awards.js.html +0 -0
  55. package/docs/gamification_awards.ts.html +26 -12
  56. package/docs/gamification_gamification.js.html +2 -2
  57. package/docs/gamification_types.js.html +0 -0
  58. package/docs/global.html +2 -2
  59. package/docs/index.html +2 -2
  60. package/docs/liveTesting.ts.html +2 -2
  61. package/docs/module-Accounts.html +14 -14
  62. package/docs/module-Artist.html +12 -10
  63. package/docs/module-Awards.html +106 -6
  64. package/docs/module-Categories.html +0 -0
  65. package/docs/module-Config.html +5 -4
  66. package/docs/module-Content-Services-V2.html +440 -9
  67. package/docs/module-ForumCategories.html +0 -0
  68. package/docs/module-ForumDiscussions.html +0 -0
  69. package/docs/module-Forums.html +627 -63
  70. package/docs/module-Genre.html +6 -6
  71. package/docs/module-GuidedCourses.html +2 -2
  72. package/docs/module-Instructor.html +10 -10
  73. package/docs/module-Interests.html +2 -2
  74. package/docs/module-LearningPaths.html +640 -12
  75. package/docs/module-Onboarding.html +32 -8
  76. package/docs/module-Payments.html +2 -2
  77. package/docs/module-Permissions.html +2 -2
  78. package/docs/module-Playlists.html +2 -2
  79. package/docs/module-ProgressRow.html +2 -2
  80. package/docs/module-Railcontent-Services.html +2 -2
  81. package/docs/module-Sanity-Services.html +786 -1853
  82. package/docs/module-Sessions.html +2 -2
  83. package/docs/module-Threads.html +0 -0
  84. package/docs/module-UserActivity.html +4 -4
  85. package/docs/module-UserChat.html +2 -2
  86. package/docs/module-UserManagement.html +2 -2
  87. package/docs/module-UserMemberships.html +2 -2
  88. package/docs/module-UserNotifications.html +2 -2
  89. package/docs/module-UserProfile.html +2 -2
  90. package/docs/progress-row_method-card.js.html +3 -3
  91. package/docs/railcontent.js.html +2 -2
  92. package/docs/sanity.js.html +230 -321
  93. package/docs/scripts/collapse.js +0 -0
  94. package/docs/scripts/commonNav.js +0 -0
  95. package/docs/scripts/linenumber.js +0 -0
  96. package/docs/scripts/nav.js +0 -0
  97. package/docs/scripts/polyfill.js +0 -0
  98. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  99. package/docs/scripts/prettify/lang-css.js +0 -0
  100. package/docs/scripts/prettify/prettify.js +0 -0
  101. package/docs/scripts/search.js +0 -0
  102. package/docs/styles/jsdoc.css +0 -0
  103. package/docs/styles/prettify.css +0 -0
  104. package/docs/userActivity.js.html +4 -3
  105. package/docs/user_account.ts.html +14 -13
  106. package/docs/user_chat.js.html +2 -2
  107. package/docs/user_interests.js.html +2 -2
  108. package/docs/user_management.js.html +2 -2
  109. package/docs/user_memberships.js.html +0 -0
  110. package/docs/user_memberships.ts.html +2 -2
  111. package/docs/user_notifications.js.html +2 -2
  112. package/docs/user_onboarding.ts.html +95 -4
  113. package/docs/user_payments.ts.html +2 -2
  114. package/docs/user_permissions.js.html +3 -3
  115. package/docs/user_profile.js.html +2 -2
  116. package/docs/user_sessions.js.html +2 -2
  117. package/docs/user_types.js.html +2 -2
  118. package/docs/user_user-management-system.js.html +2 -2
  119. package/jest.config.js +0 -0
  120. package/jsdoc.json +0 -0
  121. package/package.json +1 -1
  122. package/src/contentMetaData.js +0 -0
  123. package/src/contentTypeConfig.js +24 -16
  124. package/src/filterBuilder.js +0 -0
  125. package/src/index.d.ts +0 -0
  126. package/src/index.js +0 -0
  127. package/src/infrastructure/http/HttpClient.ts +0 -0
  128. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  129. package/src/infrastructure/http/index.ts +0 -0
  130. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  131. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  132. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  133. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  134. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  135. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  136. package/src/lib/brands.ts +8 -0
  137. package/src/lib/httpHelper.js +0 -0
  138. package/src/lib/lastUpdated.js +0 -0
  139. package/src/lib/sanity/query.ts +30 -0
  140. package/src/services/api/types.js +0 -0
  141. package/src/services/api/types.ts +0 -0
  142. package/src/services/config.js +0 -0
  143. package/src/services/content/artist.ts +34 -39
  144. package/src/services/content/content.ts +7 -1
  145. package/src/services/content/genre.ts +27 -32
  146. package/src/services/content/instructor.ts +23 -25
  147. package/src/services/content-org/content-org.js +0 -0
  148. package/src/services/content-org/guided-courses.ts +0 -0
  149. package/src/services/content-org/learning-paths.ts +4 -4
  150. package/src/services/content-org/playlists-types.js +0 -0
  151. package/src/services/content-org/playlists.js +0 -0
  152. package/src/services/content.js +0 -0
  153. package/src/services/contentAggregator.js +5 -1
  154. package/src/services/contentLikes.js +0 -0
  155. package/src/services/contentProgress.js +6 -6
  156. package/src/services/dataContext.js +0 -0
  157. package/src/services/dateUtils.js +0 -0
  158. package/src/services/eventsAPI.js +0 -0
  159. package/src/services/forums/categories.ts +0 -0
  160. package/src/services/forums/forums.ts +0 -0
  161. package/src/services/forums/posts.ts +0 -0
  162. package/src/services/forums/threads.ts +0 -0
  163. package/src/services/forums/types.ts +0 -0
  164. package/src/services/gamification/awards.ts +0 -0
  165. package/src/services/gamification/gamification.js +0 -0
  166. package/src/services/imageSRCBuilder.js +0 -0
  167. package/src/services/imageSRCVerify.js +0 -0
  168. package/src/services/liveTesting.ts +0 -0
  169. package/src/services/permissions/PermissionsAdapter.ts +0 -0
  170. package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
  171. package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
  172. package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
  173. package/src/services/permissions/README.md +0 -0
  174. package/src/services/permissions/index.ts +0 -0
  175. package/src/services/progress-row/method-card.js +0 -0
  176. package/src/services/railcontent.js +0 -0
  177. package/src/services/recommendations.js +0 -0
  178. package/src/services/sanity.js +52 -45
  179. package/src/services/types.js +0 -0
  180. package/src/services/user/account.ts +0 -0
  181. package/src/services/user/chat.js +0 -0
  182. package/src/services/user/interests.js +0 -0
  183. package/src/services/user/management.js +0 -0
  184. package/src/services/user/memberships.ts +0 -0
  185. package/src/services/user/notifications.js +0 -0
  186. package/src/services/user/onboarding.ts +93 -2
  187. package/src/services/user/payments.ts +0 -0
  188. package/src/services/user/permissions.js +0 -0
  189. package/src/services/user/profile.js +0 -0
  190. package/src/services/user/sessions.js +0 -0
  191. package/src/services/user/types.d.ts +0 -0
  192. package/src/services/user/types.js +0 -0
  193. package/src/services/user/user-management-system.js +0 -0
  194. package/src/services/userActivity.js +1 -1
  195. package/test/HttpClient.test.js +0 -0
  196. package/test/content.test.js +0 -0
  197. package/test/contentLikes.test.js +0 -0
  198. package/test/contentProgress.test.js +0 -0
  199. package/test/dataContext.test.js +0 -0
  200. package/test/forum.test.js +0 -0
  201. package/test/imageSRCBuilder.test.js +0 -0
  202. package/test/imageSRCVerify.test.js +0 -0
  203. package/test/initializeTests.js +0 -0
  204. package/test/learningPaths.test.js +0 -0
  205. package/test/lib/lastUpdated.test.js +0 -0
  206. package/test/live/contentProgressLive.test.js +0 -0
  207. package/test/live/railcontentLive.test.js +0 -0
  208. package/test/localStorageMock.js +0 -0
  209. package/test/log.js +0 -0
  210. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  211. package/test/mockData/mockData_progress_content.json +0 -0
  212. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  213. package/test/mockData/mockData_user_practices.json +0 -0
  214. package/test/notifications.test.js +0 -0
  215. package/test/progressRows.test.js +0 -0
  216. package/test/sanityQueryService.test.js +0 -0
  217. package/test/streakMessage.test.js +0 -0
  218. package/test/user/permissions.test.js +0 -0
  219. package/test/userActivity.test.js +0 -0
  220. package/tools/generate-index.cjs +0 -0
  221. package/.claude/settings.local.json +0 -14
  222. package/.yarnrc.yml +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.85.1",
3
+ "version": "2.86.1",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
File without changes
@@ -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
- "'genre': genre[]->name",
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
- "'genre': genre[]->name",
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": genre[]->name`, `difficulty_string`, `"type": _type`],
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
- '"style": genre[]->name',
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 // Show all children in lists
830
+ showMembershipRestrictedContent: true, // Show all children in lists
823
831
  }).buildFilter()
824
832
  parentFields.push(
825
833
  `"children": child[${childFilter}]->{
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
@@ -0,0 +1,8 @@
1
+ export enum Brand {
2
+ MUSORA = 'musora',
3
+ DRUMEO = 'drumeo',
4
+ PIANOTE = 'pianote',
5
+ GUITAREO = 'guitareo',
6
+ SINGEO = 'singeo',
7
+ PLAYBASS = 'playbass',
8
+ }
File without changes
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
File without changes
@@ -1,22 +1,24 @@
1
1
  /**
2
2
  * @module Artist
3
3
  */
4
- import { DEFAULT_FIELDS, filtersToGroq } from '../../contentTypeConfig.js'
4
+ import { filtersToGroq, getFieldsForContentType } from '../../contentTypeConfig.js'
5
5
  import { FilterBuilder } from '../../filterBuilder.js'
6
- import { fetchSanity, getSanityDate, getSortOrder } from '../sanity.js'
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
- lessons?: Lesson[]
13
- lessonsCount: number
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 {string} brand - The brand for which to fetch artists.
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: string): Promise<Artist[] | null> {
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
- "lessonsCount": count(*[${filter}])
37
- }[lessonsCount > 0] |order(lower(name)) `
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 {string} [brand] - The brand for which to fetch the artist.
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?: string): Promise<Artist | null> {
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
- "lessonsCount": count(*[${filter}])
63
- }[lessonsCount > 0] |order(lower(name)) `
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: Artist[]
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 {string} brand - The brand for which to fetch lessons.
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.progressIds] - The ids of the lessons that are in progress or completed
92
- * @returns {Promise<LessonsByArtistResponse|null>} - The lessons for the artist and some details about the artist (name and thumbnail).
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: string,
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 = undefined,
113
+ progressIds = [],
110
114
  }: ArtistLessonOptions = {}
111
115
  ): Promise<LessonsByArtistResponse | null> {
112
- const fieldsString = DEFAULT_FIELDS.join(',')
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 !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
128
- const now = getSanityDate(new Date())
129
- const query = `{
130
- "data":
131
- *[_type == 'artist' && slug.current == '${slug}']
132
- {'type': _type, name, 'thumbnail':thumbnail_url.asset->url,
133
- 'lessons_count': count(*[${addType} brand == '${brand}' && references(^._id)]),
134
- 'lessons': *[${addType} brand == '${brand}' && references(^._id) && (status in ['published'] || (status == 'scheduled' && defined(published_on) && published_on >= '${now}')) ${searchFilter} ${includedFieldsFilter} ${progressFilter}]{${fieldsString}}
135
- [${start}...${end}]}
136
- |order(${sortOrder})
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: string[] | string | null
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 { DEFAULT_FIELDS, filtersToGroq } from '../../contentTypeConfig.js'
5
- import { fetchSanity, getSanityDate, getSortOrder } from '../sanity.js'
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: string): Promise<Genre[]> {
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 {string} [brand] - The brand for which to fetch the genre. Lesson count will be filtered by this brand if provided.
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?: string): Promise<Genre | null> {
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: Genre[]
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 {string} brand - The brand for which to fetch lessons.
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 artist and some details about the artist (name and thumbnail).
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: string,
108
- contentType: string,
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 = DEFAULT_FIELDS.join(',')
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
- // limits the results to supplied progressIds for started & completed filters
124
+ const addType = contentType ? `_type == '${contentType}' && ` : ''
126
125
  const progressFilter =
127
- progressIds !== undefined ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
128
- const now = getSanityDate(new Date())
129
- const query = `{
130
- "data":
131
- *[_type == 'genre' && slug.current == '${slug}']
132
- {
133
- 'type': _type,
134
- name,
135
- 'thumbnail': thumbnail_url.asset->url,
136
- 'lessons_count': count(*[${addType} brand == '${brand}' && references(^._id)]),
137
- 'lessons': *[${addType} brand == '${brand}' && references(^._id) && (status in ['published'] || (status == 'scheduled' && defined(published_on) && published_on >= '${now}')) ${searchFilter} ${includedFieldsFilter} ${progressFilter}]{${fieldsString}} [${start}...${end}]
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, getSanityDate, getSortOrder } from '../sanity.js'
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 {string} brand - The brand for which to fetch instructors.
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: string): Promise<Instructor[]> {
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
- "lessonsCount": count(*[${filter}])
38
- }[lessonsCount > 0] |order(lower(name)) `
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 {string} [brand] - The brand for which to fetch the instructor. Lesson count will be filtered by this brand if provided.
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?: string
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
- "lessonsCount": count(*[${filter}])
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|null>} - The lessons for the instructor or null if not found.
102
+ * @returns {Promise<InstructorLessonsResponse>} - The lessons for the instructor or null if not found.
99
103
  * @example
100
- * fetchInstructorLessons('instructor123', 'drumeo', { page: 2, limit: 10 })
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: string,
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 | null> {
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 now = getSanityDate(new Date())
123
- const query = `{
124
- "data":
125
- *[_type == 'instructor' && slug.current == '${slug}']
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