musora-content-services 1.3.21 → 2.0.5

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.
Files changed (96) hide show
  1. package/.editorconfig +16 -0
  2. package/.github/workflows/node.js.yml +0 -0
  3. package/.prettierignore +0 -0
  4. package/.prettierrc +0 -0
  5. package/CHANGELOG.md +4 -4
  6. package/README.md +0 -0
  7. package/babel.config.cjs +0 -0
  8. package/docs/Content-Organization.html +245 -0
  9. package/docs/Playlists.html +192 -0
  10. package/docs/config.js.html +14 -5
  11. package/docs/content-org_playlists-types.js.html +109 -0
  12. package/docs/content-org_playlists.js.html +194 -0
  13. package/docs/content-org_types.js.html +112 -0
  14. package/docs/content.js.html +443 -0
  15. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  16. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  17. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  18. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  19. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  20. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  21. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  22. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  23. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  24. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
  25. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  26. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  27. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  28. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  29. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
  30. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  31. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  32. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  33. package/docs/global.html +3878 -0
  34. package/docs/index.html +2 -2
  35. package/docs/module-Config.html +60 -7
  36. package/docs/module-Content-Organization-Playlists.html +194 -0
  37. package/docs/module-Content-Organization.html +976 -0
  38. package/docs/module-Content-Services-V2.html +2433 -0
  39. package/docs/module-Playlists.html +969 -0
  40. package/docs/module-Railcontent-Services.html +3052 -1991
  41. package/docs/module-Sanity-Services.html +57 -43
  42. package/docs/module-Session-Management.html +575 -0
  43. package/docs/module-User-Permissions.html +406 -0
  44. package/docs/module-playlists.html +1878 -0
  45. package/docs/module-playlists_.html +108 -0
  46. package/docs/railcontent.js.html +149 -112
  47. package/docs/sanity.js.html +297 -109
  48. package/docs/scripts/collapse.js +0 -0
  49. package/docs/scripts/commonNav.js +0 -0
  50. package/docs/scripts/linenumber.js +0 -0
  51. package/docs/scripts/nav.js +0 -0
  52. package/docs/scripts/polyfill.js +0 -0
  53. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
  54. package/docs/scripts/prettify/lang-css.js +0 -0
  55. package/docs/scripts/prettify/prettify.js +0 -0
  56. package/docs/scripts/search.js +0 -0
  57. package/docs/styles/jsdoc.css +0 -0
  58. package/docs/styles/prettify.css +0 -0
  59. package/docs/user_permissions.js.html +110 -0
  60. package/docs/user_sessions.js.html +139 -0
  61. package/docs/user_types.js.html +188 -0
  62. package/jest.config.js +0 -0
  63. package/jsdoc.json +3 -0
  64. package/package.json +1 -1
  65. package/publish.sh +2 -2
  66. package/src/contentMetaData.js +307 -1088
  67. package/src/contentTypeConfig.js +108 -4
  68. package/src/filterBuilder.js +6 -6
  69. package/src/index.d.ts +67 -9
  70. package/src/index.js +67 -9
  71. package/src/{services → lib}/lastUpdated.js +17 -1
  72. package/src/services/content-org/playlists-types.js +37 -0
  73. package/src/services/content-org/playlists.js +122 -0
  74. package/src/services/content.js +371 -0
  75. package/src/services/contentLikes.js +0 -0
  76. package/src/services/contentProgress.js +0 -0
  77. package/src/services/forum.js +57 -0
  78. package/src/services/railcontent.js +122 -122
  79. package/src/services/recommendations.js +19 -0
  80. package/src/services/sanity.js +278 -104
  81. package/src/services/{userPermissions.js → user/permissions.js} +16 -2
  82. package/src/services/user/sessions.js +67 -0
  83. package/src/services/user/types.js +116 -0
  84. package/src/services/userActivity.js +32 -0
  85. package/test/content.test.js +116 -0
  86. package/test/contentProgress.test.js +83 -5
  87. package/test/forum.test.js +18 -0
  88. package/test/initializeTests.js +6 -1
  89. package/test/{lastUpdated.test.js → lib/lastUpdated.test.js} +2 -5
  90. package/test/live/contentProgressLive.test.js +0 -0
  91. package/test/live/railcontentLive.test.js +0 -0
  92. package/test/localStorageMock.js +0 -0
  93. package/test/log.js +0 -0
  94. package/test/sanityQueryService.test.js +66 -18
  95. package/test/{userPermissions.test.js → user/permissions.test.js} +3 -3
  96. package/tools/generate-index.cjs +16 -3
@@ -0,0 +1,122 @@
1
+ /**
2
+ * @namespace Content-Organization
3
+ * @property {module:Playlists} Playlists
4
+ */
5
+ /**
6
+ * @module Playlists
7
+ */
8
+ import { fetchHandler } from '../railcontent'
9
+ import { globalConfig } from './config.js'
10
+ import './playlists-types'
11
+
12
+ /**
13
+ * Exported functions that are excluded from index generation.
14
+ *
15
+ * @type {string[]}
16
+ */
17
+ const excludeFromGeneratedIndex = []
18
+
19
+ const BASE_URL = `${globalConfig.railcontentConfig.baseUrl}/api/content-org`
20
+
21
+ /**
22
+ * Fetches user playlists for a specific brand.
23
+ *
24
+ * Allows optional pagination, sorting, and search parameters to control the result set.
25
+ *
26
+ * @param {string} brand - The brand identifier for which playlists are being fetched.
27
+ * @param {number} [params.limit=10] - The maximum number of playlists to return per page (default is 10).
28
+ * @param {number} [params.page=1] - The page number for pagination.
29
+ * @param {string} [params.sort='-created_at'] - The sorting order for the playlists (default is by created_at in descending order).
30
+ * @param {string} [params.searchTerm] - A search term to filter playlists by name.
31
+ * @param {int|string} [params.content_id] - If content_id exists, the endpoint checks in each playlist if we have the content in the items.
32
+ *
33
+ * @returns {Promise<Object|null>} - A promise that resolves to the response from the API, containing the user playlists data.
34
+ *
35
+ * @example
36
+ * fetchUserPlaylists('drumeo', { page: 1, sort: 'name', searchTerm: 'rock' })
37
+ * .then(playlists => console.log(playlists))
38
+ * .catch(error => console.error(error));
39
+ */
40
+ export async function fetchUserPlaylists(
41
+ brand,
42
+ { page, limit, sort, searchTerm, content_id, categories } = {}
43
+ ) {
44
+ let url
45
+ const limitString = limit ? `&limit=${limit}` : ''
46
+ const pageString = page ? `&page=${page}` : ''
47
+ const sortString = sort ? `&sort=${sort}` : ''
48
+ const searchFilter = searchTerm ? `&term=${searchTerm}` : ''
49
+ const content = content_id ? `&content_id=${content_id}` : ''
50
+ const categoryString =
51
+ categories && categories.length ? categories.map((cat) => `categories[]=${cat}`).join('&') : ''
52
+ url = `${BASE_URL}/v1/user/playlists?brand=${brand}${limitString}${pageString}${sortString}${searchFilter}${content}${categoryString ? `&${categoryString}` : ''}`
53
+ return await fetchHandler(url)
54
+ }
55
+
56
+ /**
57
+ * Creates a new user playlist by sending a POST request with playlist data to the API.
58
+ *
59
+ * This function calls the `/playlists/playlist` endpoint, where the server validates the incoming data and associates
60
+ * the new playlist with the authenticated user. The `name` field is required, while other fields are optional.
61
+ *
62
+ * @param {CreatePlaylistDTO} playlistData - An object containing data to create the playlist. The fields include:
63
+ * - `name` (string): The name of the new playlist (required, max 255 characters).
64
+ * - `description` (string): A description of the playlist (optional, max 1000 characters).
65
+ * - `category` (string): The category of the playlist.
66
+ * - `thumbnail_url` (string): The URL of the playlist thumbnail (optional, must be a valid URL).
67
+ * - `private` (boolean): Whether the playlist is private (optional, defaults to true).
68
+ * - `brand` (string): Brand identifier for the playlist.
69
+ *
70
+ * @returns {Promise<Playlist>} - A promise that resolves to the created playlist data if successful, or an error response if validation fails.
71
+ *
72
+ * The server response includes:
73
+ * - `message`: Success message indicating playlist creation (e.g., "Playlist created successfully").
74
+ * - `playlist`: The data for the created playlist, including the `user_id` of the authenticated user.
75
+ *
76
+ * @example
77
+ * createPlaylist({ name: "My Playlist", description: "A cool playlist", private: true })
78
+ * .then(response => console.log(response.message))
79
+ * .catch(error => console.error('Error creating playlist:', error));
80
+ */
81
+ export async function createPlaylist(playlistData) {
82
+ const url = `${BASE_URL}/v1/user/playlists`
83
+ return await fetchHandler(url, 'POST', null, playlistData)
84
+ }
85
+
86
+ /**
87
+ * Adds an item to one or more playlists by making a POST request to the `/playlists/add-item` endpoint.
88
+ *
89
+ * @param {AddItemToPlaylistDTO} payload - The request payload containing necessary parameters.
90
+ *
91
+ * @returns {Promise<Object|null>} - A promise that resolves to an object with the response data, including:
92
+ * - `success` (boolean): Indicates if the items were added successfully (`true` on success).
93
+ * - `limit_excedeed` (Array): A list of playlists where the item limit was exceeded, if any.
94
+ * - `successful` (Array): A list of successfully added items (empty if none).
95
+ *
96
+ * Resolves to `null` if the request fails.
97
+ * @throws {Error} - Throws an error if the request encounters issues during the operation.
98
+ *
99
+ * @example
100
+ * const payload = {
101
+ * content_id: 123,
102
+ * playlist_id: [1, 2, 3],
103
+ * import_all_assignments: true
104
+ * };
105
+ *
106
+ * addItemToPlaylist(payload)
107
+ * .then(response => {
108
+ * if (response?.success) {
109
+ * console.log("Item(s) added to playlist successfully");
110
+ * }
111
+ * if (response?.limit_excedeed) {
112
+ * console.warn("Some playlists exceeded the item limit:", response.limit_excedeed);
113
+ * }
114
+ * })
115
+ * .catch(error => {
116
+ * console.error("Error adding item to playlist:", error);
117
+ * });
118
+ */
119
+ export async function addItemToPlaylist(payload) {
120
+ const url = `${BASE_URL}/v1/user/playlists/items`
121
+ return await fetchHandler(url, 'POST', null, payload)
122
+ }
@@ -0,0 +1,371 @@
1
+ /**
2
+ * @module Content-Services-V2
3
+ */
4
+
5
+ import {
6
+ fetchAll,
7
+ fetchByRailContentIds,
8
+ fetchMetadata,
9
+ fetchRecent,
10
+ fetchTabData,
11
+ fetchNewReleases,
12
+ fetchUpcomingEvents,
13
+ fetchScheduledReleases,
14
+ fetchReturning,
15
+ fetchLeaving, fetchScheduledAndNewReleases
16
+ } from './sanity.js'
17
+ import {TabResponseType, Tabs, capitalizeFirstLetter} from '../contentMetaData.js'
18
+ import {getAllStartedOrCompleted} from "./contentProgress";
19
+ import {fetchHandler} from "./railcontent";
20
+ import {recommendations} from "./recommendations";
21
+
22
+ export async function getLessonContentRows (brand='drumeo', pageName = 'lessons') {
23
+ let recentContentIds = await fetchRecent(brand, pageName, { progress: 'recent' });
24
+ recentContentIds = recentContentIds.map(item => item.id);
25
+
26
+ let contentRows = await getContentRows(brand, pageName);
27
+ contentRows = Array.isArray(contentRows) ? contentRows : [];
28
+ contentRows.unshift({
29
+ id: 'recent',
30
+ title: 'Recent ' + capitalizeFirstLetter(pageName),
31
+ content: recentContentIds || []
32
+ });
33
+
34
+ const results = await Promise.all(
35
+ contentRows.map(async (row) => {
36
+ if (row.content.length == 0){
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 }
41
+ })
42
+ )
43
+ return results
44
+ }
45
+
46
+ /**
47
+ * Get data that should be displayed for a specific tab with pagination
48
+ * @param {string} brand - The brand for which to fetch data.
49
+ * @param {string} pageName - The page name (e.g., 'lessons', 'songs','challenges).
50
+ * @param {string} tabName - The name for the selected tab. Should be same name received from fetchMetadata (e.g., 'Individuals', 'Collections','For You').
51
+ * @param {Object} params - Parameters for pagination, sorting, and filter.
52
+ * @param {number} [params.page=1] - The page number for pagination.
53
+ * @param {number} [params.limit=10] - The number of items per page.
54
+ * @param {string} [params.sort="-published_on"] - The field to sort the data by.
55
+ * @param {Array<string>} [params.selectedFilters=[]] - The selected filter.
56
+ * @returns {Promise<Object|null>} - The fetched content data or null if not found.
57
+ *
58
+ * @example
59
+ * getTabResults('drumeo', 'lessons','Singles', {
60
+ * page: 2,
61
+ * limit: 20,
62
+ * sort: '-popularity',
63
+ * includedFields: ['difficulty,Intermediate'],
64
+ * })
65
+ * .then(content => console.log(content))
66
+ * .catch(error => console.error(error));
67
+ */
68
+ export async function getTabResults(brand, pageName, tabName, {
69
+ page = 1,
70
+ limit = 10,
71
+ sort = 'recommended',
72
+ selectedFilters = []
73
+ } = {}) {
74
+
75
+ // Extract and handle 'progress' filter separately
76
+ const progressFilter = selectedFilters.find(f => f.startsWith('progress,')) || 'progress,all';
77
+ const progressValue = progressFilter.split(',')[1].toLowerCase();
78
+ const filteredSelectedFilters = selectedFilters.filter(f => !f.startsWith('progress,'));
79
+
80
+ // Prepare included fields
81
+ const mergedIncludedFields = [...filteredSelectedFilters, `tab,${tabName.toLowerCase()}`];
82
+
83
+ // Fetch data
84
+ const results = tabName === Tabs.ForYou.name
85
+ ? { entity: await getLessonContentRows(brand, pageName) }
86
+ : await fetchTabData(brand, pageName, { page, limit, sort, includedFields: mergedIncludedFields, progress: progressValue });
87
+
88
+ // Fetch metadata
89
+ const metaData = await fetchMetadata(brand, pageName);
90
+
91
+ // Process filters
92
+ const filters = (metaData.filters ?? []).map(filter => ({
93
+ ...filter,
94
+ items: filter.items.map(item => {
95
+ const value = item.value.split(',')[1];
96
+ return {
97
+ ...item,
98
+ selected: selectedFilters.includes(`${filter.key},${value}`) ||
99
+ (filter.key === 'progress' && value === 'all' && !selectedFilters.some(f => f.startsWith('progress,')))
100
+ };
101
+ })
102
+ }));
103
+
104
+ // Process sort options
105
+ const sortOptions = {
106
+ title: metaData.sort?.title ?? 'Sort By',
107
+ type: metaData.sort?.type ?? 'radio',
108
+ items: (metaData.sort?.items ?? []).map(option => ({
109
+ ...option,
110
+ selected: option.value === sort
111
+ }))
112
+ };
113
+
114
+ return {
115
+ type: tabName === Tabs.ForYou.name ? TabResponseType.SECTIONS : TabResponseType.CATALOG,
116
+ data: results.entity,
117
+ meta: { filters, sort: sortOptions }
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Fetches recent content for a given brand and page with pagination.
123
+ *
124
+ * @param {string} brand - The brand for which to fetch data.
125
+ * @param {string} pageName - The page name (e.g., 'all', 'incomplete', 'completed').
126
+ * @param {string} [tabName='all'] - The tab name (defaults to 'all' for recent content).
127
+ * @param {Object} params - Parameters for pagination and sorting.
128
+ * @param {number} [params.page=1] - The page number for pagination.
129
+ * @param {number} [params.limit=10] - The number of items per page.
130
+ * @param {string} [params.sort="-published_on"] - The field to sort the data by.
131
+ * @returns {Promise<Object>} - The fetched content data.
132
+ *
133
+ * @example
134
+ * getRecent('drumeo', 'lessons', 'all', {
135
+ * page: 2,
136
+ * limit: 15,
137
+ * sort: '-popularity'
138
+ * })
139
+ * .then(content => console.log(content))
140
+ * .catch(error => console.error(error));
141
+ */
142
+ export async function getRecent(brand, pageName, tabName = 'all', {
143
+ page = 1,
144
+ limit = 10,
145
+ sort = '-published_on',
146
+ } = {}) {
147
+ const progress = tabName.toLowerCase() == 'all' ? 'recent':tabName.toLowerCase();
148
+ const recentContentIds = await fetchRecent(brand, pageName, { page:page, limit:limit, progress: progress });
149
+ const metaData = await fetchMetadata(brand, 'recent');
150
+ return {
151
+ type: TabResponseType.CATALOG,
152
+ data: recentContentIds,
153
+ meta: { tabs: metaData.tabs }
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Fetches content rows for a given brand and page with optional filtering by content row id.
159
+ *
160
+ * @param {string} brand - The brand for which to fetch content rows.
161
+ * @param {string} pageName - The page name (e.g., 'lessons', 'songs', 'challenges').
162
+ * @param {string} [contentRowId] - The specific content row ID to fetch.
163
+ * @param {Object} params - Parameters for pagination.
164
+ * @param {number} [params.page=1] - The page number for pagination.
165
+ * @param {number} [params.limit=10] - The maximum number of content items per row.
166
+ * @returns {Promise<Object>} - The fetched content rows.
167
+ *
168
+ * @example
169
+ * getContentRows('drumeo', 'lessons', 'Your-Daily-Warmup', {
170
+ * page: 1,
171
+ * limit: 5
172
+ * })
173
+ * .then(content => console.log(content))
174
+ * .catch(error => console.error(error));
175
+ */
176
+ export async function getContentRows(brand, pageName, contentRowId , {
177
+ page = 1,
178
+ limit = 10,
179
+ } = {}) {
180
+ const contentRow = contentRowId ? `&content_row_id=${contentRowId}` : ''
181
+ const url = `/api/content/v1/rows?brand=${brand}&page_name=${pageName}${contentRow}&page=${page}&limit=${limit}`;
182
+ return await fetchHandler(url, 'get', null);
183
+ }
184
+
185
+ /**
186
+ * Fetches new and upcoming releases for a given brand with pagination options.
187
+ *
188
+ * @param {string} brand - The brand for which to fetch new and upcoming releases.
189
+ * @param {Object} [params={}] - Pagination parameters.
190
+ * @param {number} [params.page=1] - The page number for pagination.
191
+ * @param {number} [params.limit=10] - The maximum number of content items to fetch.
192
+ * @returns {Promise<{ data: Object[] } | null>} - A promise that resolves to the fetched content data or `null` if no data is found.
193
+ *
194
+ * @example
195
+ * // Fetch the first page with 10 results
196
+ * getNewAndUpcoming('drumeo')
197
+ * .then(response => console.log(response))
198
+ * .catch(error => console.error(error));
199
+ *
200
+ * @example
201
+ * // Fetch the second page with 20 results
202
+ * getNewAndUpcoming('drumeo', { page: 2, limit: 20 })
203
+ * .then(response => console.log(response))
204
+ * .catch(error => console.error(error));
205
+ */
206
+ export async function getNewAndUpcoming(brand, {
207
+ page = 1,
208
+ limit = 10,
209
+ } = {}) {
210
+
211
+ const data = await fetchScheduledAndNewReleases(brand, {page: page, limit: limit});
212
+ if (!data) {
213
+ return null;
214
+ }
215
+
216
+ return {
217
+ data: data,
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Fetches scheduled content rows for a given brand with optional filtering by content row ID.
223
+ *
224
+ * @param {string} brand - The brand for which to fetch content rows.
225
+ * @param {string} [contentRowId=null] - The specific content row ID to fetch (optional).
226
+ * @param {Object} [params={}] - Pagination parameters.
227
+ * @param {number} [params.page=1] - The page number for pagination.
228
+ * @param {number} [params.limit=10] - The maximum number of content items per row.
229
+ * @returns {Promise<Object>} - A promise that resolves to the fetched content rows.
230
+ *
231
+ * @example
232
+ * // Fetch all sections with default pagination
233
+ * getScheduleContentRows('drumeo')
234
+ * .then(content => console.log(content))
235
+ * .catch(error => console.error(error));
236
+ *
237
+ * @example
238
+ * // Fetch only the 'New-Releases' section with custom pagination
239
+ * getScheduleContentRows('drumeo', 'New-Releases', { page: 1, limit: 30 })
240
+ * .then(content => console.log(content))
241
+ * .catch(error => console.error(error));
242
+ *
243
+ * @example
244
+ * // Fetch only the 'Live-Streams' section with unlimited results
245
+ * getScheduleContentRows('drumeo', 'Live-Streams')
246
+ * .then(content => console.log(content))
247
+ * .catch(error => console.error(error));
248
+ */
249
+ export async function getScheduleContentRows(brand, contentRowId = null, { page = 1, limit = 10 } = {}) {
250
+ const sections = {
251
+ 'New-Releases': {
252
+ title: 'New Releases',
253
+ fetchMethod: fetchNewReleases
254
+ },
255
+ 'Live-Streams': {
256
+ title: 'Live Streams',
257
+ fetchMethod: fetchUpcomingEvents
258
+ },
259
+ 'Upcoming-Releases': {
260
+ title: 'Upcoming Releases',
261
+ fetchMethod: fetchScheduledReleases
262
+ },
263
+ 'Returning-Soon': {
264
+ title: 'Returning Soon',
265
+ fetchMethod: fetchReturning
266
+ },
267
+ 'Leaving-Soon': {
268
+ title: 'Leaving Soon',
269
+ fetchMethod: fetchLeaving
270
+ }
271
+ };
272
+
273
+ if (contentRowId) {
274
+ if (!sections[contentRowId]) {
275
+ return null; // Return null if the requested section does not exist
276
+ }
277
+
278
+ const items = await sections[contentRowId].fetchMethod(brand, { page, limit });
279
+
280
+ // Fetch only the requested section
281
+ const result = {
282
+ id: contentRowId,
283
+ title: sections[contentRowId].title,
284
+ // TODO: Remove content after FE/MA updates the existing code to use items
285
+ content: items,
286
+ items: items
287
+ };
288
+
289
+ return {
290
+ type: TabResponseType.CATALOG,
291
+ data: result,
292
+ meta: {}
293
+ };
294
+ }
295
+
296
+ // If no specific contentRowId, fetch all sections
297
+ const results = await Promise.all(
298
+ Object.entries(sections).map(async ([id, section]) => {
299
+ // Apply special pagination rules
300
+ const isNewReleases = id === 'New-Releases';
301
+ const pagination = isNewReleases ? { page: 1, limit: 30 } : { page: 1, limit: Number.MAX_SAFE_INTEGER };
302
+
303
+ return {
304
+ id,
305
+ title: section.title,
306
+ content: await section.fetchMethod(brand, pagination)
307
+ };
308
+ })
309
+ );
310
+
311
+ return {
312
+ type: TabResponseType.SECTIONS,
313
+ data: results,
314
+ meta: {}
315
+ };
316
+ }
317
+
318
+ /**
319
+ * Fetches recommended content for a given brand with pagination support.
320
+ *
321
+ * @param {string} brand - The brand for which to fetch recommended content.
322
+ * @param {Object} [params={}] - Pagination parameters.
323
+ * @param {number} [params.page=1] - The page number for pagination.
324
+ * @param {number} [params.limit=10] - The maximum number of recommended content items per page.
325
+ * @returns {Promise<Object>} - A promise that resolves to an object containing recommended content.
326
+ *
327
+ * @example
328
+ * // Fetch recommended content for a brand with default pagination
329
+ * getRecommendedForYou('drumeo')
330
+ * .then(content => console.log(content))
331
+ * .catch(error => console.error(error));
332
+ *
333
+ * @example
334
+ * // Fetch recommended content for a brand with custom pagination
335
+ * getRecommendedForYou('drumeo', { page: 2, limit: 5 })
336
+ * .then(content => console.log(content))
337
+ * .catch(error => console.error(error));
338
+ */
339
+ export async function getRecommendedForYou(brand, rowId = null, {
340
+ page = 1,
341
+ limit = 10,
342
+ } = {}) {
343
+ const requiredItems = page * limit;
344
+ const data = await recommendations(brand, {limit: requiredItems});
345
+ if (!data || !data.length) {
346
+ return { id: 'recommended', title: 'Recommended For You', items: [] };
347
+ }
348
+
349
+ // Apply pagination before calling fetchByRailContentIds
350
+ const startIndex = (page - 1) * limit;
351
+ const paginatedData = data.slice(startIndex, startIndex + limit);
352
+
353
+ const contents = await fetchByRailContentIds(paginatedData);
354
+ const result = {
355
+ id: 'recommended',
356
+ title: 'Recommended For You',
357
+ items: contents
358
+ };
359
+
360
+ if (rowId) {
361
+ return {
362
+ type: TabResponseType.CATALOG,
363
+ data: contents,
364
+ meta: {}
365
+ };
366
+ }
367
+
368
+ return { id: 'recommended', title: 'Recommended For You', items: contents }
369
+ }
370
+
371
+
File without changes
File without changes
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @module Forum-V2
3
+ */
4
+
5
+
6
+ export async function getActiveDiscussions(brand, { page = 1, limit = 10 } = {}) {
7
+ // Dummy data TODO: BE endpoint call
8
+ const results = {
9
+ entity: [
10
+ {
11
+ id: 11,
12
+ url: 'https://forum.example.com/post/11',
13
+ post: "<p><strong>Lorem ipsum</strong> dolor sit amet, <em>consectetur adipiscing elit</em>. <a href='#'>Click here</a> for more info.</p>",
14
+ author: {
15
+ id: 123,
16
+ name: 'John Doe',
17
+ avatar: 'https://d3fzm1tzeyr5n3.cloudfront.net/profile_picture_url/5f6abe99-f1ed-49ec-aff4-893c017ed1aa-1681577292-565638.jpg'
18
+ }
19
+ },
20
+ {
21
+ id: 12,
22
+ url: 'https://forum.example.com/post/12',
23
+ post: "<p>This is a <span style='color: red;'>sample forum post</span> to <strong>test</strong> data structure. <ul><li>Point 1</li><li>Point 2</li></ul></p>",
24
+ author: {
25
+ id: 124,
26
+ name: 'Jane Smith',
27
+ avatar: 'https://d3fzm1tzeyr5n3.cloudfront.net/profile_picture_url/5f6abe99-f1ed-49ec-aff4-893c017ed1aa-1681577292-565638.jpg'
28
+ }
29
+ },
30
+ {
31
+ id: 13,
32
+ url: 'https://forum.example.com/post/13',
33
+ post: "<blockquote>Another test post with some <i>dummy text</i> content.</blockquote>",
34
+ author: {
35
+ id: 125,
36
+ name: 'Alice Johnson',
37
+ avatar: 'https://d3fzm1tzeyr5n3.cloudfront.net/profile_picture_url/5f6abe99-f1ed-49ec-aff4-893c017ed1aa-1681577292-565638.jpg'
38
+ }
39
+ },
40
+ {
41
+ id: 14,
42
+ url: 'https://forum.example.com/post/14',
43
+ post: "<h2>Final example post</h2><p>To complete the dataset, here is a <strong>bold</strong> statement and an image: <img src='https://example.com/image.jpg' alt='Example Image'></p>",
44
+ author: {
45
+ id: 126,
46
+ name: 'Bob Williams',
47
+ avatar: 'https://d3fzm1tzeyr5n3.cloudfront.net/profile_picture_url/5f6abe99-f1ed-49ec-aff4-893c017ed1aa-1681577292-565638.jpg'
48
+ }
49
+ }
50
+ ]
51
+ };
52
+
53
+ return {
54
+ data: results.entity,
55
+ meta: {}
56
+ };
57
+ }