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.
@@ -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
@@ -6,6 +6,7 @@
6
6
  "src/services/content",
7
7
  "src/services/gamification",
8
8
  "src/services/multi-user-accounts",
9
+ "src/services/my-path",
9
10
  "src/services/awards",
10
11
  "src/services/api",
11
12
  "src/services/progress-row",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.181.0",
3
+ "version": "2.183.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
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 './content-org/learning-paths.ts'
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) as DailySessionResponse | ''
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 as DailySessionResponse
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 '../content-org/learning-paths'
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 '../../content-org/learning-paths'
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 "./content-org/learning-paths.ts";
16
+ import { mapContentsThatWereLastProgressedFromMethod } from "./my-path/learning-paths.ts";
17
17
 
18
18
  const DAYS = ['M', 'T', 'W', 'T', 'F', 'S', 'S']
19
19
 
@@ -1 +1 @@
1
- export const MCS_VERSION = '2.181.0'
1
+ export const MCS_VERSION = '2.183.0'
Binary file
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });