musora-content-services 2.107.4 → 2.107.6
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 +3 -10
- package/.coderabbit.yaml +0 -0
- package/.editorconfig +0 -0
- package/.github/pull_request_template.md +0 -0
- package/.github/workflows/conventional-commits.yaml +0 -0
- package/.github/workflows/docs.js.yml +0 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +0 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/package.json +1 -1
- package/src/constants/award-assets.js +0 -0
- package/src/contentMetaData.js +0 -0
- package/src/filterBuilder.js +0 -0
- package/src/index.d.ts +2 -6
- package/src/index.js +2 -6
- package/src/infrastructure/http/HttpClient.ts +0 -0
- package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
- package/src/infrastructure/http/index.ts +0 -0
- package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
- package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
- package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
- package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
- package/src/lib/ads/monoid.ts +0 -0
- package/src/lib/ads/semigroup.ts +0 -0
- package/src/lib/brands.ts +0 -0
- package/src/lib/lastUpdated.js +0 -0
- package/src/lib/sanity/filter.ts +0 -0
- package/src/lib/sanity/query.ts +0 -0
- package/src/services/api/types.js +0 -0
- package/src/services/api/types.ts +0 -0
- package/src/services/awards/award-callbacks.js +0 -0
- package/src/services/awards/award-query.js +0 -0
- package/src/services/awards/internal/.indexignore +0 -0
- package/src/services/awards/internal/award-definitions.js +0 -0
- package/src/services/awards/internal/award-events.js +0 -0
- package/src/services/awards/internal/award-manager.js +0 -0
- package/src/services/awards/internal/certificate-builder.js +0 -0
- package/src/services/awards/internal/completion-data-generator.js +0 -0
- package/src/services/awards/internal/content-progress-observer.js +0 -0
- package/src/services/awards/internal/image-utils.js +0 -0
- package/src/services/awards/internal/message-generator.js +0 -0
- package/src/services/awards/internal/types.js +0 -0
- package/src/services/awards/types.d.ts +0 -0
- package/src/services/awards/types.js +0 -0
- package/src/services/config.js +0 -0
- package/src/services/content/artist.ts +0 -0
- package/src/services/content/content.ts +0 -0
- package/src/services/content/genre.ts +0 -0
- package/src/services/content/instructor.ts +0 -0
- package/src/services/content-org/content-org.js +0 -0
- package/src/services/content-org/guided-courses.ts +0 -0
- package/src/services/content-org/learning-paths.ts +5 -5
- package/src/services/content-org/playlists-types.js +0 -0
- package/src/services/content-org/playlists.js +0 -0
- package/src/services/content.js +0 -0
- package/src/services/contentAggregator.js +0 -0
- package/src/services/contentLikes.js +0 -0
- package/src/services/contentProgress.js +83 -33
- package/src/services/dataContext.js +0 -0
- package/src/services/dateUtils.js +0 -0
- package/src/services/eventsAPI.js +0 -0
- package/src/services/forums/categories.ts +0 -0
- package/src/services/forums/forums.ts +0 -0
- package/src/services/forums/types.ts +0 -0
- package/src/services/gamification/awards.ts +0 -0
- package/src/services/gamification/gamification.js +0 -0
- package/src/services/imageSRCBuilder.js +0 -0
- package/src/services/imageSRCVerify.js +0 -0
- package/src/services/liveTesting.ts +0 -0
- package/src/services/permissions/PermissionsAdapter.ts +0 -0
- package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
- package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
- package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
- package/src/services/permissions/README.md +0 -0
- package/src/services/permissions/index.ts +0 -0
- package/src/services/progress-events.js +0 -52
- package/src/services/progress-row/method-card.js +0 -0
- package/src/services/recommendations.js +0 -0
- package/src/services/reporting/README.md +0 -0
- package/src/services/reporting/types.ts +0 -0
- package/src/services/sanity.js +1 -1
- package/src/services/sentry/.indexignore +0 -0
- package/src/services/sentry/index.ts +0 -0
- package/src/services/sync/.indexignore +0 -0
- package/src/services/sync/adapters/factory.ts +0 -0
- package/src/services/sync/adapters/lokijs.ts +0 -0
- package/src/services/sync/adapters/sqlite.ts +0 -0
- package/src/services/sync/concurrency-safety.ts +0 -0
- package/src/services/sync/context/index.ts +0 -0
- package/src/services/sync/context/providers/base.ts +0 -0
- package/src/services/sync/context/providers/connectivity.ts +0 -0
- package/src/services/sync/context/providers/durability.ts +0 -0
- package/src/services/sync/context/providers/index.ts +0 -0
- package/src/services/sync/context/providers/session.ts +0 -0
- package/src/services/sync/context/providers/tabs.ts +0 -0
- package/src/services/sync/context/providers/visibility.ts +0 -0
- package/src/services/sync/database/factory.ts +0 -0
- package/src/services/sync/errors/boundary.ts +0 -0
- package/src/services/sync/errors/index.ts +0 -0
- package/src/services/sync/errors/validators.ts +0 -0
- package/src/services/sync/fetch.ts +0 -0
- package/src/services/sync/index.ts +0 -0
- package/src/services/sync/manager.ts +0 -4
- package/src/services/sync/models/Base.ts +0 -0
- package/src/services/sync/models/ContentLike.ts +0 -0
- package/src/services/sync/models/ContentProgress.ts +0 -0
- package/src/services/sync/models/Practice.ts +0 -0
- package/src/services/sync/models/PracticeDayNote.ts +0 -0
- package/src/services/sync/models/UserAwardProgress.ts +0 -0
- package/src/services/sync/models/index.ts +0 -0
- package/src/services/sync/repositories/base.ts +12 -8
- package/src/services/sync/repositories/content-likes.ts +0 -0
- package/src/services/sync/repositories/content-progress.ts +11 -7
- package/src/services/sync/repositories/index.ts +0 -0
- package/src/services/sync/repositories/practice-day-notes.ts +0 -0
- package/src/services/sync/repositories/practices.ts +0 -0
- package/src/services/sync/repositories/user-award-progress.ts +0 -0
- package/src/services/sync/repository-proxy.ts +0 -0
- package/src/services/sync/resolver.ts +0 -0
- package/src/services/sync/retry.ts +0 -0
- package/src/services/sync/run-scope.ts +0 -0
- package/src/services/sync/schema/index.ts +0 -0
- package/src/services/sync/serializers/index.ts +0 -0
- package/src/services/sync/serializers/model.ts +0 -0
- package/src/services/sync/serializers/raw.ts +0 -0
- package/src/services/sync/store/index.ts +13 -9
- package/src/services/sync/store/push-coalescer.ts +0 -0
- package/src/services/sync/store-configs.ts +0 -0
- package/src/services/sync/strategies/base.ts +0 -0
- package/src/services/sync/strategies/index.ts +0 -0
- package/src/services/sync/strategies/initial.ts +0 -0
- package/src/services/sync/strategies/polling.ts +0 -0
- package/src/services/sync/telemetry/index.ts +0 -0
- package/src/services/sync/telemetry/sampling.ts +0 -0
- package/src/services/sync/utils/event-emitter.ts +0 -0
- package/src/services/sync/utils/index.ts +0 -0
- package/src/services/sync/utils/throttle.ts +0 -0
- package/src/services/sync/utils/timers.ts +0 -0
- package/src/services/types.js +0 -0
- package/src/services/user/account.ts +0 -0
- package/src/services/user/chat.js +0 -0
- package/src/services/user/interests.js +0 -0
- package/src/services/user/management.js +0 -0
- package/src/services/user/memberships.ts +0 -0
- package/src/services/user/notifications.js +0 -0
- package/src/services/user/onboarding.ts +0 -0
- package/src/services/user/payments.ts +0 -0
- package/src/services/user/permissions.js +0 -0
- package/src/services/user/profile.js +0 -0
- package/src/services/user/sessions.js +0 -0
- package/src/services/user/types.d.ts +0 -0
- package/src/services/user/types.js +0 -0
- package/src/services/user/user-management-system.js +0 -0
- package/src/services/userActivity.js +0 -0
- package/test/HttpClient.test.js +0 -0
- package/test/awards/award-alacarte-observer.test.js +0 -0
- package/test/awards/award-auto-refresh.test.js +0 -0
- package/test/awards/award-calculations.test.js +0 -0
- package/test/awards/award-certificate-display.test.js +0 -0
- package/test/awards/award-collection-edge-cases.test.js +0 -0
- package/test/awards/award-collection-filtering.test.js +0 -0
- package/test/awards/award-completion-flow.test.js +0 -0
- package/test/awards/award-exclusion-handling.test.js +0 -0
- package/test/awards/award-multi-lesson.test.js +0 -0
- package/test/awards/award-observer-integration.test.js +0 -0
- package/test/awards/award-query-messages.test.js +0 -0
- package/test/awards/award-user-collection.test.js +0 -0
- package/test/awards/duplicate-prevention.test.js +0 -0
- package/test/awards/helpers/completion-mock.js +0 -0
- package/test/awards/helpers/index.js +0 -0
- package/test/awards/helpers/mock-setup.js +0 -0
- package/test/awards/helpers/progress-emitter.js +0 -0
- package/test/awards/message-generator.test.js +0 -0
- package/test/content.test.js +0 -0
- package/test/contentLikes.test.js +0 -0
- package/test/contentProgress.test.js +0 -0
- package/test/dataContext.test.js +0 -0
- package/test/forum.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/initializeTests.js +0 -0
- package/test/learningPaths.test.js +0 -0
- package/test/lib/__snapshots__/filter.test.ts.snap +0 -0
- package/test/lib/filter.test.ts +0 -0
- package/test/lib/lastUpdated.test.js +0 -0
- package/test/lib/query.test.ts +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/mockData/award-definitions.js +0 -0
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
- package/test/mockData/mockData_progress_content.json +0 -0
- package/test/mockData/mockData_sanity_progress_content.json +0 -0
- package/test/mockData/mockData_user_practices.json +0 -0
- package/test/notifications.test.js +0 -0
- package/test/progressRows.test.js +0 -0
- package/test/sanityQueryService.test.js +0 -0
- package/test/streakMessage.test.js +0 -0
- package/test/sync/adapter.ts +0 -0
- package/test/sync/initialize-sync-manager.js +0 -0
- package/test/sync/models/award-database-integration.test.js +0 -0
- package/test/user/permissions.test.js +0 -0
- package/test/userActivity.test.js +0 -0
- package/tools/generate-index.cjs +0 -0
- package/.yarnrc.yml +0 -1
- package/check_content.js +0 -30
- package/check_content.mjs +0 -32
- package/test/reporting.test.js +0 -132
- package/test_owned_navigate.js +0 -74
|
@@ -1,16 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"Bash(find:*)",
|
|
7
|
-
"Bash(sed:*)",
|
|
8
|
-
"Read(//app/**)",
|
|
9
|
-
"Bash(cat:*)",
|
|
10
|
-
"Bash(docker exec:*)",
|
|
11
|
-
"Bash(npm config:*)"
|
|
4
|
+
"Bash(rg:*)",
|
|
5
|
+
"Bash(npm run lint:*)"
|
|
12
6
|
],
|
|
13
|
-
"deny": []
|
|
14
|
-
"ask": []
|
|
7
|
+
"deny": []
|
|
15
8
|
}
|
|
16
9
|
}
|
package/.coderabbit.yaml
CHANGED
|
File without changes
|
package/.editorconfig
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/.prettierignore
CHANGED
|
File without changes
|
package/.prettierrc
CHANGED
|
File without changes
|
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.107.6](https://github.com/railroadmedia/musora-content-services/compare/v2.107.5...v2.107.6) (2025-12-29)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **TP-1051:** group contentProgress upsert pushes ([#665](https://github.com/railroadmedia/musora-content-services/issues/665)) ([27ff11f](https://github.com/railroadmedia/musora-content-services/commit/27ff11fb1b10075313e614cf895356a221f0c0d1))
|
|
11
|
+
|
|
12
|
+
### [2.107.5](https://github.com/railroadmedia/musora-content-services/compare/v2.107.4...v2.107.5) (2025-12-29)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* establishes a positive progress validation ([#672](https://github.com/railroadmedia/musora-content-services/issues/672)) ([e9bc211](https://github.com/railroadmedia/musora-content-services/commit/e9bc211659262b282e1073c2746c3c8824371c35))
|
|
18
|
+
|
|
5
19
|
### [2.107.4](https://github.com/railroadmedia/musora-content-services/compare/v2.107.1...v2.107.4) (2025-12-22)
|
|
6
20
|
|
|
7
21
|
|
package/CLAUDE.md
CHANGED
|
File without changes
|
package/README.md
CHANGED
|
File without changes
|
package/babel.config.cjs
CHANGED
|
File without changes
|
package/jest.config.js
CHANGED
|
File without changes
|
package/jsdoc.json
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
File without changes
|
package/src/contentMetaData.js
CHANGED
|
File without changes
|
package/src/filterBuilder.js
CHANGED
|
File without changes
|
package/src/index.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
getEnrichedLearningPaths,
|
|
55
55
|
getLearningPathLessonsByIds,
|
|
56
56
|
mapContentToParent,
|
|
57
|
-
|
|
57
|
+
onContentCompletedLearningPathActions,
|
|
58
58
|
resetAllLearningPaths,
|
|
59
59
|
startLearningPath,
|
|
60
60
|
updateDailySession
|
|
@@ -202,9 +202,7 @@ import {
|
|
|
202
202
|
} from './services/liveTesting.ts';
|
|
203
203
|
|
|
204
204
|
import {
|
|
205
|
-
emitContentCompleted,
|
|
206
205
|
emitProgressSaved,
|
|
207
|
-
onContentCompleted,
|
|
208
206
|
onProgressSaved
|
|
209
207
|
} from './services/progress-events.js';
|
|
210
208
|
|
|
@@ -484,7 +482,6 @@ declare module 'musora-content-services' {
|
|
|
484
482
|
deleteUserActivity,
|
|
485
483
|
duplicatePlaylist,
|
|
486
484
|
editComment,
|
|
487
|
-
emitContentCompleted,
|
|
488
485
|
emitProgressSaved,
|
|
489
486
|
enrollUserInGuidedCourse,
|
|
490
487
|
extractSanityUrl,
|
|
@@ -675,8 +672,7 @@ declare module 'musora-content-services' {
|
|
|
675
672
|
markNotificationAsUnread,
|
|
676
673
|
markThreadAsRead,
|
|
677
674
|
numberOfActiveUsers,
|
|
678
|
-
|
|
679
|
-
onContentCompletedLearningPathListener,
|
|
675
|
+
onContentCompletedLearningPathActions,
|
|
680
676
|
onProgressSaved,
|
|
681
677
|
openComment,
|
|
682
678
|
otherStats,
|
package/src/index.js
CHANGED
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
getEnrichedLearningPaths,
|
|
59
59
|
getLearningPathLessonsByIds,
|
|
60
60
|
mapContentToParent,
|
|
61
|
-
|
|
61
|
+
onContentCompletedLearningPathActions,
|
|
62
62
|
resetAllLearningPaths,
|
|
63
63
|
startLearningPath,
|
|
64
64
|
updateDailySession
|
|
@@ -206,9 +206,7 @@ import {
|
|
|
206
206
|
} from './services/liveTesting.ts';
|
|
207
207
|
|
|
208
208
|
import {
|
|
209
|
-
emitContentCompleted,
|
|
210
209
|
emitProgressSaved,
|
|
211
|
-
onContentCompleted,
|
|
212
210
|
onProgressSaved
|
|
213
211
|
} from './services/progress-events.js';
|
|
214
212
|
|
|
@@ -483,7 +481,6 @@ export {
|
|
|
483
481
|
deleteUserActivity,
|
|
484
482
|
duplicatePlaylist,
|
|
485
483
|
editComment,
|
|
486
|
-
emitContentCompleted,
|
|
487
484
|
emitProgressSaved,
|
|
488
485
|
enrollUserInGuidedCourse,
|
|
489
486
|
extractSanityUrl,
|
|
@@ -674,8 +671,7 @@ export {
|
|
|
674
671
|
markNotificationAsUnread,
|
|
675
672
|
markThreadAsRead,
|
|
676
673
|
numberOfActiveUsers,
|
|
677
|
-
|
|
678
|
-
onContentCompletedLearningPathListener,
|
|
674
|
+
onContentCompletedLearningPathActions,
|
|
679
675
|
onProgressSaved,
|
|
680
676
|
openComment,
|
|
681
677
|
otherStats,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/lib/ads/monoid.ts
CHANGED
|
File without changes
|
package/src/lib/ads/semigroup.ts
CHANGED
|
File without changes
|
package/src/lib/brands.ts
CHANGED
|
File without changes
|
package/src/lib/lastUpdated.js
CHANGED
|
File without changes
|
package/src/lib/sanity/filter.ts
CHANGED
|
File without changes
|
package/src/lib/sanity/query.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/src/services/config.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -445,11 +445,11 @@ async function resetIfPossible(contentId: number, collection: CollectionParamete
|
|
|
445
445
|
return status !== '' ? await contentStatusReset(contentId, collection) : null
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
-
export async function
|
|
449
|
-
if (
|
|
450
|
-
if (
|
|
448
|
+
export async function onContentCompletedLearningPathActions(contentId: number, collection: CollectionObject|null) {
|
|
449
|
+
if (collection?.type !== COLLECTION_TYPE.LEARNING_PATH) return
|
|
450
|
+
if (contentId !== collection?.id) return
|
|
451
451
|
|
|
452
|
-
const learningPathId =
|
|
452
|
+
const learningPathId = contentId
|
|
453
453
|
const learningPath = await getEnrichedLearningPath(learningPathId)
|
|
454
454
|
|
|
455
455
|
const brand = learningPath.brand
|
|
@@ -470,5 +470,5 @@ export async function onContentCompletedLearningPathListener(event) {
|
|
|
470
470
|
await startLearningPath(brand, nextLearningPath.id)
|
|
471
471
|
const nextLearningPathData = await getEnrichedLearningPath(nextLearningPath.id)
|
|
472
472
|
|
|
473
|
-
await contentStatusReset(nextLearningPathData.intro_video.id)
|
|
473
|
+
await contentStatusReset(nextLearningPathData.intro_video.id, {skipPush: true})
|
|
474
474
|
}
|
|
File without changes
|
|
File without changes
|
package/src/services/content.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -3,8 +3,7 @@ import { db } from './sync'
|
|
|
3
3
|
import { COLLECTION_TYPE, STATE } from './sync/models/ContentProgress'
|
|
4
4
|
import { trackUserPractice, findIncompleteLesson } from './userActivity'
|
|
5
5
|
import { getNextLessonLessonParentTypes } from '../contentTypeConfig.js'
|
|
6
|
-
import {
|
|
7
|
-
import {getDailySession} from "./content-org/learning-paths.ts";
|
|
6
|
+
import {getDailySession, onContentCompletedLearningPathActions} from "./content-org/learning-paths.ts";
|
|
8
7
|
import {getToday} from "./dateUtils.js";
|
|
9
8
|
import { fetchBrandsByContentIds } from './sanity.js'
|
|
10
9
|
|
|
@@ -464,47 +463,69 @@ export async function contentStatusStarted(contentId, collection = null) {
|
|
|
464
463
|
false
|
|
465
464
|
)
|
|
466
465
|
}
|
|
467
|
-
export async function contentStatusReset(contentId, collection = null) {
|
|
468
|
-
return resetStatus(contentId, collection)
|
|
466
|
+
export async function contentStatusReset(contentId, collection = null, {skipPush = false} = {}) {
|
|
467
|
+
return resetStatus(contentId, collection, {skipPush})
|
|
469
468
|
}
|
|
470
469
|
|
|
470
|
+
async function saveContentProgress(contentId, collection, progress, currentSeconds, {skipPush = false} = {}) {
|
|
471
|
+
const isLP = collection?.type === COLLECTION_TYPE.LEARNING_PATH
|
|
471
472
|
async function saveContentProgress(contentId, collection, progress, currentSeconds) {
|
|
473
|
+
|
|
474
|
+
// filter out contentIds that are setting progress lower than existing
|
|
475
|
+
const contentIdProgress = await getProgressDataByIds([contentId], collection)
|
|
476
|
+
if (progress <= contentIdProgress[contentId].progress) {
|
|
477
|
+
return
|
|
478
|
+
}
|
|
479
|
+
|
|
472
480
|
const response = await db.contentProgress.recordProgress(
|
|
473
481
|
contentId,
|
|
474
482
|
collection,
|
|
475
483
|
progress,
|
|
476
|
-
currentSeconds
|
|
484
|
+
currentSeconds,
|
|
485
|
+
{skipPush: true}
|
|
477
486
|
)
|
|
478
|
-
if (progress === 100) emitContentCompleted(contentId, collection)
|
|
479
|
-
|
|
480
487
|
// note - previous implementation explicitly did not trickle progress to children here
|
|
481
488
|
// (only to siblings/parents via le bubbles)
|
|
482
489
|
|
|
483
490
|
const hierarchy = await getHierarchy(contentId, collection)
|
|
484
491
|
|
|
485
492
|
const bubbledProgresses = await bubbleProgress(hierarchy, contentId, collection)
|
|
493
|
+
|
|
494
|
+
// filter out contentIds that are setting progress lower than existing
|
|
495
|
+
const existingProgresses = await getProgressDataByIds(Object.keys(bubbledProgresses), collection)
|
|
496
|
+
for (const [bubbledContentId, bubbledProgress] of Object.entries(bubbledProgresses)) {
|
|
497
|
+
if (bubbledProgress <= existingProgresses[bubbledContentId].progress) {
|
|
498
|
+
delete bubbledProgresses[bubbledContentId]
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
486
502
|
// BE bubbling/trickling currently does not work, so we utilize non-tentative pushing when learning path collection
|
|
487
|
-
await db.contentProgress.recordProgressMany(bubbledProgresses, collection,
|
|
503
|
+
await db.contentProgress.recordProgressMany(bubbledProgresses, collection, {tentative: !isLP, skipPush: true})
|
|
488
504
|
|
|
489
|
-
if (
|
|
505
|
+
if (isLP) {
|
|
490
506
|
let exportIds = bubbledProgresses
|
|
491
507
|
exportIds[contentId] = progress
|
|
492
|
-
await duplicateLearningPathProgressToExternalContents(exportIds, collection, hierarchy)
|
|
508
|
+
await duplicateLearningPathProgressToExternalContents(exportIds, collection, hierarchy, {skipPush: true})
|
|
493
509
|
}
|
|
494
510
|
|
|
511
|
+
if (progress === 100) await onContentCompletedLearningPathActions(contentId, collection)
|
|
512
|
+
|
|
495
513
|
for (const [bubbledContentId, bubbledProgress] of Object.entries(bubbledProgresses)) {
|
|
496
514
|
if (bubbledProgress === 100) {
|
|
497
|
-
|
|
515
|
+
await onContentCompletedLearningPathActions(Number(bubbledContentId), collection)
|
|
498
516
|
}
|
|
499
517
|
}
|
|
518
|
+
|
|
519
|
+
if (!skipPush) db.contentProgress.requestPushUnsynced()
|
|
520
|
+
|
|
500
521
|
return response
|
|
501
522
|
}
|
|
502
523
|
|
|
503
|
-
async function setStartedOrCompletedStatus(contentId, collection, isCompleted) {
|
|
504
|
-
const
|
|
505
|
-
const response = await db.contentProgress.recordProgress(contentId, collection, progress)
|
|
524
|
+
async function setStartedOrCompletedStatus(contentId, collection, isCompleted, {skipPush = false} = {}) {
|
|
525
|
+
const isLP = collection?.type === COLLECTION_TYPE.LEARNING_PATH
|
|
506
526
|
|
|
507
|
-
|
|
527
|
+
const progress = isCompleted ? 100 : 0
|
|
528
|
+
const response = await db.contentProgress.recordProgress(contentId, collection, progress, null, {skipPush: true})
|
|
508
529
|
|
|
509
530
|
const hierarchy = await getHierarchy(contentId, collection)
|
|
510
531
|
|
|
@@ -513,29 +534,33 @@ async function setStartedOrCompletedStatus(contentId, collection, isCompleted) {
|
|
|
513
534
|
...await bubbleProgress(hierarchy, contentId, collection)
|
|
514
535
|
}
|
|
515
536
|
// BE bubbling/trickling currently does not work, so we utilize non-tentative pushing when learning path collection
|
|
516
|
-
await db.contentProgress.recordProgressMany(progresses, collection,
|
|
537
|
+
await db.contentProgress.recordProgressMany(progresses, collection, {tentative: !isLP, skipPush: true})
|
|
538
|
+
if (isLP) {
|
|
517
539
|
|
|
518
|
-
if (collection && collection.type === COLLECTION_TYPE.LEARNING_PATH) {
|
|
519
540
|
let exportProgresses = progresses
|
|
520
541
|
exportProgresses[contentId] = progress
|
|
521
|
-
await duplicateLearningPathProgressToExternalContents(exportProgresses, collection, hierarchy)
|
|
542
|
+
await duplicateLearningPathProgressToExternalContents(exportProgresses, collection, hierarchy, {skipPush: true})
|
|
522
543
|
}
|
|
523
544
|
|
|
545
|
+
if (progress === 100) await onContentCompletedLearningPathActions(contentId, collection)
|
|
546
|
+
|
|
524
547
|
for (const [id, progress] of Object.entries(progresses)) {
|
|
525
548
|
if (progress === 100) {
|
|
526
|
-
|
|
549
|
+
await onContentCompletedLearningPathActions(Number(id), collection)
|
|
527
550
|
}
|
|
528
551
|
}
|
|
529
552
|
|
|
553
|
+
if (!skipPush) db.contentProgress.requestPushUnsynced()
|
|
554
|
+
|
|
530
555
|
return response
|
|
531
556
|
}
|
|
532
557
|
|
|
533
558
|
// we cannot simply pass LP id with self collection, because we do not have a-la-carte LP's set up yet,
|
|
534
559
|
// and we need each lesson to bubble to its parent outside of LP
|
|
535
|
-
async function duplicateLearningPathProgressToExternalContents(ids, collection, hierarchy) {
|
|
560
|
+
async function duplicateLearningPathProgressToExternalContents(ids, collection, hierarchy, {skipPush = false} = {}) {
|
|
536
561
|
// filter out LPs. we dont want to duplicate to LP's while we dont have a-la-cart LP's set up.
|
|
537
562
|
let filteredIds = Object.fromEntries(
|
|
538
|
-
Object.entries(ids).filter((id) => {
|
|
563
|
+
Object.entries(ids).filter(([id]) => {
|
|
539
564
|
return hierarchy.parents[parseInt(id)] !== null
|
|
540
565
|
})
|
|
541
566
|
)
|
|
@@ -551,8 +576,13 @@ async function duplicateLearningPathProgressToExternalContents(ids, collection,
|
|
|
551
576
|
})
|
|
552
577
|
|
|
553
578
|
// each handles its own bubbling.
|
|
554
|
-
|
|
555
|
-
|
|
579
|
+
// skipPush on all but last to avoid multiple push requests
|
|
580
|
+
filteredIds.forEach(([id, pct], index) => {
|
|
581
|
+
if (index === filteredIds.length - 1) {
|
|
582
|
+
saveContentProgress(parseInt(id), null, pct, null, {skipPush})
|
|
583
|
+
} else {
|
|
584
|
+
saveContentProgress(parseInt(id), null, pct, null, {skipPush: true})
|
|
585
|
+
}
|
|
556
586
|
})
|
|
557
587
|
}
|
|
558
588
|
|
|
@@ -564,10 +594,18 @@ async function getHierarchy(contentId, collection) {
|
|
|
564
594
|
}
|
|
565
595
|
}
|
|
566
596
|
|
|
567
|
-
async function setStartedOrCompletedStatusMany(contentIds, collection, isCompleted) {
|
|
597
|
+
async function setStartedOrCompletedStatusMany(contentIds, collection, isCompleted, {skipPush = false} = {}) {
|
|
598
|
+
const isLP = collection?.type === COLLECTION_TYPE.LEARNING_PATH
|
|
568
599
|
const progress = isCompleted ? 100 : 0
|
|
600
|
+
|
|
601
|
+
if (progress === 100) {
|
|
602
|
+
for (const contentId of contentIds) {
|
|
603
|
+
await onContentCompletedLearningPathActions(contentId, collection)
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
569
607
|
const contents = Object.fromEntries(contentIds.map((id) => [id, progress]))
|
|
570
|
-
const response = await db.contentProgress.recordProgressMany(contents, collection, true)
|
|
608
|
+
const response = await db.contentProgress.recordProgressMany(contents, collection, {tentative: !isLP, skipPush: true})
|
|
571
609
|
|
|
572
610
|
// we assume this is used only for contents within the same hierarchy
|
|
573
611
|
const hierarchy = await getHierarchy(collection.id, collection)
|
|
@@ -581,22 +619,32 @@ async function setStartedOrCompletedStatusMany(contentIds, collection, isComplet
|
|
|
581
619
|
}
|
|
582
620
|
}
|
|
583
621
|
// BE bubbling/trickling currently does not work, so we utilize non-tentative pushing when learning path collection
|
|
584
|
-
await db.contentProgress.recordProgressMany(progresses, collection,
|
|
622
|
+
await db.contentProgress.recordProgressMany(progresses, collection, {tentative: !isLP, skipPush: true})
|
|
585
623
|
|
|
586
|
-
if (
|
|
624
|
+
if (isLP) {
|
|
587
625
|
let exportProgresses = progresses
|
|
588
626
|
for (const contentId of contentIds){
|
|
589
627
|
exportProgresses[contentId] = progress
|
|
590
628
|
}
|
|
591
|
-
await duplicateLearningPathProgressToExternalContents(exportProgresses, collection, hierarchy)
|
|
629
|
+
await duplicateLearningPathProgressToExternalContents(exportProgresses, collection, hierarchy, {skipPush: true})
|
|
592
630
|
}
|
|
593
631
|
|
|
632
|
+
for (const [id, progress] of Object.entries(progresses)) {
|
|
633
|
+
if (progress === 100) {
|
|
634
|
+
await onContentCompletedLearningPathActions(Number(id), collection)
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (!skipPush) db.contentProgress.requestPushUnsynced()
|
|
639
|
+
|
|
594
640
|
return response
|
|
595
641
|
}
|
|
596
642
|
|
|
597
|
-
async function resetStatus(contentId, collection = null) {
|
|
643
|
+
async function resetStatus(contentId, collection = null, {skipPush = false} = {}) {
|
|
644
|
+
const isLP = collection?.type === COLLECTION_TYPE.LEARNING_PATH
|
|
645
|
+
|
|
598
646
|
const progress = 0
|
|
599
|
-
const response = await db.contentProgress.eraseProgress(contentId, collection)
|
|
647
|
+
const response = await db.contentProgress.eraseProgress(contentId, collection, {skipPush: true})
|
|
600
648
|
const hierarchy = await getHierarchy(contentId, collection)
|
|
601
649
|
|
|
602
650
|
let progresses = {
|
|
@@ -604,13 +652,15 @@ async function resetStatus(contentId, collection = null) {
|
|
|
604
652
|
...await bubbleProgress(hierarchy, contentId, collection)
|
|
605
653
|
}
|
|
606
654
|
// BE bubbling/trickling currently does not work, so we utilize non-tentative pushing when learning path collection
|
|
607
|
-
await db.contentProgress.recordProgressMany(progresses, collection,
|
|
655
|
+
await db.contentProgress.recordProgressMany(progresses, collection, {tentative: !isLP, skipPush: true})
|
|
608
656
|
|
|
609
|
-
if (
|
|
657
|
+
if (isLP) {
|
|
610
658
|
progresses[contentId] = progress
|
|
611
|
-
await duplicateLearningPathProgressToExternalContents(progresses, collection, hierarchy)
|
|
659
|
+
await duplicateLearningPathProgressToExternalContents(progresses, collection, hierarchy, {skipPush: true})
|
|
612
660
|
}
|
|
613
661
|
|
|
662
|
+
if (!skipPush) db.contentProgress.requestPushUnsynced()
|
|
663
|
+
|
|
614
664
|
return response
|
|
615
665
|
}
|
|
616
666
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -56,55 +56,3 @@ export function emitProgressSaved(event) {
|
|
|
56
56
|
}
|
|
57
57
|
})
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* @typedef {Object} ContentCompletedEvent
|
|
62
|
-
* @property {number} contentId - Railcontent ID of the completed content item
|
|
63
|
-
* @property {Object|null} collection - Collection context information
|
|
64
|
-
* @property {string} collection.type - Collection type (learning-path, guided-course, etc.)
|
|
65
|
-
* @property {number} collection.id - Collection ID
|
|
66
|
-
*/
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* @callback ContentCompletedListener
|
|
70
|
-
* @param {ContentCompletedEvent} event - The content completion event data
|
|
71
|
-
* @returns {void}
|
|
72
|
-
*/
|
|
73
|
-
const completedListeners = new Set()
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @param {ContentCompletedListener} listener - Function called when content is completed
|
|
77
|
-
* @returns {function(): void} Cleanup function to unregister the listener
|
|
78
|
-
*
|
|
79
|
-
* @example Listen for content completion
|
|
80
|
-
* const cleanup = onContentCompleted((event) => {
|
|
81
|
-
* console.log(`Content ${event.contentId} completed!`)
|
|
82
|
-
* if (event.collection) {
|
|
83
|
-
* console.log(`Within ${event.collection.type}: ${event.collection.id}`)
|
|
84
|
-
* checkCollectionProgress(event.collection.id)
|
|
85
|
-
* }
|
|
86
|
-
* })
|
|
87
|
-
*
|
|
88
|
-
* // Later, when no longer needed:
|
|
89
|
-
* cleanup()
|
|
90
|
-
*/
|
|
91
|
-
export function onContentCompleted(listener) {
|
|
92
|
-
completedListeners.add(listener)
|
|
93
|
-
return () => completedListeners.delete(listener)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* @param {number} contentId - The ID of the completed content item
|
|
98
|
-
* @param {Object|null} collection - Collection context information
|
|
99
|
-
* @returns {void}
|
|
100
|
-
*/
|
|
101
|
-
export function emitContentCompleted(contentId, collection) {
|
|
102
|
-
const event = { contentId: contentId, collection: collection }
|
|
103
|
-
completedListeners.forEach((listener) => {
|
|
104
|
-
try {
|
|
105
|
-
listener(event)
|
|
106
|
-
} catch (error) {
|
|
107
|
-
console.error('Error in contentCompleted listener:', error)
|
|
108
|
-
}
|
|
109
|
-
})
|
|
110
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|