musora-content-services 2.118.1 → 2.119.1

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 (218) 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 +14 -0
  10. package/CLAUDE.md +0 -0
  11. package/README.md +0 -0
  12. package/babel.config.cjs +0 -0
  13. package/jsdoc.json +0 -0
  14. package/package.json +1 -1
  15. package/src/constants/award-assets.js +0 -0
  16. package/src/constants/membership-permissions.ts +0 -0
  17. package/src/filterBuilder.js +0 -0
  18. package/src/infrastructure/http/HttpClient.ts +0 -0
  19. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  20. package/src/infrastructure/http/index.ts +0 -0
  21. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  22. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  23. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  24. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  25. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  26. package/src/lib/ads/monoid.ts +0 -0
  27. package/src/lib/ads/semigroup.ts +0 -0
  28. package/src/lib/brands.ts +0 -0
  29. package/src/lib/lastUpdated.js +0 -0
  30. package/src/lib/sanity/filter.ts +0 -0
  31. package/src/lib/sanity/query.ts +0 -0
  32. package/src/services/api/types.js +0 -0
  33. package/src/services/api/types.ts +0 -0
  34. package/src/services/awards/award-callbacks.js +0 -0
  35. package/src/services/awards/award-query.js +0 -0
  36. package/src/services/awards/internal/.indexignore +0 -0
  37. package/src/services/awards/internal/award-definitions.js +0 -0
  38. package/src/services/awards/internal/award-events.js +0 -0
  39. package/src/services/awards/internal/award-manager.js +0 -0
  40. package/src/services/awards/internal/certificate-builder.js +0 -0
  41. package/src/services/awards/internal/completion-data-generator.js +0 -0
  42. package/src/services/awards/internal/content-progress-observer.js +0 -0
  43. package/src/services/awards/internal/image-utils.js +0 -0
  44. package/src/services/awards/internal/message-generator.js +0 -0
  45. package/src/services/awards/internal/types.js +0 -0
  46. package/src/services/awards/types.d.ts +0 -0
  47. package/src/services/awards/types.js +0 -0
  48. package/src/services/content/artist.ts +0 -0
  49. package/src/services/content/content.ts +0 -0
  50. package/src/services/content/genre.ts +0 -0
  51. package/src/services/content/instructor.ts +0 -0
  52. package/src/services/content-org/content-org.js +0 -0
  53. package/src/services/content-org/guided-courses.ts +0 -0
  54. package/src/services/content-org/learning-paths.ts +0 -0
  55. package/src/services/content-org/playlists-types.js +0 -0
  56. package/src/services/content-org/playlists.js +0 -0
  57. package/src/services/contentAggregator.js +0 -0
  58. package/src/services/contentLikes.js +0 -0
  59. package/src/services/contentProgress.js +0 -0
  60. package/src/services/dateUtils.js +0 -0
  61. package/src/services/eventsAPI.js +0 -0
  62. package/src/services/forums/categories.ts +0 -0
  63. package/src/services/forums/forums.ts +0 -0
  64. package/src/services/forums/posts.ts +0 -0
  65. package/src/services/forums/threads.ts +0 -0
  66. package/src/services/forums/types.ts +0 -0
  67. package/src/services/gamification/awards.ts +0 -0
  68. package/src/services/gamification/gamification.js +0 -0
  69. package/src/services/imageSRCBuilder.js +0 -0
  70. package/src/services/imageSRCVerify.js +0 -0
  71. package/src/services/liveTesting.ts +0 -0
  72. package/src/services/permissions/PermissionsAdapter.ts +0 -0
  73. package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
  74. package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
  75. package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
  76. package/src/services/permissions/README.md +0 -0
  77. package/src/services/permissions/index.ts +0 -0
  78. package/src/services/progress-events.js +0 -0
  79. package/src/services/progress-row/rows/.indexignore +0 -0
  80. package/src/services/progress-row/rows/content-card.js +0 -0
  81. package/src/services/progress-row/rows/playlist-card.js +0 -0
  82. package/src/services/railcontent.js +0 -0
  83. package/src/services/reporting/README.md +0 -0
  84. package/src/services/reporting/reporting.ts +0 -0
  85. package/src/services/reporting/types.ts +0 -0
  86. package/src/services/sentry/.indexignore +0 -0
  87. package/src/services/sentry/index.ts +0 -0
  88. package/src/services/sync/.indexignore +0 -0
  89. package/src/services/sync/adapters/factory.ts +0 -0
  90. package/src/services/sync/adapters/lokijs.ts +0 -0
  91. package/src/services/sync/adapters/sqlite.ts +0 -0
  92. package/src/services/sync/context/index.ts +0 -0
  93. package/src/services/sync/context/providers/base.ts +0 -0
  94. package/src/services/sync/context/providers/connectivity.ts +0 -0
  95. package/src/services/sync/context/providers/durability.ts +0 -0
  96. package/src/services/sync/context/providers/index.ts +0 -0
  97. package/src/services/sync/context/providers/session.ts +0 -0
  98. package/src/services/sync/context/providers/tabs.ts +0 -0
  99. package/src/services/sync/context/providers/visibility.ts +0 -0
  100. package/src/services/sync/database/factory.ts +0 -0
  101. package/src/services/sync/effects/index.ts +0 -0
  102. package/src/services/sync/effects/logout-warning.ts +0 -0
  103. package/src/services/sync/errors/boundary.ts +0 -0
  104. package/src/services/sync/errors/index.ts +0 -0
  105. package/src/services/sync/errors/validators.ts +0 -0
  106. package/src/services/sync/fetch.ts +0 -0
  107. package/src/services/sync/index.ts +0 -0
  108. package/src/services/sync/manager.ts +4 -0
  109. package/src/services/sync/models/Base.ts +0 -0
  110. package/src/services/sync/models/ContentLike.ts +0 -0
  111. package/src/services/sync/models/ContentProgress.ts +0 -0
  112. package/src/services/sync/models/Practice.ts +0 -0
  113. package/src/services/sync/models/PracticeDayNote.ts +0 -0
  114. package/src/services/sync/models/UserAwardProgress.ts +0 -0
  115. package/src/services/sync/models/index.ts +0 -0
  116. package/src/services/sync/repositories/base.ts +18 -0
  117. package/src/services/sync/repositories/content-likes.ts +0 -0
  118. package/src/services/sync/repositories/content-progress.ts +0 -0
  119. package/src/services/sync/repositories/index.ts +0 -0
  120. package/src/services/sync/repositories/practice-day-notes.ts +0 -0
  121. package/src/services/sync/repositories/practices.ts +0 -0
  122. package/src/services/sync/repositories/user-award-progress.ts +0 -0
  123. package/src/services/sync/repository-proxy.ts +0 -0
  124. package/src/services/sync/resolver.ts +0 -0
  125. package/src/services/sync/retry.ts +0 -0
  126. package/src/services/sync/run-scope.ts +0 -0
  127. package/src/services/sync/schema/index.ts +0 -0
  128. package/src/services/sync/serializers/index.ts +0 -0
  129. package/src/services/sync/serializers/model.ts +0 -0
  130. package/src/services/sync/serializers/raw.ts +0 -0
  131. package/src/services/sync/store/index.ts +154 -33
  132. package/src/services/sync/store/push-coalescer.ts +0 -0
  133. package/src/services/sync/store-configs.ts +0 -0
  134. package/src/services/sync/strategies/base.ts +0 -0
  135. package/src/services/sync/strategies/index.ts +0 -0
  136. package/src/services/sync/strategies/initial.ts +0 -0
  137. package/src/services/sync/strategies/polling.ts +0 -0
  138. package/src/services/sync/telemetry/flood-prevention.ts +0 -0
  139. package/src/services/sync/telemetry/index.ts +0 -0
  140. package/src/services/sync/telemetry/sampling.ts +0 -0
  141. package/src/services/sync/utils/event-emitter.ts +0 -0
  142. package/src/services/sync/utils/index.ts +0 -0
  143. package/src/services/sync/utils/throttle.ts +0 -0
  144. package/src/services/sync/utils/timers.ts +0 -0
  145. package/src/services/urlBuilder.ts +0 -0
  146. package/src/services/user/account.ts +0 -0
  147. package/src/services/user/chat.js +0 -0
  148. package/src/services/user/interests.js +0 -0
  149. package/src/services/user/management.js +0 -0
  150. package/src/services/user/memberships.ts +0 -0
  151. package/src/services/user/notifications.js +0 -0
  152. package/src/services/user/onboarding.ts +0 -0
  153. package/src/services/user/payments.ts +0 -0
  154. package/src/services/user/permissions.js +0 -0
  155. package/src/services/user/profile.js +0 -0
  156. package/src/services/user/streakCalculator.ts +66 -0
  157. package/src/services/user/types.d.ts +0 -0
  158. package/src/services/user/types.js +0 -0
  159. package/src/services/user/user-management-system.js +0 -0
  160. package/src/services/userActivity.js +74 -103
  161. package/test/HttpClient.test.js +0 -0
  162. package/test/awards/award-alacarte-observer.test.js +0 -0
  163. package/test/awards/award-auto-refresh.test.js +0 -0
  164. package/test/awards/award-calculations.test.js +0 -0
  165. package/test/awards/award-certificate-display.test.js +0 -0
  166. package/test/awards/award-collection-edge-cases.test.js +0 -0
  167. package/test/awards/award-collection-filtering.test.js +0 -0
  168. package/test/awards/award-completion-flow.test.js +0 -0
  169. package/test/awards/award-exclusion-handling.test.js +0 -0
  170. package/test/awards/award-multi-lesson.test.js +0 -0
  171. package/test/awards/award-observer-integration.test.js +0 -0
  172. package/test/awards/award-query-messages.test.js +0 -0
  173. package/test/awards/award-user-collection.test.js +0 -0
  174. package/test/awards/duplicate-prevention.test.js +0 -0
  175. package/test/awards/helpers/completion-mock.js +0 -0
  176. package/test/awards/helpers/index.js +0 -0
  177. package/test/awards/helpers/mock-setup.js +0 -0
  178. package/test/awards/helpers/progress-emitter.js +0 -0
  179. package/test/awards/message-generator.test.js +0 -0
  180. package/test/content.test.js +0 -0
  181. package/test/contentLikes.test.js +0 -0
  182. package/test/contentProgress.test.js +0 -0
  183. package/test/dataContext.test.js +0 -0
  184. package/test/forum.test.js +0 -0
  185. package/test/imageSRCBuilder.test.js +0 -0
  186. package/test/imageSRCVerify.test.js +0 -0
  187. package/test/initializeTests.js +0 -0
  188. package/test/learningPaths.test.js +0 -0
  189. package/test/lib/__snapshots__/filter.test.ts.snap +0 -0
  190. package/test/lib/filter.test.ts +0 -0
  191. package/test/lib/lastUpdated.test.js +0 -0
  192. package/test/lib/query.test.ts +0 -0
  193. package/test/live/contentProgressLive.test.js +0 -0
  194. package/test/live/railcontentLive.test.js +0 -0
  195. package/test/log.js +0 -0
  196. package/test/mockData/award-definitions.js +0 -0
  197. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  198. package/test/mockData/mockData_progress_content.json +0 -0
  199. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  200. package/test/mockData/mockData_user_practices.json +0 -0
  201. package/test/notifications.test.js +0 -0
  202. package/test/progressRows.test.js +0 -0
  203. package/test/sanityQueryService.test.js +0 -0
  204. package/test/streakMessage.test.js +0 -0
  205. package/test/sync/adapter.ts +0 -0
  206. package/test/sync/initialize-sync-manager.js +0 -0
  207. package/test/sync/models/award-database-integration.test.js +0 -0
  208. package/test/user/permissions.test.js +0 -0
  209. package/test/userActivity.test.js +0 -0
  210. package/tools/generate-index.cjs +0 -0
  211. package/.claude/settings.local.json +0 -16
  212. package/.yarnrc.yml +0 -1
  213. package/check_content.js +0 -30
  214. package/check_content.mjs +0 -32
  215. package/test/logout.test.js +0 -199
  216. package/test/reporting.test.js +0 -132
  217. package/test_owned_navigate.js +0 -74
  218. package/tsconfig.json +0 -17
