musora-content-services 2.169.4 → 2.171.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,14 +1,4 @@
1
1
  {
2
- "permissions": {
3
- "allow": [
4
- "Bash(rg:*)",
5
- "Bash(npm run lint:*)",
6
- "Bash(ls:*)",
7
- "Bash(npm test *)",
8
- "Bash(npx jest *)"
9
- ],
10
- "deny": []
11
- },
12
2
  "enabledMcpjsonServers": [
13
3
  "1password",
14
4
  "atlassian",
@@ -19,15 +9,15 @@
19
9
  "hex",
20
10
  "sanity",
21
11
  "mysql",
12
+ "mysql-staging",
13
+ "mysql-local",
22
14
  "slack",
23
15
  "langfuse",
24
16
  "chrome-devtools",
17
+ "mobile",
25
18
  "railway",
26
19
  "github",
27
20
  "asana",
28
- "nightwatch",
29
- "mysql-staging",
30
- "mysql-local",
31
- "mobile"
21
+ "nightwatch"
32
22
  ]
33
23
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,22 @@
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.171.0](https://github.com/railroadmedia/musora-content-services/compare/v2.170.0...v2.171.0) (2026-08-12)
6
+
7
+
8
+ ### Features
9
+
10
+ * adds create pending user fn ([#1025](https://github.com/railroadmedia/musora-content-services/issues/1025)) ([c4ba8ef](https://github.com/railroadmedia/musora-content-services/commit/c4ba8ef3a118718236d177885372781714a08c9a))
11
+
12
+ ## [2.170.0](https://github.com/railroadmedia/musora-content-services/compare/v2.169.4...v2.170.0) (2026-08-10)
13
+
14
+ ## [2.169.0-0](https://github.com/railroadmedia/musora-content-services/compare/v2.168.0...v2.169.0-0) (2026-07-31)
15
+
16
+
17
+ ### Features
18
+
19
+ * **TP-1300:** add badge field to Liker, ForumUser, and User interfaces ([8a3ee80](https://github.com/railroadmedia/musora-content-services/commit/8a3ee802722a0d9dcd41a6a3998ff2a7a3352aeb))
20
+
5
21
  ### [2.169.4](https://github.com/railroadmedia/musora-content-services/compare/v2.169.3...v2.169.4) (2026-08-07)
6
22
 
7
23
 
@@ -30,6 +46,7 @@ All notable changes to this project will be documented in this file. See [standa
30
46
 
31
47
  ### Features
32
48
 
49
+ * **TP-1300:** add badge field to Liker, ForumUser, and User interfaces ([8a3ee80](https://github.com/railroadmedia/musora-content-services/commit/8a3ee802722a0d9dcd41a6a3998ff2a7a3352aeb))
33
50
  * **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
51
 
35
52
  ## [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.171.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, {
package/src/index.d.ts CHANGED
@@ -410,6 +410,7 @@ import {
410
410
 
411
411
  import {
412
412
  confirmEmailChange,
413
+ createPendingAccount,
413
414
  deleteAccount,
414
415
  numberOfActiveUsers,
415
416
  requestEmailChange,
@@ -569,6 +570,7 @@ declare module 'musora-content-services' {
569
570
  createComment,
570
571
  createForumCategory,
571
572
  createInvites,
573
+ createPendingAccount,
572
574
  createPlaylist,
573
575
  createPost,
574
576
  createPracticeNotes,
package/src/index.js CHANGED
@@ -414,6 +414,7 @@ import {
414
414
 
415
415
  import {
416
416
  confirmEmailChange,
417
+ createPendingAccount,
417
418
  deleteAccount,
418
419
  numberOfActiveUsers,
419
420
  requestEmailChange,
@@ -568,6 +569,7 @@ export {
568
569
  createComment,
569
570
  createForumCategory,
570
571
  createInvites,
572
+ createPendingAccount,
571
573
  createPlaylist,
572
574
  createPost,
573
575
  createPracticeNotes,
@@ -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,
@@ -109,6 +109,30 @@ export async function setupAccount(props: AccountSetupProps): Promise<AccountSet
109
109
  return res
110
110
  }
111
111
 
112
+ export interface PendingAccountProps {
113
+ email: string
114
+ revenuecatAppUserId?: string
115
+ }
116
+
117
+ /**
118
+ * @param {Object} props - The parameters for creating the pending account.
119
+ * @property {string} email - The email address for the account.
120
+ * @property {string} [revenuecatAppUserId] - The RevenueCat App User ID for MA environments.
121
+ *
122
+ * @returns {Promise<AccountSetupResponse>} - A promise that resolves when the pending account is created or an HttpError if the request fails.
123
+ * @throws {HttpError} - Throws an HttpError if the HTTP request fails.
124
+ */
125
+ export async function createPendingAccount(props: PendingAccountProps): Promise<AccountSetupResponse> {
126
+ const httpClient = new HttpClient(globalConfig.baseUrl)
127
+ return await httpClient.post<AccountSetupResponse>(
128
+ `/api/user-management-system/v1/accounts/pending`,
129
+ {
130
+ email: props.email,
131
+ mobile_app_id: props.revenuecatAppUserId,
132
+ }
133
+ )
134
+ }
135
+
112
136
  /**
113
137
  * @param {string} email - The email address to send the password reset email to.
114
138
  * @returns {Promise<void>} - A promise that resolves when the email change request is made.
@@ -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.171.0'
Binary file
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });