musora-content-services 2.71.0 → 2.72.1
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/CHANGELOG.md +14 -0
- package/docs/ContentOrganization.html +2 -2
- package/docs/Forums.html +2 -2
- package/docs/Gamification.html +2 -2
- package/docs/TestUser.html +3 -3
- package/docs/UserManagementSystem.html +2 -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_guided-courses.ts.html +2 -2
- package/docs/content-org_learning-paths.ts.html +94 -5
- package/docs/content-org_playlists-types.js.html +2 -2
- package/docs/content-org_playlists.js.html +2 -2
- package/docs/content.js.html +2 -2
- package/docs/forums_categories.ts.html +2 -2
- package/docs/forums_forums.ts.html +41 -41
- package/docs/forums_posts.ts.html +34 -27
- package/docs/forums_threads.ts.html +2 -2
- package/docs/gamification_awards.ts.html +2 -2
- package/docs/gamification_gamification.js.html +2 -2
- package/docs/global.html +3 -3
- package/docs/index.html +2 -2
- package/docs/liveTesting.ts.html +4 -3
- package/docs/module-Accounts.html +2 -2
- package/docs/module-Awards.html +2 -2
- package/docs/module-Config.html +2 -2
- package/docs/module-Content-Services-V2.html +2 -2
- package/docs/module-Forums.html +26 -26
- package/docs/module-GuidedCourses.html +2 -1198
- package/docs/module-Interests.html +2 -2
- package/docs/module-LearningPaths.html +1077 -0
- package/docs/module-Onboarding.html +2 -2
- package/docs/module-Payments.html +2 -2
- package/docs/module-Permissions.html +2 -2
- package/docs/module-Playlists.html +2 -2
- package/docs/module-Railcontent-Services.html +2 -2
- package/docs/module-Sanity-Services.html +32 -32
- package/docs/module-Sessions.html +2 -2
- package/docs/module-UserActivity.html +2 -2
- package/docs/module-UserChat.html +2 -2
- package/docs/module-UserManagement.html +2 -2
- package/docs/module-UserMemberships.html +2 -2
- package/docs/module-UserNotifications.html +2 -2
- package/docs/module-UserProfile.html +2 -2
- package/docs/railcontent.js.html +2 -2
- package/docs/sanity.js.html +5 -7
- package/docs/userActivity.js.html +2 -2
- package/docs/user_account.ts.html +2 -2
- package/docs/user_chat.js.html +2 -2
- package/docs/user_interests.js.html +2 -2
- package/docs/user_management.js.html +2 -2
- package/docs/user_memberships.ts.html +2 -2
- package/docs/user_notifications.js.html +2 -2
- package/docs/user_onboarding.ts.html +2 -2
- package/docs/user_payments.ts.html +2 -2
- package/docs/user_permissions.js.html +2 -2
- package/docs/user_profile.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 +2 -2
- package/package.json +1 -1
- package/src/contentTypeConfig.js +162 -105
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/lib/httpHelper.js +5 -0
- package/src/services/content-org/learning-paths.ts +92 -3
- package/src/services/forums/forums.ts +38 -38
- package/src/services/forums/posts.ts +32 -25
- package/test/initializeTests.js +21 -36
- package/test/learningPaths.test.js +19 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {HttpClient} from
|
|
2
|
-
import {PaginatedResponse} from
|
|
3
|
-
import {ForumThread} from
|
|
1
|
+
import { HttpClient } from '../../infrastructure/http/HttpClient'
|
|
2
|
+
import { PaginatedResponse } from '../api/types'
|
|
3
|
+
import { ForumThread } from './types'
|
|
4
4
|
import { globalConfig } from '../config.js'
|
|
5
5
|
|
|
6
6
|
const baseUrl = `/api/forums`
|
|
@@ -13,39 +13,41 @@ const baseUrl = `/api/forums`
|
|
|
13
13
|
|
|
14
14
|
export async function getActiveDiscussions(brand: string, { page = 1, limit = 10 } = {}) {
|
|
15
15
|
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
16
|
-
const latestDiscussions = await httpClient.get<PaginatedResponse<ForumThread>>(
|
|
16
|
+
const latestDiscussions = await httpClient.get<PaginatedResponse<ForumThread>>(
|
|
17
|
+
`${baseUrl}/v1/threads/latest?brand=${brand}&page=${page}&limit=${limit}`
|
|
18
|
+
)
|
|
17
19
|
|
|
18
20
|
// Transform the response
|
|
19
|
-
return transformLatestDiscussions(latestDiscussions)
|
|
21
|
+
return transformLatestDiscussions(latestDiscussions)
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
interface TransformedDiscussion {
|
|
23
|
-
id: number
|
|
24
|
-
url: string
|
|
25
|
-
title: string
|
|
26
|
-
post: string
|
|
25
|
+
id: number
|
|
26
|
+
url: string
|
|
27
|
+
title: string
|
|
28
|
+
post: string
|
|
27
29
|
author: {
|
|
28
|
-
id: number
|
|
29
|
-
name: string
|
|
30
|
-
avatar: string
|
|
31
|
-
}
|
|
30
|
+
id: number
|
|
31
|
+
name: string
|
|
32
|
+
avatar: string
|
|
33
|
+
}
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
interface TransformedResponse {
|
|
35
|
-
data: TransformedDiscussion[]
|
|
36
|
-
meta: PaginatedResponse<ForumThread>['meta']
|
|
37
|
+
data: TransformedDiscussion[]
|
|
38
|
+
meta: PaginatedResponse<ForumThread>['meta']
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
// Helper function to strip HTML tags and decode entities
|
|
40
42
|
function stripHtml(html: string): string {
|
|
41
43
|
// Remove blockquotes and their content first
|
|
42
|
-
let filtered = html.replace(/<blockquote[^>]
|
|
44
|
+
let filtered = html.replace(/<blockquote[^>]*>[\s\S]*?<\/blockquote>/gi, '')
|
|
43
45
|
|
|
44
46
|
// Remove iframes and their content
|
|
45
|
-
filtered = filtered.replace(/<iframe[^>]
|
|
47
|
+
filtered = filtered.replace(/<iframe[^>]*>[\s\S]*?<\/iframe>/gi, '')
|
|
46
48
|
|
|
47
49
|
// Remove remaining HTML tags
|
|
48
|
-
const withoutTags = filtered.replace(/<[^>]*>/g, '')
|
|
50
|
+
const withoutTags = filtered.replace(/<[^>]*>/g, '')
|
|
49
51
|
|
|
50
52
|
// Decode common HTML entities
|
|
51
53
|
const decoded = withoutTags
|
|
@@ -54,35 +56,33 @@ function stripHtml(html: string): string {
|
|
|
54
56
|
.replace(/>/g, '>')
|
|
55
57
|
.replace(/"/g, '"')
|
|
56
58
|
.replace(/'/g, "'")
|
|
57
|
-
.replace(/ /g, ' ')
|
|
59
|
+
.replace(/ /g, ' ')
|
|
58
60
|
|
|
59
|
-
return decoded.trim()
|
|
61
|
+
return decoded.trim()
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
function transformLatestDiscussions(
|
|
63
|
-
response: PaginatedResponse<ForumThread>
|
|
64
|
-
): TransformedResponse {
|
|
64
|
+
function transformLatestDiscussions(response: PaginatedResponse<ForumThread>): TransformedResponse {
|
|
65
65
|
const transformedData = response.data
|
|
66
66
|
.filter((thread) => thread.last_post && thread.author) // Filter out null posts/authors
|
|
67
67
|
.map((thread) => {
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
const postContent = stripHtml(thread.last_post!.content)
|
|
69
|
+
const postId = thread.last_post!.id
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
71
|
+
return {
|
|
72
|
+
id: thread.id,
|
|
73
|
+
url: `forums/post/${postId}`,
|
|
74
|
+
title: thread.title,
|
|
75
|
+
post: postContent,
|
|
76
|
+
author: {
|
|
77
|
+
id: thread.author!.id,
|
|
78
|
+
name: thread.author!.display_name,
|
|
79
|
+
avatar: thread.author!.profile_picture_url || '',
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
83
|
|
|
84
84
|
return {
|
|
85
85
|
data: transformedData,
|
|
86
86
|
meta: response.meta,
|
|
87
|
-
}
|
|
87
|
+
}
|
|
88
88
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { HttpClient } from '../../infrastructure/http/HttpClient'
|
|
5
5
|
import { globalConfig } from '../config.js'
|
|
6
|
-
import {ForumPost} from './types'
|
|
6
|
+
import { ForumPost } from './types'
|
|
7
7
|
import { PaginatedResponse } from '../api/types'
|
|
8
8
|
|
|
9
9
|
const baseUrl = `/api/forums`
|
|
@@ -21,10 +21,7 @@ export interface CreatePostParams {
|
|
|
21
21
|
* @returns {Promise<ForumPost>} - A promise that resolves to the created post.
|
|
22
22
|
* @throws {HttpError} - If the request fails.
|
|
23
23
|
*/
|
|
24
|
-
export async function createPost(
|
|
25
|
-
threadId: number,
|
|
26
|
-
params: CreatePostParams
|
|
27
|
-
): Promise<ForumPost> {
|
|
24
|
+
export async function createPost(threadId: number, params: CreatePostParams): Promise<ForumPost> {
|
|
28
25
|
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
29
26
|
return httpClient.post<ForumPost>(`${baseUrl}/v1/threads/${threadId}/posts`, params)
|
|
30
27
|
}
|
|
@@ -37,18 +34,14 @@ export async function createPost(
|
|
|
37
34
|
* @returns {Promise<ForumPost>} - A promise that resolves to the updated post.
|
|
38
35
|
* @throws {HttpError} - If the request fails.
|
|
39
36
|
*/
|
|
40
|
-
export async function updatePost(
|
|
41
|
-
postId: number,
|
|
42
|
-
params: CreatePostParams
|
|
43
|
-
): Promise<ForumPost> {
|
|
37
|
+
export async function updatePost(postId: number, params: CreatePostParams): Promise<ForumPost> {
|
|
44
38
|
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
45
39
|
return httpClient.put<ForumPost>(`${baseUrl}/v1/posts/${postId}`, params)
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
|
|
49
42
|
export interface FetchPostParams {
|
|
50
|
-
page?: number
|
|
51
|
-
limit?: number
|
|
43
|
+
page?: number
|
|
44
|
+
limit?: number
|
|
52
45
|
/** Sort order: "-published_on" (default), "published_on", or "mine". */
|
|
53
46
|
sort?: '-published_on' | string
|
|
54
47
|
}
|
|
@@ -67,9 +60,14 @@ export async function fetchPosts(
|
|
|
67
60
|
params: FetchPostParams = {}
|
|
68
61
|
): Promise<PaginatedResponse<ForumPost>> {
|
|
69
62
|
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
70
|
-
const queryObj: Record<string, string> = {
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
const queryObj: Record<string, string> = {
|
|
64
|
+
brand,
|
|
65
|
+
...Object.fromEntries(
|
|
66
|
+
Object.entries(params)
|
|
67
|
+
.filter(([_, v]) => v !== undefined && v !== null)
|
|
68
|
+
.map(([k, v]) => [k, String(v)])
|
|
69
|
+
),
|
|
70
|
+
}
|
|
73
71
|
const query = new URLSearchParams(queryObj).toString()
|
|
74
72
|
|
|
75
73
|
const url = `${baseUrl}/v1/threads/${threadId}/posts?${query}`
|
|
@@ -126,7 +124,7 @@ export async function deletePost(postId: number, brand: string): Promise<void> {
|
|
|
126
124
|
export async function fetchCommunityGuidelines(brand: string): Promise<ForumPost[]> {
|
|
127
125
|
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
128
126
|
const url = `${baseUrl}/v1/rules?brand=${brand}`
|
|
129
|
-
return httpClient.get<ForumPost[]>(url)
|
|
127
|
+
return httpClient.get<ForumPost[]>(url)
|
|
130
128
|
}
|
|
131
129
|
|
|
132
130
|
export interface SearchParams {
|
|
@@ -134,8 +132,8 @@ export interface SearchParams {
|
|
|
134
132
|
limit?: number,
|
|
135
133
|
category_id?: number,
|
|
136
134
|
/** Sort order: "-published_on" (default), "published_on", or "mine". */
|
|
137
|
-
sort?: '-published_on' | string
|
|
138
|
-
term
|
|
135
|
+
sort?: '-published_on' | string
|
|
136
|
+
term?: string
|
|
139
137
|
}
|
|
140
138
|
|
|
141
139
|
/**
|
|
@@ -151,9 +149,14 @@ export async function search(
|
|
|
151
149
|
params: SearchParams
|
|
152
150
|
): Promise<PaginatedResponse<ForumPost>> {
|
|
153
151
|
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
154
|
-
const queryObj: Record<string, string> = {
|
|
155
|
-
|
|
156
|
-
|
|
152
|
+
const queryObj: Record<string, string> = {
|
|
153
|
+
brand,
|
|
154
|
+
...Object.fromEntries(
|
|
155
|
+
Object.entries(params)
|
|
156
|
+
.filter(([_, v]) => v !== undefined && v !== null)
|
|
157
|
+
.map(([k, v]) => [k, String(v)])
|
|
158
|
+
),
|
|
159
|
+
}
|
|
157
160
|
const query = new URLSearchParams(queryObj).toString()
|
|
158
161
|
|
|
159
162
|
const url = `${baseUrl}/v1/search?${query}`
|
|
@@ -175,12 +178,16 @@ export async function jumpToPost(
|
|
|
175
178
|
params: FetchPostParams = {}
|
|
176
179
|
): Promise<PaginatedResponse<ForumPost>> {
|
|
177
180
|
const httpClient = new HttpClient(globalConfig.baseUrl)
|
|
178
|
-
const queryObj: Record<string, string> = {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
const queryObj: Record<string, string> = {
|
|
182
|
+
brand,
|
|
183
|
+
...Object.fromEntries(
|
|
184
|
+
Object.entries(params)
|
|
185
|
+
.filter(([_, v]) => v !== undefined && v !== null)
|
|
186
|
+
.map(([k, v]) => [k, String(v)])
|
|
187
|
+
),
|
|
188
|
+
}
|
|
181
189
|
const query = new URLSearchParams(queryObj).toString()
|
|
182
190
|
|
|
183
191
|
const url = `${baseUrl}/v1/posts/${postId}/jump?${query}`
|
|
184
192
|
return httpClient.get<PaginatedResponse<ForumPost>>(url)
|
|
185
193
|
}
|
|
186
|
-
|
package/test/initializeTests.js
CHANGED
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
import { globalConfig, initializeService } from '../src'
|
|
2
2
|
import { LocalStorageMock } from './localStorageMock'
|
|
3
|
-
|
|
4
3
|
const railContentModule = require('../src/services/railcontent.js')
|
|
5
4
|
let token = null
|
|
6
5
|
let userId = process.env.RAILCONTENT_USER_ID ?? null
|
|
7
6
|
|
|
8
7
|
export async function initializeTestService(useLive = false, isAdmin = false) {
|
|
8
|
+
let token, userId
|
|
9
9
|
if (useLive && !token && process.env.RAILCONTENT_BASE_URL) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const baseUrl = `${process.env.RAILCONTENT_BASE_URL}/api/user-management-system`
|
|
11
|
+
const response = await fetch(`${baseUrl}/v1/sessions`, {
|
|
12
|
+
method: 'POST',
|
|
13
|
+
headers: {
|
|
14
|
+
'X-Client-Platform': 'mobile',
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
Authorization: null,
|
|
17
|
+
},
|
|
18
|
+
body: JSON.stringify({
|
|
19
|
+
email: process.env.RAILCONTENT_EMAIL,
|
|
20
|
+
password: process.env.RAILCONTENT_PASSWORD,
|
|
21
|
+
device_name: 'test',
|
|
22
|
+
device_token: '',
|
|
23
|
+
platform: '',
|
|
24
|
+
}),
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
let data = await response.json() // Parse the JSON body
|
|
14
28
|
token = data['token']
|
|
15
|
-
userId = data['
|
|
29
|
+
userId = data['user']['id']
|
|
16
30
|
}
|
|
17
31
|
|
|
18
32
|
const config = {
|
|
@@ -25,42 +39,13 @@ export async function initializeTestService(useLive = false, isAdmin = false) {
|
|
|
25
39
|
debug: process.env.DEBUG === 'true' || false,
|
|
26
40
|
useDummyRailContentMethods: true,
|
|
27
41
|
},
|
|
28
|
-
sessionConfig: {
|
|
29
|
-
token: token,
|
|
30
|
-
userId: userId,
|
|
31
|
-
authToken: token,
|
|
32
|
-
},
|
|
42
|
+
sessionConfig: { token: token, userId: userId, authToken: token },
|
|
33
43
|
baseUrl: process.env.RAILCONTENT_BASE_URL,
|
|
34
44
|
localStorage: new LocalStorageMock(),
|
|
35
45
|
isMA: true,
|
|
36
46
|
}
|
|
37
47
|
initializeService(config)
|
|
38
|
-
|
|
39
48
|
let mock = jest.spyOn(railContentModule, 'fetchUserPermissionsData')
|
|
40
49
|
let testData = { permissions: [78, 91, 92], isAdmin: isAdmin }
|
|
41
50
|
mock.mockImplementation(() => testData)
|
|
42
51
|
}
|
|
43
|
-
|
|
44
|
-
async function fetchLoginToken(email, password) {
|
|
45
|
-
try {
|
|
46
|
-
const url = `${process.env.RAILCONTENT_BASE_URL}/user-management-system/login/token`
|
|
47
|
-
const response = await fetch(url, {
|
|
48
|
-
method: 'POST',
|
|
49
|
-
headers: {
|
|
50
|
-
Accept: 'application/json',
|
|
51
|
-
'Content-Type': 'application/json',
|
|
52
|
-
},
|
|
53
|
-
body: JSON.stringify({ email: email, password: password, device_name: 'test' }),
|
|
54
|
-
})
|
|
55
|
-
if (response.ok) {
|
|
56
|
-
let data = await response.json()
|
|
57
|
-
return { token: data.token, userId: data.user.id }
|
|
58
|
-
} else {
|
|
59
|
-
console.log('fetch error:', response.status)
|
|
60
|
-
console.log(response)
|
|
61
|
-
}
|
|
62
|
-
} catch (error) {
|
|
63
|
-
console.error('Fetch error:', error)
|
|
64
|
-
}
|
|
65
|
-
return null
|
|
66
|
-
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { initializeTestService } from './initializeTests.js'
|
|
2
|
+
import { fetchLearningPathLessons } from '../src/services/content-org/learning-paths.ts'
|
|
3
|
+
import { fetchByRailContentId } from '../src/services/sanity.js'
|
|
4
|
+
import { contentStatusCompleted } from '../src/services/contentProgress.js'
|
|
5
|
+
describe('learning-paths', function () {
|
|
6
|
+
beforeEach(async () => {
|
|
7
|
+
await initializeTestService(true)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
test('getLearningPathsV2Test', async () => {
|
|
11
|
+
const results = await fetchByRailContentId(417140, 'learning-path-v2')
|
|
12
|
+
})
|
|
13
|
+
test('getlearningPathLessonsTestNew', async () => {
|
|
14
|
+
await contentStatusCompleted(417105)
|
|
15
|
+
const userDate = new Date('2025-10-31')
|
|
16
|
+
const results = await fetchLearningPathLessons(422533, 'drumeo', userDate)
|
|
17
|
+
console.log(results)
|
|
18
|
+
})
|
|
19
|
+
})
|