@@ -2,26 +2,18 @@
2
2
  * @module UserActivity
3
3
  */
4
4
 
5
- import {
6
- fetchUserPractices,
7
- fetchUserPracticeMeta,
8
- fetchRecentUserActivities,
9
- } from './railcontent'
5
+ import { fetchUserPractices, fetchUserPracticeMeta, fetchRecentUserActivities } from './railcontent'
10
6
  import { GET, POST, PUT, DELETE } from '../infrastructure/http/HttpClient.ts'
11
7
  import { DataContext, UserActivityVersionKey } from './dataContext.js'
12
8
  import { fetchByRailContentIds } from './sanity'
13
- import {
14
- getMonday,
15
- getWeekNumber,
16
- isSameDate,
17
- isNextDay,
18
- } from './dateUtils.js'
9
+ import { getMonday, getWeekNumber, isSameDate, isNextDay } from './dateUtils.js'
19
10
  import { globalConfig } from './config'
20
11
  import { getFormattedType } from '../contentTypeConfig'
21
12
  import dayjs from 'dayjs'
22
13
  import { addContextToContent } from './contentAggregator.js'
23
14
  import { db, Q } from './sync'
24
- import {COLLECTION_TYPE} from "./sync/models/ContentProgress";
15
+ import { COLLECTION_TYPE } from './sync/models/ContentProgress'
16
+ import { streakCalculator } from './user/streakCalculator'
25
17
 
