musora-content-services 2.89.0 → 2.92.3

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 (139) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/docs/ContentOrganization.html +2 -2
  3. package/docs/Forums.html +2 -2
  4. package/docs/Gamification.html +2 -2
  5. package/docs/TestUser.html +2 -2
  6. package/docs/UserManagementSystem.html +2 -2
  7. package/docs/api_types.js.html +2 -2
  8. package/docs/config.js.html +2 -2
  9. package/docs/content-org_content-org.js.html +2 -2
  10. package/docs/content-org_guided-courses.ts.html +2 -2
  11. package/docs/content-org_learning-paths.ts.html +52 -40
  12. package/docs/content-org_playlists-types.js.html +2 -2
  13. package/docs/content-org_playlists.js.html +2 -2
  14. package/docs/content.js.html +2 -2
  15. package/docs/content_artist.ts.html +2 -2
  16. package/docs/content_genre.ts.html +2 -2
  17. package/docs/content_instructor.ts.html +2 -2
  18. package/docs/forums_categories.ts.html +2 -2
  19. package/docs/forums_forums.ts.html +2 -2
  20. package/docs/forums_posts.ts.html +2 -2
  21. package/docs/forums_threads.ts.html +2 -2
  22. package/docs/gamification_awards.ts.html +2 -2
  23. package/docs/gamification_gamification.js.html +2 -2
  24. package/docs/global.html +2 -2
  25. package/docs/index.html +2 -2
  26. package/docs/liveTesting.ts.html +2 -2
  27. package/docs/module-Accounts.html +2 -2
  28. package/docs/module-Artist.html +2 -2
  29. package/docs/module-Awards.html +2 -2
  30. package/docs/module-Config.html +2 -2
  31. package/docs/module-Content-Services-V2.html +2 -2
  32. package/docs/module-Forums.html +2 -2
  33. package/docs/module-Genre.html +2 -2
  34. package/docs/module-GuidedCourses.html +2 -2
  35. package/docs/module-Instructor.html +2 -2
  36. package/docs/module-Interests.html +2 -2
  37. package/docs/module-LearningPaths.html +269 -143
  38. package/docs/module-Onboarding.html +3 -3
  39. package/docs/module-Payments.html +2 -2
  40. package/docs/module-Permissions.html +2 -2
  41. package/docs/module-Playlists.html +2 -2
  42. package/docs/module-ProgressRow.html +2 -2
  43. package/docs/module-Railcontent-Services.html +34 -893
  44. package/docs/module-Sanity-Services.html +2 -2
  45. package/docs/module-Sessions.html +2 -2
  46. package/docs/module-UserActivity.html +70 -116
  47. package/docs/module-UserChat.html +2 -2
  48. package/docs/module-UserManagement.html +2 -2
  49. package/docs/module-UserMemberships.html +2 -2
  50. package/docs/module-UserNotifications.html +2 -2
  51. package/docs/module-UserProfile.html +2 -2
  52. package/docs/progress-row_method-card.js.html +3 -2
  53. package/docs/railcontent.js.html +14 -137
  54. package/docs/sanity.js.html +2 -2
  55. package/docs/userActivity.js.html +85 -150
  56. package/docs/user_account.ts.html +2 -2
  57. package/docs/user_chat.js.html +2 -2
  58. package/docs/user_interests.js.html +2 -2
  59. package/docs/user_management.js.html +2 -2
  60. package/docs/user_memberships.ts.html +2 -2
  61. package/docs/user_notifications.js.html +2 -2
  62. package/docs/user_onboarding.ts.html +10 -6
  63. package/docs/user_payments.ts.html +2 -2
  64. package/docs/user_permissions.js.html +2 -2
  65. package/docs/user_profile.js.html +2 -2
  66. package/docs/user_sessions.js.html +2 -2
  67. package/docs/user_types.js.html +2 -2
  68. package/docs/user_user-management-system.js.html +2 -2
  69. package/package.json +11 -3
  70. package/src/contentTypeConfig.js +6 -0
  71. package/src/index.d.ts +7 -31
  72. package/src/index.js +10 -34
  73. package/src/services/content-org/learning-paths.ts +31 -0
  74. package/src/services/contentAggregator.js +2 -2
  75. package/src/services/contentLikes.js +6 -39
  76. package/src/services/contentProgress.js +181 -479
  77. package/src/services/dataContext.js +0 -2
  78. package/src/services/progress-row/method-card.js +1 -0
  79. package/src/services/railcontent.js +12 -135
  80. package/src/services/sentry/.indexignore +0 -0
  81. package/src/services/sentry/index.ts +23 -0
  82. package/src/services/sync/.indexignore +0 -0
  83. package/src/services/sync/adapters/factory.ts +26 -0
  84. package/src/services/sync/adapters/lokijs.ts +1 -0
  85. package/src/services/sync/adapters/sqlite.ts +1 -0
  86. package/src/services/sync/concurrency-safety.ts +4 -0
  87. package/src/services/sync/context/index.ts +43 -0
  88. package/src/services/sync/context/providers/base.ts +4 -0
  89. package/src/services/sync/context/providers/connectivity.ts +14 -0
  90. package/src/services/sync/context/providers/durability.ts +5 -0
  91. package/src/services/sync/context/providers/index.ts +5 -0
  92. package/src/services/sync/context/providers/session.ts +8 -0
  93. package/src/services/sync/context/providers/tabs.ts +18 -0
  94. package/src/services/sync/context/providers/visibility.ts +14 -0
  95. package/src/services/sync/database/factory.ts +10 -0
  96. package/src/services/sync/errors/boundary.ts +45 -0
  97. package/src/services/sync/errors/index.ts +49 -0
  98. package/src/services/sync/fetch.ts +310 -0
  99. package/src/services/sync/index.ts +80 -0
  100. package/src/services/sync/manager.ts +139 -0
  101. package/src/services/sync/models/Base.ts +47 -0
  102. package/src/services/sync/models/ContentLike.ts +16 -0
  103. package/src/services/sync/models/ContentProgress.ts +69 -0
  104. package/src/services/sync/models/Practice.ts +72 -0
  105. package/src/services/sync/models/PracticeDayNote.ts +23 -0
  106. package/src/services/sync/models/index.ts +4 -0
  107. package/src/services/sync/repositories/base.ts +247 -0
  108. package/src/services/sync/repositories/content-likes.ts +26 -0
  109. package/src/services/sync/repositories/content-progress.ts +160 -0
  110. package/src/services/sync/repositories/index.ts +4 -0
  111. package/src/services/sync/repositories/practice-day-notes.ts +4 -0
  112. package/src/services/sync/repositories/practices.ts +52 -0
  113. package/src/services/sync/repository-proxy.ts +48 -0
  114. package/src/services/sync/resolver.ts +84 -0
  115. package/src/services/sync/retry.ts +88 -0
  116. package/src/services/sync/run-scope.ts +30 -0
  117. package/src/services/sync/schema/index.ts +66 -0
  118. package/src/services/sync/serializers/index.ts +2 -0
  119. package/src/services/sync/serializers/model.ts +32 -0
  120. package/src/services/sync/serializers/raw.ts +21 -0
  121. package/src/services/sync/store/index.ts +779 -0
  122. package/src/services/sync/store/push-coalescer.ts +57 -0
  123. package/src/services/sync/store-configs.ts +41 -0
  124. package/src/services/sync/strategies/base.ts +21 -0
  125. package/src/services/sync/strategies/index.ts +12 -0
  126. package/src/services/sync/strategies/initial.ts +11 -0
  127. package/src/services/sync/strategies/polling.ts +54 -0
  128. package/src/services/sync/telemetry/index.ts +140 -0
  129. package/src/services/sync/telemetry/sampling.ts +91 -0
  130. package/src/services/sync/utils/event-emitter.ts +24 -0
  131. package/src/services/sync/utils/index.ts +1 -0
  132. package/src/services/sync/utils/throttle.ts +93 -0
  133. package/src/services/sync/utils/timers.ts +9 -0
  134. package/src/services/userActivity.js +83 -148
  135. package/test/contentProgress.test.js +6 -39
  136. package/test/live/contentProgressLive.test.js +2 -31
  137. package/tools/generate-index.cjs +10 -4
  138. package/.claude/settings.local.json +0 -8
  139. package/babel.config.cjs +0 -3
