musora-content-services 2.104.8 → 2.105.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.
Files changed (177) hide show
  1. package/.claude/settings.local.json +8 -7
  2. package/.coderabbit.yaml +0 -0
  3. package/.editorconfig +0 -0
  4. package/.github/pull_request_template.md +0 -0
  5. package/.github/workflows/docs.js.yml +0 -0
  6. package/CHANGELOG.md +15 -0
  7. package/CLAUDE.md +0 -0
  8. package/README.md +0 -0
  9. package/babel.config.cjs +0 -0
  10. package/jsdoc.json +0 -0
  11. package/package.json +1 -1
  12. package/src/constants/award-assets.js +0 -0
  13. package/src/contentMetaData.js +0 -0
  14. package/src/contentTypeConfig.js +0 -8
  15. package/src/infrastructure/http/HttpClient.ts +0 -0
  16. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  17. package/src/infrastructure/http/index.ts +0 -0
  18. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  19. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  20. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  21. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  22. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  23. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  24. package/src/lib/brands.ts +0 -0
  25. package/src/lib/lastUpdated.js +0 -0
  26. package/src/services/api/types.js +0 -0
  27. package/src/services/api/types.ts +0 -0
  28. package/src/services/awards/award-callbacks.js +2 -0
  29. package/src/services/awards/award-query.js +8 -14
  30. package/src/services/awards/internal/.indexignore +0 -0
  31. package/src/services/awards/internal/award-definitions.js +5 -2
  32. package/src/services/awards/internal/award-events.js +0 -0
  33. package/src/services/awards/internal/award-manager.js +1 -1
  34. package/src/services/awards/internal/certificate-builder.js +1 -4
  35. package/src/services/awards/internal/completion-data-generator.js +0 -0
  36. package/src/services/awards/internal/content-progress-observer.js +0 -0
  37. package/src/services/awards/internal/image-utils.js +0 -0
  38. package/src/services/awards/internal/message-generator.js +0 -0
  39. package/src/services/awards/internal/types.js +0 -0
  40. package/src/services/awards/types.d.ts +1 -0
  41. package/src/services/awards/types.js +5 -2
  42. package/src/services/config.js +0 -0
  43. package/src/services/content/content.ts +0 -0
  44. package/src/services/content-org/content-org.js +0 -0
  45. package/src/services/content-org/guided-courses.ts +0 -0
  46. package/src/services/content-org/learning-paths.ts +2 -0
  47. package/src/services/content-org/playlists-types.js +0 -0
  48. package/src/services/content-org/playlists.js +0 -0
  49. package/src/services/contentLikes.js +0 -0
  50. package/src/services/contentProgress.js +39 -31
  51. package/src/services/dataContext.js +0 -0
  52. package/src/services/dateUtils.js +0 -0
  53. package/src/services/eventsAPI.js +0 -0
  54. package/src/services/forums/categories.ts +0 -0
  55. package/src/services/forums/posts.ts +0 -0
  56. package/src/services/forums/types.ts +0 -0
  57. package/src/services/gamification/awards.ts +0 -0
  58. package/src/services/gamification/gamification.js +0 -0
  59. package/src/services/imageSRCBuilder.js +0 -0
  60. package/src/services/imageSRCVerify.js +0 -0
  61. package/src/services/liveTesting.ts +0 -0
  62. package/src/services/permissions/PermissionsAdapter.ts +0 -0
  63. package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
  64. package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
  65. package/src/services/permissions/README.md +0 -0
  66. package/src/services/permissions/index.ts +0 -0
  67. package/src/services/progress-row/method-card.js +1 -1
  68. package/src/services/recommendations.js +0 -0
  69. package/src/services/reporting/README.md +0 -0
  70. package/src/services/reporting/reporting.ts +0 -0
  71. package/src/services/reporting/types.ts +0 -0
  72. package/src/services/sanity.js +23 -0
  73. package/src/services/sentry/.indexignore +0 -0
  74. package/src/services/sentry/index.ts +0 -0
  75. package/src/services/sync/.indexignore +0 -0
  76. package/src/services/sync/adapters/factory.ts +0 -0
  77. package/src/services/sync/adapters/lokijs.ts +0 -0
  78. package/src/services/sync/adapters/sqlite.ts +0 -0
  79. package/src/services/sync/concurrency-safety.ts +0 -0
  80. package/src/services/sync/context/index.ts +0 -0
  81. package/src/services/sync/context/providers/base.ts +0 -0
  82. package/src/services/sync/context/providers/connectivity.ts +0 -0
  83. package/src/services/sync/context/providers/durability.ts +0 -0
  84. package/src/services/sync/context/providers/index.ts +0 -0
  85. package/src/services/sync/context/providers/session.ts +0 -0
  86. package/src/services/sync/context/providers/tabs.ts +0 -0
  87. package/src/services/sync/context/providers/visibility.ts +0 -0
  88. package/src/services/sync/database/factory.ts +0 -0
  89. package/src/services/sync/errors/boundary.ts +0 -0
  90. package/src/services/sync/errors/index.ts +0 -0
  91. package/src/services/sync/index.ts +0 -0
  92. package/src/services/sync/models/Base.ts +0 -0
  93. package/src/services/sync/models/ContentLike.ts +0 -0
  94. package/src/services/sync/models/ContentProgress.ts +7 -4
  95. package/src/services/sync/models/Practice.ts +0 -0
  96. package/src/services/sync/models/PracticeDayNote.ts +0 -0
  97. package/src/services/sync/models/UserAwardProgress.ts +3 -3
  98. package/src/services/sync/models/index.ts +0 -0
  99. package/src/services/sync/repositories/base.ts +0 -0
  100. package/src/services/sync/repositories/content-likes.ts +0 -0
  101. package/src/services/sync/repositories/content-progress.ts +8 -15
  102. package/src/services/sync/repositories/index.ts +0 -0
  103. package/src/services/sync/repositories/practice-day-notes.ts +0 -0
  104. package/src/services/sync/repositories/practices.ts +0 -0
  105. package/src/services/sync/repositories/user-award-progress.ts +4 -4
  106. package/src/services/sync/repository-proxy.ts +0 -0
  107. package/src/services/sync/resolver.ts +0 -0
  108. package/src/services/sync/run-scope.ts +0 -0
  109. package/src/services/sync/schema/index.ts +2 -2
  110. package/src/services/sync/serializers/index.ts +0 -0
  111. package/src/services/sync/serializers/model.ts +0 -0
  112. package/src/services/sync/serializers/raw.ts +0 -0
  113. package/src/services/sync/store/push-coalescer.ts +0 -0
  114. package/src/services/sync/store-configs.ts +0 -0
  115. package/src/services/sync/strategies/base.ts +0 -0
  116. package/src/services/sync/strategies/index.ts +0 -0
  117. package/src/services/sync/strategies/initial.ts +0 -0
  118. package/src/services/sync/strategies/polling.ts +0 -0
  119. package/src/services/sync/telemetry/index.ts +0 -0
  120. package/src/services/sync/telemetry/sampling.ts +0 -0
  121. package/src/services/sync/utils/event-emitter.ts +0 -0
  122. package/src/services/sync/utils/index.ts +0 -0
  123. package/src/services/sync/utils/throttle.ts +0 -0
  124. package/src/services/sync/utils/timers.ts +0 -0
  125. package/src/services/types.js +0 -0
  126. package/src/services/user/account.ts +0 -0
  127. package/src/services/user/chat.js +0 -0
  128. package/src/services/user/interests.js +0 -0
  129. package/src/services/user/management.js +0 -0
  130. package/src/services/user/notifications.js +0 -0
  131. package/src/services/user/payments.ts +0 -0
  132. package/src/services/user/permissions.js +0 -0
  133. package/src/services/user/profile.js +0 -0
  134. package/src/services/user/types.d.ts +0 -2
  135. package/src/services/user/types.js +0 -2
  136. package/src/services/user/user-management-system.js +0 -0
  137. package/src/services/userActivity.js +2 -3
  138. package/test/HttpClient.test.js +0 -0
  139. package/test/awards/award-alacarte-observer.test.js +0 -0
  140. package/test/awards/award-auto-refresh.test.js +0 -0
  141. package/test/awards/award-calculations.test.js +0 -0
  142. package/test/awards/award-certificate-display.test.js +0 -0
  143. package/test/awards/award-collection-edge-cases.test.js +0 -0
  144. package/test/awards/award-collection-filtering.test.js +0 -0
  145. package/test/awards/award-completion-flow.test.js +2 -1
  146. package/test/awards/award-exclusion-handling.test.js +0 -0
  147. package/test/awards/award-multi-lesson.test.js +0 -0
  148. package/test/awards/award-observer-integration.test.js +0 -0
  149. package/test/awards/award-query-messages.test.js +0 -0
  150. package/test/awards/award-user-collection.test.js +0 -0
  151. package/test/awards/duplicate-prevention.test.js +0 -0
  152. package/test/awards/helpers/completion-mock.js +0 -0
  153. package/test/awards/helpers/index.js +0 -0
  154. package/test/awards/helpers/mock-setup.js +0 -0
  155. package/test/awards/helpers/progress-emitter.js +0 -0
  156. package/test/awards/message-generator.test.js +0 -0
  157. package/test/content.test.js +0 -0
  158. package/test/contentLikes.test.js +0 -0
  159. package/test/contentProgress.test.js +0 -0
  160. package/test/forum.test.js +0 -0
  161. package/test/imageSRCBuilder.test.js +0 -0
  162. package/test/imageSRCVerify.test.js +0 -0
  163. package/test/lib/lastUpdated.test.js +0 -0
  164. package/test/live/contentProgressLive.test.js +0 -0
  165. package/test/live/railcontentLive.test.js +0 -0
  166. package/test/mockData/award-definitions.js +0 -0
  167. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  168. package/test/mockData/mockData_progress_content.json +0 -0
  169. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  170. package/test/mockData/mockData_user_practices.json +0 -0
  171. package/test/notifications.test.js +0 -0
  172. package/test/progressRows.test.js +0 -0
  173. package/test/streakMessage.test.js +0 -0
  174. package/test/sync/models/award-database-integration.test.js +0 -0
  175. package/test/user/permissions.test.js +0 -0
  176. package/test/userActivity.test.js +0 -0
  177. package/tools/generate-index.cjs +0 -0
