musora-content-services 2.95.2 → 2.95.3
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 +7 -0
- package/CLAUDE.md +0 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/package.json +1 -1
- package/src/constants/award-assets.js +0 -0
- package/src/contentMetaData.js +0 -0
- 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/ads/monoid.ts +5 -0
- package/src/lib/ads/semigroup.ts +3 -0
- package/src/lib/brands.ts +0 -0
- package/src/lib/httpHelper.js +0 -0
- package/src/lib/lastUpdated.js +0 -0
- package/src/lib/sanity/query.ts +125 -28
- package/src/services/api/types.js +0 -0
- package/src/services/api/types.ts +0 -0
- package/src/services/awards/award-callbacks.js +0 -0
- package/src/services/awards/award-query.js +0 -0
- package/src/services/awards/internal/.indexignore +0 -0
- package/src/services/awards/internal/award-definitions.js +0 -0
- package/src/services/awards/internal/award-events.js +0 -0
- package/src/services/awards/internal/award-manager.js +0 -0
- package/src/services/awards/internal/certificate-builder.js +0 -0
- package/src/services/awards/internal/completion-data-generator.js +0 -0
- package/src/services/awards/internal/content-progress-observer.js +0 -0
- package/src/services/awards/internal/image-utils.js +0 -0
- package/src/services/awards/internal/message-generator.js +0 -0
- package/src/services/awards/internal/types.js +0 -0
- package/src/services/awards/types.d.ts +0 -0
- package/src/services/awards/types.js +0 -0
- package/src/services/config.js +0 -0
- package/src/services/content/artist.ts +69 -37
- package/src/services/content/content.ts +0 -0
- package/src/services/content/genre.ts +67 -39
- package/src/services/content/instructor.ts +76 -41
- 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 +0 -0
- 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 +0 -0
- package/src/services/contentLikes.js +0 -0
- package/src/services/contentProgress.js +0 -0
- 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-events.js +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/reporting/README.md +0 -0
- package/src/services/reporting/reporting.ts +0 -0
- package/src/services/reporting/types.ts +0 -0
- package/src/services/sentry/.indexignore +0 -0
- package/src/services/sentry/index.ts +0 -0
- package/src/services/sync/.indexignore +0 -0
- package/src/services/sync/adapters/factory.ts +0 -0
- package/src/services/sync/adapters/lokijs.ts +0 -0
- package/src/services/sync/adapters/sqlite.ts +0 -0
- package/src/services/sync/concurrency-safety.ts +0 -0
- package/src/services/sync/context/index.ts +0 -0
- package/src/services/sync/context/providers/base.ts +0 -0
- package/src/services/sync/context/providers/connectivity.ts +0 -0
- package/src/services/sync/context/providers/durability.ts +0 -0
- package/src/services/sync/context/providers/index.ts +0 -0
- package/src/services/sync/context/providers/session.ts +0 -0
- package/src/services/sync/context/providers/tabs.ts +0 -0
- package/src/services/sync/context/providers/visibility.ts +0 -0
- package/src/services/sync/database/factory.ts +0 -0
- package/src/services/sync/errors/boundary.ts +0 -0
- package/src/services/sync/errors/index.ts +0 -0
- package/src/services/sync/fetch.ts +0 -0
- package/src/services/sync/index.ts +0 -0
- package/src/services/sync/manager.ts +0 -0
- package/src/services/sync/models/Base.ts +0 -0
- package/src/services/sync/models/ContentLike.ts +0 -0
- package/src/services/sync/models/ContentProgress.ts +0 -0
- package/src/services/sync/models/Practice.ts +0 -0
- package/src/services/sync/models/PracticeDayNote.ts +0 -0
- package/src/services/sync/models/UserAwardProgress.ts +0 -0
- package/src/services/sync/models/index.ts +0 -0
- package/src/services/sync/repositories/base.ts +0 -0
- package/src/services/sync/repositories/content-likes.ts +0 -0
- package/src/services/sync/repositories/content-progress.ts +0 -0
- package/src/services/sync/repositories/index.ts +0 -0
- package/src/services/sync/repositories/practice-day-notes.ts +0 -0
- package/src/services/sync/repositories/practices.ts +0 -0
- package/src/services/sync/repositories/user-award-progress.ts +0 -0
- package/src/services/sync/repository-proxy.ts +0 -0
- package/src/services/sync/resolver.ts +0 -0
- package/src/services/sync/retry.ts +0 -0
- package/src/services/sync/run-scope.ts +0 -0
- package/src/services/sync/schema/index.ts +0 -0
- package/src/services/sync/serializers/index.ts +0 -0
- package/src/services/sync/serializers/model.ts +0 -0
- package/src/services/sync/serializers/raw.ts +0 -0
- package/src/services/sync/store/index.ts +0 -0
- package/src/services/sync/store/push-coalescer.ts +0 -0
- package/src/services/sync/store-configs.ts +0 -0
- package/src/services/sync/strategies/base.ts +0 -0
- package/src/services/sync/strategies/index.ts +0 -0
- package/src/services/sync/strategies/initial.ts +0 -0
- package/src/services/sync/strategies/polling.ts +0 -0
- package/src/services/sync/telemetry/index.ts +0 -0
- package/src/services/sync/telemetry/sampling.ts +0 -0
- package/src/services/sync/utils/event-emitter.ts +0 -0
- package/src/services/sync/utils/index.ts +0 -0
- package/src/services/sync/utils/throttle.ts +0 -0
- package/src/services/sync/utils/timers.ts +0 -0
- 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 +0 -0
- 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 +0 -0
- package/test/HttpClient.test.js +0 -0
- package/test/awards/award-alacarte-observer.test.js +0 -0
- package/test/awards/award-auto-refresh.test.js +0 -0
- package/test/awards/award-calculations.test.js +0 -0
- package/test/awards/award-certificate-display.test.js +0 -0
- package/test/awards/award-collection-edge-cases.test.js +0 -0
- package/test/awards/award-collection-filtering.test.js +0 -0
- package/test/awards/award-completion-flow.test.js +0 -0
- package/test/awards/award-exclusion-handling.test.js +0 -0
- package/test/awards/award-multi-lesson.test.js +0 -0
- package/test/awards/award-observer-integration.test.js +0 -0
- package/test/awards/award-query-messages.test.js +0 -0
- package/test/awards/award-user-collection.test.js +0 -0
- package/test/awards/duplicate-prevention.test.js +0 -0
- package/test/awards/helpers/completion-mock.js +0 -0
- package/test/awards/helpers/index.js +0 -0
- package/test/awards/helpers/mock-setup.js +0 -0
- package/test/awards/helpers/progress-emitter.js +0 -0
- package/test/awards/message-generator.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/award-definitions.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/sync/models/award-database-integration.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 -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,13 @@
|
|
|
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.3](https://github.com/railroadmedia/musora-content-services/compare/v2.95.2...v2.95.3) (2025-12-08)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* make agi responses standard ([#630](https://github.com/railroadmedia/musora-content-services/issues/630)) ([0301133](https://github.com/railroadmedia/musora-content-services/commit/03011331cb835412af80111a97e6ae83fd6e56d5))
|
|
11
|
+
|
|
5
12
|
### [2.95.2](https://github.com/railroadmedia/musora-content-services/compare/v2.95.1...v2.95.2) (2025-12-08)
|
|
6
13
|
|
|
7
14
|
### [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
|
File without changes
|
package/src/contentMetaData.js
CHANGED
|
File without changes
|
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/brands.ts
CHANGED
|
File without changes
|
package/src/lib/httpHelper.js
CHANGED
|
File without changes
|
package/src/lib/lastUpdated.js
CHANGED
|
File without changes
|
package/src/lib/sanity/query.ts
CHANGED
|
@@ -1,30 +1,127 @@
|
|
|
1
|
+
import { Monoid } from '../ads/monoid'
|
|
2
|
+
import { Semigroup } from '../ads/semigroup'
|
|
3
|
+
|
|
1
4
|
export interface BuildQueryOptions {
|
|
2
|
-
sort
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
filter: string
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
package/src/services/config.js
CHANGED
|
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 {
|
|
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(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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<
|
|
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
|
-
|
|
137
|
+
offset = 1,
|
|
114
138
|
limit = 10,
|
|
115
139
|
includedFields = [],
|
|
116
140
|
progressIds = [],
|
|
117
141
|
}: ArtistLessonOptions = {}
|
|
118
|
-
): Promise<
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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 {
|
|
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(
|
|
30
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
'
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
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
|
|
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<
|
|
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
|
-
|
|
136
|
+
offset = 1,
|
|
117
137
|
limit = 10,
|
|
118
138
|
includedFields = [],
|
|
119
139
|
progressIds = [],
|
|
120
|
-
}:
|
|
121
|
-
): Promise<
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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
|
}
|