musora-content-services 2.185.0 → 2.186.0
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/.agent/decisions/2026-10-01-agi-remove-limit-from-queries.md +60 -0
- package/.claude/settings.local.json +9 -14
- package/CHANGELOG.md +10 -0
- package/jest.config.js +0 -1
- package/package.json +1 -1
- package/src/index.d.ts +17 -2
- package/src/index.js +17 -2
- package/src/lib/ads/async-either.ts +148 -0
- package/src/lib/ads/coproduct.ts +36 -0
- package/src/lib/sanity/decorators/navigate-to.ts +20 -14
- package/src/lib/sanity/examples.ts +27 -31
- package/src/lib/sanity/filter.ts +62 -0
- package/src/lib/sanity/groq.ts +60 -19
- package/src/lib/sanity/runner.ts +3 -2
- package/src/lib/sanity/types/marketing.d.ts +33 -0
- package/src/services/content/artist.ts +4 -3
- package/src/services/content/genre.ts +4 -3
- package/src/services/content/instructor.ts +5 -4
- package/src/services/content.js +181 -191
- package/src/services/endScreen/endScreen.ts +1 -1
- package/src/services/marketing/promotions.ts +178 -0
- package/src/services/recommendations.ts +307 -0
- package/src/services/sanity.js +1 -1
- package/src/services/user/account.ts +23 -0
- package/src/version-info.js +1 -1
- package/.yarn/install-state.gz +0 -0
- package/dist/services/api/types.js +0 -2
- package/src/services/recommendations.js +0 -142
|
@@ -25,19 +25,20 @@ const publishedSongs = (brand: string) =>
|
|
|
25
25
|
f.combine(f.type('song'), f.statusIn(['published']), f.defined('railcontent_id'), f.brand(brand))
|
|
26
26
|
|
|
27
27
|
export async function fetchSongTitles(brand: string) {
|
|
28
|
-
const result = await groq()
|
|
28
|
+
const result = await groq<Song[]>()
|
|
29
29
|
.and(publishedSongs(brand))
|
|
30
30
|
.order('published_on desc')
|
|
31
31
|
.slice(0, 10)
|
|
32
32
|
.select('_id', 'title', 'railcontent_id')
|
|
33
|
-
.
|
|
33
|
+
.default([])
|
|
34
|
+
.run()
|
|
34
35
|
|
|
35
36
|
return result.fold(
|
|
36
37
|
(error) => {
|
|
37
38
|
console.error(`${error.name}: ${error.message}`, error.groq)
|
|
38
39
|
return []
|
|
39
40
|
},
|
|
40
|
-
(songs) => songs
|
|
41
|
+
(songs) => songs
|
|
41
42
|
)
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -105,27 +106,28 @@ interface Lesson {
|
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
export async function fetchLessonsWithoutPermissions(brand: string) {
|
|
108
|
-
const result = await groq().and(f.brand(brand)).slice(0, 10).
|
|
109
|
+
const result = await groq<Lesson[]>().and(f.brand(brand)).slice(0, 10).default([]).run()
|
|
109
110
|
|
|
110
111
|
return result
|
|
111
112
|
.map((lessons) =>
|
|
112
|
-
decorateAll<Lesson>(lessons
|
|
113
|
+
decorateAll<Lesson>(lessons, [pageTypeDecorator as FieldDecorator<Lesson>])
|
|
113
114
|
)
|
|
114
115
|
.recover([])
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
export async function fetchDecoratedLessons(brand: string) {
|
|
118
119
|
const [result, permissions] = await Promise.all([
|
|
119
|
-
groq()
|
|
120
|
+
groq<Lesson[]>()
|
|
120
121
|
.and(f.combine(f.typeIn(['course']), f.brand(brand)))
|
|
121
122
|
.slice(0, 10)
|
|
122
|
-
.
|
|
123
|
+
.default([])
|
|
124
|
+
.run(),
|
|
123
125
|
fetchUserPermissions(),
|
|
124
126
|
])
|
|
125
127
|
|
|
126
128
|
return result
|
|
127
129
|
.map((lessons) =>
|
|
128
|
-
decorateAll<Lesson>(lessons
|
|
130
|
+
decorateAll<Lesson>(lessons, [
|
|
129
131
|
accessDecorator(permissions) as FieldDecorator<Lesson>,
|
|
130
132
|
lifetimeUpgradeDecorator(permissions) as FieldDecorator<Lesson>,
|
|
131
133
|
pageTypeDecorator as FieldDecorator<Lesson>,
|
|
@@ -135,18 +137,15 @@ export async function fetchDecoratedLessons(brand: string) {
|
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
export async function fetchLessonsWithNavigation(brand: string) {
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
fetchUserPermissions(),
|
|
141
|
-
])
|
|
140
|
+
const lessons = groq<Lesson[]>().and(f.brand(brand)).slice(0, 10).default([]).run()
|
|
141
|
+
const permissions = await fetchUserPermissions()
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
return lessons
|
|
144
144
|
.map((lessons) =>
|
|
145
|
-
decorateAll<Lesson>(lessons
|
|
145
|
+
decorateAll<Lesson>(lessons, [accessDecorator(permissions) as FieldDecorator<Lesson>])
|
|
146
146
|
)
|
|
147
147
|
.mapAsync((lessons) => decorateNavigateTo(lessons) as Promise<Lesson[]>)
|
|
148
|
-
|
|
149
|
-
return decorated.recover([])
|
|
148
|
+
.recover([])
|
|
150
149
|
}
|
|
151
150
|
|
|
152
151
|
export async function fetchWithACustomDecorator(brand: string) {
|
|
@@ -155,9 +154,9 @@ export async function fetchWithACustomDecorator(brand: string) {
|
|
|
155
154
|
compute: (lesson) => lesson.permission_id === undefined,
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
const result = await groq().and(f.brand(brand)).
|
|
157
|
+
const result = await groq<Lesson[]>().and(f.brand(brand)).default([]).run()
|
|
159
158
|
|
|
160
|
-
return result.map((lessons) => decorateAll<Lesson>(lessons
|
|
159
|
+
return result.map((lessons) => decorateAll<Lesson>(lessons, [isFree])).recover([])
|
|
161
160
|
}
|
|
162
161
|
|
|
163
162
|
export async function fetchWhenPermissionsMayBeUnavailable(brand: string) {
|
|
@@ -167,7 +166,7 @@ export async function fetchWhenPermissionsMayBeUnavailable(brand: string) {
|
|
|
167
166
|
)
|
|
168
167
|
|
|
169
168
|
const [result, permissions] = await Promise.all([
|
|
170
|
-
groq().and(f.brand(brand)).
|
|
169
|
+
groq<Lesson[]>().and(f.brand(brand)).default([]).run(),
|
|
171
170
|
fetchUserPermissions()
|
|
172
171
|
.then(Either.right<SanityQueryError, UserPermissions>)
|
|
173
172
|
.catch(queryError),
|
|
@@ -176,7 +175,7 @@ export async function fetchWhenPermissionsMayBeUnavailable(brand: string) {
|
|
|
176
175
|
return result
|
|
177
176
|
.flatMap((lessons) =>
|
|
178
177
|
permissions.map((userPermissions) =>
|
|
179
|
-
decorateAll<Lesson>(lessons
|
|
178
|
+
decorateAll<Lesson>(lessons, [
|
|
180
179
|
accessDecorator(userPermissions) as FieldDecorator<Lesson>,
|
|
181
180
|
])
|
|
182
181
|
)
|
|
@@ -188,17 +187,14 @@ export async function fetchWhenPermissionsMayBeUnavailable(brand: string) {
|
|
|
188
187
|
}
|
|
189
188
|
|
|
190
189
|
export async function decorateManuallyForPreciseTypes(brand: string) {
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
fetchUserPermissions(),
|
|
194
|
-
])
|
|
190
|
+
const lessons = groq<Lesson[]>().and(f.brand(brand)).slice(0, 10).default([]).run()
|
|
191
|
+
const permissions = await fetchUserPermissions()
|
|
195
192
|
|
|
196
|
-
|
|
197
|
-
.map((lessons) => decorateAccess(lessons
|
|
193
|
+
return lessons
|
|
194
|
+
.map((lessons) => decorateAccess(lessons, permissions))
|
|
198
195
|
.mapAsync((lessons) => decorateNavigateTo(lessons))
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
)
|
|
196
|
+
.fold(
|
|
197
|
+
() => [],
|
|
198
|
+
(lessons) => lessons.map((lesson) => [lesson.need_access, lesson.navigateTo] as const)
|
|
199
|
+
)
|
|
204
200
|
}
|
package/src/lib/sanity/filter.ts
CHANGED
|
@@ -123,6 +123,15 @@ export class Filters {
|
|
|
123
123
|
return `slug.current == "${escapeDoubleQuoted(slug)}"`
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* @param {string[]} slugs - Array of slugs to filter by
|
|
128
|
+
* @returns {string} Filter expression
|
|
129
|
+
* @example Filters.slugIn(['a', 'b']) // "slug.current in ['a','b']"
|
|
130
|
+
*/
|
|
131
|
+
static slugIn(slugs: string[]): string {
|
|
132
|
+
return `slug.current in ${arrayToStringRepresentation(slugs)}`
|
|
133
|
+
}
|
|
134
|
+
|
|
126
135
|
/**
|
|
127
136
|
* @param {number} id - The railcontent_id to filter by
|
|
128
137
|
* @returns {string} Filter expression
|
|
@@ -218,6 +227,59 @@ export class Filters {
|
|
|
218
227
|
return `published_on >= "${escapeDoubleQuoted(date)}"`
|
|
219
228
|
}
|
|
220
229
|
|
|
230
|
+
/**
|
|
231
|
+
* @param {string} field - The date field to compare
|
|
232
|
+
* @param {string} date - ISO date string
|
|
233
|
+
* @returns {string} Filter expression
|
|
234
|
+
* @example Filters.dateBefore('ends_at', '2026-01-01') // 'ends_at <= "2026-01-01"'
|
|
235
|
+
*/
|
|
236
|
+
static dateBefore(field: string, date: string): string {
|
|
237
|
+
return `${field} <= "${escapeDoubleQuoted(date)}"`
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* @param {string} field - The date field to compare
|
|
242
|
+
* @param {string} date - ISO date string
|
|
243
|
+
* @returns {string} Filter expression
|
|
244
|
+
* @example Filters.dateAfter('starts_at', '2026-01-01') // 'starts_at >= "2026-01-01"'
|
|
245
|
+
*/
|
|
246
|
+
static dateAfter(field: string, date: string): string {
|
|
247
|
+
return `${field} >= "${escapeDoubleQuoted(date)}"`
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Matches documents whose window covers the given date.
|
|
252
|
+
*
|
|
253
|
+
* @param {string} startField - The field holding the start of the window
|
|
254
|
+
* @param {string} endField - The field holding the end of the window
|
|
255
|
+
* @param {string} date - ISO date string
|
|
256
|
+
* @returns {string} Filter expression
|
|
257
|
+
* @example Filters.dateWithin('starts_at', 'ends_at', '2026-11-25')
|
|
258
|
+
*/
|
|
259
|
+
static dateWithin(startField: string, endField: string, date: string): string {
|
|
260
|
+
return Filters.combine(Filters.dateBefore(startField, date), Filters.dateAfter(endField, date))
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @param {string} field - The boolean field to check
|
|
265
|
+
* @param {boolean} [value=true] - The value to match
|
|
266
|
+
* @returns {string} Filter expression
|
|
267
|
+
* @example Filters.flag('active') // "active == true"
|
|
268
|
+
*/
|
|
269
|
+
static flag(field: string, value: boolean = true): string {
|
|
270
|
+
return `${field} == ${value}`
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* @param {string} field - The field to compare
|
|
275
|
+
* @param {string} value - The value to match
|
|
276
|
+
* @returns {string} Filter expression
|
|
277
|
+
* @example Filters.fieldEquals('key', 'upgrade-banner') // 'key == "upgrade-banner"'
|
|
278
|
+
*/
|
|
279
|
+
static fieldEquals(field: string, value: string): string {
|
|
280
|
+
return `${field} == "${escapeDoubleQuoted(value)}"`
|
|
281
|
+
}
|
|
282
|
+
|
|
221
283
|
/**
|
|
222
284
|
* @param {string} field - The field to check
|
|
223
285
|
* @returns {string} Filter expression
|
package/src/lib/sanity/groq.ts
CHANGED
|
@@ -1,30 +1,54 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AsyncEither } from '../ads/async-either'
|
|
2
2
|
import { FieldAccess } from './field-access'
|
|
3
|
-
import { composite, query, QueryBuilder } from './query'
|
|
3
|
+
import { composite, query, QueryBuilder, QueryBuilderState } from './query'
|
|
4
4
|
import { QueryRunner, run, SanityQueryError } from './runner'
|
|
5
5
|
|
|
6
|
+
type ElementOf<T> = T extends (infer U)[] ? U : T
|
|
7
|
+
|
|
8
|
+
type Field<T> = (keyof ElementOf<T> & string) | (string & {})
|
|
9
|
+
|
|
6
10
|
export interface RunnableQuery {
|
|
7
11
|
build(): string
|
|
8
12
|
toString(): string
|
|
9
|
-
run<T>(runner?: QueryRunner<T>):
|
|
13
|
+
run<T>(runner?: QueryRunner<T>): AsyncEither<SanityQueryError, T | null>
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
selector(selector: string):
|
|
14
|
-
and(expr: string):
|
|
15
|
-
or(...exprs: string[]):
|
|
16
|
-
order(expr: string):
|
|
17
|
-
slice(offset: number, limit?: number):
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
interface Chainable<T, Self> {
|
|
17
|
+
selector(selector: string): Self
|
|
18
|
+
and(expr: string): Self
|
|
19
|
+
or(...exprs: string[]): Self
|
|
20
|
+
order(expr: string): Self
|
|
21
|
+
slice(offset: number, limit?: number): Self
|
|
22
|
+
select(...fields: Field<T>[]): Self
|
|
23
|
+
access(field: Field<T>, fieldAccess?: FieldAccess): Self
|
|
24
|
+
dereference(): Self
|
|
25
|
+
postFilter(expr: string): Self
|
|
26
|
+
build(): string
|
|
27
|
+
toString(): string
|
|
28
|
+
|
|
29
|
+
_state(): QueryBuilderState
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* `Empty` is what `run` resolves to when the query matched nothing. {@link GroqBuilder.default}
|
|
34
|
+
* supplies a fallback and narrows it to `never`, which is the state a builder is assumed to be in
|
|
35
|
+
* unless it says otherwise: a builder that has not been given one is a `GroqBuilder<T, null>`.
|
|
36
|
+
*/
|
|
37
|
+
export interface GroqBuilder<T = unknown, Empty = never> extends Chainable<T, GroqBuilder<T, Empty>> {
|
|
38
|
+
/**
|
|
39
|
+
* Never read. Methods are checked bivariantly, so without a property here a builder with no
|
|
40
|
+
* fallback would silently satisfy a signature promising one.
|
|
41
|
+
*/
|
|
42
|
+
readonly __empty?: Empty
|
|
43
|
+
|
|
44
|
+
first(): GroqBuilder<ElementOf<T>, Empty>
|
|
45
|
+
default(value: T): GroqBuilder<T, never>
|
|
46
|
+
run<U = T>(runner?: QueryRunner<U>): AsyncEither<SanityQueryError, U | Empty>
|
|
23
47
|
}
|
|
24
48
|
|
|
25
49
|
/**
|
|
26
50
|
* @param {string} selector
|
|
27
|
-
* @returns {GroqBuilder}
|
|
51
|
+
* @returns {GroqBuilder<T>}
|
|
28
52
|
* @example
|
|
29
53
|
* const result = await groq()
|
|
30
54
|
* .and(f.type('song'))
|
|
@@ -32,13 +56,30 @@ export interface GroqBuilder extends QueryBuilder, RunnableQuery {
|
|
|
32
56
|
* .slice(0, 10)
|
|
33
57
|
* .select('_id', 'title')
|
|
34
58
|
* .run<Song[]>()
|
|
59
|
+
* @example
|
|
60
|
+
* // naming the type up front keeps it off every call in the chain
|
|
61
|
+
* const songs = await groq<Song[]>().and(f.type('song')).default([]).run()
|
|
35
62
|
*/
|
|
36
|
-
const groqBuilder = (selector?: string): GroqBuilder => {
|
|
63
|
+
const groqBuilder = <T = unknown>(selector?: string): GroqBuilder<T, null> => {
|
|
37
64
|
const builder = query(selector)
|
|
65
|
+
let fallback: { value: unknown } | undefined
|
|
66
|
+
|
|
67
|
+
const runnable = Object.assign(builder, {
|
|
68
|
+
default: (value: unknown) => {
|
|
69
|
+
fallback = { value }
|
|
70
|
+
return runnable
|
|
71
|
+
},
|
|
72
|
+
run: <U = T>(runner?: QueryRunner<U>) => {
|
|
73
|
+
const result = run<U>(builder.build(), runner)
|
|
74
|
+
const fallbackAtRunTime = fallback
|
|
75
|
+
|
|
76
|
+
return fallbackAtRunTime
|
|
77
|
+
? result.map((value) => value ?? (fallbackAtRunTime.value as U))
|
|
78
|
+
: result
|
|
79
|
+
},
|
|
80
|
+
})
|
|
38
81
|
|
|
39
|
-
return
|
|
40
|
-
run: <T>(runner?: QueryRunner<T>) => run<T>(builder.build(), runner),
|
|
41
|
-
}) as GroqBuilder
|
|
82
|
+
return runnable as unknown as GroqBuilder<T, null>
|
|
42
83
|
}
|
|
43
84
|
|
|
44
85
|
/**
|
package/src/lib/sanity/runner.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncEither } from '../ads/async-either'
|
|
1
2
|
import { Either } from '../ads/either'
|
|
2
3
|
import { SanityClient } from '../../infrastructure/sanity/SanityClient'
|
|
3
4
|
|
|
@@ -44,7 +45,7 @@ const defaultRunner = <T>(): QueryRunner<T> =>
|
|
|
44
45
|
/**
|
|
45
46
|
* @param {string} groq
|
|
46
47
|
* @param {QueryRunner<T>} runner
|
|
47
|
-
* @returns {
|
|
48
|
+
* @returns {AsyncEither<SanityQueryError, T | null>}
|
|
48
49
|
* @example
|
|
49
50
|
* const result = await run<Song[]>(query().and(f.type('song')).build())
|
|
50
51
|
* result.fold(
|
|
@@ -61,4 +62,4 @@ const defaultRunner = <T>(): QueryRunner<T> =>
|
|
|
61
62
|
export const run = <T>(
|
|
62
63
|
groq: string,
|
|
63
64
|
runner: QueryRunner<T> = defaultRunner<T>()
|
|
64
|
-
):
|
|
65
|
+
): AsyncEither<SanityQueryError, T | null> => AsyncEither.of(runner(groq))
|
|
@@ -92,3 +92,36 @@ export type FaqDocument = {
|
|
|
92
92
|
answer?: string
|
|
93
93
|
}[]
|
|
94
94
|
}
|
|
95
|
+
|
|
96
|
+
export type PromotionalCampaignDocument = {
|
|
97
|
+
_id: string
|
|
98
|
+
_type: "promotional-campaign"
|
|
99
|
+
_createdAt: string
|
|
100
|
+
_updatedAt: string
|
|
101
|
+
_rev: string
|
|
102
|
+
/** Campaign Name */
|
|
103
|
+
name: string
|
|
104
|
+
slug: { _type: "slug"; current: string }
|
|
105
|
+
/** Starts At */
|
|
106
|
+
starts_at: string
|
|
107
|
+
/** Ends At */
|
|
108
|
+
ends_at: string
|
|
109
|
+
/** Active? */
|
|
110
|
+
active?: boolean
|
|
111
|
+
/** Placements */
|
|
112
|
+
placements: {
|
|
113
|
+
_key: string
|
|
114
|
+
/** Placement Key */
|
|
115
|
+
key: string
|
|
116
|
+
title?: string
|
|
117
|
+
body?: string
|
|
118
|
+
/** Resolved to a CDN url by the promotions query projection. */
|
|
119
|
+
dark_mode_image?: string
|
|
120
|
+
/** Resolved to a CDN url by the promotions query projection. */
|
|
121
|
+
light_mode_image?: string
|
|
122
|
+
/** CTA Label */
|
|
123
|
+
cta_label?: string
|
|
124
|
+
/** Dismissible? */
|
|
125
|
+
dismissible?: boolean
|
|
126
|
+
}[]
|
|
127
|
+
}
|
|
@@ -37,12 +37,11 @@ export async function fetchArtists(
|
|
|
37
37
|
): Promise<Artists> {
|
|
38
38
|
const type = f.type('artist')
|
|
39
39
|
const postFilter = `lesson_count > 0`
|
|
40
|
-
const { sort = 'lower(name)', offset = 0, limit
|
|
40
|
+
const { sort = 'lower(name)', offset = 0, limit } = options
|
|
41
41
|
|
|
42
42
|
const data = query()
|
|
43
43
|
.and(type)
|
|
44
44
|
.order(getSortOrder(sort, brand))
|
|
45
|
-
.slice(offset, limit)
|
|
46
45
|
.select(
|
|
47
46
|
'name',
|
|
48
47
|
`"slug": slug.current`,
|
|
@@ -51,6 +50,8 @@ export async function fetchArtists(
|
|
|
51
50
|
)
|
|
52
51
|
.postFilter(postFilter)
|
|
53
52
|
|
|
53
|
+
if (limit) data.slice(offset, limit)
|
|
54
|
+
|
|
54
55
|
const q = `{
|
|
55
56
|
"data": ${data},
|
|
56
57
|
}`
|
|
@@ -140,7 +141,7 @@ export async function fetchArtistLessons(
|
|
|
140
141
|
f.notDeprecated(),
|
|
141
142
|
f.referencesIDWithFilter(f.combine(f.type('artist'), f.slug(slug))),
|
|
142
143
|
f.brand(brand),
|
|
143
|
-
f.permissions({showMembershipRestrictedContent: true}),
|
|
144
|
+
f.permissions({ showMembershipRestrictedContent: true }),
|
|
144
145
|
f.searchMatch('title', searchTerm),
|
|
145
146
|
f.includedFields(includedFields),
|
|
146
147
|
f.progressIds(progressIds)
|
|
@@ -37,12 +37,11 @@ export async function fetchGenres(
|
|
|
37
37
|
): Promise<Genres> {
|
|
38
38
|
const type = f.type('genre')
|
|
39
39
|
const postFilter = `lesson_count > 0`
|
|
40
|
-
const { sort = 'lower(name)', offset = 0, limit
|
|
40
|
+
const { sort = 'lower(name)', offset = 0, limit } = options
|
|
41
41
|
|
|
42
42
|
const data = query()
|
|
43
43
|
.and(type)
|
|
44
44
|
.order(getSortOrder(sort, brand))
|
|
45
|
-
.slice(offset, limit)
|
|
46
45
|
.select(
|
|
47
46
|
'name',
|
|
48
47
|
`"slug": slug.current`,
|
|
@@ -51,6 +50,8 @@ export async function fetchGenres(
|
|
|
51
50
|
)
|
|
52
51
|
.postFilter(postFilter)
|
|
53
52
|
|
|
53
|
+
if (limit) data.slice(offset, limit)
|
|
54
|
+
|
|
54
55
|
const q = `{
|
|
55
56
|
"data": ${data},
|
|
56
57
|
}`
|
|
@@ -139,7 +140,7 @@ export async function fetchGenreLessons(
|
|
|
139
140
|
f.notDeprecated(),
|
|
140
141
|
f.referencesIDWithFilter(f.combine(f.type('genre'), f.slug(slug))),
|
|
141
142
|
f.brand(brand),
|
|
142
|
-
f.permissions({showMembershipRestrictedContent: true}),
|
|
143
|
+
f.permissions({ showMembershipRestrictedContent: true }),
|
|
143
144
|
f.searchMatch('title', searchTerm),
|
|
144
145
|
f.includedFields(includedFields),
|
|
145
146
|
f.progressIds(progressIds)
|
|
@@ -38,12 +38,11 @@ export async function fetchInstructors(
|
|
|
38
38
|
): Promise<Instructors> {
|
|
39
39
|
const type = f.type('instructor')
|
|
40
40
|
const postFilter = `lesson_count > 0`
|
|
41
|
-
const { sort = 'lower(name)', offset = 0, limit
|
|
41
|
+
const { sort = 'lower(name)', offset = 0, limit } = options
|
|
42
42
|
|
|
43
43
|
const data = query()
|
|
44
44
|
.and(type)
|
|
45
45
|
.order(getSortOrder(sort, brand))
|
|
46
|
-
.slice(offset, limit)
|
|
47
46
|
.select(
|
|
48
47
|
'name',
|
|
49
48
|
`"slug": slug.current`,
|
|
@@ -52,6 +51,8 @@ export async function fetchInstructors(
|
|
|
52
51
|
)
|
|
53
52
|
.postFilter(postFilter)
|
|
54
53
|
|
|
54
|
+
if (limit) data.slice(offset, limit)
|
|
55
|
+
|
|
55
56
|
const q = `{
|
|
56
57
|
"data": ${data},
|
|
57
58
|
}`
|
|
@@ -129,7 +130,7 @@ export async function fetchInstructorLessons(
|
|
|
129
130
|
offset = 0,
|
|
130
131
|
limit = 20,
|
|
131
132
|
includedFields = [],
|
|
132
|
-
contentType = null
|
|
133
|
+
contentType = null,
|
|
133
134
|
}: InstructorLessonsOptions = {}
|
|
134
135
|
): Promise<InstructorLessons> {
|
|
135
136
|
sort = getSortOrder(sort, brand)
|
|
@@ -140,7 +141,7 @@ export async function fetchInstructorLessons(
|
|
|
140
141
|
f.notDeprecated(),
|
|
141
142
|
f.referencesIDWithFilter(f.combine(f.type('instructor'), f.slug(slug))),
|
|
142
143
|
f.brand(brand),
|
|
143
|
-
f.permissions({showMembershipRestrictedContent: true}),
|
|
144
|
+
f.permissions({ showMembershipRestrictedContent: true }),
|
|
144
145
|
f.searchMatch('title', searchTerm),
|
|
145
146
|
f.includedFields(includedFields)
|
|
146
147
|
)
|