musora-content-services 2.119.3 → 2.121.2

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,29 @@
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.121.2](https://github.com/railroadmedia/musora-content-services/compare/v2.121.1...v2.121.2) (2026-01-20)
6
+
7
+ ### [2.121.1](https://github.com/railroadmedia/musora-content-services/compare/v2.121.0...v2.121.1) (2026-01-20)
8
+
9
+ ## [2.121.0](https://github.com/railroadmedia/musora-content-services/compare/v2.119.3...v2.121.0) (2026-01-20)
10
+
11
+
12
+ ### Features
13
+
14
+ * add fetchLiveStreamData method ([aa9b704](https://github.com/railroadmedia/musora-content-services/commit/aa9b7046c7fc69e8b30c0f035577da62f8c5b5fd))
15
+ * add vimeo_live_event_id to GROQ queries ([d4fff6f](https://github.com/railroadmedia/musora-content-services/commit/d4fff6f09ca62682f6cead7e43f6979a56b86e0a))
16
+ * **TP-1060:** method data caching in mcs ([#701](https://github.com/railroadmedia/musora-content-services/issues/701)) ([25efc43](https://github.com/railroadmedia/musora-content-services/commit/25efc43b6c07172db2f20d51d5b79ac9fc204eef))
17
+ * update brand endpoint ([#719](https://github.com/railroadmedia/musora-content-services/issues/719)) ([4938b61](https://github.com/railroadmedia/musora-content-services/commit/4938b61c8526e31194b284c6daf172764b7900ed))
18
+
19
+ ## [2.120.0](https://github.com/railroadmedia/musora-content-services/compare/v2.119.3...v2.120.0) (2026-01-19)
20
+
21
+
22
+ ### Features
23
+
24
+ * add fetchLiveStreamData method ([aa9b704](https://github.com/railroadmedia/musora-content-services/commit/aa9b7046c7fc69e8b30c0f035577da62f8c5b5fd))
25
+ * add vimeo_live_event_id to GROQ queries ([d4fff6f](https://github.com/railroadmedia/musora-content-services/commit/d4fff6f09ca62682f6cead7e43f6979a56b86e0a))
26
+ * **TP-1060:** method data caching in mcs ([#701](https://github.com/railroadmedia/musora-content-services/issues/701)) ([25efc43](https://github.com/railroadmedia/musora-content-services/commit/25efc43b6c07172db2f20d51d5b79ac9fc204eef))
27
+
5
28
  ### [2.119.3](https://github.com/railroadmedia/musora-content-services/compare/v2.119.2...v2.119.3) (2026-01-14)
6
29
 
7
30
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.119.3",
3
+ "version": "2.121.2",
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
@@ -226,6 +226,7 @@ import {
226
226
  fetchComments,
227
227
  fetchContentPageUserData,
228
228
  fetchLikeCount,
229
+ fetchLiveStreamData,
229
230
  fetchRecentUserActivities,
230
231
  fetchTopComment,
231
232
  fetchUserPermissionsData,
@@ -350,6 +351,7 @@ import {
350
351
  setUserSignature,
351
352
  toggleSignaturePrivate,
352
353
  unblockUser,
354
+ updateBrand,
353
355
  updateDisplayName,
354
356
  uploadPicture,
355
357
  uploadPictureFromS3
@@ -415,6 +417,7 @@ import {
415
417
  getPracticeNotes,
416
418
  getPracticeSessions,
417
419
  getRecentActivity,
420
+ getStreaksAndMessage,
418
421
  getUserMonthlyStats,
419
422
  getUserWeeklyStats,
420
423
  recordUserActivity,
@@ -523,6 +526,7 @@ declare module 'musora-content-services' {
523
526
  fetchLikeCount,
524
527
  fetchLiveEvent,
525
528
  fetchLiveEventPollingState,
529
+ fetchLiveStreamData,
526
530
  fetchMemberships,
527
531
  fetchMetadata,
528
532
  fetchMethodV2IntroVideo,
@@ -623,6 +627,7 @@ declare module 'musora-content-services' {
623
627
  getSongTypesFor,
624
628
  getSortOrder,
625
629
  getStartedOrCompletedProgressOnly,
630
+ getStreaksAndMessage,
626
631
  getTabResults,
627
632
  getTimeRemainingUntilLocal,
628
633
  getToday,
@@ -720,6 +725,7 @@ declare module 'musora-content-services' {
720
725
  unlockThread,
721
726
  unpinProgressRow,
722
727
  unpinThread,
728
+ updateBrand,
723
729
  updateDailySession,
724
730
  updateDisplayName,
725
731
  updateForumCategory,
package/src/index.js CHANGED
@@ -230,6 +230,7 @@ import {
230
230
  fetchComments,
231
231
  fetchContentPageUserData,
232
232
  fetchLikeCount,
233
+ fetchLiveStreamData,
233
234
  fetchRecentUserActivities,
234
235
  fetchTopComment,
235
236
  fetchUserPermissionsData,
@@ -354,6 +355,7 @@ import {
354
355
  setUserSignature,
355
356
  toggleSignaturePrivate,
356
357
  unblockUser,
358
+ updateBrand,
357
359
  updateDisplayName,
358
360
  uploadPicture,
359
361
  uploadPictureFromS3
@@ -419,6 +421,7 @@ import {
419
421
  getPracticeNotes,
420
422
  getPracticeSessions,
421
423
  getRecentActivity,
424
+ getStreaksAndMessage,
422
425
  getUserMonthlyStats,
423
426
  getUserWeeklyStats,
424
427
  recordUserActivity,
@@ -522,6 +525,7 @@ export {
522
525
  fetchLikeCount,
523
526
  fetchLiveEvent,
524
527
  fetchLiveEventPollingState,
528
+ fetchLiveStreamData,
525
529
  fetchMemberships,
526
530
  fetchMetadata,
527
531
  fetchMethodV2IntroVideo,
@@ -622,6 +626,7 @@ export {
622
626
  getSongTypesFor,
623
627
  getSortOrder,
624
628
  getStartedOrCompletedProgressOnly,
629
+ getStreaksAndMessage,
625
630
  getTabResults,
626
631
  getTimeRemainingUntilLocal,
627
632
  getToday,
@@ -719,6 +724,7 @@ export {
719
724
  unlockThread,
720
725
  unpinProgressRow,
721
726
  unpinThread,
727
+ updateBrand,
722
728
  updateDailySession,
723
729
  updateDisplayName,
724
730
  updateForumCategory,
@@ -7,6 +7,11 @@ import { DefaultHeaderProvider } from './providers/DefaultHeaderProvider'
7
7
  import { FetchRequestExecutor } from './executors/FetchRequestExecutor'
8
8
  import { globalConfig } from '../../services/config'
9
9
 
10
+ type _RequestOptions = {
11
+ dataVersion?: string | null
12
+ cache?: RequestCache
13
+ }
14
+
10
15
  export class HttpClient {
11
16
  private baseUrl: string
12
17
  private token: string | null
@@ -33,38 +38,38 @@ export class HttpClient {
33
38
  this.token = null;
34
39
  }
35
40
 
36
- public async get<T>(url: string, dataVersion: string | null = null): Promise<T> {
37
- return this.request<T>(url, 'GET', dataVersion)
41
+ public async get<T>(url: string, options: _RequestOptions = {}): Promise<T> {
42
+ return this.request<T>(url, 'GET', options)
38
43
  }
39
44
 
40
- public async post<T>(url: string, data: any, dataVersion: string | null = null): Promise<T> {
41
- return this.request<T>(url, 'POST', dataVersion, data)
45
+ public async post<T>(url: string, data: any, options: _RequestOptions = {}): Promise<T> {
46
+ return this.request<T>(url, 'POST', options, data)
42
47
  }
43
48
 
44
- public async put<T>(url: string, data: any, dataVersion: string | null = null): Promise<T> {
45
- return this.request<T>(url, 'PUT', dataVersion, data)
49
+ public async put<T>(url: string, data: any, options: _RequestOptions = {}): Promise<T> {
50
+ return this.request<T>(url, 'PUT', options, data)
46
51
  }
47
52
 
48
- public async patch<T>(url: string, data: any, dataVersion: string | null = null): Promise<T> {
49
- return this.request<T>(url, 'PATCH', dataVersion, data)
53
+ public async patch<T>(url: string, data: any, options: _RequestOptions = {}): Promise<T> {
54
+ return this.request<T>(url, 'PATCH', options, data)
50
55
  }
51
56
 
52
- public async delete<T>(url: string, data: any = null, dataVersion: string | null = null): Promise<T> {
53
- return this.request<T>(url, 'DELETE', dataVersion, data)
57
+ public async delete<T>(url: string, data: any = null, options: _RequestOptions = {}): Promise<T> {
58
+ return this.request<T>(url, 'DELETE', options, data)
54
59
  }
55
60
 
56
61
  private async request<T>(
57
62
  url: string,
58
63
  method: string,
59
- dataVersion: string | null = null,
64
+ options: _RequestOptions = {},
60
65
  body: any = null
61
66
  ): Promise<T> {
62
67
  try {
63
- const headers = this.buildHeaders(dataVersion)
64
- const options = this.buildRequestOptions(method, headers, body)
68
+ const headers = this.buildHeaders(options.dataVersion)
69
+ const requestOptions = this.buildRequestOptions(method, headers, body, options.cache)
65
70
  const fullUrl = this.resolveUrl(url)
66
71
 
67
- return await this.requestExecutor.execute<T>(fullUrl, options)
72
+ return await this.requestExecutor.execute<T>(fullUrl, requestOptions)
68
73
  } catch (error: any) {
69
74
  return this.handleError(error, url, method)
70
75
  }
@@ -90,7 +95,8 @@ export class HttpClient {
90
95
  private buildRequestOptions(
91
96
  method: string,
92
97
  headers: Record<string, string>,
93
- body: any
98
+ body: any,
99
+ cache?: RequestCache | null
94
100
  ): RequestOptions {
95
101
  const options: RequestOptions = {
96
102
  method,
@@ -98,6 +104,10 @@ export class HttpClient {
98
104
  credentials: 'include',
99
105
  }
100
106
 
107
+ if (cache) {
108
+ options.cache = cache
109
+ }
110
+
101
111
  if (body) {
102
112
  const isFormData = typeof FormData !== 'undefined' && body instanceof FormData
103
113
  if (isFormData) {
@@ -3,4 +3,5 @@ export interface RequestOptions {
3
3
  headers: Record<string, string>
4
4
  credentials?: "omit" | "same-origin" | "include"
5
5
  body?: string
6
+ cache?: RequestCache
6
7
  }
@@ -16,11 +16,13 @@ import { COLLECTION_TYPE, STATE } from '../sync/models/ContentProgress'
16
16
  import { SyncWriteDTO } from '../sync'
17
17
  import { ContentProgress } from '../sync/models'
18
18
  import { CollectionParameter } from '../sync/repositories/content-progress'
19
- import { getToday } from "../dateUtils.js";
19
+ import dayjs from 'dayjs'
20
20
 
21
21
  const BASE_PATH: string = `/api/content-org`
22
22
  const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
23
23
  const LEARNING_PATH_LESSON = 'learning-path-lesson-v2'
24
+ let dailySessionPromise: Promise<DailySessionResponse> | null = null
25
+ let activePathPromise: Promise<ActiveLearningPathResponse> | null = null
24
26
 
25
27
  interface ActiveLearningPathResponse {
26
28
  user_id: number
@@ -51,17 +53,20 @@ interface CollectionObject {
51
53
  * Gets today's daily session for the user.
52
54
  * If the daily session doesn't exist, it will be created.
53
55
  * @param brand
54
- * @param userDate
56
+ * @param userDate - local datetime. must have date and time - format 2025-10-31T13:45:00
57
+ * @param forceRefresh - force cache refresh
55
58
  */
56
- export async function getDailySession(brand: string, userDate: Date) {
57
- const stringDate = userDate.toISOString().split('T')[0]
58
- const url: string = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${stringDate}`
59
+ export async function getDailySession(brand: string, userDate: Date, forceRefresh: boolean = false) {
60
+ const dateWithTimezone = formatLocalDateTime(userDate)
61
+ const url: string = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${encodeURIComponent(dateWithTimezone)}`
59
62
  try {
60
- const response = await GET(url)
63
+ const response = await dataPromiseGET(url, forceRefresh) as DailySessionResponse
61
64
  if (!response) {
62
65
  return await updateDailySession(brand, userDate, false)
63
66
  }
67
+ dailySessionPromise = null // reset promise after successful fetch
64
68
  return response as DailySessionResponse
69
+
65
70
  } catch (error: any) {
66
71
  if (error.status === 204) {
67
72
  return await updateDailySession(brand, userDate, false)
@@ -81,20 +86,38 @@ export async function updateDailySession(
81
86
  userDate: Date,
82
87
  keepFirstLearningPath: boolean = false
83
88
  ) {
84
- const stringDate = userDate.toISOString().split('T')[0]
89
+ const dateWithTimezone = formatLocalDateTime(userDate)
85
90
  const url: string = `${LEARNING_PATHS_PATH}/daily-session/create`
86
- const body = { brand: brand, userDate: stringDate, keepFirstLearningPath: keepFirstLearningPath }
87
- return (await POST(url, body)) as DailySessionResponse
91
+ const body = { brand: brand, userDate: dateWithTimezone, keepFirstLearningPath: keepFirstLearningPath }
92
+
93
+ const response = await POST(url, body) as DailySessionResponse
94
+
95
+ if (response) {
96
+ const urlGet: string = `${LEARNING_PATHS_PATH}/daily-session/get?brand=${brand}&userDate=${encodeURIComponent(dateWithTimezone)}`
97
+ dataPromiseGET(urlGet, true) // refresh cache
98
+ }
99
+
100
+ return response
101
+ }
102
+
103
+ function formatLocalDateTime(date: Date): string {
104
+ return dayjs(date).format('YYYY-MM-DD Z')
88
105
  }
89
106
 
90
107
  /**
91
108
  * Gets user's active learning path.
92
109
  * @param brand
110
+ * @param forceRefresh - force cache refresh
93
111
  */
94
- export async function getActivePath(brand: string) {
112
+ export async function getActivePath(brand: string, forceRefresh: boolean = false) {
95
113
  const url: string = `${LEARNING_PATHS_PATH}/active-path/get?brand=${brand}`
96
- return (await GET(url)) as ActiveLearningPathResponse
114
+
115
+ const response = await dataPromiseGET(url, forceRefresh) as ActiveLearningPathResponse
116
+ activePathPromise = null // reset promise after successful fetch
117
+
118
+ return response
97
119
  }
120
+
98
121
  /**
99
122
  * Sets a new learning path as the user's active learning path.
100
123
  * @param brand
@@ -103,7 +126,31 @@ export async function getActivePath(brand: string) {
103
126
  export async function startLearningPath(brand: string, learningPathId: number) {
104
127
  const url: string = `${LEARNING_PATHS_PATH}/active-path/set`
105
128
  const body = { brand: brand, learning_path_id: learningPathId }
106
- return (await POST(url, body)) as ActiveLearningPathResponse
129
+
130
+ const response = await POST(url, body) as ActiveLearningPathResponse
131
+
132
+ // manual BE call to avoid recursive POST<->GET calls
133
+ if (response) {
134
+ const urlGet: string = `${LEARNING_PATHS_PATH}/active-path/get?brand=${brand}`
135
+ dataPromiseGET(urlGet, true) // refresh cache
136
+ }
137
+
138
+ return response
139
+ }
140
+
141
+ async function dataPromiseGET(url: string, forceRefresh: boolean): Promise<DailySessionResponse|ActiveLearningPathResponse> {
142
+ if (url.includes('daily-session')) {
143
+ if (!dailySessionPromise || forceRefresh) {
144
+ dailySessionPromise = GET(url, {cache: forceRefresh ? 'reload' : 'default'}) as Promise<DailySessionResponse>
145
+ }
146
+ return dailySessionPromise
147
+
148
+ } else if (url.includes('active-path')) {
149
+ if (!activePathPromise || forceRefresh) {
150
+ activePathPromise = GET(url, {cache: forceRefresh ? 'reload' : 'default'}) as Promise<ActiveLearningPathResponse>
151
+ }
152
+ return activePathPromise
153
+ }
107
154
  }
108
155
 
109
156
  /**
@@ -374,7 +421,7 @@ export async function completeMethodIntroVideo(
374
421
  const methodStructure = await fetchMethodV2Structure(brand)
375
422
 
376
423
  const firstLearningPathId = methodStructure.learning_paths[0].id
377
- response.active_path_response = await methodIntroVideoCompleteActions(brand, firstLearningPathId, getToday())
424
+ response.active_path_response = await methodIntroVideoCompleteActions(brand, firstLearningPathId, new Date())
378
425
 
379
426
  response.intro_video_response = await completeIfNotCompleted(introVideoId)
380
427
 
@@ -428,7 +475,7 @@ export async function completeLearningPathIntroVideo(
428
475
  if (activePath.active_learning_path_id === learningPathId) {
429
476
  response.update_dailies_response = await updateDailySession(
430
477
  brand,
431
- getToday(),
478
+ new Date(),
432
479
  true
433
480
  )
434
481
  }
@@ -4,7 +4,6 @@ import { COLLECTION_TYPE, STATE } from './sync/models/ContentProgress'
4
4
  import { trackUserPractice, findIncompleteLesson } from './userActivity'
5
5
  import { getNextLessonLessonParentTypes } from '../contentTypeConfig.js'
6
6
  import {getDailySession, onContentCompletedLearningPathActions} from "./content-org/learning-paths.ts";
7
- import {getToday} from "./dateUtils.js";
8
7
  import { fetchBrandsByContentIds } from './sanity.js'
9
8
 
10
9
  const STATE_STARTED = STATE.STARTED
@@ -37,7 +36,7 @@ export async function getNavigateToForMethod(data) {
37
36
  let navigateToData = {}
38
37
 
39
38
  const brand = data[0].content.brand || null
40
- const dailySessionResponse = await getDailySession(brand, getToday())
39
+ const dailySessionResponse = await getDailySession(brand, new Date())
41
40
  const dailySession = dailySessionResponse?.daily_session || null
42
41
  const activeLearningPathId = dailySessionResponse?.active_learning_path_id || null
43
42
 
@@ -3,7 +3,6 @@
3
3
  */
4
4
 
5
5
  import { getActivePath, fetchLearningPathLessons } from '../../content-org/learning-paths'
6
- import { getToday } from '../../dateUtils.js'
7
6
  import { fetchMethodV2IntroVideo } from '../../sanity'
8
7
  import { getProgressState } from '../../contentProgress'
9
8
  import {COLLECTION_TYPE, STATE} from '../../sync/models/ContentProgress'
@@ -45,7 +44,7 @@ export async function getMethodCard(brand) {
45
44
  const learningPath = await fetchLearningPathLessons(
46
45
  activeLearningPath.active_learning_path_id,
47
46
  brand,
48
- getToday()
47
+ new Date()
49
48
  )
50
49
 
51
50
  if (!learningPath) {
@@ -37,6 +37,15 @@ export async function fetchLikeCount(contendId) {
37
37
  return await GET(url)
38
38
  }
39
39
 
40
+ /**
41
+ * @param {number} contentId
42
+ * @returns {Promise<{hls_url: string|null, status: string, vimeo_event_id: string|null}>}
43
+ */
44
+ export async function fetchLiveStreamData(contentId) {
45
+ const url = `/api/content/v1/live-events/${contentId}/stream`
46
+ return await GET(url)
47
+ }
48
+
40
49
  export async function postPlaylistContentEngaged(playlistItemId) {
41
50
  const url = `/railtracker/v1/last-engaged/${playlistItemId}`
42
51
  return await POST(url, null)
@@ -964,6 +964,7 @@ export async function fetchLessonContent(railContentId, { addParent = false } =
964
964
  "live_event_start_time": live_event_start_time,
965
965
  "live_event_end_time": live_event_end_time,
966
966
  "live_event_stream_id": live_event_stream_id,
967
+ "vimeo_live_event_id": vimeo_live_event_id,
967
968
  "videoId": coalesce(live_event_stream_id, video.external_id),
968
969
  "live_event_is_global": live_global_event == true
969
970
  }
@@ -1194,6 +1195,7 @@ export async function fetchLiveEvent(brand, forcedContentId = null) {
1194
1195
  live_event_start_time,
1195
1196
  live_event_end_time,
1196
1197
  live_event_stream_id,
1198
+ vimeo_live_event_id,
1197
1199
  railcontent_id,
1198
1200
  published_on,
1199
1201
  'event_coach_url' : instructor[0]->web_url_path,
@@ -1211,6 +1213,7 @@ export async function fetchLiveEvent(brand, forcedContentId = null) {
1211
1213
  live_event_start_time,
1212
1214
  live_event_end_time,
1213
1215
  live_event_stream_id,
1216
+ vimeo_live_event_id,
1214
1217
  railcontent_id,
1215
1218
  published_on,
1216
1219
  'event_coach_url' : instructor[0]->web_url_path,
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * @module UserManagement
3
3
  */
4
- import { GET, POST, PUT, DELETE } from '../../infrastructure/http/HttpClient.ts'
4
+ import { GET, POST, PUT, DELETE, PATCH } from '../../infrastructure/http/HttpClient.ts'
5
5
  import { globalConfig } from '../config.js'
6
6
  import './types.js'
7
7
 
@@ -131,5 +131,13 @@ export async function toggleSignaturePrivate(showSignature = true) {
131
131
  return await PUT(apiUrl, { show_signature: showSignature })
132
132
  }
133
133
 
134
-
135
-
134
+ /**
135
+ * Updates the user's brand.
136
+ *
137
+ * @param {string} brand - The brand to assign to the user.
138
+ * @returns {Promise<{ brand: string }>} - A promise that resolves with the updated brand.
139
+ */
140
+ export async function updateBrand(brand) {
141
+ const apiUrl = `/api/user-management-system/v1/user/brand`
142
+ return await PATCH(apiUrl, { brand })
143
+ }
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(rg:*)",
5
- "Bash(npm run lint:*)"
6
- ],
7
- "deny": []
8
- }
9
- }