musora-content-services 2.169.0-0 → 2.169.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,39 +1,16 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(npx jest *)",
5
- "Bash(npx tsc *)",
6
- "Skill(counselors)",
7
- "Bash(counselors ls *)",
8
- "Bash(counselors groups *)",
9
- "Bash(counselors run *)",
4
+ "Bash(rg:*)",
5
+ "Bash(npm run lint:*)",
6
+ "Bash(ls:*)",
10
7
  "Bash(npm test *)",
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
- ]
8
+ "Bash(npx jest *)"
9
+ ],
10
+ "deny": []
34
11
  },
35
- "model": "sonnet",
36
12
  "enabledMcpjsonServers": [
13
+ "1password",
37
14
  "atlassian",
38
15
  "figma",
39
16
  "google-workspace",
@@ -47,14 +24,10 @@
47
24
  "chrome-devtools",
48
25
  "railway",
49
26
  "github",
50
- "asana"
51
- ],
52
- "disabledMcpjsonServers": [
27
+ "asana",
53
28
  "nightwatch",
54
- "1password",
55
29
  "mysql-staging",
56
30
  "mysql-local",
57
31
  "mobile"
58
- ],
59
- "effortLevel": "medium"
32
+ ]
60
33
  }
Binary file
package/CHANGELOG.md CHANGED
@@ -2,12 +2,12 @@
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)
5
+ ## [2.169.0](https://github.com/railroadmedia/musora-content-services/compare/v2.168.0...v2.169.0) (2026-08-04)
6
6
 
7
7
 
8
8
  ### Features
9
9
 
10
- * **TP-1300:** add badge field to Liker, ForumUser, and User interfaces ([8a3ee80](https://github.com/railroadmedia/musora-content-services/commit/8a3ee802722a0d9dcd41a6a3998ff2a7a3352aeb))
10
+ * **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))
11
11
 
12
12
  ## [2.168.0](https://github.com/railroadmedia/musora-content-services/compare/v2.167.6...v2.168.0) (2026-07-30)
13
13
 
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.169.0-0",
3
+ "version": "2.169.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -45,6 +45,7 @@
45
45
  "ts-jest": "^29.2.4"
46
46
  },
