musora-content-services 2.121.2 → 2.122.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/.claude/settings.local.json +9 -0
- package/CHANGELOG.md +29 -0
- package/package.json +1 -1
- package/src/contentMetaData.js +36 -0
- package/src/contentTypeConfig.js +36 -8
- package/src/services/contentProgress.js +20 -10
- package/src/services/progress-row/base.js +7 -3
- package/src/services/progress-row/rows/method-card.js +1 -1
- package/src/services/sanity.js +25 -52
- package/src/services/sync/repositories/base.ts +2 -16
- package/src/services/sync/repositories/content-progress.ts +7 -4
- package/src/services/sync/store/index.ts +4 -2
- package/src/services/user/account.ts +5 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
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.122.1](https://github.com/railroadmedia/musora-content-services/compare/v2.122.0...v2.122.1) (2026-01-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* handle global live streams ([#727](https://github.com/railroadmedia/musora-content-services/issues/727)) ([e0ed5f3](https://github.com/railroadmedia/musora-content-services/commit/e0ed5f398f61a2188beae56a05547494862a16b7))
|
|
11
|
+
* removes tentative progress call functionality ([#716](https://github.com/railroadmedia/musora-content-services/issues/716)) ([a167d3b](https://github.com/railroadmedia/musora-content-services/commit/a167d3b94c1d2ef6df9a7f76e4c4ea4c71f9bfb5))
|
|
12
|
+
* **T3PS-1562:** hide nonpinned method card ([#721](https://github.com/railroadmedia/musora-content-services/issues/721)) ([cc250ee](https://github.com/railroadmedia/musora-content-services/commit/cc250ee072cac976fc7389eb86e77eff1230b73a))
|
|
13
|
+
* **T3PS-1579:** fix reset bubbling ([#718](https://github.com/railroadmedia/musora-content-services/issues/718)) ([a9490a8](https://github.com/railroadmedia/musora-content-services/commit/a9490a85aeb4df947770a265543fa6782e30b436))
|
|
14
|
+
|
|
15
|
+
## [2.122.0](https://github.com/railroadmedia/musora-content-services/compare/v2.118.1...v2.122.0) (2026-01-22)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* add fetchLiveStreamData method ([aa9b704](https://github.com/railroadmedia/musora-content-services/commit/aa9b7046c7fc69e8b30c0f035577da62f8c5b5fd))
|
|
21
|
+
* add vimeo_live_event_id to GROQ queries ([d4fff6f](https://github.com/railroadmedia/musora-content-services/commit/d4fff6f09ca62682f6cead7e43f6979a56b86e0a))
|
|
22
|
+
* adds restore methods (for progress deletion undo) ([#705](https://github.com/railroadmedia/musora-content-services/issues/705)) ([b01a718](https://github.com/railroadmedia/musora-content-services/commit/b01a71892175b38789d62fffac0ce5f8e2e4513d))
|
|
23
|
+
* **TP-1060:** method data caching in mcs ([#701](https://github.com/railroadmedia/musora-content-services/issues/701)) ([25efc43](https://github.com/railroadmedia/musora-content-services/commit/25efc43b6c07172db2f20d51d5b79ac9fc204eef))
|
|
24
|
+
* update brand endpoint ([#719](https://github.com/railroadmedia/musora-content-services/issues/719)) ([4938b61](https://github.com/railroadmedia/musora-content-services/commit/4938b61c8526e31194b284c6daf172764b7900ed))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* adds live_global_event field where live_event_stream_id is returned ([#708](https://github.com/railroadmedia/musora-content-services/issues/708)) ([23c58f4](https://github.com/railroadmedia/musora-content-services/commit/23c58f42da866cd1035276c62c773b82c09d3aa5))
|
|
30
|
+
* better logs in Sentry for debugging ([#712](https://github.com/railroadmedia/musora-content-services/issues/712)) ([e1019b5](https://github.com/railroadmedia/musora-content-services/commit/e1019b50b0cf606b86e71028db0a2f9ac354cfca))
|
|
31
|
+
* remove unsupported collection types ([#713](https://github.com/railroadmedia/musora-content-services/issues/713)) ([0441941](https://github.com/railroadmedia/musora-content-services/commit/04419413ffea82303258b839c8e1e11b3a4c507a))
|
|
32
|
+
* **T3PS-1347:** Implement WatermelonDB-based streak calculator with year boundary fix ([916a8ef](https://github.com/railroadmedia/musora-content-services/commit/916a8ef532a858f82a8133d1725ad69ea3d550e9))
|
|
33
|
+
|
|
5
34
|
### [2.121.2](https://github.com/railroadmedia/musora-content-services/compare/v2.121.1...v2.121.2) (2026-01-20)
|
|
6
35
|
|
|
7
36
|
### [2.121.1](https://github.com/railroadmedia/musora-content-services/compare/v2.121.0...v2.121.1) (2026-01-20)
|
package/package.json
CHANGED
package/src/contentMetaData.js
CHANGED
|
@@ -128,6 +128,7 @@ export class Tabs {
|
|
|
128
128
|
static SingleLessons = { name: 'Single Lessons', short_name: 'Single Lessons', value: 'type,Single Lessons', recSysSection: 'lesson', }
|
|
129
129
|
static SkillPacks = { name: 'Skill Packs', short_name: 'Skill Packs', value: 'type,Skill Packs', recSysSection: 'lesson', }
|
|
130
130
|
static Entertainment = { name: 'Entertainment', short_name: 'Entertainment', value: 'type,Entertainment', recSysSection: 'lesson', }
|
|
131
|
+
static Routines = { name: 'Routines', short_name: 'Routines', value: 'type,routine', recSysSection: 'lesson', }
|
|
131
132
|
}
|
|
132
133
|
|
|
133
134
|
/**
|
|
@@ -325,6 +326,41 @@ const contentMetadata = {
|
|
|
325
326
|
},
|
|
326
327
|
singeo: {
|
|
327
328
|
'songs-types': ['Tutorials', 'Sheet Music', 'Play-Alongs', 'Jam Tracks'],
|
|
329
|
+
lessons: {
|
|
330
|
+
name: 'Lessons',
|
|
331
|
+
filterOptions: {
|
|
332
|
+
difficulty: DIFFICULTY_STRINGS,
|
|
333
|
+
length: LengthFilterOptions.AllOptions,
|
|
334
|
+
style: [
|
|
335
|
+
'Country/Folk',
|
|
336
|
+
'Funk/Disco',
|
|
337
|
+
'Hard Rock/Metal',
|
|
338
|
+
'Hip-Hop/Rap/EDM',
|
|
339
|
+
'Holiday/Soundtrack',
|
|
340
|
+
'Jazz/Blues',
|
|
341
|
+
'Latin/World',
|
|
342
|
+
'Pop/Rock',
|
|
343
|
+
'R&B/Soul',
|
|
344
|
+
'Worship/Gospel',
|
|
345
|
+
],
|
|
346
|
+
type: LESSON_TYPE_FILTER,
|
|
347
|
+
progress: PROGRESS_NAMES,
|
|
348
|
+
},
|
|
349
|
+
sortingOptions: {
|
|
350
|
+
title: 'Sort By',
|
|
351
|
+
type: 'radio',
|
|
352
|
+
items: SortingOptions.AllSortingOptions,
|
|
353
|
+
},
|
|
354
|
+
tabs: [
|
|
355
|
+
Tabs.ForYou,
|
|
356
|
+
Tabs.SingleLessons,
|
|
357
|
+
Tabs.Courses,
|
|
358
|
+
Tabs.SkillPacks,
|
|
359
|
+
Tabs.Routines,
|
|
360
|
+
Tabs.Entertainment,
|
|
361
|
+
Tabs.ExploreAll,
|
|
362
|
+
],
|
|
363
|
+
},
|
|
328
364
|
},
|
|
329
365
|
}
|
|
330
366
|
|
package/src/contentTypeConfig.js
CHANGED
|
@@ -50,7 +50,7 @@ export const DEFAULT_FIELDS = [
|
|
|
50
50
|
"'image': thumbnail.asset->url",
|
|
51
51
|
"'thumbnail': thumbnail.asset->url",
|
|
52
52
|
'difficulty',
|
|
53
|
-
|
|
53
|
+
difficultyStringField(),
|
|
54
54
|
'published_on',
|
|
55
55
|
"'type': _type",
|
|
56
56
|
"'length_in_seconds' : coalesce(length_in_seconds, soundslice[0].soundslice_length_in_second)",
|
|
@@ -132,6 +132,24 @@ export const assignmentsField = `"assignments":assignment[]{
|
|
|
132
132
|
"description": coalesce(assignment_description,'')
|
|
133
133
|
},`
|
|
134
134
|
|
|
135
|
+
// todo: refactor live event queries to use this
|
|
136
|
+
export const liveFields = `
|
|
137
|
+
'slug': slug.current,
|
|
138
|
+
'id': railcontent_id,
|
|
139
|
+
title,
|
|
140
|
+
live_event_start_time,
|
|
141
|
+
live_event_end_time,
|
|
142
|
+
live_event_stream_id,
|
|
143
|
+
"live_event_is_global": live_global_event == true,
|
|
144
|
+
published_on,
|
|
145
|
+
"thumbnail": thumbnail.asset->url,
|
|
146
|
+
${artistOrInstructorName()},
|
|
147
|
+
difficulty_string,
|
|
148
|
+
railcontent_id,
|
|
149
|
+
"instructors": ${instructorField},
|
|
150
|
+
'videoId': coalesce(live_event_stream_id, video.external_id)
|
|
151
|
+
`
|
|
152
|
+
|
|
135
153
|
const contentWithInstructorsField = {
|
|
136
154
|
fields: ['"instructors": instructor[]->name'],
|
|
137
155
|
}
|
|
@@ -224,7 +242,7 @@ export const collectionLessonTypes = [...coursesLessonTypes, ...showsLessonTypes
|
|
|
224
242
|
|
|
225
243
|
export const lessonTypesMapping = {
|
|
226
244
|
lessons: singleLessonTypes,
|
|
227
|
-
'practice alongs': practiceAlongsLessonTypes,
|
|
245
|
+
'practice alongs': [ ...practiceAlongsLessonTypes, 'routine'],
|
|
228
246
|
'live archives': liveArchivesLessonTypes,
|
|
229
247
|
performances: performancesLessonTypes,
|
|
230
248
|
'student archives': studentArchivesLessonTypes,
|
|
@@ -250,6 +268,7 @@ export const lessonTypesMapping = {
|
|
|
250
268
|
...studentArchivesLessonTypes,
|
|
251
269
|
...practiceAlongsLessonTypes,
|
|
252
270
|
],
|
|
271
|
+
routines: ['routine']
|
|
253
272
|
}
|
|
254
273
|
|
|
255
274
|
export const getNextLessonLessonParentTypes = [
|
|
@@ -270,7 +289,8 @@ export const progressTypesMapping = {
|
|
|
270
289
|
...studentArchivesLessonTypes,
|
|
271
290
|
'documentary-lesson',
|
|
272
291
|
'live',
|
|
273
|
-
'course-lesson'
|
|
292
|
+
'course-lesson',
|
|
293
|
+
'routine'
|
|
274
294
|
],
|
|
275
295
|
course: ['course'],
|
|
276
296
|
show: showsLessonTypes,
|
|
@@ -302,6 +322,7 @@ export const filterTypes = {
|
|
|
302
322
|
...coursesLessonTypes,
|
|
303
323
|
...skillLessonTypes,
|
|
304
324
|
...entertainmentLessonTypes,
|
|
325
|
+
'routine'
|
|
305
326
|
],
|
|
306
327
|
songs: [
|
|
307
328
|
...tutorialsLessonTypes,
|
|
@@ -314,9 +335,12 @@ export const filterTypes = {
|
|
|
314
335
|
export const recentTypes = {
|
|
315
336
|
lessons: [
|
|
316
337
|
...individualLessonsTypes,
|
|
338
|
+
...skillLessonTypes,
|
|
339
|
+
...entertainmentLessonTypes,
|
|
317
340
|
'course-lesson',
|
|
318
341
|
'guided-course-lesson',
|
|
319
342
|
'quick-tips',
|
|
343
|
+
'routine'
|
|
320
344
|
],
|
|
321
345
|
songs: [...SONG_TYPES],
|
|
322
346
|
home: [
|
|
@@ -331,6 +355,7 @@ export const recentTypes = {
|
|
|
331
355
|
'live',
|
|
332
356
|
'course',
|
|
333
357
|
'course-collection',
|
|
358
|
+
'routine'
|
|
334
359
|
],
|
|
335
360
|
}
|
|
336
361
|
|
|
@@ -623,9 +648,9 @@ export function getIntroVideoFields(type) {
|
|
|
623
648
|
`"id": railcontent_id`,
|
|
624
649
|
'title',
|
|
625
650
|
'brand',
|
|
626
|
-
`"instructor":
|
|
627
|
-
`
|
|
628
|
-
`
|
|
651
|
+
`"instructor": ${instructorField}`,
|
|
652
|
+
`difficulty`,
|
|
653
|
+
`difficulty_string`,
|
|
629
654
|
`"type": _type`,
|
|
630
655
|
'brand',
|
|
631
656
|
`"description": ${descriptionField}`,
|
|
@@ -666,7 +691,7 @@ export function getNewReleasesTypes(brand) {
|
|
|
666
691
|
'quick-tips',
|
|
667
692
|
'workout',
|
|
668
693
|
'podcasts',
|
|
669
|
-
'
|
|
694
|
+
'course-collection',
|
|
670
695
|
'song',
|
|
671
696
|
'play-along',
|
|
672
697
|
'course',
|
|
@@ -703,7 +728,6 @@ export function getUpcomingEventsTypes(brand) {
|
|
|
703
728
|
'boot-camp',
|
|
704
729
|
'quick-tips',
|
|
705
730
|
'recording',
|
|
706
|
-
'pack-bundle-lesson',
|
|
707
731
|
]
|
|
708
732
|
switch (brand) {
|
|
709
733
|
case 'drumeo':
|
|
@@ -732,6 +756,10 @@ export function artistOrInstructorNameAsArray(key = 'artists') {
|
|
|
732
756
|
return `'${key}': select(artist->name != null => [artist->name], instructor[]->name)`
|
|
733
757
|
}
|
|
734
758
|
|
|
759
|
+
export function difficultyStringField(key = 'difficulty_string') {
|
|
760
|
+
return `'${key}': select(difficulty_string == 'Novice' => 'Introductory', difficulty_string)`
|
|
761
|
+
}
|
|
762
|
+
|
|
735
763
|
export async function getFieldsForContentTypeWithFilteredChildren(
|
|
736
764
|
contentType,
|
|
737
765
|
asQueryString = true
|
|
@@ -531,9 +531,8 @@ async function saveContentProgress(contentId, collection, progress, currentSecon
|
|
|
531
531
|
}
|
|
532
532
|
}
|
|
533
533
|
|
|
534
|
-
if (Object.keys(bubbledProgresses).length
|
|
535
|
-
|
|
536
|
-
await db.contentProgress.recordProgressMany(bubbledProgresses, collection, {tentative: !isLP, skipPush: true, fromLearningPath})
|
|
534
|
+
if (Object.keys(bubbledProgresses).length > 0) {
|
|
535
|
+
await db.contentProgress.recordProgressMany(bubbledProgresses, collection, {skipPush: true, fromLearningPath})
|
|
537
536
|
}
|
|
538
537
|
|
|
539
538
|
if (isLP) {
|
|
@@ -567,8 +566,7 @@ async function setStartedOrCompletedStatus(contentId, collection, isCompleted, {
|
|
|
567
566
|
...trickleProgress(hierarchy, contentId, collection, progress),
|
|
568
567
|
...await bubbleProgress(hierarchy, contentId, collection)
|
|
569
568
|
}
|
|
570
|
-
|
|
571
|
-
await db.contentProgress.recordProgressMany(progresses, collection, {tentative: !isLP, skipPush: true})
|
|
569
|
+
await db.contentProgress.recordProgressMany(progresses, collection, {skipPush: true})
|
|
572
570
|
|
|
573
571
|
if (isLP) {
|
|
574
572
|
let exportProgresses = progresses
|
|
@@ -600,7 +598,7 @@ async function setStartedOrCompletedStatusMany(contentIds, collection, isComplet
|
|
|
600
598
|
}
|
|
601
599
|
|
|
602
600
|
const contents = Object.fromEntries(contentIds.map((id) => [id, progress]))
|
|
603
|
-
const response = await db.contentProgress.recordProgressMany(contents, collection, {
|
|
601
|
+
const response = await db.contentProgress.recordProgressMany(contents, collection, {skipPush: true})
|
|
604
602
|
|
|
605
603
|
// we assume this is used only for contents within the same hierarchy
|
|
606
604
|
const hierarchy = await getHierarchy(collection.id, collection)
|
|
@@ -613,8 +611,7 @@ async function setStartedOrCompletedStatusMany(contentIds, collection, isComplet
|
|
|
613
611
|
...(await bubbleProgress(hierarchy, contentId, collection)),
|
|
614
612
|
}
|
|
615
613
|
}
|
|
616
|
-
|
|
617
|
-
await db.contentProgress.recordProgressMany(progresses, collection, {tentative: !isLP, skipPush: true})
|
|
614
|
+
await db.contentProgress.recordProgressMany(progresses, collection, {skipPush: true})
|
|
618
615
|
|
|
619
616
|
if (isLP) {
|
|
620
617
|
let exportProgresses = progresses
|
|
@@ -647,8 +644,21 @@ async function resetStatus(contentId, collection = null, {skipPush = false} = {}
|
|
|
647
644
|
...trickleProgress(hierarchy, contentId, collection, progress),
|
|
648
645
|
...await bubbleProgress(hierarchy, contentId, collection)
|
|
649
646
|
}
|
|
650
|
-
//
|
|
651
|
-
|
|
647
|
+
// have to use different endpoints for erase vs record
|
|
648
|
+
const eraseProgresses = Object.fromEntries(
|
|
649
|
+
Object.entries(progresses).filter(([_, pct]) => pct === 0)
|
|
650
|
+
)
|
|
651
|
+
progresses = Object.fromEntries(
|
|
652
|
+
Object.entries(progresses).filter(([_, pct]) => pct > 0)
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
if (Object.keys(progresses).length > 0) {
|
|
656
|
+
await db.contentProgress.recordProgressMany(progresses, collection, {skipPush: true, fromLearningPath: isLP})
|
|
657
|
+
}
|
|
658
|
+
if (Object.keys(eraseProgresses).length > 0) {
|
|
659
|
+
const eraseIds = Object.keys(eraseProgresses).map(Number)
|
|
660
|
+
await db.contentProgress.eraseProgressMany(eraseIds, collection, {skipPush: true})
|
|
661
|
+
}
|
|
652
662
|
|
|
653
663
|
if (isLP) {
|
|
654
664
|
progresses[contentId] = progress
|
|
@@ -178,12 +178,16 @@ function sortCards(pinnedCard, contentCardMap, playlistCards, methodCard, limit)
|
|
|
178
178
|
combined.push(pinnedCard)
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
-
|
|
181
|
+
const progressList = Array.from(contentCardMap.values())
|
|
182
|
+
|
|
183
|
+
combined = [...combined, ...progressList, ...playlistCards]
|
|
184
|
+
|
|
185
|
+
// welcome card state will only show if pinned
|
|
186
|
+
if (methodCard.type !== 'method') {
|
|
182
187
|
combined.push(methodCard)
|
|
183
188
|
}
|
|
184
189
|
|
|
185
|
-
|
|
186
|
-
return mergeAndSortItems([...combined, ...progressList, ...playlistCards], limit)
|
|
190
|
+
return mergeAndSortItems(combined, limit)
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
function mergeAndSortItems(items, limit) {
|
package/src/services/sanity.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
contentTypeConfig,
|
|
10
10
|
DEFAULT_FIELDS,
|
|
11
11
|
descriptionField,
|
|
12
|
+
difficultyStringField,
|
|
12
13
|
filtersToGroq,
|
|
13
14
|
getChildFieldsForContentType,
|
|
14
15
|
getFieldsForContentType,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
showsTypes,
|
|
32
33
|
SONG_TYPES,
|
|
33
34
|
SONG_TYPES_WITH_CHILDREN,
|
|
35
|
+
liveFields,
|
|
34
36
|
} from '../contentTypeConfig.js'
|
|
35
37
|
import { fetchSimilarItems, recommendations } from './recommendations.js'
|
|
36
38
|
import { getSongType, processMetadata, ALWAYS_VISIBLE_TABS } from '../contentMetaData.js'
|
|
@@ -330,7 +332,7 @@ export async function fetchNewReleases(
|
|
|
330
332
|
"instructor": ${instructorField},
|
|
331
333
|
"artists": instructor[]->name,
|
|
332
334
|
difficulty,
|
|
333
|
-
|
|
335
|
+
${difficultyStringField()},
|
|
334
336
|
length_in_seconds,
|
|
335
337
|
published_on,
|
|
336
338
|
"type": _type,
|
|
@@ -368,7 +370,7 @@ export async function fetchUpcomingEvents(brand, { page = 1, limit = 10 } = {})
|
|
|
368
370
|
"artists": instructor[]->name,
|
|
369
371
|
"instructor": ${instructorField},
|
|
370
372
|
difficulty,
|
|
371
|
-
|
|
373
|
+
${difficultyStringField()},
|
|
372
374
|
length_in_seconds,
|
|
373
375
|
published_on,
|
|
374
376
|
"type": _type,
|
|
@@ -421,7 +423,7 @@ export async function fetchScheduledReleases(brand, { page = 1, limit = 10 }) {
|
|
|
421
423
|
"instructor": ${instructorField},
|
|
422
424
|
"artists": instructor[]->name,
|
|
423
425
|
difficulty,
|
|
424
|
-
|
|
426
|
+
${difficultyStringField()},
|
|
425
427
|
length_in_seconds,
|
|
426
428
|
published_on,
|
|
427
429
|
"type": _type,
|
|
@@ -961,9 +963,9 @@ export async function fetchLessonContent(railContentId, { addParent = false } =
|
|
|
961
963
|
${parentQuery}
|
|
962
964
|
...select(
|
|
963
965
|
defined(live_event_start_time) => {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
966
|
+
live_event_start_time,
|
|
967
|
+
live_event_end_time,
|
|
968
|
+
live_event_stream_id,
|
|
967
969
|
"vimeo_live_event_id": vimeo_live_event_id,
|
|
968
970
|
"videoId": coalesce(live_event_stream_id, video.external_id),
|
|
969
971
|
"live_event_is_global": live_global_event == true
|
|
@@ -1096,7 +1098,7 @@ export async function fetchSiblingContent(railContentId, brand = null) {
|
|
|
1096
1098
|
}).buildFilter()
|
|
1097
1099
|
|
|
1098
1100
|
const brandString = brand ? ` && brand == "${brand}"` : ''
|
|
1099
|
-
const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty,
|
|
1101
|
+
const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, ${difficultyStringField()}, artist->, "permission_id": permission_v2, "genre": genre[]->name, "parent_id": parent_content_data[0].id`
|
|
1100
1102
|
|
|
1101
1103
|
const query = `*[railcontent_id == ${railContentId}${brandString}]{
|
|
1102
1104
|
_type, parent_type, 'parent_id': parent_content_data[0].id, railcontent_id,
|
|
@@ -1143,7 +1145,7 @@ export async function fetchRelatedLessons(railContentId) {
|
|
|
1143
1145
|
{ showMembershipRestrictedContent: true }
|
|
1144
1146
|
).buildFilter()
|
|
1145
1147
|
|
|
1146
|
-
const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty,
|
|
1148
|
+
const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, ${difficultyStringField()}, railcontent_id, artist->,"permission_id": permission_v2,_type, "genre": genre[]->name`
|
|
1147
1149
|
|
|
1148
1150
|
const query = `*[railcontent_id == ${railContentId} && (!defined(permission) || references(*[_type=='permission']._id))]{
|
|
1149
1151
|
_type, parent_type, railcontent_id,
|
|
@@ -1184,50 +1186,21 @@ export async function fetchLiveEvent(brand, forcedContentId = null) {
|
|
|
1184
1186
|
)
|
|
1185
1187
|
endDateTemp = new Date(endDateTemp.setMinutes(endDateTemp.getMinutes() - LIVE_EXTRA_MINUTES))
|
|
1186
1188
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
const query =
|
|
1189
|
+
const liveEventFields = liveFields + `, 'event_coach_calendar_id': coalesce(calendar_id, '${defaultCalendarID}')`
|
|
1190
|
+
|
|
1191
|
+
const baseFilter =
|
|
1191
1192
|
forcedContentId !== null
|
|
1192
|
-
?
|
|
1193
|
-
'
|
|
1194
|
-
'
|
|
1195
|
-
live_event_start_time
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
title,
|
|
1204
|
-
"thumbnail": thumbnail.asset->url,
|
|
1205
|
-
${artistOrInstructorName()},
|
|
1206
|
-
difficulty_string,
|
|
1207
|
-
"instructors": ${instructorField},
|
|
1208
|
-
'videoId': coalesce(live_event_stream_id, video.external_id),
|
|
1209
|
-
} | order(live_event_start_time)[0...1]`
|
|
1210
|
-
: `*[status == 'scheduled' && brand == '${brand}' && defined(live_event_start_time) && live_event_start_time <= '${getSanityDate(startDateTemp, false)}' && live_event_end_time >= '${getSanityDate(endDateTemp, false)}']{
|
|
1211
|
-
'slug': slug.current,
|
|
1212
|
-
'id': railcontent_id,
|
|
1213
|
-
live_event_start_time,
|
|
1214
|
-
live_event_end_time,
|
|
1215
|
-
live_event_stream_id,
|
|
1216
|
-
vimeo_live_event_id,
|
|
1217
|
-
railcontent_id,
|
|
1218
|
-
published_on,
|
|
1219
|
-
'event_coach_url' : instructor[0]->web_url_path,
|
|
1220
|
-
'event_coach_calendar_id': coalesce(calendar_id, '${defaultCalendarID}'),
|
|
1221
|
-
title,
|
|
1222
|
-
"thumbnail": thumbnail.asset->url,
|
|
1223
|
-
${artistOrInstructorName()},
|
|
1224
|
-
difficulty_string,
|
|
1225
|
-
"instructors": instructor[]->{
|
|
1226
|
-
name,
|
|
1227
|
-
web_url_path,
|
|
1228
|
-
},
|
|
1229
|
-
'videoId': coalesce(live_event_stream_id, video.external_id),
|
|
1230
|
-
} | order(live_event_start_time)[0...1]`
|
|
1193
|
+
? `railcontent_id == ${forcedContentId}`
|
|
1194
|
+
: `status == 'scheduled'
|
|
1195
|
+
&& (brand == '${brand}' || live_global_event == true)
|
|
1196
|
+
&& defined(live_event_start_time)
|
|
1197
|
+
&& live_event_start_time <= '${getSanityDate(startDateTemp, false)}'
|
|
1198
|
+
&& live_event_end_time >= '${getSanityDate(endDateTemp, false)}'`
|
|
1199
|
+
|
|
1200
|
+
const filter = await new FilterBuilder(baseFilter, {bypassPermissions: true}).buildFilter()
|
|
1201
|
+
|
|
1202
|
+
// This query finds the first scheduled event (sorted by start_time) that ends after now()
|
|
1203
|
+
const query = `*[${filter}]{${liveEventFields}} | order(live_event_start_time)[0...1]`
|
|
1231
1204
|
|
|
1232
1205
|
return await fetchSanity(query, false, { processNeedAccess: false })
|
|
1233
1206
|
}
|
|
@@ -2001,7 +1974,7 @@ export async function fetchScheduledAndNewReleases(
|
|
|
2001
1974
|
${artistOrInstructorName()},
|
|
2002
1975
|
"artists": instructor[]->name,
|
|
2003
1976
|
difficulty,
|
|
2004
|
-
|
|
1977
|
+
${difficultyStringField()},
|
|
2005
1978
|
length_in_seconds,
|
|
2006
1979
|
published_on,
|
|
2007
1980
|
"type": _type,
|
|
@@ -100,13 +100,6 @@ export default class SyncRepository<TModel extends BaseModel> {
|
|
|
100
100
|
)
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
protected async upsertOneTentative(id: RecordId, builder: (record: TModel) => void) {
|
|
104
|
-
return this.store.telemetry.trace(
|
|
105
|
-
{ name: `upsertOneTentative:${this.store.model.table}`, op: 'upsert', attributes: { ...this.context.session.toJSON() } },
|
|
106
|
-
(span) => this._respondToWrite(() => this.store.upsertOneTentative(id, builder, span), span)
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
103
|
protected async upsertSome(builders: Record<RecordId, (record: TModel) => void>, { skipPush = false } = {}) {
|
|
111
104
|
return this.store.telemetry.trace(
|
|
112
105
|
{ name: `upsertSome:${this.store.model.table}`, op: 'upsert', attributes: { ...this.context.session.toJSON() } },
|
|
@@ -114,13 +107,6 @@ export default class SyncRepository<TModel extends BaseModel> {
|
|
|
114
107
|
)
|
|
115
108
|
}
|
|
116
109
|
|
|
117
|
-
protected async upsertSomeTentative(builders: Record<RecordId, (record: TModel) => void>, { skipPush = false } = {}) {
|
|
118
|
-
return this.store.telemetry.trace(
|
|
119
|
-
{ name: `upsertSomeTentative:${this.store.model.table}`, op: 'upsert', attributes: { ...this.context.session.toJSON() } },
|
|
120
|
-
(span) => this._respondToWrite(() => this.store.upsertSomeTentative(builders, span, {skipPush}), span)
|
|
121
|
-
)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
110
|
protected async deleteOne(id: RecordId, { skipPush = false } = {}) {
|
|
125
111
|
return this.store.telemetry.trace(
|
|
126
112
|
{ name: `delete:${this.store.model.table}`, op: 'delete', attributes: { ...this.context.session.toJSON() } },
|
|
@@ -128,10 +114,10 @@ export default class SyncRepository<TModel extends BaseModel> {
|
|
|
128
114
|
)
|
|
129
115
|
}
|
|
130
116
|
|
|
131
|
-
protected async deleteSome(ids: RecordId[]) {
|
|
117
|
+
protected async deleteSome(ids: RecordId[], { skipPush = false } = {}) {
|
|
132
118
|
return this.store.telemetry.trace(
|
|
133
119
|
{ name: `deleteSome:${this.store.model.table}`, op: 'delete', attributes: { ...this.context.session.toJSON() } },
|
|
134
|
-
(span) => this._respondToWriteIds(() => this.store.deleteSome(ids, span), span)
|
|
120
|
+
(span) => this._respondToWriteIds(() => this.store.deleteSome(ids, span, {skipPush}), span)
|
|
135
121
|
)
|
|
136
122
|
}
|
|
137
123
|
|
|
@@ -187,7 +187,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
187
187
|
recordProgressMany(
|
|
188
188
|
contentProgresses: Record<string, number>, // Accept plain object
|
|
189
189
|
collection: CollectionParameter | null,
|
|
190
|
-
{
|
|
190
|
+
{ skipPush = false, fromLearningPath = false }: { skipPush?: boolean; fromLearningPath?: boolean } = {}
|
|
191
191
|
) {
|
|
192
192
|
if (collection?.type === COLLECTION_TYPE.LEARNING_PATH) {
|
|
193
193
|
fromLearningPath = true
|
|
@@ -209,9 +209,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
209
209
|
},
|
|
210
210
|
])
|
|
211
211
|
)
|
|
212
|
-
return
|
|
213
|
-
? this.upsertSomeTentative(data, { skipPush })
|
|
214
|
-
: this.upsertSome(data, { skipPush })
|
|
212
|
+
return this.upsertSome(data, { skipPush })
|
|
215
213
|
|
|
216
214
|
//todo add event emitting for bulk updates?
|
|
217
215
|
}
|
|
@@ -220,6 +218,11 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
220
218
|
return this.deleteOne(ProgressRepository.generateId(contentId, collection), { skipPush })
|
|
221
219
|
}
|
|
222
220
|
|
|
221
|
+
eraseProgressMany(contentIds: number[], collection: CollectionParameter | null, {skipPush = false} = {}) {
|
|
222
|
+
const ids = contentIds.map((id) => ProgressRepository.generateId(id, collection))
|
|
223
|
+
return this.deleteSome(ids, { skipPush })
|
|
224
|
+
}
|
|
225
|
+
|
|
223
226
|
private static generateId(
|
|
224
227
|
contentId: number,
|
|
225
228
|
collection: CollectionParameter | null
|
|
@@ -354,7 +354,7 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
|
|
|
354
354
|
})
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
async deleteSome(ids: RecordId[], span?: Span) {
|
|
357
|
+
async deleteSome(ids: RecordId[], span?: Span, { skipPush = false } = {}) {
|
|
358
358
|
return this.runScope.abortable(async () => {
|
|
359
359
|
await this.telemeterizedWrite(span, async writer => {
|
|
360
360
|
const existing = await this.queryRecords(Q.where('id', Q.oneOf(ids)))
|
|
@@ -364,7 +364,9 @@ export default class SyncStore<TModel extends BaseModel = BaseModel> {
|
|
|
364
364
|
|
|
365
365
|
this.emit('deleted', ids)
|
|
366
366
|
|
|
367
|
-
|
|
367
|
+
if (!skipPush) {
|
|
368
|
+
this.pushUnsyncedWithRetry(span)
|
|
369
|
+
}
|
|
368
370
|
await this.ensurePersistence()
|
|
369
371
|
|
|
370
372
|
return ids
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { HttpClient } from '../../infrastructure/http/HttpClient'
|
|
5
5
|
import { HttpError } from '../../infrastructure/http/interfaces/HttpError'
|
|
6
6
|
import { globalConfig } from '../config.js'
|
|
7
|
+
import { clearAllCachedData } from '../dataContext.js'
|
|
7
8
|
import { Onboarding } from './onboarding'
|
|
8
9
|
import { AuthResponse } from './types'
|
|
9
10
|
|
|
@@ -149,7 +150,10 @@ export async function confirmEmailChange(token: string): Promise<void> {
|
|
|
149
150
|
export async function deleteAccount(userId: number): Promise<void> {
|
|
150
151
|
const apiUrl = `/api/user-management-system/v1/users/${userId}`
|
|
151
152
|
const httpClient = new HttpClient(globalConfig.baseUrl, globalConfig.sessionConfig.token)
|
|
152
|
-
|
|
153
|
+
await httpClient.delete(apiUrl)
|
|
154
|
+
|
|
155
|
+
// Clear all locally cached data to prevent data leakage between users
|
|
156
|
+
await clearAllCachedData()
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
/**
|