musora-content-services 2.93.1 → 2.93.2
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 +7 -0
- package/package.json +1 -1
- package/src/index.d.ts +4 -0
- package/src/index.js +4 -0
- package/src/services/content-org/learning-paths.ts +31 -18
- package/src/services/contentProgress.js +50 -13
- package/src/services/progress-row/method-card.js +2 -1
- package/src/services/sanity.js +24 -3
- package/src/services/sync/models/ContentProgress.ts +1 -1
- package/src/services/sync/repositories/content-progress.ts +34 -7
- package/src/services/userActivity.js +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [2.93.2](https://github.com/railroadmedia/musora-content-services/compare/v2.93.1...v2.93.2) (2025-12-02)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* progress fixes and features with watermelon ([#607](https://github.com/railroadmedia/musora-content-services/issues/607)) ([005403c](https://github.com/railroadmedia/musora-content-services/commit/005403c9bbaeebe8c5ecb5315754de4448bcbf43))
|
|
11
|
+
|
|
5
12
|
### [2.93.1](https://github.com/railroadmedia/musora-content-services/compare/v2.93.0...v2.93.1) (2025-12-02)
|
|
6
13
|
|
|
7
14
|
## [2.93.0](https://github.com/railroadmedia/musora-content-services/compare/v2.92.7...v2.93.0) (2025-12-02)
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
contentStatusCompleted,
|
|
92
92
|
contentStatusReset,
|
|
93
93
|
contentStatusStarted,
|
|
94
|
+
contentsStatusCompleted,
|
|
94
95
|
getAllCompleted,
|
|
95
96
|
getAllCompletedByIds,
|
|
96
97
|
getAllStarted,
|
|
@@ -250,6 +251,7 @@ import {
|
|
|
250
251
|
fetchContentRows,
|
|
251
252
|
fetchFoundation,
|
|
252
253
|
fetchHierarchy,
|
|
254
|
+
fetchLearningPathHierarchy,
|
|
253
255
|
fetchLeaving,
|
|
254
256
|
fetchLessonContent,
|
|
255
257
|
fetchLessonsFeaturingThisContent,
|
|
@@ -428,6 +430,7 @@ declare module 'musora-content-services' {
|
|
|
428
430
|
contentStatusCompleted,
|
|
429
431
|
contentStatusReset,
|
|
430
432
|
contentStatusStarted,
|
|
433
|
+
contentsStatusCompleted,
|
|
431
434
|
convertToTimeZone,
|
|
432
435
|
createComment,
|
|
433
436
|
createForumCategory,
|
|
@@ -494,6 +497,7 @@ declare module 'musora-content-services' {
|
|
|
494
497
|
fetchInterests,
|
|
495
498
|
fetchLastInteractedChild,
|
|
496
499
|
fetchLatestThreads,
|
|
500
|
+
fetchLearningPathHierarchy,
|
|
497
501
|
fetchLearningPathLessons,
|
|
498
502
|
fetchLearningPathProgressCheckLessons,
|
|
499
503
|
fetchLeaving,
|
package/src/index.js
CHANGED
|
@@ -95,6 +95,7 @@ import {
|
|
|
95
95
|
contentStatusCompleted,
|
|
96
96
|
contentStatusReset,
|
|
97
97
|
contentStatusStarted,
|
|
98
|
+
contentsStatusCompleted,
|
|
98
99
|
getAllCompleted,
|
|
99
100
|
getAllCompletedByIds,
|
|
100
101
|
getAllStarted,
|
|
@@ -254,6 +255,7 @@ import {
|
|
|
254
255
|
fetchContentRows,
|
|
255
256
|
fetchFoundation,
|
|
256
257
|
fetchHierarchy,
|
|
258
|
+
fetchLearningPathHierarchy,
|
|
257
259
|
fetchLeaving,
|
|
258
260
|
fetchLessonContent,
|
|
259
261
|
fetchLessonsFeaturingThisContent,
|
|
@@ -427,6 +429,7 @@ export {
|
|
|
427
429
|
contentStatusCompleted,
|
|
428
430
|
contentStatusReset,
|
|
429
431
|
contentStatusStarted,
|
|
432
|
+
contentsStatusCompleted,
|
|
430
433
|
convertToTimeZone,
|
|
431
434
|
createComment,
|
|
432
435
|
createForumCategory,
|
|
@@ -493,6 +496,7 @@ export {
|
|
|
493
496
|
fetchInterests,
|
|
494
497
|
fetchLastInteractedChild,
|
|
495
498
|
fetchLatestThreads,
|
|
499
|
+
fetchLearningPathHierarchy,
|
|
496
500
|
fetchLearningPathLessons,
|
|
497
501
|
fetchLearningPathProgressCheckLessons,
|
|
498
502
|
fetchLeaving,
|
|
@@ -7,11 +7,14 @@ import { fetchByRailContentId, fetchMethodV2Structure } from '../sanity.js'
|
|
|
7
7
|
import { addContextToContent } from '../contentAggregator.js'
|
|
8
8
|
import {
|
|
9
9
|
contentStatusCompleted,
|
|
10
|
+
contentsStatusCompleted,
|
|
10
11
|
contentStatusReset,
|
|
11
12
|
getAllCompletedByIds,
|
|
12
13
|
getProgressState,
|
|
13
14
|
} from '../contentProgress.js'
|
|
14
|
-
import { STATE } from
|
|
15
|
+
import { COLLECTION_TYPE, STATE } from "../sync/models/ContentProgress";
|
|
16
|
+
import { SyncWriteDTO } from "../sync";
|
|
17
|
+
import { ContentProgress } from "../sync/models";
|
|
15
18
|
|
|
16
19
|
const BASE_PATH: string = `/api/content-org`
|
|
17
20
|
const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
|
|
@@ -22,6 +25,20 @@ interface ActiveLearningPathResponse {
|
|
|
22
25
|
active_learning_path_id: number,
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
interface DailySessionResponse {
|
|
29
|
+
user_id: number,
|
|
30
|
+
brand: string,
|
|
31
|
+
user_date: string
|
|
32
|
+
daily_session: DailySession[],
|
|
33
|
+
active_learning_path_id: number,
|
|
34
|
+
active_learning_path_created_at: string,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface DailySession {
|
|
38
|
+
content_ids: number[],
|
|
39
|
+
learning_path_id: number,
|
|
40
|
+
}
|
|
41
|
+
|
|
25
42
|
/**
|
|
26
43
|
* Gets today's daily session for the user.
|
|
27
44
|
* @param brand
|
|
@@ -30,7 +47,7 @@ interface ActiveLearningPathResponse {
|
|
|
30
47
|
export async function getDailySession(brand: string, userDate: Date) {
|
|
31
48
|
const stringDate = userDate.toISOString().split('T')[0]
|
|
32
49
|
const url: string = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${stringDate}`
|
|
33
|
-
return await fetchHandler(url, 'GET', null, null)
|
|
50
|
+
return await fetchHandler(url, 'GET', null, null) as DailySessionResponse
|
|
34
51
|
}
|
|
35
52
|
|
|
36
53
|
/**
|
|
@@ -47,7 +64,7 @@ export async function updateDailySession(
|
|
|
47
64
|
const stringDate = userDate.toISOString().split('T')[0]
|
|
48
65
|
const url: string = `${LEARNING_PATHS_PATH}/daily-session/create`
|
|
49
66
|
const body = { brand: brand, userDate: stringDate, keepFirstLearningPath: keepFirstLearningPath }
|
|
50
|
-
return await fetchHandler(url, 'POST', null, body)
|
|
67
|
+
return await fetchHandler(url, 'POST', null, body) as DailySessionResponse
|
|
51
68
|
}
|
|
52
69
|
|
|
53
70
|
/**
|
|
@@ -56,7 +73,7 @@ export async function updateDailySession(
|
|
|
56
73
|
*/
|
|
57
74
|
export async function getActivePath(brand: string) {
|
|
58
75
|
const url: string = `${LEARNING_PATHS_PATH}/active-path/get?brand=${brand}`
|
|
59
|
-
return await fetchHandler(url, 'GET', null, null)
|
|
76
|
+
return await fetchHandler(url, 'GET', null, null) as ActiveLearningPathResponse
|
|
60
77
|
}
|
|
61
78
|
|
|
62
79
|
/**
|
|
@@ -67,7 +84,7 @@ export async function getActivePath(brand: string) {
|
|
|
67
84
|
export async function startLearningPath(brand: string, learningPathId: number) {
|
|
68
85
|
const url: string = `${LEARNING_PATHS_PATH}/active-path/set`
|
|
69
86
|
const body = { brand: brand, learning_path_id: learningPathId }
|
|
70
|
-
return await fetchHandler(url, 'POST', null, body)
|
|
87
|
+
return await fetchHandler(url, 'POST', null, body) as ActiveLearningPathResponse
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
/**
|
|
@@ -87,9 +104,9 @@ export async function getEnrichedLearningPath(learningPathId) {
|
|
|
87
104
|
const response = (await addContextToContent(
|
|
88
105
|
fetchByRailContentId,
|
|
89
106
|
learningPathId,
|
|
90
|
-
|
|
107
|
+
COLLECTION_TYPE.LEARNING_PATH,
|
|
91
108
|
{
|
|
92
|
-
collection: { id: learningPathId, type:
|
|
109
|
+
collection: { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH },
|
|
93
110
|
dataField: 'children',
|
|
94
111
|
dataField_includeParent: true,
|
|
95
112
|
addProgressStatus: true,
|
|
@@ -241,7 +258,7 @@ export async function fetchLearningPathProgressCheckLessons(contentIds: number[]
|
|
|
241
258
|
}
|
|
242
259
|
|
|
243
260
|
interface completeMethodIntroVideo {
|
|
244
|
-
intro_video_response:
|
|
261
|
+
intro_video_response: SyncWriteDTO<ContentProgress, any> | null,
|
|
245
262
|
active_path_response: ActiveLearningPathResponse
|
|
246
263
|
}
|
|
247
264
|
/**
|
|
@@ -267,9 +284,9 @@ export async function completeMethodIntroVideo(introVideoId: number, brand: stri
|
|
|
267
284
|
}
|
|
268
285
|
|
|
269
286
|
interface completeLearningPathIntroVideo {
|
|
270
|
-
intro_video_response:
|
|
271
|
-
learning_path_reset_response:
|
|
272
|
-
lesson_import_response:
|
|
287
|
+
intro_video_response: SyncWriteDTO<ContentProgress, any> | null,
|
|
288
|
+
learning_path_reset_response: SyncWriteDTO<ContentProgress, any> | null,
|
|
289
|
+
lesson_import_response: SyncWriteDTO<ContentProgress, any> | null
|
|
273
290
|
}
|
|
274
291
|
/**
|
|
275
292
|
* Handles completion of learning path intro video and other related actions.
|
|
@@ -286,24 +303,20 @@ export async function completeLearningPathIntroVideo(introVideoId: number, learn
|
|
|
286
303
|
|
|
287
304
|
response.intro_video_response = await completeIfNotCompleted(introVideoId)
|
|
288
305
|
|
|
289
|
-
const collection = { id: learningPathId, type:
|
|
306
|
+
const collection = { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH }
|
|
290
307
|
|
|
291
308
|
if (!lessonsToImport) {
|
|
292
309
|
response.learning_path_reset_response = await contentStatusReset(learningPathId, collection)
|
|
293
310
|
|
|
294
311
|
} else {
|
|
295
|
-
|
|
296
|
-
for (const contentId of lessonsToImport) {
|
|
297
|
-
// todo: create bulk complete endpoint with bubbling. and set up watermelon method bubbling
|
|
298
|
-
response.lesson_import_response[contentId] = await contentStatusCompleted(contentId, collection)
|
|
299
|
-
}
|
|
312
|
+
response.lesson_import_response = await contentsStatusCompleted(lessonsToImport, collection)
|
|
300
313
|
}
|
|
301
314
|
|
|
302
315
|
return response
|
|
303
316
|
}
|
|
304
317
|
|
|
305
318
|
|
|
306
|
-
async function completeIfNotCompleted(contentId: number): Promise<
|
|
319
|
+
async function completeIfNotCompleted(contentId: number): Promise<SyncWriteDTO<ContentProgress, any> | null> {
|
|
307
320
|
const introVideoStatus = await getProgressState(contentId)
|
|
308
321
|
|
|
309
322
|
return introVideoStatus !== 'completed' ? await contentStatusCompleted(contentId) : null
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { fetchHierarchy } from './sanity.js'
|
|
1
|
+
import { fetchHierarchy, fetchLearningPathHierarchy } from './sanity.js'
|
|
2
2
|
import { db } from './sync'
|
|
3
|
-
import { STATE
|
|
3
|
+
import {COLLECTION_TYPE, STATE} from './sync/models/ContentProgress'
|
|
4
4
|
import { trackUserPractice, findIncompleteLesson } from './userActivity'
|
|
5
5
|
import { getNextLessonLessonParentTypes } from '../contentTypeConfig.js'
|
|
6
6
|
|
|
@@ -66,7 +66,7 @@ export async function getNavigateTo(data, collection = null) {
|
|
|
66
66
|
let incompleteChild = findIncompleteLesson(childrenStates, lastInteracted, content.type)
|
|
67
67
|
navigateToData[content.id] = buildNavigateTo(children.get(incompleteChild), null, collection)
|
|
68
68
|
}
|
|
69
|
-
} else if (['song-tutorial', 'guided-course',
|
|
69
|
+
} else if (['song-tutorial', 'guided-course', COLLECTION_TYPE.LEARNING_PATH].includes(content.type)) { // send to first incomplete
|
|
70
70
|
let incompleteChild = findIncompleteLesson(childrenStates, lastInteracted, content.type)
|
|
71
71
|
navigateToData[content.id] = buildNavigateTo(children.get(incompleteChild), null, collection)
|
|
72
72
|
} else if (twoDepthContentTypes.includes(content.type)) { // send to navigateTo child of last interacted child
|
|
@@ -268,6 +268,11 @@ async function trackProgress(contentId, collection, currentSeconds, mediaLengthS
|
|
|
268
268
|
export async function contentStatusCompleted(contentId, collection = null) {
|
|
269
269
|
return setStartedOrCompletedStatus(contentId, collection, true)
|
|
270
270
|
}
|
|
271
|
+
|
|
272
|
+
export async function contentsStatusCompleted(contentIds, collection = null) {
|
|
273
|
+
return setStartedOrCompletedStatuses(contentIds, collection, true)
|
|
274
|
+
}
|
|
275
|
+
|
|
271
276
|
export async function contentStatusStarted(contentId, collection = null) {
|
|
272
277
|
return setStartedOrCompletedStatus(contentId, collection, false)
|
|
273
278
|
}
|
|
@@ -276,12 +281,12 @@ export async function contentStatusReset(contentId, collection = null) {
|
|
|
276
281
|
}
|
|
277
282
|
|
|
278
283
|
async function saveContentProgress(contentId, collection, progress, currentSeconds) {
|
|
279
|
-
const response = await db.contentProgress.
|
|
284
|
+
const response = await db.contentProgress.recordProgress(contentId, collection, progress, currentSeconds)
|
|
280
285
|
|
|
281
286
|
// note - previous implementation explicitly did not trickle progress to children here
|
|
282
287
|
// (only to siblings/parents via le bubbles)
|
|
283
288
|
|
|
284
|
-
const bubbledProgresses = bubbleProgress(await
|
|
289
|
+
const bubbledProgresses = bubbleProgress(await getHierarchy(contentId, collection), contentId, collection)
|
|
285
290
|
await db.contentProgress.recordProgressesTentative(bubbledProgresses, collection)
|
|
286
291
|
|
|
287
292
|
return response
|
|
@@ -292,23 +297,55 @@ async function setStartedOrCompletedStatus(contentId, collection, isCompleted) {
|
|
|
292
297
|
// we explicitly pessimistically await a remote push here
|
|
293
298
|
// because awards may be generated (on server) on completion
|
|
294
299
|
// which we would want to toast the user about *in band*
|
|
295
|
-
const response = await db.contentProgress.
|
|
300
|
+
const response = await db.contentProgress.recordProgress(contentId, collection, progress)
|
|
301
|
+
|
|
302
|
+
const hierarchy = await getHierarchy(contentId, collection)
|
|
303
|
+
|
|
304
|
+
await Promise.all([
|
|
305
|
+
db.contentProgress.recordProgressesTentative(trickleProgress(hierarchy, contentId, collection, progress), collection),
|
|
306
|
+
bubbleProgress(hierarchy, contentId, collection).then(bubbledProgresses => db.contentProgress.recordProgressesTentative(bubbledProgresses, collection))
|
|
307
|
+
])
|
|
296
308
|
|
|
297
|
-
|
|
298
|
-
|
|
309
|
+
return response
|
|
310
|
+
}
|
|
299
311
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
312
|
+
async function getHierarchy(contentId, collection) {
|
|
313
|
+
if (collection && collection.type === COLLECTION_TYPE.LEARNING_PATH) {
|
|
314
|
+
return await fetchLearningPathHierarchy(contentId, collection)
|
|
315
|
+
} else {
|
|
316
|
+
return await fetchHierarchy(contentId)
|
|
304
317
|
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function setStartedOrCompletedStatuses(contentIds, collection, isCompleted) {
|
|
321
|
+
const progress = isCompleted ? 100 : 0
|
|
322
|
+
// we explicitly pessimistically await a remote push here
|
|
323
|
+
// because awards may be generated (on server) on completion
|
|
324
|
+
// which we would want to toast the user about *in band*
|
|
325
|
+
const response = await db.contentProgress.recordProgresses(contentIds, collection, progress)
|
|
326
|
+
|
|
327
|
+
// we assume this is used only for contents within the same hierarchy
|
|
328
|
+
const hierarchy = await getHierarchy(contentIds[0], collection)
|
|
329
|
+
|
|
330
|
+
let ids = {}
|
|
331
|
+
for (const contentId of contentIds) {
|
|
332
|
+
ids = {
|
|
333
|
+
...ids,
|
|
334
|
+
...trickleProgress(hierarchy, contentId, collection, progress),
|
|
335
|
+
...await bubbleProgress(hierarchy, contentId, collection)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
await Promise.all([
|
|
340
|
+
db.contentProgress.recordProgressesTentative(ids, collection),
|
|
341
|
+
]);
|
|
305
342
|
|
|
306
343
|
return response
|
|
307
344
|
}
|
|
308
345
|
|
|
309
346
|
async function resetStatus(contentId, collection = null) {
|
|
310
347
|
const response = await db.contentProgress.eraseProgress(contentId, collection)
|
|
311
|
-
const hierarchy = await
|
|
348
|
+
const hierarchy = await getHierarchy(contentId, collection)
|
|
312
349
|
|
|
313
350
|
await Promise.all([
|
|
314
351
|
db.contentProgress.recordProgressesTentative(trickleProgress(hierarchy, contentId, collection, 0), collection),
|
|
@@ -6,6 +6,7 @@ import { getActivePath, fetchLearningPathLessons } from '../content-org/learning
|
|
|
6
6
|
import { getToday } from '../dateUtils.js'
|
|
7
7
|
import { fetchMethodV2IntroVideo } from '../sanity'
|
|
8
8
|
import { getProgressState } from '../contentProgress'
|
|
9
|
+
import {COLLECTION_TYPE} from "../sync/models/ContentProgress.js";
|
|
9
10
|
|
|
10
11
|
export async function getMethodCard(brand) {
|
|
11
12
|
const introVideo = await fetchMethodV2IntroVideo(brand)
|
|
@@ -82,7 +83,7 @@ export async function getMethodCard(brand) {
|
|
|
82
83
|
|
|
83
84
|
return {
|
|
84
85
|
id: 1,
|
|
85
|
-
type:
|
|
86
|
+
type: COLLECTION_TYPE.LEARNING_PATH,
|
|
86
87
|
progressType: 'method',
|
|
87
88
|
header: 'Method',
|
|
88
89
|
body: learningPath,
|
package/src/services/sanity.js
CHANGED
|
@@ -1523,6 +1523,25 @@ export async function fetchTopLevelParentId(railcontentId) {
|
|
|
1523
1523
|
return response['railcontent_id']
|
|
1524
1524
|
}
|
|
1525
1525
|
|
|
1526
|
+
export async function fetchLearningPathHierarchy(railcontentId, collection) {
|
|
1527
|
+
if (!collection) {
|
|
1528
|
+
return null
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
const topLevelId = collection.id
|
|
1532
|
+
|
|
1533
|
+
let response = await fetchByRailContentId(topLevelId, collection.type)
|
|
1534
|
+
if (!response) return null
|
|
1535
|
+
|
|
1536
|
+
let data = {
|
|
1537
|
+
topLevelId: topLevelId,
|
|
1538
|
+
parents: {},
|
|
1539
|
+
children: {},
|
|
1540
|
+
}
|
|
1541
|
+
populateHierarchyLookups(response, data, null)
|
|
1542
|
+
return data
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1526
1545
|
export async function fetchHierarchy(railcontentId) {
|
|
1527
1546
|
let topLevelId = await fetchTopLevelParentId(railcontentId)
|
|
1528
1547
|
const childrenFilter = await new FilterBuilder(``, { isChildrenFilter: true }).buildFilter()
|
|
@@ -1557,12 +1576,14 @@ export async function fetchHierarchy(railcontentId) {
|
|
|
1557
1576
|
}
|
|
1558
1577
|
|
|
1559
1578
|
function populateHierarchyLookups(currentLevel, data, parentId) {
|
|
1560
|
-
|
|
1579
|
+
const railcontentIdField = currentLevel.railcontent_id ? "railcontent_id" : "id";
|
|
1580
|
+
|
|
1581
|
+
let contentId = currentLevel[railcontentIdField]
|
|
1561
1582
|
let children = currentLevel['children']
|
|
1562
1583
|
|
|
1563
1584
|
data.parents[contentId] = parentId
|
|
1564
1585
|
if (children) {
|
|
1565
|
-
data.children[contentId] = children.map((child) => child[
|
|
1586
|
+
data.children[contentId] = children.map((child) => child[railcontentIdField])
|
|
1566
1587
|
for (let i = 0; i < children.length; i++) {
|
|
1567
1588
|
populateHierarchyLookups(children[i], data, contentId)
|
|
1568
1589
|
}
|
|
@@ -1572,7 +1593,7 @@ function populateHierarchyLookups(currentLevel, data, parentId) {
|
|
|
1572
1593
|
|
|
1573
1594
|
let assignments = currentLevel['assignments']
|
|
1574
1595
|
if (assignments) {
|
|
1575
|
-
let assignmentIds = assignments.map((assignment) => assignment[
|
|
1596
|
+
let assignmentIds = assignments.map((assignment) => assignment[railcontentIdField])
|
|
1576
1597
|
data.children[contentId] = (data.children[contentId] ?? []).concat(assignmentIds)
|
|
1577
1598
|
assignmentIds.forEach((assignmentId) => {
|
|
1578
1599
|
data.parents[assignmentId] = contentId
|
|
@@ -115,10 +115,10 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
115
115
|
return await this.queryAll(...clauses)
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
recordProgress(contentId: number, collection: { type: COLLECTION_TYPE; id: number } | null, progressPct: number, resumeTime?: number) {
|
|
119
119
|
const id = ProgressRepository.generateId(contentId, collection)
|
|
120
120
|
|
|
121
|
-
return this.
|
|
121
|
+
return this.upsertOne(id, (r) => {
|
|
122
122
|
r.content_id = contentId
|
|
123
123
|
r.collection_type = collection?.type ?? null
|
|
124
124
|
r.collection_id = collection?.id ?? null
|
|
@@ -132,12 +132,16 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
132
132
|
})
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
recordProgresses(
|
|
136
|
+
contentIds: number[],
|
|
137
|
+
collection: { type: COLLECTION_TYPE; id: number } | null,
|
|
138
|
+
progressPct: number
|
|
139
|
+
) {
|
|
140
|
+
return this.upsertSome(
|
|
137
141
|
Object.fromEntries(
|
|
138
|
-
|
|
139
|
-
ProgressRepository.generateId(contentId,
|
|
140
|
-
(r) => {
|
|
142
|
+
contentIds.map((contentId) => [
|
|
143
|
+
ProgressRepository.generateId(contentId, collection),
|
|
144
|
+
(r: ContentProgress) => {
|
|
141
145
|
r.content_id = contentId
|
|
142
146
|
r.collection_type = collection?.type ?? null
|
|
143
147
|
r.collection_id = collection?.id ?? null
|
|
@@ -150,6 +154,29 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
150
154
|
)
|
|
151
155
|
}
|
|
152
156
|
|
|
157
|
+
recordProgressesTentative(
|
|
158
|
+
contentProgresses: Record<string, number>, // Accept plain object
|
|
159
|
+
collection: { type: COLLECTION_TYPE; id: number } | null
|
|
160
|
+
) {
|
|
161
|
+
const data = Object.fromEntries(
|
|
162
|
+
Object.entries(contentProgresses).map(([contentId, progressPct]) => {
|
|
163
|
+
const generatedId = ProgressRepository.generateId(Number(contentId), collection)
|
|
164
|
+
console.log('Processing:', { contentId, progressPct, generatedId, collection })
|
|
165
|
+
return [
|
|
166
|
+
generatedId,
|
|
167
|
+
(record: ContentProgress) => {
|
|
168
|
+
record.content_id = Number(contentId)
|
|
169
|
+
record.collection_type = collection?.type ?? null
|
|
170
|
+
record.collection_id = collection?.id ?? null
|
|
171
|
+
record.state = progressPct === 100 ? STATE.COMPLETED : STATE.STARTED
|
|
172
|
+
record.progress_percent = progressPct
|
|
173
|
+
},
|
|
174
|
+
]
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
return this.upsertSomeTentative(data)
|
|
178
|
+
}
|
|
179
|
+
|
|
153
180
|
eraseProgress(contentId: number, collection: { type: COLLECTION_TYPE; id: number } | null) {
|
|
154
181
|
return this.deleteOne(ProgressRepository.generateId(contentId, collection))
|
|
155
182
|
}
|
|
@@ -40,6 +40,7 @@ import dayjs from 'dayjs'
|
|
|
40
40
|
import { addContextToContent } from './contentAggregator.js'
|
|
41
41
|
import { getMethodCard } from './progress-row/method-card.js'
|
|
42
42
|
import { db, Q } from './sync'
|
|
43
|
+
import {COLLECTION_TYPE} from "./sync/models/ContentProgress.js";
|
|
43
44
|
|
|
44
45
|
const DATA_KEY_PRACTICES = 'practices'
|
|
45
46
|
|
|
@@ -1039,7 +1040,7 @@ export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}) {
|
|
|
1039
1040
|
switch (item.type) {
|
|
1040
1041
|
case 'playlist':
|
|
1041
1042
|
return processPlaylistItem(item)
|
|
1042
|
-
case
|
|
1043
|
+
case COLLECTION_TYPE.LEARNING_PATH:
|
|
1043
1044
|
case 'method':
|
|
1044
1045
|
return item
|
|
1045
1046
|
default:
|
|
@@ -1282,7 +1283,7 @@ function mergeAndSortItems(items, limit) {
|
|
|
1282
1283
|
|
|
1283
1284
|
export function findIncompleteLesson(progressOnItems, currentContentId, contentType) {
|
|
1284
1285
|
const ids = Object.keys(progressOnItems).map(Number)
|
|
1285
|
-
if (contentType === 'guided-course' || contentType ===
|
|
1286
|
+
if (contentType === 'guided-course' || contentType === COLLECTION_TYPE.LEARNING_PATH) {
|
|
1286
1287
|
// Return first incomplete lesson
|
|
1287
1288
|
return ids.find((id) => progressOnItems[id] !== 'completed') || ids.at(0)
|
|
1288
1289
|
}
|