musora-content-services 2.181.0 → 2.183.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/.claude/settings.local.json +4 -14
- package/CHANGELOG.md +20 -0
- package/CLAUDE.md +20 -0
- package/jsdoc.json +1 -0
- package/package.json +1 -1
- package/src/index.d.ts +32 -17
- package/src/index.js +32 -17
- package/src/services/contentProgress.js +1 -1
- package/src/services/my-path/dev/map-types.ts +123 -0
- package/src/services/{content-org → my-path}/learning-paths.ts +22 -8
- package/src/services/my-path/placement-quiz.ts +57 -0
- package/src/services/progress/mutations.ts +1 -1
- package/src/services/progress-row/rows/method-card.js +1 -1
- package/src/services/user/session.ts +34 -0
- package/src/services/userActivity.js +1 -1
- package/src/version-info.js +1 -1
- package/.yarn/install-state.gz +0 -0
- package/dist/services/api/types.js +0 -2
|
@@ -1,14 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"permissions": {
|
|
3
|
-
"allow": [
|
|
4
|
-
"Bash(rg:*)",
|
|
5
|
-
"Bash(npm run lint:*)",
|
|
6
|
-
"Bash(ls:*)",
|
|
7
|
-
"Bash(npm test *)",
|
|
8
|
-
"Bash(npx jest *)"
|
|
9
|
-
],
|
|
10
|
-
"deny": []
|
|
11
|
-
},
|
|
12
2
|
"enabledMcpjsonServers": [
|
|
13
3
|
"1password",
|
|
14
4
|
"atlassian",
|
|
@@ -19,15 +9,15 @@
|
|
|
19
9
|
"hex",
|
|
20
10
|
"sanity",
|
|
21
11
|
"mysql",
|
|
12
|
+
"mysql-staging",
|
|
13
|
+
"mysql-local",
|
|
22
14
|
"slack",
|
|
23
15
|
"langfuse",
|
|
24
16
|
"chrome-devtools",
|
|
17
|
+
"mobile",
|
|
25
18
|
"railway",
|
|
26
19
|
"github",
|
|
27
20
|
"asana",
|
|
28
|
-
"nightwatch"
|
|
29
|
-
"mysql-staging",
|
|
30
|
-
"mysql-local",
|
|
31
|
-
"mobile"
|
|
21
|
+
"nightwatch"
|
|
32
22
|
]
|
|
33
23
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
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.183.0](https://github.com/railroadmedia/musora-content-services/compare/v2.182.0...v2.183.0) (2026-09-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **MU2-1970:** Add magic link login services ([#1056](https://github.com/railroadmedia/musora-content-services/issues/1056)) ([964dc8e](https://github.com/railroadmedia/musora-content-services/commit/964dc8ee532b2bd30e36b697527cee8d5a039ae6))
|
|
11
|
+
|
|
12
|
+
## [2.182.0](https://github.com/railroadmedia/musora-content-services/compare/v2.181.0...v2.182.0) (2026-09-21)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **BEHLTP-368:** placement quiz endpoints ([#1051](https://github.com/railroadmedia/musora-content-services/issues/1051)) ([248b4c9](https://github.com/railroadmedia/musora-content-services/commit/248b4c9e48b056a651d44c129d5d0cb3ce80777d))
|
|
18
|
+
* **BEHLTP-395:** add resetActiveLearningPath() ([#1055](https://github.com/railroadmedia/musora-content-services/issues/1055)) ([02df2df](https://github.com/railroadmedia/musora-content-services/commit/02df2dffd59844ff0c11a43f357efa6ed346a07f))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* move learning-paths.ts to my-path/ module ([#1054](https://github.com/railroadmedia/musora-content-services/issues/1054)) ([49fb9a9](https://github.com/railroadmedia/musora-content-services/commit/49fb9a983512e1d657285ddd13555d5ae9cb8e7b))
|
|
24
|
+
|
|
5
25
|
## [2.181.0](https://github.com/railroadmedia/musora-content-services/compare/v2.180.0...v2.181.0) (2026-09-17)
|
|
6
26
|
|
|
7
27
|
|
package/CLAUDE.md
CHANGED
|
@@ -11,6 +11,24 @@ Do not run tests or try to run tests with npm test unless explicitly asked.
|
|
|
11
11
|
|
|
12
12
|
## Code Style Standards
|
|
13
13
|
|
|
14
|
+
### TypeScript Is Required for New Code
|
|
15
|
+
|
|
16
|
+
This repository is migrating from JavaScript to TypeScript. **All new code must be written in TypeScript.**
|
|
17
|
+
|
|
18
|
+
- New files are `.ts`, never `.js`.
|
|
19
|
+
- New functionality added to an existing `.js` service belongs in a `.ts` file, not appended to the `.js` one. Add it to the existing `.ts` module for that domain (e.g. `session.ts` rather than the legacy `sessions.js`), or create a new `.ts` file if none fits.
|
|
20
|
+
- Do not convert an existing `.js` file to `.ts` as a side effect of an unrelated change - migrations are their own commit.
|
|
21
|
+
- Type every exported function's parameters and return value. Use `Promise<void>` rather than leaving a return untyped.
|
|
22
|
+
- Declare request/response shapes as exported `interface`s. Extend the shared types in `src/services/user/types.d.ts` where one already fits, rather than redeclaring it.
|
|
23
|
+
- JSDoc is for explaining behavior the types cannot express (for example, that an endpoint deliberately resolves on both success and failure). Do not restate parameter and return types in JSDoc - the signature is the documentation.
|
|
24
|
+
- Typecheck before considering a change done:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx tsc --noEmit -p tsconfig.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The index generator handles `.js` and `.ts` alike, so a new `.ts` service is picked up by `npm run build-index` with no extra step.
|
|
31
|
+
|
|
14
32
|
### Self-Documenting Code
|
|
15
33
|
- Write clear, descriptive function and variable names that eliminate the need for comments
|
|
16
34
|
- Never add comments explaining what code does - the code itself should be clear
|
|
@@ -125,6 +143,8 @@ All services are located in `src/services/` and organized by domain:
|
|
|
125
143
|
|
|
126
144
|
**User Services** (`src/services/user/`)
|
|
127
145
|
- `account.ts` - Account management, email changes, password resets
|
|
146
|
+
- `session.ts` - OAuth providers, admin impersonation, magic link login
|
|
147
|
+
- `sessions.js` - Legacy password login, logout, auth-key URLs
|
|
128
148
|
- `profile.js` - User profiles, pictures, signatures
|
|
129
149
|
- `permissions.js` - User permission checking
|
|
130
150
|
- `notifications.js` - Notification management
|
package/jsdoc.json
CHANGED
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
globalConfig,
|
|
22
22
|
initializeEnvVar,
|
|
23
23
|
initializeService,
|
|
24
|
+
setSessionUserData,
|
|
24
25
|
updateSessionConfig
|
|
25
26
|
} from './services/config.js';
|
|
26
27
|
|
|
@@ -54,23 +55,6 @@ import {
|
|
|
54
55
|
unEnrollUserInGuidedCourse
|
|
55
56
|
} from './services/content-org/guided-courses.ts';
|
|
56
57
|
|
|
57
|
-
import {
|
|
58
|
-
completeLearningPathIntroVideo,
|
|
59
|
-
completeMethodIntroVideo,
|
|
60
|
-
fetchLearningPathLessons,
|
|
61
|
-
fetchLearningPathProgressCheckLessons,
|
|
62
|
-
getActivePath,
|
|
63
|
-
getDailySession,
|
|
64
|
-
getEnrichedLearningPath,
|
|
65
|
-
getEnrichedLearningPaths,
|
|
66
|
-
getLearningPathLessonsByIds,
|
|
67
|
-
isNextLessonLocked,
|
|
68
|
-
mapContentToParent,
|
|
69
|
-
resetAllLearningPaths,
|
|
70
|
-
startLearningPath,
|
|
71
|
-
updateDailySession
|
|
72
|
-
} from './services/content-org/learning-paths.ts';
|
|
73
|
-
|
|
74
58
|
import {
|
|
75
59
|
addItemToPlaylist,
|
|
76
60
|
createPlaylist,
|
|
@@ -244,6 +228,29 @@ import {
|
|
|
244
228
|
updateMultiUserAccount
|
|
245
229
|
} from './services/multi-user-accounts/multi-user-accounts.ts';
|
|
246
230
|
|
|
231
|
+
import {
|
|
232
|
+
completeLearningPathIntroVideo,
|
|
233
|
+
completeMethodIntroVideo,
|
|
234
|
+
fetchLearningPathLessons,
|
|
235
|
+
fetchLearningPathProgressCheckLessons,
|
|
236
|
+
getActivePath,
|
|
237
|
+
getDailySession,
|
|
238
|
+
getEnrichedLearningPath,
|
|
239
|
+
getEnrichedLearningPaths,
|
|
240
|
+
getLearningPathLessonsByIds,
|
|
241
|
+
isNextLessonLocked,
|
|
242
|
+
mapContentToParent,
|
|
243
|
+
resetActiveLearningPath,
|
|
244
|
+
resetAllLearningPaths,
|
|
245
|
+
startLearningPath,
|
|
246
|
+
updateDailySession
|
|
247
|
+
} from './services/my-path/learning-paths.ts';
|
|
248
|
+
|
|
249
|
+
import {
|
|
250
|
+
fetchQuizAnswers,
|
|
251
|
+
storeQuizAnswers
|
|
252
|
+
} from './services/my-path/placement-quiz.ts';
|
|
253
|
+
|
|
247
254
|
import {
|
|
248
255
|
deleteAllNotifications,
|
|
249
256
|
deleteNotification,
|
|
@@ -508,7 +515,9 @@ import {
|
|
|
508
515
|
import {
|
|
509
516
|
listOAuthProviders,
|
|
510
517
|
loginAsUser,
|
|
518
|
+
loginWithMagicLink,
|
|
511
519
|
redirectToOAuthProvider,
|
|
520
|
+
requestMagicLoginLink,
|
|
512
521
|
unlinkOAuthProvider,
|
|
513
522
|
verifyOAuthToken
|
|
514
523
|
} from './services/user/session.ts';
|
|
@@ -688,6 +697,7 @@ declare module 'musora-content-services' {
|
|
|
688
697
|
fetchPosts,
|
|
689
698
|
fetchPracticeGoals,
|
|
690
699
|
fetchPublicAnnouncement,
|
|
700
|
+
fetchQuizAnswers,
|
|
691
701
|
fetchRecent,
|
|
692
702
|
fetchRecentActivitiesActiveTabs,
|
|
693
703
|
fetchRecentUserActivities,
|
|
@@ -825,6 +835,7 @@ declare module 'musora-content-services' {
|
|
|
825
835
|
lockThread,
|
|
826
836
|
login,
|
|
827
837
|
loginAsUser,
|
|
838
|
+
loginWithMagicLink,
|
|
828
839
|
logout,
|
|
829
840
|
mapContentToParent,
|
|
830
841
|
markAllNotificationsAsRead,
|
|
@@ -861,8 +872,10 @@ declare module 'musora-content-services' {
|
|
|
861
872
|
reportComment,
|
|
862
873
|
reportPlaylist,
|
|
863
874
|
requestEmailChange,
|
|
875
|
+
requestMagicLoginLink,
|
|
864
876
|
rescindInvite,
|
|
865
877
|
reset,
|
|
878
|
+
resetActiveLearningPath,
|
|
866
879
|
resetAllAwards,
|
|
867
880
|
resetAllLearningPaths,
|
|
868
881
|
resetPassword,
|
|
@@ -878,6 +891,7 @@ declare module 'musora-content-services' {
|
|
|
878
891
|
sendAccountSetupEmail,
|
|
879
892
|
sendPasswordResetEmail,
|
|
880
893
|
sendRevenueCatPurchaseMetadata,
|
|
894
|
+
setSessionUserData,
|
|
881
895
|
setStudentViewForUser,
|
|
882
896
|
setUserPinnedProgressRow,
|
|
883
897
|
setUserSignature,
|
|
@@ -886,6 +900,7 @@ declare module 'musora-content-services' {
|
|
|
886
900
|
startLiveEventPolling,
|
|
887
901
|
startOnboarding,
|
|
888
902
|
status,
|
|
903
|
+
storeQuizAnswers,
|
|
889
904
|
toDayjs,
|
|
890
905
|
toLocalDay,
|
|
891
906
|
togglePlaylistPrivate,
|
package/src/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
globalConfig,
|
|
26
26
|
initializeEnvVar,
|
|
27
27
|
initializeService,
|
|
28
|
+
setSessionUserData,
|
|
28
29
|
updateSessionConfig
|
|
29
30
|
} from './services/config.js';
|
|
30
31
|
|
|
@@ -58,23 +59,6 @@ import {
|
|
|
58
59
|
unEnrollUserInGuidedCourse
|
|
59
60
|
} from './services/content-org/guided-courses.ts';
|
|
60
61
|
|
|
61
|
-
import {
|
|
62
|
-
completeLearningPathIntroVideo,
|
|
63
|
-
completeMethodIntroVideo,
|
|
64
|
-
fetchLearningPathLessons,
|
|
65
|
-
fetchLearningPathProgressCheckLessons,
|
|
66
|
-
getActivePath,
|
|
67
|
-
getDailySession,
|
|
68
|
-
getEnrichedLearningPath,
|
|
69
|
-
getEnrichedLearningPaths,
|
|
70
|
-
getLearningPathLessonsByIds,
|
|
71
|
-
isNextLessonLocked,
|
|
72
|
-
mapContentToParent,
|
|
73
|
-
resetAllLearningPaths,
|
|
74
|
-
startLearningPath,
|
|
75
|
-
updateDailySession
|
|
76
|
-
} from './services/content-org/learning-paths.ts';
|
|
77
|
-
|
|
78
62
|
import {
|
|
79
63
|
addItemToPlaylist,
|
|
80
64
|
createPlaylist,
|
|
@@ -248,6 +232,29 @@ import {
|
|
|
248
232
|
updateMultiUserAccount
|
|
249
233
|
} from './services/multi-user-accounts/multi-user-accounts.ts';
|
|
250
234
|
|
|
235
|
+
import {
|
|
236
|
+
completeLearningPathIntroVideo,
|
|
237
|
+
completeMethodIntroVideo,
|
|
238
|
+
fetchLearningPathLessons,
|
|
239
|
+
fetchLearningPathProgressCheckLessons,
|
|
240
|
+
getActivePath,
|
|
241
|
+
getDailySession,
|
|
242
|
+
getEnrichedLearningPath,
|
|
243
|
+
getEnrichedLearningPaths,
|
|
244
|
+
getLearningPathLessonsByIds,
|
|
245
|
+
isNextLessonLocked,
|
|
246
|
+
mapContentToParent,
|
|
247
|
+
resetActiveLearningPath,
|
|
248
|
+
resetAllLearningPaths,
|
|
249
|
+
startLearningPath,
|
|
250
|
+
updateDailySession
|
|
251
|
+
} from './services/my-path/learning-paths.ts';
|
|
252
|
+
|
|
253
|
+
import {
|
|
254
|
+
fetchQuizAnswers,
|
|
255
|
+
storeQuizAnswers
|
|
256
|
+
} from './services/my-path/placement-quiz.ts';
|
|
257
|
+
|
|
251
258
|
import {
|
|
252
259
|
deleteAllNotifications,
|
|
253
260
|
deleteNotification,
|
|
@@ -512,7 +519,9 @@ import {
|
|
|
512
519
|
import {
|
|
513
520
|
listOAuthProviders,
|
|
514
521
|
loginAsUser,
|
|
522
|
+
loginWithMagicLink,
|
|
515
523
|
redirectToOAuthProvider,
|
|
524
|
+
requestMagicLoginLink,
|
|
516
525
|
unlinkOAuthProvider,
|
|
517
526
|
verifyOAuthToken
|
|
518
527
|
} from './services/user/session.ts';
|
|
@@ -687,6 +696,7 @@ export {
|
|
|
687
696
|
fetchPosts,
|
|
688
697
|
fetchPracticeGoals,
|
|
689
698
|
fetchPublicAnnouncement,
|
|
699
|
+
fetchQuizAnswers,
|
|
690
700
|
fetchRecent,
|
|
691
701
|
fetchRecentActivitiesActiveTabs,
|
|
692
702
|
fetchRecentUserActivities,
|
|
@@ -824,6 +834,7 @@ export {
|
|
|
824
834
|
lockThread,
|
|
825
835
|
login,
|
|
826
836
|
loginAsUser,
|
|
837
|
+
loginWithMagicLink,
|
|
827
838
|
logout,
|
|
828
839
|
mapContentToParent,
|
|
829
840
|
markAllNotificationsAsRead,
|
|
@@ -860,8 +871,10 @@ export {
|
|
|
860
871
|
reportComment,
|
|
861
872
|
reportPlaylist,
|
|
862
873
|
requestEmailChange,
|
|
874
|
+
requestMagicLoginLink,
|
|
863
875
|
rescindInvite,
|
|
864
876
|
reset,
|
|
877
|
+
resetActiveLearningPath,
|
|
865
878
|
resetAllAwards,
|
|
866
879
|
resetAllLearningPaths,
|
|
867
880
|
resetPassword,
|
|
@@ -877,6 +890,7 @@ export {
|
|
|
877
890
|
sendAccountSetupEmail,
|
|
878
891
|
sendPasswordResetEmail,
|
|
879
892
|
sendRevenueCatPurchaseMetadata,
|
|
893
|
+
setSessionUserData,
|
|
880
894
|
setStudentViewForUser,
|
|
881
895
|
setUserPinnedProgressRow,
|
|
882
896
|
setUserSignature,
|
|
@@ -885,6 +899,7 @@ export {
|
|
|
885
899
|
startLiveEventPolling,
|
|
886
900
|
startOnboarding,
|
|
887
901
|
status,
|
|
902
|
+
storeQuizAnswers,
|
|
888
903
|
toDayjs,
|
|
889
904
|
toLocalDay,
|
|
890
905
|
togglePlaylistPrivate,
|
|
@@ -3,7 +3,7 @@ import { db } from './sync'
|
|
|
3
3
|
import { COLLECTION_ID_SELF, COLLECTION_TYPE, STATE } from './sync/models/ContentProgress'
|
|
4
4
|
import { trackUserPractice } from './userActivity'
|
|
5
5
|
import { getNextLessonLessonParentTypes } from '../contentTypeConfig.js'
|
|
6
|
-
import { getDailySession, onLearningPathCompletedActions } from './
|
|
6
|
+
import { getDailySession, onLearningPathCompletedActions } from './my-path/learning-paths.ts'
|
|
7
7
|
import { duplicateProgressToALaCarteOffline } from './offline/progress.ts'
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
type ContentId = number
|
|
4
|
+
type Branch = "essentials" | "classical" | "blues"
|
|
5
|
+
|
|
6
|
+
interface LearningPath {
|
|
7
|
+
id: ContentId
|
|
8
|
+
thumbnail?: string
|
|
9
|
+
title: string
|
|
10
|
+
difficulty?: number
|
|
11
|
+
branch: Branch
|
|
12
|
+
intro_video_id?: number // only if we have intro videos
|
|
13
|
+
published_on?: string
|
|
14
|
+
// ... any other content-related fields we need
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Connection {
|
|
18
|
+
id: string // `${source}:${target}`, stable key
|
|
19
|
+
source: ContentId // prerequisite LP
|
|
20
|
+
target: ContentId // up_next LP
|
|
21
|
+
branch: Branch // which branch this connection belongs to
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface BranchIndex {
|
|
25
|
+
node_ids: ContentId[]
|
|
26
|
+
edge_ids: string[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface CanonicalMapData {
|
|
30
|
+
nodes: Record<ContentId, LearningPath>
|
|
31
|
+
edges: Connection[]
|
|
32
|
+
branches: Record<Branch, BranchIndex>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// example CanonicalMapData:
|
|
36
|
+
const exampleCurriculumMap: CanonicalMapData = {
|
|
37
|
+
"nodes": {
|
|
38
|
+
"1": { "id": 1, "title": "Essentials 1", "branch": "essentials" },
|
|
39
|
+
"2": { "id": 2, "title": "Essentials 2", "branch": "essentials" },
|
|
40
|
+
"3": { "id": 3, "title": "Essentials 3", "branch": "essentials" },
|
|
41
|
+
"4": { "id": 4, "title": "Classical 1", "branch": "classical" },
|
|
42
|
+
"5": { "id": 5, "title": "Classical 2", "branch": "classical" },
|
|
43
|
+
"6": { "id": 6, "title": "Classical 3", "branch": "classical" },
|
|
44
|
+
"7": { "id": 7, "title": "Blues 1", "branch": "blues" },
|
|
45
|
+
"8": { "id": 8, "title": "Blues 2", "branch": "blues" },
|
|
46
|
+
"9": { "id": 9, "title": "Blues 3", "branch": "blues" },
|
|
47
|
+
},
|
|
48
|
+
"edges": [
|
|
49
|
+
{ "id": "1-2", "source": 1, "target": 2, "branch": "essentials" },
|
|
50
|
+
{ "id": "2-3", "source": 2, "target": 3, "branch": "essentials" },
|
|
51
|
+
{ "id": "3-4", "source": 3, "target": 4, "branch": "classical" },
|
|
52
|
+
{ "id": "4-5", "source": 4, "target": 5, "branch": "classical" },
|
|
53
|
+
{ "id": "5-6", "source": 5, "target": 6, "branch": "classical" },
|
|
54
|
+
{ "id": "3-7", "source": 3, "target": 7, "branch": "blues" },
|
|
55
|
+
{ "id": "7-8", "source": 7, "target": 8, "branch": "blues" },
|
|
56
|
+
{ "id": "8-9", "source": 8, "target": 9, "branch": "blues" },
|
|
57
|
+
],
|
|
58
|
+
"branches": {
|
|
59
|
+
"essentials": {
|
|
60
|
+
"node_ids": [1, 2, 3],
|
|
61
|
+
"edge_ids": ["1-2", "2-3"],
|
|
62
|
+
},
|
|
63
|
+
"classical": {
|
|
64
|
+
"node_ids": [4, 5, 6],
|
|
65
|
+
"edge_ids": ["3-4", "4-5", "5-6"],
|
|
66
|
+
},
|
|
67
|
+
"blues": {
|
|
68
|
+
"node_ids": [7, 8, 9],
|
|
69
|
+
"edge_ids": ["3-7", "7-8", "8-9"],
|
|
70
|
+
},
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
const exampleSongSelectionDTO =
|
|
76
|
+
[
|
|
77
|
+
{
|
|
78
|
+
learning_path_id: 8,
|
|
79
|
+
song_id: 101,
|
|
80
|
+
song_title: "Fur Elise",
|
|
81
|
+
song_artist: "Beethoven",
|
|
82
|
+
song_difficulty: 3,
|
|
83
|
+
song_thumbnail: "https://example.com/fur_elise_thumbnail.jpg",
|
|
84
|
+
song_preview: "https://example.com/fur_elise_preview.mp3"
|
|
85
|
+
},
|
|
86
|
+
// ... more song selections
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
const myPathMap = [
|
|
90
|
+
"path-node-1",
|
|
91
|
+
"path-node-2",
|
|
92
|
+
// ... more path nodes
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
const pathNode = {
|
|
96
|
+
"_id": "path-node-1",
|
|
97
|
+
"name": "Learning Path 1",
|
|
98
|
+
"pathStreamArray": [
|
|
99
|
+
{
|
|
100
|
+
"lpReferences": [
|
|
101
|
+
"LP1" // learning path 1
|
|
102
|
+
],
|
|
103
|
+
"isInABTest": true,
|
|
104
|
+
"specificInstrument": "all"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"lpReferences": [
|
|
108
|
+
"LP1.1", // LP1 split into 2 smaller LPs
|
|
109
|
+
"LP1.2"
|
|
110
|
+
],
|
|
111
|
+
"isInABTest": true,
|
|
112
|
+
"specificInstrument": "all"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"lpReferences": [
|
|
116
|
+
"LP1_pad-only" // a pad-only version of LP1
|
|
117
|
+
],
|
|
118
|
+
"isInABTest": false,
|
|
119
|
+
"specificInstrument": "pad" // matches Placement Quiz instrument options
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
|
|
@@ -18,7 +18,8 @@ import {
|
|
|
18
18
|
contentStatusReset,
|
|
19
19
|
getAllCompletedByIds,
|
|
20
20
|
getIdsWhereLastAccessedFromMethod,
|
|
21
|
-
getProgressState,
|
|
21
|
+
getProgressState,
|
|
22
|
+
resetStatus,
|
|
22
23
|
} from '../contentProgress.js'
|
|
23
24
|
import { COLLECTION_ID_SELF, COLLECTION_TYPE, CollectionParameter, STATE } from '../sync/models/ContentProgress'
|
|
24
25
|
import { db, SyncWriteDTO } from '../sync'
|
|
@@ -94,7 +95,7 @@ interface CollectionObject {
|
|
|
94
95
|
* @param brand
|
|
95
96
|
* @param userDate - local datetime. must have date and time - format 2025-10-31T13:45:00
|
|
96
97
|
*/
|
|
97
|
-
export async function getDailySession(brand: string, userDate: Date) {
|
|
98
|
+
export async function getDailySession(brand: string, userDate: Date): Promise<DailySessionResponse | '' | null> {
|
|
98
99
|
const dateWithTimezone = formatLocalDateTime(userDate)
|
|
99
100
|
const key = dailySessionKey(brand, dateWithTimezone)
|
|
100
101
|
|
|
@@ -102,13 +103,13 @@ export async function getDailySession(brand: string, userDate: Date) {
|
|
|
102
103
|
return await dataPromiseGET(dailySessionPromises, key, async () => {
|
|
103
104
|
const url = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${encodeURIComponent(dateWithTimezone)}`
|
|
104
105
|
|
|
105
|
-
const response = await GET(url)
|
|
106
|
+
const response = await GET(url)
|
|
106
107
|
|
|
107
108
|
if (!response) {
|
|
108
109
|
return await updateDailySession(brand, userDate, false)
|
|
109
110
|
}
|
|
110
|
-
return response
|
|
111
|
-
})
|
|
111
|
+
return response
|
|
112
|
+
}) as DailySessionResponse | ''
|
|
112
113
|
} catch (error) {
|
|
113
114
|
console.error('Error fetching daily session:', (error as any).message)
|
|
114
115
|
return null
|
|
@@ -125,7 +126,7 @@ export async function updateDailySession(
|
|
|
125
126
|
brand: string,
|
|
126
127
|
userDate: Date,
|
|
127
128
|
keepFirstLearningPath: boolean = false,
|
|
128
|
-
) {
|
|
129
|
+
): Promise<DailySessionResponse | null> {
|
|
129
130
|
const dateWithTimezone = formatLocalDateTime(userDate)
|
|
130
131
|
const key = dailySessionKey(brand, dateWithTimezone)
|
|
131
132
|
const url: string = `${LEARNING_PATHS_PATH}/daily-session/create`
|
|
@@ -155,7 +156,7 @@ function formatLocalDateTime(date: Date): string {
|
|
|
155
156
|
* Gets user's active learning path.
|
|
156
157
|
* @param brand
|
|
157
158
|
*/
|
|
158
|
-
export async function getActivePath(brand: string) {
|
|
159
|
+
export async function getActivePath(brand: string): Promise<ActiveLearningPathResponse | null> {
|
|
159
160
|
const url: string = `${LEARNING_PATHS_PATH}/active-path/get?brand=${brand}`
|
|
160
161
|
|
|
161
162
|
return (await dataPromiseGET(activePathPromises, activePathKey(brand), () =>
|
|
@@ -168,7 +169,7 @@ export async function getActivePath(brand: string) {
|
|
|
168
169
|
* @param brand
|
|
169
170
|
* @param learningPathId
|
|
170
171
|
*/
|
|
171
|
-
export async function startLearningPath(brand: string, learningPathId: number) {
|
|
172
|
+
export async function startLearningPath(brand: string, learningPathId: number): Promise<ActiveLearningPathResponse | null> {
|
|
172
173
|
const url: string = `${LEARNING_PATHS_PATH}/active-path/set`
|
|
173
174
|
const body = { brand: brand, learning_path_id: learningPathId }
|
|
174
175
|
|
|
@@ -185,6 +186,19 @@ export async function startLearningPath(brand: string, learningPathId: number) {
|
|
|
185
186
|
return response
|
|
186
187
|
}
|
|
187
188
|
|
|
189
|
+
/*
|
|
190
|
+
* Resets the user's active learning path and updates the daily session.
|
|
191
|
+
* @param {string} brand
|
|
192
|
+
* @param {number} learningPathId - The ID of the active learning path to reset.
|
|
193
|
+
* @param {Date} userDate - The user's local date.
|
|
194
|
+
* @returns {Promise<DailySessionResponse | '' | null>} - The updated daily session response or null if an error occurs.
|
|
195
|
+
*/
|
|
196
|
+
export async function resetActiveLearningPath(brand: string, learningPathId: number, userDate: Date): Promise<DailySessionResponse | '' | null> {
|
|
197
|
+
const collection: CollectionObject = { id: learningPathId, type: COLLECTION_TYPE.LEARNING_PATH }
|
|
198
|
+
await resetStatus(learningPathId, collection)
|
|
199
|
+
return await updateDailySession(brand, userDate)
|
|
200
|
+
}
|
|
201
|
+
|
|
188
202
|
function dataPromiseGET<T>(
|
|
189
203
|
cache: Map<string, Promise<T>>,
|
|
190
204
|
key: string,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module MyPathPlacementQuiz
|
|
3
|
+
*/
|
|
4
|
+
import { HttpClient } from '../../infrastructure/http/HttpClient'
|
|
5
|
+
import { globalConfig } from '../config.js'
|
|
6
|
+
import { OnboardingRecommendedContent } from '@/services/user/onboarding'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
const baseUrl = `/api/my-path/v1/placement-quiz`
|
|
10
|
+
|
|
11
|
+
interface PlacementQuizAnswers {
|
|
12
|
+
skill_level: 'new' | 'beginner' | 'intermediate' | 'advanced' | 'expert'
|
|
13
|
+
genres: string[]
|
|
14
|
+
gear: string[]
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface PlacementQuizResponse {
|
|
18
|
+
user_id: string
|
|
19
|
+
brand: string
|
|
20
|
+
answers: PlacementQuizAnswers
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface StorePlacementQuizResponse {
|
|
24
|
+
placement_quiz: PlacementQuizResponse
|
|
25
|
+
recommended_content: OnboardingRecommendedContent
|
|
26
|
+
error?: any
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Fetches the placement quiz answers from the API.
|
|
31
|
+
*
|
|
32
|
+
* @param brand
|
|
33
|
+
* @returns {Promise<PlacementQuizResponse>} - A promise that resolves to the placement quiz answers.
|
|
34
|
+
* @throws {HttpError} - If the HTTP request fails.
|
|
35
|
+
*/
|
|
36
|
+
export async function fetchQuizAnswers(brand: string): Promise<PlacementQuizResponse> {
|
|
37
|
+
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
38
|
+
return httpClient.get<PlacementQuizResponse>(`${baseUrl}?brand=${brand}`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Stores the placement quiz answers to the API.
|
|
43
|
+
*
|
|
44
|
+
* @param brand
|
|
45
|
+
* @param answers
|
|
46
|
+
* @returns {Promise<PlacementQuizResponse>} - A promise that resolves to the stored placement quiz answers.
|
|
47
|
+
* @throws {HttpError} - If the HTTP request fails.
|
|
48
|
+
*/
|
|
49
|
+
export async function storeQuizAnswers(brand: string, answers: PlacementQuizAnswers): Promise<StorePlacementQuizResponse> {
|
|
50
|
+
const body = {
|
|
51
|
+
brand,
|
|
52
|
+
answers,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
56
|
+
return httpClient.post<StorePlacementQuizResponse>(baseUrl, body)
|
|
57
|
+
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
CollectionParameter,
|
|
6
6
|
} from '../sync/models/ContentProgress'
|
|
7
7
|
import { getHierarchies, getHierarchy } from '../sanity.js'
|
|
8
|
-
import { onLearningPathCompletedActions } from '../
|
|
8
|
+
import { onLearningPathCompletedActions } from '../my-path/learning-paths'
|
|
9
9
|
import { duplicateProgressToALaCarteOffline } from '../offline/progress'
|
|
10
10
|
import {
|
|
11
11
|
bubbleAndTrickleProgressesSafely,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module ProgressRow
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { getActivePath, fetchLearningPathLessons } from '../../
|
|
5
|
+
import { getActivePath, fetchLearningPathLessons } from '../../my-path/learning-paths.ts'
|
|
6
6
|
import { fetchMethodV2IntroVideo } from '../../sanity'
|
|
7
7
|
import { getProgressState } from '../../contentProgress'
|
|
8
8
|
import { COLLECTION_TYPE, STATE } from '../../sync/models/ContentProgress'
|
|
@@ -58,3 +58,37 @@ export async function loginAsUser(userId: string): Promise<AuthResponse> {
|
|
|
58
58
|
{}
|
|
59
59
|
)
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
export interface MagicLinkAuthResponse extends AuthResponse {
|
|
63
|
+
refresh_token: string | null
|
|
64
|
+
redirect_to: string | null
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface MagicLinkDeviceParams {
|
|
68
|
+
device_name?: string
|
|
69
|
+
device_token?: string
|
|
70
|
+
platform?: 'ios' | 'android'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolves whether or not an account exists for the email, so the endpoint
|
|
75
|
+
* cannot be used to discover which addresses are registered.
|
|
76
|
+
*/
|
|
77
|
+
export async function requestMagicLoginLink(
|
|
78
|
+
email: string,
|
|
79
|
+
redirectTo: string | null = null
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
const apiUrl = '/api/user-management-system/v1/sessions/magic-link'
|
|
82
|
+
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
83
|
+
await httpClient.post(apiUrl, { email, redirect_to: redirectTo })
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function loginWithMagicLink(
|
|
87
|
+
email: string,
|
|
88
|
+
token: string,
|
|
89
|
+
params: MagicLinkDeviceParams = {}
|
|
90
|
+
): Promise<MagicLinkAuthResponse> {
|
|
91
|
+
const apiUrl = '/api/user-management-system/v1/sessions/magic-link/exchange'
|
|
92
|
+
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
93
|
+
return httpClient.post<MagicLinkAuthResponse>(apiUrl, { email, token, ...params })
|
|
94
|
+
}
|
|
@@ -13,7 +13,7 @@ import dayjs from 'dayjs'
|
|
|
13
13
|
import { addContextToContent } from './contentAggregator.js'
|
|
14
14
|
import { db, Q } from './sync'
|
|
15
15
|
import { streakCalculator } from './user/streakCalculator'
|
|
16
|
-
import { mapContentsThatWereLastProgressedFromMethod } from "./
|
|
16
|
+
import { mapContentsThatWereLastProgressedFromMethod } from "./my-path/learning-paths.ts";
|
|
17
17
|
|
|
18
18
|
const DAYS = ['M', 'T', 'W', 'T', 'F', 'S', 'S']
|
|
19
19
|
|
package/src/version-info.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const MCS_VERSION = '2.
|
|
1
|
+
export const MCS_VERSION = '2.183.0'
|
package/.yarn/install-state.gz
DELETED
|
Binary file
|