musora-content-services 2.181.0 → 2.182.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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
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.182.0](https://github.com/railroadmedia/musora-content-services/compare/v2.181.0...v2.182.0) (2026-09-21)
6
+
7
+
8
+ ### Features
9
+
10
+ * **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))
11
+ * **BEHLTP-395:** add resetActiveLearningPath() ([#1055](https://github.com/railroadmedia/musora-content-services/issues/1055)) ([02df2df](https://github.com/railroadmedia/musora-content-services/commit/02df2dffd59844ff0c11a43f357efa6ed346a07f))
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * 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))
17
+
5
18
  ## [2.181.0](https://github.com/railroadmedia/musora-content-services/compare/v2.180.0...v2.181.0) (2026-09-17)
6
19
 
7
20
 
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.182.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,
@@ -688,6 +695,7 @@ declare module 'musora-content-services' {
688
695
  fetchPosts,
689
696
  fetchPracticeGoals,
690
697
  fetchPublicAnnouncement,
698
+ fetchQuizAnswers,
691
699
  fetchRecent,
692
700
  fetchRecentActivitiesActiveTabs,
693
701
  fetchRecentUserActivities,
@@ -863,6 +871,7 @@ declare module 'musora-content-services' {
863
871
  requestEmailChange,
864
872
  rescindInvite,
865
873
  reset,
874
+ resetActiveLearningPath,
866
875
  resetAllAwards,
867
876
  resetAllLearningPaths,
868
877
  resetPassword,
@@ -878,6 +887,7 @@ declare module 'musora-content-services' {
878
887
  sendAccountSetupEmail,
879
888
  sendPasswordResetEmail,
880
889
  sendRevenueCatPurchaseMetadata,
890
+ setSessionUserData,
881
891
  setStudentViewForUser,
882
892
  setUserPinnedProgressRow,
883
893
  setUserSignature,
@@ -886,6 +896,7 @@ declare module 'musora-content-services' {
886
896
  startLiveEventPolling,
887
897
  startOnboarding,
888
898
  status,
899
+ storeQuizAnswers,
889
900
  toDayjs,
890
901
  toLocalDay,
891
902
  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,
@@ -687,6 +694,7 @@ export {
687
694
  fetchPosts,
688
695
  fetchPracticeGoals,
689
696
  fetchPublicAnnouncement,
697
+ fetchQuizAnswers,
690
698
  fetchRecent,
691
699
  fetchRecentActivitiesActiveTabs,
692
700
  fetchRecentUserActivities,
@@ -862,6 +870,7 @@ export {
862
870
  requestEmailChange,
863
871
  rescindInvite,
864
872
  reset,
873
+ resetActiveLearningPath,
865
874
  resetAllAwards,
866
875
  resetAllLearningPaths,
867
876
  resetPassword,
@@ -877,6 +886,7 @@ export {
877
886
  sendAccountSetupEmail,
878
887
  sendPasswordResetEmail,
879
888
  sendRevenueCatPurchaseMetadata,
889
+ setSessionUserData,
880
890
  setStudentViewForUser,
881
891
  setUserPinnedProgressRow,
882
892
  setUserSignature,
@@ -885,6 +895,7 @@ export {
885
895
  startLiveEventPolling,
886
896
  startOnboarding,
887
897
  status,
898
+ storeQuizAnswers,
888
899
  toDayjs,
889
900
  toLocalDay,
890
901
  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'
@@ -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.182.0'