musora-content-services 2.94.7 → 2.95.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 (210) hide show
  1. package/.coderabbit.yaml +0 -0
  2. package/.editorconfig +0 -0
  3. package/.github/pull_request_template.md +0 -0
  4. package/.github/workflows/conventional-commits.yaml +0 -0
  5. package/.github/workflows/docs.js.yml +0 -0
  6. package/.github/workflows/node.js.yml +0 -0
  7. package/.prettierignore +0 -0
  8. package/.prettierrc +0 -0
  9. package/CHANGELOG.md +23 -0
  10. package/CLAUDE.md +408 -0
  11. package/README.md +0 -0
  12. package/babel.config.cjs +10 -0
  13. package/jest.config.js +0 -0
  14. package/jsdoc.json +2 -1
  15. package/package.json +2 -2
  16. package/src/constants/award-assets.js +35 -0
  17. package/src/contentMetaData.js +0 -0
  18. package/src/filterBuilder.js +7 -2
  19. package/src/index.d.ts +26 -5
  20. package/src/index.js +26 -5
  21. package/src/infrastructure/http/HttpClient.ts +0 -0
  22. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  23. package/src/infrastructure/http/index.ts +0 -0
  24. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  25. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  26. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  27. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  28. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  29. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  30. package/src/lib/brands.ts +0 -0
  31. package/src/lib/httpHelper.js +0 -0
  32. package/src/lib/lastUpdated.js +0 -0
  33. package/src/lib/sanity/query.ts +0 -0
  34. package/src/services/api/types.js +0 -0
  35. package/src/services/api/types.ts +0 -0
  36. package/src/services/awards/award-callbacks.js +126 -0
  37. package/src/services/awards/award-query.js +327 -0
  38. package/src/services/awards/internal/.indexignore +1 -0
  39. package/src/services/awards/internal/award-definitions.js +239 -0
  40. package/src/services/awards/internal/award-events.js +102 -0
  41. package/src/services/awards/internal/award-manager.js +162 -0
  42. package/src/services/awards/internal/certificate-builder.js +66 -0
  43. package/src/services/awards/internal/completion-data-generator.js +84 -0
  44. package/src/services/awards/internal/content-progress-observer.js +137 -0
  45. package/src/services/awards/internal/image-utils.js +62 -0
  46. package/src/services/awards/internal/message-generator.js +17 -0
  47. package/src/services/awards/internal/types.js +5 -0
  48. package/src/services/awards/types.d.ts +79 -0
  49. package/src/services/awards/types.js +101 -0
  50. package/src/services/config.js +24 -4
  51. package/src/services/content/artist.ts +0 -0
  52. package/src/services/content/content.ts +0 -0
  53. package/src/services/content/genre.ts +0 -0
  54. package/src/services/content/instructor.ts +0 -0
  55. package/src/services/content-org/content-org.js +0 -0
  56. package/src/services/content-org/guided-courses.ts +0 -0
  57. package/src/services/content-org/learning-paths.ts +19 -15
  58. package/src/services/content-org/playlists-types.js +0 -0
  59. package/src/services/content-org/playlists.js +0 -0
  60. package/src/services/content.js +0 -0
  61. package/src/services/contentAggregator.js +0 -0
  62. package/src/services/contentLikes.js +0 -0
  63. package/src/services/contentProgress.js +2 -1
  64. package/src/services/dataContext.js +0 -0
  65. package/src/services/dateUtils.js +0 -0
  66. package/src/services/eventsAPI.js +0 -0
  67. package/src/services/forums/forums.ts +0 -0
  68. package/src/services/forums/posts.ts +0 -0
  69. package/src/services/forums/threads.ts +0 -0
  70. package/src/services/forums/types.ts +0 -0
  71. package/src/services/gamification/awards.ts +114 -83
  72. package/src/services/gamification/gamification.js +0 -0
  73. package/src/services/imageSRCBuilder.js +0 -0
  74. package/src/services/imageSRCVerify.js +0 -0
  75. package/src/services/liveTesting.ts +0 -0
  76. package/src/services/permissions/PermissionsAdapter.ts +0 -0
  77. package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
  78. package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
  79. package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
  80. package/src/services/permissions/README.md +0 -0
  81. package/src/services/permissions/index.ts +0 -0
  82. package/src/services/progress-events.js +58 -0
  83. package/src/services/progress-row/method-card.js +20 -5
  84. package/src/services/railcontent.js +0 -0
  85. package/src/services/recommendations.js +0 -0
  86. package/src/services/reporting/README.md +0 -0
  87. package/src/services/reporting/reporting.ts +0 -0
  88. package/src/services/reporting/types.ts +0 -0
  89. package/src/services/sanity.js +1 -1
  90. package/src/services/sentry/.indexignore +0 -0
  91. package/src/services/sentry/index.ts +0 -0
  92. package/src/services/sync/.indexignore +0 -0
  93. package/src/services/sync/adapters/factory.ts +0 -0
  94. package/src/services/sync/adapters/lokijs.ts +0 -0
  95. package/src/services/sync/adapters/sqlite.ts +0 -0
  96. package/src/services/sync/concurrency-safety.ts +0 -0
  97. package/src/services/sync/context/index.ts +0 -0
  98. package/src/services/sync/context/providers/base.ts +0 -0
  99. package/src/services/sync/context/providers/connectivity.ts +0 -0
  100. package/src/services/sync/context/providers/durability.ts +0 -0
  101. package/src/services/sync/context/providers/index.ts +0 -0
  102. package/src/services/sync/context/providers/session.ts +0 -0
  103. package/src/services/sync/context/providers/tabs.ts +0 -0
  104. package/src/services/sync/context/providers/visibility.ts +0 -0
  105. package/src/services/sync/database/factory.ts +0 -0
  106. package/src/services/sync/errors/boundary.ts +0 -0
  107. package/src/services/sync/errors/index.ts +0 -0
  108. package/src/services/sync/fetch.ts +10 -2
  109. package/src/services/sync/index.ts +0 -0
  110. package/src/services/sync/manager.ts +6 -0
  111. package/src/services/sync/models/Base.ts +0 -0
  112. package/src/services/sync/models/ContentLike.ts +0 -0
  113. package/src/services/sync/models/ContentProgress.ts +5 -6
  114. package/src/services/sync/models/Practice.ts +0 -0
  115. package/src/services/sync/models/PracticeDayNote.ts +0 -0
  116. package/src/services/sync/models/UserAwardProgress.ts +55 -0
  117. package/src/services/sync/models/index.ts +1 -0
  118. package/src/services/sync/repositories/base.ts +0 -0
  119. package/src/services/sync/repositories/content-likes.ts +0 -0
  120. package/src/services/sync/repositories/content-progress.ts +47 -25
  121. package/src/services/sync/repositories/index.ts +1 -0
  122. package/src/services/sync/repositories/practice-day-notes.ts +0 -0
  123. package/src/services/sync/repositories/practices.ts +16 -1
  124. package/src/services/sync/repositories/user-award-progress.ts +133 -0
  125. package/src/services/sync/repository-proxy.ts +6 -0
  126. package/src/services/sync/resolver.ts +0 -0
  127. package/src/services/sync/retry.ts +12 -11
  128. package/src/services/sync/run-scope.ts +0 -0
  129. package/src/services/sync/schema/index.ts +18 -3
  130. package/src/services/sync/serializers/index.ts +0 -0
  131. package/src/services/sync/serializers/model.ts +0 -0
  132. package/src/services/sync/serializers/raw.ts +0 -0
  133. package/src/services/sync/store/index.ts +53 -8
  134. package/src/services/sync/store/push-coalescer.ts +3 -3
  135. package/src/services/sync/store-configs.ts +7 -1
  136. package/src/services/sync/strategies/base.ts +0 -0
  137. package/src/services/sync/strategies/index.ts +0 -0
  138. package/src/services/sync/strategies/initial.ts +0 -0
  139. package/src/services/sync/strategies/polling.ts +0 -0
  140. package/src/services/sync/telemetry/index.ts +0 -0
  141. package/src/services/sync/telemetry/sampling.ts +0 -0
  142. package/src/services/sync/utils/event-emitter.ts +0 -0
  143. package/src/services/sync/utils/index.ts +0 -0
  144. package/src/services/sync/utils/throttle.ts +0 -0
  145. package/src/services/sync/utils/timers.ts +0 -0
  146. package/src/services/types.js +0 -0
  147. package/src/services/user/account.ts +0 -0
  148. package/src/services/user/chat.js +0 -0
  149. package/src/services/user/interests.js +0 -0
  150. package/src/services/user/management.js +0 -0
  151. package/src/services/user/memberships.ts +0 -0
  152. package/src/services/user/notifications.js +0 -0
  153. package/src/services/user/onboarding.ts +0 -0
  154. package/src/services/user/payments.ts +0 -0
  155. package/src/services/user/permissions.js +0 -0
  156. package/src/services/user/profile.js +0 -0
  157. package/src/services/user/sessions.js +0 -0
  158. package/src/services/user/types.d.ts +0 -0
  159. package/src/services/user/types.js +0 -0
  160. package/src/services/user/user-management-system.js +0 -0
  161. package/src/services/userActivity.js +0 -1
  162. package/test/HttpClient.test.js +6 -6
  163. package/test/awards/award-alacarte-observer.test.js +196 -0
  164. package/test/awards/award-auto-refresh.test.js +83 -0
  165. package/test/awards/award-calculations.test.js +33 -0
  166. package/test/awards/award-certificate-display.test.js +328 -0
  167. package/test/awards/award-collection-edge-cases.test.js +210 -0
  168. package/test/awards/award-collection-filtering.test.js +285 -0
  169. package/test/awards/award-completion-flow.test.js +213 -0
  170. package/test/awards/award-exclusion-handling.test.js +273 -0
  171. package/test/awards/award-multi-lesson.test.js +241 -0
  172. package/test/awards/award-observer-integration.test.js +325 -0
  173. package/test/awards/award-query-messages.test.js +438 -0
  174. package/test/awards/award-user-collection.test.js +412 -0
  175. package/test/awards/duplicate-prevention.test.js +118 -0
  176. package/test/awards/helpers/completion-mock.js +54 -0
  177. package/test/awards/helpers/index.js +3 -0
  178. package/test/awards/helpers/mock-setup.js +69 -0
  179. package/test/awards/helpers/progress-emitter.js +39 -0
  180. package/test/awards/message-generator.test.js +162 -0
  181. package/test/content.test.js +0 -0
  182. package/test/contentLikes.test.js +0 -0
  183. package/test/contentProgress.test.js +0 -0
  184. package/test/dataContext.test.js +0 -0
  185. package/test/forum.test.js +0 -0
  186. package/test/imageSRCBuilder.test.js +0 -0
  187. package/test/imageSRCVerify.test.js +0 -0
  188. package/test/initializeTests.js +6 -0
  189. package/test/learningPaths.test.js +0 -0
  190. package/test/lib/lastUpdated.test.js +0 -0
  191. package/test/live/contentProgressLive.test.js +0 -0
  192. package/test/live/railcontentLive.test.js +0 -0
  193. package/test/localStorageMock.js +0 -0
  194. package/test/log.js +0 -0
  195. package/test/mockData/award-definitions.js +171 -0
  196. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  197. package/test/mockData/mockData_progress_content.json +0 -0
  198. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  199. package/test/mockData/mockData_user_practices.json +0 -0
  200. package/test/notifications.test.js +0 -0
  201. package/test/progressRows.test.js +0 -0
  202. package/test/sanityQueryService.test.js +0 -0
  203. package/test/streakMessage.test.js +0 -0
  204. package/test/sync/models/award-database-integration.test.js +519 -0
  205. package/test/user/permissions.test.js +0 -0
  206. package/test/userActivity.test.js +0 -0
  207. package/tools/generate-index.cjs +9 -0
  208. package/.claude/settings.local.json +0 -14
  209. package/.yarnrc.yml +0 -1
  210. package/test/reporting.test.js +0 -132
