musora-content-services 2.92.7 → 2.93.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.
@@ -39,14 +39,6 @@ jobs:
39
39
  with:
40
40
  node-version: '20'
41
41
 
42
- - name: Install dependencies for v1
43
- working-directory: main-content
44
- run: npm ci
45
-
46
- - name: Generate v1 documentation
47
- working-directory: main-content
48
- run: npm run doc
49
-
50
42
  - name: Install dependencies for v2
51
43
  working-directory: v2-content
52
44
  run: npm ci
@@ -58,8 +50,11 @@ jobs:
58
50
  - name: Combine v1 and v2 docs into deployment structure
59
51
  run: |
60
52
  mkdir -p _site
61
- # Copy v1 docs to root
62
- cp -r main-content/docs/* _site/
53
+ # Copy existing v1 docs from main branch (already committed)
54
+ if [ -d "main-content/docs" ]; then
55
+ cp -r main-content/docs/* _site/
56
+ echo "✅ Copied existing v1 docs from main branch"
57
+ fi
63
58
  # Copy v2 docs to /v2/ subdirectory
64
59
  mkdir -p _site/v2
65
60
  cp -r v2-content/docs/* _site/v2/
package/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ## [2.93.0](https://github.com/railroadmedia/musora-content-services/compare/v2.92.7...v2.93.0) (2025-12-02)
6
+
7
+
8
+ ### Features
9
+
10
+ * **BEH-1192:** revise fetchLearningPathProgressCheckLessons ([#604](https://github.com/railroadmedia/musora-content-services/issues/604)) ([db8cb70](https://github.com/railroadmedia/musora-content-services/commit/db8cb70ed70f4341fdd4651cdd3fea2b8047dc21))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * method progress card subtitle ([#608](https://github.com/railroadmedia/musora-content-services/issues/608)) ([3bbfb56](https://github.com/railroadmedia/musora-content-services/commit/3bbfb56fce08e0753e7708bdf2d1356000b1c398))
16
+ * Pull in permission_v2 ([#602](https://github.com/railroadmedia/musora-content-services/issues/602)) ([289e32d](https://github.com/railroadmedia/musora-content-services/commit/289e32d08bd3a6448191b9f6034801e4cf8d40e5))
17
+
5
18
  ### [2.92.7](https://github.com/railroadmedia/musora-content-services/compare/v2.92.6...v2.92.7) (2025-12-02)
6
19
 
7
20
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.92.7",
3
+ "version": "2.93.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -61,7 +61,7 @@ export const DEFAULT_FIELDS = [
61
61
  `'genre': ${genreField}`,
62
62
  'status',
63
63
  "'slug' : slug.current",
64
- "'permission_id': permission[]->railcontent_id",
64
+ "'permission_id': permission_v2",
65
65
  'child_count',
66
66
  '"parent_id": parent_content_data[0].id',
67
67
  ]
@@ -84,7 +84,7 @@ export const DEFAULT_CHILD_FIELDS = [
84
84
  `'genre': ${genreField}`,
85
85
  'status',
86
86
  "'slug' : slug.current",
87
- "'permission_id': permission[]->railcontent_id",
87
+ "'permission_id': permission_v2",
88
88
  'child_count',
89
89
  '"parent_id": parent_content_data[0].id',
90
90
  ]
@@ -11,6 +11,7 @@ import {
11
11
  getAllCompletedByIds,
12
12
  getProgressState,
13
13
  } from '../contentProgress.js'
14
+ import { STATE } from '../sync/models/ContentProgress'
14
15
 
15
16
  const BASE_PATH: string = `/api/content-org`
16
17
  const LEARNING_PATHS_PATH = `${BASE_PATH}/v1/user/learning-paths`
@@ -225,29 +226,18 @@ export async function fetchLearningPathLessons(
225
226
  * For an array of contentIds, fetch any content progress with state=completed,
226
227
  * including other learning paths and a la carte progress.
227
228
  *
228
- * @param contentIds The array of content IDs within the learning path
229
- * @param learningPathId The learning path ID
230
- * @returns {Promise<Object>} Response object
231
- * @returns {Array} result.lessons - Array of all learning path lesson contentIds.
232
- * @returns {Array} result.completed_lessons - Array of learning path lesson contentIds that are completed.
233
- * @returns {Array} result.lessons_count - Count of learning path lessons.
234
- * @returns {Array} result.completed_lessons_count - Count of learning path completed lessons.
229
+ * @param {number[]} contentIds The array of content IDs within the learning path
230
+ * @returns {Promise<Object>} Object with content IDs as keys and the progress state as values
235
231
  */
236
- export async function fetchLearningPathProgressCheckLessons(
237
- contentIds: number[],
238
- learningPathId: number
239
- ): Promise<object> {
240
- let query = await getAllCompletedByIds(contentIds, {
241
- id: learningPathId,
242
- type: 'learning-path-v2',
243
- })
244
- let completedContentIds = query.data
245
- return {
246
- lessons: contentIds,
247
- completed_lessons: completedContentIds,
248
- lessons_count: contentIds.length,
249
- completed_lessons_count: completedContentIds.length,
250
- }
232
+ export async function fetchLearningPathProgressCheckLessons(contentIds: number[]): Promise<Object> {
233
+ let query = await getAllCompletedByIds(contentIds)
234
+ let completedLessons = query.data.map(lesson => lesson.content_id)
235
+
236
+ return contentIds.reduce((obj, contentId) => {
237
+ let lessonIsCompleted = completedLessons.includes(contentId)
238
+ obj[contentId] = lessonIsCompleted ? STATE.COMPLETED : ""
239
+ return obj
240
+ }, {})
251
241
  }
252
242
 
253
243
  interface completeMethodIntroVideo {
@@ -162,18 +162,8 @@ export async function getAllCompleted(limit = null) {
162
162
  return db.contentProgress.completedIds(limit).then(r => r.data.map(id => parseInt(id)))
163
163
  }
164
164
 
165
- /**
166
- *
167
- * @param {array} contentIds List of content children within learning path
168
- * @param {object} collection Learning path object
169
- * @returns {Promise<array>} Filtered list of contentIds that are completed
170
- */
171
- export async function getAllCompletedByIds(contentIds, collection) {
172
- // TODO - implement collection filtering
173
- return db.contentProgress.queryAllIds(
174
- Q.whereIn('content_id', contentIds),
175
- Q.where('state', STATE_COMPLETED)
176
- )
165
+ export async function getAllCompletedByIds(contentIds) {
166
+ return db.contentProgress.completedByContentIds(contentIds)
177
167
  }
178
168
 
179
169
  export async function getAllStartedOrCompleted({
@@ -51,9 +51,7 @@ export class PermissionsV2Adapter extends PermissionsAdapter {
51
51
  }
52
52
 
53
53
  // Get content's required permissions
54
- const oldPermissions = content?.permission_id ?? []
55
- const newPermissions = content?.permission_v2 ?? []
56
- const contentPermissions = new Set([...oldPermissions, ...newPermissions])
54
+ const contentPermissions = new Set(content?.permission_id ?? [])
57
55
 
58
56
  // Content with no permissions is accessible to all
59
57
  if (contentPermissions.size === 0) {
@@ -2,16 +2,9 @@
2
2
  * @module ProgressRow
3
3
  */
4
4
 
5
- import {
6
- getDailySession,
7
- getActivePath,
8
- resetAllLearningPaths,
9
- startLearningPath,
10
- fetchLearningPathLessons,
11
- } from '../content-org/learning-paths'
5
+ import { getActivePath, fetchLearningPathLessons } from '../content-org/learning-paths'
12
6
  import { getToday } from '../dateUtils.js'
13
- import { fetchByRailContentId, fetchByRailContentIds, fetchMethodV2IntroVideo } from '../sanity'
14
- import { addContextToContent } from '../contentAggregator.js'
7
+ import { fetchMethodV2IntroVideo } from '../sanity'
15
8
  import { getProgressState } from '../contentProgress'
16
9
 
17
10
  export async function getMethodCard(brand) {
@@ -21,6 +14,10 @@ export async function getMethodCard(brand) {
21
14
  if (introVideoProgressState !== 'completed') {
22
15
  //startLearningPath('drumeo', 422533)
23
16
  const timestamp = Math.floor(Date.now() / 1000)
17
+ const instructorText =
18
+ introVideo.instructor?.length > 1
19
+ ? 'Multiple Instructors'
20
+ : introVideo.instructor?.[0]?.name || ''
24
21
  return {
25
22
  id: 1, // method card has no id
26
23
  type: 'method',
@@ -29,7 +26,7 @@ export async function getMethodCard(brand) {
29
26
  body: {
30
27
  thumbnail: introVideo.thumbnail,
31
28
  title: introVideo.title,
32
- subtitle: `${introVideo.difficulty_string} • ${introVideo.instructor?.[0]?.name}`,
29
+ subtitle: `${introVideo.difficulty_string} • ${instructorText}`,
33
30
  },
34
31
  cta: {
35
32
  text: 'Get Started',
@@ -59,9 +56,7 @@ export async function getMethodCard(brand) {
59
56
  const nextIncompleteLesson = learningPath?.todays_lessons.find(
60
57
  (lesson) => lesson.progressStatus !== 'completed'
61
58
  )
62
- let ctaText,
63
- action,
64
- nextLesson = null
59
+ let ctaText, action
65
60
  if (noneCompleted) {
66
61
  ctaText = 'Start Session'
67
62
  action = getMethodActionCTA(nextIncompleteLesson)
@@ -219,7 +219,7 @@ export async function fetchRelatedSongs(brand, songId) {
219
219
  "published_on": published_on,
220
220
  status,
221
221
  "image": thumbnail.asset->url,
222
- "permission_id": permission[]->railcontent_id,
222
+ "permission_id": permission_v2,
223
223
  "fields": [
224
224
  {
225
225
  "key": "title",
@@ -245,7 +245,7 @@ export async function fetchRelatedSongs(brand, songId) {
245
245
  "id": railcontent_id,
246
246
  "url": web_url_path,
247
247
  "published_on": published_on,
248
- "permission_id": permission[]->railcontent_id,
248
+ "permission_id": permission_v2,
249
249
  status,
250
250
  "fields": [
251
251
  {
@@ -306,7 +306,7 @@ export async function fetchNewReleases(
306
306
  published_on,
307
307
  "type": _type,
308
308
  web_url_path,
309
- "permission_id": permission[]->railcontent_id,
309
+ "permission_id": permission_v2,
310
310
  `
311
311
  const query = buildRawQuery(filter, fields, { sortOrder: sortOrder, start, end: end })
312
312
  return fetchSanity(query, true)
@@ -343,7 +343,7 @@ export async function fetchUpcomingEvents(brand, { page = 1, limit = 10 } = {})
343
343
  published_on,
344
344
  "type": _type,
345
345
  web_url_path,
346
- "permission_id": permission[]->railcontent_id,
346
+ "permission_id": permission_v2,
347
347
  live_event_start_time,
348
348
  live_event_end_time,
349
349
  "isLive": live_event_start_time <= '${now}' && live_event_end_time >= '${now}'`
@@ -395,7 +395,7 @@ export async function fetchScheduledReleases(brand, { page = 1, limit = 10 }) {
395
395
  published_on,
396
396
  "type": _type,
397
397
  web_url_path,
398
- "permission_id": permission[]->railcontent_id,
398
+ "permission_id": permission_v2,
399
399
  } | order(published_on asc)[${start}...${end}]`
400
400
  return fetchSanity(query, true)
401
401
  }
@@ -911,7 +911,7 @@ export async function fetchMethod(brand, slug) {
911
911
  "url": *[railcontent_id == ^.id][0].web_url_path
912
912
  } | order(length(url)),
913
913
  "type": _type,
914
- "permission_id": permission[]->railcontent_id,
914
+ "permission_id": permission_v2,
915
915
  "levels": child[${childrenFilter}]->
916
916
  {
917
917
  "id": railcontent_id,
@@ -1128,7 +1128,7 @@ export async function fetchLessonContent(railContentId, { addParent = false } =
1128
1128
  mp3_no_drums_yes_click_url,
1129
1129
  mp3_yes_drums_no_click_url,
1130
1130
  mp3_yes_drums_yes_click_url,
1131
- "permission_id": permission[]->railcontent_id,
1131
+ "permission_id": permission_v2,
1132
1132
  ${parentQuery}
1133
1133
  ...select(
1134
1134
  defined(live_event_start_time) => {
@@ -1266,7 +1266,7 @@ export async function fetchSiblingContent(railContentId, brand = null) {
1266
1266
  }).buildFilter()
1267
1267
 
1268
1268
  const brandString = brand ? ` && brand == "${brand}"` : ''
1269
- const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, difficulty_string, artist->, "permission_id": permission[]->railcontent_id, "genre": genre[]->name, "parent_id": parent_content_data[0].id`
1269
+ const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, difficulty_string, artist->, "permission_id": permission_v2, "genre": genre[]->name, "parent_id": parent_content_data[0].id`
1270
1270
 
1271
1271
  const query = `*[railcontent_id == ${railContentId}${brandString}]{
1272
1272
  _type, parent_type, 'parent_id': parent_content_data[0].id, railcontent_id,
@@ -1313,7 +1313,7 @@ export async function fetchRelatedLessons(railContentId) {
1313
1313
  { showMembershipRestrictedContent: true }
1314
1314
  ).buildFilter()
1315
1315
 
1316
- const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission[]->railcontent_id,_type, "genre": genre[]->name`
1316
+ const queryFields = `_id, "id":railcontent_id, published_on, "instructor": instructor[0]->name, title, "thumbnail":thumbnail.asset->url, length_in_seconds, status, "type": _type, difficulty, difficulty_string, railcontent_id, artist->,"permission_id": permission_v2,_type, "genre": genre[]->name`
1317
1317
 
1318
1318
  const query = `*[railcontent_id == ${railContentId} && (!defined(permission) || references(*[_type=='permission']._id))]{
1319
1319
  _type, parent_type, railcontent_id,
@@ -2134,7 +2134,7 @@ export async function fetchScheduledAndNewReleases(
2134
2134
  published_on,
2135
2135
  "type": _type,
2136
2136
  show_in_new_feed,
2137
- "permission_id": permission[]->railcontent_id,
2137
+ "permission_id": permission_v2,
2138
2138
  "isLive": live_event_start_time <= '${now}' && live_event_end_time >= '${now}',
2139
2139
  }`
2140
2140
 
@@ -23,6 +23,13 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
23
23
  )
24
24
  }
25
25
 
26
+ async completedByContentIds(contentIds: number[]) {
27
+ return this.queryAll(
28
+ Q.where('content_id', Q.oneOf(contentIds)),
29
+ Q.where('state', STATE.COMPLETED)
30
+ )
31
+ }
32
+
26
33
  // null collection only
27
34
  async startedOrCompleted(opts: Parameters<typeof this.startedOrCompletedClauses>[0] = {}) {
28
35
  return this.queryAll(...this.startedOrCompletedClauses(opts))