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.
- package/.coderabbit.yaml +0 -0
- package/.editorconfig +0 -0
- package/.github/pull_request_template.md +0 -0
- package/.github/workflows/conventional-commits.yaml +0 -0
- package/.github/workflows/docs.js.yml +0 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/CHANGELOG.md +14 -0
- package/CLAUDE.md +0 -0
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/jsdoc.json +0 -0
- package/package.json +1 -1
- package/src/constants/award-assets.js +0 -0
- package/src/constants/membership-permissions.ts +0 -0
- package/src/filterBuilder.js +0 -0
- package/src/infrastructure/http/HttpClient.ts +0 -0
- package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
- package/src/infrastructure/http/index.ts +0 -0
- package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
- package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
- package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
- package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
- package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
- package/src/lib/ads/monoid.ts +0 -0
- package/src/lib/ads/semigroup.ts +0 -0
- package/src/lib/brands.ts +0 -0
- package/src/lib/lastUpdated.js +0 -0
- package/src/lib/sanity/filter.ts +0 -0
- package/src/lib/sanity/query.ts +0 -0
- package/src/services/api/types.js +0 -0
- package/src/services/api/types.ts +0 -0
- package/src/services/awards/award-callbacks.js +0 -0
- package/src/services/awards/award-query.js +0 -0
- package/src/services/awards/internal/.indexignore +0 -0
- package/src/services/awards/internal/award-definitions.js +0 -0
- package/src/services/awards/internal/award-events.js +0 -0
- package/src/services/awards/internal/award-manager.js +0 -0
- package/src/services/awards/internal/certificate-builder.js +0 -0
- package/src/services/awards/internal/completion-data-generator.js +0 -0
- package/src/services/awards/internal/content-progress-observer.js +0 -0
- package/src/services/awards/internal/image-utils.js +0 -0
- package/src/services/awards/internal/message-generator.js +0 -0
- package/src/services/awards/internal/types.js +0 -0
- package/src/services/awards/types.d.ts +0 -0
- package/src/services/awards/types.js +0 -0
- package/src/services/content/artist.ts +0 -0
- package/src/services/content/content.ts +0 -0
- package/src/services/content/genre.ts +0 -0
- package/src/services/content/instructor.ts +0 -0
- package/src/services/content-org/content-org.js +0 -0
- package/src/services/content-org/guided-courses.ts +0 -0
- package/src/services/content-org/learning-paths.ts +0 -0
- package/src/services/content-org/playlists-types.js +0 -0
- package/src/services/content-org/playlists.js +0 -0
- package/src/services/contentAggregator.js +0 -0
- package/src/services/contentLikes.js +0 -0
- package/src/services/contentProgress.js +0 -0
- package/src/services/dateUtils.js +0 -0
- package/src/services/eventsAPI.js +0 -0
- package/src/services/forums/categories.ts +0 -0
- package/src/services/forums/forums.ts +0 -0
- package/src/services/forums/posts.ts +0 -0
- package/src/services/forums/threads.ts +0 -0
- package/src/services/forums/types.ts +0 -0
- package/src/services/gamification/awards.ts +0 -0
- package/src/services/gamification/gamification.js +0 -0
- package/src/services/imageSRCBuilder.js +0 -0
- package/src/services/imageSRCVerify.js +0 -0
- package/src/services/liveTesting.ts +0 -0
- package/src/services/permissions/PermissionsAdapter.ts +0 -0
- package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
- package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
- package/src/services/permissions/PermissionsV2Adapter.ts +0 -0
- package/src/services/permissions/README.md +0 -0
- package/src/services/permissions/index.ts +0 -0
- package/src/services/progress-events.js +0 -0
- package/src/services/progress-row/rows/.indexignore +0 -0
- package/src/services/progress-row/rows/content-card.js +0 -0
- package/src/services/progress-row/rows/playlist-card.js +0 -0
- package/src/services/railcontent.js +0 -0
- package/src/services/reporting/README.md +0 -0
- package/src/services/reporting/reporting.ts +0 -0
- package/src/services/reporting/types.ts +0 -0
- package/src/services/sentry/.indexignore +0 -0
- package/src/services/sentry/index.ts +0 -0
- package/src/services/sync/.indexignore +0 -0
- package/src/services/sync/adapters/factory.ts +0 -0
- package/src/services/sync/adapters/lokijs.ts +0 -0
- package/src/services/sync/adapters/sqlite.ts +0 -0
- package/src/services/sync/context/index.ts +0 -0
- package/src/services/sync/context/providers/base.ts +0 -0
- package/src/services/sync/context/providers/connectivity.ts +0 -0
- package/src/services/sync/context/providers/durability.ts +0 -0
- package/src/services/sync/context/providers/index.ts +0 -0
- package/src/services/sync/context/providers/session.ts +0 -0
- package/src/services/sync/context/providers/tabs.ts +0 -0
- package/src/services/sync/context/providers/visibility.ts +0 -0
- package/src/services/sync/database/factory.ts +0 -0
- package/src/services/sync/effects/index.ts +0 -0
- package/src/services/sync/effects/logout-warning.ts +0 -0
- package/src/services/sync/errors/boundary.ts +0 -0
- package/src/services/sync/errors/index.ts +0 -0
- package/src/services/sync/errors/validators.ts +0 -0
- package/src/services/sync/fetch.ts +0 -0
- package/src/services/sync/index.ts +0 -0
- package/src/services/sync/manager.ts +4 -0
- package/src/services/sync/models/Base.ts +0 -0
- package/src/services/sync/models/ContentLike.ts +0 -0
- package/src/services/sync/models/ContentProgress.ts +0 -0
- package/src/services/sync/models/Practice.ts +0 -0
- package/src/services/sync/models/PracticeDayNote.ts +0 -0
- package/src/services/sync/models/UserAwardProgress.ts +0 -0
- package/src/services/sync/models/index.ts +0 -0
- package/src/services/sync/repositories/base.ts +18 -0
- package/src/services/sync/repositories/content-likes.ts +0 -0
- package/src/services/sync/repositories/content-progress.ts +0 -0
- package/src/services/sync/repositories/index.ts +0 -0
- package/src/services/sync/repositories/practice-day-notes.ts +0 -0
- package/src/services/sync/repositories/practices.ts +0 -0
- package/src/services/sync/repositories/user-award-progress.ts +0 -0
- package/src/services/sync/repository-proxy.ts +0 -0
- package/src/services/sync/resolver.ts +0 -0
- package/src/services/sync/retry.ts +0 -0
- package/src/services/sync/run-scope.ts +0 -0
- package/src/services/sync/schema/index.ts +0 -0
- package/src/services/sync/serializers/index.ts +0 -0
- package/src/services/sync/serializers/model.ts +0 -0
- package/src/services/sync/serializers/raw.ts +0 -0
- package/src/services/sync/store/index.ts +154 -33
- package/src/services/sync/store/push-coalescer.ts +0 -0
- package/src/services/sync/store-configs.ts +0 -0
- package/src/services/sync/strategies/base.ts +0 -0
- package/src/services/sync/strategies/index.ts +0 -0
- package/src/services/sync/strategies/initial.ts +0 -0
- package/src/services/sync/strategies/polling.ts +0 -0
- package/src/services/sync/telemetry/flood-prevention.ts +0 -0
- package/src/services/sync/telemetry/index.ts +0 -0
- package/src/services/sync/telemetry/sampling.ts +0 -0
- package/src/services/sync/utils/event-emitter.ts +0 -0
- package/src/services/sync/utils/index.ts +0 -0
- package/src/services/sync/utils/throttle.ts +0 -0
- package/src/services/sync/utils/timers.ts +0 -0
- package/src/services/urlBuilder.ts +0 -0
- package/src/services/user/account.ts +0 -0
- package/src/services/user/chat.js +0 -0
- package/src/services/user/interests.js +0 -0
- package/src/services/user/management.js +0 -0
- package/src/services/user/memberships.ts +0 -0
- package/src/services/user/notifications.js +0 -0
- package/src/services/user/onboarding.ts +0 -0
- package/src/services/user/payments.ts +0 -0
- package/src/services/user/permissions.js +0 -0
- package/src/services/user/profile.js +0 -0
- package/src/services/user/streakCalculator.ts +66 -0
- package/src/services/user/types.d.ts +0 -0
- package/src/services/user/types.js +0 -0
- package/src/services/user/user-management-system.js +0 -0
- package/src/services/userActivity.js +74 -103
- package/test/HttpClient.test.js +0 -0
- package/test/awards/award-alacarte-observer.test.js +0 -0
- package/test/awards/award-auto-refresh.test.js +0 -0
- package/test/awards/award-calculations.test.js +0 -0
- package/test/awards/award-certificate-display.test.js +0 -0
- package/test/awards/award-collection-edge-cases.test.js +0 -0
- package/test/awards/award-collection-filtering.test.js +0 -0
- package/test/awards/award-completion-flow.test.js +0 -0
- package/test/awards/award-exclusion-handling.test.js +0 -0
- package/test/awards/award-multi-lesson.test.js +0 -0
- package/test/awards/award-observer-integration.test.js +0 -0
- package/test/awards/award-query-messages.test.js +0 -0
- package/test/awards/award-user-collection.test.js +0 -0
- package/test/awards/duplicate-prevention.test.js +0 -0
- package/test/awards/helpers/completion-mock.js +0 -0
- package/test/awards/helpers/index.js +0 -0
- package/test/awards/helpers/mock-setup.js +0 -0
- package/test/awards/helpers/progress-emitter.js +0 -0
- package/test/awards/message-generator.test.js +0 -0
- package/test/content.test.js +0 -0
- package/test/contentLikes.test.js +0 -0
- package/test/contentProgress.test.js +0 -0
- package/test/dataContext.test.js +0 -0
- package/test/forum.test.js +0 -0
- package/test/imageSRCBuilder.test.js +0 -0
- package/test/imageSRCVerify.test.js +0 -0
- package/test/initializeTests.js +0 -0
- package/test/learningPaths.test.js +0 -0
- package/test/lib/__snapshots__/filter.test.ts.snap +0 -0
- package/test/lib/filter.test.ts +0 -0
- package/test/lib/lastUpdated.test.js +0 -0
- package/test/lib/query.test.ts +0 -0
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/log.js +0 -0
- package/test/mockData/award-definitions.js +0 -0
- package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
- package/test/mockData/mockData_progress_content.json +0 -0
- package/test/mockData/mockData_sanity_progress_content.json +0 -0
- package/test/mockData/mockData_user_practices.json +0 -0
- package/test/notifications.test.js +0 -0
- package/test/progressRows.test.js +0 -0
- package/test/sanityQueryService.test.js +0 -0
- package/test/streakMessage.test.js +0 -0
- package/test/sync/adapter.ts +0 -0
- package/test/sync/initialize-sync-manager.js +0 -0
- package/test/sync/models/award-database-integration.test.js +0 -0
- package/test/user/permissions.test.js +0 -0
- package/test/userActivity.test.js +0 -0
- package/tools/generate-index.cjs +0 -0
- package/.claude/settings.local.json +0 -16
- package/.yarnrc.yml +0 -1
- package/check_content.js +0 -30
- package/check_content.mjs +0 -32
- package/test/logout.test.js +0 -199
- package/test/reporting.test.js +0 -132
- package/test_owned_navigate.js +0 -74
- 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
|
|
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) =>
|
|
99
|
-
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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
|
|
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')
|
|
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
|
-
|
|
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')
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
426
|
-
const response = await
|
|
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
|
|
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
|
|
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
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
-
|
|
633
|
+
break // Properly break on non-consecutive week
|
|
664
634
|
}
|
|
665
|
-
|
|
666
|
-
|
|
635
|
+
}
|
|
636
|
+
prevWeekTimestamp = currentWeekTimestamp
|
|
637
|
+
}
|
|
667
638
|
currentWeeklyStreak = weeklyStreak
|
|
668
639
|
|
|
669
640
|
// Calculate streak message only if includeStreakMessage is true
|
package/test/HttpClient.test.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/content.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/dataContext.test.js
CHANGED
|
File without changes
|
package/test/forum.test.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/initializeTests.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/test/lib/filter.test.ts
CHANGED
|
File without changes
|
|
File without changes
|
package/test/lib/query.test.ts
CHANGED
|
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
|
package/test/sync/adapter.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/tools/generate-index.cjs
CHANGED
|
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);
|