musora-content-services 2.115.3 → 2.117.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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
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.117.0](https://github.com/railroadmedia/musora-content-services/compare/v2.116.0...v2.117.0) (2026-01-13)
6
+
7
+
8
+ ### Features
9
+
10
+ * **BEH-1442:** old method migration + course collection updates ([#673](https://github.com/railroadmedia/musora-content-services/issues/673)) ([24dd6bf](https://github.com/railroadmedia/musora-content-services/commit/24dd6bf6a1604ef3ee639979cf2ffab5abc05a24))
11
+
12
+ ## [2.116.0](https://github.com/railroadmedia/musora-content-services/compare/v2.115.3...v2.116.0) (2026-01-12)
13
+
14
+
15
+ ### Features
16
+
17
+ * **T3Ps-1324:** Rename Tiered Courses filter to Course Collections ([97efe76](https://github.com/railroadmedia/musora-content-services/commit/97efe76fff3860d769593809bea2e59fb317b528))
18
+
5
19
  ### [2.115.3](https://github.com/railroadmedia/musora-content-services/compare/v2.115.2...v2.115.3) (2026-01-12)
6
20
 
7
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.115.3",
3
+ "version": "2.117.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -0,0 +1,7 @@
1
+
2
+ export const MEMBERSHIP_PERMISSIONS = {
3
+ free: 134,
4
+ base: 91,
5
+ plus: 92,
6
+ lifetime: 108,
7
+ }
@@ -1,7 +1,7 @@
1
1
  // Metadata is taken from the 'common' element and then merged with the <brand> metadata.
2
2
  // Brand values are prioritized and will override the same property in the 'common' element.
3
3
 
4
- import {ALWAYS_VISIBLE_TABS} from "./services/sanity.js";
4
+ import { ALWAYS_VISIBLE_TABS } from './services/sanity.js'
5
5
 
6
6
  const PROGRESS_NAMES = ['All', 'In Progress', 'Completed', 'Not Started']
7
7
  const DIFFICULTY_STRINGS = ['Introductory', 'Beginner', 'Intermediate', 'Advanced', 'Expert']
@@ -9,19 +9,19 @@ const DIFFICULTY_STRINGS = ['Introductory', 'Beginner', 'Intermediate', 'Advance
9
9
  const LESSON_TYPE_FILTER = [
10
10
  {
11
11
  title: 'Single Lessons',
12
- children: ['Lessons', 'Practice Alongs', 'Live Archives', 'Student Archives']
12
+ children: ['Lessons', 'Practice Alongs', 'Live Archives', 'Student Archives'],
13
13
  },
14
14
  {
15
15
  title: 'Courses',
16
- children: ['Courses', 'Guided Courses', 'Tiered Courses']
16
+ children: ['Courses', 'Guided Courses', 'Course Collections'],
17
17
  },
18
18
  {
19
19
  title: 'Skill Packs',
20
20
  },
21
21
  {
22
22
  title: 'Entertainment',
23
- children: ['Specials', 'Documentaries', 'Shows']
24
- }
23
+ children: ['Specials', 'Documentaries', 'Shows'],
24
+ },
25
25
  ]
26
26
 
27
27
  class SortingOptions {
@@ -32,7 +32,7 @@ class SortingOptions {
32
32
  static Slug = { value: 'slug', name: 'Name: A to Z' }
33
33
  static SlugDesc = { value: '-slug', name: 'Name: Z to A' }
34
34
  static AllSortingOptions = [
35
- this.PopularityDesc,
35
+ this.PopularityDesc,
36
36
  this.Popularity,
37
37
  this.PublishedOn,
38
38
  this.PublishedOnDesc,
@@ -46,12 +46,7 @@ export class LengthFilterOptions {
46
46
  static From7To15 = { value: '420-900', name: '7 to 15 Minutes' }
47
47
  static From15To30 = { value: '901-1800', name: '15 to 30 Minutes' }
48
48
  static More30 = { value: '>1801', name: '30+ Minutes' }
49
- static AllOptions = [
50
- this.UpTo7.name,
51
- this.From7To15.name,
52
- this.From15To30.name,
53
- this.More30.name,
54
- ]
49
+ static AllOptions = [this.UpTo7.name, this.From7To15.name, this.From15To30.name, this.More30.name]
55
50
  }
56
51
 
57
52
  export class Tabs {
@@ -63,18 +58,63 @@ export class Tabs {
63
58
  static Courses = { name: 'Courses', short_name: 'Courses', value: 'type,Courses' }
64
59
  static SkillLevel = { name: 'Skill Level', short_name: 'SKILL LEVEL', is_group_by: true, value: 'difficulty_string' }
65
60
  static Genres = { name: 'Genres', short_name: 'Genres', is_group_by: true, value: 'genre' }
66
- static Completed = { name: 'Completed', short_name: 'COMPLETED', is_group_by: false, value: 'completed' }
67
- static InProgress = { name: 'In Progress', short_name: 'IN PROGRESS', is_group_by: false, value: 'in progress' }
68
- static Instructors = { name: 'Instructors', short_name: 'INSTRUCTORS', is_group_by: true, value: 'instructor' }
61
+ static Completed = {
62
+ name: 'Completed',
63
+ short_name: 'COMPLETED',
64
+ is_group_by: false,
65
+ value: 'completed',
66
+ }
67
+ static InProgress = {
68
+ name: 'In Progress',
69
+ short_name: 'IN PROGRESS',
70
+ is_group_by: false,
71
+ value: 'in progress',
72
+ }
73
+ static Instructors = {
74
+ name: 'Instructors',
75
+ short_name: 'INSTRUCTORS',
76
+ is_group_by: true,
77
+ value: 'instructor',
78
+ }
69
79
  static Lessons = { name: 'Lessons', short_name: 'LESSONS', value: '' }
70
80
  static Artists = { name: 'Artists', short_name: 'ARTISTS', is_group_by: true, value: 'artist' }
71
81
  static Songs = { name: 'Songs', short_name: 'Songs', value: '' }
72
- static Tutorials = { name: 'Tutorials', short_name: 'Tutorials', value: 'type,tutorials', cardType: 'big' }
73
- static Transcriptions = { name: 'Transcriptions', short_name: 'Transcriptions', value: 'type,transcriptions', cardType: 'small' }
74
- static SheetMusic = { name: 'Sheet Music', short_name: 'Sheet Music', value: 'type,transcriptions', cardType: 'small' }
75
- static Tabs = { name: 'Tabs', short_name: 'Tabs', value: 'type,transcriptions', cardType: 'small' }
76
- static PlayAlongs = { name: 'Play-Alongs', short_name: 'Play-Alongs', value:'type,play along', cardType: 'small' }
77
- static JamTracks = { name: 'Jam Tracks', short_name: 'Jam Tracks', value:'type,jam-track', cardType: 'small' }
82
+ static Tutorials = {
83
+ name: 'Tutorials',
84
+ short_name: 'Tutorials',
85
+ value: 'type,tutorials',
86
+ cardType: 'big',
87
+ }
88
+ static Transcriptions = {
89
+ name: 'Transcriptions',
90
+ short_name: 'Transcriptions',
91
+ value: 'type,transcriptions',
92
+ cardType: 'small',
93
+ }
94
+ static SheetMusic = {
95
+ name: 'Sheet Music',
96
+ short_name: 'Sheet Music',
97
+ value: 'type,transcriptions',
98
+ cardType: 'small',
99
+ }
100
+ static Tabs = {
101
+ name: 'Tabs',
102
+ short_name: 'Tabs',
103
+ value: 'type,transcriptions',
104
+ cardType: 'small',
105
+ }
106
+ static PlayAlongs = {
107
+ name: 'Play-Alongs',
108
+ short_name: 'Play-Alongs',
109
+ value: 'type,play along',
110
+ cardType: 'small',
111
+ }
112
+ static JamTracks = {
113
+ name: 'Jam Tracks',
114
+ short_name: 'Jam Tracks',
115
+ value: 'type,jam-track',
116
+ cardType: 'small',
117
+ }
78
118
  static RecentAll = { name: 'All', short_name: 'All' }
79
119
  static RecentIncomplete = { name: 'Incomplete', short_name: 'Incomplete' }
80
120
  static RecentCompleted = { name: 'Completed', short_name: 'Completed' }
@@ -92,15 +132,26 @@ export const TabResponseType = {
92
132
  SECTIONS: 'sections',
93
133
  CATALOG: 'catalog',
94
134
  PROGRESS_ROWS: 'progress_rows',
95
- };
135
+ }
96
136
 
97
137
  const commonMetadata = {
98
- 'lessons': {
138
+ lessons: {
99
139
  name: 'Lessons',
100
140
  filterOptions: {
101
141
  difficulty: DIFFICULTY_STRINGS,
102
142
  length: LengthFilterOptions.AllOptions,
103
- style: ['Country/Folk', 'Funk/Disco', 'Hard Rock/Metal', 'Hip-Hop/Rap/EDM', 'Holiday/Soundtrack', 'Jazz/Blues', 'Latin/World', 'Pop/Rock', 'R&B/Soul', 'Worship/Gospel'],
143
+ style: [
144
+ 'Country/Folk',
145
+ 'Funk/Disco',
146
+ 'Hard Rock/Metal',
147
+ 'Hip-Hop/Rap/EDM',
148
+ 'Holiday/Soundtrack',
149
+ 'Jazz/Blues',
150
+ 'Latin/World',
151
+ 'Pop/Rock',
152
+ 'R&B/Soul',
153
+ 'Worship/Gospel',
154
+ ],
104
155
  type: LESSON_TYPE_FILTER,
105
156
  progress: PROGRESS_NAMES,
106
157
  },
@@ -115,15 +166,32 @@ const commonMetadata = {
115
166
  Tabs.Courses,
116
167
  Tabs.SkillPacks,
117
168
  Tabs.Entertainment,
118
- Tabs.ExploreAll
169
+ Tabs.ExploreAll,
119
170
  ],
120
171
  },
121
- 'songs': {
172
+ songs: {
122
173
  name: 'Songs',
123
174
  filterOptions: {
124
175
  difficulty: DIFFICULTY_STRINGS,
125
- style: ['Blues','Christian','Classical','Country','Disco','Electronic','Folk','Funk','Hip-Hop/Rap','Holiday','Jazz','Soundtrack',
126
- 'World','Metal','Pop','R&B/Soul','Rock'],
176
+ style: [
177
+ 'Blues',
178
+ 'Christian',
179
+ 'Classical',
180
+ 'Country',
181
+ 'Disco',
182
+ 'Electronic',
183
+ 'Folk',
184
+ 'Funk',
185
+ 'Hip-Hop/Rap',
186
+ 'Holiday',
187
+ 'Jazz',
188
+ 'Soundtrack',
189
+ 'World',
190
+ 'Metal',
191
+ 'Pop',
192
+ 'R&B/Soul',
193
+ 'Rock',
194
+ ],
127
195
  type: ['Tutorials', 'Transcriptions', 'Jam Tracks'],
128
196
  progress: PROGRESS_NAMES,
129
197
  },
@@ -138,16 +206,12 @@ const commonMetadata = {
138
206
  Tabs.Transcriptions,
139
207
  Tabs.PlayAlongs,
140
208
  Tabs.JamTracks,
141
- Tabs.ExploreAll
209
+ Tabs.ExploreAll,
142
210
  ],
143
211
  },
144
- 'recent': {
212
+ recent: {
145
213
  name: 'Recent Lessons',
146
- tabs: [
147
- Tabs.RecentAll,
148
- Tabs.RecentIncomplete,
149
- Tabs.RecentCompleted
150
- ],
214
+ tabs: [Tabs.RecentAll, Tabs.RecentIncomplete, Tabs.RecentCompleted],
151
215
  },
152
216
  recommendation: {
153
217
  tabs: [
@@ -171,12 +235,23 @@ const commonMetadata = {
171
235
  }
172
236
  const contentMetadata = {
173
237
  drumeo: {
174
- 'lessons': {
238
+ lessons: {
175
239
  name: 'Lessons',
176
240
  filterOptions: {
177
241
  difficulty: DIFFICULTY_STRINGS,
178
242
  length: LengthFilterOptions.AllOptions,
179
- style: ['Country/Folk', 'Funk/Disco', 'Hard Rock/Metal', 'Hip-Hop/Rap/EDM', 'Holiday/Soundtrack', 'Jazz/Blues', 'Latin/World', 'Pop/Rock', 'R&B/Soul', 'Worship/Gospel'],
243
+ style: [
244
+ 'Country/Folk',
245
+ 'Funk/Disco',
246
+ 'Hard Rock/Metal',
247
+ 'Hip-Hop/Rap/EDM',
248
+ 'Holiday/Soundtrack',
249
+ 'Jazz/Blues',
250
+ 'Latin/World',
251
+ 'Pop/Rock',
252
+ 'R&B/Soul',
253
+ 'Worship/Gospel',
254
+ ],
180
255
  type: LESSON_TYPE_FILTER,
181
256
  progress: PROGRESS_NAMES,
182
257
  },
@@ -191,18 +266,29 @@ const contentMetadata = {
191
266
  Tabs.Courses,
192
267
  Tabs.SkillPacks,
193
268
  Tabs.Entertainment,
194
- Tabs.ExploreAll
269
+ Tabs.ExploreAll,
195
270
  ],
196
271
  },
197
272
  'songs-types': ['Tutorials', 'Transcriptions', 'Play-Alongs', 'Jam Tracks'],
198
273
  },
199
274
  pianote: {
200
- 'lessons': {
275
+ lessons: {
201
276
  name: 'Lessons',
202
277
  filterOptions: {
203
278
  difficulty: DIFFICULTY_STRINGS,
204
279
  length: LengthFilterOptions.AllOptions,
205
- style: ['Classical', 'Country/Folk', 'Funk/Disco', 'Hip-Hop/Rap/EDM', 'Holiday/Soundtrack', 'Jazz/Blues', 'Latin/World', 'Pop/Rock', 'R&B/Soul', 'Worship/Gospel'],
280
+ style: [
281
+ 'Classical',
282
+ 'Country/Folk',
283
+ 'Funk/Disco',
284
+ 'Hip-Hop/Rap/EDM',
285
+ 'Holiday/Soundtrack',
286
+ 'Jazz/Blues',
287
+ 'Latin/World',
288
+ 'Pop/Rock',
289
+ 'R&B/Soul',
290
+ 'Worship/Gospel',
291
+ ],
206
292
  type: LESSON_TYPE_FILTER,
207
293
  progress: PROGRESS_NAMES,
208
294
  },
@@ -217,7 +303,7 @@ const contentMetadata = {
217
303
  Tabs.Courses,
218
304
  Tabs.SkillPacks,
219
305
  Tabs.Entertainment,
220
- Tabs.ExploreAll
306
+ Tabs.ExploreAll,
221
307
  ],
222
308
  },
223
309
  'songs-types': ['Tutorials', 'Sheet Music', 'Play-Alongs', 'Jam Tracks'],
@@ -230,10 +316,9 @@ const contentMetadata = {
230
316
  },
231
317
  singeo: {
232
318
  'songs-types': ['Tutorials', 'Sheet Music', 'Play-Alongs', 'Jam Tracks'],
233
- }
319
+ },
234
320
  }
235
321
 
236
-
237
322
  export function processMetadata(brand, type, withFilters = false) {
238
323
  let brandMetaData = contentMetadata[brand]?.[type]
239
324
  let commonMetaData = commonMetadata[type]
@@ -269,22 +354,20 @@ export function processMetadata(brand, type, withFilters = false) {
269
354
 
270
355
  function mapSongTabNames(brandMetaData) {
271
356
  brandMetaData.tabs.forEach((tab, index) => {
272
- if (ALWAYS_VISIBLE_TABS.some(visibleTab => visibleTab.name === tab)) {
273
- return;
357
+ if (ALWAYS_VISIBLE_TABS.some((visibleTab) => visibleTab.name === tab)) {
358
+ return
274
359
  }
275
360
 
276
- const targetName = brandMetaData['filterOptions']['type'][index - 1];
361
+ const targetName = brandMetaData['filterOptions']['type'][index - 1]
277
362
 
278
363
  // Find the matching Tab by name
279
- const matchingTab = Object.values(Tabs).find(
280
- tabObj => tabObj.name === targetName
281
- );
364
+ const matchingTab = Object.values(Tabs).find((tabObj) => tabObj.name === targetName)
282
365
 
283
366
  if (matchingTab) {
284
- brandMetaData.tabs[index] = matchingTab;
367
+ brandMetaData.tabs[index] = matchingTab
285
368
  }
286
- });
287
- return brandMetaData.tabs;
369
+ })
370
+ return brandMetaData.tabs
288
371
  }
289
372
 
290
373
  /**
@@ -316,25 +399,27 @@ function transformFilters(filterOptions) {
316
399
  return Object.entries(filterOptions).map(([key, values]) => {
317
400
  // Check if values is hierarchical (array of objects with title property)
318
401
  // We check for 'title' property to distinguish from simple string arrays
319
- const isHierarchical = Array.isArray(values) &&
402
+ const isHierarchical =
403
+ Array.isArray(values) &&
320
404
  values.length > 0 &&
321
405
  typeof values[0] === 'object' &&
322
- values[0].title !== undefined;
406
+ values[0].title !== undefined
323
407
 
324
408
  if (isHierarchical) {
325
409
  // Handle hierarchical structure - nest children inside parents
326
- const items = values.map(group => ({
410
+ const items = values.map((group) => ({
327
411
  name: group.title,
328
412
  value: `${key},${group.title}`,
329
413
  // Only include isParent and items if children exist
330
- ...(group.children && group.children.length > 0 && {
331
- isParent: true,
332
- items: group.children.map(child => ({
333
- name: child,
334
- value: `${key},${child}`,
335
- }))
336
- })
337
- }));
414
+ ...(group.children &&
415
+ group.children.length > 0 && {
416
+ isParent: true,
417
+ items: group.children.map((child) => ({
418
+ name: child,
419
+ value: `${key},${child}`,
420
+ })),
421
+ }),
422
+ }))
338
423
 
339
424
  return {
340
425
  title: capitalizeFirstLetter(key),
@@ -342,20 +427,20 @@ function transformFilters(filterOptions) {
342
427
  key,
343
428
  items,
344
429
  isHierarchical: true,
345
- };
430
+ }
346
431
  } else {
347
432
  // Handle flat structure (existing behavior - no changes)
348
433
  return {
349
434
  title: capitalizeFirstLetter(key),
350
435
  type: filterTypes[key] || 'checkbox',
351
436
  key,
352
- items: values.map(value => ({
437
+ items: values.map((value) => ({
353
438
  name: value,
354
439
  value: `${key},${key === 'progress' ? value.toLowerCase() : value}`,
355
440
  })),
356
- };
441
+ }
357
442
  }
358
- });
443
+ })
359
444
  }
360
445
 
361
446
  /**
@@ -62,6 +62,7 @@ export const DEFAULT_FIELDS = [
62
62
  "'permission_id': permission_v2",
63
63
  'child_count',
64
64
  '"parent_id": parent_content_data[0].id',
65
+ '"grandparent_id": parent_content_data[1].id',
65
66
  ]
66
67
 
67
68
  // these are identical... why
@@ -157,13 +158,12 @@ export const showsTypes = {
157
158
  }
158
159
 
159
160
  export const coachLessonsTypes = [
161
+ 'course-collection',
160
162
  'course',
161
163
  'course-lesson',
162
164
  'coach-stream',
163
165
  'student-focus',
164
166
  'quick-tips',
165
- 'pack',
166
- 'semester-pack',
167
167
  'question-and-answer',
168
168
  'song-tutorial',
169
169
  'song-tutorial-lesson',
@@ -203,7 +203,7 @@ export const individualLessonsTypes = [
203
203
 
204
204
  export const coursesLessonTypes = [
205
205
  'course',
206
- 'tiered-course', // TODO: new content type
206
+ 'course-collection',
207
207
  'guided-course',
208
208
  ]
209
209
 
@@ -218,11 +218,7 @@ export const showsLessonTypes = [
218
218
  'spotlight',
219
219
  'performance',
220
220
  ]
221
- export const entertainmentLessonTypes = [
222
- 'special',
223
- 'documentary-lesson',
224
- ...showsLessonTypes,
225
- ]
221
+ export const entertainmentLessonTypes = ['special', 'documentary-lesson', ...showsLessonTypes]
226
222
  export const collectionLessonTypes = [...coursesLessonTypes, ...showsLessonTypes]
227
223
 
228
224
  export const lessonTypesMapping = {
@@ -258,8 +254,7 @@ export const lessonTypesMapping = {
258
254
  export const getNextLessonLessonParentTypes = [
259
255
  'course',
260
256
  'guided-course',
261
- 'pack',
262
- 'pack-bundle',
257
+ 'course-collection',
263
258
  'song-tutorial',
264
259
  'learning-path-v2',
265
260
  'skill-pack',
@@ -274,7 +269,7 @@ export const progressTypesMapping = {
274
269
  ...studentArchivesLessonTypes,
275
270
  'documentary-lesson',
276
271
  'live',
277
- 'pack-bundle-lesson',
272
+ 'course-lesson'
278
273
  ],
279
274
  course: ['course'],
280
275
  show: showsLessonTypes,
@@ -282,7 +277,7 @@ export const progressTypesMapping = {
282
277
  songs: transcriptionsLessonTypes,
283
278
  'play along': playAlongLessonTypes,
284
279
  'guided course': ['guided-course'],
285
- pack: ['pack', 'semester-pack'],
280
+ 'course collection': ['course-collection'],
286
281
  'learning path': ['learning-path-v2'],
287
282
  'skill pack': skillLessonTypes,
288
283
  'jam track': jamTrackLessonTypes,
@@ -319,12 +314,12 @@ export const recentTypes = {
319
314
  lessons: [
320
315
  ...individualLessonsTypes,
321
316
  'course-lesson',
322
- 'pack-bundle-lesson',
323
317
  'guided-course-lesson',
324
318
  'quick-tips',
325
319
  ],
326
320
  songs: [...SONG_TYPES],
327
321
  home: [
322
+ ...skillLessonTypes,
328
323
  ...individualLessonsTypes,
329
324
  ...tutorialsLessonTypes,
330
325
  ...skillLessonTypes,
@@ -334,7 +329,7 @@ export const recentTypes = {
334
329
  'learning-path-v2',
335
330
  'live',
336
331
  'course',
337
- 'pack',
332
+ 'course-collection',
338
333
  ],
339
334
  }
340
335
 
@@ -347,7 +342,6 @@ export const ownedContentTypes = {
347
342
  ...coursesLessonTypes,
348
343
  ...skillLessonTypes,
349
344
  ...entertainmentLessonTypes,
350
- 'pack',
351
345
  ],
352
346
  songs: [
353
347
  ...tutorialsLessonTypes,
@@ -420,6 +414,9 @@ export let contentTypeConfig = {
420
414
  ],
421
415
  slug: 'courses',
422
416
  },
417
+ 'course-lesson': {
418
+ fields: [`"resources": ${resourcesField}`],
419
+ },
423
420
  download: {
424
421
  fields: [
425
422
  `"resource": ${resourcesField}`,
@@ -495,7 +492,7 @@ export let contentTypeConfig = {
495
492
  ],
496
493
  slug: 'play-alongs',
497
494
  },
498
- pack: {
495
+ 'course-collection': {
499
496
  fields: [
500
497
  '"lesson_count": coalesce(count(child[]->.child[]->), 0)',
501
498
  `"description": ${descriptionField}`,
@@ -521,24 +518,9 @@ export let contentTypeConfig = {
521
518
  slug: 'rudiments',
522
519
  },
523
520
  routine: {
524
- fields: [`"description": ${descriptionField}`, 'high_soundslice_slug', 'low_soundslice_slug'],
521
+ fields: [`"description": ${descriptionField}`, 'soundslice_slug'],
525
522
  slug: 'routines',
526
523
  },
527
- 'pack-children': {
528
- fields: [
529
- 'child_count',
530
- `"resources": ${resourcesField}`,
531
- '"image": logo_image_url.asset->url',
532
- '"thumbnail": thumbnail.asset->url',
533
- '"light_mode_logo": light_mode_logo_url.asset->url',
534
- '"dark_mode_logo": dark_mode_logo_url.asset->url',
535
- `"description": ${descriptionField}`,
536
- ],
537
- childFields: [`"description": ${descriptionField}`],
538
- },
539
- 'pack-bundle-lesson': {
540
- fields: [`"resources": ${resourcesField}`],
541
- },
542
524
  foundation: {
543
525
  fields: [
544
526
  `"description": ${descriptionField}`,
package/src/index.d.ts CHANGED
@@ -257,7 +257,6 @@ import {
257
257
  buildEntityAndTotalQuery,
258
258
  fetchAll,
259
259
  fetchAllFilterOptions,
260
- fetchAllPacks,
261
260
  fetchBrandsByContentIds,
262
261
  fetchByRailContentId,
263
262
  fetchByRailContentIds,
@@ -267,6 +266,7 @@ import {
267
266
  fetchCommentModContentData,
268
267
  fetchContentRows,
269
268
  fetchContentTypeCounts,
269
+ fetchCourseCollectionData,
270
270
  fetchHierarchy,
271
271
  fetchLearningPathHierarchy,
272
272
  fetchLeaving,
@@ -280,7 +280,6 @@ import {
280
280
  fetchNewReleases,
281
281
  fetchOtherSongVersions,
282
282
  fetchOwnedContent,
283
- fetchPackAll,
284
283
  fetchPackData,
285
284
  fetchPlayAlongsCount,
286
285
  fetchRecent,
@@ -479,7 +478,6 @@ declare module 'musora-content-services' {
479
478
  extractSanityUrl,
480
479
  fetchAll,
481
480
  fetchAllFilterOptions,
482
- fetchAllPacks,
483
481
  fetchArtistBySlug,
484
482
  fetchArtistLessons,
485
483
  fetchArtists,
@@ -499,6 +497,7 @@ declare module 'musora-content-services' {
499
497
  fetchContentPageUserData,
500
498
  fetchContentRows,
501
499
  fetchContentTypeCounts,
500
+ fetchCourseCollectionData,
502
501
  fetchCustomerPayments,
503
502
  fetchEnrollmentPageMetadata,
504
503
  fetchFollowedThreads,
@@ -531,7 +530,6 @@ declare module 'musora-content-services' {
531
530
  fetchNotifications,
532
531
  fetchOtherSongVersions,
533
532
  fetchOwnedContent,
534
- fetchPackAll,
535
533
  fetchPackData,
536
534
  fetchPlayAlongsCount,
537
535
  fetchPlaylist,
package/src/index.js CHANGED
@@ -261,7 +261,6 @@ import {
261
261
  buildEntityAndTotalQuery,
262
262
  fetchAll,
263
263
  fetchAllFilterOptions,
264
- fetchAllPacks,
265
264
  fetchBrandsByContentIds,
266
265
  fetchByRailContentId,
267
266
  fetchByRailContentIds,
@@ -271,6 +270,7 @@ import {
271
270
  fetchCommentModContentData,
272
271
  fetchContentRows,
273
272
  fetchContentTypeCounts,
273
+ fetchCourseCollectionData,
274
274
  fetchHierarchy,
275
275
  fetchLearningPathHierarchy,
276
276
  fetchLeaving,
@@ -284,7 +284,6 @@ import {
284
284
  fetchNewReleases,
285
285
  fetchOtherSongVersions,
286
286
  fetchOwnedContent,
287
- fetchPackAll,
288
287
  fetchPackData,
289
288
  fetchPlayAlongsCount,
290
289
  fetchRecent,
@@ -478,7 +477,6 @@ export {
478
477
  extractSanityUrl,
479
478
  fetchAll,
480
479
  fetchAllFilterOptions,
481
- fetchAllPacks,
482
480
  fetchArtistBySlug,
483
481
  fetchArtistLessons,
484
482
  fetchArtists,
@@ -498,6 +496,7 @@ export {
498
496
  fetchContentPageUserData,
499
497
  fetchContentRows,
500
498
  fetchContentTypeCounts,
499
+ fetchCourseCollectionData,
501
500
  fetchCustomerPayments,
502
501
  fetchEnrollmentPageMetadata,
503
502
  fetchFollowedThreads,
@@ -530,7 +529,6 @@ export {
530
529
  fetchNotifications,
531
530
  fetchOtherSongVersions,
532
531
  fetchOwnedContent,
533
- fetchPackAll,
534
532
  fetchPackData,
535
533
  fetchPlayAlongsCount,
536
534
  fetchPlaylist,
@@ -11,7 +11,7 @@ import {
11
11
  fetchUpcomingEvents,
12
12
  fetchScheduledReleases,
13
13
  fetchReturning,
14
- fetchLeaving, fetchScheduledAndNewReleases, fetchContentRows, fetchOwnedContent
14
+ fetchLeaving, fetchScheduledAndNewReleases, fetchContentRows, fetchOwnedContent, fetchCourseCollectionData
15
15
  } from './sanity.js'
16
16
  import {TabResponseType, Tabs, capitalizeFirstLetter} from '../contentMetaData.js'
17
17
  import {recommendations, rankCategories, rankItems} from "./recommendations";
@@ -19,6 +19,8 @@ import {addContextToContent} from "./contentAggregator.js";
19
19
  import {globalConfig} from "./config";
20
20
  import {getUserData} from "./user/management";
21
21
  import {filterTypes, ownedContentTypes} from "../contentTypeConfig";
22
+ import {getPermissionsAdapter} from "./permissions/index.ts";
23
+ import {MEMBERSHIP_PERMISSIONS} from "../constants/membership-permissions.ts";
22
24
 
23
25
 
24
26
  export async function getLessonContentRows (brand='drumeo', pageName = 'lessons') {
@@ -458,24 +460,33 @@ export async function getRecommendedForYou(brand, rowId = null, {
458
460
  * .then(content => console.log(content))
459
461
  * .catch(error => console.error(error));
460
462
  */
461
- export async function getLegacyMethods(brand) {
462
-
463
- // TODO: Replace with real data from Sanity when available with permissions
464
-
465
- return [
466
- {
467
- id: 1,
468
- title: '2020 Method',
469
- type: 'pack',
470
- child_count: 12,
471
- },
472
- {
473
- id: 2,
474
- title: '2016 Foundations',
475
- type: 'pack',
476
- child_count: 12,
477
- },
478
- ]
463
+ export async function getLegacyMethods(brand)
464
+ {
465
+ const brandMap = {
466
+ drumeo: [241247],
467
+ pianote: [
468
+ 276693,
469
+ 215952 //Foundations 2019
470
+ ],
471
+ singeo: [308514],
472
+ guitareo: [333652],
473
+ }
474
+ const ids = brandMap[brand] ?? null;
475
+ if (!ids) return [];
476
+ const adapter = getPermissionsAdapter()
477
+ const userPermissionsData = await adapter.fetchUserPermissions()
478
+ const userPermissions = userPermissionsData.permissions
479
+ // Users should only have access to this if they have an active membership AS WELL as the content access
480
+ // This is hardcoded behaviour and isn't found elsewhere
481
+ const hasMembership = userPermissionsData.isAdmin
482
+ || userPermissions.includes(MEMBERSHIP_PERMISSIONS.base)
483
+ || userPermissions.includes(MEMBERSHIP_PERMISSIONS.plus)
484
+ const hasContentPermission = userPermissions.includes(100000000 + ids[0])
485
+ if (hasMembership && hasContentPermission) {
486
+ return Promise.all(ids.map(id => fetchCourseCollectionData(id)))
487
+ } else {
488
+ return []
489
+ }
479
490
  }
480
491
 
481
492
  /**
@@ -509,7 +520,7 @@ export async function getLegacyMethods(brand) {
509
520
  * @example
510
521
  * // Fetch owned content filtered by types
511
522
  * getOwnedContent('drumeo', {
512
- * type: ['course', 'pack'],
523
+ * type: ['course', 'course-collection'],
513
524
  * page: 1,
514
525
  * limit: 10
515
526
  * })
@@ -20,7 +20,7 @@ import {getContentAwardsByIds} from "./awards/award-query.js";
20
20
  * {} <- fetchLessonContent || on playback page (main window)
21
21
  * in the examples below, dataField would be set to `children`
22
22
  * [{id, children}, {id, children,}] <- getTabData || catalog Page
23
- * {childen, } <- getPackData || pack index page
23
+ * {children, } <- getCourseCollectionData || Course Collection index page
24
24
  *
25
25
  *
26
26
  * @param dataPromise - promise or method that provides sanity data
@@ -94,7 +94,7 @@ export async function getNavigateTo(data, collection = null) {
94
94
  collection = normalizeCollection(collection)
95
95
  let navigateToData = {}
96
96
 
97
- const twoDepthContentTypes = ['pack'] // not adding method because it has its own logic (with active path)
97
+ const twoDepthContentTypes = ['course-collection'] // not adding method because it has its own logic (with active path)
98
98
  //TODO add parent hierarchy upwards as well
99
99
  // data structure is the same but instead of child{} we use parent{}
100
100
  for (const content of data) {
@@ -134,7 +134,7 @@ export async function getNavigateTo(data, collection = null) {
134
134
  const lastInteracted = await getLastInteractedOf(childrenIds, collection)
135
135
  const lastInteractedStatus = childrenStates[lastInteracted]
136
136
 
137
- if (['course', 'pack-bundle', 'skill-pack'].includes(content.type)) {
137
+ if (['course', 'skill-pack'].includes(content.type)) {
138
138
  if (lastInteractedStatus === STATE_STARTED) {
139
139
  // send to last interacted
140
140
  navigateToData[content.id] = buildNavigateTo(
@@ -169,7 +169,7 @@ export async function getNavigateTo(data, collection = null) {
169
169
  collection
170
170
  )
171
171
  if (childrenStates[lastInteractedChildId] === STATE_COMPLETED) {
172
- // TODO: packs have an extra situation where we need to jump to the next course if all lessons in the last engaged course are completed
172
+ // TODO: course collections have an extra situation where we need to jump to the next course if all lessons in the last engaged course are completed
173
173
  }
174
174
  let lastInteractedChildNavToData = await getNavigateTo(firstChildren, collection)
175
175
  lastInteractedChildNavToData = lastInteractedChildNavToData[lastInteractedChildId]
@@ -169,7 +169,7 @@ function getDefaultCTATextForContent(content, contentType) {
169
169
  if (contentType === 'lesson') ctaText = 'Revisit Lesson'
170
170
  if (contentType === 'song tutorial' || collectionLessonTypes.includes(content.type))
171
171
  ctaText = 'Revisit Lessons'
172
- if (contentType === 'pack') ctaText = 'View Lessons'
172
+ if (contentType === 'course-collection') ctaText = 'View Lessons'
173
173
  }
174
174
  return ctaText
175
175
  }
@@ -33,7 +33,7 @@ import {
33
33
  SONG_TYPES_WITH_CHILDREN,
34
34
  } from '../contentTypeConfig.js'
35
35
  import { fetchSimilarItems, recommendations } from './recommendations.js'
36
- import {getSongType, processMetadata, Tabs} from '../contentMetaData.js'
36
+ import { getSongType, processMetadata, Tabs } from '../contentMetaData.js'
37
37
  import { GET } from '../infrastructure/http/HttpClient.ts'
38
38
 
39
39
  import { globalConfig } from './config.js'
@@ -55,7 +55,7 @@ const excludeFromGeneratedIndex = ['fetchRelatedByLicense']
55
55
  *
56
56
  * @type {object[]}
57
57
  */
58
- export const ALWAYS_VISIBLE_TABS = [Tabs.ForYou, Tabs.ExploreAll];
58
+ export const ALWAYS_VISIBLE_TABS = [Tabs.ForYou, Tabs.ExploreAll]
59
59
 
60
60
  /**
61
61
  * Mapping from tab names to their underlying Sanity content types.
@@ -64,13 +64,13 @@ export const ALWAYS_VISIBLE_TABS = [Tabs.ForYou, Tabs.ExploreAll];
64
64
  */
65
65
  const TAB_TO_CONTENT_TYPES = {
66
66
  'Single Lessons': individualLessonsTypes,
67
- 'Courses': coursesLessonTypes,
67
+ Courses: coursesLessonTypes,
68
68
  'Skill Packs': skillLessonTypes,
69
- 'Entertainment': entertainmentLessonTypes,
70
- 'Tutorials': tutorialsLessonTypes,
71
- 'Transcriptions': transcriptionsLessonTypes,
69
+ Entertainment: entertainmentLessonTypes,
70
+ Tutorials: tutorialsLessonTypes,
71
+ Transcriptions: transcriptionsLessonTypes,
72
72
  'Sheet Music': transcriptionsLessonTypes,
73
- 'Tabs': transcriptionsLessonTypes,
73
+ Tabs: transcriptionsLessonTypes,
74
74
  'Play-Alongs': playAlongLessonTypes,
75
75
  'Jam Tracks': jamTrackLessonTypes,
76
76
  }
@@ -639,8 +639,6 @@ export async function fetchAll(
639
639
  bypassStatusAndPublishedValidation = true
640
640
  } else if (type === 'lessons' || type === 'songs') {
641
641
  typeFilter = ``
642
- } else if (type === 'pack') {
643
- typeFilter = `&& (_type == 'pack' || _type == 'semester-pack')`
644
642
  } else {
645
643
  typeFilter = type ? `&& _type == '${type}'` : ''
646
644
  }
@@ -1163,50 +1161,6 @@ export async function fetchRelatedLessons(railContentId) {
1163
1161
  return await fetchSanity(query, false, { processNeedAccess: true })
1164
1162
  }
1165
1163
 
1166
- /**
1167
- * Fetch all packs.
1168
- * @param {string} brand - The brand for which to fetch packs.
1169
- * @param {string} [searchTerm=""] - The search term to filter packs.
1170
- * @param {string} [sort="-published_on"] - The field to sort the packs by.
1171
- * @param {number} [params.page=1] - The page number for pagination.
1172
- * @param {number} [params.limit=10] - The number of items per page.
1173
- * @returns {Promise<Array<Object>|null>} - The fetched pack content data or null if not found.
1174
- */
1175
- export async function fetchAllPacks(
1176
- brand,
1177
- sort = '-published_on',
1178
- searchTerm = '',
1179
- page = 1,
1180
- limit = 10
1181
- ) {
1182
- const sortOrder = getSortOrder(sort, brand)
1183
- const filter = `(_type == 'pack' || _type == 'semester-pack') && brand == '${brand}' && title match "${searchTerm}*"`
1184
- const filterParams = {}
1185
- const start = (page - 1) * limit
1186
- const end = start + limit
1187
-
1188
- const query = await buildQuery(
1189
- filter,
1190
- filterParams,
1191
- await getFieldsForContentTypeWithFilteredChildren('pack'),
1192
- {
1193
- sortOrder: sortOrder,
1194
- start,
1195
- end,
1196
- }
1197
- )
1198
- return fetchSanity(query, true)
1199
- }
1200
-
1201
- /**
1202
- * Fetch all content for a specific pack by Railcontent ID.
1203
- * @param {string} railcontentId - The Railcontent ID of the pack.
1204
- * @returns {Promise<Array<Object>|null>} - The fetched pack content data or null if not found.
1205
- */
1206
- export async function fetchPackAll(railcontentId, type = 'pack') {
1207
- return fetchByRailContentId(railcontentId, type)
1208
- }
1209
-
1210
1164
  export async function fetchLiveEvent(brand, forcedContentId = null) {
1211
1165
  const LIVE_EXTRA_MINUTES = 30
1212
1166
  //calendarIDs taken from addevent.php
@@ -1283,6 +1237,25 @@ export async function fetchLiveEvent(brand, forcedContentId = null) {
1283
1237
  }
1284
1238
 
1285
1239
  /**
1240
+ * Fetch the data needed for the CourseCollection Overview screen.
1241
+ * @param {number} id - The Railcontent ID of the CourseCollection
1242
+ * @returns {Promise<Object|null>} - The CourseCollection information and lessons or null if not found.
1243
+ *
1244
+ * @example
1245
+ * fetchCourseCollectionData(404048)
1246
+ * .then(CourseCollection => console.log(CourseCollection))
1247
+ * .catch(error => console.error(error));
1248
+ */
1249
+ export async function fetchCourseCollectionData(id) {
1250
+ const builder = await new FilterBuilder(`railcontent_id == ${id}`).buildFilter()
1251
+ const query = `*[${builder}]{
1252
+ ${await getFieldsForContentTypeWithFilteredChildren('course-collection')}
1253
+ } [0...1]`
1254
+ return fetchSanity(query, false)
1255
+ }
1256
+
1257
+ /**
1258
+ * DEPRECATED: Use fetchCourseCollectionData
1286
1259
  * Fetch the data needed for the Pack Overview screen.
1287
1260
  * @param {number} id - The Railcontent ID of the pack
1288
1261
  * @returns {Promise<Object|null>} - The pack information and lessons or null if not found.
@@ -1293,11 +1266,7 @@ export async function fetchLiveEvent(brand, forcedContentId = null) {
1293
1266
  * .catch(error => console.error(error));
1294
1267
  */
1295
1268
  export async function fetchPackData(id) {
1296
- const builder = await new FilterBuilder(`railcontent_id == ${id}`).buildFilter()
1297
- const query = `*[${builder}]{
1298
- ${await getFieldsForContentTypeWithFilteredChildren('pack')}
1299
- } [0...1]`
1300
- return fetchSanity(query, false)
1269
+ return fetchCourseCollectionData(id)
1301
1270
  }
1302
1271
 
1303
1272
  /**
@@ -2258,9 +2227,9 @@ export async function fetchContentTypeCounts(brand, pageName) {
2258
2227
  * @returns {Array} - Filtered array of tabs with content
2259
2228
  */
2260
2229
  function filterTabsByContentCounts(tabs, contentTypeCounts) {
2261
- return tabs.filter(tab => {
2262
- if (ALWAYS_VISIBLE_TABS.some(visibleTab => visibleTab.name === tab.name)) {
2263
- return true;
2230
+ return tabs.filter((tab) => {
2231
+ if (ALWAYS_VISIBLE_TABS.some((visibleTab) => visibleTab.name === tab.name)) {
2232
+ return true
2264
2233
  }
2265
2234
 
2266
2235
  const tabContentTypes = TAB_TO_CONTENT_TYPES[tab.name] || []
@@ -2359,7 +2328,7 @@ function getContentTypesForFilterName(displayName) {
2359
2328
  'Student Archives': 'student archives',
2360
2329
  Courses: 'courses',
2361
2330
  'Guided Courses': 'guided courses',
2362
- 'Tiered Courses': 'tiered courses',
2331
+ 'Course Collections': 'course collections',
2363
2332
  Specials: 'specials',
2364
2333
  Documentaries: 'documentaries',
2365
2334
  Shows: 'shows',
@@ -1,9 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(rg:*)",
5
- "Bash(npm run lint:*)"
6
- ],
7
- "deny": []
8
- }
9
- }