package/.coderabbit.yaml CHANGED
File without changes
package/.editorconfig CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
package/.prettierignore CHANGED
File without changes
package/.prettierrc CHANGED
File without changes
package/CHANGELOG.md CHANGED
@@ -2,6 +2,29 @@
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.95.0](https://github.com/railroadmedia/musora-content-services/compare/v2.94.8...v2.95.0) (2025-12-08)
6
+
7
+
8
+ ### Features
9
+
10
+ * **BEH-1195:** Awards System ([#606](https://github.com/railroadmedia/musora-content-services/issues/606)) ([a6ffd92](https://github.com/railroadmedia/musora-content-services/commit/a6ffd92ae518c8e530d4ef743f2649725fddb285))
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **BR-330:** Wraps artists name in double quotes where its interpolated in the query ([8787399](https://github.com/railroadmedia/musora-content-services/commit/8787399e032d3522e5126ea9506f28cc5cdac22a))
16
+ * duplicate awards from wrong collection type handling ([#627](https://github.com/railroadmedia/musora-content-services/issues/627)) ([f6dfbf7](https://github.com/railroadmedia/musora-content-services/commit/f6dfbf778ad8e7d37d8e0f66ecd1aa5bec323330))
17
+ * method progress card cta ([#622](https://github.com/railroadmedia/musora-content-services/issues/622)) ([6788b48](https://github.com/railroadmedia/musora-content-services/commit/6788b48353d4122e1ca674a5fdd32abbfa8bb642))
18
+ * show next learning path lesson logic for fetch learning path lessons ([#626](https://github.com/railroadmedia/musora-content-services/issues/626)) ([2925a5b](https://github.com/railroadmedia/musora-content-services/commit/2925a5be42715af7f77720209ef78f118ae95bf4))
19
+ * watermelon fixes round [#4](https://github.com/railroadmedia/musora-content-services/issues/4) ([#621](https://github.com/railroadmedia/musora-content-services/issues/621)) ([aa1e834](https://github.com/railroadmedia/musora-content-services/commit/aa1e834dbb287e4b581b67690038e086eb7a4d55))
20
+
21
+ ### [2.94.8](https://github.com/railroadmedia/musora-content-services/compare/v2.94.7...v2.94.8) (2025-12-04)
22
+
23
+
24
+ ### Bug Fixes
25
+
26
+ * collection type check ([#619](https://github.com/railroadmedia/musora-content-services/issues/619)) ([df520a4](https://github.com/railroadmedia/musora-content-services/commit/df520a497c24091b41d207992a8a71daf0fc98e5))
27
+
5
28
  ### [2.94.7](https://github.com/railroadmedia/musora-content-services/compare/v2.94.1...v2.94.7) (2025-12-04)
6
29
 
7
30
 
package/CLAUDE.md ADDED
@@ -0,0 +1,408 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Repository Overview
6
+
7
+ **musora-content-services** is an NPM package providing utility functions for fetching and managing content from Sanity Studio, Railcontent API, and user activity data. It serves as the primary content layer for all Musora brands (Drumeo, Pianote, Guitareo, Singeo). The package supports both web and React Native mobile applications with offline-first sync capabilities.
8
+
9
+ ## Testing, Automated Tests
10
+ Do not run tests or try to run tests with npm test unless explicitly asked.
11
+
12
+ ## Code Style Standards
13
+
14
+ ### Self-Documenting Code
15
+ - Write clear, descriptive function and variable names that eliminate the need for comments
16
+ - Never add comments explaining what code does - the code itself should be clear
17
+ - Never add comments about code history, updates, or previous states
18
+ - Only add comments when code logic is genuinely complex and cannot be simplified further
19
+ - If you need a comment, first consider refactoring to make the code clearer
20
+
21
+ ### Inline Comments
22
+ - Never add comments that explain what code does - the code should be self-explanatory
23
+ - Use descriptive variable and function names instead of comments
24
+ - Only add comments for genuinely complex logic that cannot be simplified
25
+
26
+ **Bad:**
27
+ ```javascript
28
+ // Check if user has completed the award
29
+ if (progress === 100 && completedAt !== null) {
30
+
31
+ // Loop through all awards and filter completed ones
32
+ const completed = awards.filter(a => a.done)
33
+
34
+ // Calculate the percentage
35
+ const pct = (count / total) * 100
36
+ ```
37
+
38
+ **Good:**
39
+ ```javascript
40
+ const isAwardCompleted = progress === 100 && completedAt !== null
41
+ if (isAwardCompleted) {
42
+
43
+ const completedAwards = awards.filter(award => award.isCompleted)
44
+
45
+ const progressPercentage = (completedCount / totalCount) * 100
46
+ ```
47
+
48
+ ### JSDoc Guidelines
49
+ - Keep JSDoc blocks minimal - only include `@param`, `@returns`, `@throws`, `@example`, and type annotations
50
+ - Never add explainer text or descriptions before the first `@` tag
51
+ - Never add empty lines like `* ` at the start of JSDoc blocks
52
+ - Let function names be self-documenting; don't repeat what the name already says
53
+
54
+ **Bad:**
55
+ ```javascript
56
+ /**
57
+ * Register a callback function to be notified when the user earns a new award.
58
+ * The callback receives an award object with completion data, badge URLs, and practice statistics.
59
+ * Returns a cleanup function to unregister the callback when no longer needed.
60
+ *
61
+ * @param {Function} callback - The callback function
62
+ * @returns {Function} Cleanup function
63
+ */
64
+ ```
65
+
66
+ **Good:**
67
+ ```javascript
68
+ /**
69
+ * @param {Function} callback - The callback function
70
+ * @returns {Function} Cleanup function
71
+ */
72
+ ```
73
+
74
+ ## Development Setup
75
+
76
+ This repository must be developed within the railenvironment Docker container:
77
+
78
+ ```bash
79
+ # From railenvironment directory
80
+ ./rrr.sh # Enter manager container
81
+ r setup musora-content-services # Initial setup
82
+ npm install # Install dependencies
83
+ ```
84
+
85
+ Create `.env` file from 1Password note "musora-content-services .env".
86
+
87
+ ## Core Commands
88
+
89
+ ```bash
90
+ # Testing
91
+ npm test # Run full test suite
92
+ npm test -- -t="testName" # Run specific test
93
+
94
+ # Building
95
+ npm run build-index # Generate index.js and index.d.ts from exports
96
+
97
+ # Documentation
98
+ npm run doc # Generate JSDoc documentation
99
+
100
+ # Publishing
101
+ ./publish.sh # Publish new version to NPM (auto-increments version)
102
+
103
+ # Local Development
104
+ ./link_mcs.sh # Symlink to musora-platform-frontend for local testing
105
+ ```
106
+
107
+ ## Architecture Overview
108
+
109
+ ### Service Organization
110
+
111
+ All services are located in `src/services/` and organized by domain:
112
+
113
+ **Content Services** (`src/services/`)
114
+ - `sanity.js` - Sanity CMS queries using GROQ (primary content source)
115
+ - `content.js` - High-level content aggregation and content rows
116
+ - `railcontent.js` - Legacy Railcontent API interactions
117
+ - `contentProgress.js` - User progress tracking (started, completed, watch sessions)
118
+ - `contentLikes.js` - Content like/unlike operations
119
+ - `userActivity.js` - Practice sessions, activity tracking, statistics
120
+
121
+ **Content Organization** (`src/services/content-org/`)
122
+ - `learning-paths.ts` - Learning path enrollment and progression
123
+ - `guided-courses.ts` - Guided course enrollment
124
+ - `playlists.js` - User playlist management
125
+
126
+ **User Services** (`src/services/user/`)
127
+ - `account.ts` - Account management, email changes, password resets
128
+ - `profile.js` - User profiles, pictures, signatures
129
+ - `permissions.js` - User permission checking
130
+ - `notifications.js` - Notification management
131
+ - `onboarding.ts` - User onboarding flows
132
+ - `memberships.ts` - Subscription and membership data
133
+
134
+ **Forums** (`src/services/forums/`)
135
+ - `threads.ts` - Forum thread operations
136
+ - `posts.ts` - Forum post CRUD
137
+ - `categories.ts` - Forum category management
138
+
139
+ **Gamification** (`src/services/gamification/`)
140
+ - `awards.ts` - Awards, badges, and certificates
141
+
142
+ **Sync System** (`src/services/sync/`) - WatermelonDB-based offline sync
143
+ - `manager.ts` - Sync orchestration
144
+ - `models/` - ContentProgress, ContentLike, ContentPractice models
145
+ - `repositories/` - Data access layer
146
+ - `strategies/` - Initial and polling sync strategies
147
+ - `context/` - Connectivity, visibility, and session providers
148
+
149
+ ### Content Type Configuration
150
+
151
+ `src/contentTypeConfig.js` is the central configuration defining:
152
+ - Content type field mappings for Sanity queries
153
+ - Lesson type categorization (shows, courses, songs, etc.)
154
+ - Filter-to-GROQ conversion logic
155
+ - Content hierarchy relationships
156
+
157
+ Content types include: `course`, `song`, `play-along`, `workout`, `quick-tips`, `guided-course`, `learning-path-v2`, `pack`, `song-tutorial`, and many show-specific types.
158
+
159
+ ### Index Generation System
160
+
161
+ `src/index.js` and `src/index.d.ts` are **auto-generated** files:
162
+
163
+ ```bash
164
+ npm run build-index # Regenerates these files
165
+ ```
166
+
167
+ The generator (`tools/generate-index.cjs`) scans `src/services/` for:
168
+ - `export function` and `export async function` declarations
169
+ - `export const globalConfig` variables
170
+ - Functions excluded via `excludeFromGeneratedIndex` array in each file
171
+ - Directories with `.indexignore` (e.g., `sync/` - see note below)
172
+
173
+ ### Key Design Patterns
174
+
175
+ **Configuration**: Global config must be initialized before use:
176
+ ```javascript
177
+ import { initializeService } from 'musora-content-services'
178
+
179
+ initializeService({
180
+ sanityConfig: { token, projectId, dataset, version },
181
+ railcontentConfig: { token, userId, baseUrl, authToken },
182
+ localStorage: localStorage,
183
+ isMA: false // Mobile app flag
184
+ })
185
+ ```
186
+
187
+ **Content Fetching**: Most Sanity queries accept:
188
+ - `brand` - Filter by brand (drumeo, pianote, guitareo, singeo)
189
+ - `limit` and `start` - Pagination
190
+ - `filters` - Array of filter strings (converted to GROQ via `filtersToGroq()`)
191
+ - `fields` - Custom field selection (uses `getFieldsForContentType()`)
192
+
193
+ **Sync System**: The sync layer provides offline-first data access for mobile:
194
+ - Uses WatermelonDB for local storage
195
+ - Supports both SQLite (React Native) and LokiJS (web) adapters
196
+ - Tracks content progress, likes, and practice sessions
197
+ - Automatic retry with exponential backoff
198
+ - Context-aware syncing (connectivity, visibility, session state)
199
+
200
+ **Testing**: See "Testing Framework" section below.
201
+
202
+ ## Publishing Workflow
203
+
204
+ 1. Make changes and commit
205
+ 2. Run `./publish.sh` which:
206
+ - Checks for uncommitted changes
207
+ - Runs `npm run release` (standard-version for semver and CHANGELOG)
208
+ - Pushes to `project-v2` branch
209
+ - Publishes to NPM
210
+
211
+ Version is auto-incremented in package.json and documented in CHANGELOG.md.
212
+
213
+ ## Testing Framework
214
+
215
+ ### Test Configuration
216
+ - **Framework**: Jest with ts-jest preset for TypeScript support
217
+ - **Config**: `jest.config.js` in project root
218
+ - **Coverage**: Enabled by default, outputs to `coverage/` directory
219
+ - **Environment variables**: Loaded from `.env` via `dotenv/config` in `setupFilesAfterEnv`
220
+
221
+ ### Test Structure
222
+ ```
223
+ test/
224
+ ├── awards/ # Award calculation tests
225
+ ├── live/ # Live API tests (ignored by default)
226
+ ├── mockData/ # Test fixtures and mock data
227
+ ├── lib/ # Test utilities
228
+ ├── user/ # User service tests
229
+ ├── initializeTests.js # Test initialization helper
230
+ ├── localStorageMock.js # LocalStorage mock implementation
231
+ └── *.test.js # Unit tests for services
232
+ ```
233
+
234
+ ### Running Tests
235
+ ```bash
236
+ npm test # Run all tests (excludes test/live/)
237
+ npm test test/awards # Run tests in specific directory
238
+ npm test specific.test.js # Run specific test file
239
+ npm test -- -t="test name" # Run tests matching name pattern
240
+ npx jest --watch # Run in watch mode
241
+ ```
242
+
243
+ ### Test Patterns
244
+
245
+ **Initialization**: All tests use `initializeTestService()` from `test/initializeTests.js`:
246
+ ```javascript
247
+ import { initializeTestService } from './initializeTests'
248
+
249
+ beforeEach(() => {
250
+ initializeTestService() // Mocked services
251
+ })
252
+
253
+ beforeEach(async () => {
254
+ await initializeTestService(true) // Live API calls
255
+ }, 1000000)
256
+ ```
257
+
258
+ **Mocking**: Tests mock external dependencies using Jest spies:
259
+ ```javascript
260
+ const railContentModule = require('../src/services/railcontent.js')
261
+ const mock = jest.spyOn(railContentModule, 'fetchUserPermissionsData')
262
+ mock.mockImplementation(() => ({ permissions: [78, 91, 92], isAdmin: false }))
263
+ ```
264
+
265
+ **Unit Tests**: Pure calculation tests in `test/awards/` don't require service initialization:
266
+ ```javascript
267
+ describe('ContentPractice Calculations', () => {
268
+ test('converts seconds to minutes correctly', () => {
269
+ const secondsToMinutes = (seconds) => Math.round(seconds / 60)
270
+ expect(secondsToMinutes(600)).toBe(10)
271
+ })
272
+ })
273
+ ```
274
+
275
+ **Integration Tests**: Service tests in `test/*.test.js` use initialized services and mock data:
276
+ ```javascript
277
+ describe('contentProgressDataContext', () => {
278
+ beforeEach(() => {
279
+ initializeTestService()
280
+ const mock = jest.spyOn(dataContext, 'fetchData')
281
+ mock.mockImplementation(() => mockProgressData)
282
+ })
283
+
284
+ test('getProgressPercentage', async () => {
285
+ const result = await getProgressPercentage(234191)
286
+ expect(result).toBe(6)
287
+ })
288
+ })
289
+ ```
290
+
291
+ **Live Tests**: Tests in `test/live/` make real API calls (excluded by `modulePathIgnorePatterns`):
292
+ - Require valid credentials in `.env`
293
+ - Use `initializeTestService(true)` to authenticate
294
+ - Longer timeouts (1000000ms) for API calls
295
+
296
+ ### Test Utilities
297
+ - **LocalStorageMock** (`test/localStorageMock.js`): In-memory localStorage implementation
298
+ - **initializeTestService**: Configures `globalConfig` with test credentials, mocks permissions
299
+ - **Mock Data**: JSON fixtures in `test/mockData/` for consistent test scenarios
300
+
301
+ ### TypeScript Testing
302
+ - TypeScript files (`.ts`) transformed via `ts-jest`
303
+ - JavaScript files (`.js`) transformed via `babel-jest`
304
+ - Type checking occurs during test runs for `.ts` files
305
+
306
+ ## React Native Compatibility
307
+
308
+ This package is used by both web applications and the **MusoraApp** React Native mobile application (iOS/Android). All code changes must be reviewed for React Native compatibility.
309
+
310
+ ### MusoraApp Integration
311
+
312
+ MusoraApp consumes MCS in two ways:
313
+
314
+ 1. **Direct imports** from the main package for content fetching, user services, and API calls
315
+ 2. **Sync system imports** from internal paths for offline-first data management
316
+
317
+ **MusoraApp Sync Setup** (`MusoraApp/src_v2/sync/SyncManager.ts`):
318
+ ```typescript
319
+ import { SyncManager, SyncContext } from 'musora-content-services/src/services/sync/index'
320
+ import syncDatabaseFactory from 'musora-content-services/src/services/sync/database/factory'
321
+ import { InitialStrategy, PollingStrategy } from 'musora-content-services/src/services/sync/strategies/index'
322
+ import { ContentLike, ContentProgress, Practice, PracticeDayNote } from 'musora-content-services/src/services/sync/models/index'
323
+
324
+ // MusoraApp provides platform-specific context providers:
325
+ // - SessionProvider: react-native-device-info for unique device ID
326
+ // - ConnectivityProvider: network state monitoring
327
+ // - VisibilityProvider: app foreground/background state
328
+ // - DurabilityProvider: no-op (storage always available on mobile)
329
+ // - TabsProvider: no-op (single "tab" on mobile)
330
+
331
+ const manager = new SyncManager(context, db)
332
+ manager.syncStoresWithStrategies(
333
+ manager.storesForModels([ContentLike, ContentProgress, Practice, PracticeDayNote]),
334
+ [initialStrategy, onlineStrategy, activityStrategy, hourlyPollingStrategy]
335
+ )
336
+ ```
337
+
338
+ **MCS Initialization in MusoraApp**:
339
+ ```typescript
340
+ import AsyncStorage from '@react-native-async-storage/async-storage'
341
+ import { initializeService } from 'musora-content-services'
342
+
343
+ initializeService({
344
+ sanityConfig: { token, projectId, dataset, version },
345
+ railcontentConfig: { token, userId, baseUrl, authToken },
346
+ localStorage: AsyncStorage, // React Native async storage
347
+ isMA: true // Enables async localStorage handling
348
+ })
349
+ ```
350
+
351
+ ### Compatibility Checklist
352
+
353
+ When writing or modifying code, verify compatibility with these rules:
354
+
355
+ **✅ Safe to Use:**
356
+ - `fetch` API (polyfilled in RN)
357
+ - `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`
358
+ - `Promise`, `async/await`
359
+ - `Map`, `Set`, `WeakMap`, `WeakSet`
360
+ - `JSON.parse`, `JSON.stringify`
361
+ - `Date`, `Math`, `Array`, `Object` methods
362
+ - `console.log`, `console.error`, `console.warn`
363
+ - `@nozbe/watermelondb` queries and models
364
+ - Event emitter patterns (custom pub/sub)
365
+
366
+ **⚠️ Requires Conditional Handling:**
367
+ - **localStorage**: Use `globalConfig.isMA` to detect React Native and handle async:
368
+ ```javascript
369
+ const value = globalConfig.isMA
370
+ ? await globalConfig.localStorage.getItem(key) // AsyncStorage (async)
371
+ : globalConfig.localStorage.getItem(key) // Web localStorage (sync)
372
+ ```
373
+
374
+ **❌ Browser-Only APIs (avoid or provide alternatives):**
375
+ - `window`, `document`, `navigator` (except `navigator.onLine`)
376
+ - `FileReader`, `Blob.prototype.text()` for base64 conversion
377
+ - `localStorage` directly (use `globalConfig.localStorage`)
378
+ - `sessionStorage`
379
+ - `IndexedDB` directly (use WatermelonDB)
380
+ - `URL.createObjectURL`, `URL.revokeObjectURL`
381
+ - `canvas`, `Image` constructors
382
+ - `WebSocket` (use libraries like `react-native-websocket` instead)
383
+ - DOM manipulation APIs
384
+
385
+ **❌ Node.js APIs (not available in RN):**
386
+ - `fs`, `path`, `os`, `crypto` (Node modules)
387
+ - `Buffer` (use polyfill or `base-64` library)
388
+ - `process.env` (use react-native-config or similar)
389
+
390
+ ### Adding New Sync Models
391
+
392
+ When adding new WatermelonDB models for sync:
393
+
394
+ 1. Create model in `src/services/sync/models/`
395
+ 2. Create repository in `src/services/sync/repositories/`
396
+ 3. Add table schema to `src/services/sync/schema/index.ts`
397
+ 4. Register in `src/services/sync/store-configs.ts`
398
+ 5. Export from `src/services/sync/models/index.ts`
399
+ 6. **Notify mobile team** to add the model to MusoraApp's `SyncManager.ts`
400
+
401
+ ## Important Notes
402
+
403
+ - **Sync directory**: `src/services/sync/` has a `.indexignore` file to prevent sync functions from being auto-exported to the main package index (sync is used internally by mobile apps)
404
+ - **Content types**: When adding new content types, update `contentTypeConfig.js` with appropriate field mappings
405
+ - **Sanity queries**: Use GROQ syntax; complex queries should use `FilterBuilder` class
406
+ - **Multi-brand**: All functions should handle brand filtering (`drumeo`, `pianote`, `guitareo`, `singeo`)
407
+ - **Environment**: Always develop within railenvironment Docker container - paths and database connections assume this environment
408
+ - **React Native**: Always verify changes are compatible with React Native (see "React Native Compatibility" section above)
package/README.md CHANGED
File without changes
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ presets: [
3
+ ['@babel/preset-env', { targets: { node: 'current' } }],
4
+ '@babel/preset-typescript'
5
+ ],
6
+ plugins: [
7
+ '@babel/plugin-transform-class-properties',
8
+ '@babel/plugin-transform-object-rest-spread'
9
+ ]
10
+ }
package/jest.config.js CHANGED
File without changes
package/jsdoc.json CHANGED
@@ -5,6 +5,7 @@
5
5
  "src/services/content-org",
6
6
  "src/services/content",
7
7
  "src/services/gamification",
8
+ "src/services/awards",
8
9
  "src/services/api",
9
10
  "src/services/progress-row",
10
11
  "src/services/config.js",
@@ -17,7 +18,7 @@
17
18
  "src/index.js"
18
19
  ],
19
20
  "includePattern": "(.js|.ts$)",
20
- "excludePattern": "(node_modules/|docs)"
21
+ "excludePattern": "(node_modules/|docs|/internal/)"
21
22
  },
22
23
  "plugins": [
23
24
  "node_modules/jsdoc-babel"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.94.7",
3
+ "version": "2.95.0",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -8,7 +8,7 @@
8
8
  "build-index": "node tools/generate-index.cjs",
9
9
  "release": "standard-version",
10
10
  "doc": "jsdoc -c jsdoc.json --verbose",
11
- "test": "jest --setupFiles dotenv/config"
11
+ "test": "jest"
12
12
  },
13
13
  "repository": {
14
14
  "type": "git",
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @module AwardAssets
3
+ */
4
+
5
+ /**
6
+ * @typedef {Object} BrandLogos
7
+ * @property {string} drumeo - Drumeo brand logo URL
8
+ * @property {string} singeo - Singeo brand logo URL
9
+ * @property {string} guitareo - Guitareo brand logo URL
10
+ * @property {string} pianote - Pianote brand logo URL
11
+ * @property {string} musora - Musora brand logo URL
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} AwardAssets
16
+ * @property {string} ribbon - Gold ribbon image URL for certificates
17
+ * @property {string} musoraLogo - Musora logo URL
18
+ * @property {string} musoraBgLogo - Musora background logo URL for certificates
19
+ * @property {BrandLogos} brandLogos - Brand-specific logo URLs
20
+ */
21
+
22
+ /** @type {AwardAssets} */
23
+ export const AWARD_ASSETS = {
24
+ ribbon: "https://d3fzm1tzeyr5n3.cloudfront.net/challenges/gold_ribbon.png",
25
+ musoraLogo: "https://d3fzm1tzeyr5n3.cloudfront.net/challenges/on_musora.png",
26
+ musoraBgLogo: "https://d3fzm1tzeyr5n3.cloudfront.net/challenges/certificate_logo.png",
27
+
28
+ brandLogos: {
29
+ drumeo: "https://dpwjbsxqtam5n.cloudfront.net/logos/logo-blue.png",
30
+ singeo: "https://d21xeg6s76swyd.cloudfront.net/sales/2021/singeo-logo.png",
31
+ guitareo: "https://d122ay5chh2hr5.cloudfront.net/sales/guitareo-logo-green.png",
32
+ pianote: "https://d21q7xesnoiieh.cloudfront.net/fit-in/marketing/pianote/membership/homepage/2023/pianote-logo-red.png",
33
+ musora: "https://d3fzm1tzeyr5n3.cloudfront.net/challenges/on_musora.png"
34
+ }
35
+ }
File without changes
@@ -58,8 +58,13 @@ export class FilterBuilder {
58
58
  }
59
59
 
60
60
  async buildFilter() {
61
- const adapter = getPermissionsAdapter()
62
- this.userData = await adapter.fetchUserPermissions()
61
+ if (this.bypassPermissions) {
62
+ this.userData = { permissions: [], isAdmin: false }
63
+ } else {
64
+ const adapter = getPermissionsAdapter()
65
+ this.userData = await adapter.fetchUserPermissions()
66
+ }
67
+
63
68
  if (this.debug) console.log('baseFilter', this.filter)
64
69
  const filter = this._applyContentStatuses()
65
70
  ._applyPermissions()
package/src/index.d.ts CHANGED
@@ -1,5 +1,17 @@
1
1
  /*** This file was generated automatically. To recreate, please run `npm run build-index`. ***/
2
2
 
3
+ import {
4
+ registerAwardCallback,
5
+ registerProgressCallback
6
+ } from './services/awards/award-callbacks.js';
7
+
8
+ import {
9
+ getAwardStatistics,
10
+ getCompletedAwards,
11
+ getContentAwards,
12
+ getInProgressAwards
13
+ } from './services/awards/award-query.js';
14
+
3
15
  import {
4
16
  globalConfig,
5
17
  initializeService
@@ -161,9 +173,7 @@ import {
161
173
  } from './services/forums/threads.ts';
162
174
 
163
175
  import {
164
- fetchAwardsForUser,
165
- fetchCertificate,
166
- getAwardDataForGuidedContent
176
+ fetchCertificate
167
177
  } from './services/gamification/awards.ts';
168
178
 
169
179
  import {
@@ -182,6 +192,11 @@ import {
182
192
  createTestUser
183
193
  } from './services/liveTesting.ts';
184
194
 
195
+ import {
196
+ emitProgressSaved,
197
+ onProgressSaved
198
+ } from './services/progress-events.js';
199
+
185
200
  import {
186
201
  getMethodCard
187
202
  } from './services/progress-row/method-card.js';
@@ -453,6 +468,7 @@ declare module 'musora-content-services' {
453
468
  deleteUserActivity,
454
469
  duplicatePlaylist,
455
470
  editComment,
471
+ emitProgressSaved,
456
472
  enrollUserInGuidedCourse,
457
473
  extractSanityUrl,
458
474
  fetchAll,
@@ -462,7 +478,6 @@ declare module 'musora-content-services' {
462
478
  fetchArtistBySlug,
463
479
  fetchArtistLessons,
464
480
  fetchArtists,
465
- fetchAwardsForUser,
466
481
  fetchByRailContentId,
467
482
  fetchByRailContentIds,
468
483
  fetchByReference,
@@ -569,10 +584,13 @@ declare module 'musora-content-services' {
569
584
  getAllCompletedByIds,
570
585
  getAllStarted,
571
586
  getAllStartedOrCompleted,
572
- getAwardDataForGuidedContent,
587
+ getAwardStatistics,
588
+ getCompletedAwards,
589
+ getContentAwards,
573
590
  getContentRows,
574
591
  getDailySession,
575
592
  getEnrichedLearningPath,
593
+ getInProgressAwards,
576
594
  getLastInteractedOf,
577
595
  getLearningPathLessonsByIds,
578
596
  getLegacyMethods,
@@ -633,6 +651,7 @@ declare module 'musora-content-services' {
633
651
  markNotificationAsUnread,
634
652
  markThreadAsRead,
635
653
  numberOfActiveUsers,
654
+ onProgressSaved,
636
655
  openComment,
637
656
  otherStats,
638
657
  pauseLiveEventPolling,
@@ -645,6 +664,8 @@ declare module 'musora-content-services' {
645
664
  recordUserActivity,
646
665
  recordUserPractice,
647
666
  recordWatchSession,
667
+ registerAwardCallback,
668
+ registerProgressCallback,
648
669
  removeContentAsInterested,
649
670
  removeContentAsNotInterested,
650
671
  removeUserPractice,