47
47
  "dependencies": {
48
+ "@portabletext/to-html": "^2.0.14",
48
49
  "@sanity/client": "^5.4.2",
49
50
  "dayjs": "^1.11.13",
50
51
  "docdash": "^2.0.2",
@@ -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.hasAllContentAccess) {
98
+ if (this.userData.isAdmin) {
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.hasAllContentAccess(this.userData) && !this.showOnlyOwnedContent) return this
139
+ if (adapter.isAdmin(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
@@ -235,6 +235,28 @@ import {
235
235
  updateMultiUserAccount
236
236
  } from './services/multi-user-accounts/multi-user-accounts.ts';
237
237
 
238
+ import {
239
+ deleteAllNotifications,
240
+ deleteNotification,
241
+ fetchLiveEventPollingState,
242
+ fetchNotificationSettings,
243
+ fetchNotifications,
244
+ fetchUnreadCount,
245
+ markAllNotificationsAsRead,
246
+ markNotificationAsRead,
247
+ markNotificationAsUnread,
248
+ pauseLiveEventPolling,
249
+ restoreNotification,
250
+ startLiveEventPolling,
251
+ updateNotificationSetting
252
+ } from './services/notifications/notifications.js';
253
+
254
+ import {
255
+ blockContentToHtml,
256
+ fetchAllPublicAnnouncements,
257
+ fetchPublicAnnouncement
258
+ } from './services/notifications/public-announcements.ts';
259
+
238
260
  import {
239
261
  getRecentActivityOffline
240
262
  } from './services/offline/activities.ts';
@@ -439,22 +461,6 @@ import {
439
461
  upgradeSubscription
440
462
  } from './services/user/memberships.ts';
441
463
 
442
- import {
443
- deleteAllNotifications,
444
- deleteNotification,
445
- fetchLiveEventPollingState,
446
- fetchNotificationSettings,
447
- fetchNotifications,
448
- fetchUnreadCount,
449
- markAllNotificationsAsRead,
450
- markNotificationAsRead,
451
- markNotificationAsUnread,
452
- pauseLiveEventPolling,
453
- restoreNotification,
454
- startLiveEventPolling,
455
- updateNotificationSetting
456
- } from './services/user/notifications.js';
457
-
458
464
  import {
459
465
  getOnboardingRecommendedContent,
460
466
  getOnboardingStatus,
@@ -534,6 +540,7 @@ declare module 'musora-content-services' {
534
540
  applyCloudflareWrapper,
535
541
  applySanityTransformations,
536
542
  assignModeratorToComment,
543
+ blockContentToHtml,
537
544
  blockUser,
538
545
  blockedUsers,
539
546
  buildEntityAndTotalQuery,
@@ -585,6 +592,7 @@ declare module 'musora-content-services' {
585
592
  extractSanityUrl,
586
593
  fetchAll,
587
594
  fetchAllFilterOptions,
595
+ fetchAllPublicAnnouncements,
588
596
  fetchArtistBySlug,
589
597
  fetchArtistLessons,
590
598
  fetchArtists,
@@ -649,6 +657,7 @@ declare module 'musora-content-services' {
649
657
  fetchPost,
650
658
  fetchPosts,
651
659
  fetchPracticeGoals,
660
+ fetchPublicAnnouncement,
652
661
  fetchRecent,
653
662
  fetchRecentActivitiesActiveTabs,
654
663
  fetchRecentUserActivities,
package/src/index.js CHANGED
@@ -239,6 +239,28 @@ import {
239
239
  updateMultiUserAccount
240
240
  } from './services/multi-user-accounts/multi-user-accounts.ts';
241
241
 
242
+ import {
243
+ deleteAllNotifications,
244
+ deleteNotification,
245
+ fetchLiveEventPollingState,
246
+ fetchNotificationSettings,
247
+ fetchNotifications,
248
+ fetchUnreadCount,
249
+ markAllNotificationsAsRead,
250
+ markNotificationAsRead,
251
+ markNotificationAsUnread,
252
+ pauseLiveEventPolling,
253
+ restoreNotification,
254
+ startLiveEventPolling,
255
+ updateNotificationSetting
256
+ } from './services/notifications/notifications.js';
257
+
258
+ import {
259
+ blockContentToHtml,
260
+ fetchAllPublicAnnouncements,
261
+ fetchPublicAnnouncement
262
+ } from './services/notifications/public-announcements.ts';
263
+
242
264
  import {
243
265
  getRecentActivityOffline
244
266
  } from './services/offline/activities.ts';
@@ -443,22 +465,6 @@ import {
443
465
  upgradeSubscription
444
466
  } from './services/user/memberships.ts';
445
467
 
446
- import {
447
- deleteAllNotifications,
448
- deleteNotification,
449
- fetchLiveEventPollingState,
450
- fetchNotificationSettings,
451
- fetchNotifications,
452
- fetchUnreadCount,
453
- markAllNotificationsAsRead,
454
- markNotificationAsRead,
455
- markNotificationAsUnread,
456
- pauseLiveEventPolling,
457
- restoreNotification,
458
- startLiveEventPolling,
459
- updateNotificationSetting
460
- } from './services/user/notifications.js';
461
-
462
468
  import {
463
469
  getOnboardingRecommendedContent,
464
470
  getOnboardingStatus,
@@ -533,6 +539,7 @@ export {
533
539
  applyCloudflareWrapper,
534
540
  applySanityTransformations,
535
541
  assignModeratorToComment,
542
+ blockContentToHtml,
536
543
  blockUser,
537
544
  blockedUsers,
538
545
  buildEntityAndTotalQuery,
@@ -584,6 +591,7 @@ export {
584
591
  extractSanityUrl,
585
592
  fetchAll,
586
593
  fetchAllFilterOptions,
594
+ fetchAllPublicAnnouncements,
587
595
  fetchArtistBySlug,
588
596
  fetchArtistLessons,
589
597
  fetchArtists,
@@ -648,6 +656,7 @@ export {
648
656
  fetchPost,
649
657
  fetchPosts,
650
658
  fetchPracticeGoals,
659
+ fetchPublicAnnouncement,
651
660
  fetchRecent,
652
661
  fetchRecentActivitiesActiveTabs,
653
662
  fetchRecentUserActivities,
@@ -27,9 +27,6 @@ const perms: UserPermissions = {
27
27
  isAdmin: false,
28
28
  isModerator: false,
29
29
  isABasicMember: true,
30
- hasAllContentAccess: false,
31
- isForumModerator: false,
32
- isCommentModerator: false,
33
30
  }
34
31
 
35
32
  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?.hasAllContentAccess || !hasLifetime || hasPlus) return false
25
+ if (userPermissions?.isAdmin || !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
- hasAllContentAccess?: boolean
26
+ isAdmin?: 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.hasAllContentAccess(userData)) return ''
352
+ if (adapter.isAdmin(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 hasAllContentAccess = getPermissionsAdapter().hasAllContentAccess(userData)
376
+ const isAdmin = getPermissionsAdapter().isAdmin(userData)
377
377
 
378
- if (config.hasAllContentAccess || hasAllContentAccess) {
378
+ if (config.isAdmin || isAdmin) {
379
379
  statuses = [
380
380
  STATUS_DRAFT,
381
381
  STATUS_SCHEDULED,
@@ -34,8 +34,7 @@ export async function getEndScreen({
34
34
  brand
35
35
  }: GetEndScreenParams): Promise<EndScreenResult> {
36
36
  const userData = await getUserData()
37
-
38
- const isAdmin = userData?.has_all_content_access ?? false
37
+ const isAdmin = userData?.is_admin ?? false
39
38
 
40
39
  if (playlist) {
41
40
  const nextItem = next_item ??
@@ -1,4 +1,4 @@
1
- import {fetchLiveEventPollingState, fetchUnreadCount, pauseLiveEventPolling} from "./user/notifications"
1
+ import {fetchLiveEventPollingState, fetchUnreadCount, pauseLiveEventPolling} from "./notifications/notifications.js"
2
2
  import {fetchLiveEvent} from "./sanity"
3
3
  import { DataContext, PollingStateVersionKey } from './dataContext'
4
4
 
@@ -10,7 +10,6 @@ export interface Liker {
10
10
  access_level: string
11
11
  liked_at: string
12
12
  liked_at_diff: string | null
13
- badge: string | null
14
13
  }
15
14
 
16
15
  export interface WhoLikedResponse {
@@ -28,7 +27,6 @@ export interface ForumUser {
28
27
  profile_picture_url: string | null
29
28
  access_level: string
30
29
  signature: string | null
31
- badge: string | null
32
30
  }
33
31
 
34
32
  export interface ForumPost {
@@ -3,9 +3,9 @@
3
3
  */
4
4
  import { GET, PUT, DELETE } from '../../infrastructure/http/HttpClient.ts'
5
5
  import eventsAPI from '../eventsAPI.js'
6
- import './types.js'
7
- import {globalConfig} from "../config";
8
- import {fetchLiveEvent} from "../sanity";
6
+ import '../user/types.js'
7
+ import {globalConfig} from "../config.js";
8
+ import {fetchLiveEvent} from "../sanity.js";
9
9
 
10
10
  const baseUrl = `/api/notifications`
11
11
 
@@ -0,0 +1,234 @@
1
+ import { fetchSanity, getSanityDate } from '../sanity.js'
2
+ import { globalConfig } from '../config.js'
3
+
4
+ interface PublicAnnouncement {
5
+ title: string
6
+ slug: string
7
+ published_on: string
8
+ description: string
9
+ message: string
10
+ forum_data?: Record<string, ForumData>
11
+ }
12
+
13
+ interface ForumData {
14
+ category_id: string
15
+ forum_id: string
16
+ url: string
17
+ }
18
+
19
+ /**
20
+ * Fetches a specific public announcement by slug, or by default the most recent published one.
21
+ * @param {string|null} slug
22
+ * @returns {Promise<PublicAnnouncement|null>} The public announcement object, or null if not found
23
+ */
24
+ export async function fetchPublicAnnouncement(slug?: string): Promise<PublicAnnouncement | null> {
25
+ const now = getSanityDate(new Date())
26
+
27
+ const slugString = slug ? `&& slug.current == '${slug}'` : ''
28
+
29
+ // grab the specific one by slug, or the most recent one if no slug is provided
30
+ const query = `*[_type == 'public-announcement' && published_on <= '${now}' ${slugString}]
31
+ {
32
+ title,
33
+ 'slug': slug.current,
34
+ published_on,
35
+ description,
36
+ message,
37
+ forum_data
38
+ }
39
+ | order(published_on desc)`
40
+
41
+ const result = await fetchSanity(query, false, {processNeedAccess: false, processPageType: false})
42
+
43
+ if (result?.message) {
44
+ result.message = blockContentToHtml(result.message)
45
+ }
46
+
47
+ return result
48
+ }
49
+
50
+ /**
51
+ * Fetches all public announcements published within the last `spanDays` days (default 1 year).
52
+ * @param {number|null} spanDays - Number of days to look back for public announcements (default: 1 year)
53
+ * @returns {Promise<PublicAnnouncement[]|null>} Array of public announcement objects, or null if none found
54
+ */
55
+ export async function fetchAllPublicAnnouncements(spanDays: number = 365): Promise<PublicAnnouncement[] | null> {
56
+ const rawNowDate = new Date()
57
+ const rawNow = new Date().getTime()
58
+ const nowDate = getSanityDate(rawNowDate)
59
+ const startDate = getSanityDate(new Date(rawNow - spanDays * 24 * 60 * 60 * 1000))
60
+
61
+ const query = `*[_type == 'public-announcement' && published_on >= '${startDate}' && published_on <= '${nowDate}']
62
+ {
63
+ title,
64
+ 'slug': slug.current,
65
+ published_on
66
+ }
67
+ | order(published_on desc)`
68
+
69
+ return await fetchSanity(query, true, {processNeedAccess: false, processPageType: false})
70
+ }
71
+
72
+ interface ListStackEntry {
73
+ tag: 'ul' | 'ol'
74
+ level: number
75
+ }
76
+
77
+ /**
78
+ * @param {Array<Object>} blocks - Sanity Portable Text / block content array
79
+ * @returns {string} HTML string
80
+ */
81
+ export function blockContentToHtml(blocks: any[]): string {
82
+ if (!blocks) {
83
+ return ''
84
+ }
85
+
86
+ let html = ''
87
+ const listStack: ListStackEntry[] = []
88
+
89
+ for (const block of blocks) {
90
+ const listItem = block.listItem ?? null
91
+
92
+ if (!listItem) {
93
+ html += closeLists(listStack, 0)
94
+ html += block._type === 'image' ? renderImage(block) : renderBlock(block)
95
+ continue
96
+ }
97
+
98
+ const level = block.level ?? 1
99
+ const tag: 'ul' | 'ol' = listItem === 'number' ? 'ol' : 'ul'
100
+
101
+ while (
102
+ listStack.length &&
103
+ (listStack[listStack.length - 1].level > level ||
104
+ (listStack[listStack.length - 1].level === level && listStack[listStack.length - 1].tag !== tag))
105
+ ) {
106
+ const closed = listStack.pop()
107
+ html += `</${closed.tag}>`
108
+ }
109
+
110
+ while (!listStack.length || listStack[listStack.length - 1].level < level) {
111
+ listStack.push({ tag, level: listStack.length + 1 })
112
+ html += `<${tag}>`
113
+ }
114
+
115
+ html += `<li>${renderInline(block)}</li>`
116
+ }
117
+
118
+ html += closeLists(listStack, 0)
119
+ return html
120
+ }
121
+
122
+ function closeLists(listStack: ListStackEntry[], downToLevel: number): string {
123
+ let html = ''
124
+ while (listStack.length && listStack[listStack.length - 1].level > downToLevel) {
125
+ const closed = listStack.pop()
126
+ html += `</${closed.tag}>`
127
+ }
128
+ return html
129
+ }
130
+
131
+ function renderInline(block: any): string {
132
+ const markDefsByKey: Record<string, any> = {}
133
+ for (const markDef of block.markDefs ?? []) {
134
+ markDefsByKey[markDef._key] = markDef
135
+ }
136
+
137
+ let inner = ''
138
+ for (const child of block.children ?? []) {
139
+ inner += renderSpan(child, markDefsByKey)
140
+ }
141
+
142
+ return inner === '' ? '&nbsp;' : inner
143
+ }
144
+
145
+ function renderBlock(block: any): string {
146
+ const tag = ((): string => {
147
+ switch (block.style ?? 'normal') {
148
+ case 'h1':
149
+ return 'h1'
150
+ case 'h2':
151
+ return 'h2'
152
+ case 'h3':
153
+ return 'h3'
154
+ case 'h4':
155
+ return 'h4'
156
+ case 'blockquote':
157
+ return 'blockquote'
158
+ default:
159
+ return 'p'
160
+ }
161
+ })()
162
+
163
+ const inner = renderInline(block)
164
+
165
+ return `<${tag}>${inner}</${tag}>`
166
+ }
167
+
168
+ function renderSpan(child: any, markDefsByKey: Record<string, any>): string {
169
+ let text = escapeHtml(child.text ?? '')
170
+ text = text.replace(/\n/g, '<br />')
171
+
172
+ for (const mark of child.marks ?? []) {
173
+ switch (true) {
174
+ case mark === 'strong':
175
+ text = `<strong>${text}</strong>`
176
+ break
177
+ case mark === 'em':
178
+ text = `<em>${text}</em>`
179
+ break
180
+ case mark === 'underline':
181
+ text = `<u>${text}</u>`
182
+ break
183
+ case mark === 'code':
184
+ text = `<code>${text}</code>`
185
+ break
186
+ case markDefsByKey[mark] !== undefined:
187
+ text = renderMarkDef(markDefsByKey[mark], text)
188
+ break
189
+ }
190
+ }
191
+
192
+ return text
193
+ }
194
+
195
+ function renderMarkDef(markDef: any, text: string): string {
196
+ if (markDef?._type !== 'link') {
197
+ return text
198
+ }
199
+ const href = escapeHtml(markDef.href ?? '')
200
+ return `<a href="${href}">${text}</a>`
201
+ }
202
+
203
+ function renderImage(block: any): string {
204
+ const ref = block.asset?._ref ?? null
205
+ const percent = block.display_width_percent ?? 100
206
+
207
+ const url = sanityImageRefToUrl(ref)
208
+ if (!url) {
209
+ return ''
210
+ }
211
+
212
+ return percent < 100
213
+ ? `<img src="${url}" style="width:${percent}%;max-width:100%;" />`
214
+ : `<img src="${url}" />`
215
+ }
216
+
217
+ function escapeHtml(text: string): string {
218
+ return text
219
+ .replace(/&/g, '&amp;')
220
+ .replace(/"/g, '&quot;')
221
+ .replace(/'/g, '&#039;')
222
+ .replace(/</g, '&lt;')
223
+ .replace(/>/g, '&gt;')
224
+ }
225
+
226
+ function sanityImageRefToUrl(ref: string | null | undefined): string | null {
227
+ const match = ref?.match(/^image-([a-f0-9]+)-(\d+x\d+)-(\w+)$/)
228
+ if (!match) {
229
+ return null
230
+ }
231
+ const [, assetId, dimensions, format] = match
232
+ const { projectId, dataset } = globalConfig.sanityConfig
233
+ return `https://cdn.sanity.io/images/${projectId}/${dataset}/${assetId}-${dimensions}.${format}`
234
+ }
@@ -106,28 +106,4 @@ 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
- }
133
109
  }
@@ -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.hasAllContentAccess(userPermissions)) {
55
+ if (this.isAdmin(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.hasAllContentAccess(userPermissions)) {
105
+ if (this.isAdmin(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.hasAllContentAccess(userPermissions)) {
50
+ if (this.isAdmin(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.hasAllContentAccess(userPermissions)) {
111
+ if (this.isAdmin(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.hasAllContentAccess) return false
41
+ if (userPermissions.isAdmin) 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.hasAllContentAccess
49
+ return userPermissions.isAdmin
50
50
  || permissions.includes(MEMBERSHIP_PERMISSIONS.base)
51
51
  || permissions.includes(MEMBERSHIP_PERMISSIONS.plus)
52
52
  }
@@ -3,7 +3,4 @@ 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
9
6
  }
@@ -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 hasAllContentAccess = adapter.hasAllContentAccess(userPermissionsData)
902
+ const isAdmin = adapter.isAdmin(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 = !hasAllContentAccess && !['instructor', 'artist', 'genre'].includes(contentType)
909
+ const includeStatusFilter = !isAdmin && !['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 hasAllContentAccess = adapter.hasAllContentAccess(userData)
2120
+ const isAdminORModerator = adapter.isAdmin(userData) || adapter.isModerator(userData)
2121
2121
 
2122
2122
  const filterWithRestrictions = await new FilterBuilder(filter, {
2123
2123
  showMembershipRestrictedContent: true,
2124
- availableContentStatuses: hasAllContentAccess
2124
+ availableContentStatuses: isAdminORModerator
2125
2125
  ? CONTENT_STATUSES.ADMIN_ALL
2126
2126
  : CONTENT_STATUSES.PUBLISHED_ONLY,
2127
- pullFutureContent: hasAllContentAccess
2127
+ pullFutureContent: isAdminORModerator ? true : false,
2128
2128
  }).buildFilter()
2129
2129
  query = buildEntityAndTotalQuery(filterWithRestrictions, entityFieldsString, {
2130
2130
  sortOrder: sortOrder,
@@ -89,7 +89,6 @@ 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
93
92
  }
94
93
 
95
94
  export interface AuthResponse {
@@ -1 +1 @@
1
- export const MCS_VERSION = '2.169.0-0'
1
+ export const MCS_VERSION = '2.169.0'