26
18
  const DATA_KEY_PRACTICES = 'practices'
27
19
 
@@ -78,7 +70,7 @@ async function getOwnPractices(...clauses) {
78
70
  return data
79
71
  }
80
72
 
81
- export let userActivityContext = new DataContext(UserActivityVersionKey, function() {})
73
+ export let userActivityContext = new DataContext(UserActivityVersionKey, function () {})
82
74
 
83
75
  /**
84
76
  * Retrieves user activity statistics for the current week, including daily activity and streak messages.
@@ -95,24 +87,14 @@ export async function getUserWeeklyStats() {
95
87
  const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
96
88
  const today = dayjs()
97
89
  const startOfWeek = getMonday(today, timeZone)
98
- const weekDays = Array.from({ length: 7 }, (_, i) => startOfWeek.add(i, 'day').format('YYYY-MM-DD'))
99
- // Query THIS WEEK's practices for display
100
- const weekPractices = await getOwnPractices(
101
- Q.where('date', Q.oneOf(weekDays)),
102
- Q.sortBy('date', 'desc')
90
+ const weekDays = Array.from({ length: 7 }, (_, i) =>
91
+ startOfWeek.add(i, 'day').format('YYYY-MM-DD')
103
92
  )
104
93
 
105
- // Query LAST 60 DAYS for streak calculation (balances accuracy vs performance)
106
- // This captures:
107
- // - Current active streaks up to 60 days
108
- // - Recent breaks (to show "restart" message)
109
- // - Sufficient context for accurate weekly streak calculation
110
- const sixtyDaysAgo = today.subtract(60, 'days').format('YYYY-MM-DD')
111
- const recentPractices = await getOwnPractices(
112
- Q.where('date', Q.gte(sixtyDaysAgo)),
94
+ const weekPractices = await getOwnPractices(
95
+ Q.where('date', Q.oneOf(weekDays)),
113
96
  Q.sortBy('date', 'desc')
114
97
  )
115
-
116
98
  const practiceDaysSet = new Set(Object.keys(weekPractices))
117
99
  let dailyStats = []
118
100
  for (let i = 0; i < 7; i++) {
@@ -131,8 +113,15 @@ export async function getUserWeeklyStats() {
131
113
  })
132
114
  }
133
115
 
134
- let { streakMessage } = getStreaksAndMessage(recentPractices)
135
- return { data: { dailyActiveStats: dailyStats, streakMessage, practices: weekPractices } }
116
+ const streakData = await streakCalculator.getStreakData()
117
+
118
+ return {
119
+ data: {
120
+ dailyActiveStats: dailyStats,
121
+ streakMessage: streakData.streakMessage,
122
+ practices: weekPractices,
123
+ },
124
+ }
136
125
  }
137
126
 
138
127
  /**
@@ -252,7 +241,9 @@ export async function getUserMonthlyStats(params = {}) {
252
241
  return acc
253
242
  }, {})
254
243
 
255
- const { currentDailyStreak, currentWeeklyStreak } = calculateStreaks(filteredPractices)
244
+ const streakData = await streakCalculator.getStreakData()
245
+ const currentDailyStreak = streakData.currentDailyStreak
246
+ const currentWeeklyStreak = streakData.currentWeeklyStreak
256
247
 
257
248
  return {
258
249
  data: {
@@ -285,20 +276,28 @@ export async function getUserMonthlyStats(params = {}) {
285
276
  *
286
277
  */
