musora-content-services 2.95.2 → 2.95.4

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 (210) 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 +14 -0
  10. package/CLAUDE.md +0 -0
  11. package/README.md +0 -0
  12. package/babel.config.cjs +0 -0
  13. package/jest.config.js +0 -0
  14. package/jsdoc.json +0 -0
  15. package/package.json +1 -1
  16. package/src/constants/award-assets.js +0 -0
  17. package/src/contentMetaData.js +0 -0
  18. package/src/filterBuilder.js +0 -0
  19. package/src/index.d.ts +0 -0
  20. package/src/index.js +0 -0
  21. package/src/infrastructure/http/HttpClient.ts +0 -0
  22. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  23. package/src/infrastructure/http/index.ts +0 -0
  24. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  25. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  26. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  27. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  28. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  29. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  30. package/src/lib/ads/monoid.ts +5 -0
  31. package/src/lib/ads/semigroup.ts +3 -0
  32. package/src/lib/brands.ts +0 -0
  33. package/src/lib/httpHelper.js +0 -0
  34. package/src/lib/lastUpdated.js +0 -0
  35. package/src/lib/sanity/query.ts +125 -28
  36. package/src/services/api/types.js +0 -0
  37. package/src/services/api/types.ts +0 -0
  38. package/src/services/awards/award-callbacks.js +0 -0
  39. package/src/services/awards/award-query.js +0 -0
  40. package/src/services/awards/internal/.indexignore +0 -0
  41. package/src/services/awards/internal/award-definitions.js +0 -0
  42. package/src/services/awards/internal/award-events.js +0 -0
  43. package/src/services/awards/internal/award-manager.js +0 -0
  44. package/src/services/awards/internal/certificate-builder.js +0 -0
  45. package/src/services/awards/internal/completion-data-generator.js +0 -0
  46. package/src/services/awards/internal/content-progress-observer.js +0 -0
  47. package/src/services/awards/internal/image-utils.js +0 -0
  48. package/src/services/awards/internal/message-generator.js +0 -0
  49. package/src/services/awards/internal/types.js +0 -0
  50. package/src/services/awards/types.d.ts +0 -0
  51. package/src/services/awards/types.js +0 -0
  52. package/src/services/config.js +0 -0
  53. package/src/services/content/artist.ts +69 -37
  54. package/src/services/content/content.ts +0 -0
  55. package/src/services/content/genre.ts +67 -39
  56. package/src/services/content/instructor.ts +76 -41
  57. package/src/services/content-org/content-org.js +0 -0
  58. package/src/services/content-org/guided-courses.ts +0 -0
  59. package/src/services/content-org/learning-paths.ts +0 -0
  60. package/src/services/content-org/playlists-types.js +0 -0
  61. package/src/services/content-org/playlists.js +0 -0
  62. package/src/services/content.js +0 -0
  63. package/src/services/contentAggregator.js +0 -0
  64. package/src/services/contentLikes.js +0 -0
  65. package/src/services/contentProgress.js +0 -0
  66. package/src/services/dataContext.js +0 -0
  67. package/src/services/dateUtils.js +0 -0
  68. package/src/services/eventsAPI.js +0 -0
  69. package/src/services/forums/categories.ts +0 -0
  70. package/src/services/forums/forums.ts +0 -0
  71. package/src/services/forums/posts.ts +0 -0
  72. package/src/services/forums/threads.ts +0 -0
  73. package/src/services/forums/types.ts +0 -0
  74. package/src/services/gamification/awards.ts +0 -0
  75. package/src/services/gamification/gamification.js +0 -0
  76. package/src/services/imageSRCBuilder.js +0 -0
  77. package/src/services/imageSRCVerify.js +0 -0
  78. package/src/services/liveTesting.ts +0 -0
  79. package/src/services/permissions/PermissionsAdapter.ts +0 -0
  80. package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
  81. package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
  82. package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
  83. package/src/services/permissions/README.md +0 -0
  84. package/src/services/permissions/index.ts +0 -0
  85. package/src/services/progress-events.js +0 -0
  86. package/src/services/progress-row/method-card.js +0 -0
  87. package/src/services/railcontent.js +0 -0
  88. package/src/services/recommendations.js +0 -0
  89. package/src/services/reporting/README.md +0 -0
  90. package/src/services/reporting/reporting.ts +0 -0
  91. package/src/services/reporting/types.ts +0 -0
  92. package/src/services/sentry/.indexignore +0 -0
  93. package/src/services/sentry/index.ts +0 -0
  94. package/src/services/sync/.indexignore +0 -0
  95. package/src/services/sync/adapters/factory.ts +0 -0
  96. package/src/services/sync/adapters/lokijs.ts +0 -0
  97. package/src/services/sync/adapters/sqlite.ts +0 -0
  98. package/src/services/sync/concurrency-safety.ts +0 -0
  99. package/src/services/sync/context/index.ts +0 -0
  100. package/src/services/sync/context/providers/base.ts +0 -0
  101. package/src/services/sync/context/providers/connectivity.ts +0 -0
  102. package/src/services/sync/context/providers/durability.ts +0 -0
  103. package/src/services/sync/context/providers/index.ts +0 -0
  104. package/src/services/sync/context/providers/session.ts +0 -0
  105. package/src/services/sync/context/providers/tabs.ts +0 -0
  106. package/src/services/sync/context/providers/visibility.ts +0 -0
  107. package/src/services/sync/database/factory.ts +0 -0
  108. package/src/services/sync/errors/boundary.ts +0 -0
  109. package/src/services/sync/errors/index.ts +0 -0
  110. package/src/services/sync/fetch.ts +0 -0
  111. package/src/services/sync/index.ts +0 -0
  112. package/src/services/sync/manager.ts +0 -0
  113. package/src/services/sync/models/Base.ts +0 -0
  114. package/src/services/sync/models/ContentLike.ts +0 -0
  115. package/src/services/sync/models/ContentProgress.ts +0 -0
  116. package/src/services/sync/models/Practice.ts +0 -0
  117. package/src/services/sync/models/PracticeDayNote.ts +0 -0
  118. package/src/services/sync/models/UserAwardProgress.ts +0 -0
  119. package/src/services/sync/models/index.ts +0 -0
  120. package/src/services/sync/repositories/base.ts +0 -0
  121. package/src/services/sync/repositories/content-likes.ts +0 -0
  122. package/src/services/sync/repositories/content-progress.ts +0 -0
  123. package/src/services/sync/repositories/index.ts +0 -0
  124. package/src/services/sync/repositories/practice-day-notes.ts +0 -0
  125. package/src/services/sync/repositories/practices.ts +0 -0
  126. package/src/services/sync/repositories/user-award-progress.ts +0 -0
  127. package/src/services/sync/repository-proxy.ts +0 -0
  128. package/src/services/sync/resolver.ts +0 -0
  129. package/src/services/sync/retry.ts +0 -0
  130. package/src/services/sync/run-scope.ts +0 -0
  131. package/src/services/sync/schema/index.ts +0 -0
  132. package/src/services/sync/serializers/index.ts +0 -0
  133. package/src/services/sync/serializers/model.ts +0 -0
  134. package/src/services/sync/serializers/raw.ts +0 -0
  135. package/src/services/sync/store/index.ts +0 -0
  136. package/src/services/sync/store/push-coalescer.ts +0 -0
  137. package/src/services/sync/store-configs.ts +0 -0
  138. package/src/services/sync/strategies/base.ts +0 -0
  139. package/src/services/sync/strategies/index.ts +0 -0
  140. package/src/services/sync/strategies/initial.ts +0 -0
  141. package/src/services/sync/strategies/polling.ts +0 -0
  142. package/src/services/sync/telemetry/index.ts +0 -0
  143. package/src/services/sync/telemetry/sampling.ts +0 -0
  144. package/src/services/sync/utils/event-emitter.ts +0 -0
  145. package/src/services/sync/utils/index.ts +0 -0
  146. package/src/services/sync/utils/throttle.ts +0 -0
  147. package/src/services/sync/utils/timers.ts +0 -0
  148. package/src/services/types.js +0 -0
  149. package/src/services/user/account.ts +0 -0
  150. package/src/services/user/chat.js +0 -0
  151. package/src/services/user/interests.js +0 -0
  152. package/src/services/user/management.js +0 -0
  153. package/src/services/user/memberships.ts +0 -0
  154. package/src/services/user/notifications.js +0 -0
  155. package/src/services/user/onboarding.ts +12 -12
  156. package/src/services/user/payments.ts +0 -0
  157. package/src/services/user/permissions.js +0 -0
  158. package/src/services/user/profile.js +0 -0
  159. package/src/services/user/sessions.js +0 -0
  160. package/src/services/user/types.d.ts +0 -0
  161. package/src/services/user/types.js +0 -0
  162. package/src/services/user/user-management-system.js +0 -0
  163. package/src/services/userActivity.js +0 -0
  164. package/test/HttpClient.test.js +0 -0
  165. package/test/awards/award-alacarte-observer.test.js +0 -0
  166. package/test/awards/award-auto-refresh.test.js +0 -0
  167. package/test/awards/award-calculations.test.js +0 -0
  168. package/test/awards/award-certificate-display.test.js +0 -0
  169. package/test/awards/award-collection-edge-cases.test.js +0 -0
  170. package/test/awards/award-collection-filtering.test.js +0 -0
  171. package/test/awards/award-completion-flow.test.js +0 -0
  172. package/test/awards/award-exclusion-handling.test.js +0 -0
  173. package/test/awards/award-multi-lesson.test.js +0 -0
  174. package/test/awards/award-observer-integration.test.js +0 -0
  175. package/test/awards/award-query-messages.test.js +0 -0
  176. package/test/awards/award-user-collection.test.js +0 -0
  177. package/test/awards/duplicate-prevention.test.js +0 -0
  178. package/test/awards/helpers/completion-mock.js +0 -0
  179. package/test/awards/helpers/index.js +0 -0
  180. package/test/awards/helpers/mock-setup.js +0 -0
  181. package/test/awards/helpers/progress-emitter.js +0 -0
  182. package/test/awards/message-generator.test.js +0 -0
  183. package/test/content.test.js +0 -0
  184. package/test/contentLikes.test.js +0 -0
  185. package/test/contentProgress.test.js +0 -0
  186. package/test/dataContext.test.js +0 -0
  187. package/test/forum.test.js +0 -0
  188. package/test/imageSRCBuilder.test.js +0 -0
  189. package/test/imageSRCVerify.test.js +0 -0
  190. package/test/initializeTests.js +0 -0
  191. package/test/learningPaths.test.js +0 -0
  192. package/test/lib/lastUpdated.test.js +0 -0
  193. package/test/live/contentProgressLive.test.js +0 -0
  194. package/test/live/railcontentLive.test.js +0 -0
  195. package/test/localStorageMock.js +0 -0
  196. package/test/log.js +0 -0
  197. package/test/mockData/award-definitions.js +0 -0
  198. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  199. package/test/mockData/mockData_progress_content.json +0 -0
  200. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  201. package/test/mockData/mockData_user_practices.json +0 -0
  202. package/test/notifications.test.js +0 -0
  203. package/test/progressRows.test.js +0 -0
  204. package/test/sanityQueryService.test.js +0 -0
  205. package/test/streakMessage.test.js +0 -0
  206. package/test/sync/models/award-database-integration.test.js +0 -0
  207. package/test/user/permissions.test.js +0 -0
  208. package/test/userActivity.test.js +0 -0
  209. package/tools/generate-index.cjs +0 -0
  210. package/.claude/settings.local.json +0 -18
