musora-content-services 2.3.26 → 2.5.0
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/.coderabbit.yaml +0 -0
- package/.editorconfig +0 -0
- package/.github/pull_request_template.md +0 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/.yarnrc.yml +1 -0
- package/CHANGELOG.md +19 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/docs/Content-Organization.html +0 -0
- package/docs/ContentOrganization.html +2 -2
- package/docs/Gamification.html +2 -2
- package/docs/UserManagement.html +0 -0
- package/docs/UserManagementSystem.html +26 -2
- package/docs/api_types.js.html +2 -2
- package/docs/config.js.html +2 -2
- package/docs/content-org_content-org.js.html +2 -2
- package/docs/content-org_playlists-types.js.html +6 -6
- package/docs/content-org_playlists.js.html +220 -18
- package/docs/content.js.html +26 -11
- 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/gamification_awards.js.html +2 -2
- package/docs/gamification_gamification.js.html +2 -2
- package/docs/gamification_types.js.html +2 -2
- package/docs/global.html +14 -23
- package/docs/global.html#User +0 -0
- package/docs/index.html +2 -2
- package/docs/module-Awards.html +2 -2
- package/docs/module-Config.html +2 -2
- package/docs/module-Content-Services-V2.html +10 -9
- package/docs/module-Content-Services.html +763 -0
- package/docs/module-Interests.html +1066 -0
- package/docs/module-Permissions.html +2 -2
- package/docs/module-Playlists.html +1738 -157
- package/docs/module-Railcontent-Services.html +823 -3862
- package/docs/module-Sanity-Services.html +33 -33
- package/docs/module-Session-Management.html +0 -0
- package/docs/module-Sessions.html +2 -2
- package/docs/module-User-Activity.html +405 -32
- package/docs/module-User-Management.html +0 -0
- package/docs/module-User-Permissions.html +0 -0
- package/docs/module-UserManagement.html +2 -2
- package/docs/railcontent.js.html +65 -464
- package/docs/sanity.js.html +7 -5
- 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/docs/types.js.html +0 -0
- package/docs/userActivity.js.html +177 -45
- package/docs/user_interests.js.html +150 -0
- package/docs/user_management.js.html +2 -2
- package/docs/user_permissions.js.html +2 -2
- package/docs/user_sessions.js.html +2 -2
- package/docs/user_types.js.html +2 -2
- package/docs/user_user-management-system.js.html +3 -2
- package/docs/user_user-management.js.html +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 +29 -5
- package/src/contentTypeConfig.js +3 -2
- package/src/index.d.ts +17 -0
- package/src/index.js +17 -0
- package/src/infrastructure/http/HttpClient.ts +120 -0
- package/src/infrastructure/http/executors/FetchRequestExecutor.ts +45 -0
- package/src/infrastructure/http/index.ts +13 -0
- package/src/infrastructure/http/interfaces/HeaderProvider.ts +3 -0
- package/src/infrastructure/http/interfaces/HttpError.ts +7 -0
- package/src/infrastructure/http/interfaces/NetworkError.ts +6 -0
- package/src/infrastructure/http/interfaces/RequestExecutor.ts +5 -0
- package/src/infrastructure/http/interfaces/RequestOptions.ts +5 -0
- package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +18 -0
- package/src/services/content-org/playlists.js +0 -0
- package/src/services/content.js +24 -9
- package/src/services/contentAggregator.js +18 -10
- package/src/services/railcontent.js +28 -0
- package/src/services/recommendations.js +21 -25
- package/src/services/user/interests.js +78 -0
- package/src/services/user/user-management-system.js +1 -0
- package/src/services/userActivity.js +23 -0
- package/test/HttpClient.test.js +257 -0
- package/test/contentLikes.test.js +0 -0
- package/test/contentProgress.test.js +0 -0
- package/test/dataContext.test.js +0 -0
- package/test/forum.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/initializeTests.js +0 -0
- package/test/lib/lastUpdated.test.js +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
- package/test/mockData/mockData_user_practices.json +0 -0
- package/test/streakMessage.test.js +0 -0
- package/test/user/permissions.test.js +0 -0
- package/test/userActivity.test.js +0 -0
- package/tools/generate-index.cjs +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { RequestExecutor } from '../interfaces/RequestExecutor'
|
|
2
|
+
import { RequestOptions } from '../interfaces/RequestOptions'
|
|
3
|
+
import { HttpError } from '../interfaces/HttpError'
|
|
4
|
+
|
|
5
|
+
export class FetchRequestExecutor implements RequestExecutor {
|
|
6
|
+
async execute<T>(url: string, options: RequestOptions): Promise<T> {
|
|
7
|
+
const response = await fetch(url, options)
|
|
8
|
+
|
|
9
|
+
if (!response.ok) {
|
|
10
|
+
throw await this.createHttpError(response, url, options.method)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return this.parseResponse<T>(response)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private async createHttpError(
|
|
17
|
+
response: Response,
|
|
18
|
+
url: string,
|
|
19
|
+
method: string
|
|
20
|
+
): Promise<HttpError> {
|
|
21
|
+
const error: HttpError = {
|
|
22
|
+
status: response.status,
|
|
23
|
+
statusText: response.statusText,
|
|
24
|
+
url,
|
|
25
|
+
method,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
error.body = await response.json()
|
|
30
|
+
} catch (e) {
|
|
31
|
+
error.body = await response.text()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return error
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private async parseResponse<T>(response: Response): Promise<T> {
|
|
38
|
+
const contentType = response.headers.get('content-type')
|
|
39
|
+
if (contentType && contentType.indexOf('application/json') !== -1) {
|
|
40
|
+
return (await response.json()) as T
|
|
41
|
+
} else {
|
|
42
|
+
return (await response.text()) as unknown as T
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Interfaces
|
|
2
|
+
export { RequestOptions } from './interfaces/RequestOptions'
|
|
3
|
+
export { HttpError } from './interfaces/HttpError'
|
|
4
|
+
export { NetworkError } from './interfaces/NetworkError'
|
|
5
|
+
export { HeaderProvider } from './interfaces/HeaderProvider'
|
|
6
|
+
export { RequestExecutor } from './interfaces/RequestExecutor'
|
|
7
|
+
|
|
8
|
+
// Implementations
|
|
9
|
+
export { DefaultHeaderProvider } from './providers/DefaultHeaderProvider'
|
|
10
|
+
export { FetchRequestExecutor } from './executors/FetchRequestExecutor'
|
|
11
|
+
|
|
12
|
+
// Main client
|
|
13
|
+
export { HttpClient } from './HttpClient'
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HeaderProvider } from '../interfaces/HeaderProvider'
|
|
2
|
+
import { globalConfig } from '../../../services/config.js'
|
|
3
|
+
|
|
4
|
+
export class DefaultHeaderProvider implements HeaderProvider {
|
|
5
|
+
getHeaders(): Record<string, string> {
|
|
6
|
+
const headers: Record<string, string> = {
|
|
7
|
+
'Content-Type': 'application/json',
|
|
8
|
+
Accept: 'application/json',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Handle timezone
|
|
12
|
+
if (globalConfig.localTimezoneString) {
|
|
13
|
+
headers['M-Client-Timezone'] = globalConfig.localTimezoneString
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return headers
|
|
17
|
+
}
|
|
18
|
+
}
|
|
File without changes
|
package/src/services/content.js
CHANGED
|
@@ -21,23 +21,18 @@ import {recommendations} from "./recommendations";
|
|
|
21
21
|
|
|
22
22
|
export async function getLessonContentRows (brand='drumeo', pageName = 'lessons') {
|
|
23
23
|
let recentContentIds = await fetchRecent(brand, pageName, { progress: 'recent' });
|
|
24
|
-
recentContentIds = recentContentIds.map(item => item.id);
|
|
25
24
|
|
|
26
25
|
let contentRows = await getContentRows(brand, pageName);
|
|
27
26
|
contentRows = Array.isArray(contentRows) ? contentRows : [];
|
|
28
27
|
contentRows.unshift({
|
|
29
28
|
id: 'recent',
|
|
30
29
|
title: 'Recent ' + capitalizeFirstLetter(pageName),
|
|
31
|
-
|
|
30
|
+
items: recentContentIds || []
|
|
32
31
|
});
|
|
33
32
|
|
|
34
33
|
const results = await Promise.all(
|
|
35
34
|
contentRows.map(async (row) => {
|
|
36
|
-
|
|
37
|
-
return { id: row.id, title: row.title, items: [] }
|
|
38
|
-
}
|
|
39
|
-
const data = await fetchByRailContentIds(row.content)
|
|
40
|
-
return { id: row.id, title: row.title, items: data }
|
|
35
|
+
return { id: row.id, title: row.title, items: row.items }
|
|
41
36
|
})
|
|
42
37
|
)
|
|
43
38
|
return results
|
|
@@ -163,7 +158,8 @@ export async function getRecent(brand, pageName, tabName = 'all', {
|
|
|
163
158
|
* @param {Object} params - Parameters for pagination.
|
|
164
159
|
* @param {number} [params.page=1] - The page number for pagination.
|
|
165
160
|
* @param {number} [params.limit=10] - The maximum number of content items per row.
|
|
166
|
-
* @returns {Promise<Object>} - The fetched content rows.
|
|
161
|
+
* @returns {Promise<Object>} - The fetched content rows with complete Sanity data instead of just content IDs.
|
|
162
|
+
* When contentRowId is provided, returns an object with type, data, and meta properties.
|
|
167
163
|
*
|
|
168
164
|
* @example
|
|
169
165
|
* getContentRows('drumeo', 'lessons', 'Your-Daily-Warmup', {
|
|
@@ -179,7 +175,26 @@ export async function getContentRows(brand, pageName, contentRowId , {
|
|
|
179
175
|
} = {}) {
|
|
180
176
|
const contentRow = contentRowId ? `&content_row_id=${contentRowId}` : ''
|
|
181
177
|
const url = `/api/content/v1/rows?brand=${brand}&page_name=${pageName}${contentRow}&page=${page}&limit=${limit}`;
|
|
182
|
-
|
|
178
|
+
const contentRows = await fetchHandler(url, 'get', null) || [];
|
|
179
|
+
const results = await Promise.all(
|
|
180
|
+
contentRows.map(async (row) => {
|
|
181
|
+
if (row.content.length === 0){
|
|
182
|
+
return { id: row.id, title: row.title, items: [] }
|
|
183
|
+
}
|
|
184
|
+
const data = await fetchByRailContentIds(row.content)
|
|
185
|
+
return { id: row.id, title: row.title, items: data }
|
|
186
|
+
})
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if (contentRowId) {
|
|
190
|
+
return {
|
|
191
|
+
type: TabResponseType.CATALOG,
|
|
192
|
+
data: results[0].items,
|
|
193
|
+
meta: {}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return results
|
|
183
198
|
}
|
|
184
199
|
|
|
185
200
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getProgressStateByIds, getProgressPercentageByIds, getResumeTimeSecondsByIds } from "./contentProgress"
|
|
2
2
|
import { isContentLikedByIds } from "./contentLikes"
|
|
3
|
-
import { fetchLikeCount } from "./railcontent"
|
|
3
|
+
import { fetchLikeCount, fetchLastInteractedChild } from "./railcontent"
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
@@ -15,7 +15,8 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
15
15
|
addIsLiked = false,
|
|
16
16
|
addLikeCount = false,
|
|
17
17
|
addProgressStatus = false,
|
|
18
|
-
addResumeTimeSeconds = false
|
|
18
|
+
addResumeTimeSeconds = false,
|
|
19
|
+
addLastInteractedChild = false,
|
|
19
20
|
} = options
|
|
20
21
|
|
|
21
22
|
const dataParam = lastArg === options ? dataArgs.slice(0, -1) : dataArgs
|
|
@@ -35,13 +36,15 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
35
36
|
|
|
36
37
|
if(ids.length === 0) return false
|
|
37
38
|
|
|
38
|
-
const [progressPercentageData, progressStatusData, isLikedData, resumeTimeData] = await Promise.all([
|
|
39
|
+
const [progressPercentageData, progressStatusData, isLikedData, resumeTimeData, lastInteractedChildData] = await Promise.all([
|
|
39
40
|
addProgressPercentage ? getProgressPercentageByIds(ids) : Promise.resolve(null),
|
|
40
41
|
addProgressStatus ? getProgressStateByIds(ids) : Promise.resolve(null),
|
|
41
42
|
addIsLiked ? isContentLikedByIds(ids) : Promise.resolve(null),
|
|
42
43
|
addResumeTimeSeconds ? getResumeTimeSecondsByIds(ids) : Promise.resolve(null),
|
|
44
|
+
addLastInteractedChild ? fetchLastInteractedChild(ids) : Promise.resolve(null),
|
|
43
45
|
])
|
|
44
|
-
|
|
46
|
+
console.log('ids', ids)
|
|
47
|
+
console.log('lastInteractedChildData', lastInteractedChildData)
|
|
45
48
|
const addContext = async (item) => ({
|
|
46
49
|
...item,
|
|
47
50
|
...(addProgressPercentage ? { progressPercentage: progressPercentageData?.[item.id] } : {}),
|
|
@@ -49,12 +52,17 @@ export async function addContextToContent(dataPromise, ...dataArgs)
|
|
|
49
52
|
...(addIsLiked ? { isLiked: isLikedData?.[item.id] } : {}),
|
|
50
53
|
...(addLikeCount && ids.length === 1 ? { likeCount: await fetchLikeCount(item.id) } : {}),
|
|
51
54
|
...(addResumeTimeSeconds ? { resumeTime: resumeTimeData?.[item.id] } : {}),
|
|
55
|
+
...(addLastInteractedChild ? { lastInteractedChild: lastInteractedChildData?.[item.id] } : {}),
|
|
52
56
|
})
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
if (dataField) {
|
|
58
|
+
data[dataField] = Array.isArray(data[dataField])
|
|
59
|
+
? await Promise.all(data[dataField].map(addContext))
|
|
60
|
+
: await addContext(data[dataField])
|
|
61
|
+
return data
|
|
62
|
+
} else {
|
|
63
|
+
return Array.isArray(data)
|
|
64
|
+
? await Promise.all(data.map(addContext))
|
|
65
|
+
: await addContext(data)
|
|
66
|
+
}
|
|
59
67
|
}
|
|
60
68
|
|
|
@@ -821,6 +821,34 @@ export async function fetchUserPracticeNotes(date) {
|
|
|
821
821
|
return await fetchHandler(url, 'GET', null)
|
|
822
822
|
}
|
|
823
823
|
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Get the id and slug of last interacted child. Only valid for certain content types
|
|
827
|
+
*
|
|
828
|
+
* @async
|
|
829
|
+
* @function fetchLastInteractedChild
|
|
830
|
+
* @param {array} content_ids - Content ids of to get the last interacted child of
|
|
831
|
+
*
|
|
832
|
+
*
|
|
833
|
+
* @returns {Promise<Object>} - keyed object per valid content ids with the child
|
|
834
|
+
*
|
|
835
|
+
* @example
|
|
836
|
+
* try {
|
|
837
|
+
* const response = await fetchLastInteractedChild([191369, 410427]);
|
|
838
|
+
* console.log('child id', response[191369].content_id)
|
|
839
|
+
* console.log('child slug', response[191369].slug)
|
|
840
|
+
* } catch (error) {
|
|
841
|
+
* console.error('Failed to get children', error);
|
|
842
|
+
* }
|
|
843
|
+
*/
|
|
844
|
+
export async function fetchLastInteractedChild(content_ids) {
|
|
845
|
+
const params = new URLSearchParams();
|
|
846
|
+
content_ids.forEach(id => params.append('content_ids[]', id));
|
|
847
|
+
const url = `/api/content/v1/user/last_interacted_child?${params.toString()}`
|
|
848
|
+
return await fetchHandler(url, 'GET', null)
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
|
|
824
852
|
function fetchAbsolute(url, params) {
|
|
825
853
|
if (globalConfig.sessionConfig.authToken) {
|
|
826
854
|
params.headers['Authorization'] = `Bearer ${globalConfig.sessionConfig.authToken}`
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* @module Railcontent-Services
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {globalConfig} from './config.js'
|
|
6
|
-
import {
|
|
5
|
+
import { globalConfig } from './config.js'
|
|
6
|
+
import { HttpClient } from '../infrastructure/http/HttpClient'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Exported functions that are excluded from index generation.
|
|
@@ -36,7 +36,11 @@ export async function fetchSimilarItems(content_id, brand, count = 10) {
|
|
|
36
36
|
}
|
|
37
37
|
const url = `/similar_items/`
|
|
38
38
|
try {
|
|
39
|
-
const
|
|
39
|
+
const httpClient = new HttpClient(
|
|
40
|
+
globalConfig.recommendationsConfig.baseUrl,
|
|
41
|
+
globalConfig.recommendationsConfig.token
|
|
42
|
+
)
|
|
43
|
+
const response = await httpClient.post(url, data)
|
|
40
44
|
// we requested count + 1 then filtered out the extra potential value, so we need slice to the correct size if necessary
|
|
41
45
|
return response['similar_items'].filter((item) => item !== content_id).slice(0, count)
|
|
42
46
|
} catch (error) {
|
|
@@ -71,7 +75,11 @@ export async function rankCategories(brand, categories) {
|
|
|
71
75
|
}
|
|
72
76
|
const url = `/rank_each_list/`
|
|
73
77
|
try {
|
|
74
|
-
const
|
|
78
|
+
const httpClient = new HttpClient(
|
|
79
|
+
globalConfig.recommendationsConfig.baseUrl,
|
|
80
|
+
globalConfig.recommendationsConfig.token
|
|
81
|
+
)
|
|
82
|
+
const response = await httpClient.post(url, data)
|
|
75
83
|
let rankedCategories = {}
|
|
76
84
|
response['ranked_playlists'].forEach(
|
|
77
85
|
(category) =>
|
|
@@ -106,7 +114,11 @@ export async function rankItems(brand, content_ids) {
|
|
|
106
114
|
}
|
|
107
115
|
const url = `/rank_items/`
|
|
108
116
|
try {
|
|
109
|
-
const
|
|
117
|
+
const httpClient = new HttpClient(
|
|
118
|
+
globalConfig.recommendationsConfig.baseUrl,
|
|
119
|
+
globalConfig.recommendationsConfig.token
|
|
120
|
+
)
|
|
121
|
+
const response = await httpClient.post(url, data)
|
|
110
122
|
return response['ranked_content_ids']
|
|
111
123
|
} catch (error) {
|
|
112
124
|
console.error('Fetch error:', error)
|
|
@@ -114,31 +126,15 @@ export async function rankItems(brand, content_ids) {
|
|
|
114
126
|
}
|
|
115
127
|
}
|
|
116
128
|
|
|
117
|
-
export async function recommendations(brand, {section = ''} = {}) {
|
|
129
|
+
export async function recommendations(brand, { section = '' } = {}) {
|
|
118
130
|
section = section.toUpperCase().replace('-', '_')
|
|
119
|
-
const sectionString = section ? `§ion=${section}` : ''
|
|
131
|
+
const sectionString = section ? `§ion=${section}` : ''
|
|
120
132
|
const url = `/api/content/v1/recommendations?brand=${brand}${sectionString}`
|
|
121
133
|
try {
|
|
122
|
-
|
|
123
|
-
return
|
|
124
|
-
url,
|
|
125
|
-
globalConfig.sessionConfig.token,
|
|
126
|
-
globalConfig.baseUrl,
|
|
127
|
-
'get'
|
|
128
|
-
)
|
|
134
|
+
const httpClient = new HttpClient(globalConfig.baseUrl, globalConfig.sessionConfig.token)
|
|
135
|
+
return httpClient.get(url)
|
|
129
136
|
} catch (error) {
|
|
130
137
|
console.error('Fetch error:', error)
|
|
131
138
|
return null
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
|
-
|
|
135
|
-
async function fetchHandler(url, method = 'get', body = null) {
|
|
136
|
-
return fetchJSONHandler(
|
|
137
|
-
url,
|
|
138
|
-
globalConfig.recommendationsConfig.token,
|
|
139
|
-
globalConfig.recommendationsConfig.baseUrl,
|
|
140
|
-
method,
|
|
141
|
-
null,
|
|
142
|
-
body
|
|
143
|
-
)
|
|
144
|
-
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module Interests
|
|
3
|
+
*/
|
|
4
|
+
import { globalConfig } from '../config.js'
|
|
5
|
+
import { fetchHandler } from '../railcontent.js'
|
|
6
|
+
import './types.js'
|
|
7
|
+
|
|
8
|
+
const baseUrl = `/api/user-management-system`
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {number} [userId=globalConfig.sessionConfig.userId] - The user ID
|
|
12
|
+
* @returns {Promise<Array<number>>} - The list of interests
|
|
13
|
+
*/
|
|
14
|
+
export async function fetchInterests(userId = globalConfig.sessionConfig.userId) {
|
|
15
|
+
const url = `${baseUrl}/v1/users/${userId}/interests`
|
|
16
|
+
return fetchHandler(url, 'get')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {number} contentId
|
|
21
|
+
* @returns {Promise<any>}
|
|
22
|
+
*/
|
|
23
|
+
export async function markContentAsInterested(contentId) {
|
|
24
|
+
if (!contentId) {
|
|
25
|
+
throw new Error('contentId is required')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const url = `${baseUrl}/v1/users/interests/${contentId}`
|
|
29
|
+
return fetchHandler(url, 'post')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {number} contentId
|
|
34
|
+
* @returns {Promise<any>}
|
|
35
|
+
*/
|
|
36
|
+
export async function removeContentAsInterested(contentId) {
|
|
37
|
+
if (!contentId) {
|
|
38
|
+
throw new Error('contentId is required')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const url = `${baseUrl}/v1/users/interests/${contentId}`
|
|
42
|
+
return fetchHandler(url, 'delete')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {number} [userId=globalConfig.sessionConfig.userId] - The user ID
|
|
47
|
+
* @returns {Promise<Array<number>>} - The list of content the user is not interested in
|
|
48
|
+
*/
|
|
49
|
+
export async function fetchUninterests(userId = globalConfig.sessionConfig.userId) {
|
|
50
|
+
const url = `${baseUrl}/v1/users/${userId}/uninterests`
|
|
51
|
+
return fetchHandler(url, 'get')
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {number} contentId
|
|
56
|
+
* @returns {Promise<any>}
|
|
57
|
+
*/
|
|
58
|
+
export async function markContentAsNotInterested(contentId) {
|
|
59
|
+
if (!contentId) {
|
|
60
|
+
throw new Error('contentId is required')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const url = `${baseUrl}/v1/users/uninterests/${contentId}`
|
|
64
|
+
return fetchHandler(url, 'post')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {number} contentId
|
|
69
|
+
* @returns {Promise<any>}
|
|
70
|
+
*/
|
|
71
|
+
export async function removeContentAsNotInterested(contentId) {
|
|
72
|
+
if (!contentId) {
|
|
73
|
+
throw new Error('contentId is required')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const url = `${baseUrl}/v1/users/uninterests/${contentId}`
|
|
77
|
+
return fetchHandler(url, 'delete')
|
|
78
|
+
}
|
|
@@ -135,6 +135,22 @@ export async function getUserMonthlyStats( params = {}) {
|
|
|
135
135
|
today.setHours(0, 0, 0, 0);
|
|
136
136
|
|
|
137
137
|
let startOfGrid = getMonday(firstDayOfMonth)
|
|
138
|
+
|
|
139
|
+
let previousWeekStart = new Date(startOfGrid)
|
|
140
|
+
previousWeekStart.setDate(previousWeekStart.getDate() - 7)
|
|
141
|
+
|
|
142
|
+
let previousWeekEnd = new Date(startOfGrid)
|
|
143
|
+
previousWeekEnd.setDate(previousWeekEnd.getDate() - 1)
|
|
144
|
+
|
|
145
|
+
let hadStreakBeforeMonth = false
|
|
146
|
+
for (let d = new Date(previousWeekStart); d <= previousWeekEnd; d.setDate(d.getDate() + 1)) {
|
|
147
|
+
let dayKey = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
|
|
148
|
+
if (practices[dayKey]) {
|
|
149
|
+
hadStreakBeforeMonth = true
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
138
154
|
let endOfMonth = new Date(year, month + 1, 0)
|
|
139
155
|
while (endOfMonth.getDay() !== 0) {
|
|
140
156
|
endOfMonth.setDate(endOfMonth.getDate() + 1)
|
|
@@ -181,6 +197,13 @@ export async function getUserMonthlyStats( params = {}) {
|
|
|
181
197
|
})
|
|
182
198
|
}
|
|
183
199
|
|
|
200
|
+
if (hadStreakBeforeMonth) {
|
|
201
|
+
const firstWeekKey = getWeekNumber(startOfGrid)
|
|
202
|
+
if (weeklyStats[firstWeekKey]) {
|
|
203
|
+
weeklyStats[firstWeekKey].continueStreak = true
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
184
207
|
let filteredPractices = Object.keys(practices)
|
|
185
208
|
.filter((date) => new Date(date) <= endOfMonth)
|
|
186
209
|
.reduce((obj, key) => {
|