287
278
  export async function recordUserPractice(practiceDetails) {
288
- const day = new Date().toLocaleDateString('sv-SE'); // YYYY-MM-DD wall clock date in user's timezone
279
+ const day = new Date().toLocaleDateString('sv-SE') // YYYY-MM-DD wall clock date in user's timezone
289
280
  const durationSeconds = practiceDetails.duration_seconds
290
281
 
291
- return await db.practices.recordManualPractice(day, durationSeconds, {
282
+ const result = await db.practices.recordManualPractice(day, durationSeconds, {
292
283
  title: practiceDetails.title ?? null,
293
284
  category_id: practiceDetails.category_id ?? null,
294
285
  thumbnail_url: practiceDetails.thumbnail_url ?? null,
295
286
  instrument_id: practiceDetails.instrument_id ?? null,
296
287
  })
288
+
289
+ streakCalculator.invalidate()
290
+ return result
297
291
  }
298
292
 
299
293
  export async function trackUserPractice(contentId, incSeconds) {
300
- const day = new Date().toLocaleDateString('sv-SE'); // YYYY-MM-DD wall clock date in user's timezone
301
- return await db.practices.trackAutoPractice(contentId, day, incSeconds, { skipPush: true }); // NOTE - SKIPS PUSH
294
+ const day = new Date().toLocaleDateString('sv-SE') // YYYY-MM-DD wall clock date in user's timezone
295
+ const result = await db.practices.trackAutoPractice(contentId, day, incSeconds, {
296
+ skipPush: true,
297
+ }) // NOTE - SKIPS PUSH
298
+
299
+ streakCalculator.invalidate()
300
+ return result
302
301
  }
303
302
 
304
303
  /**
@@ -321,7 +320,9 @@ export async function trackUserPractice(contentId, incSeconds) {
321
320
  *
322
321
  */
323
322
  export async function updateUserPractice(id, practiceDetails) {
324
- return await db.practices.updateDetails(id, practiceDetails)
323
+ const result = await db.practices.updateDetails(id, practiceDetails)
324
+ streakCalculator.invalidate()
325
+ return result
325
326
  }
326
327
 
327
328
  /**
@@ -337,11 +338,13 @@ export async function updateUserPractice(id, practiceDetails) {
337
338
  * .catch(error => console.error(error));
338
339
  */
339
340
  export async function removeUserPractice(id) {
340
- return await db.practices.deleteOne(id)
341
+ const result = await db.practices.deleteOne(id)
342
+ streakCalculator.invalidate()
343
+ return result
341
344
  }
342
345
 
343
346
  /**
344
- * Restores a previously deleted user's practice session by ID, updating both the local and remote activity context.
347
+ * Restores a previously deleted user's practice session by ID
345
348
  *
346
349
  * @param {number} id - The unique identifier of the practice session to be restored.
347
350
  * @returns {Promise<Object>} - A promise that resolves to the response containing the restored practice session data.
@@ -353,35 +356,7 @@ export async function removeUserPractice(id) {
353
356
  * .catch(error => console.error(error));
354
357
  */
355
358
  export async function restoreUserPractice(id) {
356
- const url = `/api/user/practices/v1/practices/restore${buildQueryString([id])}`
357
- const response = await PUT(url, null)
358
- if (response?.data?.length) {
359
- const restoredPractice = response.data.find((p) => p.id === id)
360
- if (restoredPractice) {
361
- await userActivityContext.updateLocal(async function (localContext) {
362
- if (!localContext.data[DATA_KEY_PRACTICES][restoredPractice.day]) {
363
- localContext.data[DATA_KEY_PRACTICES][restoredPractice.day] = []
364
- }
365
- response.data.forEach((restoredPractice) => {
366
- localContext.data[DATA_KEY_PRACTICES][restoredPractice.day].push({
367
- id: restoredPractice.id,
368
- duration_seconds: restoredPractice.duration_seconds,
369
- })
370
- })
371
- })
372
- }
373
- }
374
- const formattedMeta = await formatPracticeMeta(response.data || [])
375
- const practiceDuration = formattedMeta.reduce(
376
- (total, practice) => total + (practice.duration || 0),
377
- 0
378
- )
379
- return {
380
- data: formattedMeta,
381
- message: response.message,
382
- version: response.version,
383
- practiceDuration,
384
- }
359
+ return await db.practices.restoreOne(id)
385
360
  }
386
361
 
387
362
  /**
@@ -402,7 +377,9 @@ export async function restoreUserPractice(id) {
402
377
  */
403
378
  export async function deletePracticeSession(day) {
404
379
  const ids = await db.practices.queryAllIds(Q.where('date', day))
405
- return await db.practices.deleteSome(ids.data)
380
+ const result = await db.practices.deleteSome(ids.data)
381
+ streakCalculator.invalidate()
382
+ return result
406
383
  }
407
384
 
408
385
  /**
@@ -422,30 +399,15 @@ export async function deletePracticeSession(day) {
422
399
  * .catch(error => console.error("Restore failed:", error));
423
400
  */
424
401
  export async function restorePracticeSession(date) {
425
- const url = `/api/user/practices/v1/practices/restore?date=${date}`
426
- const response = await PUT(url, null)
427
-
428
- if (response?.data) {
429
- await userActivityContext.updateLocal(async function (localContext) {
430
- if (!localContext.data[DATA_KEY_PRACTICES][date]) {
431
- localContext.data[DATA_KEY_PRACTICES][date] = []
432
- }
433
-
434
- response.data.forEach((restoredPractice) => {
435
- localContext.data[DATA_KEY_PRACTICES][date].push({
436
- id: restoredPractice.id,
437
- duration_seconds: restoredPractice.duration_seconds,
438
- })
439
- })
440
- })
441
- }
402
+ const ids = await db.practices.queryAllDeletedIds(Q.where('date', date))
403
+ const response = await db.practices.restoreSome(ids.data)
442
404
 
443
- const formattedMeta = await formatPracticeMeta(response?.data)
405
+ const formattedMeta = await formatPracticeMeta(response.data)
444
406
  const practiceDuration = formattedMeta.reduce(
445
407
  (total, practice) => total + (practice.duration || 0),
446
408
  0
447
409
  )
448
-
410
+ streakCalculator.invalidate()
449
411
  return { data: formattedMeta, practiceDuration }
450
412
  }
451
413
 
@@ -477,10 +439,7 @@ export async function getPracticeSessions(params = {}) {
477
439
  let data
478
440
 
479
441
  if (userId === globalConfig.sessionConfig.userId) {
480
- const query = await db.practices.queryAll(
481
- Q.where('date', day),
482
- Q.sortBy('created_at', 'asc')
483
- )
442
+ const query = await db.practices.queryAll(Q.where('date', day), Q.sortBy('created_at', 'asc'))
484
443
  data = query.data
485
444
  } else {
486
445
  const query = await fetchUserPracticeMeta(day, userId)
@@ -573,7 +532,7 @@ export async function getRecentActivity({ page = 1, limit = 5, tabName = null }
573
532
  * .catch(error => console.error(error));
574
533
  */
575
534
  export async function createPracticeNotes(payload) {
576
- return await db.practiceDayNotes.upsertOne(payload.date, r => {
535
+ return await db.practiceDayNotes.upsertOne(payload.date, (r) => {
577
536
  r.date = payload.date
578
537
  r.notes = payload.notes
579
538
  })
@@ -593,12 +552,12 @@ export async function createPracticeNotes(payload) {
593
552
  * .catch(error => console.error(error));
594
553
  */
595
554
  export async function updatePracticeNotes(payload) {
596
- return await db.practiceDayNotes.updateOneId(payload.date, r => {
555
+ return await db.practiceDayNotes.updateOneId(payload.date, (r) => {
597
556
  r.notes = payload.notes
598
557
  })
599
558
  }
600
559
 
601
- function getStreaksAndMessage(practices) {
560
+ export function getStreaksAndMessage(practices) {
602
561
  let { currentDailyStreak, currentWeeklyStreak, streakMessage } = calculateStreaks(practices, true)
603
562
 
604
563
  return {
@@ -650,20 +609,32 @@ function calculateStreaks(practices, includeStreakMessage = false) {
650
609
  })
651
610
  currentDailyStreak = dailyStreak
652
611
 
653
- // Weekly streak calculation
654
- let weekNumbers = new Set(sortedPracticeDays.map((date) => getWeekNumber(date)))
612
+ // Weekly streak calculation - using Monday dates to handle year boundaries
613
+ let weekStartMap = new Map()
614
+ sortedPracticeDays.forEach((date) => {
615
+ const mondayDate = getMonday(date).format('YYYY-MM-DD')
616
+ if (!weekStartMap.has(mondayDate)) {
617
+ weekStartMap.set(mondayDate, getMonday(date).valueOf()) // Store as timestamp
618
+ }
619
+ })
620
+ let sortedWeekTimestamps = [...weekStartMap.values()].sort((a, b) => b - a) // Descending
655
621
  let weeklyStreak = 0
656
- let lastWeek = null
657
- ;[...weekNumbers]
658
- .sort((a, b) => b - a)
659
- .forEach((week) => {
660
- if (lastWeek === null || week === lastWeek - 1) {
622
+ let prevWeekTimestamp = null
623
+ for (const currentWeekTimestamp of sortedWeekTimestamps) {
624
+ if (prevWeekTimestamp === null) {
625
+ weeklyStreak = 1
626
+ } else {
627
+ const daysDiff = (prevWeekTimestamp - currentWeekTimestamp) / (1000 * 60 * 60 * 24)
628
+ const weeksDiff = Math.round(daysDiff / 7)
629
+
630
+ if (weeksDiff === 1) {
661
631
  weeklyStreak++
662
632
  } else {
663
- return
633
+ break // Properly break on non-consecutive week
664
634
  }
665
- lastWeek = week
666
- })
635
+ }
636
+ prevWeekTimestamp = currentWeekTimestamp
637
+ }
667
638
  currentWeeklyStreak = weeklyStreak
668
639
 
669
640
  // Calculate streak message only if includeStreakMessage is true
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/test/log.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,16 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Read(//app/musora-platform-backend/**)",
5
- "Read(//app/musora-platform-frontend/**)",
6
- "Bash(find:*)",
7
- "Bash(sed:*)",
8
- "Read(//app/**)",
9
- "Bash(cat:*)",
10
- "Bash(docker exec:*)",
11
- "Bash(npm config:*)"
12
- ],
13
- "deny": [],
14
- "ask": []
15
- }
16
- }
package/.yarnrc.yml DELETED
@@ -1 +0,0 @@
1
- nodeLinker: node-modules
package/check_content.js DELETED
@@ -1,30 +0,0 @@
1
- const { initializeService } = require('./src/services/config.js');
2
- const { fetchByRailContentIds } = require('./src/services/sanity.js');
3
- require('dotenv/config');
4
-
5
- async function checkContent() {
6
- initializeService({
7
- sanityConfig: {
8
- token: process.env.SANITY_TOKEN,
9
- projectId: process.env.SANITY_PROJECT_ID,
10
- dataset: process.env.SANITY_DATASET,
11
- version: process.env.SANITY_VERSION || '2021-06-07',
12
- },
13
- railcontentConfig: {
14
- token: process.env.RAILCONTENT_TOKEN,
15
- userId: process.env.RAILCONTENT_USER_ID,
16
- baseUrl: process.env.RAILCONTENT_BASE_URL,
17
- authToken: process.env.RAILCONTENT_AUTH_TOKEN,
18
- },
19
- baseUrl: process.env.RAILCONTENT_BASE_URL,
20
- localStorage: null,
21
- isMA: false,
22
- });
23
-
24
- console.log('Checking railcontent_id: 421814');
25
- const contents = await fetchByRailContentIds([421814]);
26
- console.log('Results:', JSON.stringify(contents, null, 2));
27
- console.log('Found:', contents.length, 'items');
28
- }
29
-
30
- checkContent().catch(console.error);
package/check_content.mjs DELETED
@@ -1,32 +0,0 @@
1
- import { initializeService } from './src/services/config.js';
2
- import { fetchByRailContentIds } from './src/services/sanity.js';
3
- import dotenv from 'dotenv';
4
-
5
- dotenv.config();
6
-
7
- async function checkContent() {
8
- initializeService({
9
- sanityConfig: {
10
- token: process.env.SANITY_TOKEN,
11
- projectId: process.env.SANITY_PROJECT_ID,
12
- dataset: process.env.SANITY_DATASET,
13
- version: process.env.SANITY_VERSION || '2021-06-07',
14
- },
15
- railcontentConfig: {
16
- token: process.env.RAILCONTENT_TOKEN,
17
- userId: process.env.RAILCONTENT_USER_ID,
18
- baseUrl: process.env.RAILCONTENT_BASE_URL,
19
- authToken: process.env.RAILCONTENT_AUTH_TOKEN,
20
- },
21
- baseUrl: process.env.RAILCONTENT_BASE_URL,
22
- localStorage: null,
23
- isMA: false,
24
- });
25
-
26
- console.log('Checking railcontent_id: 421814');
27
- const contents = await fetchByRailContentIds([421814]);
28
- console.log('Results:', JSON.stringify(contents, null, 2));
29
- console.log('Found:', contents.length, 'items');
30
- }
31
-
32
- checkContent().catch(console.error);