musora-content-services 2.111.5 → 2.112.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.
@@ -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,23 @@
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.112.2](https://github.com/railroadmedia/musora-content-services/compare/v2.112.1...v2.112.2) (2026-01-08)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * changes lesson type mapping ([#690](https://github.com/railroadmedia/musora-content-services/issues/690)) ([af4dda9](https://github.com/railroadmedia/musora-content-services/commit/af4dda9e03eecfd49a341d2c1aea8afcf217e233))
11
+ * **T3PS-1187:** award progress optimizations ([#689](https://github.com/railroadmedia/musora-content-services/issues/689)) ([5d063b8](https://github.com/railroadmedia/musora-content-services/commit/5d063b8227e9ee5c70c30bc31263296bfcb4aa63))
12
+
13
+ ### [2.112.1](https://github.com/railroadmedia/musora-content-services/compare/v2.112.0...v2.112.1) (2026-01-07)
14
+
15
+ ## [2.112.0](https://github.com/railroadmedia/musora-content-services/compare/v2.111.5...v2.112.0) (2026-01-07)
16
+
17
+
18
+ ### Features
19
+
20
+ * **T3PS-1413:** homepage progress row learning path lessons need need_access flag ([#688](https://github.com/railroadmedia/musora-content-services/issues/688)) ([cde7359](https://github.com/railroadmedia/musora-content-services/commit/cde73595c76c634b711e580497e291fa074b3d51))
21
+
5
22
  ### [2.111.5](https://github.com/railroadmedia/musora-content-services/compare/v2.111.4...v2.111.5) (2026-01-07)
6
23
 
7
24
  ### [2.111.4](https://github.com/railroadmedia/musora-content-services/compare/v2.111.3...v2.111.4) (2026-01-07)
package/README.md CHANGED
@@ -54,12 +54,43 @@ the `excludeFromGeneratedIndex` array inside the service file.
54
54
 
55
55
  ## Publishing Package Updates
56
56
 
57
- To publish a new version to NPM run,
57
+ To publish a new version to NPM run,
58
58
 
59
59
  ```bash
60
60
  ./publish.sh
61
61
  ```
62
62
 
63
+ ## NPM reauthentication
64
+
65
+ If you see this error displayed when publishing, you need to reauthenticate:
66
+
67
+ ```bash
68
+ npm notice Publishing to https://registry.npmjs.org/ with tag latest and default access
69
+ npm notice Access token expired or revoked. Please try logging in again.
70
+ npm ERR! code E404
71
+ npm ERR! 404 Not Found - PUT https://registry.npmjs.org/musora-content-services - Not found
72
+ npm ERR! 404
73
+ npm ERR! 404 'musora-content-services@2.99.6' is not in this registry.
74
+ npm ERR! 404
75
+ npm ERR! 404 Note that you can also install from a
76
+ npm ERR! 404 tarball, folder, http url, or git url.
77
+ ```
78
+
79
+ Use the shared musora_dev account in 1password ("NPM Access Token For musora_dev")
80
+ and update this value as `npmAccessToken` in the railenvironment credentials file.
81
+
82
+ Alternatively, request your own account or renew your own token.
83
+ Update railenvironment credentials file with your new account details
84
+
85
+ ```bash
86
+ npmUserName=
87
+ npmPassword=
88
+ npmAccessToken=
89
+ ```
90
+
91
+ Either restart the manager container (`docker restart railenvironmentdocker_manager`)
92
+ or run `./usr/local/bin/setup-npm.sh` inside the container to update the `~/.npmrc` file.
93
+
63
94
  ## Symlinking
64
95
 
65
96
  To link this package to the MWP repo for local development run,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.111.5",
3
+ "version": "2.112.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
@@ -9,6 +9,7 @@ import {
9
9
  getAwardStatistics,
10
10
  getCompletedAwards,
11
11
  getContentAwards,
12
+ getContentAwardsByIds,
12
13
  getInProgressAwards,
13
14
  resetAllAwards
14
15
  } from './services/awards/award-query.js';
@@ -265,6 +266,7 @@ import {
265
266
  fetchComingSoon,
266
267
  fetchCommentModContentData,
267
268
  fetchContentRows,
269
+ fetchContentTypeCounts,
268
270
  fetchHierarchy,
269
271
  fetchLearningPathHierarchy,
270
272
  fetchLeaving,
@@ -495,6 +497,7 @@ declare module 'musora-content-services' {
495
497
  fetchCommunityGuidelines,
496
498
  fetchContentPageUserData,
497
499
  fetchContentRows,
500
+ fetchContentTypeCounts,
498
501
  fetchCustomerPayments,
499
502
  fetchEnrollmentPageMetadata,
500
503
  fetchFollowedThreads,
@@ -582,6 +585,7 @@ declare module 'musora-content-services' {
582
585
  getAwardStatistics,
583
586
  getCompletedAwards,
584
587
  getContentAwards,
588
+ getContentAwardsByIds,
585
589
  getContentRows,
586
590
  getDailySession,
587
591
  getEnrichedLearningPath,
package/src/index.js CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  getAwardStatistics,
14
14
  getCompletedAwards,
15
15
  getContentAwards,
16
+ getContentAwardsByIds,
16
17
  getInProgressAwards,
17
18
  resetAllAwards
18
19
  } from './services/awards/award-query.js';
@@ -269,6 +270,7 @@ import {
269
270
  fetchComingSoon,
270
271
  fetchCommentModContentData,
271
272
  fetchContentRows,
273
+ fetchContentTypeCounts,
272
274
  fetchHierarchy,
273
275
  fetchLearningPathHierarchy,
274
276
  fetchLeaving,
@@ -494,6 +496,7 @@ export {
494
496
  fetchCommunityGuidelines,
495
497
  fetchContentPageUserData,
496
498
  fetchContentRows,
499
+ fetchContentTypeCounts,
497
500
  fetchCustomerPayments,
498
501
  fetchEnrollmentPageMetadata,
499
502
  fetchFollowedThreads,
@@ -581,6 +584,7 @@ export {
581
584
  getAwardStatistics,
582
585
  getCompletedAwards,
583
586
  getContentAwards,
587
+ getContentAwardsByIds,
584
588
  getContentRows,
585
589
  getDailySession,
586
590
  getEnrichedLearningPath,
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * **Query Functions** (read-only):
8
8
  * - `getContentAwards(contentId)` - Get awards for a learning path/course
9
+ * - `getContentAwardsByIds(contentIds)` - Get awards for multiple content items (batch optimized)
9
10
  * - `getCompletedAwards(brand)` - Get user's earned awards
10
11
  * - `getInProgressAwards(brand)` - Get awards user is working toward
11
12
  * - `getAwardStatistics(brand)` - Get aggregate award stats
@@ -121,30 +122,17 @@ export async function getContentAwards(contentId) {
121
122
  }
122
123
  }
123
124
 
124
- const { definitions, progress } = await db.userAwardProgress.getAwardsForContent(contentId)
125
+ const data = await db.userAwardProgress.getAwardsForContent(contentId)
125
126
 
126
- const awards = definitions.map(def => {
127
- const userProgress = progress.get(def._id)
128
- const completionData = enhanceCompletionData(userProgress?.completion_data)
129
-
130
- return {
131
- awardId: def._id,
132
- awardTitle: def.name,
133
- badge: def.badge,
134
- award: def.award,
135
- brand: def.brand,
136
- instructorName: def.instructor_name,
137
- progressPercentage: userProgress?.progress_percentage ?? 0,
138
- isCompleted: userProgress ? UserAwardProgressRepository.isCompleted(userProgress) : false,
139
- completedAt: userProgress?.completed_at,
140
- completionData
141
- }
142
- })
127
+ const awards = data && data.definitions.length !== 0
128
+ ? defineAwards(data)
129
+ : []
143
130
 
144
131
  return {
145
- hasAwards: true,
146
- awards
132
+ hasAwards: awards.length > 0,
133
+ awards,
147
134
  }
135
+
148
136
  } catch (error) {
149
137
  console.error(`Failed to get award status for content ${contentId}:`, error)
150
138
  return {
@@ -154,6 +142,98 @@ export async function getContentAwards(contentId) {
154
142
  }
155
143
  }
156
144
 
145
+ /**
146
+ * @param {number[]} contentIds - Array of Railcontent IDs to fetch awards for
147
+ * @returns {Promise<Object.<number, ContentAwardsResponse>>} Object mapping content IDs to their award data
148
+ *
149
+ * @description
150
+ * Returns awards for multiple content items at once. More efficient than calling
151
+ * `getContentAwards()` multiple times. Returns an object where keys are content IDs
152
+ * and values are the same structure as `getContentAwards()`.
153
+ *
154
+ * Content IDs without awards will have `{ hasAwards: false, awards: [] }` in the result.
155
+ *
156
+ * Returns empty object `{}` on error (never throws).
157
+ *
158
+ * @example
159
+ * const learningPathIds = [12345, 67890, 11111]
160
+ * const awardsMap = await getContentAwardsByIds(learningPathIds)
161
+ *
162
+ * learningPathIds.forEach(id => {
163
+ * const { hasAwards, awards } = awardsMap[id] || { hasAwards: false, awards: [] }
164
+ * if (hasAwards) {
165
+ * console.log(`Learning path ${id} has ${awards.length} award(s)`)
166
+ * }
167
+ * })
168
+ *
169
+ * @example
170
+ * function CourseListWithAwards({ courseIds }) {
171
+ * const [awardsMap, setAwardsMap] = useState({})
172
+ *
173
+ * useEffect(() => {
174
+ * getContentAwardsByIds(courseIds).then(setAwardsMap)
175
+ * }, [courseIds])
176
+ *
177
+ * return courseIds.map(courseId => {
178
+ * const { hasAwards, awards } = awardsMap[courseId] || { hasAwards: false, awards: [] }
179
+ * return (
180
+ * <CourseCard key={courseId} courseId={courseId}>
181
+ * {hasAwards && <AwardBadge award={awards[0]} />}
182
+ * </CourseCard>
183
+ * )
184
+ * })
185
+ * }
186
+ */
187
+ export async function getContentAwardsByIds(contentIds) {
188
+ try {
189
+ if (!Array.isArray(contentIds) || contentIds.length === 0) {
190
+ return {}
191
+ }
192
+
193
+ const awardsDataMap = await db.userAwardProgress.getAwardsForContentMany(contentIds)
194
+
195
+ const result = {}
196
+
197
+ contentIds.forEach(contentId => {
198
+ const data = awardsDataMap.get(contentId) // {definitions, progress}
199
+
200
+ const awards = data && data.definitions.length !== 0
201
+ ? defineAwards(data)
202
+ : []
203
+
204
+ result[contentId] = {
205
+ hasAwards: awards.length > 0,
206
+ awards,
207
+ }
208
+ })
209
+
210
+ return result
211
+ } catch (error) {
212
+ console.error(`Failed to get award status for content IDs ${contentIds}:`, error)
213
+ return {}
214
+ }
215
+ }
216
+
217
+ function defineAwards(data) {
218
+ return data.definitions.map(def => {
219
+ const userProgress = data.progress.get(def._id)
220
+ const completionData = enhanceCompletionData(userProgress?.completion_data)
221
+
222
+ return {
223
+ awardId: def._id,
224
+ awardTitle: def.name,
225
+ badge: def.badge,
226
+ award: def.award,
227
+ brand: def.brand,
228
+ instructorName: def.instructor_name,
229
+ progressPercentage: userProgress?.progress_percentage ?? 0,
230
+ isCompleted: userProgress ? UserAwardProgressRepository.isCompleted(userProgress) : false,
231
+ completedAt: userProgress?.completed_at,
232
+ completionData
233
+ }
234
+ })
235
+ }
236
+
157
237
  /**
158
238
  * @param {string} [brand=null] - Brand to filter by (drumeo, pianote, guitareo, singeo), or null for all brands
159
239
  * @param {AwardPaginationOptions} [options={}] - Optional pagination and filtering
@@ -60,6 +60,23 @@ class AwardDefinitionsService {
60
60
  .filter(Boolean)
61
61
  }
62
62
 
63
+ /** @returns {Promise<Map<number, import('./types').AwardDefinition[]>>} */
64
+ async getByContentIds(contentIds) {
65
+ if (this.shouldRefresh()) {
66
+ await this.fetchFromSanity()
67
+ }
68
+
69
+ return new Map(
70
+ contentIds.map(contentId => {
71
+ const awardIds = this.contentIndex.get(contentId) || []
72
+ const definitions = awardIds
73
+ .map(id => this.definitions.get(id))
74
+ .filter(Boolean)
75
+ return [contentId, definitions]
76
+ })
77
+ )
78
+ }
79
+
63
80
  /** @returns {Promise<boolean>} */
64
81
  async hasAwards(contentId) {
65
82
  if (this.shouldRefresh()) {
@@ -20,6 +20,7 @@ import { getToday } from "../dateUtils.js";
20
20
 
21
21
  const BASE_PATH: string = `/api/content-org`
22
22
  const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
23
+ const LEARNING_PATH_LESSON = 'learning-path-lesson-v2'
23
24
 
24
25
  interface ActiveLearningPathResponse {
25
26
  user_id: number
@@ -119,7 +120,7 @@ export async function resetAllLearningPaths() {
119
120
  * @returns {Promise<Object>} Learning path with enriched lesson data
120
121
  */
121
122
  export async function getEnrichedLearningPath(learningPathId) {
122
- const response = (await addContextToLearningPaths(
123
+ let response = (await addContextToLearningPaths(
123
124
  fetchByRailContentId,
124
125
  learningPathId,
125
126
  COLLECTION_TYPE.LEARNING_PATH,
@@ -134,11 +135,13 @@ export async function getEnrichedLearningPath(learningPathId) {
134
135
  addNavigateTo: true,
135
136
  }
136
137
  )) as any
138
+ // add awards to LP parents only
139
+ response = await addContextToLearningPaths(() => response, {addAwards:true})
137
140
  if (!response) return response
138
141
 
139
142
  response.children = mapContentToParent(
140
143
  response.children,
141
- COLLECTION_TYPE.LEARNING_PATH,
144
+ LEARNING_PATH_LESSON,
142
145
  learningPathId
143
146
  )
144
147
  return response
@@ -150,7 +153,7 @@ export async function getEnrichedLearningPath(learningPathId) {
150
153
  * @returns {Promise<Object>} Learning paths with enriched lesson data
151
154
  */
152
155
  export async function getEnrichedLearningPaths(learningPathIds: number[]) {
153
- const response = (await addContextToLearningPaths(
156
+ let response = (await addContextToLearningPaths(
154
157
  fetchByRailContentIds,
155
158
  learningPathIds,
156
159
  COLLECTION_TYPE.LEARNING_PATH,
@@ -165,12 +168,15 @@ export async function getEnrichedLearningPaths(learningPathIds: number[]) {
165
168
  addNavigateTo: true,
166
169
  }
167
170
  )) as any
171
+ // add awards to LP parents only
172
+ response = await addContextToLearningPaths(() => response, {addAwards:true})
173
+
168
174
  if (!response) return response
169
175
 
170
176
  response.forEach((learningPath) => {
171
177
  learningPath.children = mapContentToParent(
172
178
  learningPath.children,
173
- COLLECTION_TYPE.LEARNING_PATH,
179
+ LEARNING_PATH_LESSON,
174
180
  learningPath.id
175
181
  )
176
182
  })
@@ -197,6 +203,7 @@ export async function getLearningPathLessonsByIds(contentIds, learningPathId) {
197
203
  * @param parentContentId
198
204
  */
199
205
  export function mapContentToParent(lessons, parentContentType, parentContentId) {
206
+ if (!lessons) return lessons
200
207
  return lessons.map((lesson: any) => {
201
208
  return { ...lesson, type: parentContentType, parent_id: parentContentId }
202
209
  })
@@ -10,6 +10,7 @@ import {
10
10
  import { isContentLikedByIds } from './contentLikes'
11
11
  import { fetchLikeCount } from './railcontent'
12
12
  import {COLLECTION_TYPE} from "./sync/models/ContentProgress";
13
+ import {getContentAwardsByIds} from "./awards/award-query.js";
13
14
 
14
15
  /**
15
16
  * Combine sanity data with BE contextual data.
@@ -73,6 +74,7 @@ export async function addContextToContent(dataPromise, ...dataArgs) {
73
74
  addProgressTimestamp = false,
74
75
  addResumeTimeSeconds = false,
75
76
  addNavigateTo = false,
77
+ addAwards = false,
76
78
  } = options
77
79
 
78
80
  const dataParam = lastArg === options ? dataArgs.slice(0, -1) : dataArgs
@@ -91,12 +93,14 @@ export async function addContextToContent(dataPromise, ...dataArgs) {
91
93
  isLikedData,
92
94
  resumeTimeData,
93
95
  navigateToData,
96
+ awards,
94
97
  ] = await Promise.all([ //for now assume these all return `collection = {type, id}`. it will be so when watermelon here
95
98
  addProgressPercentage || addProgressStatus || addProgressTimestamp
96
99
  ? getProgressDataByIds(ids, collection) : Promise.resolve(null),
97
100
  addIsLiked ? isContentLikedByIds(ids, collection) : Promise.resolve(null),
98
101
  addResumeTimeSeconds ? getResumeTimeSecondsByIds(ids, collection) : Promise.resolve(null),
99
102
  addNavigateTo ? getNavigateTo(items, collection) : Promise.resolve(null),
103
+ addAwards ? getContentAwardsByIds(ids) : Promise.resolve(null),
100
104
  ])
101
105
 
102
106
  const addContext = async (item) => ({
@@ -108,6 +112,7 @@ export async function addContextToContent(dataPromise, ...dataArgs) {
108
112
  ...(addLikeCount && ids.length === 1 ? { likeCount: await fetchLikeCount(item.id) } : {}),
109
113
  ...(addResumeTimeSeconds ? { resumeTime: resumeTimeData?.[item.id] } : {}),
110
114
  ...(addNavigateTo ? { navigateTo: navigateToData?.[item.id] } : {}),
115
+ ...(addAwards ? { awards: awards?.[item.id].awards || [] } : {}),
111
116
  })
112
117
 
113
118
  return await processItems(data, addContext, dataField, isDataAnArray, dataField_includeParent)
@@ -163,6 +168,7 @@ export async function addContextToLearningPaths(dataPromise, ...dataArgs) {
163
168
  addLikeCount = false,
164
169
  addResumeTimeSeconds = false,
165
170
  addNavigateTo = false,
171
+ addAwards = false,
166
172
  } = options
167
173
 
168
174
  const dataParam = lastArg === options ? dataArgs.slice(0, -1) : dataArgs
@@ -189,12 +195,14 @@ export async function addContextToLearningPaths(dataPromise, ...dataArgs) {
189
195
  isLikedData,
190
196
  resumeTimeData,
191
197
  navigateToData,
198
+ awards,
192
199
  ] = await Promise.all([
193
200
  addProgressPercentage || addProgressStatus || addProgressTimestamp
194
201
  ? getProgressDataByIdsAndCollections(ids) : Promise.resolve(null),
195
202
  addIsLiked ? isContentLikedByIds(justIds) : Promise.resolve(null),
196
203
  addResumeTimeSeconds ? getResumeTimeSecondsByIdsAndCollections(ids) : Promise.resolve(null),
197
204
  addNavigateTo ? getNavigateToForMethod(items) : Promise.resolve(null),
205
+ addAwards ? getContentAwardsByIds(justIds) : Promise.resolve(null),
198
206
  ])
199
207
 
200
208
  const addContext = async (item) => {
@@ -208,6 +216,7 @@ export async function addContextToLearningPaths(dataPromise, ...dataArgs) {
208
216
  ...(addLikeCount && ids.length === 1 ? { likeCount: await fetchLikeCount(itemId) } : {}),
209
217
  ...(addResumeTimeSeconds ? { resumeTime: resumeTimeData?.[itemId] } : {}),
210
218
  ...(addNavigateTo ? { navigateTo: navigateToData?.[itemId] } : {}),
219
+ ...(addAwards ? { awards: awards?.[itemId].awards || [] } : {}),
211
220
  }
212
221
 
213
222
  // Enrich intro_video if it exists and flag is set
@@ -1515,7 +1515,6 @@ export async function fetchSanity(
1515
1515
  ])
1516
1516
  const response = promisesResult[0]
1517
1517
  const userPermissions = promisesResult[1]
1518
-
1519
1518
  if (!response.ok) {
1520
1519
  throw new Error(`Sanity API error: ${response.status} - ${response.statusText}`)
1521
1520
  }
@@ -1573,6 +1572,11 @@ function contentResultsDecorator(results, fieldName, callback) {
1573
1572
  })
1574
1573
  } else {
1575
1574
  results[fieldName] = callback(results)
1575
+ if (results.children && Array.isArray(results.children)) {
1576
+ results.children.forEach((result) => {
1577
+ result[fieldName] = callback(result)
1578
+ })
1579
+ }
1576
1580
  }
1577
1581
 
1578
1582
  return results
@@ -63,6 +63,13 @@ export default class UserAwardProgressRepository extends SyncRepository<UserAwar
63
63
  return this.readOne(awardId)
64
64
  }
65
65
 
66
+ async getByAwardIds(awardIds: string[]) {
67
+ if (awardIds.length === 0) {
68
+ return { data: [] }
69
+ }
70
+ return this.readSome(awardIds)
71
+ }
72
+
66
73
  async hasCompletedAward(awardId: string): Promise<boolean> {
67
74
  const result = await this.readOne(awardId)
68
75
  if (!result.data) return false
@@ -121,16 +128,58 @@ export default class UserAwardProgressRepository extends SyncRepository<UserAwar
121
128
  const awardIds = definitions.map(d => d._id)
122
129
  const progressMap = new Map<string, ModelSerialized<UserAwardProgress>>()
123
130
 
124
- for (const awardId of awardIds) {
125
- const result = await this.getByAwardId(awardId)
126
- if (result.data) {
127
- progressMap.set(awardId, result.data)
128
- }
131
+ const progressResults = await this.getByAwardIds(awardIds)
132
+ for (const progress of progressResults.data) {
133
+ progressMap.set(progress.award_id, progress)
129
134
  }
130
135
 
131
136
  return { definitions, progress: progressMap }
132
137
  }
133
138
 
139
+ async getAwardsForContentMany(contentIds: number[]): Promise<Map<number, {
140
+ definitions: AwardDefinition[]
141
+ progress: Map<string, ModelSerialized<UserAwardProgress>>
142
+ }>> {
143
+ const { awardDefinitions } = await import('../../awards/internal/award-definitions')
144
+
145
+ const contentToDefinitionsMap = await awardDefinitions.getByContentIds(contentIds)
146
+
147
+ const allAwardIds = new Set<string>()
148
+ contentToDefinitionsMap.forEach(definitions => {
149
+ definitions.forEach(d => allAwardIds.add(d._id))
150
+ })
151
+
152
+ const progressResults = await this.getByAwardIds(Array.from(allAwardIds))
153
+ const globalProgressMap = new Map<string, ModelSerialized<UserAwardProgress>>()
154
+ for (const progress of progressResults.data) {
155
+ globalProgressMap.set(progress.award_id, progress)
156
+ }
157
+
158
+ const resultMap = new Map<number, {
159
+ definitions: AwardDefinition[]
160
+ progress: Map<string, ModelSerialized<UserAwardProgress>>
161
+ }>()
162
+
163
+ contentIds.forEach(contentId => {
164
+ const definitions = contentToDefinitionsMap.get(contentId) || []
165
+ const contentProgressMap = new Map<string, ModelSerialized<UserAwardProgress>>()
166
+
167
+ definitions.forEach(def => {
168
+ const progress = globalProgressMap.get(def._id)
169
+ if (progress) {
170
+ contentProgressMap.set(def._id, progress)
171
+ }
172
+ })
173
+
174
+ resultMap.set(contentId, {
175
+ definitions,
176
+ progress: contentProgressMap
177
+ })
178
+ })
179
+
180
+ return resultMap
181
+ }
182
+
134
183
  async deleteAllAwards() {
135
184
  const allProgress = await this.getAll()
136
185
  const ids = allProgress.data.map(p => p.id)