musora-content-services 1.0.120 → 1.0.122
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 +4 -0
- package/README.md +0 -0
- package/babel.config.js +0 -0
- package/docs/config.js.html +7 -3
- 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/index.html +1 -1
- package/docs/module-Config.html +28 -3
- package/docs/module-Railcontent-Services.html +7 -7
- package/docs/module-Sanity-Services.html +426 -48
- package/docs/railcontent.js.html +37 -47
- package/docs/sanity.js.html +69 -12
- 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/package.json +1 -1
- package/src/contentMetaData.js +11 -0
- package/src/contentTypeConfig.js +6 -2
- package/src/index.d.ts +11 -21
- package/src/index.js +11 -21
- package/src/services/config.js +6 -2
- package/src/services/contentLikes.js +38 -0
- package/src/services/dataContext.js +88 -0
- package/src/services/railcontent.js +36 -46
- package/src/services/sanity.js +64 -10
- package/test/contentLikes.test.js +86 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/sanityQueryService.test.js +39 -0
- package/tools/generate-index.js +0 -0
- package/src/services/userContext.js +0 -105
- package/test/userContext.test.js +0 -86
package/src/index.js
CHANGED
|
@@ -5,6 +5,12 @@ import {
|
|
|
5
5
|
initializeService
|
|
6
6
|
} from './services/config.js';
|
|
7
7
|
|
|
8
|
+
import {
|
|
9
|
+
isContentLiked,
|
|
10
|
+
likeContent,
|
|
11
|
+
unlikeContent
|
|
12
|
+
} from './services/contentLikes.js';
|
|
13
|
+
|
|
8
14
|
import {
|
|
9
15
|
fetchAllCompletedStates,
|
|
10
16
|
fetchCompletedContent,
|
|
@@ -12,10 +18,9 @@ import {
|
|
|
12
18
|
fetchContentInProgress,
|
|
13
19
|
fetchContentPageUserData,
|
|
14
20
|
fetchHandler,
|
|
15
|
-
fetchLikeContent,
|
|
16
21
|
fetchSongsInProgress,
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
fetchUserLikes,
|
|
23
|
+
fetchUserPermissions,
|
|
19
24
|
fetchVimeoData
|
|
20
25
|
} from './services/railcontent.js';
|
|
21
26
|
|
|
@@ -65,18 +70,7 @@ import {
|
|
|
65
70
|
getSortOrder
|
|
66
71
|
} from './services/sanity.js';
|
|
67
72
|
|
|
68
|
-
import {
|
|
69
|
-
clearCache,
|
|
70
|
-
fetchContentData,
|
|
71
|
-
init,
|
|
72
|
-
likeContent,
|
|
73
|
-
testClearLocal,
|
|
74
|
-
unlikeContent,
|
|
75
|
-
version
|
|
76
|
-
} from './services/userContext.js';
|
|
77
|
-
|
|
78
73
|
export {
|
|
79
|
-
clearCache,
|
|
80
74
|
fetchAll,
|
|
81
75
|
fetchAllCompletedStates,
|
|
82
76
|
fetchAllFilterOptions,
|
|
@@ -93,7 +87,6 @@ export {
|
|
|
93
87
|
fetchCoachLessons,
|
|
94
88
|
fetchCompletedContent,
|
|
95
89
|
fetchCompletedState,
|
|
96
|
-
fetchContentData,
|
|
97
90
|
fetchContentInProgress,
|
|
98
91
|
fetchContentPageUserData,
|
|
99
92
|
fetchCourseOverview,
|
|
@@ -101,7 +94,6 @@ export {
|
|
|
101
94
|
fetchGenreLessons,
|
|
102
95
|
fetchHandler,
|
|
103
96
|
fetchLessonContent,
|
|
104
|
-
fetchLikeContent,
|
|
105
97
|
fetchLiveEvent,
|
|
106
98
|
fetchMetadata,
|
|
107
99
|
fetchMethod,
|
|
@@ -126,17 +118,15 @@ export {
|
|
|
126
118
|
fetchSongCount,
|
|
127
119
|
fetchSongFilterOptions,
|
|
128
120
|
fetchSongsInProgress,
|
|
129
|
-
fetchUnlikeContent,
|
|
130
121
|
fetchUpcomingEvents,
|
|
131
|
-
|
|
122
|
+
fetchUserLikes,
|
|
123
|
+
fetchUserPermissions,
|
|
132
124
|
fetchVimeoData,
|
|
133
125
|
fetchWorkouts,
|
|
134
126
|
getSortOrder,
|
|
135
127
|
globalConfig,
|
|
136
|
-
init,
|
|
137
128
|
initializeService,
|
|
129
|
+
isContentLiked,
|
|
138
130
|
likeContent,
|
|
139
|
-
testClearLocal,
|
|
140
131
|
unlikeContent,
|
|
141
|
-
version,
|
|
142
132
|
};
|
package/src/services/config.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
let globalConfig = {
|
|
6
6
|
sanityConfig: {},
|
|
7
|
-
railcontentConfig: {}
|
|
7
|
+
railcontentConfig: {},
|
|
8
|
+
localStorage: null
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -24,6 +25,7 @@ let globalConfig = {
|
|
|
24
25
|
* @param {string} config.railcontentConfig.token - The token for authenticating user-specific requests.
|
|
25
26
|
* @param {string} config.railcontentConfig.userId - The user ID for fetching user-specific data.
|
|
26
27
|
* @param {string} config.railcontentConfig.baseUrl - The url for the enviroment.
|
|
28
|
+
* @param {Object} config.localStorage - Cache to use for localStorage
|
|
27
29
|
|
|
28
30
|
*
|
|
29
31
|
* @example
|
|
@@ -41,12 +43,14 @@ let globalConfig = {
|
|
|
41
43
|
* token: 'your-user-api-token',
|
|
42
44
|
* userId: 'current-user-id',
|
|
43
45
|
* baseUrl: 'https://web-staging-one.musora.com'
|
|
44
|
-
* }
|
|
46
|
+
* },
|
|
47
|
+
* localStorage: localStorage
|
|
45
48
|
* });
|
|
46
49
|
*/
|
|
47
50
|
function initializeService(config) {
|
|
48
51
|
globalConfig.sanityConfig = config.sanityConfig;
|
|
49
52
|
globalConfig.railcontentConfig = config.railcontentConfig;
|
|
53
|
+
globalConfig.localStorage = config.localStorage;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
// Export both the initialization function and the config object
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {fetchUserLikes, postContentLiked, postContentUnliked} from "./railcontent";
|
|
2
|
+
import {DataContext, ContentVersionKey} from "./dataContext";
|
|
3
|
+
|
|
4
|
+
export let dataContext = new DataContext(ContentVersionKey, fetchUserLikes);
|
|
5
|
+
|
|
6
|
+
export async function isContentLiked(contentId) {
|
|
7
|
+
let data = await dataContext.getData();
|
|
8
|
+
return data.includes(contentId);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function likeContent(contentId) {
|
|
12
|
+
await dataContext.update(
|
|
13
|
+
function (context) {
|
|
14
|
+
if (!context.data.includes(contentId)) {
|
|
15
|
+
context.data.push(contentId);
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
async function(){
|
|
19
|
+
return postContentLiked(contentId);
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function unlikeContent(contentId) {
|
|
25
|
+
await dataContext.update(
|
|
26
|
+
function (context) {
|
|
27
|
+
if (context.data.includes(contentId)) {
|
|
28
|
+
const index = context.data.indexOf(contentId);
|
|
29
|
+
if (index > -1) { // only splice array when item is found
|
|
30
|
+
context.data.splice(index, 1); // 2nd parameter means remove one item only
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
async function(){
|
|
35
|
+
return postContentUnliked(contentId);
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {globalConfig} from "./config";
|
|
2
|
+
|
|
3
|
+
//These constants need to match MWP UserDataVersionKeyEnum enum
|
|
4
|
+
export const ContentVersionKey = 0;
|
|
5
|
+
|
|
6
|
+
let cache = null;
|
|
7
|
+
|
|
8
|
+
export class DataContext {
|
|
9
|
+
context = null;
|
|
10
|
+
|
|
11
|
+
constructor(dataVersionKey, fetchDataFunction) {
|
|
12
|
+
this.dataVersionKey = dataVersionKey;
|
|
13
|
+
this.fetchDataFunction = fetchDataFunction;
|
|
14
|
+
this.localStorageKey = `dataContext_${this.dataVersionKey}`;
|
|
15
|
+
this.localStorageLastUpdatedKey = `dataContext_${this.dataVersionKey}_lastUpdated`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async getData() {
|
|
19
|
+
this.ensureLocalContextLoaded();
|
|
20
|
+
if (!this.context || this.shouldVerifyServerVerions()) {
|
|
21
|
+
let version = this.version();
|
|
22
|
+
let data = await this.fetchData(version);
|
|
23
|
+
if (data.version !== "No Change") {
|
|
24
|
+
this.context = data;
|
|
25
|
+
cache.setItem(this.localStorageKey, JSON.stringify(data));
|
|
26
|
+
}
|
|
27
|
+
cache.setItem(this.localStorageLastUpdatedKey, new Date().getTime());
|
|
28
|
+
}
|
|
29
|
+
return this.context.data;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async fetchData(version) {
|
|
33
|
+
return await this.fetchDataFunction(version);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
ensureLocalContextLoaded() {
|
|
37
|
+
if (this.context) return;
|
|
38
|
+
this.verifyConfig();
|
|
39
|
+
let localData = cache.getItem(this.localStorageKey);
|
|
40
|
+
if (localData) {
|
|
41
|
+
this.context = JSON.parse(localData);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
verifyConfig() {
|
|
46
|
+
if (!cache) {
|
|
47
|
+
cache = globalConfig.localStorage;
|
|
48
|
+
if (!cache) throw new Error('dataContext: LocalStorage cache not configured in musora content services initializeService.');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
shouldVerifyServerVerions() {
|
|
53
|
+
let lastUpdated = cache.getItem(this.localStorageLastUpdatedKey);
|
|
54
|
+
if (!lastUpdated) return false;
|
|
55
|
+
const verifyServerTime = 10000; //10 s
|
|
56
|
+
return (new Date().getTime() - lastUpdated) > verifyServerTime;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
clearCache() {
|
|
60
|
+
this.clearContext();
|
|
61
|
+
cache.removeItem(this.localStorageKey);
|
|
62
|
+
cache.removeItem(this.localStorageLastUpdatedKey);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
clearContext() {
|
|
66
|
+
this.context = null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async update(localUpdateFunction, serverUpdateFunction) {
|
|
70
|
+
this.ensureLocalContextLoaded();
|
|
71
|
+
if (this.context) {
|
|
72
|
+
localUpdateFunction(this.context);
|
|
73
|
+
this.context.version++;
|
|
74
|
+
let data = JSON.stringify(this.context);
|
|
75
|
+
cache.setItem(this.localStorageKey, data);
|
|
76
|
+
cache.setItem(this.localStorageLastUpdatedKey, new Date().getTime());
|
|
77
|
+
}
|
|
78
|
+
let response = await serverUpdateFunction();
|
|
79
|
+
if (response.version !== this.version()) {
|
|
80
|
+
this.clearCache();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
version() {
|
|
85
|
+
return this.context?.version ?? -1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @module Railcontent-Services
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
const {
|
|
5
|
+
const {globalConfig} = require('./config');
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -24,10 +24,11 @@ export async function fetchCompletedState(content_id) {
|
|
|
24
24
|
};
|
|
25
25
|
|
|
26
26
|
try {
|
|
27
|
-
const response = await fetchAbsolute(url, {
|
|
27
|
+
const response = await fetchAbsolute(url, {headers});
|
|
28
28
|
const result = await response.json();
|
|
29
29
|
|
|
30
|
-
if (result && result[content_id]) {
|
|
30
|
+
if (result && result[content_id]) {
|
|
31
|
+
return result[content_id]; // Return the correct object
|
|
31
32
|
} else {
|
|
32
33
|
return null; // Handle unexpected structure
|
|
33
34
|
}
|
|
@@ -56,7 +57,7 @@ export async function fetchVimeoData(vimeo_id) {
|
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
try {
|
|
59
|
-
const response = await fetchAbsolute(url, {
|
|
60
|
+
const response = await fetchAbsolute(url, {headers});
|
|
60
61
|
const result = await response.json();
|
|
61
62
|
|
|
62
63
|
if (result) {
|
|
@@ -91,9 +92,9 @@ export async function fetchAllCompletedStates(contentIds) {
|
|
|
91
92
|
};
|
|
92
93
|
|
|
93
94
|
try {
|
|
94
|
-
const response = await fetchAbsolute(url, {
|
|
95
|
+
const response = await fetchAbsolute(url, {headers});
|
|
95
96
|
const result = await response.json();
|
|
96
|
-
if(result){
|
|
97
|
+
if (result) {
|
|
97
98
|
return result;
|
|
98
99
|
} else {
|
|
99
100
|
console.log('result not json');
|
|
@@ -123,9 +124,9 @@ export async function fetchSongsInProgress(brand) {
|
|
|
123
124
|
};
|
|
124
125
|
|
|
125
126
|
try {
|
|
126
|
-
const response = await fetchAbsolute(url, {
|
|
127
|
+
const response = await fetchAbsolute(url, {headers});
|
|
127
128
|
const result = await response.json();
|
|
128
|
-
if(result){
|
|
129
|
+
if (result) {
|
|
129
130
|
//console.log('fetchSongsInProgress', result);
|
|
130
131
|
return result;
|
|
131
132
|
} else {
|
|
@@ -150,12 +151,12 @@ export async function fetchSongsInProgress(brand) {
|
|
|
150
151
|
* .then(songs => console.log(songs))
|
|
151
152
|
* .catch(error => console.error(error));
|
|
152
153
|
*/
|
|
153
|
-
export async function fetchContentInProgress(type="all", brand, {
|
|
154
|
+
export async function fetchContentInProgress(type = "all", brand, {page, limit} = {}) {
|
|
154
155
|
let url;
|
|
155
156
|
const limitString = limit ? `&limit=${limit}` : '';
|
|
156
157
|
const pageString = page ? `&page=${page}` : '';
|
|
157
158
|
|
|
158
|
-
if(type === "all") {
|
|
159
|
+
if (type === "all") {
|
|
159
160
|
url = `/content/in_progress/${globalConfig.railcontentConfig.userId}?brand=${brand}${limitString}${pageString}`;
|
|
160
161
|
} else {
|
|
161
162
|
url = `/content/in_progress/${globalConfig.railcontentConfig.userId}?content_type=${type}&brand=${brand}${limitString}${pageString}`;
|
|
@@ -165,9 +166,9 @@ export async function fetchContentInProgress(type="all", brand, { page, limit }
|
|
|
165
166
|
'X-CSRF-TOKEN': globalConfig.railcontentConfig.token
|
|
166
167
|
};
|
|
167
168
|
try {
|
|
168
|
-
const response = await fetchAbsolute(url, {
|
|
169
|
+
const response = await fetchAbsolute(url, {headers});
|
|
169
170
|
const result = await response.json();
|
|
170
|
-
if(result){
|
|
171
|
+
if (result) {
|
|
171
172
|
//console.log('contentInProgress', result);
|
|
172
173
|
return result;
|
|
173
174
|
} else {
|
|
@@ -192,12 +193,12 @@ export async function fetchContentInProgress(type="all", brand, { page, limit }
|
|
|
192
193
|
* .then(songs => console.log(songs))
|
|
193
194
|
* .catch(error => console.error(error));
|
|
194
195
|
*/
|
|
195
|
-
export async function fetchCompletedContent(type="all", brand, {
|
|
196
|
+
export async function fetchCompletedContent(type = "all", brand, {page, limit} = {}) {
|
|
196
197
|
let url;
|
|
197
198
|
const limitString = limit ? `&limit=${limit}` : '';
|
|
198
199
|
const pageString = page ? `&page=${page}` : '';
|
|
199
200
|
|
|
200
|
-
if(type === "all") {
|
|
201
|
+
if (type === "all") {
|
|
201
202
|
url = `/content/completed/${globalConfig.railcontentConfig.userId}?brand=${brand}${limitString}${pageString}`;
|
|
202
203
|
} else {
|
|
203
204
|
url = `/content/completed/${globalConfig.railcontentConfig.userId}?content_type=${type}&brand=${brand}${limitString}${pageString}`;
|
|
@@ -207,9 +208,9 @@ export async function fetchCompletedContent(type="all", brand, { page, limit } =
|
|
|
207
208
|
'X-CSRF-TOKEN': globalConfig.railcontentConfig.token
|
|
208
209
|
};
|
|
209
210
|
try {
|
|
210
|
-
const response = await fetchAbsolute(url, {
|
|
211
|
+
const response = await fetchAbsolute(url, {headers});
|
|
211
212
|
const result = await response.json();
|
|
212
|
-
if(result){
|
|
213
|
+
if (result) {
|
|
213
214
|
//console.log('completed content', result);
|
|
214
215
|
return result;
|
|
215
216
|
} else {
|
|
@@ -239,32 +240,11 @@ export async function fetchContentPageUserData(contentId) {
|
|
|
239
240
|
'X-CSRF-TOKEN': globalConfig.railcontentConfig.token
|
|
240
241
|
};
|
|
241
242
|
|
|
242
|
-
try {
|
|
243
|
-
const response = await fetchAbsolute(url, { headers });
|
|
244
|
-
const result = await response.json();
|
|
245
|
-
if(result){
|
|
246
|
-
console.log('fetchContentPageUserData', result);
|
|
247
|
-
return result;
|
|
248
|
-
} else {
|
|
249
|
-
console.log('result not json');
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
console.error('Fetch error:', error);
|
|
253
|
-
return null;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export async function fetchUserContext() {
|
|
258
|
-
let url = `/content/user_data_all`;
|
|
259
|
-
const headers = {
|
|
260
|
-
'Content-Type': 'application/json',
|
|
261
|
-
'X-CSRF-TOKEN': globalConfig.railcontentConfig.token
|
|
262
|
-
};
|
|
263
243
|
try {
|
|
264
244
|
const response = await fetchAbsolute(url, {headers});
|
|
265
245
|
const result = await response.json();
|
|
266
246
|
if (result) {
|
|
267
|
-
console.log('
|
|
247
|
+
console.log('fetchContentPageUserData', result);
|
|
268
248
|
return result;
|
|
269
249
|
} else {
|
|
270
250
|
console.log('result not json');
|
|
@@ -287,11 +267,16 @@ export async function fetchUserPermissions() {
|
|
|
287
267
|
return fetchHandler(url, 'get') ?? [];
|
|
288
268
|
}
|
|
289
269
|
|
|
290
|
-
|
|
291
|
-
|
|
270
|
+
async function fetchDataHandler(url, dataVersion, method = "get") {
|
|
271
|
+
return fetchHandler(url, method, dataVersion);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function fetchHandler(url, method = "get", dataVersion = null) {
|
|
275
|
+
let headers = {
|
|
292
276
|
'Content-Type': 'application/json',
|
|
293
|
-
'X-CSRF-TOKEN': globalConfig.railcontentConfig.token
|
|
277
|
+
'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
|
|
294
278
|
};
|
|
279
|
+
if (dataVersion) headers['Data-Version'] = dataVersion;
|
|
295
280
|
try {
|
|
296
281
|
const response = await fetchAbsolute(url, {method, headers});
|
|
297
282
|
const result = await response.json();
|
|
@@ -306,18 +291,23 @@ export async function fetchHandler(url, method = "get") {
|
|
|
306
291
|
return null;
|
|
307
292
|
}
|
|
308
293
|
|
|
309
|
-
export async function
|
|
310
|
-
let url = `/content
|
|
294
|
+
export async function fetchUserLikes(currentVersion) {
|
|
295
|
+
let url = `/content/user/likes/all`;
|
|
296
|
+
return fetchDataHandler(url, currentVersion);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export async function postContentLiked(contentId) {
|
|
300
|
+
let url = `/content/user/likes/like/${contentId}`;
|
|
311
301
|
return await fetchHandler(url, "post");
|
|
312
302
|
}
|
|
313
303
|
|
|
314
|
-
export async function
|
|
315
|
-
let url = `/content/${contentId}
|
|
304
|
+
export async function postContentUnliked(contentId) {
|
|
305
|
+
let url = `/content/user/likes/unlike/${contentId}`;
|
|
316
306
|
return await fetchHandler(url, "post");
|
|
317
307
|
}
|
|
318
308
|
|
|
319
309
|
function fetchAbsolute(url, params) {
|
|
320
|
-
if(globalConfig.railcontentConfig.baseUrl) {
|
|
310
|
+
if (globalConfig.railcontentConfig.baseUrl) {
|
|
321
311
|
if (url.startsWith('/')) {
|
|
322
312
|
return fetch(globalConfig.railcontentConfig.baseUrl + url, params)
|
|
323
313
|
}
|
package/src/services/sanity.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
getUpcomingEventsTypes,
|
|
14
14
|
showsTypes,
|
|
15
15
|
getNewReleasesTypes,
|
|
16
|
+
coachLessonsTypes
|
|
16
17
|
} from "../contentTypeConfig";
|
|
17
18
|
|
|
18
19
|
import {
|
|
@@ -576,6 +577,7 @@ export function getSortOrder(sort= '-published_on', groupBy)
|
|
|
576
577
|
*
|
|
577
578
|
* This function constructs a query to retrieve the total number of results and filter options such as difficulty, instrument type, and genre.
|
|
578
579
|
* The filter options are dynamically generated based on the provided filters, style, artist, and content type.
|
|
580
|
+
* If a coachId is provided, the content type must be 'coach-lessons'.
|
|
579
581
|
*
|
|
580
582
|
* @param {string} brand - The brand for which to fetch the filter options.
|
|
581
583
|
* @param {string[]} filters - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock']
|
|
@@ -584,13 +586,22 @@ export function getSortOrder(sort= '-published_on', groupBy)
|
|
|
584
586
|
* @param {string} contentType - The content type to fetch (e.g., 'song', 'lesson').
|
|
585
587
|
* @param {string} [term] - Optional search term to match against various fields such as title, album, artist name, and genre.
|
|
586
588
|
* @param {Array<string>} [progressIds=undefined] - An array of railcontent IDs to filter the results by. Used for filtering by progress.
|
|
589
|
+
* @param {string} [coachId=undefined] - Optional coach ID to filter the results by a specific coach. If provided, contentType must be 'coach-lessons'.
|
|
587
590
|
* @returns {Promise<Object|null>} - A promise that resolves to an object containing the total results and filter options, or null if the query fails.
|
|
588
591
|
*
|
|
592
|
+
* @throws {Error} Will throw an error if coachId is provided but contentType is not 'coach-lessons'.
|
|
593
|
+
*
|
|
589
594
|
* @example
|
|
590
595
|
* // Example usage:
|
|
591
596
|
* fetchAllFilterOptions('myBrand', '', 'Rock', 'John Doe', 'song', 'Love')
|
|
592
597
|
* .then(options => console.log(options))
|
|
593
598
|
* .catch(error => console.error(error));
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* // Example usage with coachId:
|
|
602
|
+
* fetchAllFilterOptions('myBrand', '', 'Rock', 'John Doe', 'coach-lessons', 'Love', undefined, '123')
|
|
603
|
+
* .then(options => console.log(options))
|
|
604
|
+
* .catch(error => console.error(error));
|
|
594
605
|
*/
|
|
595
606
|
export async function fetchAllFilterOptions(
|
|
596
607
|
brand,
|
|
@@ -599,25 +610,48 @@ export async function fetchAllFilterOptions(
|
|
|
599
610
|
artist,
|
|
600
611
|
contentType,
|
|
601
612
|
term,
|
|
602
|
-
progressIds = undefined
|
|
613
|
+
progressIds = undefined,
|
|
614
|
+
coachId = undefined, // New parameter for coach ID
|
|
603
615
|
) {
|
|
616
|
+
if (coachId && contentType !== 'coach-lessons') {
|
|
617
|
+
throw new Error(`Invalid contentType: '${contentType}' for coachId. It must be 'coach-lessons'.`);
|
|
618
|
+
}
|
|
619
|
+
|
|
604
620
|
filters = Array.isArray(filters) ? filters : [];
|
|
605
621
|
const includedFieldsFilter = filters?.length > 0 ? filtersToGroq(filters) : undefined;
|
|
606
622
|
|
|
607
623
|
const progressFilter = progressIds !== undefined ?
|
|
608
624
|
`&& railcontent_id in [${progressIds.join(',')}]` : "";
|
|
609
625
|
|
|
610
|
-
|
|
626
|
+
// General common filter logic
|
|
627
|
+
let commonFilter;
|
|
628
|
+
|
|
629
|
+
if (coachId) {
|
|
630
|
+
// Coach-specific filtering
|
|
631
|
+
commonFilter = `brand == '${brand}' && references(*[_type=='instructor' && railcontent_id == ${coachId}]._id) ${includedFieldsFilter ? includedFieldsFilter : ''}`;
|
|
632
|
+
} else {
|
|
633
|
+
// Regular content filtering
|
|
634
|
+
commonFilter = `_type == '${contentType}' && brand == "${brand}"${style ? ` && '${style}' in genre[]->name` : ''}${artist ? ` && artist->name == '${artist}'` : ''} ${progressFilter} ${includedFieldsFilter ? includedFieldsFilter : ''}`;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Determine metadata and allowable filters (handle coach lessons if coachId exists)
|
|
611
638
|
const metaData = processMetadata(brand, contentType, true);
|
|
612
639
|
const allowableFilters = metaData?.allowableFilters || [];
|
|
613
640
|
|
|
641
|
+
// Dynamic filter options construction
|
|
614
642
|
const dynamicFilterOptions = allowableFilters.map(filter => {
|
|
615
|
-
// Create a modified common filter for each allowable filter
|
|
616
643
|
let includedFieldsFilterWithoutSelectedOption = filters?.length > 0 ? filtersToGroq(filters, filter) : undefined;
|
|
617
|
-
|
|
644
|
+
let commonFilterWithoutSelectedOption;
|
|
645
|
+
|
|
646
|
+
if (coachId) {
|
|
647
|
+
commonFilterWithoutSelectedOption = `brand == '${brand}' && references(*[_type=='instructor' && railcontent_id == ${coachId}]._id) ${includedFieldsFilterWithoutSelectedOption ? includedFieldsFilterWithoutSelectedOption : ''}`;
|
|
648
|
+
} else {
|
|
649
|
+
// Regular content filter without the selected option
|
|
650
|
+
commonFilterWithoutSelectedOption = `_type == '${contentType}' && brand == "${brand}"${(style && filter !== "style") ? ` && '${style}' in genre[]->name` : ''}${(artist && filter !== "artist") ? ` && artist->name == '${artist}'` : ''} ${includedFieldsFilterWithoutSelectedOption ? includedFieldsFilterWithoutSelectedOption : ''}`;
|
|
651
|
+
}
|
|
618
652
|
|
|
619
653
|
// Call getFilterOptions with the modified common filter
|
|
620
|
-
return getFilterOptions(filter, commonFilterWithoutSelectedOption,
|
|
654
|
+
return getFilterOptions(filter, commonFilterWithoutSelectedOption, contentType, brand);
|
|
621
655
|
}).join(' ');
|
|
622
656
|
|
|
623
657
|
const query = `
|
|
@@ -937,7 +971,7 @@ export async function fetchRelatedLessons(railContentId, brand) {
|
|
|
937
971
|
* Fetch related method lessons for a specific lesson by RailContent ID and type.
|
|
938
972
|
* @param {string} railContentId - The RailContent ID of the current lesson.
|
|
939
973
|
* @param {string} brand - The current brand.
|
|
940
|
-
* @returns {Promise<Object>|null>} - The fetched related lessons
|
|
974
|
+
* @returns {Promise<Array<Object>|null>} - The fetched related lessons
|
|
941
975
|
*/
|
|
942
976
|
export async function fetchRelatedMethodLessons(railContentId, brand) {
|
|
943
977
|
const query = `*[railcontent_id == ${railContentId} && brand == "${brand}"]{
|
|
@@ -1082,8 +1116,15 @@ export async function fetchChallengeOverview(id) {
|
|
|
1082
1116
|
|
|
1083
1117
|
/**
|
|
1084
1118
|
* Fetch the data needed for the coach screen.
|
|
1119
|
+
* @param {string} brand - The brand for which to fetch coach lessons
|
|
1085
1120
|
* @param {string} id - The Railcontent ID of the coach
|
|
1086
1121
|
* @returns {Promise<Object|null>} - The lessons for the instructor or null if not found.
|
|
1122
|
+
* @param {Object} params - Parameters for pagination, filtering and sorting.
|
|
1123
|
+
* @param {string} [params.sortOrder="-published_on"] - The field to sort the lessons by.
|
|
1124
|
+
* @param {string} [params.searchTerm=""] - The search term to filter content by title.
|
|
1125
|
+
* @param {number} [params.page=1] - The page number for pagination.
|
|
1126
|
+
* @param {number} [params.limit=10] - The number of items per page.
|
|
1127
|
+
* @param {Array<string>} [params.includedFields=[]] - Additional filters to apply to the query in the format of a key,value array. eg. ['difficulty,Intermediate', 'genre,rock'].
|
|
1087
1128
|
*
|
|
1088
1129
|
* @example
|
|
1089
1130
|
* fetchCoachLessons('coach123')
|
|
@@ -1095,12 +1136,17 @@ export async function fetchCoachLessons(brand, id, {
|
|
|
1095
1136
|
searchTerm = '',
|
|
1096
1137
|
page = 1,
|
|
1097
1138
|
limit = 20,
|
|
1139
|
+
includedFields = [],
|
|
1098
1140
|
} = {}) {
|
|
1099
1141
|
const fieldsString = getFieldsForContentType();
|
|
1100
1142
|
const start = (page - 1) * limit;
|
|
1101
1143
|
const end = start + limit;
|
|
1102
1144
|
const searchFilter = searchTerm ? `&& title match "${searchTerm}*"`: ''
|
|
1103
|
-
const
|
|
1145
|
+
const includedFieldsFilter = includedFields.length > 0
|
|
1146
|
+
? filtersToGroq(includedFields)
|
|
1147
|
+
: "";
|
|
1148
|
+
const filter = `brand == '${brand}' ${searchFilter} ${includedFieldsFilter} && references(*[_type=='instructor' && railcontent_id == ${id}]._id)`;
|
|
1149
|
+
|
|
1104
1150
|
sortOrder = getSortOrder(sortOrder);
|
|
1105
1151
|
const query = buildEntityAndTotalQuery(
|
|
1106
1152
|
filter,
|
|
@@ -1374,7 +1420,7 @@ export async function fetchCatalogMetadata(contentType)
|
|
|
1374
1420
|
* Fetch shows data for a brand.
|
|
1375
1421
|
*
|
|
1376
1422
|
* @param brand - The brand for which to fetch shows.
|
|
1377
|
-
* @returns {Promise<
|
|
1423
|
+
* @returns {Promise<{name, description, type: *, thumbnailUrl}>}
|
|
1378
1424
|
*
|
|
1379
1425
|
* @example
|
|
1380
1426
|
*
|
|
@@ -1508,8 +1554,10 @@ function buildEntityAndTotalQuery(
|
|
|
1508
1554
|
}
|
|
1509
1555
|
|
|
1510
1556
|
|
|
1511
|
-
function getFilterOptions(option, commonFilter,contentType){
|
|
1557
|
+
function getFilterOptions(option, commonFilter,contentType, brand){
|
|
1512
1558
|
let filterGroq = '';
|
|
1559
|
+
const types = Array.from(new Set([...coachLessonsTypes,...showsTypes[brand]]));
|
|
1560
|
+
|
|
1513
1561
|
switch (option) {
|
|
1514
1562
|
case "difficulty":
|
|
1515
1563
|
filterGroq = `
|
|
@@ -1521,6 +1569,12 @@ function getFilterOptions(option, commonFilter,contentType){
|
|
|
1521
1569
|
{"type": "Expert", "count": count(*[${commonFilter} && difficulty_string == "Expert" ])}
|
|
1522
1570
|
][count > 0],`;
|
|
1523
1571
|
break;
|
|
1572
|
+
case "type":
|
|
1573
|
+
const dynamicTypeOptions = types.map(filter => {
|
|
1574
|
+
return `{"type": "${filter}", "count": count(*[${commonFilter} && _type == "${filter}"])}`
|
|
1575
|
+
}).join(', ');
|
|
1576
|
+
filterGroq = `"type": [${dynamicTypeOptions}][count > 0],`;
|
|
1577
|
+
break;
|
|
1524
1578
|
case "genre":
|
|
1525
1579
|
case "essential":
|
|
1526
1580
|
case "focus":
|
|
@@ -1529,7 +1583,7 @@ function getFilterOptions(option, commonFilter,contentType){
|
|
|
1529
1583
|
case "lifestyle":
|
|
1530
1584
|
case "creativity":
|
|
1531
1585
|
filterGroq = `
|
|
1532
|
-
"${option}": *[_type == '${option}' && '${contentType}' in filter_types] {
|
|
1586
|
+
"${option}": *[_type == '${option}' ${contentType ? ` && '${contentType}' in filter_types` : ''} ] {
|
|
1533
1587
|
"type": name,
|
|
1534
1588
|
"count": count(*[${commonFilter} && references(^._id)])
|
|
1535
1589
|
}[count > 0],`;
|