musora-content-services 2.95.5 → 2.96.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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/index.d.ts +7 -1
- package/src/index.js +7 -1
- package/src/services/content/artist.ts +6 -1
- package/src/services/content/genre.ts +6 -1
- package/src/services/content/instructor.ts +1 -1
- package/src/services/content-org/learning-paths.ts +66 -31
- package/src/services/contentProgress.js +144 -61
- package/src/services/progress-events.js +53 -1
- package/src/services/sync/fetch.ts +1 -6
- package/src/services/sync/manager.ts +32 -16
- package/src/services/sync/models/ContentProgress.ts +9 -6
- package/src/services/sync/repositories/content-progress.ts +22 -26
- package/src/services/sync/store/index.ts +1 -1
- package/test/initializeTests.js +4 -2
- package/test/learningPaths.test.js +59 -10
- package/test/sync/adapter.ts +41 -0
- package/test/sync/initialize-sync-manager.js +104 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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.96.1](https://github.com/railroadmedia/musora-content-services/compare/v2.96.0...v2.96.1) (2025-12-09)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **agi:** interface return types ([cc0cfae](https://github.com/railroadmedia/musora-content-services/commit/cc0cfae1282266567ac46a6eb072f76ffde7032e))
|
|
11
|
+
|
|
12
|
+
## [2.96.0](https://github.com/railroadmedia/musora-content-services/compare/v2.95.5...v2.96.0) (2025-12-09)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* handle learning path complete logic ([#628](https://github.com/railroadmedia/musora-content-services/issues/628)) ([e5266d2](https://github.com/railroadmedia/musora-content-services/commit/e5266d2219597fc295dab97cec7b7ce8ac80a705))
|
|
18
|
+
|
|
5
19
|
### [2.95.5](https://github.com/railroadmedia/musora-content-services/compare/v2.95.4...v2.95.5) (2025-12-08)
|
|
6
20
|
|
|
7
21
|
### [2.95.4](https://github.com/railroadmedia/musora-content-services/compare/v2.95.3...v2.95.4) (2025-12-08)
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
getEnrichedLearningPath,
|
|
53
53
|
getLearningPathLessonsByIds,
|
|
54
54
|
mapContentToParent,
|
|
55
|
+
onContentCompletedLearningPathListener,
|
|
55
56
|
resetAllLearningPaths,
|
|
56
57
|
startLearningPath,
|
|
57
58
|
updateDailySession
|
|
@@ -193,7 +194,9 @@ import {
|
|
|
193
194
|
} from './services/liveTesting.ts';
|
|
194
195
|
|
|
195
196
|
import {
|
|
197
|
+
emitContentCompleted,
|
|
196
198
|
emitProgressSaved,
|
|
199
|
+
onContentCompleted,
|
|
197
200
|
onProgressSaved
|
|
198
201
|
} from './services/progress-events.js';
|
|
199
202
|
|
|
@@ -423,7 +426,7 @@ import {
|
|
|
423
426
|
} from './services/userActivity.js';
|
|
424
427
|
|
|
425
428
|
import {
|
|
426
|
-
default as EventsAPI
|
|
429
|
+
default as EventsAPI
|
|
427
430
|
} from './services/eventsAPI';
|
|
428
431
|
|
|
429
432
|
declare module 'musora-content-services' {
|
|
@@ -468,6 +471,7 @@ declare module 'musora-content-services' {
|
|
|
468
471
|
deleteUserActivity,
|
|
469
472
|
duplicatePlaylist,
|
|
470
473
|
editComment,
|
|
474
|
+
emitContentCompleted,
|
|
471
475
|
emitProgressSaved,
|
|
472
476
|
enrollUserInGuidedCourse,
|
|
473
477
|
extractSanityUrl,
|
|
@@ -651,6 +655,8 @@ declare module 'musora-content-services' {
|
|
|
651
655
|
markNotificationAsUnread,
|
|
652
656
|
markThreadAsRead,
|
|
653
657
|
numberOfActiveUsers,
|
|
658
|
+
onContentCompleted,
|
|
659
|
+
onContentCompletedLearningPathListener,
|
|
654
660
|
onProgressSaved,
|
|
655
661
|
openComment,
|
|
656
662
|
otherStats,
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*** This file was generated automatically. To recreate, please run `npm run build-index`. ***/
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
-
default as EventsAPI
|
|
4
|
+
default as EventsAPI
|
|
5
5
|
} from './services/eventsAPI';
|
|
6
6
|
|
|
7
7
|
import {
|
|
@@ -56,6 +56,7 @@ import {
|
|
|
56
56
|
getEnrichedLearningPath,
|
|
57
57
|
getLearningPathLessonsByIds,
|
|
58
58
|
mapContentToParent,
|
|
59
|
+
onContentCompletedLearningPathListener,
|
|
59
60
|
resetAllLearningPaths,
|
|
60
61
|
startLearningPath,
|
|
61
62
|
updateDailySession
|
|
@@ -197,7 +198,9 @@ import {
|
|
|
197
198
|
} from './services/liveTesting.ts';
|
|
198
199
|
|
|
199
200
|
import {
|
|
201
|
+
emitContentCompleted,
|
|
200
202
|
emitProgressSaved,
|
|
203
|
+
onContentCompleted,
|
|
201
204
|
onProgressSaved
|
|
202
205
|
} from './services/progress-events.js';
|
|
203
206
|
|
|
@@ -467,6 +470,7 @@ export {
|
|
|
467
470
|
deleteUserActivity,
|
|
468
471
|
duplicatePlaylist,
|
|
469
472
|
editComment,
|
|
473
|
+
emitContentCompleted,
|
|
470
474
|
emitProgressSaved,
|
|
471
475
|
enrollUserInGuidedCourse,
|
|
472
476
|
extractSanityUrl,
|
|
@@ -650,6 +654,8 @@ export {
|
|
|
650
654
|
markNotificationAsUnread,
|
|
651
655
|
markThreadAsRead,
|
|
652
656
|
numberOfActiveUsers,
|
|
657
|
+
onContentCompleted,
|
|
658
|
+
onContentCompletedLearningPathListener,
|
|
653
659
|
onProgressSaved,
|
|
654
660
|
openComment,
|
|
655
661
|
otherStats,
|
|
@@ -15,6 +15,11 @@ export interface Artist {
|
|
|
15
15
|
lessonCount: number
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export interface Artists {
|
|
19
|
+
data: Artist[]
|
|
20
|
+
total: number
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
/**
|
|
19
24
|
* Fetch all artists with lessons available for a specific brand.
|
|
20
25
|
*
|
|
@@ -29,7 +34,7 @@ export interface Artist {
|
|
|
29
34
|
export async function fetchArtists(
|
|
30
35
|
brand: Brands | string,
|
|
31
36
|
options: BuildQueryOptions = { sort: 'lower(name) asc' }
|
|
32
|
-
): Promise<
|
|
37
|
+
): Promise<Artists> {
|
|
33
38
|
const lessonFilter = await new FilterBuilder(`brand == "${brand}" && references(^._id)`, {
|
|
34
39
|
bypassPermissions: true,
|
|
35
40
|
}).buildFilter()
|
|
@@ -15,6 +15,11 @@ export interface Genre {
|
|
|
15
15
|
thumbnail: string
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
export interface Genres {
|
|
19
|
+
data: Genre[]
|
|
20
|
+
total: number
|
|
21
|
+
}
|
|
22
|
+
|
|
18
23
|
/**
|
|
19
24
|
* Fetch all genres with lessons available for a specific brand.
|
|
20
25
|
*
|
|
@@ -29,7 +34,7 @@ export interface Genre {
|
|
|
29
34
|
export async function fetchGenres(
|
|
30
35
|
brand: Brands | string,
|
|
31
36
|
options: BuildQueryOptions = { sort: 'lower(name) asc' }
|
|
32
|
-
): Promise<
|
|
37
|
+
): Promise<Genres> {
|
|
33
38
|
const lessonFilter = await new FilterBuilder(`brand == "${brand}" && references(^._id)`, {
|
|
34
39
|
bypassPermissions: true,
|
|
35
40
|
}).buildFilter()
|
|
@@ -35,7 +35,7 @@ export interface Instructors {
|
|
|
35
35
|
export async function fetchInstructors(
|
|
36
36
|
brand: Brands | string,
|
|
37
37
|
options: BuildQueryOptions
|
|
38
|
-
): Promise<
|
|
38
|
+
): Promise<Instructors> {
|
|
39
39
|
const lessonFilter = await new FilterBuilder(`brand == "${brand}" && references(^._id)`, {
|
|
40
40
|
bypassPermissions: true,
|
|
41
41
|
}).buildFilter()
|
|
@@ -12,31 +12,31 @@ import {
|
|
|
12
12
|
getAllCompletedByIds,
|
|
13
13
|
getProgressState,
|
|
14
14
|
} from '../contentProgress.js'
|
|
15
|
-
import { COLLECTION_TYPE, STATE } from
|
|
16
|
-
import { SyncWriteDTO } from
|
|
17
|
-
import { ContentProgress } from
|
|
15
|
+
import { COLLECTION_TYPE, STATE } from '../sync/models/ContentProgress'
|
|
16
|
+
import { SyncWriteDTO } from '../sync'
|
|
17
|
+
import { ContentProgress } from '../sync/models'
|
|
18
18
|
|
|
19
19
|
const BASE_PATH: string = `/api/content-org`
|
|
20
20
|
const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
|
|
21
21
|
|
|
22
22
|
interface ActiveLearningPathResponse {
|
|
23
|
-
user_id: number
|
|
24
|
-
brand: string
|
|
25
|
-
active_learning_path_id: number
|
|
23
|
+
user_id: number
|
|
24
|
+
brand: string
|
|
25
|
+
active_learning_path_id: number
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
interface DailySessionResponse {
|
|
29
|
-
user_id: number
|
|
30
|
-
brand: string
|
|
29
|
+
user_id: number
|
|
30
|
+
brand: string
|
|
31
31
|
user_date: string
|
|
32
|
-
daily_session: DailySession[]
|
|
33
|
-
active_learning_path_id: number
|
|
34
|
-
active_learning_path_created_at: string
|
|
32
|
+
daily_session: DailySession[]
|
|
33
|
+
active_learning_path_id: number
|
|
34
|
+
active_learning_path_created_at: string
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
interface DailySession {
|
|
38
|
-
content_ids: number[]
|
|
39
|
-
learning_path_id: number
|
|
38
|
+
content_ids: number[]
|
|
39
|
+
learning_path_id: number
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -47,7 +47,7 @@ interface DailySession {
|
|
|
47
47
|
export async function getDailySession(brand: string, userDate: Date) {
|
|
48
48
|
const stringDate = userDate.toISOString().split('T')[0]
|
|
49
49
|
const url: string = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${stringDate}`
|
|
50
|
-
return await fetchHandler(url, 'GET', null, null) as DailySessionResponse
|
|
50
|
+
return (await fetchHandler(url, 'GET', null, null)) as DailySessionResponse
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -64,7 +64,7 @@ export async function updateDailySession(
|
|
|
64
64
|
const stringDate = userDate.toISOString().split('T')[0]
|
|
65
65
|
const url: string = `${LEARNING_PATHS_PATH}/daily-session/create`
|
|
66
66
|
const body = { brand: brand, userDate: stringDate, keepFirstLearningPath: keepFirstLearningPath }
|
|
67
|
-
return await fetchHandler(url, 'POST', null, body) as DailySessionResponse
|
|
67
|
+
return (await fetchHandler(url, 'POST', null, body)) as DailySessionResponse
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -73,7 +73,7 @@ export async function updateDailySession(
|
|
|
73
73
|
*/
|
|
74
74
|
export async function getActivePath(brand: string) {
|
|
75
75
|
const url: string = `${LEARNING_PATHS_PATH}/active-path/get?brand=${brand}`
|
|
76
|
-
return await fetchHandler(url, 'GET', null, null) as ActiveLearningPathResponse
|
|
76
|
+
return (await fetchHandler(url, 'GET', null, null)) as ActiveLearningPathResponse
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
@@ -84,7 +84,7 @@ export async function getActivePath(brand: string) {
|
|
|
84
84
|
export async function startLearningPath(brand: string, learningPathId: number) {
|
|
85
85
|
const url: string = `${LEARNING_PATHS_PATH}/active-path/set`
|
|
86
86
|
const body = { brand: brand, learning_path_id: learningPathId }
|
|
87
|
-
return await fetchHandler(url, 'POST', null, body) as ActiveLearningPathResponse
|
|
87
|
+
return (await fetchHandler(url, 'POST', null, body)) as ActiveLearningPathResponse
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
@@ -227,8 +227,6 @@ export async function fetchLearningPathLessons(
|
|
|
227
227
|
}))
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
|
|
231
|
-
|
|
232
230
|
return {
|
|
233
231
|
...learningPath,
|
|
234
232
|
is_active_learning_path: isActiveLearningPath,
|
|
@@ -250,14 +248,16 @@ export async function fetchLearningPathLessons(
|
|
|
250
248
|
* @param {number[]} contentIds The array of content IDs within the learning path
|
|
251
249
|
* @returns {Promise<number[]>} Array with completed content IDs
|
|
252
250
|
*/
|
|
253
|
-
export async function fetchLearningPathProgressCheckLessons(
|
|
251
|
+
export async function fetchLearningPathProgressCheckLessons(
|
|
252
|
+
contentIds: number[]
|
|
253
|
+
): Promise<number[]> {
|
|
254
254
|
let query = await getAllCompletedByIds(contentIds)
|
|
255
|
-
let completedProgress = query.data.map(progress => progress.content_id)
|
|
256
|
-
return contentIds.filter(contentId => completedProgress.includes(contentId))
|
|
255
|
+
let completedProgress = query.data.map((progress) => progress.content_id)
|
|
256
|
+
return contentIds.filter((contentId) => completedProgress.includes(contentId))
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
interface completeMethodIntroVideo {
|
|
260
|
-
intro_video_response: SyncWriteDTO<ContentProgress, any> | null
|
|
260
|
+
intro_video_response: SyncWriteDTO<ContentProgress, any> | null
|
|
261
261
|
active_path_response: ActiveLearningPathResponse
|
|
262
262
|
}
|
|
263
263
|
/**
|
|
@@ -268,7 +268,10 @@ interface completeMethodIntroVideo {
|
|
|
268
268
|
* @returns {Promise<Object|null>} response.intro_video_response - The intro video completion response or null if already completed.
|
|
269
269
|
* @returns {Promise<Object>} response.active_path_response - The set active learning path response.
|
|
270
270
|
*/
|
|
271
|
-
export async function completeMethodIntroVideo(
|
|
271
|
+
export async function completeMethodIntroVideo(
|
|
272
|
+
introVideoId: number,
|
|
273
|
+
brand: string
|
|
274
|
+
): Promise<completeMethodIntroVideo> {
|
|
272
275
|
let response = {} as completeMethodIntroVideo
|
|
273
276
|
|
|
274
277
|
response.intro_video_response = await completeIfNotCompleted(introVideoId)
|
|
@@ -278,13 +281,12 @@ export async function completeMethodIntroVideo(introVideoId: number, brand: stri
|
|
|
278
281
|
|
|
279
282
|
response.active_path_response = await startLearningPath(brand, learningPathId)
|
|
280
283
|
|
|
281
|
-
|
|
282
284
|
return response
|
|
283
285
|
}
|
|
284
286
|
|
|
285
287
|
interface completeLearningPathIntroVideo {
|
|
286
|
-
intro_video_response: SyncWriteDTO<ContentProgress, any> | null
|
|
287
|
-
learning_path_reset_response: SyncWriteDTO<ContentProgress, any> | null
|
|
288
|
+
intro_video_response: SyncWriteDTO<ContentProgress, any> | null
|
|
289
|
+
learning_path_reset_response: SyncWriteDTO<ContentProgress, any> | null
|
|
288
290
|
lesson_import_response: SyncWriteDTO<ContentProgress, any> | null
|
|
289
291
|
}
|
|
290
292
|
/**
|
|
@@ -297,7 +299,11 @@ interface completeLearningPathIntroVideo {
|
|
|
297
299
|
* @returns {Promise<void>} response.learning_path_reset_response - The reset learning path response.
|
|
298
300
|
* @returns {Promise<Object[]>} response.lesson_import_response - The responses for completing each content_id within the learning path.
|
|
299
301
|
*/
|
|
300
|
-
export async function completeLearningPathIntroVideo(
|
|
302
|
+
export async function completeLearningPathIntroVideo(
|
|
303
|
+
introVideoId: number,
|
|
304
|
+
learningPathId: number,
|
|
305
|
+
lessonsToImport: number[] | null
|
|
306
|
+
) {
|
|
301
307
|
let response = {} as completeLearningPathIntroVideo
|
|
302
308
|
|
|
303
309
|
response.intro_video_response = await completeIfNotCompleted(introVideoId)
|
|
@@ -306,7 +312,6 @@ export async function completeLearningPathIntroVideo(introVideoId: number, learn
|
|
|
306
312
|
|
|
307
313
|
if (!lessonsToImport) {
|
|
308
314
|
response.learning_path_reset_response = await contentStatusReset(learningPathId, collection)
|
|
309
|
-
|
|
310
315
|
} else {
|
|
311
316
|
response.lesson_import_response = await contentsStatusCompleted(lessonsToImport, collection)
|
|
312
317
|
}
|
|
@@ -314,9 +319,39 @@ export async function completeLearningPathIntroVideo(introVideoId: number, learn
|
|
|
314
319
|
return response
|
|
315
320
|
}
|
|
316
321
|
|
|
317
|
-
|
|
318
|
-
|
|
322
|
+
async function completeIfNotCompleted(
|
|
323
|
+
contentId: number
|
|
324
|
+
): Promise<SyncWriteDTO<ContentProgress, any> | null> {
|
|
319
325
|
const introVideoStatus = await getProgressState(contentId)
|
|
320
326
|
|
|
321
327
|
return introVideoStatus !== 'completed' ? await contentStatusCompleted(contentId) : null
|
|
322
328
|
}
|
|
329
|
+
|
|
330
|
+
export async function onContentCompletedLearningPathListener(event) {
|
|
331
|
+
console.log('if')
|
|
332
|
+
if (event?.collection?.type !== 'learning-path-v2') return
|
|
333
|
+
if (event.contentId !== event?.collection?.id) return
|
|
334
|
+
const learningPathId = event.contentId
|
|
335
|
+
const learningPath = await getEnrichedLearningPath(learningPathId)
|
|
336
|
+
console.log('LP', learningPath)
|
|
337
|
+
const brand = learningPath.brand
|
|
338
|
+
const activeLearningPath = await getActivePath(brand)
|
|
339
|
+
console.log('Active LP', activeLearningPath)
|
|
340
|
+
if (activeLearningPath.active_learning_path_id !== learningPathId) return
|
|
341
|
+
const method = await fetchMethodV2Structure(brand)
|
|
342
|
+
console.log('Method', method)
|
|
343
|
+
const currentIndex = method.learning_paths.findIndex((lp) => lp.id === learningPathId)
|
|
344
|
+
if (currentIndex === -1) {
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
const nextLearningPath = method.learning_paths[currentIndex + 1]
|
|
348
|
+
console.log('Next LP', nextLearningPath)
|
|
349
|
+
if (!nextLearningPath) {
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
await startLearningPath(brand, nextLearningPath.id)
|
|
354
|
+
const nextLearningPathData = await getEnrichedLearningPath(nextLearningPath.id)
|
|
355
|
+
console.log('Next LP Data', nextLearningPathData)
|
|
356
|
+
await contentStatusReset(nextLearningPathData.intro_video.id)
|
|
357
|
+
}
|