musora-content-services 2.107.7 → 2.108.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/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/src/contentTypeConfig.js +2 -0
- package/src/services/content-org/learning-paths.ts +8 -7
- package/src/services/contentProgress.js +17 -12
- package/src/services/sync/models/ContentProgress.ts +6 -0
- package/src/services/sync/repositories/content-progress.ts +8 -2
- package/src/services/sync/schema/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
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.108.0](https://github.com/railroadmedia/musora-content-services/compare/v2.107.8...v2.108.0) (2026-01-02)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* adds column and implements for hiding cards ([#679](https://github.com/railroadmedia/musora-content-services/issues/679)) ([35ac42c](https://github.com/railroadmedia/musora-content-services/commit/35ac42cebe397c557d0b197d4fab38907ca08ab7))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* some fixes from Rob ([#676](https://github.com/railroadmedia/musora-content-services/issues/676)) ([7e068ee](https://github.com/railroadmedia/musora-content-services/commit/7e068eee3cabc4de1d0620fe58eadf138532ab56))
|
|
16
|
+
|
|
17
|
+
### [2.107.8](https://github.com/railroadmedia/musora-content-services/compare/v2.107.7...v2.107.8) (2025-12-30)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **T3PS-1289:** navigateTo calculation ([#677](https://github.com/railroadmedia/musora-content-services/issues/677)) ([12f4ca3](https://github.com/railroadmedia/musora-content-services/commit/12f4ca38b05a2175b33a9260e7c00f9aebfb8a87))
|
|
23
|
+
|
|
5
24
|
### [2.107.7](https://github.com/railroadmedia/musora-content-services/compare/v2.107.6...v2.107.7) (2025-12-29)
|
|
6
25
|
|
|
7
26
|
|
package/package.json
CHANGED
package/src/contentTypeConfig.js
CHANGED
|
@@ -301,6 +301,7 @@ export const progressTypesMapping = {
|
|
|
301
301
|
'guided course': ['guided-course'],
|
|
302
302
|
pack: ['pack', 'semester-pack'],
|
|
303
303
|
'learning path': ['learning-path-v2'],
|
|
304
|
+
'skill pack': skillLessonTypes,
|
|
304
305
|
'jam track': jamTrackLessonTypes,
|
|
305
306
|
'course video': ['course-lesson'],
|
|
306
307
|
}
|
|
@@ -343,6 +344,7 @@ export const recentTypes = {
|
|
|
343
344
|
home: [
|
|
344
345
|
...individualLessonsTypes,
|
|
345
346
|
...tutorialsLessonTypes,
|
|
347
|
+
...skillLessonTypes,
|
|
346
348
|
...transcriptionsLessonTypes,
|
|
347
349
|
...playAlongLessonTypes,
|
|
348
350
|
'guided-course',
|
|
@@ -364,13 +364,13 @@ export async function completeMethodIntroVideo(
|
|
|
364
364
|
): Promise<completeMethodIntroVideo> {
|
|
365
365
|
let response = {} as completeMethodIntroVideo
|
|
366
366
|
|
|
367
|
-
response.intro_video_response = await completeIfNotCompleted(introVideoId)
|
|
368
|
-
|
|
369
367
|
const methodStructure = await fetchMethodV2Structure(brand)
|
|
370
|
-
const firstLearningPathId = methodStructure.learning_paths[0].id
|
|
371
368
|
|
|
369
|
+
const firstLearningPathId = methodStructure.learning_paths[0].id
|
|
372
370
|
response.active_path_response = await methodIntroVideoCompleteActions(brand, firstLearningPathId, getToday())
|
|
373
371
|
|
|
372
|
+
response.intro_video_response = await completeIfNotCompleted(introVideoId)
|
|
373
|
+
|
|
374
374
|
return response
|
|
375
375
|
}
|
|
376
376
|
|
|
@@ -408,17 +408,16 @@ export async function completeLearningPathIntroVideo(
|
|
|
408
408
|
) {
|
|
409
409
|
let response = {} as completeLearningPathIntroVideo
|
|
410
410
|
|
|
411
|
-
response.intro_video_response = await completeIfNotCompleted(introVideoId)
|
|
412
|
-
|
|
413
411
|
const collection: CollectionObject = { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH }
|
|
414
412
|
|
|
415
413
|
if (!lessonsToImport) {
|
|
416
|
-
response.learning_path_reset_response = await resetIfPossible(learningPathId, collection)
|
|
417
414
|
|
|
415
|
+
response.learning_path_reset_response = await resetIfPossible(learningPathId, collection)
|
|
418
416
|
} else {
|
|
419
|
-
response.lesson_import_response = await contentStatusCompletedMany(lessonsToImport, collection)
|
|
420
417
|
|
|
418
|
+
response.lesson_import_response = await contentStatusCompletedMany(lessonsToImport, collection)
|
|
421
419
|
const activePath = await getActivePath(brand)
|
|
420
|
+
|
|
422
421
|
if (activePath.active_learning_path_id === learningPathId) {
|
|
423
422
|
response.update_dailies_response = await updateDailySession(
|
|
424
423
|
brand,
|
|
@@ -428,6 +427,8 @@ export async function completeLearningPathIntroVideo(
|
|
|
428
427
|
}
|
|
429
428
|
}
|
|
430
429
|
|
|
430
|
+
response.intro_video_response = await completeIfNotCompleted(introVideoId)
|
|
431
|
+
|
|
431
432
|
return response
|
|
432
433
|
}
|
|
433
434
|
|
|
@@ -45,8 +45,8 @@ export async function getNavigateToForMethod(data) {
|
|
|
45
45
|
|
|
46
46
|
const {content, collection} = tuple
|
|
47
47
|
|
|
48
|
-
const findFirstIncomplete = (progresses) =>
|
|
49
|
-
|
|
48
|
+
const findFirstIncomplete = (ids, progresses) =>
|
|
49
|
+
ids.find(id => progresses[id] !== STATE_COMPLETED) || ids[0]
|
|
50
50
|
|
|
51
51
|
const findChildById = (children, id) =>
|
|
52
52
|
children?.find(child => child.id === Number(id)) || null
|
|
@@ -56,7 +56,7 @@ export async function getNavigateToForMethod(data) {
|
|
|
56
56
|
if (childrenIds.length === 0) return null
|
|
57
57
|
|
|
58
58
|
const progresses = await getProgressStateByIds(childrenIds, collection)
|
|
59
|
-
const incompleteId = findFirstIncomplete(progresses)
|
|
59
|
+
const incompleteId = findFirstIncomplete(childrenIds, progresses)
|
|
60
60
|
|
|
61
61
|
return incompleteId ? findChildById(content.children, incompleteId) : content.children[0]
|
|
62
62
|
}
|
|
@@ -64,7 +64,7 @@ export async function getNavigateToForMethod(data) {
|
|
|
64
64
|
const getDailySessionNavigateTo = async (content, dailySession, collection) => {
|
|
65
65
|
const dailiesIds = dailySession?.map(item => item.content_ids).flat() || []
|
|
66
66
|
const progresses = await getProgressStateByIds(dailiesIds, collection)
|
|
67
|
-
const incompleteId = findFirstIncomplete(progresses)
|
|
67
|
+
const incompleteId = findFirstIncomplete(dailiesIds, progresses)
|
|
68
68
|
|
|
69
69
|
return incompleteId ? findChildById(content.children, incompleteId) : null
|
|
70
70
|
}
|
|
@@ -467,13 +467,14 @@ export async function contentStatusReset(contentId, collection = null, {skipPush
|
|
|
467
467
|
return resetStatus(contentId, collection, {skipPush})
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
-
async function saveContentProgress(contentId, collection, progress, currentSeconds, {skipPush = false} = {}) {
|
|
470
|
+
async function saveContentProgress(contentId, collection, progress, currentSeconds, {skipPush = false, hideFromProgressRow = false} = {}) {
|
|
471
471
|
const isLP = collection?.type === COLLECTION_TYPE.LEARNING_PATH
|
|
472
472
|
|
|
473
473
|
// filter out contentIds that are setting progress lower than existing
|
|
474
474
|
const contentIdProgress = await getProgressDataByIds([contentId], collection)
|
|
475
|
-
|
|
476
|
-
|
|
475
|
+
const currentProgress = contentIdProgress[contentId].progress;
|
|
476
|
+
if (progress <= currentProgress) {
|
|
477
|
+
progress = currentProgress;
|
|
477
478
|
}
|
|
478
479
|
|
|
479
480
|
const response = await db.contentProgress.recordProgress(
|
|
@@ -481,11 +482,15 @@ async function saveContentProgress(contentId, collection, progress, currentSecon
|
|
|
481
482
|
collection,
|
|
482
483
|
progress,
|
|
483
484
|
currentSeconds,
|
|
484
|
-
{skipPush: true}
|
|
485
|
+
{skipPush: true, hideFromProgressRow}
|
|
485
486
|
)
|
|
486
487
|
// note - previous implementation explicitly did not trickle progress to children here
|
|
487
488
|
// (only to siblings/parents via le bubbles)
|
|
488
489
|
|
|
490
|
+
if (progress === currentProgress) {
|
|
491
|
+
return
|
|
492
|
+
}
|
|
493
|
+
|
|
489
494
|
const hierarchy = await getHierarchy(contentId, collection)
|
|
490
495
|
|
|
491
496
|
const bubbledProgresses = await bubbleProgress(hierarchy, contentId, collection)
|
|
@@ -499,7 +504,7 @@ async function saveContentProgress(contentId, collection, progress, currentSecon
|
|
|
499
504
|
}
|
|
500
505
|
|
|
501
506
|
// BE bubbling/trickling currently does not work, so we utilize non-tentative pushing when learning path collection
|
|
502
|
-
await db.contentProgress.recordProgressMany(bubbledProgresses, collection, {tentative: !isLP, skipPush: true})
|
|
507
|
+
await db.contentProgress.recordProgressMany(bubbledProgresses, collection, {tentative: !isLP, skipPush: true, hideFromProgressRow})
|
|
503
508
|
|
|
504
509
|
if (isLP) {
|
|
505
510
|
let exportIds = bubbledProgresses
|
|
@@ -577,11 +582,11 @@ async function duplicateLearningPathProgressToExternalContents(ids, collection,
|
|
|
577
582
|
// each handles its own bubbling.
|
|
578
583
|
// skipPush on all but last to avoid multiple push requests
|
|
579
584
|
filteredIds.forEach(([id, pct], index) => {
|
|
585
|
+
let skip = true
|
|
580
586
|
if (index === filteredIds.length - 1) {
|
|
581
|
-
|
|
582
|
-
} else {
|
|
583
|
-
saveContentProgress(parseInt(id), null, pct, null, {skipPush: true})
|
|
587
|
+
skip = skipPush
|
|
584
588
|
}
|
|
589
|
+
saveContentProgress(parseInt(id), null, pct, null, {skipPush: skip, hideFromProgressRow: true})
|
|
585
590
|
})
|
|
586
591
|
}
|
|
587
592
|
|
|
@@ -53,6 +53,9 @@ export default class ContentProgress extends BaseModel<{
|
|
|
53
53
|
get resume_time_seconds() {
|
|
54
54
|
return (this._getRaw('resume_time_seconds') as number) || null
|
|
55
55
|
}
|
|
56
|
+
get hide_from_progress_row() {
|
|
57
|
+
return this._getRaw('hide_from_progress_row') as boolean
|
|
58
|
+
}
|
|
56
59
|
|
|
57
60
|
set content_id(value: number) {
|
|
58
61
|
// unsigned int
|
|
@@ -87,5 +90,8 @@ export default class ContentProgress extends BaseModel<{
|
|
|
87
90
|
throwIfNotNullableNumber(value)
|
|
88
91
|
this._setRaw('resume_time_seconds', value !== null ? throwIfOutsideRange(value, 0, 65535) : value)
|
|
89
92
|
}
|
|
93
|
+
set hide_from_progress_row(value: boolean) {
|
|
94
|
+
this._setRaw('hide_from_progress_row', value)
|
|
95
|
+
}
|
|
90
96
|
|
|
91
97
|
}
|
|
@@ -62,6 +62,8 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
62
62
|
Q.where('collection_type', COLLECTION_TYPE.SELF),
|
|
63
63
|
Q.where('collection_id', COLLECTION_ID_SELF),
|
|
64
64
|
|
|
65
|
+
Q.where('hide_from_progress_row', false),
|
|
66
|
+
|
|
65
67
|
Q.or(Q.where('state', STATE.STARTED), Q.where('state', STATE.COMPLETED)),
|
|
66
68
|
Q.sortBy('updated_at', 'desc'),
|
|
67
69
|
]
|
|
@@ -133,7 +135,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
recordProgress(contentId: number, collection: CollectionParameter | null, progressPct: number, resumeTime?: number, {skipPush = false} = {}) {
|
|
138
|
+
recordProgress(contentId: number, collection: CollectionParameter | null, progressPct: number, resumeTime?: number, {skipPush = false, hideFromProgressRow = false} = {}) {
|
|
137
139
|
const id = ProgressRepository.generateId(contentId, collection)
|
|
138
140
|
|
|
139
141
|
const result = this.upsertOne(id, (r) => {
|
|
@@ -146,6 +148,8 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
146
148
|
if (typeof resumeTime != 'undefined') {
|
|
147
149
|
r.resume_time_seconds = Math.floor(resumeTime)
|
|
148
150
|
}
|
|
151
|
+
|
|
152
|
+
r.hide_from_progress_row = hideFromProgressRow
|
|
149
153
|
}, { skipPush })
|
|
150
154
|
|
|
151
155
|
// Emit event AFTER database write completes
|
|
@@ -176,7 +180,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
176
180
|
recordProgressMany(
|
|
177
181
|
contentProgresses: Record<string, number>, // Accept plain object
|
|
178
182
|
collection: CollectionParameter | null,
|
|
179
|
-
{ tentative = true, skipPush = false }: { tentative?: boolean; skipPush?: boolean } = {}
|
|
183
|
+
{ tentative = true, skipPush = false, hideFromProgressRow = false }: { tentative?: boolean; skipPush?: boolean; hideFromProgressRow?: boolean } = {}
|
|
180
184
|
) {
|
|
181
185
|
|
|
182
186
|
const data = Object.fromEntries(
|
|
@@ -188,6 +192,8 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
188
192
|
r.collection_id = collection?.id ?? COLLECTION_ID_SELF
|
|
189
193
|
|
|
190
194
|
r.progress_percent = progressPct
|
|
195
|
+
|
|
196
|
+
r.hide_from_progress_row = hideFromProgressRow
|
|
191
197
|
},
|
|
192
198
|
])
|
|
193
199
|
)
|
|
@@ -26,6 +26,7 @@ const contentProgressTable = tableSchema({
|
|
|
26
26
|
{ name: 'state', type: 'string', isIndexed: true },
|
|
27
27
|
{ name: 'progress_percent', type: 'number' },
|
|
28
28
|
{ name: 'resume_time_seconds', type: 'number', isOptional: true },
|
|
29
|
+
{ name: 'hide_from_progress_row', type: 'boolean'},
|
|
29
30
|
{ name: 'created_at', type: 'number' },
|
|
30
31
|
{ name: 'updated_at', type: 'number', isIndexed: true }
|
|
31
32
|
]
|