musora-content-services 1.0.156 → 1.0.157
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/.github/workflows/node.js.yml +0 -0
- package/CHANGELOG.md +2 -0
- package/README.md +0 -0
- package/babel.config.js +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/scripts/collapse.js +0 -0
- package/docs/scripts/commonNav.js +0 -0
- package/docs/scripts/linenumber.js +0 -0
- package/docs/scripts/nav.js +0 -0
- package/docs/scripts/polyfill.js +0 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
- package/docs/scripts/prettify/lang-css.js +0 -0
- package/docs/scripts/prettify/prettify.js +0 -0
- package/docs/scripts/search.js +0 -0
- package/docs/styles/jsdoc.css +0 -0
- package/docs/styles/prettify.css +0 -0
- package/jest.config.js +0 -0
- package/jsdoc.json +0 -0
- package/link_mcs.sh +0 -0
- package/package.json +1 -1
- package/src/contentMetaData.js +0 -0
- package/src/filterBuilder.js +22 -26
- package/src/index.d.ts +8 -1
- package/src/index.js +8 -1
- package/src/services/config.js +0 -0
- package/src/services/contentLikes.js +0 -0
- package/src/services/contentProgress.js +29 -23
- package/src/services/lastUpdated.js +18 -0
- package/src/services/railcontent.js +13 -23
- package/src/services/sanity.js +558 -555
- package/src/services/userPermissions.js +26 -0
- package/test/contentLikes.test.js +2 -3
- package/test/contentProgress.test.js +7 -7
- package/test/initializeTests.js +23 -0
- package/test/lastUpdated.test.js +22 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/sanityQueryService.test.js +29 -58
- package/test/userPermissions.test.js +19 -0
- package/tools/generate-index.js +0 -0
|
File without changes
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
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
|
+
### [1.0.157](https://github.com/railroadmedia/musora-content-services/compare/v1.0.156...v1.0.157) (2024-11-08)
|
|
6
|
+
|
|
5
7
|
### [1.0.156](https://github.com/railroadmedia/musora-content-services/compare/v1.0.155...v1.0.156) (2024-11-06)
|
|
6
8
|
|
|
7
9
|
### [1.0.155](https://github.com/railroadmedia/musora-content-services/compare/v1.0.154...v1.0.155) (2024-11-05)
|
package/README.md
CHANGED
|
File without changes
|
package/babel.config.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/docs/scripts/collapse.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/docs/scripts/nav.js
CHANGED
|
File without changes
|
package/docs/scripts/polyfill.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/docs/scripts/search.js
CHANGED
|
File without changes
|
package/docs/styles/jsdoc.css
CHANGED
|
File without changes
|
package/docs/styles/prettify.css
CHANGED
|
File without changes
|
package/jest.config.js
CHANGED
|
File without changes
|
package/jsdoc.json
CHANGED
|
File without changes
|
package/link_mcs.sh
CHANGED
|
File without changes
|
package/package.json
CHANGED
package/src/contentMetaData.js
CHANGED
|
File without changes
|
package/src/filterBuilder.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {fetchUserPermissions} from "./services/userPermissions";
|
|
1
2
|
|
|
2
3
|
|
|
3
4
|
export class FilterBuilder {
|
|
@@ -5,17 +6,15 @@ export class FilterBuilder {
|
|
|
5
6
|
STATUS_SCHEDULED = 'scheduled';
|
|
6
7
|
|
|
7
8
|
constructor(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}={}) {
|
|
18
|
-
this.user = user;
|
|
9
|
+
filter = '',
|
|
10
|
+
{
|
|
11
|
+
availableContentStatuses = [],
|
|
12
|
+
bypassPermissions = false,
|
|
13
|
+
pullFutureContent = false,
|
|
14
|
+
getFutureContentOnly = false,
|
|
15
|
+
getFutureScheduledContentsOnly = false,
|
|
16
|
+
|
|
17
|
+
} = {}) {
|
|
19
18
|
this.availableContentStatuses = availableContentStatuses;
|
|
20
19
|
this.bypassPermissions = bypassPermissions;
|
|
21
20
|
this.pullFutureContent = pullFutureContent;
|
|
@@ -27,13 +26,15 @@ export class FilterBuilder {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
|
|
30
|
-
static withOnlyFilterAvailableStatuses(filter, availableContentStatuses) {
|
|
31
|
-
return new FilterBuilder(filter,{
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
static withOnlyFilterAvailableStatuses(filter, availableContentStatuses, bypassPermissions) {
|
|
30
|
+
return new FilterBuilder(filter, {
|
|
31
|
+
availableContentStatuses,
|
|
32
|
+
bypassPermissions,
|
|
33
|
+
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
buildFilter() {
|
|
36
|
+
async buildFilter() {
|
|
37
|
+
this.userData = await fetchUserPermissions();
|
|
37
38
|
if (this.debug) console.log('baseFilter', this.filter);
|
|
38
39
|
const filter = this
|
|
39
40
|
._applyContentStatuses()
|
|
@@ -64,20 +65,15 @@ export class FilterBuilder {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
_applyPermissions() {
|
|
67
|
-
if (this.bypassPermissions) return this;
|
|
68
|
-
// TODO these need to be pulled from the user and reference either ID, or railcontent_id
|
|
68
|
+
if (this.bypassPermissions || this.userData.isAdmin) return this;
|
|
69
69
|
const requiredPermissions = this._getUserPermissions();
|
|
70
70
|
if (requiredPermissions.length === 0) return this;
|
|
71
|
-
// handle pullSongsContent, I think the flagging on this needs to be backwards compared to BE
|
|
72
|
-
// if using id, switch railcontent_id to _id in the below query
|
|
73
71
|
this._andWhere(`references(*[_type == 'permission' && railcontent_id in ${arrayToRawRepresentation(requiredPermissions)}]._id)`);
|
|
74
72
|
return this;
|
|
75
|
-
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
_getUserPermissions() {
|
|
79
|
-
|
|
80
|
-
return this?.user?.permissions ?? [];
|
|
76
|
+
return this.userData.permissions;
|
|
81
77
|
}
|
|
82
78
|
|
|
83
79
|
_applyPublishingDateRestrictions() {
|
|
@@ -88,7 +84,7 @@ export class FilterBuilder {
|
|
|
88
84
|
this._andWhere(`published_on <= '${now}'`);
|
|
89
85
|
} else {
|
|
90
86
|
const date = new Date();
|
|
91
|
-
const theFuture =
|
|
87
|
+
const theFuture = new Date(date.setMonth(date.getMonth() + 18));
|
|
92
88
|
this._andWhere(`published_on <= '${theFuture}'`);
|
|
93
89
|
}
|
|
94
90
|
return this;
|
|
@@ -106,8 +102,8 @@ export class FilterBuilder {
|
|
|
106
102
|
|
|
107
103
|
_trimAmpersands() {
|
|
108
104
|
this.filter = this.filter.trim();
|
|
109
|
-
while(
|
|
110
|
-
while(
|
|
105
|
+
while (this.filter.charAt(0) === '&' || this.filter.charAt(0) === ' ') this.filter = this.filter.substring(1);
|
|
106
|
+
while (this.filter.charAt(this.filter.length) === '&' || this.filter.charAt(this.filter.length) === ' ') this.filter = this.filter.slice(-1);
|
|
111
107
|
return this;
|
|
112
108
|
}
|
|
113
109
|
|
package/src/index.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
fetchUserBadges,
|
|
47
47
|
fetchUserChallengeProgress,
|
|
48
48
|
fetchUserLikes,
|
|
49
|
-
|
|
49
|
+
fetchUserPermissionsData,
|
|
50
50
|
fetchUserPlaylists,
|
|
51
51
|
likePlaylist,
|
|
52
52
|
postChallengesCommunityNotification,
|
|
@@ -115,6 +115,11 @@ import {
|
|
|
115
115
|
getSortOrder
|
|
116
116
|
} from './services/sanity.js';
|
|
117
117
|
|
|
118
|
+
import {
|
|
119
|
+
fetchUserPermissions,
|
|
120
|
+
reset
|
|
121
|
+
} from './services/userPermissions.js';
|
|
122
|
+
|
|
118
123
|
declare module 'musora-content-services' {
|
|
119
124
|
export {
|
|
120
125
|
contentStatusCompleted,
|
|
@@ -188,6 +193,7 @@ declare module 'musora-content-services' {
|
|
|
188
193
|
fetchUserChallengeProgress,
|
|
189
194
|
fetchUserLikes,
|
|
190
195
|
fetchUserPermissions,
|
|
196
|
+
fetchUserPermissionsData,
|
|
191
197
|
fetchUserPlaylists,
|
|
192
198
|
fetchWorkouts,
|
|
193
199
|
getProgressPercentage,
|
|
@@ -214,6 +220,7 @@ declare module 'musora-content-services' {
|
|
|
214
220
|
postContentUnliked,
|
|
215
221
|
postRecordWatchSession,
|
|
216
222
|
recordWatchSession,
|
|
223
|
+
reset,
|
|
217
224
|
unlikeContent,
|
|
218
225
|
updatePlaylist,
|
|
219
226
|
updatePlaylistItem,
|
package/src/index.js
CHANGED
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
fetchUserBadges,
|
|
47
47
|
fetchUserChallengeProgress,
|
|
48
48
|
fetchUserLikes,
|
|
49
|
-
|
|
49
|
+
fetchUserPermissionsData,
|
|
50
50
|
fetchUserPlaylists,
|
|
51
51
|
likePlaylist,
|
|
52
52
|
postChallengesCommunityNotification,
|
|
@@ -115,6 +115,11 @@ import {
|
|
|
115
115
|
getSortOrder
|
|
116
116
|
} from './services/sanity.js';
|
|
117
117
|
|
|
118
|
+
import {
|
|
119
|
+
fetchUserPermissions,
|
|
120
|
+
reset
|
|
121
|
+
} from './services/userPermissions.js';
|
|
122
|
+
|
|
118
123
|
export {
|
|
119
124
|
contentStatusCompleted,
|
|
120
125
|
contentStatusReset,
|
|
@@ -187,6 +192,7 @@ export {
|
|
|
187
192
|
fetchUserChallengeProgress,
|
|
188
193
|
fetchUserLikes,
|
|
189
194
|
fetchUserPermissions,
|
|
195
|
+
fetchUserPermissionsData,
|
|
190
196
|
fetchUserPlaylists,
|
|
191
197
|
fetchWorkouts,
|
|
192
198
|
getProgressPercentage,
|
|
@@ -213,6 +219,7 @@ export {
|
|
|
213
219
|
postContentUnliked,
|
|
214
220
|
postRecordWatchSession,
|
|
215
221
|
recordWatchSession,
|
|
222
|
+
reset,
|
|
216
223
|
unlikeContent,
|
|
217
224
|
updatePlaylist,
|
|
218
225
|
updatePlaylistItem,
|
package/src/services/config.js
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -86,31 +86,24 @@ export async function contentStatusReset(contentId) {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
export async function recordWatchSession({
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
mediaCategory,
|
|
93
|
-
watchPositionSeconds,
|
|
94
|
-
totalDurationSeconds,
|
|
95
|
-
sessionToken,
|
|
96
|
-
brand,
|
|
97
|
-
contentId = null
|
|
98
|
-
}) {
|
|
89
|
+
export async function recordWatchSession(contentId, mediaType, mediaCategory, mediaLengthSeconds, currentSeconds, secondsPlayed, sessionId = null) {
|
|
90
|
+
let mediaTypeId = getMediaTypeId(mediaType, mediaCategory);
|
|
91
|
+
let updateLocalProgress = mediaTypeId === 1 || mediaTypeId === 2; //only update for video playback
|
|
99
92
|
await dataContext.update(
|
|
100
93
|
async function (localContext) {
|
|
101
|
-
if (contentId) {
|
|
94
|
+
if (contentId && updateLocalProgress ) {
|
|
102
95
|
let data = localContext.data[contentId] ?? [];
|
|
103
96
|
let progress = data?.[DATA_KEY_PROGRESS] ?? 0;
|
|
104
97
|
let status = data?.[DATA_KEY_STATUS] ?? 0;
|
|
105
98
|
|
|
106
99
|
if (status !== STATE_COMPLETED && progress !== 100) {
|
|
107
100
|
status = STATE_STARTED;
|
|
108
|
-
progress = Math.min(99, Math.round(
|
|
101
|
+
progress = Math.min(99, Math.round(currentSeconds ?? 0 / Math.max(1, mediaLengthSeconds ?? 0) * 100));
|
|
109
102
|
}
|
|
110
103
|
|
|
111
104
|
data[DATA_KEY_PROGRESS] = progress;
|
|
112
105
|
data[DATA_KEY_STATUS] = status;
|
|
113
|
-
data[DATA_KEY_RESUME_TIME] =
|
|
106
|
+
data[DATA_KEY_RESUME_TIME] = currentSeconds;
|
|
114
107
|
localContext.data[contentId] = data;
|
|
115
108
|
|
|
116
109
|
let hierarchy = await fetchHierarchy(contentId);
|
|
@@ -118,18 +111,31 @@ export async function recordWatchSession({
|
|
|
118
111
|
}
|
|
119
112
|
},
|
|
120
113
|
async function () {
|
|
121
|
-
return postRecordWatchSession(
|
|
122
|
-
mediaId,
|
|
123
|
-
mediaType,
|
|
124
|
-
mediaCategory,
|
|
125
|
-
watchPositionSeconds,
|
|
126
|
-
totalDurationSeconds,
|
|
127
|
-
sessionToken,
|
|
128
|
-
brand,
|
|
129
|
-
contentId
|
|
130
|
-
});
|
|
114
|
+
return postRecordWatchSession(contentId, mediaTypeId, mediaLengthSeconds, currentSeconds, secondsPlayed, sessionId);
|
|
131
115
|
}
|
|
132
116
|
);
|
|
117
|
+
return sessionId;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getMediaTypeId(mediaType, mediaCategory) {
|
|
121
|
+
switch (`${mediaType}_${mediaCategory}`) {
|
|
122
|
+
case "video_youtube":
|
|
123
|
+
return 1;
|
|
124
|
+
case "video_vimeo":
|
|
125
|
+
return 2;
|
|
126
|
+
case "assignment_soundslice":
|
|
127
|
+
return 3;
|
|
128
|
+
case "practice_play-alongs":
|
|
129
|
+
return 4;
|
|
130
|
+
default:
|
|
131
|
+
throw Error(`Unsupported media type: ${mediaType}_${mediaCategory}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function uuidv4() {
|
|
136
|
+
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
|
|
137
|
+
(+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16)
|
|
138
|
+
);
|
|
133
139
|
}
|
|
134
140
|
|
|
135
141
|
function bubbleProgress(hierarchy, contentId, localContext) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {globalConfig} from "./config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Exported functions that are excluded from index generation.
|
|
5
|
+
*
|
|
6
|
+
* @type {string[]}
|
|
7
|
+
*/
|
|
8
|
+
const excludeFromGeneratedIndex = ['wasLastUpdateOlderThanXSeconds', 'setLastUpdatedTime'];
|
|
9
|
+
export function wasLastUpdateOlderThanXSeconds(seconds, key) {
|
|
10
|
+
let lastUpdated = globalConfig.localStorage.getItem(key);
|
|
11
|
+
if (!lastUpdated) return false;
|
|
12
|
+
const verifyServerTime = seconds * 1000;
|
|
13
|
+
return (new Date().getTime() - lastUpdated) > verifyServerTime;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function setLastUpdatedTime(key) {
|
|
17
|
+
globalConfig.localStorage.setItem(key, new Date().getTime()?.toString());
|
|
18
|
+
}
|
|
@@ -16,7 +16,8 @@ const excludeFromGeneratedIndex = [
|
|
|
16
16
|
'postRecordWatchSession',
|
|
17
17
|
'postContentStarted',
|
|
18
18
|
'postContentCompleted',
|
|
19
|
-
'postContentReset'
|
|
19
|
+
'postContentReset',
|
|
20
|
+
'fetchUserPermissionsData'
|
|
20
21
|
];
|
|
21
22
|
|
|
22
23
|
|
|
@@ -236,10 +237,10 @@ export async function fetchContentPageUserData(contentId) {
|
|
|
236
237
|
}
|
|
237
238
|
}
|
|
238
239
|
|
|
239
|
-
export async function
|
|
240
|
-
let url = `/content/
|
|
240
|
+
export async function fetchUserPermissionsData() {
|
|
241
|
+
let url = `/content/user/permissions`;
|
|
241
242
|
// in the case of an unauthorized user, we return empty permissions
|
|
242
|
-
return fetchHandler(url, 'get') ?? [];
|
|
243
|
+
return await fetchHandler(url, 'get') ?? [];
|
|
243
244
|
}
|
|
244
245
|
|
|
245
246
|
async function fetchDataHandler(url, dataVersion, method = "get") {
|
|
@@ -297,26 +298,15 @@ export async function fetchContentProgress(currentVersion) {
|
|
|
297
298
|
return fetchDataHandler(url, currentVersion);
|
|
298
299
|
}
|
|
299
300
|
|
|
300
|
-
export async function postRecordWatchSession({
|
|
301
|
-
|
|
302
|
-
mediaType,
|
|
303
|
-
mediaCategory,
|
|
304
|
-
watchPosition,
|
|
305
|
-
totalDuration,
|
|
306
|
-
sessionToken,
|
|
307
|
-
brand,
|
|
308
|
-
contentId = null
|
|
309
|
-
}) {
|
|
310
|
-
let url = `/railtracker/media-playback-session`;
|
|
301
|
+
export async function postRecordWatchSession(contentId, mediaTypeId, mediaLengthSeconds, currentSeconds, secondsPlayed, sessionId) {
|
|
302
|
+
let url = `/v2/railtracker/media-playback-session`;
|
|
311
303
|
return postDataHandler(url, {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
brand,
|
|
319
|
-
contentId
|
|
304
|
+
"content_id": contentId,
|
|
305
|
+
"media_type_id": mediaTypeId,
|
|
306
|
+
"media_length_seconds": mediaLengthSeconds,
|
|
307
|
+
"current_second": currentSeconds,
|
|
308
|
+
"seconds_played": secondsPlayed,
|
|
309
|
+
"session_id": sessionId
|
|
320
310
|
});
|
|
321
311
|
}
|
|
322
312
|
|