musora-content-services 2.94.8 → 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.
- package/CHANGELOG.md +16 -0
- package/CLAUDE.md +408 -0
- package/babel.config.cjs +10 -0
- package/jsdoc.json +2 -1
- package/package.json +2 -2
- package/src/constants/award-assets.js +35 -0
- package/src/filterBuilder.js +7 -2
- package/src/index.d.ts +26 -5
- package/src/index.js +26 -5
- package/src/services/awards/award-callbacks.js +126 -0
- package/src/services/awards/award-query.js +327 -0
- package/src/services/awards/internal/.indexignore +1 -0
- package/src/services/awards/internal/award-definitions.js +239 -0
- package/src/services/awards/internal/award-events.js +102 -0
- package/src/services/awards/internal/award-manager.js +162 -0
- package/src/services/awards/internal/certificate-builder.js +66 -0
- package/src/services/awards/internal/completion-data-generator.js +84 -0
- package/src/services/awards/internal/content-progress-observer.js +137 -0
- package/src/services/awards/internal/image-utils.js +62 -0
- package/src/services/awards/internal/message-generator.js +17 -0
- package/src/services/awards/internal/types.js +5 -0
- package/src/services/awards/types.d.ts +79 -0
- package/src/services/awards/types.js +101 -0
- package/src/services/config.js +24 -4
- package/src/services/content-org/learning-paths.ts +19 -15
- package/src/services/gamification/awards.ts +114 -83
- package/src/services/progress-events.js +58 -0
- package/src/services/progress-row/method-card.js +20 -5
- package/src/services/sanity.js +1 -1
- package/src/services/sync/fetch.ts +10 -2
- package/src/services/sync/manager.ts +6 -0
- package/src/services/sync/models/ContentProgress.ts +5 -6
- package/src/services/sync/models/UserAwardProgress.ts +55 -0
- package/src/services/sync/models/index.ts +1 -0
- package/src/services/sync/repositories/content-progress.ts +47 -25
- package/src/services/sync/repositories/index.ts +1 -0
- package/src/services/sync/repositories/practices.ts +16 -1
- package/src/services/sync/repositories/user-award-progress.ts +133 -0
- package/src/services/sync/repository-proxy.ts +6 -0
- package/src/services/sync/retry.ts +12 -11
- package/src/services/sync/schema/index.ts +18 -3
- package/src/services/sync/store/index.ts +53 -8
- package/src/services/sync/store/push-coalescer.ts +3 -3
- package/src/services/sync/store-configs.ts +7 -1
- package/src/services/userActivity.js +0 -1
- package/test/HttpClient.test.js +6 -6
- package/test/awards/award-alacarte-observer.test.js +196 -0
- package/test/awards/award-auto-refresh.test.js +83 -0
- package/test/awards/award-calculations.test.js +33 -0
- package/test/awards/award-certificate-display.test.js +328 -0
- package/test/awards/award-collection-edge-cases.test.js +210 -0
- package/test/awards/award-collection-filtering.test.js +285 -0
- package/test/awards/award-completion-flow.test.js +213 -0
- package/test/awards/award-exclusion-handling.test.js +273 -0
- package/test/awards/award-multi-lesson.test.js +241 -0
- package/test/awards/award-observer-integration.test.js +325 -0
- package/test/awards/award-query-messages.test.js +438 -0
- package/test/awards/award-user-collection.test.js +412 -0
- package/test/awards/duplicate-prevention.test.js +118 -0
- package/test/awards/helpers/completion-mock.js +54 -0
- package/test/awards/helpers/index.js +3 -0
- package/test/awards/helpers/mock-setup.js +69 -0
- package/test/awards/helpers/progress-emitter.js +39 -0
- package/test/awards/message-generator.test.js +162 -0
- package/test/initializeTests.js +6 -0
- package/test/mockData/award-definitions.js +171 -0
- package/test/sync/models/award-database-integration.test.js +519 -0
- package/tools/generate-index.cjs +9 -0
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.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
|
+
|
|
5
21
|
### [2.94.8](https://github.com/railroadmedia/musora-content-services/compare/v2.94.7...v2.94.8) (2025-12-04)
|
|
6
22
|
|
|
7
23
|
|
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/babel.config.cjs
ADDED
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.
|
|
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
|
|
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
|
+
}
|
package/src/filterBuilder.js
CHANGED
|
@@ -58,8 +58,13 @@ export class FilterBuilder {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
async buildFilter() {
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|