musora-content-services 2.112.1 → 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.
- package/.claude/settings.local.json +9 -0
- package/CHANGELOG.md +8 -0
- package/README.md +32 -1
- package/package.json +1 -1
- package/src/index.d.ts +4 -0
- package/src/index.js +4 -0
- package/src/services/awards/award-query.js +100 -20
- package/src/services/awards/internal/award-definitions.js +17 -0
- package/src/services/content-org/learning-paths.ts +11 -4
- package/src/services/contentAggregator.js +9 -0
- package/src/services/sync/repositories/user-award-progress.ts +54 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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
|
+
|
|
5
13
|
### [2.112.1](https://github.com/railroadmedia/musora-content-services/compare/v2.112.0...v2.112.1) (2026-01-07)
|
|
6
14
|
|
|
7
15
|
## [2.112.0](https://github.com/railroadmedia/musora-content-services/compare/v2.111.5...v2.112.0) (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
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
|
|
125
|
+
const data = await db.userAwardProgress.getAwardsForContent(contentId)
|
|
125
126
|
|
|
126
|
-
const awards = definitions.
|
|
127
|
-
|
|
128
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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)
|