musora-content-services 2.136.4 → 2.137.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/docs.js.yml +5 -18
- package/CHANGELOG.md +22 -0
- package/package.json +1 -1
- package/src/contentTypeConfig.js +40 -44
- package/src/index.d.ts +6 -0
- package/src/index.js +6 -0
- package/src/services/awards/award-callbacks.js +1 -0
- package/src/services/content-org/learning-paths.ts +4 -2
- package/src/services/content.js +3 -0
- package/src/services/imageSRCBuilder.js +2 -3
- package/src/services/progress-row/rows/method-card.js +119 -76
- package/src/services/sanity.js +13 -2
- package/src/services/sync/manager.ts +2 -4
- package/src/services/user/memberships.ts +27 -1
- package/.github/workflows/node.js.yml +0 -39
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
name: Deploy Docs to GitHub Pages
|
|
2
2
|
on:
|
|
3
3
|
push:
|
|
4
|
-
branches: [main
|
|
4
|
+
branches: [main]
|
|
5
5
|
|
|
6
6
|
# Required permissions for GitHub Pages deployment
|
|
7
7
|
permissions:
|
|
@@ -28,37 +28,24 @@ jobs:
|
|
|
28
28
|
ref: main
|
|
29
29
|
path: main-content
|
|
30
30
|
|
|
31
|
-
- name: Checkout project-v2 branch
|
|
32
|
-
uses: actions/checkout@v4
|
|
33
|
-
with:
|
|
34
|
-
ref: project-v2
|
|
35
|
-
path: v2-content
|
|
36
|
-
|
|
37
31
|
- name: Setup Node.js
|
|
38
32
|
uses: actions/setup-node@v4
|
|
39
33
|
with:
|
|
40
34
|
node-version: '20'
|
|
41
35
|
|
|
42
36
|
- name: Install dependencies for v2
|
|
43
|
-
working-directory:
|
|
37
|
+
working-directory: main-content
|
|
44
38
|
run: npm ci
|
|
45
39
|
|
|
46
40
|
- name: Generate v2 documentation
|
|
47
|
-
working-directory:
|
|
41
|
+
working-directory: main-content
|
|
48
42
|
run: npm run doc
|
|
49
43
|
|
|
50
44
|
- name: Combine v1 and v2 docs into deployment structure
|
|
51
45
|
run: |
|
|
52
46
|
mkdir -p _site
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
cp -r main-content/docs/* _site/
|
|
56
|
-
echo "✅ Copied existing v1 docs from main branch"
|
|
57
|
-
fi
|
|
58
|
-
# Copy v2 docs to /v2/ subdirectory
|
|
59
|
-
mkdir -p _site/v2
|
|
60
|
-
cp -r v2-content/docs/* _site/v2/
|
|
61
|
-
echo "✅ Combined v1 (root) and v2 (/v2/) documentation"
|
|
47
|
+
cp -r main-content/docs/* _site/
|
|
48
|
+
echo "✅ Copied existing docs from main branch"
|
|
62
49
|
|
|
63
50
|
- name: Setup GitHub Pages
|
|
64
51
|
uses: actions/configure-pages@v4
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
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.137.1](https://github.com/railroadmedia/musora-content-services/compare/v2.137.0...v2.137.1) (2026-02-25)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **T3PS-1975:** Add functions for sub platform request ([dea8900](https://github.com/railroadmedia/musora-content-services/commit/dea89007b67fe3dfdffaa45feb9f4f0af8a42123))
|
|
11
|
+
* **T3PS-2159:** Add hasCertificate to award object in callback ([f742b8e](https://github.com/railroadmedia/musora-content-services/commit/f742b8e32556854518316f51003134544421eb8c))
|
|
12
|
+
* unset syncmanager instance late ([#830](https://github.com/railroadmedia/musora-content-services/issues/830)) ([a908c03](https://github.com/railroadmedia/musora-content-services/commit/a908c03bd4de1ec4b1020ef63b1eeab7eeff6bea))
|
|
13
|
+
|
|
14
|
+
## [2.137.0](https://github.com/railroadmedia/musora-content-services/compare/v2.136.4...v2.137.0) (2026-02-24)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* add collection data in fetch sibling content ([#836](https://github.com/railroadmedia/musora-content-services/issues/836)) ([6472764](https://github.com/railroadmedia/musora-content-services/commit/6472764b41da98b0754aec91b4714b70444f8b84))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* **T3PS-2156:** changes what is shown on Your Progress page (lessons) ([#821](https://github.com/railroadmedia/musora-content-services/issues/821)) ([58cfc1b](https://github.com/railroadmedia/musora-content-services/commit/58cfc1b6660471fcb50ebfe15865a9b69c8a8830))
|
|
25
|
+
* **T3PS-2273:** Add function for retrieving latest subscription platform ([c3e74ce](https://github.com/railroadmedia/musora-content-services/commit/c3e74cecb43619d69912a04644e01ffbb0ff4cc1))
|
|
26
|
+
|
|
5
27
|
### [2.136.4](https://github.com/railroadmedia/musora-content-services/compare/v2.136.3...v2.136.4) (2026-02-20)
|
|
6
28
|
|
|
7
29
|
|
package/package.json
CHANGED
package/src/contentTypeConfig.js
CHANGED
|
@@ -22,6 +22,10 @@ export const SINGLE_PARENT_TYPES = ['course-lesson', 'pack-bundle-lesson', 'song
|
|
|
22
22
|
|
|
23
23
|
export const LEARNING_PATH_LESSON = 'learning-path-lesson-v2'
|
|
24
24
|
|
|
25
|
+
export const parentField = 'parent_content_data[0]'
|
|
26
|
+
|
|
27
|
+
export const grandParentField = 'parent_content_data[1]'
|
|
28
|
+
|
|
25
29
|
export const genreField = `genre[]->{
|
|
26
30
|
name,
|
|
27
31
|
'slug': slug.current,
|
|
@@ -224,11 +228,7 @@ export const individualLessonsTypes = [
|
|
|
224
228
|
...studentArchivesLessonTypes,
|
|
225
229
|
]
|
|
226
230
|
|
|
227
|
-
export const coursesLessonTypes = [
|
|
228
|
-
'course',
|
|
229
|
-
'course-collection',
|
|
230
|
-
'guided-course',
|
|
231
|
-
]
|
|
231
|
+
export const coursesLessonTypes = ['course', 'course-collection', 'guided-course']
|
|
232
232
|
|
|
233
233
|
export const skillLessonTypes = ['skill-pack']
|
|
234
234
|
|
|
@@ -246,7 +246,7 @@ export const collectionLessonTypes = [...coursesLessonTypes]
|
|
|
246
246
|
|
|
247
247
|
export const lessonTypesMapping = {
|
|
248
248
|
lessons: singleLessonTypes,
|
|
249
|
-
'practice alongs': [
|
|
249
|
+
'practice alongs': [...practiceAlongsLessonTypes, 'routine'],
|
|
250
250
|
'live archives': liveArchivesLessonTypes,
|
|
251
251
|
performances: performancesLessonTypes,
|
|
252
252
|
'student archives': studentArchivesLessonTypes,
|
|
@@ -272,7 +272,7 @@ export const lessonTypesMapping = {
|
|
|
272
272
|
...studentArchivesLessonTypes,
|
|
273
273
|
...practiceAlongsLessonTypes,
|
|
274
274
|
],
|
|
275
|
-
routines: ['routine']
|
|
275
|
+
routines: ['routine'],
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
export const getNextLessonLessonParentTypes = [
|
|
@@ -294,7 +294,7 @@ export const progressTypesMapping = {
|
|
|
294
294
|
'documentary-lesson',
|
|
295
295
|
'live',
|
|
296
296
|
'course-lesson',
|
|
297
|
-
'routine'
|
|
297
|
+
'routine',
|
|
298
298
|
],
|
|
299
299
|
course: ['course'],
|
|
300
300
|
show: showsLessonTypes,
|
|
@@ -326,7 +326,7 @@ export const filterTypes = {
|
|
|
326
326
|
...coursesLessonTypes,
|
|
327
327
|
...skillLessonTypes,
|
|
328
328
|
...entertainmentLessonTypes,
|
|
329
|
-
'routine'
|
|
329
|
+
'routine',
|
|
330
330
|
],
|
|
331
331
|
songs: [
|
|
332
332
|
...tutorialsLessonTypes,
|
|
@@ -336,26 +336,22 @@ export const filterTypes = {
|
|
|
336
336
|
],
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
const lessonRecentTypes = [
|
|
340
|
+
...individualLessonsTypes,
|
|
341
|
+
'skill-pack-lesson',
|
|
342
|
+
...entertainmentLessonTypes,
|
|
343
|
+
'course-lesson',
|
|
344
|
+
'guided-course-lesson',
|
|
345
|
+
'quick-tips',
|
|
346
|
+
'routine',
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
const songsRecentTypes = [...SONG_TYPES]
|
|
350
|
+
|
|
339
351
|
export const recentTypes = {
|
|
340
|
-
lessons:
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
...entertainmentLessonTypes,
|
|
344
|
-
'course-lesson',
|
|
345
|
-
'guided-course-lesson',
|
|
346
|
-
'quick-tips',
|
|
347
|
-
'routine'
|
|
348
|
-
],
|
|
349
|
-
songs: [...SONG_TYPES],
|
|
350
|
-
home: [
|
|
351
|
-
...individualLessonsTypes,
|
|
352
|
-
...transcriptionsLessonTypes,
|
|
353
|
-
...playAlongLessonTypes,
|
|
354
|
-
...showsLessonTypes,
|
|
355
|
-
...getNextLessonLessonParentTypes,
|
|
356
|
-
'live',
|
|
357
|
-
'routine'
|
|
358
|
-
],
|
|
352
|
+
lessons: lessonRecentTypes,
|
|
353
|
+
songs: songsRecentTypes,
|
|
354
|
+
home: [...lessonRecentTypes, ...songsRecentTypes],
|
|
359
355
|
}
|
|
360
356
|
|
|
361
357
|
export const ownedContentTypes = {
|
|
@@ -694,7 +690,7 @@ export function getNewReleasesTypes(brand) {
|
|
|
694
690
|
'song',
|
|
695
691
|
'play-along',
|
|
696
692
|
'course',
|
|
697
|
-
'skill-pack'
|
|
693
|
+
'skill-pack',
|
|
698
694
|
]
|
|
699
695
|
switch (brand) {
|
|
700
696
|
case 'drumeo':
|
|
@@ -980,29 +976,29 @@ export const getFormattedType = (type, brand) => {
|
|
|
980
976
|
|
|
981
977
|
export const awardTemplate = {
|
|
982
978
|
drumeo: {
|
|
983
|
-
front:
|
|
984
|
-
rear:
|
|
985
|
-
unearned:
|
|
979
|
+
front: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/drumeo.svg',
|
|
980
|
+
rear: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/drumeo-rear.svg',
|
|
981
|
+
unearned: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/drumeo-unearned.svg',
|
|
986
982
|
},
|
|
987
983
|
guitareo: {
|
|
988
|
-
front:
|
|
989
|
-
rear:
|
|
990
|
-
unearned:
|
|
984
|
+
front: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/guitareo.svg',
|
|
985
|
+
rear: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/guitareo-rear.svg',
|
|
986
|
+
unearned: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/guitareo-unearned.svg',
|
|
991
987
|
},
|
|
992
988
|
pianote: {
|
|
993
|
-
front:
|
|
994
|
-
rear:
|
|
995
|
-
unearned:
|
|
989
|
+
front: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/pianote.svg',
|
|
990
|
+
rear: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/pianote-rear.svg',
|
|
991
|
+
unearned: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/pianote-unearned.svg',
|
|
996
992
|
},
|
|
997
993
|
singeo: {
|
|
998
|
-
front:
|
|
999
|
-
rear:
|
|
1000
|
-
unearned:
|
|
994
|
+
front: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/singeo.svg',
|
|
995
|
+
rear: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/singeo-rear.svg',
|
|
996
|
+
unearned: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/singeo-unearned.svg',
|
|
1001
997
|
},
|
|
1002
998
|
playbass: {
|
|
1003
|
-
front:
|
|
1004
|
-
rear:
|
|
1005
|
-
unearned:
|
|
999
|
+
front: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/playbass.svg',
|
|
1000
|
+
rear: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/playbass-rear.svg',
|
|
1001
|
+
unearned: 'https://d3fzm1tzeyr5n3.cloudfront.net/v2/awards/playbass-unearned.svg',
|
|
1006
1002
|
},
|
|
1007
1003
|
musora: {
|
|
1008
1004
|
front: null,
|
package/src/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
getAwardStatistics,
|
|
10
|
+
getBadgeFields,
|
|
10
11
|
getCompletedAwards,
|
|
11
12
|
getContentAwards,
|
|
12
13
|
getContentAwardsByIds,
|
|
@@ -369,6 +370,8 @@ import {
|
|
|
369
370
|
} from './services/user/management.js';
|
|
370
371
|
|
|
371
372
|
import {
|
|
373
|
+
fetchHasActivePlatformSubscription,
|
|
374
|
+
fetchLastSubscriptionPlatform,
|
|
372
375
|
fetchMemberships,
|
|
373
376
|
fetchRechargeTokens,
|
|
374
377
|
getUpgradePrice,
|
|
@@ -523,11 +526,13 @@ declare module 'musora-content-services' {
|
|
|
523
526
|
fetchGenreBySlug,
|
|
524
527
|
fetchGenreLessons,
|
|
525
528
|
fetchGenres,
|
|
529
|
+
fetchHasActivePlatformSubscription,
|
|
526
530
|
fetchHierarchy,
|
|
527
531
|
fetchInstructorBySlug,
|
|
528
532
|
fetchInstructorLessons,
|
|
529
533
|
fetchInstructors,
|
|
530
534
|
fetchInterests,
|
|
535
|
+
fetchLastSubscriptionPlatform,
|
|
531
536
|
fetchLatestThreads,
|
|
532
537
|
fetchLearningPathHierarchy,
|
|
533
538
|
fetchLearningPathLessons,
|
|
@@ -604,6 +609,7 @@ declare module 'musora-content-services' {
|
|
|
604
609
|
getAllStarted,
|
|
605
610
|
getAllStartedOrCompleted,
|
|
606
611
|
getAwardStatistics,
|
|
612
|
+
getBadgeFields,
|
|
607
613
|
getCompletedAwards,
|
|
608
614
|
getContentAwards,
|
|
609
615
|
getContentAwardsByIds,
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
13
|
getAwardStatistics,
|
|
14
|
+
getBadgeFields,
|
|
14
15
|
getCompletedAwards,
|
|
15
16
|
getContentAwards,
|
|
16
17
|
getContentAwardsByIds,
|
|
@@ -373,6 +374,8 @@ import {
|
|
|
373
374
|
} from './services/user/management.js';
|
|
374
375
|
|
|
375
376
|
import {
|
|
377
|
+
fetchHasActivePlatformSubscription,
|
|
378
|
+
fetchLastSubscriptionPlatform,
|
|
376
379
|
fetchMemberships,
|
|
377
380
|
fetchRechargeTokens,
|
|
378
381
|
getUpgradePrice,
|
|
@@ -522,11 +525,13 @@ export {
|
|
|
522
525
|
fetchGenreBySlug,
|
|
523
526
|
fetchGenreLessons,
|
|
524
527
|
fetchGenres,
|
|
528
|
+
fetchHasActivePlatformSubscription,
|
|
525
529
|
fetchHierarchy,
|
|
526
530
|
fetchInstructorBySlug,
|
|
527
531
|
fetchInstructorLessons,
|
|
528
532
|
fetchInstructors,
|
|
529
533
|
fetchInterests,
|
|
534
|
+
fetchLastSubscriptionPlatform,
|
|
530
535
|
fetchLatestThreads,
|
|
531
536
|
fetchLearningPathHierarchy,
|
|
532
537
|
fetchLearningPathLessons,
|
|
@@ -603,6 +608,7 @@ export {
|
|
|
603
608
|
getAllStarted,
|
|
604
609
|
getAllStartedOrCompleted,
|
|
605
610
|
getAwardStatistics,
|
|
611
|
+
getBadgeFields,
|
|
606
612
|
getCompletedAwards,
|
|
607
613
|
getContentAwards,
|
|
608
614
|
getContentAwardsByIds,
|
|
@@ -76,6 +76,7 @@ export function registerAwardCallback(callback) {
|
|
|
76
76
|
brand: definition.brand,
|
|
77
77
|
...getBadgeFields(definition),
|
|
78
78
|
contentType: definition.content_type,
|
|
79
|
+
hasCertificate: definition.type === 'content-award',
|
|
79
80
|
completedAt: completionData.completed_at,
|
|
80
81
|
isCompleted: true,
|
|
81
82
|
completionData: {
|
|
@@ -275,10 +275,10 @@ export async function getLearningPathLessonsByIds(contentIds, learningPathId) {
|
|
|
275
275
|
* @param options.parentContentId
|
|
276
276
|
*/
|
|
277
277
|
export function mapContentToParent(
|
|
278
|
-
|
|
278
|
+
lessons: any,
|
|
279
279
|
options?: { lessonType?: string; parentContentId?: number }
|
|
280
280
|
) {
|
|
281
|
-
if (!lessons) return lessons
|
|
281
|
+
if (!lessons || (Array.isArray(lessons) && lessons.length === 0)) return lessons
|
|
282
282
|
|
|
283
283
|
function mapIt(lesson: any) {
|
|
284
284
|
const mappedLesson = { ...lesson }
|
|
@@ -342,6 +342,8 @@ export async function fetchLearningPathLessons(
|
|
|
342
342
|
userDate: Date
|
|
343
343
|
) {
|
|
344
344
|
const learningPath = await getEnrichedLearningPath(learningPathId)
|
|
345
|
+
if (!learningPath || learningPath.children?.length === 0) return null
|
|
346
|
+
|
|
345
347
|
let dailySession = (await getDailySession(brand, userDate)) as DailySessionResponse
|
|
346
348
|
|
|
347
349
|
const isActiveLearningPath = (dailySession?.active_learning_path_id || 0) == learningPathId
|
package/src/services/content.js
CHANGED
|
@@ -93,6 +93,9 @@ export async function getTabResults(brand, pageName, tabName, {
|
|
|
93
93
|
sort = 'recommended',
|
|
94
94
|
selectedFilters = []
|
|
95
95
|
} = {}) {
|
|
96
|
+
|
|
97
|
+
if (!tabName && ['lessons', 'songs'].includes(pageName)) return { type: TabResponseType.CATALOG, data: [], meta: { filters: [], sort: {} } }
|
|
98
|
+
|
|
96
99
|
// Extract and handle 'progress' filter separately
|
|
97
100
|
const progressFilter = selectedFilters.find(f => f.startsWith('progress,')) || 'progress,all';
|
|
98
101
|
const progressValue = progressFilter.split(',')[1].toLowerCase();
|
|
@@ -60,14 +60,13 @@ export function buildImageSRC(url, options = {}) {
|
|
|
60
60
|
* @private
|
|
61
61
|
*/
|
|
62
62
|
export function applySanityTransformations(url, options) {
|
|
63
|
-
const { width, height
|
|
63
|
+
const { width, height } = options
|
|
64
64
|
|
|
65
|
-
const sanityOptions = ['
|
|
65
|
+
const sanityOptions = ['q=100']
|
|
66
66
|
|
|
67
67
|
// Dimensions
|
|
68
68
|
if (width) sanityOptions.push(`w=${width}`)
|
|
69
69
|
if (height) sanityOptions.push(`h=${height}`)
|
|
70
|
-
if (quality) sanityOptions.push(`q=${quality}`)
|
|
71
70
|
|
|
72
71
|
// Add parameters to Sanity URL
|
|
73
72
|
const sanityQuery = sanityOptions.length > 0 ? `?${sanityOptions.join('&')}` : ''
|
|
@@ -8,15 +8,31 @@ import { getProgressState } from '../../contentProgress'
|
|
|
8
8
|
import {COLLECTION_TYPE, STATE} from '../../sync/models/ContentProgress'
|
|
9
9
|
|
|
10
10
|
export async function getMethodCard(brand) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
let introVideo
|
|
12
|
+
try {
|
|
13
|
+
introVideo = await fetchMethodV2IntroVideo(brand)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error('Error fetching method intro video:', error)
|
|
14
16
|
return null
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
if (!introVideo) return null
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
let introVideoProgressState
|
|
22
|
+
try {
|
|
23
|
+
introVideoProgressState = await getProgressState(introVideo?.id)
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error('Error fetching progress state for method intro video:', error)
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let activeLearningPath
|
|
30
|
+
try {
|
|
31
|
+
activeLearningPath = await getActivePath(brand)
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Error fetching active learning path:', error)
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
20
36
|
|
|
21
37
|
if (introVideoProgressState !== STATE.COMPLETED || !activeLearningPath) {
|
|
22
38
|
const timestamp = Math.floor(Date.now())
|
|
@@ -41,76 +57,21 @@ export async function getMethodCard(brand) {
|
|
|
41
57
|
progressTimestamp: timestamp,
|
|
42
58
|
}
|
|
43
59
|
} else {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
let learningPath
|
|
61
|
+
try {
|
|
62
|
+
learningPath = await fetchLearningPathLessons(
|
|
63
|
+
activeLearningPath.active_learning_path_id,
|
|
64
|
+
brand,
|
|
65
|
+
new Date()
|
|
66
|
+
)
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.error('Failed to fetch learning path lessons', e)
|
|
51
69
|
return null
|
|
52
70
|
}
|
|
53
71
|
|
|
54
|
-
|
|
55
|
-
const allDailies = [
|
|
56
|
-
...learningPath.previous_learning_path_dailies,
|
|
57
|
-
...learningPath.learning_path_dailies,
|
|
58
|
-
...learningPath.next_learning_path_dailies
|
|
59
|
-
]
|
|
60
|
-
|
|
61
|
-
let allDailiesCompleted = true;
|
|
62
|
-
let anyDailiesStarted = false;
|
|
63
|
-
let noDailiesStarted = true;
|
|
64
|
-
let nextIncompleteDaily = null;
|
|
65
|
-
|
|
66
|
-
for (const lesson of allDailies) {
|
|
67
|
-
switch (lesson.progressStatus) {
|
|
68
|
-
case STATE.COMPLETED:
|
|
69
|
-
anyDailiesStarted = true;
|
|
70
|
-
noDailiesStarted = false;
|
|
71
|
-
break;
|
|
72
|
-
case STATE.STARTED:
|
|
73
|
-
anyDailiesStarted = true;
|
|
74
|
-
noDailiesStarted = false;
|
|
75
|
-
allDailiesCompleted = false;
|
|
76
|
-
if (!nextIncompleteDaily) {
|
|
77
|
-
nextIncompleteDaily = lesson;
|
|
78
|
-
}
|
|
79
|
-
break;
|
|
80
|
-
default:
|
|
81
|
-
allDailiesCompleted = false;
|
|
82
|
-
if (!nextIncompleteDaily) {
|
|
83
|
-
nextIncompleteDaily = lesson;
|
|
84
|
-
}
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
if (!allDailiesCompleted && anyDailiesStarted && nextIncompleteDaily) {
|
|
88
|
-
break;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
72
|
+
if (!learningPath) return null
|
|
91
73
|
|
|
92
|
-
|
|
93
|
-
const nextLesson = [
|
|
94
|
-
...learningPath?.upcoming_lessons,
|
|
95
|
-
...learningPath?.next_learning_path_dailies,
|
|
96
|
-
]?.find((lesson) => lesson.progressStatus !== STATE.COMPLETED)
|
|
97
|
-
|
|
98
|
-
let ctaText, action
|
|
99
|
-
if (noDailiesStarted) {
|
|
100
|
-
ctaText = 'Start Session'
|
|
101
|
-
action = getMethodActionCTA(nextIncompleteDaily)
|
|
102
|
-
} else if (anyDailiesStarted && !allDailiesCompleted) {
|
|
103
|
-
ctaText = 'Continue Session'
|
|
104
|
-
action = getMethodActionCTA(nextIncompleteDaily)
|
|
105
|
-
} else if (allDailiesCompleted) {
|
|
106
|
-
ctaText = nextLesson ? 'Start Next Lesson' : 'Browse Lessons'
|
|
107
|
-
action = nextLesson
|
|
108
|
-
? getMethodActionCTA(nextLesson)
|
|
109
|
-
: {
|
|
110
|
-
type: 'method-complete',
|
|
111
|
-
brand,
|
|
112
|
-
}
|
|
113
|
-
}
|
|
74
|
+
const { ctaText, action } = getCtaAndText(learningPath)
|
|
114
75
|
|
|
115
76
|
let maxProgressTimestamp = Math.max(
|
|
116
77
|
...learningPath?.children.map((lesson) => lesson.progressTimestamp)
|
|
@@ -139,10 +100,92 @@ export async function getMethodCard(brand) {
|
|
|
139
100
|
|
|
140
101
|
function getMethodActionCTA(item) {
|
|
141
102
|
return {
|
|
142
|
-
type: item.type,
|
|
143
|
-
brand: item.brand,
|
|
144
|
-
id: item.id,
|
|
145
|
-
slug: item.slug,
|
|
146
|
-
parent_id: item.parent_id,
|
|
103
|
+
type: item.type ?? null,
|
|
104
|
+
brand: item.brand ?? null,
|
|
105
|
+
id: item.id ?? null,
|
|
106
|
+
slug: item.slug ?? null,
|
|
107
|
+
parent_id: item.parent_id ?? null,
|
|
147
108
|
}
|
|
148
109
|
}
|
|
110
|
+
|
|
111
|
+
function getCtaAndText(learningPath) {
|
|
112
|
+
const {
|
|
113
|
+
allDailiesCompleted,
|
|
114
|
+
anyDailiesStarted,
|
|
115
|
+
noDailiesStarted,
|
|
116
|
+
nextIncompleteDaily
|
|
117
|
+
} = analyzeDailySession(learningPath)
|
|
118
|
+
|
|
119
|
+
// get the first incomplete lesson from upcoming and next learning path lessons
|
|
120
|
+
const nextLesson = [
|
|
121
|
+
...learningPath?.upcoming_lessons,
|
|
122
|
+
...learningPath?.next_learning_path_dailies,
|
|
123
|
+
]?.find((lesson) => lesson.progressStatus !== STATE.COMPLETED)
|
|
124
|
+
|
|
125
|
+
let ctaText, action
|
|
126
|
+
if (noDailiesStarted) {
|
|
127
|
+
ctaText = 'Start Session'
|
|
128
|
+
action = getMethodActionCTA(nextIncompleteDaily)
|
|
129
|
+
} else if (anyDailiesStarted && !allDailiesCompleted) {
|
|
130
|
+
ctaText = 'Continue Session'
|
|
131
|
+
action = getMethodActionCTA(nextIncompleteDaily)
|
|
132
|
+
} else if (allDailiesCompleted) {
|
|
133
|
+
ctaText = nextLesson ? 'Start Next Lesson' : 'Browse Lessons'
|
|
134
|
+
action = nextLesson
|
|
135
|
+
? getMethodActionCTA(nextLesson)
|
|
136
|
+
: {
|
|
137
|
+
type: 'method-complete',
|
|
138
|
+
brand: learningPath.brand,
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return { action, ctaText }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
function analyzeDailySession(learningPath) {
|
|
147
|
+
const allDailies = [
|
|
148
|
+
...learningPath.previous_learning_path_dailies,
|
|
149
|
+
...learningPath.learning_path_dailies,
|
|
150
|
+
...learningPath.next_learning_path_dailies
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
let allDailiesCompleted = true;
|
|
154
|
+
let anyDailiesStarted = false;
|
|
155
|
+
let noDailiesStarted = true;
|
|
156
|
+
let nextIncompleteDaily = null;
|
|
157
|
+
|
|
158
|
+
for (const lesson of allDailies) {
|
|
159
|
+
switch (lesson.progressStatus) {
|
|
160
|
+
case STATE.COMPLETED:
|
|
161
|
+
anyDailiesStarted = true;
|
|
162
|
+
noDailiesStarted = false;
|
|
163
|
+
break;
|
|
164
|
+
case STATE.STARTED:
|
|
165
|
+
anyDailiesStarted = true;
|
|
166
|
+
noDailiesStarted = false;
|
|
167
|
+
allDailiesCompleted = false;
|
|
168
|
+
if (!nextIncompleteDaily) {
|
|
169
|
+
nextIncompleteDaily = lesson;
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
default:
|
|
173
|
+
allDailiesCompleted = false;
|
|
174
|
+
if (!nextIncompleteDaily) {
|
|
175
|
+
nextIncompleteDaily = lesson;
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
if (!allDailiesCompleted && anyDailiesStarted && nextIncompleteDaily) {
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
allDailiesCompleted,
|
|
186
|
+
anyDailiesStarted,
|
|
187
|
+
noDailiesStarted,
|
|
188
|
+
nextIncompleteDaily
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
package/src/services/sanity.js
CHANGED
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
liveFields,
|
|
35
35
|
postProcessBadge,
|
|
36
36
|
contentAwardField,
|
|
37
|
+
parentField,
|
|
38
|
+
grandParentField,
|
|
37
39
|
} from '../contentTypeConfig.js'
|
|
38
40
|
import { fetchSimilarItems } from './recommendations.js'
|
|
39
41
|
import { getSongType, processMetadata, ALWAYS_VISIBLE_TABS, CONTENT_STATUSES } from '../contentMetaData.js'
|
|
@@ -1117,8 +1119,12 @@ export async function fetchSiblingContent(railContentId, brand = null) {
|
|
|
1117
1119
|
const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, difficulty_string, artist->, "permission_id": permission_v2, "genre": genre[]->name, "parent_id": parent_content_data[0].id`
|
|
1118
1120
|
|
|
1119
1121
|
const query = `*[railcontent_id == ${railContentId}${brandString}]{
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
+
_type,
|
|
1123
|
+
parent_type,
|
|
1124
|
+
railcontent_id,
|
|
1125
|
+
'parent_id': ${parentField}.id,
|
|
1126
|
+
'grandparent_id':${grandParentField}.id,
|
|
1127
|
+
'for-calculations': *[${filterGetParent}][0]{
|
|
1122
1128
|
'siblings-list': child[]->railcontent_id,
|
|
1123
1129
|
'parents-list': *[${filterForParentList}][0].child[]->railcontent_id
|
|
1124
1130
|
},
|
|
@@ -1136,6 +1142,11 @@ export async function fetchSiblingContent(railContentId, brand = null) {
|
|
|
1136
1142
|
const currentSiblingIndex = calc['siblings-list'].indexOf(result['railcontent_id']) + 1
|
|
1137
1143
|
|
|
1138
1144
|
delete result['for-calculations']
|
|
1145
|
+
|
|
1146
|
+
if (result['grandparent_id']) {
|
|
1147
|
+
result['collection_data'] = await fetchCourseCollectionData(result['grandparent_id'])
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1139
1150
|
result = { ...result, parentCount, currentParentIndex, siblingCount, currentSiblingIndex }
|
|
1140
1151
|
return result
|
|
1141
1152
|
} else {
|
|
@@ -29,10 +29,8 @@ export default class SyncManager {
|
|
|
29
29
|
const teardown = instance.setup()
|
|
30
30
|
|
|
31
31
|
return async (mode: SyncTeardownMode = 'reset') => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
SyncManager.instance = instance // restore instance on teardown failure
|
|
35
|
-
throw error
|
|
32
|
+
return teardown(mode).then(() => {
|
|
33
|
+
SyncManager.instance = null
|
|
36
34
|
})
|
|
37
35
|
}
|
|
38
36
|
}
|
|
@@ -76,6 +76,14 @@ export interface RestorePurchasesSetupAccountResponse {
|
|
|
76
76
|
originalAppUserId: string
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Represents response for latest subscription platform as best we can determine.
|
|
81
|
+
*/
|
|
82
|
+
export interface SubscriptionPlatform {
|
|
83
|
+
last_platform: 'ios' | 'android' | 'web' | null
|
|
84
|
+
has_active_platform_subscription: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
79
87
|
/**
|
|
80
88
|
* Represents all possible responses from RevenueCat purchase restoration
|
|
81
89
|
*/
|
|
@@ -230,7 +238,7 @@ export async function restorePurchases(
|
|
|
230
238
|
*
|
|
231
239
|
* @returns {Promise<{price: number, currency: string, period: string|null}>} - The upgrade price information
|
|
232
240
|
* @property {number} price - The upgrade cost in USD (monthly for month/year, annual for lifetime)
|
|
233
|
-
* @property {string} currency - The currency
|
|
241
|
+
* @property {string} currency - The currency
|
|
234
242
|
* @property {string|null} period - The billing period for the price ('month' or 'year'). Note: lifetime subscribers return 'year' period with annual price
|
|
235
243
|
*
|
|
236
244
|
* @example
|
|
@@ -250,3 +258,21 @@ export async function getUpgradePrice() {
|
|
|
250
258
|
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
251
259
|
return httpClient.get(`${baseUrl}/v1/upgrade-price`)
|
|
252
260
|
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* @returns {Promise<'ios' | 'android' | 'web' | null>} The platform of the user's last known subscription
|
|
264
|
+
*/
|
|
265
|
+
export async function fetchLastSubscriptionPlatform(): Promise<'ios' | 'android' | 'web' | null> {
|
|
266
|
+
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
267
|
+
const response = await httpClient.get<SubscriptionPlatform>(`${baseUrl}/v1/subscription-platform`)
|
|
268
|
+
return response.last_platform
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @returns {Promise<boolean>} Whether the user has any subscription from a known platform (web, ios, android)
|
|
273
|
+
*/
|
|
274
|
+
export async function fetchHasActivePlatformSubscription(): Promise<boolean> {
|
|
275
|
+
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
276
|
+
const response = await httpClient.get<SubscriptionPlatform>(`${baseUrl}/v1/subscription-platform`)
|
|
277
|
+
return response.has_active_platform_subscription
|
|
278
|
+
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
|
3
|
-
|
|
4
|
-
name: Node.js CI
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
push:
|
|
8
|
-
branches: [ ]
|
|
9
|
-
pull_request:
|
|
10
|
-
branches: [ ]
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
build:
|
|
14
|
-
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
|
|
17
|
-
strategy:
|
|
18
|
-
matrix:
|
|
19
|
-
node-version: [20.x]
|
|
20
|
-
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
|
21
|
-
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@v4
|
|
24
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
25
|
-
uses: actions/setup-node@v4
|
|
26
|
-
with:
|
|
27
|
-
node-version: ${{ matrix.node-version }}
|
|
28
|
-
cache: 'npm'
|
|
29
|
-
- run: npm ci
|
|
30
|
-
- run: npm run build --if-present
|
|
31
|
-
- name: 'Create env file'
|
|
32
|
-
run: |
|
|
33
|
-
touch .env
|
|
34
|
-
echo SANITY_API_TOKEN=${{ secrets.SANITY_API_TOKEN }} >> .env
|
|
35
|
-
echo SANITY_PROJECT_ID=4032r8py >> .env
|
|
36
|
-
echo SANITY_DATASET=staging >> .env
|
|
37
|
-
echo SANITY_USE_CACHED_API=false >> .env
|
|
38
|
-
cat .env
|
|
39
|
-
- run: npm test
|