package/.coderabbit.yaml CHANGED
File without changes
package/.editorconfig CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
package/.prettierignore CHANGED
File without changes
package/.prettierrc CHANGED
File without changes
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [2.95.4](https://github.com/railroadmedia/musora-content-services/compare/v2.95.3...v2.95.4) (2025-12-08)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **onboarding:** update hard-coded data ([e33f309](https://github.com/railroadmedia/musora-content-services/commit/e33f309d0b051829e58f631f4b1368501bafc72b))
11
+
12
+ ### [2.95.3](https://github.com/railroadmedia/musora-content-services/compare/v2.95.2...v2.95.3) (2025-12-08)
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * make agi responses standard ([#630](https://github.com/railroadmedia/musora-content-services/issues/630)) ([0301133](https://github.com/railroadmedia/musora-content-services/commit/03011331cb835412af80111a97e6ae83fd6e56d5))
18
+
5
19
  ### [2.95.2](https://github.com/railroadmedia/musora-content-services/compare/v2.95.1...v2.95.2) (2025-12-08)
6
20
 
7
21
  ### [2.95.1](https://github.com/railroadmedia/musora-content-services/compare/v2.95.0...v2.95.1) (2025-12-08)
package/CLAUDE.md CHANGED
File without changes
package/README.md CHANGED
File without changes
package/babel.config.cjs CHANGED
File without changes
package/jest.config.js CHANGED
File without changes
package/jsdoc.json CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.95.2",
3
+ "version": "2.95.4",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
File without changes
File without changes
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,5 @@
1
+ import { Semigroup } from './semigroup'
2
+
3
+ export interface Monoid<A> extends Semigroup<A> {
4
+ empty: A
5
+ }
@@ -0,0 +1,3 @@
1
+ export interface Semigroup<A> {
2
+ concat: (a: A, b: A) => A
3
+ }
package/src/lib/brands.ts CHANGED
File without changes
File without changes
File without changes
@@ -1,30 +1,127 @@
1
+ import { Monoid } from '../ads/monoid'
2
+ import { Semigroup } from '../ads/semigroup'
3
+
1
4
  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
5
+ sort?: string
6
+ offset?: number
7
+ limit?: number
8
+ }
9
+
10
+ export type Projection = string[]
11
+
12
+ export interface QueryBuilderState {
13
+ filter: string
14
+ ordering: string
15
+ slice: string
16
+ projection: string
17
+ postFilter: string
18
+ }
19
+
20
+ export interface QueryBuilder {
21
+ and(expr: string): QueryBuilder
22
+ or(...exprs: string[]): QueryBuilder
23
+ order(expr: string): QueryBuilder
24
+ slice(start: number, end: number): QueryBuilder
25
+ first(): QueryBuilder
26
+ select(...fields: string[]): QueryBuilder
27
+ postFilter(expr: string): QueryBuilder
28
+ build(): string
29
+
30
+ _state(): QueryBuilderState
31
+ }
32
+
33
+ const and: Monoid<string> = {
34
+ empty: '',
35
+ concat: (a, b) => (!a ? b : !b ? a : `${a} && ${b}`),
36
+ }
37
+
38
+ const or: Monoid<string> = {
39
+ empty: '',
40
+ concat: (a, b) => (!a ? b : !b ? a : `(${a} || ${b})`),
41
+ }
42
+
43
+ const order: Monoid<string> = {
44
+ empty: '',
45
+ concat: (a, b) => `| order(${b || a})`,
46
+ }
47
+
48
+ const slice: Monoid<string> = {
49
+ empty: '',
50
+ concat: (a, b) => b || a,
51
+ }
52
+
53
+ const project: Semigroup<string> = {
54
+ concat: (a, b) => `${a}, ${b}`,
55
+ }
56
+
57
+ export const query = (): QueryBuilder => {
58
+ let state: QueryBuilderState = {
59
+ filter: and.empty,
60
+ ordering: order.empty,
61
+ slice: slice.empty,
62
+ projection: '_id',
63
+ postFilter: and.empty,
64
+ }
65
+
66
+ const builder: QueryBuilder = {
67
+ // main filters
68
+ and(expr: string) {
69
+ state.filter = and.concat(state.filter, expr)
70
+ return builder
71
+ },
72
+
73
+ or(...exprs: string[]) {
74
+ const orExpr = exprs.reduce(or.concat, or.empty)
75
+ state.filter = and.concat(state.filter, orExpr)
76
+ return builder
77
+ },
78
+
79
+ // sorting
80
+ order(expr: string) {
81
+ state.ordering = order.concat(state.ordering, expr)
82
+ return builder
83
+ },
84
+
85
+ // pagination / slicing
86
+ slice(start: number = 0, end?: number) {
87
+ const sliceExpr = !end ? `[${start}]` : `[${start}...${end}]`
88
+
89
+ state.slice = slice.concat(state.slice, sliceExpr)
90
+ return builder
91
+ },
92
+
93
+ first() {
94
+ return this.slice()
95
+ },
96
+
97
+ // projection
98
+ select(...fields: string[]) {
99
+ state.projection = fields.reduce(project.concat, state.projection)
100
+ return builder
101
+ },
102
+
103
+ // post filters
104
+ postFilter(expr: string) {
105
+ state.postFilter = and.concat(state.postFilter, expr)
106
+ return builder
107
+ },
108
+
109
+ build() {
110
+ const { filter, ordering, slice, projection } = state
111
+
112
+ return `
113
+ *[${filter}]
114
+ ${ordering}
115
+ ${slice}
116
+ { ${projection} }
117
+ ${state.postFilter ? `[${state.postFilter}]` : ''}
118
+ `.trim()
119
+ },
120
+
121
+ _state() {
122
+ return state
123
+ },
124
+ }
125
+
126
+ return builder
30
127
  }
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
File without changes
File without changes
File without changes
File without changes
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { filtersToGroq, getFieldsForContentType } from '../../contentTypeConfig.js'
5
5
  import { FilterBuilder } from '../../filterBuilder.js'
6
- import { buildDataAndTotalQuery } from '../../lib/sanity/query'
6
+ import { BuildQueryOptions, query } from '../../lib/sanity/query'
7
7
  import { fetchSanity, getSortOrder } from '../sanity.js'
8
8
  import { Lesson } from './content'
9
9
  import { Brands } from '../../lib/brands'
@@ -26,19 +26,39 @@ export interface Artist {
26
26
  * .then(artists => console.log(artists))
27
27
  * .catch(error => console.error(error));
28
28
  */
29
- export async function fetchArtists(brand: Brands | string): Promise<Artist[] | null> {
30
- const filter = await new FilterBuilder(
31
- `_type == "song" && brand == "${brand}" && references(^._id)`,
32
- { bypassPermissions: true }
33
- ).buildFilter()
34
- const query = `
35
- *[_type == "artist"]{
36
- name,
37
- "slug": slug.current,
38
- 'thumbnail': thumbnail_url.asset->url,
39
- "lessonCount": count(*[${filter}])
40
- }[lessonCount > 0] |order(lower(name)) `
41
- return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
29
+ export async function fetchArtists(
30
+ brand: Brands | string,
31
+ options: BuildQueryOptions = { sort: 'lower(name) asc' }
32
+ ): Promise<Artist[] | null> {
33
+ const lessonFilter = await new FilterBuilder(`brand == "${brand}" && references(^._id)`, {
34
+ bypassPermissions: true,
35
+ }).buildFilter()
36
+
37
+ const data = query()
38
+ .and(`_type == "artist"`)
39
+ .order(options.sort || 'lower(name) asc')
40
+ .slice(options.offset || 0, (options.offset || 0) + (options.limit || 20))
41
+ .select(
42
+ 'name',
43
+ `"slug": slug.current`,
44
+ `"thumbnail": thumbnail_url.asset->url`,
45
+ `"lessonCount": count(*[${lessonFilter}])`
46
+ )
47
+ .postFilter(`lessonCount > 0`)
48
+ .build()
49
+
50
+ const total = query()
51
+ .and(`_type == "artist"`)
52
+ .select(`"lessonCount": count(*[${lessonFilter}])`)
53
+ .postFilter(`lessonCount > 0`)
54
+ .build()
55
+
56
+ const q = `{
57
+ "data": ${data},
58
+ "total": ${total}
59
+ }`
60
+
61
+ return fetchSanity(q, true, { processNeedAccess: false, processPageType: false })
42
62
  }
43
63
 
44
64
  /**
@@ -61,25 +81,29 @@ export async function fetchArtistBySlug(
61
81
  const filter = await new FilterBuilder(`${brandFilter} _type == "song" && references(^._id)`, {
62
82
  bypassPermissions: true,
63
83
  }).buildFilter()
64
- const query = `
65
- *[_type == "artist" && slug.current == '${slug}']{
66
- name,
67
- "slug": slug.current,
68
- "lessonCount": count(*[${filter}])
69
- }[lessonCount > 0] |order(lower(name)) `
70
- return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
84
+
85
+ const q = query()
86
+ .and(`_type == "artist"`)
87
+ .and(`&& slug.current == '${slug}'`)
88
+ .select(
89
+ 'name',
90
+ `"slug": slug.current`,
91
+ `"thumbnail": thumbnail_url.asset->url`,
92
+ `"lessonCount": count(*[${filter}])`
93
+ )
94
+ .first()
95
+ .build()
96
+
97
+ return fetchSanity(q, true, { processNeedAccess: false, processPageType: false })
71
98
  }
72
99
 
73
- export interface ArtistLessonOptions {
74
- sort?: string
100
+ export interface ArtistLessonOptions extends BuildQueryOptions {
75
101
  searchTerm?: string
76
- page?: number
77
- limit?: number
78
102
  includedFields?: Array<string>
79
103
  progressIds?: Array<number>
80
104
  }
81
105
 
82
- export interface LessonsByArtistResponse {
106
+ export interface ArtistLessons {
83
107
  data: Lesson[]
84
108
  total: number
85
109
  }
@@ -96,7 +120,7 @@ export interface LessonsByArtistResponse {
96
120
  * @param {number} [params.limit=10] - The number of items per page.
97
121
  * @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'].
98
122
  * @param {Array<number>} [params.progressId=[]] - The ids of the lessons that are in progress or completed
99
- * @returns {Promise<LessonsByArtistResponse|null>} - The lessons for the artist
123
+ * @returns {Promise<ArtistLessons>} - The lessons for the artist
100
124
  *
101
125
  * @example
102
126
  * fetchArtistLessons('10 Years', 'drumeo', 'song', {'-published_on', '', 1, 10, ["difficulty,Intermediate"], [232168, 232824, 303375, 232194, 393125]})
@@ -110,15 +134,13 @@ export async function fetchArtistLessons(
110
134
  {
111
135
  sort = '-published_on',
112
136
  searchTerm = '',
113
- page = 1,
137
+ offset = 1,
114
138
  limit = 10,
115
139
  includedFields = [],
116
140
  progressIds = [],
117
141
  }: ArtistLessonOptions = {}
118
- ): Promise<LessonsByArtistResponse | null> {
142
+ ): Promise<ArtistLessons> {
119
143
  const fieldsString = getFieldsForContentType(contentType) as string
120
- const start = (page - 1) * limit
121
- const end = start + limit
122
144
  const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
123
145
  const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
124
146
  const addType = contentType ? `_type == '${contentType}' && ` : ''
@@ -128,10 +150,20 @@ export async function fetchArtistLessons(
128
150
  const filterWithRestrictions = await new FilterBuilder(filter).buildFilter()
129
151
 
130
152
  sort = getSortOrder(sort, brand)
131
- const query = buildDataAndTotalQuery(filterWithRestrictions, fieldsString, {
132
- sort,
133
- start: start,
134
- end: end,
135
- })
136
- return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
153
+
154
+ const data = query()
155
+ .and(filterWithRestrictions)
156
+ .order(sort)
157
+ .slice(offset, offset + limit)
158
+ .select(...(fieldsString ? [fieldsString] : []))
159
+ .build()
160
+
161
+ const total = query().and(filterWithRestrictions).build()
162
+
163
+ const q = `{
164
+ "data": ${data},
165
+ "total": ${total}
166
+ }`
167
+
168
+ return fetchSanity(q, true, { processNeedAccess: false, processPageType: false })
137
169
  }
File without changes
@@ -5,7 +5,7 @@ import { filtersToGroq, getFieldsForContentType } from '../../contentTypeConfig.
5
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'
8
+ import { BuildQueryOptions, query } from '../../lib/sanity/query'
9
9
  import { Brands } from '../../lib/brands'
10
10
 
11
11
  export interface Genre {
@@ -26,20 +26,39 @@ 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: Brands | string): Promise<Genre[]> {
30
- const filter = await new FilterBuilder(`brand == "${brand}" && references(^._id)`, {
29
+ export async function fetchGenres(
30
+ brand: Brands | string,
31
+ options: BuildQueryOptions = { sort: 'lower(name) asc' }
32
+ ): Promise<Genre[]> {
33
+ const lessonFilter = await new FilterBuilder(`brand == "${brand}" && references(^._id)`, {
31
34
  bypassPermissions: true,
32
35
  }).buildFilter()
33
36
 
34
- const query = `
35
- *[_type == 'genre'] {
36
- 'type': _type,
37
- name,
38
- "slug": slug.current,
39
- 'thumbnail': thumbnail_url.asset->url,
40
- "lessons_count": count(*[${filter}])
41
- } |order(lower(name)) `
42
- return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
37
+ const data = query()
38
+ .and(`_type == "genre"`)
39
+ .order(options.sort || 'lower(name) asc')
40
+ .slice(options.offset || 0, (options.offset || 0) + (options.limit || 20))
41
+ .select(
42
+ 'name',
43
+ `"slug": slug.current`,
44
+ `"thumbnail": thumbnail_url.asset->url`,
45
+ `"lessons_count": count(*[${lessonFilter}])`
46
+ )
47
+ .postFilter(`lessons_count > 0`)
48
+ .build()
49
+
50
+ const total = query()
51
+ .and(`_type == "genre"`)
52
+ .select(`"lessons_count": count(*[${lessonFilter}])`)
53
+ .postFilter(`lessons_count > 0`)
54
+ .build()
55
+
56
+ const q = `{
57
+ "data": ${data},
58
+ "total": ${total}
59
+ }`
60
+
61
+ return fetchSanity(q, true, { processNeedAccess: false, processPageType: false })
43
62
  }
44
63
 
45
64
  /**
@@ -47,7 +66,7 @@ export async function fetchGenres(brand: Brands | string): Promise<Genre[]> {
47
66
  *
48
67
  * @param {string} slug - The slug of the genre to fetch.
49
68
  * @param {Brands|string} [brand] - The brand for which to fetch the genre. Lesson count will be filtered by this brand if provided.
50
- * @returns {Promise<Genre[]|null>} - A promise that resolves to an genre object or null if not found.
69
+ * @returns {Promise<Genre | null>} - A promise that resolves to an genre object or null if not found.
51
70
  *
52
71
  * @example
53
72
  * fetchGenreBySlug('drumeo')
@@ -63,27 +82,28 @@ export async function fetchGenreBySlug(
63
82
  bypassPermissions: true,
64
83
  }).buildFilter()
65
84
 
66
- const query = `
67
- *[_type == 'genre' && slug.current == '${slug}'] {
68
- 'type': _type, name,
69
- name,
70
- "slug": slug.current,
71
- 'thumbnail':thumbnail_url.asset->url,
72
- "lessonsCount": count(*[${filter}])
73
- }`
74
- return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
85
+ const q = query()
86
+ .and(`_type == "genre"`)
87
+ .and(`slug.current == "${slug}"`)
88
+ .select(
89
+ 'name',
90
+ `"slug": slug.current`,
91
+ `"thumbnail": thumbnail_url.asset->url`,
92
+ `"lessons_count": count(*[${filter}])`
93
+ )
94
+ .first()
95
+ .build()
96
+
97
+ return fetchSanity(q, true, { processNeedAccess: false, processPageType: false })
75
98
  }
76
99
 
77
- export interface FetchGenreLessonsOptions {
78
- sort?: string
100
+ export interface GenreLessonsOptions extends BuildQueryOptions {
79
101
  searchTerm?: string
80
- page?: number
81
- limit?: number
82
102
  includedFields?: Array<string>
83
103
  progressIds?: Array<number>
84
104
  }
85
105
 
86
- export interface LessonsByGenreResponse {
106
+ export interface GenreLessons {
87
107
  data: Lesson[]
88
108
  total: number
89
109
  }
@@ -99,7 +119,7 @@ export interface LessonsByGenreResponse {
99
119
  * @param {number} [params.limit=10] - The number of items per page.
100
120
  * @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'].
101
121
  * @param {Array<number>} [params.progressIds=[]] - The ids of the lessons that are in progress or completed
102
- * @returns {Promise<LessonsByGenreResponse|null>} - The lessons for the genre
122
+ * @returns {Promise<GenreLessons|null>} - The lessons for the genre
103
123
  *
104
124
  * @example
105
125
  * fetchGenreLessons('Blues', 'drumeo', 'song', {'-published_on', '', 1, 10, ["difficulty,Intermediate"], [232168, 232824, 303375, 232194, 393125]})
@@ -113,15 +133,13 @@ export async function fetchGenreLessons(
113
133
  {
114
134
  sort = '-published_on',
115
135
  searchTerm = '',
116
- page = 1,
136
+ offset = 1,
117
137
  limit = 10,
118
138
  includedFields = [],
119
139
  progressIds = [],
120
- }: FetchGenreLessonsOptions = {}
121
- ): Promise<LessonsByGenreResponse | null> {
140
+ }: GenreLessonsOptions = {}
141
+ ): Promise<GenreLessons> {
122
142
  const fieldsString = getFieldsForContentType(contentType) as string
123
- const start = (page - 1) * limit
124
- const end = start + limit
125
143
  const searchFilter = searchTerm ? `&& title match "${searchTerm}*"` : ''
126
144
  const includedFieldsFilter = includedFields.length > 0 ? filtersToGroq(includedFields) : ''
127
145
  const addType = contentType ? `_type == '${contentType}' && ` : ''
@@ -131,10 +149,20 @@ export async function fetchGenreLessons(
131
149
  const filterWithRestrictions = await new FilterBuilder(filter).buildFilter()
132
150
 
133
151
  sort = getSortOrder(sort, brand)
134
- const query = buildDataAndTotalQuery(filterWithRestrictions, fieldsString, {
135
- sort: sort,
136
- start: start,
137
- end: end,
138
- })
139
- return fetchSanity(query, true, { processNeedAccess: false, processPageType: false })
152
+ const data = query()
153
+ .and(filterWithRestrictions)
154
+ .and(`brand == ${brand}`)
155
+ .order(sort)
156
+ .slice(offset, offset + limit)
157
+ .select(...(fieldsString ? [fieldsString] : []))
158
+ .build()
159
+
160
+ const total = query().and(filterWithRestrictions).build()
161
+
162
+ const q = `{
163
+ "data": ${data},
164
+ "total": ${total}
165
+ }`
166
+
167
+ return fetchSanity(q, true, { processNeedAccess: false, processPageType: false })
140
168
  }