@@ -4,7 +4,6 @@
4
4
 
5
5
  import {
6
6
  fetchUserPractices,
7
- logUserPractice,
8
7
  fetchUserPracticeMeta,
9
8
  fetchUserPracticeNotes,
10
9
  fetchHandler,
@@ -38,13 +37,11 @@ import {
38
37
  import { getAllStartedOrCompleted, getProgressStateByIds } from './contentProgress'
39
38
  import { TabResponseType } from '../contentMetaData'
40
39
  import dayjs from 'dayjs'
41
- import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'
42
- import weekOfYear from 'dayjs/plugin/weekOfYear'
43
40
  import { addContextToContent } from './contentAggregator.js'
44
41
  import { getMethodCard } from './progress-row/method-card.js'
42
+ import { db, Q } from './sync'
45
43
 
46
44
  const DATA_KEY_PRACTICES = 'practices'
47
- const DATA_KEY_LAST_UPDATED_TIME = 'u'
48
45
 
49
46
  const DAYS = ['M', 'T', 'W', 'T', 'F', 'S', 'S']
50
47
 
@@ -77,17 +74,29 @@ function getIndefiniteArticle(streak) {
77
74
  : 'a'
78
75
  }
79
76
 
80
- export async function getUserPractices(userId = globalConfig.sessionConfig.userId) {
81
- if (userId !== globalConfig.sessionConfig.userId) {
82
- let data = await fetchUserPractices(0, { userId: userId })
83
- return data?.['data']?.[DATA_KEY_PRACTICES] ?? {}
77
+ async function getUserPractices(userId) {
78
+ if (userId === globalConfig.sessionConfig.userId) {
79
+ return getOwnPractices()
84
80
  } else {
85
- let data = await userActivityContext.getData()
86
- return data?.[DATA_KEY_PRACTICES] ?? {}
81
+ return await fetchUserPractices(userId)
87
82
  }
88
83
  }
89
84
 
90
- export let userActivityContext = new DataContext(UserActivityVersionKey, fetchUserPractices)
85
+ async function getOwnPractices(...clauses) {
86
+ const query = await db.practices.queryAll(...clauses)
87
+ const data = query.data.reduce((acc, practice) => {
88
+ acc[practice.date] = acc[practice.date] || []
89
+ acc[practice.date].push({
90
+ id: practice.id,
91
+ duration_seconds: practice.duration_seconds,
92
+ })
93
+ return acc
94
+ }, {})
95
+
96
+ return data
97
+ }
98
+
99
+ export let userActivityContext = new DataContext(UserActivityVersionKey, function() {})
91
100
 
92
101
  /**
93
102
  * Retrieves user activity statistics for the current week, including daily activity and streak messages.
@@ -102,20 +111,21 @@ export let userActivityContext = new DataContext(UserActivityVersionKey, fetchUs
102
111
  */
103
112
  export async function getUserWeeklyStats() {
104
113
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
105
- let data = await userActivityContext.getData()
106
- let practices = data?.[DATA_KEY_PRACTICES] ?? {}
107
- let sortedPracticeDays = Object.keys(practices)
108
- .map((date) => toDayjs(date)) // Convert to dayjs instance
109
- .sort((a, b) => b.valueOf() - a.valueOf())
110
- let today = dayjs()
111
- let startOfWeek = getMonday(today, timeZone) // Get last Monday
114
+ const today = dayjs()
115
+ const startOfWeek = getMonday(today, timeZone)
116
+ const weekDays = Array.from({ length: 7 }, (_, i) => startOfWeek.add(i, 'day').format('YYYY-MM-DD'))
117
+
118
+ const practices = await getOwnPractices(
119
+ Q.where('date', Q.oneOf(weekDays)),
120
+ Q.sortBy('date', 'desc')
121
+ )
122
+ const practiceDaysSet = new Set(Object.keys(practices))
112
123
  let dailyStats = []
113
124
  for (let i = 0; i < 7; i++) {
114
125
  const day = startOfWeek.add(i, 'day')
115
- let hasPractice = sortedPracticeDays.some((practiceDate) =>
116
- isSameDate(practiceDate, day.format('YYYY-MM-DD'))
117
- )
118
- let isActive = isSameDate(today.format(), day.format())
126
+ const dayStr = day.format('YYYY-MM-DD')
127
+ let hasPractice = practiceDaysSet.has(dayStr)
128
+ let isActive = isSameDate(today.format(), dayStr)
119
129
  let type = hasPractice ? 'tracked' : isActive ? 'active' : 'none'
120
130
  dailyStats.push({
121
131
  key: i,
@@ -123,7 +133,7 @@ export async function getUserWeeklyStats() {
123
133
  isActive,
124
134
  inStreak: hasPractice,
125
135
  type,
126
- day: day.format('YYYY-MM-DD'),
136
+ day: dayStr,
127
137
  })
128
138
  }
129
139
 
@@ -264,82 +274,50 @@ export async function getUserMonthlyStats(params = {}) {
264
274
  }
265
275
 
266
276
  /**
267
- * Records user practice data and updates both the remote and local activity context.
277
+ * Records a manual user practice data, updating the local database and syncing with remote.
268
278
  *
269
279
  * @param {Object} practiceDetails - The details of the practice session.
270
280
  * @param {number} practiceDetails.duration_seconds - The duration of the practice session in seconds.
271
- * @param {boolean} [practiceDetails.auto=true] - Whether the session was automatically logged.
272
- * @param {number} [practiceDetails.content_id] - The ID of the practiced content (if available).
273
- * @param {number} [practiceDetails.category_id] - The ID of the associated category (if available).
274
281
  * @param {string} [practiceDetails.title] - The title of the practice session (max 64 characters).
282
+ * @param {number} [practiceDetails.category_id] - The ID of the associated category (if available).
275
283
  * @param {string} [practiceDetails.thumbnail_url] - The URL of the session's thumbnail (max 255 characters).
284
+ * @param {number} [practiceDetails.instrument_id] - The ID of the associated instrument (if available).
276
285
  * @returns {Promise<Object>} - A promise that resolves to the response from logging the user practice.
277
286
  *
278
287
  * @example
279
- * // Record an auto practice session with content ID
280
- * recordUserPractice({ content_id: 123, duration_seconds: 300 })
288
+ * // Record a manual practice session with a title
289
+ * recordUserPractice({ title: "Some title", duration_seconds: 300 })
281
290
  * .then(response => console.log(response))
282
291
  * .catch(error => console.error(error));
283
292
  *
284
- * @example
285
- * // Record a custom practice session with additional details
286
- * recordUserPractice({
287
- * duration_seconds: 600,
288
- * auto: false,
289
- * category_id: 5,
290
- * title: "Guitar Warm-up",
291
- * thumbnail_url: "https://example.com/thumbnail.jpg",
292
- * instrument_id: 1,
293
- * instrument_id: 2,
294
- * })
295
- * .then(response => console.log(response))
296
- * .catch(error => console.error(error));
297
293
  */
298
294
  export async function recordUserPractice(practiceDetails) {
299
- practiceDetails.auto = 0
300
- practiceDetails.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone
301
- if (practiceDetails.content_id) {
302
- practiceDetails.auto = 1
303
- }
295
+ const day = new Date().toLocaleDateString('sv-SE'); // YYYY-MM-DD wall clock date in user's timezone
296
+ const durationSeconds = practiceDetails.duration_seconds
297
+
298
+ return await db.practices.recordManualPractice(day, durationSeconds, {
299
+ title: practiceDetails.title ?? null,
300
+ category_id: practiceDetails.category_id ?? null,
301
+ thumbnail_url: practiceDetails.thumbnail_url ?? null,
302
+ instrument_id: practiceDetails.instrument_id ?? null,
303
+ })
304
+ }
304
305
 
305
- await userActivityContext.update(
306
- async function (localContext) {
307
- let userData = localContext.data ?? { [DATA_KEY_PRACTICES]: {} }
308
- localContext.data = userData
309
- },
310
- async function () {
311
- const response = await logUserPractice(practiceDetails)
312
- if (response) {
313
- await userActivityContext.updateLocal(async function (localContext) {
314
- const newPractices = response.data ?? []
315
- newPractices.forEach((newPractice) => {
316
- const { date } = newPractice
317
- if (!localContext.data[DATA_KEY_PRACTICES][date]) {
318
- localContext.data[DATA_KEY_PRACTICES][date] = []
319
- }
320
- localContext.data[DATA_KEY_PRACTICES][date][DATA_KEY_LAST_UPDATED_TIME] = Math.round(
321
- new Date().getTime() / 1000
322
- )
323
- localContext.data[DATA_KEY_PRACTICES][date].push({
324
- id: newPractice.id,
325
- duration_seconds: newPractice.duration_seconds, // Add the new practice for this date
326
- })
327
- })
328
- })
329
- }
330
- return response
331
- }
332
- )
306
+ export async function trackUserPractice(contentId, incSeconds) {
307
+ const day = new Date().toLocaleDateString('sv-SE'); // YYYY-MM-DD wall clock date in user's timezone
308
+ return await db.practices.trackAutoPractice(contentId, day, incSeconds);
333
309
  }
310
+
334
311
  /**
335
312
  * Updates a user's practice session with new details and syncs the changes remotely.
336
313
  *
337
- * @param {number} id - The unique identifier of the practice session to update.
314
+ * @param {string} id - The unique identifier of the practice session to update.
338
315
  * @param {Object} practiceDetails - The updated details of the practice session.
339
316
  * @param {number} [practiceDetails.duration_seconds] - The duration of the practice session in seconds.
340
317
  * @param {number} [practiceDetails.category_id] - The ID of the associated category (if available).
341
318
  * @param {string} [practiceDetails.title] - The title of the practice session (max 64 characters).
342
319
  * @param {string} [practiceDetails.thumbnail_url] - The URL of the session's thumbnail (max 255 characters).
320
+ * @param {number} [practiceDetails.instrument_id] - The ID of the associated instrument (if available).
343
321
  * @returns {Promise<Object>} - A promise that resolves to the response from updating the user practice.
344
322
  *
345
323
  * @example
@@ -348,15 +326,9 @@ export async function recordUserPractice(practiceDetails) {
348
326
  * .then(response => console.log(response))
349
327
  * .catch(error => console.error(error));
350
328
  *
351
- * @example
352
- * // Change a practice session to manual and update its category
353
- * updateUserPractice(456, { auto: false, category_id: 8 })
354
- * .then(response => console.log(response))
355
- * .catch(error => console.error(error));
356
329
  */
357
330
  export async function updateUserPractice(id, practiceDetails) {
358
- const url = `/api/user/practices/v1/practices/${id}`
359
- return await fetchHandler(url, 'PUT', null, practiceDetails)
331
+ return await db.practices.updateDetails(id, practiceDetails)
360
332
  }
361
333
 
362
334
  /**
@@ -372,26 +344,7 @@ export async function updateUserPractice(id, practiceDetails) {
372
344
  * .catch(error => console.error(error));
373
345
  */
374
346
  export async function removeUserPractice(id) {
375
- let url = `/api/user/practices/v1/practices${buildQueryString([id])}`
376
- await userActivityContext.update(
377
- async function (localContext) {
378
- if (localContext.data?.[DATA_KEY_PRACTICES]) {
379
- Object.keys(localContext.data[DATA_KEY_PRACTICES]).forEach((date) => {
380
- const filtered = localContext.data[DATA_KEY_PRACTICES][date].filter(
381
- (practice) => practice.id !== id
382
- )
383
- if (filtered.length > 0) {
384
- localContext.data[DATA_KEY_PRACTICES][date] = filtered
385
- } else {
386
- delete localContext.data[DATA_KEY_PRACTICES][date]
387
- }
388
- })
389
- }
390
- },
391
- async function () {
392
- return await fetchHandler(url, 'delete')
393
- }
394
- )
347
+ return await db.practices.deleteOne(id)
395
348
  }
396
349
 
397
350
  /**
@@ -455,20 +408,8 @@ export async function restoreUserPractice(id) {
455
408
  * .catch(error => console.error("Delete failed:", error));
456
409
  */
457
410
  export async function deletePracticeSession(day) {
458
- const userPracticesIds = await getUserPracticeIds(day)
459
- if (!userPracticesIds.length) return []
460
-
461
- const url = `/api/user/practices/v1/practices${buildQueryString(userPracticesIds)}`
462
- await userActivityContext.update(
463
- async function (localContext) {
464
- if (localContext.data?.[DATA_KEY_PRACTICES]?.[day]) {
465
- delete localContext.data[DATA_KEY_PRACTICES][day]
466
- }
467
- },
468
- async function () {
469
- return await fetchHandler(url, 'DELETE', null)
470
- }
471
- )
411
+ const ids = await db.practices.queryAllIds(Q.where('date', day))
412
+ return await db.practices.deleteSome(ids.data)
472
413
  }
473
414
 
474
415
  /**
@@ -539,13 +480,23 @@ export async function restorePracticeSession(date) {
539
480
  */
540
481
  export async function getPracticeSessions(params = {}) {
541
482
  const { day, userId = globalConfig.sessionConfig.userId } = params
542
- const userPracticesIds = await getUserPracticeIds(day, userId)
543
- if (!userPracticesIds.length) return { data: { practices: [], practiceDuration: 0 } }
544
483
 
545
- const meta = await fetchUserPracticeMeta(userPracticesIds, userId)
546
- if (!meta.data.length) return { data: { practices: [], practiceDuration: 0 } }
484
+ let data
547
485
 
548
- const formattedMeta = await formatPracticeMeta(meta.data)
486
+ if (userId === globalConfig.sessionConfig.userId) {
487
+ const query = await db.practices.queryAll(
488
+ Q.where('date', day),
489
+ Q.sortBy('created_at', 'asc')
490
+ )
491
+ data = query.data
492
+ } else {
493
+ const query = await fetchUserPracticeMeta(day, userId)
494
+ data = query.data
495
+ }
496
+
497
+ if (!data.length) return { data: { practices: [], practiceDuration: 0 } }
498
+
499
+ const formattedMeta = await formatPracticeMeta(data)
549
500
  const practiceDuration = formattedMeta.reduce(
550
501
  (total, practice) => total + (practice.duration || 0),
551
502
  0
@@ -567,9 +518,8 @@ export async function getPracticeSessions(params = {}) {
567
518
  * .then(({ data }) => console.log("Practice notes:", data))
568
519
  * .catch(error => console.error("Failed to get notes:", error));
569
520
  */
570
- export async function getPracticeNotes(day) {
571
- const notes = await fetchUserPracticeNotes(day)
572
- return { data: notes }
521
+ export async function getPracticeNotes(date) {
522
+ return await db.practiceDayNotes.queryOne(Q.where('date', date))
573
523
  }
574
524
 
575
525
  /**
@@ -618,8 +568,10 @@ export async function getRecentActivity({ page = 1, limit = 5, tabName = null }
618
568
  * .catch(error => console.error(error));
619
569
  */
620
570
  export async function createPracticeNotes(payload) {
621
- const url = `/api/user/practices/v1/notes`
622
- return await fetchHandler(url, 'POST', null, payload)
571
+ return await db.practiceDayNotes.upsertOne(payload.date, r => {
572
+ r.date = payload.date
573
+ r.notes = payload.notes
574
+ })
623
575
  }
624
576
 
625
577
  /**
@@ -636,8 +588,9 @@ export async function createPracticeNotes(payload) {
636
588
  * .catch(error => console.error(error));
637
589
  */
638
590
  export async function updatePracticeNotes(payload) {
639
- const url = `/api/user/practices/v1/notes`
640
- return await fetchHandler(url, 'PUT', null, payload)
591
+ return await db.practiceDayNotes.updateOneId(payload.date, r => {
592
+ r.notes = payload.notes
593
+ })
641
594
  }
642
595
 
643
596
  function getStreaksAndMessage(practices) {
@@ -650,24 +603,6 @@ function getStreaksAndMessage(practices) {
650
603
  }
651
604
  }
652
605
 
653
- async function getUserPracticeIds(day = dayjs().format('YYYY-MM-DD'), userId = null) {
654
- let practices = {}
655
- if (userId !== globalConfig.sessionConfig.userId) {
656
- let data = await fetchUserPractices(0, { userId: userId })
657
- practices = data?.['data']?.[DATA_KEY_PRACTICES] ?? {}
658
- } else {
659
- let data = await userActivityContext.getData()
660
- practices = data?.[DATA_KEY_PRACTICES] ?? {}
661
- }
662
- let userPracticesIds = []
663
- Object.keys(practices).forEach((date) => {
664
- if (date === day) {
665
- practices[date].forEach((practice) => userPracticesIds.push(practice.id))
666
- }
667
- })
668
- return userPracticesIds
669
- }
670
-
671
606
  function buildQueryString(ids, paramName = 'practice_ids') {
672
607
  if (!ids.length) return ''
673
608
  return '?' + ids.map((id) => `${paramName}[]=${id}`).join('&')
@@ -1049,7 +984,7 @@ export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}) {
1049
984
  const methodCardPromise = getMethodCard(brand)
1050
985
  const [recentPlaylists, progressContents, userPinnedItem] = await Promise.all([
1051
986
  fetchUserPlaylists(brand, { sort: '-last_progress', limit: limit }),
1052
- getAllStartedOrCompleted({ onlyIds: false, brand: brand }),
987
+ getAllStartedOrCompleted({ onlyIds: false, brand: brand, limit }),
1053
988
  getUserPinnedItem(brand),
1054
989
  ])
1055
990
 
@@ -14,7 +14,7 @@ import {
14
14
  getAllStartedOrCompleted,
15
15
  } from '../src/services/contentProgress'
16
16
  import { initializeTestService } from './initializeTests'
17
- import {getLessonContentRows, postContentComplete} from '../src'
17
+ import {getLessonContentRows} from '../src'
18
18
  import {fetchRecent} from "../src/services/sanity";
19
19
  import {getRecent, getTabResults} from "../src/services/content";
20
20
  import {individualLessonsTypes, playAlongLessonTypes, transcriptionsLessonTypes, tutorialsLessonTypes} from "../src/contentTypeConfig";
@@ -37,15 +37,6 @@ describe('contentProgressDataContext', function () {
37
37
  )
38
38
  mock.mockImplementation(() => json)
39
39
 
40
- let mock2 = jest.spyOn(railContentModule, 'postRecordWatchSession')
41
- mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`))
42
-
43
- let mock3 = jest.spyOn(railContentModule, 'postContentComplete')
44
- mock3.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`))
45
-
46
- let mock4 = jest.spyOn(railContentModule, 'postContentReset')
47
- mock4.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`))
48
-
49
40
  let mock5 = jest.spyOn(contentModule, 'getContentRows')
50
41
  let testData = [
51
42
  {
@@ -107,42 +98,18 @@ describe('contentProgressDataContext', function () {
107
98
  expect(result).toStrictEqual([407665, 259426, 412986, 233955, 190417,234191])
108
99
  })
109
100
 
110
- // test('getAllStartedWithUpdate', async () => {
111
- // let mock2 = jest.spyOn(railContentModule, 'postRecordWatchSession');
112
- // let serverVersion = 2;
113
- // mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
114
- // let result = await getAllStarted();
115
- // expect(result).toStrictEqual([233955, 234191]);
116
- // await recordWatchSession(111111, "video", "vimeo", 100, 50, 50);
117
- //
118
- // let result2 = await getAllStarted();
119
- // expect(result2).toStrictEqual([111111, 233955, 234191]);
120
- // });
121
-
122
- // test('getAllCompletedWithUpdate', async () => {
123
- // let mock2 = jest.spyOn(railContentModule, 'postContentComplete');
124
- // let serverVersion = 2;
125
- // mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
126
- //
127
- // let result = await getAllCompleted();
128
- // expect(result).toStrictEqual([259426]);
129
- // await contentStatusCompleted(111111);
130
- // let result2 = await getAllCompleted();
131
- // expect(result2).toStrictEqual([111111, 259426]);
132
- // });
133
-
134
101
  test('progressBubbling', async () => {
135
102
  let progress = await getProgressPercentage(241250) //force load context
136
103
 
137
- await recordWatchSession(241250, 'video', 'vimeo', 100, 50, 50)
104
+ await recordWatchSession(241250, null, 'video', 'vimeo', 100, 50, 50)
138
105
  serverVersion++
139
- await recordWatchSession(241251, 'video', 'vimeo', 100, 50, 50)
106
+ await recordWatchSession(241251, null, 'video', 'vimeo', 100, 50, 50)
140
107
  serverVersion++
141
- await recordWatchSession(241252, 'video', 'vimeo', 100, 50, 50)
108
+ await recordWatchSession(241252, null, 'video', 'vimeo', 100, 50, 50)
142
109
  serverVersion++
143
- await recordWatchSession(241260, 'video', 'vimeo', 100, 100, 100)
110
+ await recordWatchSession(241260, null, 'video', 'vimeo', 100, 100, 100)
144
111
  serverVersion++
145
- await recordWatchSession(241261, 'video', 'vimeo', 100, 100, 100)
112
+ await recordWatchSession(241261, null, 'video', 'vimeo', 100, 100, 100)
146
113
  serverVersion++
147
114
  progress = await getProgressPercentage(241250)
148
115
 
@@ -18,7 +18,7 @@ describe('contentProgressDataContextLocal', function () {
18
18
  let contentId = 241250
19
19
  await contentStatusReset(contentId)
20
20
 
21
- await recordWatchSession(contentId, 'video', 'vimeo', 100, 50, 50)
21
+ await recordWatchSession(contentId, null, 'video', 'vimeo', 100, 50, 50)
22
22
 
23
23
  let result = await getProgressPercentage(contentId)
24
24
  expect(result).toBe(50)
@@ -35,7 +35,7 @@ describe('contentProgressDataContextLocal', function () {
35
35
  let result = await getProgressState(contentId)
36
36
  expect(result).toBe('')
37
37
 
38
- await recordWatchSession(contentId, 'video', 'vimeo', 100, 50, 50)
38
+ await recordWatchSession(contentId, null, 'video', 'vimeo', 100, 50, 50)
39
39
 
40
40
  result = await getProgressState(contentId)
41
41
  expect(result).toBe('started')
@@ -107,33 +107,4 @@ describe('contentProgressDataContextLocal', function () {
107
107
  expect(state).toBe('completed')
108
108
  }
109
109
  }, 100000)
110
-
111
- //
112
- // test('progressBubbling', async () => {
113
- // let serverVersion = 2;
114
- // let mock2 = jest.spyOn(railContentModule, 'postRecordWatchSession');
115
- // mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
116
- // let progress = await getProgressPercentage(241250); //force load context
117
- //
118
- // await recordWatchSession(241250, "video", "vimeo", 100, 50, 50);
119
- // serverVersion++;
120
- // await recordWatchSession(241251, "video", "vimeo", 100, 50, 50);
121
- // serverVersion++;
122
- // await recordWatchSession(241252, "video", "vimeo", 100, 50, 50);
123
- // serverVersion++;
124
- // await recordWatchSession(241260, "video", "vimeo", 100, 100, 100);
125
- // serverVersion++;
126
- // await recordWatchSession(241261, "video", "vimeo", 100, 100, 100);
127
- // serverVersion++;
128
- // progress = await getProgressPercentage(241250);
129
- //
130
- // expect(progress).toBe(50);
131
- // let progress241249 = await getProgressPercentage(241249);
132
- // expect(progress241249).toBe(15);
133
- // let progress241248 = await getProgressPercentage(241248);
134
- // expect(progress241248).toBe(7);
135
- // let progress241247 = await getProgressPercentage(241247);
136
- // expect(progress241247).toBe(1);
137
- //
138
- // }, 100000);
139
110
  })
@@ -73,6 +73,12 @@ treeElements.forEach((treeNode) => {
73
73
  if (fs.lstatSync(filePath).isFile()) {
74
74
  addFunctionsToFileExports(filePath, treeNode)
75
75
  } else if (fs.lstatSync(filePath).isDirectory()) {
76
+
77
+ // Check for .indexignore file to skip this directory
78
+ if (fs.existsSync(path.join(filePath, '.indexignore'))) {
79
+ console.log(`Skipping directory: ${treeNode} due to .indexignore`)
80
+ return
81
+ }
76
82
  // Skip the permissions directory - it has its own index.ts barrel export
77
83
  if (treeNode === 'permissions') {
78
84
  return
@@ -80,8 +86,8 @@ treeElements.forEach((treeNode) => {
80
86
 
81
87
  const subDir = fs.readdirSync(filePath)
82
88
  subDir.forEach((subFile) => {
83
- const filePath = path.join(servicesDir, treeNode, subFile)
84
- addFunctionsToFileExports(filePath, treeNode + '/' + subFile)
89
+ const subFilePath = path.join(servicesDir, treeNode, subFile)
90
+ addFunctionsToFileExports(subFilePath, treeNode + '/' + subFile)
85
91
  })
86
92
  }
87
93
  })
@@ -90,12 +96,12 @@ treeElements.forEach((treeNode) => {
90
96
  let content =
91
97
  '/*** This file was generated automatically. To recreate, please run `npm run build-index`. ***/\n'
92
98
 
99
+ content += `\nimport {\n\t default as EventsAPI \n} from './services/eventsAPI';\n`
100
+
93
101
  Object.entries(fileExports).forEach(([file, functionNames]) => {
94
102
  content += `\nimport {\n\t${functionNames.join(',\n\t')}\n} from './services/${file}';\n`
95
103
  })
96
104
 
97
- content += `\nimport {\n\t default as EventsAPI \n} from './services/eventsAPI';\n`
98
-
99
105
  const allFunctionNames = Object.values(fileExports).flat().sort()
100
106
  content += '\nexport {\n'
101
107
  content += `\t${allFunctionNames.join(',\n\t')},\n`
@@ -1,8 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(rg:*)"
5
- ],
6
- "deny": []
7
- }
8
- }
package/babel.config.cjs DELETED
@@ -1,3 +0,0 @@
1
- module.exports = {
2
- presets: ['@babel/preset-env'],
3
- }