musora-content-services 2.167.6 → 2.169.0-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/.claude/settings.local.json +4 -1
- package/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/filterBuilder.js +2 -2
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -0
- package/src/lib/sanity/decorators/examples.ts +3 -0
- package/src/lib/sanity/decorators/need-lifetime-upgrade.ts +1 -1
- package/src/lib/sanity/filter.ts +4 -4
- package/src/services/content/counts.ts +20 -0
- package/src/services/endScreen/endScreen.ts +2 -1
- package/src/services/forums/types.ts +2 -0
- package/src/services/permissions/PermissionsAdapter.ts +24 -0
- package/src/services/permissions/PermissionsV1Adapter.ts +2 -2
- package/src/services/permissions/PermissionsV2Adapter.ts +2 -2
- package/src/services/permissions/permissions.ts +2 -2
- package/src/services/permissions/types.d.ts +3 -0
- package/src/services/sanity.js +5 -5
- package/src/services/user/memberships.ts +12 -0
- package/src/services/user/types.d.ts +1 -0
- package/src/version-info.js +1 -1
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.169.0-0](https://github.com/railroadmedia/musora-content-services/compare/v2.168.0...v2.169.0-0) (2026-07-31)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **TP-1300:** add badge field to Liker, ForumUser, and User interfaces ([8a3ee80](https://github.com/railroadmedia/musora-content-services/commit/8a3ee802722a0d9dcd41a6a3998ff2a7a3352aeb))
|
|
11
|
+
|
|
12
|
+
## [2.168.0](https://github.com/railroadmedia/musora-content-services/compare/v2.167.6...v2.168.0) (2026-07-30)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **BEHLTP-186:** split membership and subscriber data for membership controller- [#1414](https://github.com/railroadmedia/musora-content-services/issues/1414) ([#1022](https://github.com/railroadmedia/musora-content-services/issues/1022)) ([728f03a](https://github.com/railroadmedia/musora-content-services/commit/728f03ae25a8ddb0ee337a7dd6034bb65ba56297))
|
|
18
|
+
|
|
5
19
|
### [2.167.6](https://github.com/railroadmedia/musora-content-services/compare/v2.167.5...v2.167.6) (2026-07-29)
|
|
6
20
|
|
|
7
21
|
### [2.167.5](https://github.com/railroadmedia/musora-content-services/compare/v2.167.4...v2.167.5) (2026-07-24)
|
package/package.json
CHANGED
package/src/filterBuilder.js
CHANGED
|
@@ -95,7 +95,7 @@ export class FilterBuilder {
|
|
|
95
95
|
// This must be run before _applyPublishDateRestrictions()
|
|
96
96
|
if (this.bypassStatuses) return this
|
|
97
97
|
if (this.availableContentStatuses.length === 0) {
|
|
98
|
-
if (this.userData.
|
|
98
|
+
if (this.userData.hasAllContentAccess) {
|
|
99
99
|
this.availableContentStatuses = [
|
|
100
100
|
this.STATUS_DRAFT,
|
|
101
101
|
this.STATUS_SCHEDULED,
|
|
@@ -136,7 +136,7 @@ export class FilterBuilder {
|
|
|
136
136
|
if (this.bypassPermissions && !this.showOnlyOwnedContent) return this
|
|
137
137
|
const adapter = getPermissionsAdapter()
|
|
138
138
|
// Check if admin (admins bypass permissions)
|
|
139
|
-
if (adapter.
|
|
139
|
+
if (adapter.hasAllContentAccess(this.userData) && !this.showOnlyOwnedContent) return this
|
|
140
140
|
|
|
141
141
|
// Generate permissions filter using adapter
|
|
142
142
|
const permissionsFilter = adapter.generatePermissionsFilter(this.userData, {
|
package/src/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from './services/content/artist.ts';
|
|
31
31
|
|
|
32
32
|
import {
|
|
33
|
+
fetchLearningPathCount,
|
|
33
34
|
fetchSongAndLessonCounts
|
|
34
35
|
} from './services/content/counts.ts';
|
|
35
36
|
|
|
@@ -619,6 +620,7 @@ declare module 'musora-content-services' {
|
|
|
619
620
|
fetchInvite,
|
|
620
621
|
fetchLastSubscriptionPlatform,
|
|
621
622
|
fetchLatestThreads,
|
|
623
|
+
fetchLearningPathCount,
|
|
622
624
|
fetchLearningPathLessons,
|
|
623
625
|
fetchLearningPathProgressCheckLessons,
|
|
624
626
|
fetchLeaving,
|
package/src/index.js
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from './services/content/artist.ts';
|
|
35
35
|
|
|
36
36
|
import {
|
|
37
|
+
fetchLearningPathCount,
|
|
37
38
|
fetchSongAndLessonCounts
|
|
38
39
|
} from './services/content/counts.ts';
|
|
39
40
|
|
|
@@ -618,6 +619,7 @@ export {
|
|
|
618
619
|
fetchInvite,
|
|
619
620
|
fetchLastSubscriptionPlatform,
|
|
620
621
|
fetchLatestThreads,
|
|
622
|
+
fetchLearningPathCount,
|
|
621
623
|
fetchLearningPathLessons,
|
|
622
624
|
fetchLearningPathProgressCheckLessons,
|
|
623
625
|
fetchLeaving,
|
|
@@ -22,7 +22,7 @@ export function lifetimeUpgradeDecorator(
|
|
|
22
22
|
return {
|
|
23
23
|
field: NEED_LIFETIME_UPGRADE_FIELD,
|
|
24
24
|
compute: (item) => {
|
|
25
|
-
if (userPermissions?.
|
|
25
|
+
if (userPermissions?.hasAllContentAccess || !hasLifetime || hasPlus) return false
|
|
26
26
|
if (!item.need_access) return false
|
|
27
27
|
return item.membership_tier === 'plus'
|
|
28
28
|
},
|
package/src/lib/sanity/filter.ts
CHANGED
|
@@ -23,7 +23,7 @@ export interface StatusConfig {
|
|
|
23
23
|
statuses?: string[]
|
|
24
24
|
bypassStatuses?: boolean
|
|
25
25
|
isSingle?: boolean
|
|
26
|
-
|
|
26
|
+
hasAllContentAccess?: boolean
|
|
27
27
|
prefix?: Prefix
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -349,7 +349,7 @@ export class Filters {
|
|
|
349
349
|
const adapter = getPermissionsAdapter()
|
|
350
350
|
const userData = config.userData || (await adapter.fetchUserPermissions())
|
|
351
351
|
|
|
352
|
-
if (adapter.
|
|
352
|
+
if (adapter.hasAllContentAccess(userData)) return ''
|
|
353
353
|
|
|
354
354
|
const permissionsFilter = adapter.generatePermissionsFilter(userData, {
|
|
355
355
|
prefix: config.prefix || '',
|
|
@@ -373,9 +373,9 @@ export class Filters {
|
|
|
373
373
|
// Auto-determine statuses if not provided
|
|
374
374
|
if (statuses.length === 0) {
|
|
375
375
|
const userData = await getPermissionsAdapter().fetchUserPermissions()
|
|
376
|
-
const
|
|
376
|
+
const hasAllContentAccess = getPermissionsAdapter().hasAllContentAccess(userData)
|
|
377
377
|
|
|
378
|
-
if (config.
|
|
378
|
+
if (config.hasAllContentAccess || hasAllContentAccess) {
|
|
379
379
|
statuses = [
|
|
380
380
|
STATUS_DRAFT,
|
|
381
381
|
STATUS_SCHEDULED,
|
|
@@ -45,3 +45,23 @@ export async function fetchSongAndLessonCounts(
|
|
|
45
45
|
|
|
46
46
|
return { ...counts, total: counts.songs + counts.lessons }
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
export interface LearningPathCount {
|
|
50
|
+
total: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function fetchLearningPathCount(brand?: Brands | string): Promise<LearningPathCount> {
|
|
54
|
+
const learningPathFilter = f.combine(
|
|
55
|
+
f.typeIn(['learning-path-v2']),
|
|
56
|
+
f.statusIn(['published']),
|
|
57
|
+
f.defined('railcontent_id'),
|
|
58
|
+
brand ? f.brand(brand) : f.empty
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const q = `{
|
|
62
|
+
"total": count(${query().and(learningPathFilter)})
|
|
63
|
+
}`
|
|
64
|
+
|
|
65
|
+
const count = (await sanityClient.executeQuery<LearningPathCount>(q)) ?? { total: 0 }
|
|
66
|
+
return count
|
|
67
|
+
}
|
|
@@ -34,7 +34,8 @@ export async function getEndScreen({
|
|
|
34
34
|
brand
|
|
35
35
|
}: GetEndScreenParams): Promise<EndScreenResult> {
|
|
36
36
|
const userData = await getUserData()
|
|
37
|
-
|
|
37
|
+
|
|
38
|
+
const isAdmin = userData?.has_all_content_access ?? false
|
|
38
39
|
|
|
39
40
|
if (playlist) {
|
|
40
41
|
const nextItem = next_item ??
|
|
@@ -10,6 +10,7 @@ export interface Liker {
|
|
|
10
10
|
access_level: string
|
|
11
11
|
liked_at: string
|
|
12
12
|
liked_at_diff: string | null
|
|
13
|
+
badge: string | null
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export interface WhoLikedResponse {
|
|
@@ -27,6 +28,7 @@ export interface ForumUser {
|
|
|
27
28
|
profile_picture_url: string | null
|
|
28
29
|
access_level: string
|
|
29
30
|
signature: string | null
|
|
31
|
+
badge: string | null
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export interface ForumPost {
|
|
@@ -106,4 +106,28 @@ export abstract class PermissionsAdapter {
|
|
|
106
106
|
isModerator(userPermissions: UserPermissions): boolean {
|
|
107
107
|
return userPermissions?.isModerator ?? false
|
|
108
108
|
}
|
|
109
|
+
|
|
110
|
+
hasAllContentAccess(userPermissions: UserPermissions): boolean {
|
|
111
|
+
return userPermissions?.hasAllContentAccess ?? false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Check if user is a forum moderator.
|
|
116
|
+
*
|
|
117
|
+
* @param userPermissions - The user's permissions
|
|
118
|
+
* @returns True if user is a forum moderator
|
|
119
|
+
*/
|
|
120
|
+
isForumModerator(userPermissions: UserPermissions): boolean {
|
|
121
|
+
return userPermissions?.isForumModerator ?? false
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if user is a comment moderator.
|
|
126
|
+
*
|
|
127
|
+
* @param userPermissions - The user's permissions
|
|
128
|
+
* @returns True if user is a comment moderator
|
|
129
|
+
*/
|
|
130
|
+
isCommentModerator(userPermissions: UserPermissions): boolean {
|
|
131
|
+
return userPermissions?.isCommentModerator ?? false
|
|
132
|
+
}
|
|
109
133
|
}
|
|
@@ -52,7 +52,7 @@ export class PermissionsV1Adapter extends PermissionsAdapter {
|
|
|
52
52
|
*/
|
|
53
53
|
doesUserNeedAccess(content: ContentItem, userPermissions: UserPermissions): boolean {
|
|
54
54
|
// Admins always have access
|
|
55
|
-
if (this.
|
|
55
|
+
if (this.hasAllContentAccess(userPermissions)) {
|
|
56
56
|
return false
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -102,7 +102,7 @@ export class PermissionsV1Adapter extends PermissionsAdapter {
|
|
|
102
102
|
} = options
|
|
103
103
|
|
|
104
104
|
// Admins bypass permission filter
|
|
105
|
-
if (this.
|
|
105
|
+
if (this.hasAllContentAccess(userPermissions)) {
|
|
106
106
|
return null
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -47,7 +47,7 @@ export class PermissionsV2Adapter extends PermissionsAdapter {
|
|
|
47
47
|
*/
|
|
48
48
|
doesUserNeedAccess(content: ContentItem, userPermissions: UserPermissions): boolean {
|
|
49
49
|
// Admins always have access
|
|
50
|
-
if (this.
|
|
50
|
+
if (this.hasAllContentAccess(userPermissions)) {
|
|
51
51
|
return false
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -108,7 +108,7 @@ export class PermissionsV2Adapter extends PermissionsAdapter {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
// Admins bypass permission filter
|
|
111
|
-
if (this.
|
|
111
|
+
if (this.hasAllContentAccess(userPermissions)) {
|
|
112
112
|
return null
|
|
113
113
|
}
|
|
114
114
|
|
|
@@ -38,7 +38,7 @@ export async function reset(): Promise<void> {
|
|
|
38
38
|
export function isUserFreeTier(userPermissions: UserPermissions): boolean {
|
|
39
39
|
const permissions = userPermissions.permissions
|
|
40
40
|
|
|
41
|
-
if (userPermissions.
|
|
41
|
+
if (userPermissions.hasAllContentAccess) return false
|
|
42
42
|
|
|
43
43
|
return permissions.includes(MEMBERSHIP_PERMISSIONS.free)
|
|
44
44
|
&& !permissions.some(p => [MEMBERSHIP_PERMISSIONS.base, MEMBERSHIP_PERMISSIONS.plus].includes(p))
|
|
@@ -46,7 +46,7 @@ export function isUserFreeTier(userPermissions: UserPermissions): boolean {
|
|
|
46
46
|
|
|
47
47
|
export function doesUserHaveMembership(userPermissions: UserPermissions): boolean {
|
|
48
48
|
const permissions = userPermissions.permissions
|
|
49
|
-
return userPermissions.
|
|
49
|
+
return userPermissions.hasAllContentAccess
|
|
50
50
|
|| permissions.includes(MEMBERSHIP_PERMISSIONS.base)
|
|
51
51
|
|| permissions.includes(MEMBERSHIP_PERMISSIONS.plus)
|
|
52
52
|
}
|
|
@@ -3,4 +3,7 @@ export interface UserPermissions {
|
|
|
3
3
|
isAdmin: boolean // Whether the user is an admin
|
|
4
4
|
isModerator: boolean // Whether the user is a moderator
|
|
5
5
|
isABasicMember: boolean // Whether the user has basic membership
|
|
6
|
+
hasAllContentAccess: boolean
|
|
7
|
+
isForumModerator: boolean
|
|
8
|
+
isCommentModerator: boolean
|
|
6
9
|
}
|
package/src/services/sanity.js
CHANGED
|
@@ -899,14 +899,14 @@ export async function fetchAllFilterOptions(
|
|
|
899
899
|
const progressFilter = progressIds ? `&& railcontent_id in [${progressIds.join(',')}]` : ''
|
|
900
900
|
const adapter = getPermissionsAdapter()
|
|
901
901
|
const userPermissionsData = await adapter.fetchUserPermissions()
|
|
902
|
-
const
|
|
902
|
+
const hasAllContentAccess = adapter.hasAllContentAccess(userPermissionsData)
|
|
903
903
|
|
|
904
904
|
const constructCommonFilter = (excludeFilter) => {
|
|
905
905
|
const filterWithoutOption = excludeFilter
|
|
906
906
|
? filtersToGroq(filters, excludeFilter)
|
|
907
907
|
: includedFieldsFilter
|
|
908
908
|
const statusFilter = ' && status == "published"'
|
|
909
|
-
const includeStatusFilter = !
|
|
909
|
+
const includeStatusFilter = !hasAllContentAccess && !['instructor', 'artist', 'genre'].includes(contentType)
|
|
910
910
|
|
|
911
911
|
return coachId
|
|
912
912
|
? `brand == '${brand}' && status == "published" && references(*[_type=='instructor' && railcontent_id == ${coachId}]._id) ${filterWithoutOption || ''} ${term ? ` && (title match "${term}" || album match "${term}" || artist->name match "${term}" || genre[]->name match "${term}")` : ''}`
|
|
@@ -2117,14 +2117,14 @@ export async function fetchTabData(
|
|
|
2117
2117
|
// Check if user is admin to determine available content statuses
|
|
2118
2118
|
const adapter = getPermissionsAdapter()
|
|
2119
2119
|
const userData = await adapter.fetchUserPermissions()
|
|
2120
|
-
const
|
|
2120
|
+
const hasAllContentAccess = adapter.hasAllContentAccess(userData)
|
|
2121
2121
|
|
|
2122
2122
|
const filterWithRestrictions = await new FilterBuilder(filter, {
|
|
2123
2123
|
showMembershipRestrictedContent: true,
|
|
2124
|
-
availableContentStatuses:
|
|
2124
|
+
availableContentStatuses: hasAllContentAccess
|
|
2125
2125
|
? CONTENT_STATUSES.ADMIN_ALL
|
|
2126
2126
|
: CONTENT_STATUSES.PUBLISHED_ONLY,
|
|
2127
|
-
pullFutureContent:
|
|
2127
|
+
pullFutureContent: hasAllContentAccess
|
|
2128
2128
|
}).buildFilter()
|
|
2129
2129
|
query = buildEntityAndTotalQuery(filterWithRestrictions, entityFieldsString, {
|
|
2130
2130
|
sortOrder: sortOrder,
|
|
@@ -27,6 +27,18 @@ export interface UserMembershipResponse {
|
|
|
27
27
|
sub_account_data: MultiUserAccountResponse // post multiUserAccount data
|
|
28
28
|
upgrade_options: UpgradeOption[] // post multiuser account data
|
|
29
29
|
user_subscriber_data: {
|
|
30
|
+
active_subscription: {
|
|
31
|
+
subscribed_on: string
|
|
32
|
+
renews_on: string
|
|
33
|
+
plan: string
|
|
34
|
+
platform: 'web' | 'apple' | 'google'
|
|
35
|
+
product_sku: string
|
|
36
|
+
rc_product_ids: {
|
|
37
|
+
apple: string
|
|
38
|
+
google: string
|
|
39
|
+
}
|
|
40
|
+
tier_key: string
|
|
41
|
+
}
|
|
30
42
|
has_had_apple_subscription: boolean
|
|
31
43
|
has_had_google_subscription: boolean
|
|
32
44
|
has_had_web_subscription: boolean
|
package/src/version-info.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const MCS_VERSION = '2.
|
|
1
|
+
export const MCS_VERSION = '2.169.0-0'
|