musora-content-services 2.131.4 → 2.131.5

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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(rg:*)",
5
+ "Bash(npm run lint:*)"
6
+ ],
7
+ "deny": []
8
+ }
9
+ }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
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.131.5](https://github.com/railroadmedia/musora-content-services/compare/v2.131.4...v2.131.5) (2026-02-05)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * **T3PS-1401:** 5 minute ttl for pinned data ([#767](https://github.com/railroadmedia/musora-content-services/issues/767)) ([a14cea5](https://github.com/railroadmedia/musora-content-services/commit/a14cea5ce9e65297e5f16c9ab320445786da0c5d))
11
+
5
12
  ### [2.131.4](https://github.com/railroadmedia/musora-content-services/compare/v2.131.3...v2.131.4) (2026-02-05)
6
13
 
7
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.131.4",
3
+ "version": "2.131.5",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -14,54 +14,29 @@ import { fetchByRailContentIds } from '../sanity.js'
14
14
  import { addContextToContent } from '../contentAggregator.js'
15
15
  import { fetchPlaylist } from '../content-org/playlists.js'
16
16
  import { TabResponseType } from '../../contentMetaData.js'
17
- import { PUT } from '../../infrastructure/http/HttpClient.ts'
17
+ import { GET, PUT } from '../../infrastructure/http/HttpClient.ts'
18
18
  import { postProcessBadge } from "../../contentTypeConfig.js";
19
19
 
20
20
  export const USER_PIN_PROGRESS_KEY = 'user_pin_progress_row'
21
+ const CACHE_EXPIRY_MS = 5 * 60 * 1000
21
22
 
22
- /**
23
- * Gets the localStorage key for user pinned progress, scoped by user ID
24
- */
25
- export function getUserPinProgressKey(id) {
26
- const userId = id || globalConfig.sessionConfig?.userId || globalConfig.railcontentConfig?.userId
27
- return userId ? `user_pin_progress_row_${userId}` : USER_PIN_PROGRESS_KEY
28
- }
23
+ async function getUserPinnedItem(brand) {
24
+ const key = getUserPinProgressKey()
29
25
 
30
- /**
31
- * Fetches and combines recent user progress rows and playlists, excluding certain types and parents.
32
- *
33
- * @param {Object} [options={}] - Options for fetching progress rows.
34
- * @param {string|null} [options.brand=null] - The brand context for progress data.
35
- * @param {number} [options.limit=8] - Maximum number of progress rows to return.
36
- * @returns {Promise<Object>} - A promise that resolves to an object containing progress rows formatted for UI.
37
- *
38
- * @example
39
- * getProgressRows({ brand: 'drumeo', limit: 10 })
40
- * .then(data => console.log(data))
41
- * .catch(error => console.error(error));
42
- */
43
- export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}) {
44
- const [userPinnedItem, recentPlaylists] = await Promise.all([
45
- getUserPinnedItem(brand),
46
- getRecentPlaylists(brand, limit),
47
- ])
48
- const playlistEngagedOnContent = await getPlaylistEngagedOnContent(recentPlaylists)
49
- const [contentCardMap, playlistCards, methodCard] = await Promise.all([
50
- getContentCardMap(brand, limit, playlistEngagedOnContent, userPinnedItem),
51
- getPlaylistCards(recentPlaylists),
52
- getMethodCard(brand),
53
- ])
54
- const pinnedCard = await popPinnedItem(userPinnedItem, contentCardMap, playlistCards, methodCard)
55
- let allResultsLength = playlistCards.length + contentCardMap.size
56
- if (methodCard) {
57
- allResultsLength += 1
26
+ const pinnedProgress = await getStoredPinnedData(key)
27
+ const cachedData = pinnedProgress[brand]
28
+
29
+ if (isCacheValid(cachedData)) {
30
+ return cachedData
58
31
  }
59
- const results = sortCards(pinnedCard, contentCardMap, playlistCards, methodCard, limit)
60
- return {
61
- type: TabResponseType.PROGRESS_ROWS,
62
- displayBrowseAll: allResultsLength > limit,
63
- data: results,
32
+
33
+ const url = `/api/user-management-system/v1/progress/pin?brand=${brand}`
34
+ const response = await GET(url)
35
+
36
+ if (response && !response.error) {
37
+ return await setUserBrandPinnedItem(brand, response)
64
38
  }
39
+ return response
65
40
  }
66
41
 
67
42
  /**
@@ -80,11 +55,11 @@ export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}) {
80
55
  export async function pinProgressRow(brand, id, progressType) {
81
56
  const url = `/api/user-management-system/v1/progress/pin?brand=${brand}&id=${id}&progressType=${progressType}`
82
57
  const response = await PUT(url, null)
58
+
83
59
  if (response && !response.error) {
84
- await updateUserPinnedProgressRow(brand, {
60
+ return await setUserBrandPinnedItem(brand, {
85
61
  id,
86
62
  progressType,
87
- pinnedAt: new Date().toISOString(),
88
63
  })
89
64
  }
90
65
  return response
@@ -105,22 +80,90 @@ export async function unpinProgressRow(brand) {
105
80
  const url = `/api/user-management-system/v1/progress/unpin?brand=${brand}`
106
81
  const response = await PUT(url, null)
107
82
  if (response && !response.error) {
108
- await updateUserPinnedProgressRow(brand, null)
83
+ await setUserBrandPinnedItem(brand, null)
109
84
  }
110
85
  return response
111
86
  }
112
87
 
88
+ /**
89
+ * Gets the localStorage key for user pinned progress, scoped by user ID
90
+ */
91
+ export function getUserPinProgressKey(id) {
92
+ const userId = id || globalConfig.sessionConfig?.userId || globalConfig.railcontentConfig?.userId
93
+ return USER_PIN_PROGRESS_KEY + `_${userId}`
94
+ }
95
+
113
96
  export async function setUserPinnedProgressRow(userId, pinnedData) {
114
97
  const key = getUserPinProgressKey(userId)
98
+
99
+ pinnedData.map(item => ({...item, cachedAt: Date.now()}))
115
100
  await globalConfig.localStorage.setItem(key, JSON.stringify(pinnedData))
116
101
  }
117
102
 
118
- async function getUserPinnedItem(brand) {
103
+ async function setUserBrandPinnedItem(brand, pinnedData) {
104
+ if (!brand) return
105
+
119
106
  const key = getUserPinProgressKey()
107
+ let pinnedProgress = await getStoredPinnedData(key)
108
+
109
+ pinnedProgress[brand] = setPinnedData(pinnedData)
110
+ await globalConfig.localStorage.setItem(key, JSON.stringify(pinnedProgress))
111
+ return pinnedProgress
112
+ }
113
+
114
+ async function getStoredPinnedData(key) {
120
115
  const pinnedProgressRaw = await globalConfig.localStorage.getItem(key)
121
- let pinnedProgress = pinnedProgressRaw ? JSON.parse(pinnedProgressRaw) : {}
122
- pinnedProgress = pinnedProgress || {}
123
- return pinnedProgress[brand] ?? null
116
+ const pinnedProgress = pinnedProgressRaw ? JSON.parse(pinnedProgressRaw) : {}
117
+ return pinnedProgress || {}
118
+ }
119
+
120
+ function setPinnedData(pinnedData) {
121
+ const now = Date.now()
122
+ return {
123
+ ...pinnedData,
124
+ cachedAt: now
125
+ }
126
+ }
127
+
128
+ function isCacheValid(cachedData) {
129
+ return cachedData?.cachedAt && (Date.now() - cachedData.cachedAt) < CACHE_EXPIRY_MS
130
+ }
131
+
132
+ /**
133
+ * Fetches and combines recent user progress rows and playlists, excluding certain types and parents.
134
+ *
135
+ * @param {Object} [options={}] - Options for fetching progress rows.
136
+ * @param {string|null} [options.brand=null] - The brand context for progress data.
137
+ * @param {number} [options.limit=8] - Maximum number of progress rows to return.
138
+ * @returns {Promise<Object>} - A promise that resolves to an object containing progress rows formatted for UI.
139
+ *
140
+ * @example
141
+ * getProgressRows({ brand: 'drumeo', limit: 10 })
142
+ * .then(data => console.log(data))
143
+ * .catch(error => console.error(error));
144
+ */
145
+ export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}) {
146
+ const [userPinnedItem, recentPlaylists] = await Promise.all([
147
+ getUserPinnedItem(brand),
148
+ getRecentPlaylists(brand, limit),
149
+ ])
150
+ const playlistEngagedOnContent = await getPlaylistEngagedOnContent(recentPlaylists)
151
+ const [contentCardMap, playlistCards, methodCard] = await Promise.all([
152
+ getContentCardMap(brand, limit, playlistEngagedOnContent, userPinnedItem),
153
+ getPlaylistCards(recentPlaylists),
154
+ getMethodCard(brand),
155
+ ])
156
+ const pinnedCard = await popPinnedItem(userPinnedItem, contentCardMap, playlistCards, methodCard)
157
+ let allResultsLength = playlistCards.length + contentCardMap.size
158
+ if (methodCard) {
159
+ allResultsLength += 1
160
+ }
161
+ const results = sortCards(pinnedCard, contentCardMap, playlistCards, methodCard, limit)
162
+ return {
163
+ type: TabResponseType.PROGRESS_ROWS,
164
+ displayBrowseAll: allResultsLength > limit,
165
+ data: results,
166
+ }
124
167
  }
125
168
 
126
169
  /**
@@ -131,7 +174,6 @@ async function getUserPinnedItem(brand) {
131
174
  async function popPinnedItem(userPinnedItem, contentCardMap, playlistCards, methodCard) {
132
175
  if (!userPinnedItem) return null
133
176
  const pinnedId = parseInt(userPinnedItem.id)
134
- const pinnedAt = userPinnedItem.pinnedAt
135
177
  const progressType = userPinnedItem.progressType ?? userPinnedItem.type
136
178
 
137
179
  let item = null
@@ -163,7 +205,7 @@ async function popPinnedItem(userPinnedItem, contentCardMap, playlistCards, meth
163
205
  id: pinnedId,
164
206
  playlist: playlist,
165
207
  type: 'playlist',
166
- progressTimestamp: new Date(pinnedAt).getTime(),
208
+ progressTimestamp: new Date().getTime(),
167
209
  })
168
210
  }
169
211
  } else if (progressType === 'method') {
@@ -218,12 +260,3 @@ function mergeAndSortItems(items, limit) {
218
260
  })
219
261
  .slice(0, limit)
220
262
  }
221
-
222
- async function updateUserPinnedProgressRow(brand, pinnedData) {
223
- const key = getUserPinProgressKey()
224
- const pinnedProgressRaw = await globalConfig.localStorage.getItem(key)
225
- let pinnedProgress = pinnedProgressRaw ? JSON.parse(pinnedProgressRaw) : {}
226
- pinnedProgress = pinnedProgress || {}
227
- pinnedProgress[brand] = pinnedData
228
- await globalConfig.localStorage.setItem(key, JSON.stringify(pinnedProgress))
229
- }