musora-content-services 1.2.5 → 1.3.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/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/.yarnrc.yml +1 -0
- package/CHANGELOG.md +11 -0
- package/README.md +0 -0
- package/babel.config.cjs +1 -1
- package/docs/config.js.html +0 -0
- package/docs/index.html +0 -0
- package/docs/module-Config.html +0 -0
- package/docs/module-Railcontent-Services.html +0 -0
- package/docs/module-Sanity-Services.html +0 -0
- package/docs/railcontent.js.html +0 -0
- package/docs/sanity.js.html +0 -0
- package/jest.config.js +9 -10
- package/jsdoc.json +17 -12
- package/package.json +2 -1
- package/src/contentMetaData.js +1190 -1131
- package/src/contentTypeConfig.js +492 -387
- package/src/filterBuilder.js +163 -145
- package/src/index.d.ts +227 -237
- package/src/index.js +226 -236
- package/src/services/config.js +12 -12
- package/src/services/contentLikes.js +33 -32
- package/src/services/contentProgress.js +233 -200
- package/src/services/dataContext.js +99 -93
- package/src/services/lastUpdated.js +7 -7
- package/src/services/railcontent.js +368 -364
- package/src/services/sanity.js +983 -955
- package/src/services/userPermissions.js +12 -14
- package/test/contentLikes.test.js +89 -86
- package/test/contentProgress.test.js +229 -236
- package/test/initializeTests.js +54 -51
- package/test/lastUpdated.test.js +20 -18
- package/test/live/contentProgressLive.test.js +135 -137
- package/test/live/railcontentLive.test.js +12 -14
- package/test/localStorageMock.js +16 -16
- package/test/log.js +5 -5
- package/test/sanityQueryService.test.js +857 -821
- package/test/userPermissions.test.js +15 -15
- package/tools/generate-index.cjs +108 -111
|
@@ -1,210 +1,225 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from
|
|
7
|
-
import {DataContext, ContentProgressVersionKey} from
|
|
8
|
-
import {fetchHierarchy} from
|
|
2
|
+
fetchContentProgress,
|
|
3
|
+
postContentCompleted,
|
|
4
|
+
postContentReset,
|
|
5
|
+
postRecordWatchSession,
|
|
6
|
+
} from './railcontent.js'
|
|
7
|
+
import { DataContext, ContentProgressVersionKey } from './dataContext.js'
|
|
8
|
+
import { fetchHierarchy } from './sanity.js'
|
|
9
9
|
|
|
10
|
-
const STATE_STARTED = 'started'
|
|
11
|
-
const STATE_COMPLETED = 'completed'
|
|
12
|
-
const DATA_KEY_STATUS = 's'
|
|
13
|
-
const DATA_KEY_PROGRESS = 'p'
|
|
14
|
-
const DATA_KEY_RESUME_TIME = 't'
|
|
15
|
-
const DATA_KEY_LAST_UPDATED_TIME = 'u'
|
|
16
|
-
export let dataContext = new DataContext(ContentProgressVersionKey, fetchContentProgress)
|
|
10
|
+
const STATE_STARTED = 'started'
|
|
11
|
+
const STATE_COMPLETED = 'completed'
|
|
12
|
+
const DATA_KEY_STATUS = 's'
|
|
13
|
+
const DATA_KEY_PROGRESS = 'p'
|
|
14
|
+
const DATA_KEY_RESUME_TIME = 't'
|
|
15
|
+
const DATA_KEY_LAST_UPDATED_TIME = 'u'
|
|
16
|
+
export let dataContext = new DataContext(ContentProgressVersionKey, fetchContentProgress)
|
|
17
17
|
|
|
18
18
|
export async function getProgressPercentage(contentId) {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
let data = await dataContext.getData()
|
|
20
|
+
return data[contentId]?.[DATA_KEY_PROGRESS] ?? 0
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export async function getProgressPercentageByIds(contentIds) {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const data = await dataContext.getData()
|
|
25
|
+
let progress = {}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
contentIds?.forEach((id) => (progress[id] = data[id]?.[DATA_KEY_PROGRESS] ?? 0))
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
return progress
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
export async function getProgressState(contentId) {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
let data = await dataContext.getData()
|
|
34
|
+
return data[contentId]?.[DATA_KEY_STATUS] ?? ''
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export async function getProgressStateByIds(contentIds) {
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const data = await dataContext.getData()
|
|
39
|
+
let progress = {}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
contentIds?.forEach((id) => (progress[id] = data[id]?.[DATA_KEY_STATUS] ?? ''))
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
return progress
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export async function getAllStarted(limit = null) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
47
|
+
const data = await dataContext.getData()
|
|
48
|
+
let ids = Object.keys(data)
|
|
49
|
+
.filter(function (key) {
|
|
50
|
+
return data[parseInt(key)][DATA_KEY_STATUS] === STATE_STARTED
|
|
51
|
+
})
|
|
52
|
+
.map(function (key) {
|
|
53
|
+
return parseInt(key)
|
|
54
|
+
})
|
|
55
|
+
.sort(function (a, b) {
|
|
56
|
+
let v1 = data[a][DATA_KEY_LAST_UPDATED_TIME]
|
|
57
|
+
let v2 = data[b][DATA_KEY_LAST_UPDATED_TIME]
|
|
58
|
+
if (v1 > v2) return -1
|
|
59
|
+
else if (v1 < v2) return 1
|
|
60
|
+
return 0
|
|
61
|
+
})
|
|
62
|
+
if (limit) {
|
|
63
|
+
ids = ids.slice(0, limit)
|
|
64
|
+
}
|
|
65
|
+
return ids
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
export async function getAllCompleted(limit = null) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
69
|
+
const data = await dataContext.getData()
|
|
70
|
+
let ids = Object.keys(data)
|
|
71
|
+
.filter(function (key) {
|
|
72
|
+
return data[parseInt(key)][DATA_KEY_STATUS] === STATE_COMPLETED
|
|
73
|
+
})
|
|
74
|
+
.map(function (key) {
|
|
75
|
+
return parseInt(key)
|
|
76
|
+
})
|
|
77
|
+
.sort(function (a, b) {
|
|
78
|
+
let v1 = data[a][DATA_KEY_LAST_UPDATED_TIME]
|
|
79
|
+
let v2 = data[b][DATA_KEY_LAST_UPDATED_TIME]
|
|
80
|
+
if (v1 > v2) return -1
|
|
81
|
+
else if (v1 < v2) return 1
|
|
82
|
+
return 0
|
|
83
|
+
})
|
|
84
|
+
if (limit) {
|
|
85
|
+
ids = ids.slice(0, limit)
|
|
86
|
+
}
|
|
87
|
+
return ids
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
export async function getAllStartedOrCompleted(limit = null) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
const data = await dataContext.getData()
|
|
92
|
+
let ids = Object.keys(data)
|
|
93
|
+
.filter(function (key) {
|
|
94
|
+
return (
|
|
95
|
+
data[parseInt(key)][DATA_KEY_STATUS] === STATE_STARTED ||
|
|
96
|
+
data[parseInt(key)][DATA_KEY_STATUS] === STATE_COMPLETED
|
|
97
|
+
)
|
|
98
|
+
})
|
|
99
|
+
.map(function (key) {
|
|
100
|
+
return parseInt(key)
|
|
101
|
+
})
|
|
102
|
+
.sort(function (a, b) {
|
|
103
|
+
let v1 = data[a][DATA_KEY_LAST_UPDATED_TIME]
|
|
104
|
+
let v2 = data[b][DATA_KEY_LAST_UPDATED_TIME]
|
|
105
|
+
if (v1 > v2) return -1
|
|
106
|
+
else if (v1 < v2) return 1
|
|
107
|
+
return 0
|
|
108
|
+
})
|
|
109
|
+
if (limit) {
|
|
110
|
+
ids = ids.slice(0, limit)
|
|
111
|
+
}
|
|
112
|
+
return ids
|
|
101
113
|
}
|
|
102
114
|
|
|
103
115
|
export async function getResumeTimeSeconds(contentId) {
|
|
104
|
-
|
|
105
|
-
|
|
116
|
+
let data = await dataContext.getData()
|
|
117
|
+
return data[contentId]?.[DATA_KEY_RESUME_TIME] ?? 0
|
|
106
118
|
}
|
|
107
119
|
|
|
108
120
|
export async function assignmentStatusCompleted(assignmentId, parentContentId) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
await dataContext.update(
|
|
122
|
+
async function (localContext) {
|
|
123
|
+
let hierarchy = await fetchHierarchy(parentContentId)
|
|
124
|
+
completeStatusInLocalContext(localContext, assignmentId, hierarchy)
|
|
125
|
+
},
|
|
126
|
+
async function () {
|
|
127
|
+
return postContentCompleted(assignmentId)
|
|
128
|
+
}
|
|
129
|
+
)
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
export async function contentStatusCompleted(contentId) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
await dataContext.update(
|
|
134
|
+
async function (localContext) {
|
|
135
|
+
let hierarchy = await fetchHierarchy(contentId)
|
|
136
|
+
completeStatusInLocalContext(localContext, contentId, hierarchy)
|
|
137
|
+
},
|
|
138
|
+
async function () {
|
|
139
|
+
return postContentCompleted(contentId)
|
|
140
|
+
}
|
|
141
|
+
)
|
|
128
142
|
}
|
|
129
143
|
|
|
130
|
-
|
|
131
144
|
function saveContentProgress(localContext, contentId, progress, currentSeconds, hierarchy) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
if (progress === 100) {
|
|
146
|
+
completeStatusInLocalContext(localContext, contentId, hierarchy)
|
|
147
|
+
return
|
|
148
|
+
}
|
|
136
149
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
150
|
+
let data = localContext.data[contentId] ?? {}
|
|
151
|
+
const currentProgress = data[DATA_KEY_STATUS]
|
|
152
|
+
if (!currentProgress || currentProgress !== STATE_COMPLETED) {
|
|
153
|
+
data[DATA_KEY_PROGRESS] = progress
|
|
154
|
+
data[DATA_KEY_STATUS] = STATE_STARTED
|
|
155
|
+
}
|
|
156
|
+
data[DATA_KEY_RESUME_TIME] = currentSeconds
|
|
157
|
+
data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000)
|
|
158
|
+
localContext.data[contentId] = data
|
|
146
159
|
|
|
147
|
-
|
|
160
|
+
bubbleProgress(hierarchy, contentId, localContext)
|
|
148
161
|
}
|
|
149
162
|
|
|
150
163
|
function completeStatusInLocalContext(localContext, contentId, hierarchy) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
let data = localContext.data[contentId] ?? {}
|
|
165
|
+
data[DATA_KEY_PROGRESS] = 100
|
|
166
|
+
data[DATA_KEY_STATUS] = STATE_COMPLETED
|
|
167
|
+
data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000)
|
|
168
|
+
localContext.data[contentId] = data
|
|
156
169
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
170
|
+
if (!hierarchy) return
|
|
171
|
+
let children = hierarchy.children[contentId] ?? []
|
|
172
|
+
for (let i = 0; i < children.length; i++) {
|
|
173
|
+
let childId = children[i]
|
|
174
|
+
completeStatusInLocalContext(localContext, childId, hierarchy)
|
|
175
|
+
}
|
|
176
|
+
bubbleProgress(hierarchy, contentId, localContext)
|
|
164
177
|
}
|
|
165
178
|
|
|
166
179
|
function getChildrenToDepth(parentId, hierarchy, depth = 1) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
180
|
+
let childIds = hierarchy.children[parentId] ?? []
|
|
181
|
+
let allChildrenIds = childIds
|
|
182
|
+
childIds.forEach((id) => {
|
|
183
|
+
allChildrenIds = allChildrenIds.concat(getChildrenToDepth(id, hierarchy, depth - 1))
|
|
184
|
+
})
|
|
185
|
+
return allChildrenIds
|
|
173
186
|
}
|
|
174
187
|
|
|
175
|
-
|
|
176
188
|
export async function assignmentStatusReset(assignmentId, contentId) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
189
|
+
await dataContext.update(
|
|
190
|
+
async function (localContext) {
|
|
191
|
+
let hierarchy = await fetchHierarchy(contentId)
|
|
192
|
+
resetStatusInLocalContext(localContext, assignmentId, hierarchy)
|
|
193
|
+
},
|
|
194
|
+
async function () {
|
|
195
|
+
return postContentReset(contentId)
|
|
196
|
+
}
|
|
197
|
+
)
|
|
185
198
|
}
|
|
186
199
|
|
|
187
200
|
export async function contentStatusReset(contentId) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
await dataContext.update(
|
|
202
|
+
async function (localContext) {
|
|
203
|
+
let hierarchy = await fetchHierarchy(contentId)
|
|
204
|
+
resetStatusInLocalContext(localContext, contentId, hierarchy)
|
|
205
|
+
},
|
|
206
|
+
async function () {
|
|
207
|
+
return postContentReset(contentId)
|
|
208
|
+
}
|
|
209
|
+
)
|
|
196
210
|
}
|
|
197
211
|
|
|
198
212
|
function resetStatusInLocalContext(localContext, contentId, hierarchy) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
213
|
+
let allChildIds = getChildrenToDepth(contentId, hierarchy, 5)
|
|
214
|
+
allChildIds.push(contentId)
|
|
215
|
+
allChildIds.forEach((id) => {
|
|
216
|
+
const index = Object.keys(localContext.data).indexOf(id.toString())
|
|
217
|
+
if (index > -1) {
|
|
218
|
+
// only splice array when item is found
|
|
219
|
+
delete localContext.data[id]
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
bubbleProgress(hierarchy, contentId, localContext)
|
|
208
223
|
}
|
|
209
224
|
|
|
210
225
|
/**
|
|
@@ -218,63 +233,81 @@ function resetStatusInLocalContext(localContext, contentId, hierarchy) {
|
|
|
218
233
|
* @param {int} secondsPlayed
|
|
219
234
|
* @param {string} sessionId - This function records a sessionId to pass into future updates to progress on the same video
|
|
220
235
|
*/
|
|
221
|
-
export async function recordWatchSession(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
236
|
+
export async function recordWatchSession(
|
|
237
|
+
contentId,
|
|
238
|
+
mediaType,
|
|
239
|
+
mediaCategory,
|
|
240
|
+
mediaLengthSeconds,
|
|
241
|
+
currentSeconds,
|
|
242
|
+
secondsPlayed,
|
|
243
|
+
sessionId = null
|
|
244
|
+
) {
|
|
245
|
+
let mediaTypeId = getMediaTypeId(mediaType, mediaCategory)
|
|
246
|
+
let updateLocalProgress = mediaTypeId === 1 || mediaTypeId === 2 //only update for video playback
|
|
247
|
+
if (!sessionId) {
|
|
248
|
+
sessionId = uuidv4()
|
|
249
|
+
}
|
|
250
|
+
await dataContext.update(
|
|
251
|
+
async function (localContext) {
|
|
252
|
+
if (contentId && updateLocalProgress) {
|
|
253
|
+
if (mediaLengthSeconds <= 0) {
|
|
254
|
+
return
|
|
240
255
|
}
|
|
241
|
-
|
|
242
|
-
|
|
256
|
+
let progress = Math.min(
|
|
257
|
+
99,
|
|
258
|
+
Math.round(((currentSeconds ?? 0) / Math.max(1, mediaLengthSeconds ?? 0)) * 100)
|
|
259
|
+
)
|
|
260
|
+
let hierarchy = await fetchHierarchy(contentId)
|
|
261
|
+
saveContentProgress(localContext, contentId, progress, currentSeconds, hierarchy)
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
async function () {
|
|
265
|
+
return postRecordWatchSession(
|
|
266
|
+
contentId,
|
|
267
|
+
mediaTypeId,
|
|
268
|
+
mediaLengthSeconds,
|
|
269
|
+
currentSeconds,
|
|
270
|
+
secondsPlayed,
|
|
271
|
+
sessionId
|
|
272
|
+
)
|
|
273
|
+
}
|
|
274
|
+
)
|
|
275
|
+
return sessionId
|
|
243
276
|
}
|
|
244
277
|
|
|
245
278
|
function getMediaTypeId(mediaType, mediaCategory) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
279
|
+
switch (`${mediaType}_${mediaCategory}`) {
|
|
280
|
+
case 'video_youtube':
|
|
281
|
+
return 1
|
|
282
|
+
case 'video_vimeo':
|
|
283
|
+
return 2
|
|
284
|
+
case 'assignment_soundslice':
|
|
285
|
+
return 3
|
|
286
|
+
case 'practice_play-alongs':
|
|
287
|
+
return 4
|
|
288
|
+
default:
|
|
289
|
+
throw Error(`Unsupported media type: ${mediaType}_${mediaCategory}`)
|
|
290
|
+
}
|
|
258
291
|
}
|
|
259
292
|
|
|
260
293
|
function uuidv4() {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
295
|
+
var r = (Math.random() * 16) | 0,
|
|
296
|
+
v = c === 'x' ? r : (r & 0x3) | 0x8
|
|
297
|
+
return v.toString(16)
|
|
298
|
+
})
|
|
265
299
|
}
|
|
266
300
|
|
|
267
301
|
function bubbleProgress(hierarchy, contentId, localContext) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
302
|
+
let parentId = hierarchy?.parents?.[contentId]
|
|
303
|
+
if (!parentId) return
|
|
304
|
+
let data = localContext.data[parentId] ?? {}
|
|
305
|
+
let childProgress = hierarchy?.children?.[parentId]?.map(function (childId) {
|
|
306
|
+
return localContext.data[childId]?.[DATA_KEY_PROGRESS] ?? 0
|
|
307
|
+
})
|
|
308
|
+
let progress = Math.round(childProgress.reduce((a, b) => a + b, 0) / childProgress.length)
|
|
309
|
+
data[DATA_KEY_PROGRESS] = progress
|
|
310
|
+
data[DATA_KEY_STATUS] = progress === 100 ? STATE_COMPLETED : STATE_STARTED
|
|
311
|
+
localContext.data[parentId] = data
|
|
312
|
+
bubbleProgress(hierarchy, parentId, localContext)
|
|
279
313
|
}
|
|
280
|
-
|