musora-content-services 2.152.1 → 2.154.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/.github/workflows/automated-testing.yml +24 -0
- package/CHANGELOG.md +29 -0
- package/codecov.yml +5 -0
- package/jest.config.js +39 -8
- package/package.json +5 -1
- package/src/contentTypeConfig.js +13 -14
- package/src/index.d.ts +8 -2
- package/src/index.js +8 -2
- package/src/infrastructure/http/interfaces/RequestOptions.ts +1 -1
- package/src/services/awards/internal/award-definitions.js +3 -3
- package/src/services/content-org/guided-courses.ts +2 -2
- package/src/services/contentProgress.js +35 -54
- package/src/services/dateUtils.js +9 -1
- package/src/services/forums/posts.ts +2 -2
- package/src/services/multi-user-accounts/multi-user-accounts.ts +43 -7
- package/src/services/reporting/reporting.ts +3 -4
- package/src/services/sanity.js +26 -58
- package/src/services/sync/adapters/lokijs.ts +7 -4
- package/src/services/sync/fetch.ts +2 -14
- package/src/services/sync/repositories/base.ts +4 -0
- package/src/services/sync/repositories/content-progress.ts +3 -3
- package/src/services/sync/store/index.ts +6 -1
- package/src/services/sync/strategies/base.ts +1 -1
- package/src/services/sync/telemetry/index.ts +1 -1
- package/src/services/urlBuilder.ts +1 -0
- package/src/services/user/streakCalculator.ts +1 -1
- package/test/SKIPPED_TESTS.md +151 -0
- package/test/initializeTests.js +2 -3
- package/test/{content.test.js → integration/content.test.js} +7 -23
- package/test/integration/contentProgress.test.js +73 -0
- package/test/{forum.test.js → integration/forum.test.js} +2 -4
- package/test/{sanityQueryService.test.js → integration/sanityQueryService.test.js} +143 -291
- package/test/{user → integration/user}/permissions.test.js +5 -4
- package/test/live/README.md +29 -0
- package/test/setupConsole.js +6 -0
- package/test/setupNetworkGuard.js +3 -0
- package/test/{HttpClient.test.js → unit/HttpClient.test.js} +5 -5
- package/test/{awards → unit/awards}/award-alacarte-observer.test.js +13 -12
- package/test/{awards → unit/awards}/award-auto-refresh.test.js +4 -3
- package/test/{awards → unit/awards}/award-calculations.test.js +3 -2
- package/test/{awards → unit/awards}/award-certificate-display.test.js +12 -11
- package/test/{awards → unit/awards}/award-collection-edge-cases.test.js +12 -11
- package/test/{awards → unit/awards}/award-collection-filtering.test.js +12 -11
- package/test/{awards → unit/awards}/award-completion-flow.test.js +15 -14
- package/test/{awards → unit/awards}/award-exclusion-handling.test.js +20 -19
- package/test/{awards → unit/awards}/award-multi-lesson.test.js +14 -13
- package/test/{awards → unit/awards}/award-observer-integration.test.js +14 -13
- package/test/{awards → unit/awards}/award-query-messages.test.js +30 -21
- package/test/{awards → unit/awards}/award-user-collection.test.js +11 -8
- package/test/{awards → unit/awards}/duplicate-prevention.test.js +12 -11
- package/test/unit/awards/helpers/index.js +3 -0
- package/test/{awards → unit/awards}/helpers/mock-setup.js +1 -1
- package/test/{awards → unit/awards}/helpers/progress-emitter.js +2 -2
- package/test/{awards → unit/awards}/message-generator.test.js +1 -1
- package/test/unit/contentLikes.test.js +62 -0
- package/test/unit/contentProgress.test.js +75 -0
- package/test/{dataContext.test.js → unit/dataContext.test.js} +2 -2
- package/test/unit/dateUtils.test.js +188 -0
- package/test/{imageSRCBuilder.test.js → unit/imageSRCBuilder.test.js} +2 -2
- package/test/{imageSRCVerify.test.js → unit/imageSRCVerify.test.js} +1 -1
- package/test/{lib → unit/lib}/filter.test.ts +10 -4
- package/test/{lib → unit/lib}/lastUpdated.test.js +6 -6
- package/test/{lib → unit/lib}/query.test.ts +1 -1
- package/test/{notifications.test.js → unit/notifications.test.js} +51 -39
- package/test/{progressRows.test.js → unit/progressRows.test.js} +57 -35
- package/test/unit/sanityQueryService.test.js +180 -0
- package/test/{streakMessage.test.js → unit/streakMessage.test.js} +18 -27
- package/test/unit/sync/adapters/idb-errors.test.ts +144 -0
- package/test/unit/sync/adapters/sqlite-errors.test.ts +173 -0
- package/test/unit/sync/helpers/TestModel.ts +44 -0
- package/test/unit/sync/helpers/index.ts +172 -0
- package/test/unit/sync/repositories/content-likes.test.ts +99 -0
- package/test/unit/sync/repositories/practices.test.ts +179 -0
- package/test/unit/sync/repositories/progress.test.ts +245 -0
- package/test/unit/sync/store/store-idb.test.ts +180 -0
- package/test/unit/sync/store/store.test.ts +274 -0
- package/test/unit/userActivity.test.js +99 -0
- package/tsconfig.json +15 -0
- package/test/awards/helpers/index.js +0 -3
- package/test/contentLikes.test.js +0 -95
- package/test/contentProgress.test.js +0 -279
- package/test/learningPaths.test.js +0 -70
- package/test/live/contentProgressLive.test.js +0 -110
- package/test/live/railcontentLive.test.js +0 -7
- package/test/sync/adapter.ts +0 -9
- package/test/sync/initialize-sync-manager.js +0 -88
- package/test/sync/models/award-database-integration.test.js +0 -519
- package/test/userActivity.test.js +0 -118
- /package/test/{awards → unit/awards}/helpers/completion-mock.js +0 -0
- /package/test/{lib → unit/lib}/__snapshots__/filter.test.ts.snap +0 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Automated Testing
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
types: [opened, synchronize, reopened]
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
jobs:
|
|
8
|
+
unit-tests:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
timeout-minutes: 5
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- uses: actions/setup-node@v4
|
|
14
|
+
with:
|
|
15
|
+
node-version: 20
|
|
16
|
+
cache: npm
|
|
17
|
+
- name: Install dependencies
|
|
18
|
+
run: npm ci
|
|
19
|
+
- name: Run unit tests
|
|
20
|
+
run: npm test -- --coverage
|
|
21
|
+
- name: Upload coverage to Codecov
|
|
22
|
+
uses: codecov/codecov-action@v4
|
|
23
|
+
with:
|
|
24
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
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.154.0](https://github.com/railroadmedia/musora-content-services/compare/v2.153.0...v2.154.0) (2026-04-21)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **BEHLTP-23:** add multi-user account update endpoint ([#914](https://github.com/railroadmedia/musora-content-services/issues/914)) ([98f6c4b](https://github.com/railroadmedia/musora-content-services/commit/98f6c4bb830c7a8a25ba556ca07fc97023667011))
|
|
11
|
+
* muppet - add invite validity/error data ([#923](https://github.com/railroadmedia/musora-content-services/issues/923)) ([3938fa5](https://github.com/railroadmedia/musora-content-services/commit/3938fa56ec9295023b6ba624c5f238d1645a911b))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* add typeof before LokiJSAdapter in parameter list ([6a135b5](https://github.com/railroadmedia/musora-content-services/commit/6a135b57c516c277a50d707417544ab2f9eeb194))
|
|
17
|
+
* **BR-632:** dont trickle progress to children when enrolling in GC ([#916](https://github.com/railroadmedia/musora-content-services/issues/916)) ([67a96d6](https://github.com/railroadmedia/musora-content-services/commit/67a96d69bd8b61f12d0efd45475a7b0b08c58813))
|
|
18
|
+
|
|
19
|
+
## [2.153.0](https://github.com/railroadmedia/musora-content-services/compare/v2.152.1...v2.153.0) (2026-04-15)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* 15 minutes ([#911](https://github.com/railroadmedia/musora-content-services/issues/911)) ([87594f8](https://github.com/railroadmedia/musora-content-services/commit/87594f8d33a90452df66d55e7c00d9b0eb15b30b))
|
|
25
|
+
* initial not bad melon test slop ([#918](https://github.com/railroadmedia/musora-content-services/issues/918)) ([008052b](https://github.com/railroadmedia/musora-content-services/commit/008052bbce724ae49fc12734e28957846be76ab3))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
* loki adapter cjs/esm export issues in mcs-cli ([#920](https://github.com/railroadmedia/musora-content-services/issues/920)) ([480755e](https://github.com/railroadmedia/musora-content-services/commit/480755e816e28540cd123efd24ae7e299baecabe))
|
|
31
|
+
* remove flush session interval ([#867](https://github.com/railroadmedia/musora-content-services/issues/867)) ([67af03b](https://github.com/railroadmedia/musora-content-services/commit/67af03b5a0d8159d7754e86d94fa736ac47beab0))
|
|
32
|
+
* unref timer only in node ([92385d6](https://github.com/railroadmedia/musora-content-services/commit/92385d66659b76f1f41ebbfba3557aaea14f1abd))
|
|
33
|
+
|
|
5
34
|
### [2.152.1](https://github.com/railroadmedia/musora-content-services/compare/v2.152.0...v2.152.1) (2026-04-09)
|
|
6
35
|
|
|
7
36
|
## [2.152.0](https://github.com/railroadmedia/musora-content-services/compare/v2.151.1...v2.152.0) (2026-04-09)
|
package/codecov.yml
ADDED
package/jest.config.js
CHANGED
|
@@ -17,19 +17,43 @@ export default {
|
|
|
17
17
|
// Automatically clear mock calls, instances, contexts and results before every test
|
|
18
18
|
clearMocks: true,
|
|
19
19
|
|
|
20
|
+
testTimeout: 30000,
|
|
21
|
+
|
|
20
22
|
// Indicates whether the coverage information should be collected while executing the test
|
|
21
23
|
collectCoverage: true,
|
|
22
24
|
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
+
// Exclude pure transport/adapter layers with no business logic.
|
|
26
|
+
// Files with business logic stay in even at low coverage — gaps should be visible.
|
|
27
|
+
collectCoverageFrom: [
|
|
28
|
+
'src/**/*.{js,ts}',
|
|
29
|
+
'!src/services/sanity.js',
|
|
30
|
+
'!src/services/railcontent.js',
|
|
31
|
+
'!src/services/recommendations.js',
|
|
32
|
+
'!src/index.js',
|
|
33
|
+
'!src/index.d.ts',
|
|
34
|
+
'!src/services/user/account.ts',
|
|
35
|
+
'!src/services/user/sessions.js',
|
|
36
|
+
'!src/services/user/profile.js',
|
|
37
|
+
'!src/services/user/management.js',
|
|
38
|
+
'!src/services/user/interests.js',
|
|
39
|
+
'!src/services/user/payments.ts',
|
|
40
|
+
'!src/services/user/chat.js',
|
|
41
|
+
],
|
|
25
42
|
|
|
26
43
|
// The directory where Jest should output its coverage files
|
|
27
44
|
coverageDirectory: 'coverage',
|
|
28
45
|
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
|
|
46
|
+
// Global threshold — set just below current baseline.
|
|
47
|
+
// Intent is to ratchet up over time as coverage improves.
|
|
48
|
+
// Do not lower these numbers — raise them as tests are added.
|
|
49
|
+
coverageThreshold: {
|
|
50
|
+
global: {
|
|
51
|
+
statements: 40,
|
|
52
|
+
branches: 25,
|
|
53
|
+
functions: 40,
|
|
54
|
+
lines: 40,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
33
57
|
|
|
34
58
|
// Indicates which provider should be used to instrument code for coverage
|
|
35
59
|
// coverageProvider: "babel",
|
|
@@ -92,7 +116,10 @@ export default {
|
|
|
92
116
|
// moduleNameMapper: {},
|
|
93
117
|
|
|
94
118
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
|
95
|
-
modulePathIgnorePatterns: [
|
|
119
|
+
modulePathIgnorePatterns: [
|
|
120
|
+
'<rootDir>/test/live',
|
|
121
|
+
'<rootDir>/test/integration'
|
|
122
|
+
],
|
|
96
123
|
|
|
97
124
|
// Activates notifications for test results
|
|
98
125
|
// notify: false,
|
|
@@ -136,7 +163,11 @@ export default {
|
|
|
136
163
|
// setupFiles: [],
|
|
137
164
|
|
|
138
165
|
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
|
139
|
-
setupFilesAfterEnv: [
|
|
166
|
+
setupFilesAfterEnv: [
|
|
167
|
+
'dotenv/config',
|
|
168
|
+
'<rootDir>/test/setupConsole.js',
|
|
169
|
+
'<rootDir>/test/setupNetworkGuard.js'
|
|
170
|
+
],
|
|
140
171
|
|
|
141
172
|
// The number of seconds after which a test is considered as slow and reported as such in the results.
|
|
142
173
|
// slowTestThreshold: 5,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "musora-content-services",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.154.0",
|
|
4
4
|
"description": "A package for Musoras content services ",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -26,10 +26,14 @@
|
|
|
26
26
|
"@babel/preset-env": "^7.25.3",
|
|
27
27
|
"@babel/preset-typescript": "^7.27.1",
|
|
28
28
|
"@sentry/browser": "^10.21.0",
|
|
29
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
29
30
|
"@types/jest": "^30.0.0",
|
|
30
31
|
"babel-jest": "^29.7.0",
|
|
32
|
+
"better-sqlite3": "^12.9.0",
|
|
31
33
|
"dotenv": "^16.4.5",
|
|
34
|
+
"fake-indexeddb": "^6.2.5",
|
|
32
35
|
"jest": "^29.7.0",
|
|
36
|
+
"jest-environment-jsdom": "^30.3.0",
|
|
33
37
|
"jsdoc": "^4.0.3",
|
|
34
38
|
"jsdoc-babel": "^0.5.0",
|
|
35
39
|
"prettier": "3.4.2",
|
package/src/contentTypeConfig.js
CHANGED
|
@@ -8,6 +8,9 @@ export const CloudFrontURl = 'https://d3fzm1tzeyr5n3.cloudfront.net'
|
|
|
8
8
|
|
|
9
9
|
// This is used to pull related content by license, so we only show "consumable" content
|
|
10
10
|
export const SONG_TYPES = ['song', 'play-along', 'jam-track', 'song-tutorial-lesson']
|
|
11
|
+
|
|
12
|
+
export const parentReferenceField = 'parent_content_reference[0]'
|
|
13
|
+
export const grandParentReferenceField = 'parent_content_reference[1]'
|
|
11
14
|
// Oct 2025: It turns out content-meta categories are not really clear
|
|
12
15
|
// THis is used for the page_type field as a post processor so we include parents and children
|
|
13
16
|
// Duplicated in SanityGateway.php if you update this, update that
|
|
@@ -23,10 +26,6 @@ export const SINGLE_PARENT_TYPES = ['course-lesson', 'pack-bundle-lesson', 'song
|
|
|
23
26
|
|
|
24
27
|
export const LEARNING_PATH_LESSON = 'learning-path-lesson-v2'
|
|
25
28
|
|
|
26
|
-
export const parentField = 'parent_content_data[0]'
|
|
27
|
-
|
|
28
|
-
export const grandParentField = 'parent_content_data[1]'
|
|
29
|
-
|
|
30
29
|
export const genreField = `genre[]->{
|
|
31
30
|
name,
|
|
32
31
|
'slug': slug.current,
|
|
@@ -45,7 +44,7 @@ export const instructorField = `instructor[]->{
|
|
|
45
44
|
|
|
46
45
|
export const artistField = `select(
|
|
47
46
|
defined(artist) => artist->{ 'name': name, 'slug': slug.current, 'thumbnail': thumbnail_url.asset->url},
|
|
48
|
-
defined(
|
|
47
|
+
defined(parent_content_reference) => ${parentReferenceField}->artist->{ 'name': name, 'slug': slug.current, 'thumbnail': thumbnail_url.asset->url}
|
|
49
48
|
)`
|
|
50
49
|
|
|
51
50
|
export const DEFAULT_FIELDS = [
|
|
@@ -68,8 +67,8 @@ export const DEFAULT_FIELDS = [
|
|
|
68
67
|
"'slug' : slug.current",
|
|
69
68
|
"'permission_id': permission_v2",
|
|
70
69
|
'child_count',
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
`"parent_id": ${parentReferenceField}->railcontent_id`,
|
|
71
|
+
`"grandparent_id": ${grandParentReferenceField}->railcontent_id`,
|
|
73
72
|
'live_event_start_time',
|
|
74
73
|
'live_event_end_time',
|
|
75
74
|
'enrollment_start_time',
|
|
@@ -96,8 +95,8 @@ export const DEFAULT_CHILD_FIELDS = [
|
|
|
96
95
|
"'slug' : slug.current",
|
|
97
96
|
"'permission_id': permission_v2",
|
|
98
97
|
'child_count',
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
`"parent_id": ${parentReferenceField}->railcontent_id`,
|
|
99
|
+
`"grandparent_id": ${grandParentReferenceField}->railcontent_id`,
|
|
101
100
|
]
|
|
102
101
|
|
|
103
102
|
export const playAlongMp3sField = `{
|
|
@@ -118,7 +117,7 @@ export const descriptionField = 'description[0].children[0].text'
|
|
|
118
117
|
// this pulls both any defined resources for the document as well as any resources in the parent document
|
|
119
118
|
export const resourcesField = `[
|
|
120
119
|
... resource[]{resource_name, _key, "resource_url": coalesce('${CloudFrontURl}'+string::split(resource_aws.asset->fileURL, '${AWSUrl}')[1], resource_url )},
|
|
121
|
-
...
|
|
120
|
+
... coalesce(parent_content_reference[]->resource[]{resource_name, _key, "resource_url": coalesce('${CloudFrontURl}'+string::split(resource_aws.asset->fileURL, '${AWSUrl}')[1], resource_url )}, []),
|
|
122
121
|
]`
|
|
123
122
|
|
|
124
123
|
export const contentAwardField = "*[references(^._id) && _type == 'content-award'][0]"
|
|
@@ -405,7 +404,7 @@ export let contentTypeConfig = {
|
|
|
405
404
|
},
|
|
406
405
|
'progress-tracker': {
|
|
407
406
|
fields: [
|
|
408
|
-
'"parent_content_data":
|
|
407
|
+
'"parent_content_data": parent_content_reference[]->railcontent_id',
|
|
409
408
|
`"badge" : ${contentAwardField}.badge.asset->url`,
|
|
410
409
|
`"badge_rear" : ${contentAwardField}.badge_rear.asset->url`,
|
|
411
410
|
`"badge_logo" : ${contentAwardField}.logo.asset->url`,
|
|
@@ -528,9 +527,9 @@ export let contentTypeConfig = {
|
|
|
528
527
|
],
|
|
529
528
|
includeChildFields: true,
|
|
530
529
|
childFields: [
|
|
531
|
-
`"parent_data":
|
|
532
|
-
"id":
|
|
533
|
-
|
|
530
|
+
`"parent_data": ${parentReferenceField}->{
|
|
531
|
+
"id": railcontent_id,
|
|
532
|
+
title,
|
|
534
533
|
}`,
|
|
535
534
|
],
|
|
536
535
|
},
|
package/src/index.d.ts
CHANGED
|
@@ -149,7 +149,8 @@ import {
|
|
|
149
149
|
getWeekNumber,
|
|
150
150
|
isNextDay,
|
|
151
151
|
isSameDate,
|
|
152
|
-
toDayjs
|
|
152
|
+
toDayjs,
|
|
153
|
+
toLocalDay
|
|
153
154
|
} from './services/dateUtils.js';
|
|
154
155
|
|
|
155
156
|
import {
|
|
@@ -217,9 +218,11 @@ import {
|
|
|
217
218
|
acceptInvite,
|
|
218
219
|
createAccount,
|
|
219
220
|
createInvites,
|
|
221
|
+
fetchInvite,
|
|
220
222
|
fetchUsersMultiAccountDetails,
|
|
221
223
|
removeUserFromActiveMultiUserAccount,
|
|
222
|
-
rescindInvite
|
|
224
|
+
rescindInvite,
|
|
225
|
+
updateMultiUserAccount
|
|
223
226
|
} from './services/multi-user-accounts/multi-user-accounts.ts';
|
|
224
227
|
|
|
225
228
|
import {
|
|
@@ -550,6 +553,7 @@ declare module 'musora-content-services' {
|
|
|
550
553
|
fetchInstructorLessons,
|
|
551
554
|
fetchInstructors,
|
|
552
555
|
fetchInterests,
|
|
556
|
+
fetchInvite,
|
|
553
557
|
fetchLastSubscriptionPlatform,
|
|
554
558
|
fetchLatestThreads,
|
|
555
559
|
fetchLearningPathLessons,
|
|
@@ -759,6 +763,7 @@ declare module 'musora-content-services' {
|
|
|
759
763
|
startOnboarding,
|
|
760
764
|
status,
|
|
761
765
|
toDayjs,
|
|
766
|
+
toLocalDay,
|
|
762
767
|
togglePlaylistPrivate,
|
|
763
768
|
toggleSignaturePrivate,
|
|
764
769
|
toggleStudentView,
|
|
@@ -779,6 +784,7 @@ declare module 'musora-content-services' {
|
|
|
779
784
|
updateDailySession,
|
|
780
785
|
updateDisplayName,
|
|
781
786
|
updateForumCategory,
|
|
787
|
+
updateMultiUserAccount,
|
|
782
788
|
updateNotificationSetting,
|
|
783
789
|
updateOnboarding,
|
|
784
790
|
updatePlaylist,
|
package/src/index.js
CHANGED
|
@@ -153,7 +153,8 @@ import {
|
|
|
153
153
|
getWeekNumber,
|
|
154
154
|
isNextDay,
|
|
155
155
|
isSameDate,
|
|
156
|
-
toDayjs
|
|
156
|
+
toDayjs,
|
|
157
|
+
toLocalDay
|
|
157
158
|
} from './services/dateUtils.js';
|
|
158
159
|
|
|
159
160
|
import {
|
|
@@ -221,9 +222,11 @@ import {
|
|
|
221
222
|
acceptInvite,
|
|
222
223
|
createAccount,
|
|
223
224
|
createInvites,
|
|
225
|
+
fetchInvite,
|
|
224
226
|
fetchUsersMultiAccountDetails,
|
|
225
227
|
removeUserFromActiveMultiUserAccount,
|
|
226
|
-
rescindInvite
|
|
228
|
+
rescindInvite,
|
|
229
|
+
updateMultiUserAccount
|
|
227
230
|
} from './services/multi-user-accounts/multi-user-accounts.ts';
|
|
228
231
|
|
|
229
232
|
import {
|
|
@@ -549,6 +552,7 @@ export {
|
|
|
549
552
|
fetchInstructorLessons,
|
|
550
553
|
fetchInstructors,
|
|
551
554
|
fetchInterests,
|
|
555
|
+
fetchInvite,
|
|
552
556
|
fetchLastSubscriptionPlatform,
|
|
553
557
|
fetchLatestThreads,
|
|
554
558
|
fetchLearningPathLessons,
|
|
@@ -758,6 +762,7 @@ export {
|
|
|
758
762
|
startOnboarding,
|
|
759
763
|
status,
|
|
760
764
|
toDayjs,
|
|
765
|
+
toLocalDay,
|
|
761
766
|
togglePlaylistPrivate,
|
|
762
767
|
toggleSignaturePrivate,
|
|
763
768
|
toggleStudentView,
|
|
@@ -778,6 +783,7 @@ export {
|
|
|
778
783
|
updateDailySession,
|
|
779
784
|
updateDisplayName,
|
|
780
785
|
updateForumCategory,
|
|
786
|
+
updateMultiUserAccount,
|
|
781
787
|
updateNotificationSetting,
|
|
782
788
|
updateOnboarding,
|
|
783
789
|
updatePlaylist,
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
/** @typedef {Map} AwardDefinitionsMap */
|
|
7
7
|
/** @typedef {Map} ContentToAwardsMap */
|
|
8
8
|
|
|
9
|
+
import { globalConfig } from '../../config'
|
|
10
|
+
|
|
9
11
|
const STORAGE_KEY = 'musora_award_definitions_last_fetch'
|
|
10
12
|
|
|
11
13
|
class AwardDefinitionsService {
|
|
@@ -173,7 +175,6 @@ class AwardDefinitionsService {
|
|
|
173
175
|
|
|
174
176
|
async loadLastFetchFromStorage() {
|
|
175
177
|
try {
|
|
176
|
-
const { globalConfig } = await import('../../config')
|
|
177
178
|
if (!globalConfig.localStorage) {
|
|
178
179
|
return
|
|
179
180
|
}
|
|
@@ -194,8 +195,7 @@ class AwardDefinitionsService {
|
|
|
194
195
|
|
|
195
196
|
async saveLastFetchToStorage() {
|
|
196
197
|
try {
|
|
197
|
-
|
|
198
|
-
if (!globalConfig.localStorage) {
|
|
198
|
+
if (!globalConfig?.localStorage) {
|
|
199
199
|
return
|
|
200
200
|
}
|
|
201
201
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module GuidedCourses
|
|
3
3
|
*/
|
|
4
|
-
import { GET, POST } from '../../infrastructure/http/HttpClient
|
|
4
|
+
import { GET, POST } from '../../infrastructure/http/HttpClient'
|
|
5
5
|
import { contentStatusStarted, getProgressState } from '../contentProgress.js'
|
|
6
6
|
import './playlists-types.js'
|
|
7
7
|
|
|
@@ -14,7 +14,7 @@ export async function enrollUserInGuidedCourse(guidedCourse, { notifications_ena
|
|
|
14
14
|
const response = await POST(url, { notifications_enabled })
|
|
15
15
|
const state = await getProgressState(guidedCourse)
|
|
16
16
|
if (!state) {
|
|
17
|
-
await contentStatusStarted(guidedCourse)
|
|
17
|
+
await contentStatusStarted(guidedCourse, null, { skipBubbleTrickle: true })
|
|
18
18
|
}
|
|
19
19
|
return response
|
|
20
20
|
}
|
|
@@ -416,7 +416,6 @@ export async function recordWatchSession(
|
|
|
416
416
|
mediaLengthSeconds,
|
|
417
417
|
currentSeconds,
|
|
418
418
|
secondsPlayed,
|
|
419
|
-
prevSession = null,
|
|
420
419
|
instrumentId = null,
|
|
421
420
|
categoryId = null,
|
|
422
421
|
isLivestream = false,
|
|
@@ -424,32 +423,14 @@ export async function recordWatchSession(
|
|
|
424
423
|
contentId = normalizeContentId(contentId)
|
|
425
424
|
collection = normalizeCollection(collection)
|
|
426
425
|
|
|
427
|
-
if (!prevSession) {
|
|
428
|
-
prevSession = {
|
|
429
|
-
pushInterval: null
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
426
|
// Track practice and progress locally (no immediate push)
|
|
434
427
|
await Promise.all([
|
|
435
428
|
trackPractice(contentId, secondsPlayed, { instrumentId, categoryId }),
|
|
436
429
|
trackProgress(contentId, collection, currentSeconds, mediaLengthSeconds, isLivestream),
|
|
437
430
|
])
|
|
438
|
-
|
|
439
|
-
if (!prevSession.pushInterval) {
|
|
440
|
-
prevSession.pushInterval = setInterval(() => {
|
|
441
|
-
flushWatchSession()
|
|
442
|
-
}, PUSH_INTERVAL)
|
|
443
|
-
}
|
|
444
|
-
return prevSession
|
|
445
431
|
}
|
|
446
432
|
|
|
447
|
-
export async function flushWatchSession(
|
|
448
|
-
if (shouldClearInterval && sessionToFlush?.pushInterval) {
|
|
449
|
-
clearInterval(sessionToFlush.pushInterval)
|
|
450
|
-
sessionToFlush.pushInterval = null
|
|
451
|
-
}
|
|
452
|
-
|
|
433
|
+
export async function flushWatchSession() {
|
|
453
434
|
db.contentProgress.requestPushUnsynced('flush-watch-session')
|
|
454
435
|
db.practices.requestPushUnsynced('flush-watch-session')
|
|
455
436
|
}
|
|
@@ -490,12 +471,14 @@ export async function contentStatusCompletedMany(contentIds, collection = null)
|
|
|
490
471
|
)
|
|
491
472
|
}
|
|
492
473
|
|
|
493
|
-
|
|
474
|
+
// skipBubbleTrickle is only for starting enrolled GC's as a hack to get them into the progress row.
|
|
475
|
+
export async function contentStatusStarted(contentId, collection = null, {skipPush = false, skipBubbleTrickle = false} = {}) {
|
|
494
476
|
collection = collection ?? {id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF}
|
|
495
477
|
return setStartedOrCompletedStatus(
|
|
496
478
|
normalizeContentId(contentId),
|
|
497
479
|
normalizeCollection(collection),
|
|
498
|
-
false
|
|
480
|
+
false,
|
|
481
|
+
{skipPush, skipBubbleTrickle}
|
|
499
482
|
)
|
|
500
483
|
}
|
|
501
484
|
export async function contentStatusReset(contentId, collection = null, {skipPush = false} = {}) {
|
|
@@ -510,9 +493,9 @@ async function saveContentProgress(contentId, collection, progress, currentSecon
|
|
|
510
493
|
|
|
511
494
|
// filter out contentIds that are setting progress lower than existing
|
|
512
495
|
const contentIdProgress = await getProgressDataByIds([contentId], collection)
|
|
513
|
-
const currentProgress = contentIdProgress[contentId].progress
|
|
496
|
+
const currentProgress = contentIdProgress[contentId].progress
|
|
514
497
|
if (progress <= currentProgress) {
|
|
515
|
-
progress = currentProgress
|
|
498
|
+
progress = currentProgress
|
|
516
499
|
}
|
|
517
500
|
|
|
518
501
|
const hierarchy = await getHierarchy(contentId, collection)
|
|
@@ -578,7 +561,7 @@ async function saveContentProgress(contentId, collection, progress, currentSecon
|
|
|
578
561
|
return response
|
|
579
562
|
}
|
|
580
563
|
|
|
581
|
-
async function setStartedOrCompletedStatus(contentId, collection, isCompleted, {skipPush = false} = {}) {
|
|
564
|
+
async function setStartedOrCompletedStatus(contentId, collection, isCompleted, {skipPush = false, skipBubbleTrickle = false} = {}) {
|
|
582
565
|
const isLP = collection?.type === COLLECTION_TYPE.LEARNING_PATH
|
|
583
566
|
|
|
584
567
|
const hierarchy = await getHierarchy(contentId, collection)
|
|
@@ -594,24 +577,25 @@ async function setStartedOrCompletedStatus(contentId, collection, isCompleted, {
|
|
|
594
577
|
{skipPush: true}
|
|
595
578
|
)
|
|
596
579
|
|
|
597
|
-
let
|
|
598
|
-
|
|
599
|
-
...await bubbleProgress(hierarchy, contentId, collection)
|
|
600
|
-
}
|
|
580
|
+
let allProgresses = {}
|
|
581
|
+
allProgresses[contentId] = progress
|
|
601
582
|
|
|
602
|
-
|
|
603
|
-
|
|
583
|
+
if (!skipBubbleTrickle) {
|
|
584
|
+
let progresses = {
|
|
585
|
+
...trickleProgress(hierarchy, contentId, collection, progress),
|
|
586
|
+
...await bubbleProgress(hierarchy, contentId, collection)
|
|
587
|
+
}
|
|
588
|
+
Object.assign(allProgresses, progresses)
|
|
604
589
|
|
|
605
|
-
|
|
606
|
-
let exportProgresses = progresses
|
|
607
|
-
exportProgresses[contentId] = progress
|
|
608
|
-
await duplicateProgressToALaCarte(exportProgresses, collection, {skipPush: true})
|
|
590
|
+
await bubbleAndTrickleProgressesSafely(progresses, collection, metadata, false)
|
|
609
591
|
}
|
|
610
592
|
|
|
611
|
-
if (
|
|
593
|
+
if (isLP) {
|
|
594
|
+
await duplicateProgressToALaCarte(allProgresses, collection, {skipPush: true})
|
|
595
|
+
}
|
|
612
596
|
|
|
613
|
-
for (const [id,
|
|
614
|
-
if (
|
|
597
|
+
for (const [id, prog] of Object.entries(allProgresses)) {
|
|
598
|
+
if (prog === 100) {
|
|
615
599
|
await onContentCompletedLearningPathActions(Number(id), collection)
|
|
616
600
|
}
|
|
617
601
|
}
|
|
@@ -637,6 +621,8 @@ async function setStartedOrCompletedStatusMany(contentIds, collection, isComplet
|
|
|
637
621
|
{skipPush: true}
|
|
638
622
|
)
|
|
639
623
|
|
|
624
|
+
let allProgresses = Object.fromEntries(contentIds.map(id => [id, progress]))
|
|
625
|
+
|
|
640
626
|
let progresses = {}
|
|
641
627
|
for (const contentId of contentIds) {
|
|
642
628
|
progresses = {
|
|
@@ -645,24 +631,16 @@ async function setStartedOrCompletedStatusMany(contentIds, collection, isComplet
|
|
|
645
631
|
...(await bubbleProgress(hierarchy, contentId, collection)),
|
|
646
632
|
}
|
|
647
633
|
}
|
|
648
|
-
|
|
634
|
+
Object.assign(allProgresses, progresses)
|
|
635
|
+
|
|
649
636
|
await bubbleAndTrickleProgressesSafely(progresses, collection, metadata, false)
|
|
650
637
|
|
|
651
638
|
if (isLP) {
|
|
652
|
-
|
|
653
|
-
for (const contentId of contentIds){
|
|
654
|
-
exportProgresses[contentId] = progress
|
|
655
|
-
}
|
|
656
|
-
await duplicateProgressToALaCarte(exportProgresses, collection, {skipPush: true})
|
|
639
|
+
await duplicateProgressToALaCarte(allProgresses, collection, {skipPush: true})
|
|
657
640
|
}
|
|
658
641
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
await onContentCompletedLearningPathActions(contentId, collection)
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
for (const [id, progress] of Object.entries(progresses)) {
|
|
665
|
-
if (progress === 100) {
|
|
642
|
+
for (const [id, prog] of Object.entries(allProgresses)) {
|
|
643
|
+
if (prog === 100) {
|
|
666
644
|
await onContentCompletedLearningPathActions(Number(id), collection)
|
|
667
645
|
}
|
|
668
646
|
}
|
|
@@ -681,17 +659,20 @@ async function resetStatus(contentId, collection = null, {skipPush = false} = {}
|
|
|
681
659
|
const hierarchy = await getHierarchy(contentId, collection)
|
|
682
660
|
const metadata = hierarchy.metadata || {}
|
|
683
661
|
|
|
662
|
+
let allProgresses = {}
|
|
663
|
+
allProgresses[contentId] = progress
|
|
664
|
+
|
|
684
665
|
let progresses = {
|
|
685
666
|
...trickleProgress(hierarchy, contentId, collection, progress),
|
|
686
667
|
...await bubbleProgress(hierarchy, contentId, collection)
|
|
687
668
|
}
|
|
669
|
+
Object.assign(allProgresses, progresses)
|
|
688
670
|
|
|
689
|
-
// have to use different endpoints for erase vs record
|
|
690
671
|
await bubbleAndTrickleProgressesSafely(progresses, collection, metadata, true)
|
|
691
672
|
|
|
673
|
+
|
|
692
674
|
if (isLP) {
|
|
693
|
-
|
|
694
|
-
await duplicateProgressToALaCarte(progresses, collection, {skipPush: true})
|
|
675
|
+
await duplicateProgressToALaCarte(allProgresses, collection, {skipPush: true})
|
|
695
676
|
}
|
|
696
677
|
|
|
697
678
|
if (!skipPush) db.contentProgress.requestPushUnsynced('reset-status')
|
|
@@ -31,9 +31,17 @@ export function getMonday(date, timeZone = Intl.DateTimeFormat().resolvedOptions
|
|
|
31
31
|
export function getWeekNumber(date) {
|
|
32
32
|
return dayjs(date).isoWeek()
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
// Decides if we need to convert or interpret the date based on whether it has timezone info.
|
|
36
|
+
export function toLocalDay(date, timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone) {
|
|
37
|
+
const hasTimezoneInfo = /Z$|[+-]\d{2}:?\d{2}$/.test(String(date))
|
|
38
|
+
// Has timezone info ? convert : interpret
|
|
39
|
+
return hasTimezoneInfo ? dayjs(date).tz(timeZone) : dayjs.tz(date, timeZone)
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
//Check if two dates are the same
|
|
35
43
|
export function isSameDate(date1, date2) {
|
|
36
|
-
return
|
|
44
|
+
return toLocalDay(date1).isSame(toLocalDay(date2), 'day')
|
|
37
45
|
}
|
|
38
46
|
|
|
39
47
|
// Check if two dates are consecutive days
|
|
@@ -23,7 +23,7 @@ export interface CreatePostParams {
|
|
|
23
23
|
* @throws {HttpError} - If the request fails.
|
|
24
24
|
*/
|
|
25
25
|
export async function createPost(threadId: number, params: CreatePostParams): Promise<ForumPost> {
|
|
26
|
-
const { generateForumPostUrl } = await import('../urlBuilder
|
|
26
|
+
const { generateForumPostUrl } = await import('../urlBuilder')
|
|
27
27
|
|
|
28
28
|
// Generate forum post URL
|
|
29
29
|
const contentUrl = generateForumPostUrl({
|
|
@@ -114,7 +114,7 @@ export async function fetchPosts(
|
|
|
114
114
|
* @throws {HttpError} - If the request fails.
|
|
115
115
|
*/
|
|
116
116
|
export async function likePost(postId: number, brand: string): Promise<void> {
|
|
117
|
-
const { generateForumPostUrl } = await import('../urlBuilder
|
|
117
|
+
const { generateForumPostUrl } = await import('../urlBuilder')
|
|
118
118
|
|
|
119
119
|
// Generate forum post URL
|
|
120
120
|
const contentUrl = generateForumPostUrl({
|