File without changes
@@ -52,11 +52,14 @@ export default class ContentProgress extends BaseModel<{
52
52
  set content_brand(value: string) {
53
53
  this._setRaw('content_brand', value)
54
54
  }
55
- set state(value: STATE) {
56
- this._setRaw('state', value)
57
- }
55
+ // IMPORTANT: progress percent only moves forward and is clamped between 0 and 100
56
+ // also has implications for last-write-wins sync strategy
58
57
  set progress_percent(value: number) {
59
- this._setRaw('progress_percent', Math.min(100, Math.max(0, value)))
58
+ const normalizedValue = Math.min(100, Math.max(0, value))
59
+ const percent = normalizedValue === 0 ? 0 : Math.max(normalizedValue, this.progress_percent)
60
+
61
+ this._setRaw('progress_percent', percent)
62
+ this._setRaw('state', percent === 100 ? STATE.COMPLETED : STATE.STARTED)
60
63
  }
61
64
  set collection_type(value: COLLECTION_TYPE) {
62
65
  this._setRaw('collection_type', value)
File without changes
File without changes
@@ -5,7 +5,7 @@ import type { CompletionData } from '../../awards/types'
5
5
  export default class UserAwardProgress extends BaseModel<{
6
6
  award_id: string
7
7
  progress_percentage: number
8
- completed_at: number | null
8
+ completed_at: string | null
9
9
  progress_data: string | null
10
10
  completion_data: string | null
11
11
  }> {
@@ -20,7 +20,7 @@ export default class UserAwardProgress extends BaseModel<{
20
20
  }
21
21
 
22
22
  get completed_at() {
23
- return this._getRaw('completed_at') as number | null
23
+ return this._getRaw('completed_at') as string | null
24
24
  }
25
25
 
26
26
  get progress_data() {
@@ -41,7 +41,7 @@ export default class UserAwardProgress extends BaseModel<{
41
41
  this._setRaw('progress_percentage', value)
42
42
  }
43
43
 
44
- set completed_at(value: number | null) {
44
+ set completed_at(value: string | null) {
45
45
  this._setRaw('completed_at', value)
46
46
  }
47
47
 
File without changes
File without changes
File without changes
@@ -13,7 +13,7 @@ export interface CollectionParameter {
13
13
  export default class ProgressRepository extends SyncRepository<ContentProgress> {
14
14
  // null collection only
15
15
  async startedIds(limit?: number) {
16
- return this.queryAllIds(...[
16
+ return this.queryAll(...[
17
17
  Q.where('collection_type', COLLECTION_TYPE.SELF),
18
18
  Q.where('collection_id', COLLECTION_ID_SELF),
19
19
 
@@ -21,12 +21,12 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
21
21
  Q.sortBy('updated_at', 'desc'),
22
22
 
23
23
  ...(limit ? [Q.take(limit)] : []),
24
- ])
24
+ ]).then((r) => r.data.map((r) => r.content_id))
25
25
  }
26
26
 
27
27
  // null collection only
28
28
  async completedIds(limit?: number) {
29
- return this.queryAllIds(...[
29
+ return this.queryAll(...[
30
30
  Q.where('collection_type', COLLECTION_TYPE.SELF),
31
31
  Q.where('collection_id', COLLECTION_ID_SELF),
32
32
 
@@ -34,7 +34,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
34
34
  Q.sortBy('updated_at', 'desc'),
35
35
 
36
36
  ...(limit ? [Q.take(limit)] : []),
37
- ])
37
+ ]).then((r) => r.data.map((r) => r.content_id))
38
38
  }
39
39
 
40
40
  //this _specifically_ needs to get content_ids from ALL collection_types (including null)
@@ -50,17 +50,12 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
50
50
  return this.queryAll(...this.startedOrCompletedClauses(opts))
51
51
  }
52
52
 
53
- // null collection only
54
- async startedOrCompletedIds(opts: Parameters<typeof this.startedOrCompletedClauses>[0] = {}) {
55
- return this.queryAllIds(...this.startedOrCompletedClauses(opts))
56
- }
57
-
58
53
  // null collection only
59
54
  private startedOrCompletedClauses(
60
55
  opts: {
61
- brand?: string
62
- updatedAfter?: number
63
- limit?: number
56
+ brand?: string | null
57
+ updatedAfter?: number,
58
+ limit?: number,
64
59
  } = {}
65
60
  ) {
66
61
  const clauses: Q.Clause[] = [
@@ -75,7 +70,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
75
70
  clauses.push(Q.where('updated_at', Q.gte(opts.updatedAfter)))
76
71
  }
77
72
 
78
- if (opts.brand) {
73
+ if (typeof opts.brand != 'undefined') {
79
74
  clauses.push(Q.where('content_brand', opts.brand))
80
75
  }
81
76
 
@@ -146,7 +141,6 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
146
141
  r.collection_type = collection?.type ?? COLLECTION_TYPE.SELF
147
142
  r.collection_id = collection?.id ?? COLLECTION_ID_SELF
148
143
 
149
- r.state = progressPct === 100 ? STATE.COMPLETED : STATE.STARTED
150
144
  r.progress_percent = progressPct
151
145
 
152
146
  if (typeof resumeTime != 'undefined') {
@@ -193,7 +187,6 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
193
187
  r.collection_type = collection?.type ?? COLLECTION_TYPE.SELF
194
188
  r.collection_id = collection?.id ?? COLLECTION_ID_SELF
195
189
 
196
- r.state = progressPct === 100 ? STATE.COMPLETED : STATE.STARTED
197
190
  r.progress_percent = progressPct
198
191
  },
199
192
  ])
File without changes
File without changes
@@ -5,7 +5,7 @@ import type { AwardDefinition, CompletionData } from '../../awards/types'
5
5
  import type { ModelSerialized } from '../serializers'
6
6
 
7
7
  type AwardProgressData = {
8
- completed_at: number | null
8
+ completed_at: string | null
9
9
  progress_percentage: number
10
10
  }
11
11
 
@@ -18,7 +18,7 @@ export default class UserAwardProgressRepository extends SyncRepository<UserAwar
18
18
  return progress.progress_percentage >= 0 && !UserAwardProgressRepository.isCompleted(progress)
19
19
  }
20
20
 
21
- static completedAtDate(progress: { completed_at: number | null }): Date | null {
21
+ static completedAtDate(progress: { completed_at: string | null }): Date | null {
22
22
  return progress.completed_at ? new Date(progress.completed_at) : null
23
23
  }
24
24
 
@@ -73,7 +73,7 @@ export default class UserAwardProgressRepository extends SyncRepository<UserAwar
73
73
  awardId: string,
74
74
  progressPercentage: number,
75
75
  options?: {
76
- completedAt?: number | null
76
+ completedAt?: string | null
77
77
  progressData?: any
78
78
  completionData?: CompletionData | null
79
79
  immediate?: boolean
@@ -104,7 +104,7 @@ export default class UserAwardProgressRepository extends SyncRepository<UserAwar
104
104
  completionData: CompletionData
105
105
  ) {
106
106
  return this.recordAwardProgress(awardId, 100, {
107
- completedAt: Date.now(),
107
+ completedAt: new Date().toISOString(),
108
108
  completionData,
109
109
  immediate: true
110
110
  })
File without changes
File without changes
File without changes
@@ -20,7 +20,7 @@ const contentProgressTable = tableSchema({
20
20
  name: SYNC_TABLES.CONTENT_PROGRESS,
21
21
  columns: [
22
22
  { name: 'content_id', type: 'number', isIndexed: true },
23
- { name: 'content_brand', type: 'string', isIndexed: true },
23
+ { name: 'content_brand', type: 'string', isOptional: true, isIndexed: true },
24
24
  { name: 'collection_type', type: 'string', isOptional: true, isIndexed: true },
25
25
  { name: 'collection_id', type: 'number', isOptional: true, isIndexed: true },
26
26
  { name: 'state', type: 'string', isIndexed: true },
@@ -61,7 +61,7 @@ const userAwardProgressTable = tableSchema({
61
61
  columns: [
62
62
  { name: 'award_id', type: 'string', isIndexed: true },
63
63
  { name: 'progress_percentage', type: 'number' },
64
- { name: 'completed_at', type: 'number', isOptional: true, isIndexed: true },
64
+ { name: 'completed_at', type: 'string', isOptional: true, isIndexed: true },
65
65
  { name: 'progress_data', type: 'string', isOptional: true },
66
66
  { name: 'completion_data', type: 'string', isOptional: true },
67
67
  { name: 'created_at', type: 'number' },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -42,9 +42,7 @@ export interface User {
42
42
  revenuecat_origin_app_user_id: string | null
43
43
  is_drumeo_lifetime_member: number
44
44
  access_level: string
45
- total_xp: number
46
45
  brand_method_levels: BrandMethodLevels
47
- brand_total_xp: BrandTotalXp
48
46
  brand_minutes_practiced: BrandTimePracticed
49
47
  brand_seconds_practiced: BrandTimePracticed
50
48
  guitar_playing_since_year: number | null
@@ -46,9 +46,7 @@
46
46
  * @property {string|null} revenuecat_origin_app_user_id
47
47
  * @property {number} is_drumeo_lifetime_member
48
48
  * @property {string} access_level
49
- * @property {number} total_xp
50
49
  * @property {BrandMethodLevels} brand_method_levels
51
- * @property {BrandTotalXp} brand_total_xp
52
50
  * @property {BrandTimePracticed} brand_minutes_practiced
53
51
  * @property {BrandTimePracticed} brand_seconds_practiced
54
52
  * @property {number|null} guitar_playing_since_year
File without changes
@@ -982,9 +982,9 @@ function generateContentsMap(contents, playlistsContents) {
982
982
  export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}) {
983
983
  // TODO slice progress to a reasonable number, say 100
984
984
  const methodCardPromise = getMethodCard(brand)
985
- const [recentPlaylists, progressContents, userPinnedItem] = await Promise.all([
985
+ const [recentPlaylists, nonPlaylistContentIds, userPinnedItem] = await Promise.all([
986
986
  fetchUserPlaylists(brand, { sort: '-last_progress', limit: limit }),
987
- getAllStartedOrCompleted({ onlyIds: false, brand: brand, limit }),
987
+ getAllStartedOrCompleted({ brand: brand, limit }),
988
988
  getUserPinnedItem(brand),
989
989
  ])
990
990
 
@@ -995,7 +995,6 @@ export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}) {
995
995
  )
996
996
 
997
997
  // todo post v2: refactor this once we migrate playlist progress tracking to new system
998
- const nonPlaylistContentIds = Object.keys(progressContents)
999
998
  if (userPinnedItem?.progressType === 'content') {
1000
999
  nonPlaylistContentIds.push(userPinnedItem.id)
1001
1000
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -91,7 +91,8 @@ describe('Award Completion Flow - E2E Scenarios', () => {
91
91
  definition: expect.objectContaining({
92
92
  name: testAward.name,
93
93
  badge: testAward.badge,
94
- award: testAward.award
94
+ award: testAward.award,
95
+ content_type: expect.any(String)
95
96
  }),
96
97
  completionData: expect.objectContaining({
97
98
  content_title: expect.any(String),
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes