musora-content-services 2.114.0 → 2.115.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/package.json +1 -1
- package/src/contentMetaData.js +30 -1
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/services/content.js +4 -1
- package/src/services/sanity.js +116 -98
- package/src/services/sync/models/Base.ts +5 -0
- package/src/services/sync/schema/index.ts +5 -0
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.115.1](https://github.com/railroadmedia/musora-content-services/compare/v2.115.0...v2.115.1) (2026-01-09)
|
|
6
|
+
|
|
7
|
+
## [2.115.0](https://github.com/railroadmedia/musora-content-services/compare/v2.114.0...v2.115.0) (2026-01-09)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* Use Get requests for Sanity if query under character limit ([#694](https://github.com/railroadmedia/musora-content-services/issues/694)) ([da5c384](https://github.com/railroadmedia/musora-content-services/commit/da5c384e7f538cf7c72a3d8f742787f24a4ed570))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **BEH-878:** correct brand song type names ([#695](https://github.com/railroadmedia/musora-content-services/issues/695)) ([2b1cde9](https://github.com/railroadmedia/musora-content-services/commit/2b1cde9978dd744129bd906aff3a9bb03e01b04e))
|
|
18
|
+
|
|
5
19
|
## [2.114.0](https://github.com/railroadmedia/musora-content-services/compare/v2.113.0...v2.114.0) (2026-01-08)
|
|
6
20
|
|
|
7
21
|
|
package/package.json
CHANGED
package/src/contentMetaData.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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";
|
|
5
|
+
|
|
4
6
|
const PROGRESS_NAMES = ['All', 'In Progress', 'Completed', 'Not Started']
|
|
5
7
|
const DIFFICULTY_STRINGS = ['Introductory', 'Beginner', 'Intermediate', 'Advanced', 'Expert']
|
|
6
8
|
|
|
@@ -68,7 +70,9 @@ export class Tabs {
|
|
|
68
70
|
static Artists = { name: 'Artists', short_name: 'ARTISTS', is_group_by: true, value: 'artist' }
|
|
69
71
|
static Songs = { name: 'Songs', short_name: 'Songs', value: '' }
|
|
70
72
|
static Tutorials = { name: 'Tutorials', short_name: 'Tutorials', value: 'type,tutorials', cardType: 'big' }
|
|
71
|
-
static Transcriptions = { name: 'Transcriptions', short_name: 'Transcriptions', value: 'type,
|
|
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' }
|
|
72
76
|
static PlayAlongs = { name: 'Play-Alongs', short_name: 'Play-Alongs', value:'type,play along', cardType: 'small' }
|
|
73
77
|
static JamTracks = { name: 'Jam Tracks', short_name: 'Jam Tracks', value:'type,jam-track', cardType: 'small' }
|
|
74
78
|
static RecentAll = { name: 'All', short_name: 'All' }
|
|
@@ -236,6 +240,7 @@ export function processMetadata(brand, type, withFilters = false) {
|
|
|
236
240
|
brandMetaData = { ...commonMetaData, ...brandMetaData }
|
|
237
241
|
if (type === 'songs' && contentMetadata[brand]?.['songs-types']) {
|
|
238
242
|
brandMetaData['filterOptions']['type'] = contentMetadata[brand]['songs-types']
|
|
243
|
+
brandMetaData.tabs = mapSongTabNames(brandMetaData)
|
|
239
244
|
}
|
|
240
245
|
if (Object.keys(brandMetaData).length === 0) {
|
|
241
246
|
return null
|
|
@@ -262,6 +267,26 @@ export function processMetadata(brand, type, withFilters = false) {
|
|
|
262
267
|
return processedData
|
|
263
268
|
}
|
|
264
269
|
|
|
270
|
+
function mapSongTabNames(brandMetaData) {
|
|
271
|
+
brandMetaData.tabs.forEach((tab, index) => {
|
|
272
|
+
if (ALWAYS_VISIBLE_TABS.some(visibleTab => visibleTab.name === tab)) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const targetName = brandMetaData['filterOptions']['type'][index - 1];
|
|
277
|
+
|
|
278
|
+
// Find the matching Tab by name
|
|
279
|
+
const matchingTab = Object.values(Tabs).find(
|
|
280
|
+
tabObj => tabObj.name === targetName
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (matchingTab) {
|
|
284
|
+
brandMetaData.tabs[index] = matchingTab;
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
return brandMetaData.tabs;
|
|
288
|
+
}
|
|
289
|
+
|
|
265
290
|
/**
|
|
266
291
|
* Defines the filter types for each key
|
|
267
292
|
*/
|
|
@@ -339,3 +364,7 @@ function transformFilters(filterOptions) {
|
|
|
339
364
|
export function capitalizeFirstLetter(string) {
|
|
340
365
|
return string.charAt(0).toUpperCase() + string.slice(1)
|
|
341
366
|
}
|
|
367
|
+
|
|
368
|
+
export function getSongType(brand) {
|
|
369
|
+
return contentMetadata[brand]?.['songs-types'][1]
|
|
370
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -300,6 +300,7 @@ import {
|
|
|
300
300
|
fetchTopLevelParentId,
|
|
301
301
|
fetchUpcomingEvents,
|
|
302
302
|
getSanityDate,
|
|
303
|
+
getSongTypesFor,
|
|
303
304
|
getSortOrder,
|
|
304
305
|
jumpToContinueContent
|
|
305
306
|
} from './services/sanity.js';
|
|
@@ -617,6 +618,7 @@ declare module 'musora-content-services' {
|
|
|
617
618
|
getResumeTimeSecondsByIdsAndCollections,
|
|
618
619
|
getSanityDate,
|
|
619
620
|
getScheduleContentRows,
|
|
621
|
+
getSongTypesFor,
|
|
620
622
|
getSortOrder,
|
|
621
623
|
getStartedOrCompletedProgressOnly,
|
|
622
624
|
getTabResults,
|
package/src/index.js
CHANGED
|
@@ -304,6 +304,7 @@ import {
|
|
|
304
304
|
fetchTopLevelParentId,
|
|
305
305
|
fetchUpcomingEvents,
|
|
306
306
|
getSanityDate,
|
|
307
|
+
getSongTypesFor,
|
|
307
308
|
getSortOrder,
|
|
308
309
|
jumpToContinueContent
|
|
309
310
|
} from './services/sanity.js';
|
|
@@ -616,6 +617,7 @@ export {
|
|
|
616
617
|
getResumeTimeSecondsByIdsAndCollections,
|
|
617
618
|
getSanityDate,
|
|
618
619
|
getScheduleContentRows,
|
|
620
|
+
getSongTypesFor,
|
|
619
621
|
getSortOrder,
|
|
620
622
|
getStartedOrCompletedProgressOnly,
|
|
621
623
|
getTabResults,
|
package/src/services/content.js
CHANGED
|
@@ -94,7 +94,10 @@ export async function getTabResults(brand, pageName, tabName, {
|
|
|
94
94
|
const filteredSelectedFilters = selectedFilters.filter(f => !f.startsWith('progress,'));
|
|
95
95
|
|
|
96
96
|
// Prepare included fields
|
|
97
|
-
const
|
|
97
|
+
const tabValue = Object.values(Tabs).find(
|
|
98
|
+
tabObj => tabObj.name === tabName
|
|
99
|
+
).value
|
|
100
|
+
const mergedIncludedFields = [...filteredSelectedFilters, tabValue];
|
|
98
101
|
|
|
99
102
|
// Fetch data
|
|
100
103
|
let results
|
package/src/services/sanity.js
CHANGED
|
@@ -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 { processMetadata } 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'
|
|
@@ -53,9 +53,9 @@ const excludeFromGeneratedIndex = ['fetchRelatedByLicense']
|
|
|
53
53
|
/**
|
|
54
54
|
* Song/Lesson tabs that are always visible.
|
|
55
55
|
*
|
|
56
|
-
* @type {
|
|
56
|
+
* @type {object[]}
|
|
57
57
|
*/
|
|
58
|
-
const ALWAYS_VISIBLE_TABS = [
|
|
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.
|
|
@@ -69,9 +69,11 @@ const TAB_TO_CONTENT_TYPES = {
|
|
|
69
69
|
'Entertainment': entertainmentLessonTypes,
|
|
70
70
|
'Tutorials': tutorialsLessonTypes,
|
|
71
71
|
'Transcriptions': transcriptionsLessonTypes,
|
|
72
|
+
'Sheet Music': transcriptionsLessonTypes,
|
|
73
|
+
'Tabs': transcriptionsLessonTypes,
|
|
72
74
|
'Play-Alongs': playAlongLessonTypes,
|
|
73
75
|
'Jam Tracks': jamTrackLessonTypes,
|
|
74
|
-
}
|
|
76
|
+
}
|
|
75
77
|
|
|
76
78
|
/**
|
|
77
79
|
* Fetch a song by its document ID from Sanity.
|
|
@@ -1492,22 +1494,34 @@ export async function fetchSanity(
|
|
|
1492
1494
|
if (!checkSanityConfig(globalConfig)) {
|
|
1493
1495
|
return null
|
|
1494
1496
|
}
|
|
1495
|
-
|
|
1496
1497
|
const perspective = globalConfig.sanityConfig.perspective ?? 'published'
|
|
1497
1498
|
const api = globalConfig.sanityConfig.useCachedAPI ? 'apicdn' : 'api'
|
|
1498
|
-
const
|
|
1499
|
+
const baseUrl = `https://${globalConfig.sanityConfig.projectId}.${api}.sanity.io/v${globalConfig.sanityConfig.version}/data/query/${globalConfig.sanityConfig.dataset}?perspective=${perspective}`
|
|
1499
1500
|
const headers = {
|
|
1500
1501
|
'Content-Type': 'application/json',
|
|
1501
1502
|
}
|
|
1502
|
-
|
|
1503
1503
|
try {
|
|
1504
|
-
const
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1504
|
+
const encodedQuery = encodeURIComponent(query)
|
|
1505
|
+
const fullGetUrl = `${baseUrl}&query=${encodedQuery}`
|
|
1506
|
+
const useGet = fullGetUrl.length < 8000
|
|
1507
|
+
|
|
1508
|
+
let url, method, options
|
|
1509
|
+
if (useGet) {
|
|
1510
|
+
url = fullGetUrl
|
|
1511
|
+
method = 'GET'
|
|
1512
|
+
options = {
|
|
1513
|
+
method,
|
|
1514
|
+
headers,
|
|
1515
|
+
}
|
|
1516
|
+
} else {
|
|
1517
|
+
url = baseUrl
|
|
1518
|
+
method = 'POST'
|
|
1519
|
+
options = {
|
|
1520
|
+
method,
|
|
1521
|
+
headers,
|
|
1522
|
+
body: JSON.stringify({ query }),
|
|
1523
|
+
}
|
|
1509
1524
|
}
|
|
1510
|
-
|
|
1511
1525
|
const adapter = getPermissionsAdapter()
|
|
1512
1526
|
let promisesResult = await Promise.all([
|
|
1513
1527
|
fetch(url, options),
|
|
@@ -1647,8 +1661,8 @@ export async function fetchShowsData(brand) {
|
|
|
1647
1661
|
*/
|
|
1648
1662
|
export async function fetchMetadata(brand, type, options = {}) {
|
|
1649
1663
|
// Handle backward compatibility - type was previously the 3rd param (boolean)
|
|
1650
|
-
const withFilters = typeof options === 'boolean' ? options : true
|
|
1651
|
-
const skipTabFiltering = options.skipTabFiltering || false
|
|
1664
|
+
const withFilters = typeof options === 'boolean' ? options : true
|
|
1665
|
+
const skipTabFiltering = options.skipTabFiltering || false
|
|
1652
1666
|
let processedData = processMetadata(brand, type, withFilters)
|
|
1653
1667
|
|
|
1654
1668
|
if (processedData?.onlyAvailableTabs === true) {
|
|
@@ -1659,23 +1673,20 @@ export async function fetchMetadata(brand, type, options = {}) {
|
|
|
1659
1673
|
if ((type === 'lessons' || type === 'songs') && !skipTabFiltering) {
|
|
1660
1674
|
try {
|
|
1661
1675
|
// Single API call to get all content type counts
|
|
1662
|
-
const contentTypeCounts = await fetchContentTypeCounts(brand, type)
|
|
1676
|
+
const contentTypeCounts = await fetchContentTypeCounts(brand, type)
|
|
1663
1677
|
|
|
1664
1678
|
// Filter tabs based on counts
|
|
1665
|
-
processedData.tabs = filterTabsByContentCounts(
|
|
1666
|
-
processedData.tabs,
|
|
1667
|
-
contentTypeCounts
|
|
1668
|
-
);
|
|
1679
|
+
processedData.tabs = filterTabsByContentCounts(processedData.tabs, contentTypeCounts)
|
|
1669
1680
|
|
|
1670
1681
|
// Filter Type options based on counts
|
|
1671
1682
|
if (processedData.filters) {
|
|
1672
1683
|
processedData.filters = filterTypeOptionsByContentCounts(
|
|
1673
1684
|
processedData.filters,
|
|
1674
1685
|
contentTypeCounts
|
|
1675
|
-
)
|
|
1686
|
+
)
|
|
1676
1687
|
}
|
|
1677
1688
|
} catch (error) {
|
|
1678
|
-
console.error('Error fetching content type counts, using all tabs/filters:', error)
|
|
1689
|
+
console.error('Error fetching content type counts, using all tabs/filters:', error)
|
|
1679
1690
|
// Fail open - show all tabs and filters
|
|
1680
1691
|
}
|
|
1681
1692
|
}
|
|
@@ -2188,7 +2199,7 @@ export async function fetchBrandsByContentIds(contentIds) {
|
|
|
2188
2199
|
* // Returns: ['lesson', 'quick-tips', 'course', 'guided-course', ...]
|
|
2189
2200
|
*/
|
|
2190
2201
|
function getAllContentTypesForPage(pageName) {
|
|
2191
|
-
return filterTypes[pageName] || []
|
|
2202
|
+
return filterTypes[pageName] || []
|
|
2192
2203
|
}
|
|
2193
2204
|
|
|
2194
2205
|
/**
|
|
@@ -2205,16 +2216,14 @@ function getAllContentTypesForPage(pageName) {
|
|
|
2205
2216
|
* // Returns: { 'guided-course': 45, 'skill-pack': 12, 'special': 8 }
|
|
2206
2217
|
*/
|
|
2207
2218
|
export async function fetchContentTypeCounts(brand, pageName) {
|
|
2208
|
-
const allContentTypes = getAllContentTypesForPage(pageName)
|
|
2219
|
+
const allContentTypes = getAllContentTypesForPage(pageName)
|
|
2209
2220
|
|
|
2210
2221
|
if (allContentTypes.length === 0) {
|
|
2211
|
-
return {}
|
|
2222
|
+
return {}
|
|
2212
2223
|
}
|
|
2213
2224
|
|
|
2214
2225
|
// Build array of type objects for GROQ query
|
|
2215
|
-
const typesString = allContentTypes
|
|
2216
|
-
.map(type => `{"type": "${type}"}`)
|
|
2217
|
-
.join(', ');
|
|
2226
|
+
const typesString = allContentTypes.map((type) => `{"type": "${type}"}`).join(', ')
|
|
2218
2227
|
|
|
2219
2228
|
const query = `{
|
|
2220
2229
|
"typeCounts": [${typesString}]{
|
|
@@ -2225,19 +2234,19 @@ export async function fetchContentTypeCounts(brand, pageName) {
|
|
|
2225
2234
|
&& status == "published"
|
|
2226
2235
|
])
|
|
2227
2236
|
}[count > 0]
|
|
2228
|
-
}
|
|
2237
|
+
}`
|
|
2229
2238
|
|
|
2230
|
-
const results = await fetchSanity(query, true, { processNeedAccess: false })
|
|
2239
|
+
const results = await fetchSanity(query, true, { processNeedAccess: false })
|
|
2231
2240
|
|
|
2232
2241
|
// Convert array to object for easier lookup: { 'guided-course': 45, ... }
|
|
2233
|
-
const countsMap = {}
|
|
2242
|
+
const countsMap = {}
|
|
2234
2243
|
if (results.typeCounts) {
|
|
2235
|
-
results.typeCounts.forEach(item => {
|
|
2236
|
-
countsMap[item.type] = item.count
|
|
2237
|
-
})
|
|
2244
|
+
results.typeCounts.forEach((item) => {
|
|
2245
|
+
countsMap[item.type] = item.count
|
|
2246
|
+
})
|
|
2238
2247
|
}
|
|
2239
2248
|
|
|
2240
|
-
return countsMap
|
|
2249
|
+
return countsMap
|
|
2241
2250
|
}
|
|
2242
2251
|
|
|
2243
2252
|
/**
|
|
@@ -2250,21 +2259,21 @@ export async function fetchContentTypeCounts(brand, pageName) {
|
|
|
2250
2259
|
*/
|
|
2251
2260
|
function filterTabsByContentCounts(tabs, contentTypeCounts) {
|
|
2252
2261
|
return tabs.filter(tab => {
|
|
2253
|
-
if (ALWAYS_VISIBLE_TABS.
|
|
2262
|
+
if (ALWAYS_VISIBLE_TABS.some(visibleTab => visibleTab.name === tab.name)) {
|
|
2254
2263
|
return true;
|
|
2255
2264
|
}
|
|
2256
2265
|
|
|
2257
|
-
const tabContentTypes = TAB_TO_CONTENT_TYPES[tab.name] || []
|
|
2266
|
+
const tabContentTypes = TAB_TO_CONTENT_TYPES[tab.name] || []
|
|
2258
2267
|
|
|
2259
2268
|
if (tabContentTypes.length === 0) {
|
|
2260
2269
|
// Unknown tab - show it to be safe
|
|
2261
|
-
console.warn(`Unknown tab "${tab.name}" - showing by default`)
|
|
2262
|
-
return true
|
|
2270
|
+
console.warn(`Unknown tab "${tab.name}" - showing by default`)
|
|
2271
|
+
return true
|
|
2263
2272
|
}
|
|
2264
2273
|
|
|
2265
2274
|
// Tab has content if ANY of its content types have count > 0
|
|
2266
|
-
return tabContentTypes.some(type => contentTypeCounts[type] > 0)
|
|
2267
|
-
})
|
|
2275
|
+
return tabContentTypes.some((type) => contentTypeCounts[type] > 0)
|
|
2276
|
+
})
|
|
2268
2277
|
}
|
|
2269
2278
|
|
|
2270
2279
|
/**
|
|
@@ -2277,60 +2286,64 @@ function filterTabsByContentCounts(tabs, contentTypeCounts) {
|
|
|
2277
2286
|
* @returns {Array} - Filtered filter groups
|
|
2278
2287
|
*/
|
|
2279
2288
|
function filterTypeOptionsByContentCounts(filters, contentTypeCounts) {
|
|
2280
|
-
return filters
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2289
|
+
return filters
|
|
2290
|
+
.map((filter) => {
|
|
2291
|
+
// Only process Type filter
|
|
2292
|
+
if (filter.key !== 'type') {
|
|
2293
|
+
return filter
|
|
2294
|
+
}
|
|
2285
2295
|
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2296
|
+
const filteredItems = filter.items
|
|
2297
|
+
.map((item) => {
|
|
2298
|
+
// For hierarchical filters (parent with children)
|
|
2299
|
+
if (item.isParent && item.items) {
|
|
2300
|
+
// Filter children based on their content types
|
|
2301
|
+
const availableChildren = item.items.filter((child) => {
|
|
2302
|
+
const childTypes = getContentTypesForFilterName(child.name)
|
|
2303
|
+
|
|
2304
|
+
if (!childTypes || childTypes.length === 0) {
|
|
2305
|
+
console.warn(`Unknown filter child "${child.name}" - showing by default`)
|
|
2306
|
+
return true
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
// Child has content if ANY of its types have count > 0
|
|
2310
|
+
return childTypes.some((type) => contentTypeCounts[type] > 0)
|
|
2311
|
+
})
|
|
2292
2312
|
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2313
|
+
// Keep parent only if it has available children
|
|
2314
|
+
if (availableChildren.length > 0) {
|
|
2315
|
+
// Return NEW object to avoid mutation
|
|
2316
|
+
return { ...item, items: availableChildren }
|
|
2317
|
+
}
|
|
2318
|
+
return null
|
|
2296
2319
|
}
|
|
2297
2320
|
|
|
2298
|
-
//
|
|
2299
|
-
|
|
2300
|
-
});
|
|
2321
|
+
// For flat items (no children)
|
|
2322
|
+
const itemTypes = getContentTypesForFilterName(item.name)
|
|
2301
2323
|
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
}
|
|
2307
|
-
return null;
|
|
2308
|
-
}
|
|
2324
|
+
if (!itemTypes || itemTypes.length === 0) {
|
|
2325
|
+
console.warn(`Unknown filter item "${item.name}" - showing by default`)
|
|
2326
|
+
return item
|
|
2327
|
+
}
|
|
2309
2328
|
|
|
2310
|
-
|
|
2311
|
-
|
|
2329
|
+
// Item has content if ANY of its types have count > 0
|
|
2330
|
+
const hasContent = itemTypes.some((type) => contentTypeCounts[type] > 0)
|
|
2331
|
+
return hasContent ? item : null
|
|
2332
|
+
})
|
|
2333
|
+
.filter(Boolean) // Remove nulls
|
|
2312
2334
|
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2335
|
+
// Return new filter object with filtered items
|
|
2336
|
+
return {
|
|
2337
|
+
...filter,
|
|
2338
|
+
items: filteredItems,
|
|
2316
2339
|
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
return {
|
|
2325
|
-
...filter,
|
|
2326
|
-
items: filteredItems
|
|
2327
|
-
};
|
|
2328
|
-
}).filter(filter => {
|
|
2329
|
-
if (filter.key === 'type' && filter.items.length === 0) {
|
|
2330
|
-
return false;
|
|
2331
|
-
}
|
|
2332
|
-
return true;
|
|
2333
|
-
});
|
|
2340
|
+
})
|
|
2341
|
+
.filter((filter) => {
|
|
2342
|
+
if (filter.key === 'type' && filter.items.length === 0) {
|
|
2343
|
+
return false
|
|
2344
|
+
}
|
|
2345
|
+
return true
|
|
2346
|
+
})
|
|
2334
2347
|
}
|
|
2335
2348
|
|
|
2336
2349
|
/**
|
|
@@ -2340,25 +2353,30 @@ function filterTypeOptionsByContentCounts(filters, contentTypeCounts) {
|
|
|
2340
2353
|
*/
|
|
2341
2354
|
function getContentTypesForFilterName(displayName) {
|
|
2342
2355
|
const displayNameToKey = {
|
|
2343
|
-
|
|
2356
|
+
Lessons: 'lessons',
|
|
2344
2357
|
'Practice Alongs': 'practice alongs',
|
|
2345
2358
|
'Live Archives': 'live archives',
|
|
2346
2359
|
'Student Archives': 'student archives',
|
|
2347
|
-
|
|
2360
|
+
Courses: 'courses',
|
|
2348
2361
|
'Guided Courses': 'guided courses',
|
|
2349
2362
|
'Tiered Courses': 'tiered courses',
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2363
|
+
Specials: 'specials',
|
|
2364
|
+
Documentaries: 'documentaries',
|
|
2365
|
+
Shows: 'shows',
|
|
2353
2366
|
'Skill Packs': 'skill packs',
|
|
2354
|
-
|
|
2355
|
-
|
|
2367
|
+
Tutorials: 'tutorials',
|
|
2368
|
+
Transcriptions: 'transcriptions',
|
|
2356
2369
|
'Sheet Music': 'sheet music',
|
|
2357
|
-
|
|
2370
|
+
Tabs: 'tabs',
|
|
2358
2371
|
'Play-Alongs': 'play-alongs',
|
|
2359
2372
|
'Jam Tracks': 'jam tracks',
|
|
2360
|
-
}
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
const mappingKey = displayNameToKey[displayName]
|
|
2376
|
+
return mappingKey ? lessonTypesMapping[mappingKey] : undefined
|
|
2377
|
+
}
|
|
2361
2378
|
|
|
2362
|
-
|
|
2363
|
-
|
|
2379
|
+
// this is so we can export the inner function from mcs
|
|
2380
|
+
export function getSongTypesFor(brand) {
|
|
2381
|
+
return getSongType(brand)
|
|
2364
2382
|
}
|
|
@@ -3,10 +3,15 @@ import { EpochMs } from '..'
|
|
|
3
3
|
|
|
4
4
|
export default abstract class BaseModel<ExtraRaw extends object = {}> extends Model {
|
|
5
5
|
declare _raw: RawRecord & ExtraRaw & {
|
|
6
|
+
server_record_id: number
|
|
6
7
|
created_at: EpochMs
|
|
7
8
|
updated_at: EpochMs
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
get server_record_id() {
|
|
12
|
+
return this._getRaw('server_record_id') as number
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
get created_at() {
|
|
11
16
|
return this._getRaw('created_at') as EpochMs
|
|
12
17
|
}
|
|
@@ -11,6 +11,7 @@ export const SYNC_TABLES = {
|
|
|
11
11
|
const contentLikesTable = tableSchema({
|
|
12
12
|
name: SYNC_TABLES.CONTENT_LIKES,
|
|
13
13
|
columns: [
|
|
14
|
+
{ name: 'server_record_id', type: 'number', isIndexed: true },
|
|
14
15
|
{ name: 'content_id', type: 'number', isIndexed: true },
|
|
15
16
|
{ name: 'created_at', type: 'number' },
|
|
16
17
|
{ name: 'updated_at', type: 'number' }
|
|
@@ -19,6 +20,7 @@ const contentLikesTable = tableSchema({
|
|
|
19
20
|
const contentProgressTable = tableSchema({
|
|
20
21
|
name: SYNC_TABLES.CONTENT_PROGRESS,
|
|
21
22
|
columns: [
|
|
23
|
+
{ name: 'server_record_id', type: 'number', isIndexed: true },
|
|
22
24
|
{ name: 'content_id', type: 'number', isIndexed: true },
|
|
23
25
|
{ name: 'content_brand', type: 'string', isOptional: true, isIndexed: true },
|
|
24
26
|
{ name: 'collection_type', type: 'string', isIndexed: true },
|
|
@@ -34,6 +36,7 @@ const contentProgressTable = tableSchema({
|
|
|
34
36
|
const practicesTable = tableSchema({
|
|
35
37
|
name: SYNC_TABLES.PRACTICES,
|
|
36
38
|
columns: [
|
|
39
|
+
{ name: 'server_record_id', type: 'number', isIndexed: true },
|
|
37
40
|
{ name: 'manual_id', type: 'string', isOptional: true },
|
|
38
41
|
{ name: 'content_id', type: 'number', isOptional: true, isIndexed: true },
|
|
39
42
|
{ name: 'date', type: 'string', isIndexed: true },
|
|
@@ -50,6 +53,7 @@ const practicesTable = tableSchema({
|
|
|
50
53
|
const practiceDayNotesTable = tableSchema({
|
|
51
54
|
name: SYNC_TABLES.PRACTICE_DAY_NOTES,
|
|
52
55
|
columns: [
|
|
56
|
+
{ name: 'server_record_id', type: 'number', isIndexed: true },
|
|
53
57
|
{ name: 'date', type: 'string', isIndexed: true },
|
|
54
58
|
{ name: 'notes', type: 'string' },
|
|
55
59
|
{ name: 'created_at', type: 'number' },
|
|
@@ -60,6 +64,7 @@ const practiceDayNotesTable = tableSchema({
|
|
|
60
64
|
const userAwardProgressTable = tableSchema({
|
|
61
65
|
name: SYNC_TABLES.USER_AWARD_PROGRESS,
|
|
62
66
|
columns: [
|
|
67
|
+
{ name: 'server_record_id', type: 'number', isIndexed: true },
|
|
63
68
|
{ name: 'award_id', type: 'string', isIndexed: true },
|
|
64
69
|
{ name: 'progress_percentage', type: 'number' },
|
|
65
70
|
{ name: 'completed_at', type: 'string', isOptional: true, isIndexed: true },
|