musora-content-services 2.169.4 → 2.170.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.
@@ -1,16 +1,39 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(rg:*)",
5
- "Bash(npm run lint:*)",
6
- "Bash(ls:*)",
4
+ "Bash(npx jest *)",
5
+ "Bash(npx tsc *)",
6
+ "Skill(counselors)",
7
+ "Bash(counselors ls *)",
8
+ "Bash(counselors groups *)",
9
+ "Bash(counselors run *)",
7
10
  "Bash(npm test *)",
8
- "Bash(npx jest *)"
9
- ],
10
- "deny": []
11
+ "Bash(gh pr *)",
12
+ "Bash(gh api *)",
13
+ "Bash(mkdir -p /tmp/pr-review-v2)",
14
+ "Read(//tmp/pr-review-v2/**)",
15
+ "Bash(cat /home/alesevero/railenvironment/applications/musora-content-services/AGENTS.md)",
16
+ "Bash(echo \"no AGENTS.md\")",
17
+ "Bash(echo \"exit=$?\")",
18
+ "Bash(git checkout *)",
19
+ "Skill(pr)",
20
+ "Skill(create-decision)",
21
+ "mcp__github__pull_request_read",
22
+ "mcp__github__push_files",
23
+ "Bash(git push *)",
24
+ "mcp__github__create_pull_request",
25
+ "mcp__github__search_pull_requests",
26
+ "Bash(git commit *)",
27
+ "Bash(git add *)",
28
+ "mcp__github__list_pull_requests",
29
+ "mcp__sanity__get_schema",
30
+ "mcp__sanity__query_documents",
31
+ "Bash(git rm *)",
32
+ "Bash(git rebase *)"
33
+ ]
11
34
  },
35
+ "model": "sonnet",
12
36
  "enabledMcpjsonServers": [
13
- "1password",
14
37
  "atlassian",
15
38
  "figma",
16
39
  "google-workspace",
@@ -24,10 +47,14 @@
24
47
  "chrome-devtools",
25
48
  "railway",
26
49
  "github",
27
- "asana",
50
+ "asana"
51
+ ],
52
+ "disabledMcpjsonServers": [
28
53
  "nightwatch",
54
+ "1password",
29
55
  "mysql-staging",
30
56
  "mysql-local",
31
57
  "mobile"
32
- ]
58
+ ],
59
+ "effortLevel": "medium"
33
60
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
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.170.0](https://github.com/railroadmedia/musora-content-services/compare/v2.169.4...v2.170.0) (2026-08-10)
6
+
7
+ ## [2.169.0-0](https://github.com/railroadmedia/musora-content-services/compare/v2.168.0...v2.169.0-0) (2026-07-31)
8
+
9
+
10
+ ### Features
11
+
12
+ * **TP-1300:** add badge field to Liker, ForumUser, and User interfaces ([8a3ee80](https://github.com/railroadmedia/musora-content-services/commit/8a3ee802722a0d9dcd41a6a3998ff2a7a3352aeb))
13
+
5
14
  ### [2.169.4](https://github.com/railroadmedia/musora-content-services/compare/v2.169.3...v2.169.4) (2026-08-07)
6
15
 
7
16
 
@@ -30,6 +39,7 @@ All notable changes to this project will be documented in this file. See [standa
30
39
 
31
40
  ### Features
32
41
 
42
+ * **TP-1300:** add badge field to Liker, ForumUser, and User interfaces ([8a3ee80](https://github.com/railroadmedia/musora-content-services/commit/8a3ee802722a0d9dcd41a6a3998ff2a7a3352aeb))
33
43
  * **BEHSTP-415:** MCS public announcement fetch fn's ([#1010](https://github.com/railroadmedia/musora-content-services/issues/1010)) ([d504fb1](https://github.com/railroadmedia/musora-content-services/commit/d504fb105ba5303bfe7327ddef24fe468bdec374))
34
44
 
35
45
  ## [2.168.0](https://github.com/railroadmedia/musora-content-services/compare/v2.167.6...v2.168.0) (2026-07-30)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.169.4",
3
+ "version": "2.170.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -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.isAdmin) {
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.isAdmin(this.userData) && !this.showOnlyOwnedContent) return this
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, {
@@ -27,6 +27,9 @@ const perms: UserPermissions = {
27
27
  isAdmin: false,
28
28
  isModerator: false,
29
29
  isABasicMember: true,
30
+ hasAllContentAccess: false,
31
+ isForumModerator: false,
32
+ isCommentModerator: false,
30
33
  }
31
34
 
32
35
  const rows: ContentRow[] = [
@@ -22,7 +22,7 @@ export function lifetimeUpgradeDecorator(
22
22
  return {
23
23
  field: NEED_LIFETIME_UPGRADE_FIELD,
24
24
  compute: (item) => {
25
- if (userPermissions?.isAdmin || !hasLifetime || hasPlus) return false
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
  },
@@ -23,7 +23,7 @@ export interface StatusConfig {
23
23
  statuses?: string[]
24
24
  bypassStatuses?: boolean
25
25
  isSingle?: boolean
26
- isAdmin?: boolean
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.isAdmin(userData)) return ''
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 isAdmin = getPermissionsAdapter().isAdmin(userData)
376
+ const hasAllContentAccess = getPermissionsAdapter().hasAllContentAccess(userData)
377
377
 
378
- if (config.isAdmin || isAdmin) {
378
+ if (config.hasAllContentAccess || hasAllContentAccess) {
379
379
  statuses = [
380
380
  STATUS_DRAFT,
381
381
  STATUS_SCHEDULED,
@@ -34,7 +34,8 @@ export async function getEndScreen({
34
34
  brand
35
35
  }: GetEndScreenParams): Promise<EndScreenResult> {
36
36
  const userData = await getUserData()
37
- const isAdmin = userData?.is_admin ?? false
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.isAdmin(userPermissions)) {
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.isAdmin(userPermissions)) {
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.isAdmin(userPermissions)) {
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.isAdmin(userPermissions)) {
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.isAdmin) return false
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.isAdmin
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
  }
@@ -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 isAdmin = adapter.isAdmin(userPermissionsData)
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 = !isAdmin && !['instructor', 'artist', 'genre'].includes(contentType)
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 isAdminORModerator = adapter.isAdmin(userData) || adapter.isModerator(userData)
2120
+ const hasAllContentAccess = adapter.hasAllContentAccess(userData)
2121
2121
 
2122
2122
  const filterWithRestrictions = await new FilterBuilder(filter, {
2123
2123
  showMembershipRestrictedContent: true,
2124
- availableContentStatuses: isAdminORModerator
2124
+ availableContentStatuses: hasAllContentAccess
2125
2125
  ? CONTENT_STATUSES.ADMIN_ALL
2126
2126
  : CONTENT_STATUSES.PUBLISHED_ONLY,
2127
- pullFutureContent: isAdminORModerator ? true : false,
2127
+ pullFutureContent: hasAllContentAccess
2128
2128
  }).buildFilter()
2129
2129
  query = buildEntityAndTotalQuery(filterWithRestrictions, entityFieldsString, {
2130
2130
  sortOrder: sortOrder,
@@ -89,6 +89,7 @@ export interface User {
89
89
  first_access_at: string
90
90
  is_challenge_owner: number
91
91
  login_as_users: boolean
92
+ badge: string | null
92
93
  }
93
94
 
94
95
  export interface AuthResponse {
@@ -1 +1 @@
1
- export const MCS_VERSION = '2.169.4'
1
+ export const MCS_VERSION = '2.170.0'
Binary file
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });