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.
- package/CHANGELOG.md +31 -0
- package/docs/ContentOrganization.html +2 -2
- package/docs/Forums.html +2 -2
- package/docs/Gamification.html +2 -2
- package/docs/TestUser.html +2 -2
- package/docs/UserManagementSystem.html +2 -2
- package/docs/api_types.js.html +2 -2
- package/docs/config.js.html +2 -2
- package/docs/content-org_content-org.js.html +2 -2
- package/docs/content-org_guided-courses.ts.html +2 -2
- package/docs/content-org_learning-paths.ts.html +52 -40
- package/docs/content-org_playlists-types.js.html +2 -2
- package/docs/content-org_playlists.js.html +2 -2
- package/docs/content.js.html +2 -2
- package/docs/content_artist.ts.html +2 -2
- package/docs/content_genre.ts.html +2 -2
- package/docs/content_instructor.ts.html +2 -2
- package/docs/forums_categories.ts.html +2 -2
- package/docs/forums_forums.ts.html +2 -2
- package/docs/forums_posts.ts.html +2 -2
- package/docs/forums_threads.ts.html +2 -2
- package/docs/gamification_awards.ts.html +2 -2
- package/docs/gamification_gamification.js.html +2 -2
- package/docs/global.html +2 -2
- package/docs/index.html +2 -2
- package/docs/liveTesting.ts.html +2 -2
- package/docs/module-Accounts.html +2 -2
- package/docs/module-Artist.html +2 -2
- package/docs/module-Awards.html +2 -2
- package/docs/module-Config.html +2 -2
- package/docs/module-Content-Services-V2.html +2 -2
- package/docs/module-Forums.html +2 -2
- package/docs/module-Genre.html +2 -2
- package/docs/module-GuidedCourses.html +2 -2
- package/docs/module-Instructor.html +2 -2
- package/docs/module-Interests.html +2 -2
- package/docs/module-LearningPaths.html +269 -143
- package/docs/module-Onboarding.html +3 -3
- package/docs/module-Payments.html +2 -2
- package/docs/module-Permissions.html +2 -2
- package/docs/module-Playlists.html +2 -2
- package/docs/module-ProgressRow.html +2 -2
- package/docs/module-Railcontent-Services.html +34 -893
- package/docs/module-Sanity-Services.html +2 -2
- package/docs/module-Sessions.html +2 -2
- package/docs/module-UserActivity.html +70 -116
- package/docs/module-UserChat.html +2 -2
- package/docs/module-UserManagement.html +2 -2
- package/docs/module-UserMemberships.html +2 -2
- package/docs/module-UserNotifications.html +2 -2
- package/docs/module-UserProfile.html +2 -2
- package/docs/progress-row_method-card.js.html +3 -2
- package/docs/railcontent.js.html +14 -137
- package/docs/sanity.js.html +2 -2
- package/docs/userActivity.js.html +85 -150
- package/docs/user_account.ts.html +2 -2
- package/docs/user_chat.js.html +2 -2
- package/docs/user_interests.js.html +2 -2
- package/docs/user_management.js.html +2 -2
- package/docs/user_memberships.ts.html +2 -2
- package/docs/user_notifications.js.html +2 -2
- package/docs/user_onboarding.ts.html +10 -6
- package/docs/user_payments.ts.html +2 -2
- package/docs/user_permissions.js.html +2 -2
- package/docs/user_profile.js.html +2 -2
- package/docs/user_sessions.js.html +2 -2
- package/docs/user_types.js.html +2 -2
- package/docs/user_user-management-system.js.html +2 -2
- package/package.json +11 -3
- package/src/contentTypeConfig.js +6 -0
- package/src/index.d.ts +7 -31
- package/src/index.js +10 -34
- package/src/services/content-org/learning-paths.ts +31 -0
- package/src/services/contentAggregator.js +2 -2
- package/src/services/contentLikes.js +6 -39
- package/src/services/contentProgress.js +181 -479
- package/src/services/dataContext.js +0 -2
- package/src/services/progress-row/method-card.js +1 -0
- package/src/services/railcontent.js +12 -135
- package/src/services/sentry/.indexignore +0 -0
- package/src/services/sentry/index.ts +23 -0
- package/src/services/sync/.indexignore +0 -0
- package/src/services/sync/adapters/factory.ts +26 -0
- package/src/services/sync/adapters/lokijs.ts +1 -0
- package/src/services/sync/adapters/sqlite.ts +1 -0
- package/src/services/sync/concurrency-safety.ts +4 -0
- package/src/services/sync/context/index.ts +43 -0
- package/src/services/sync/context/providers/base.ts +4 -0
- package/src/services/sync/context/providers/connectivity.ts +14 -0
- package/src/services/sync/context/providers/durability.ts +5 -0
- package/src/services/sync/context/providers/index.ts +5 -0
- package/src/services/sync/context/providers/session.ts +8 -0
- package/src/services/sync/context/providers/tabs.ts +18 -0
- package/src/services/sync/context/providers/visibility.ts +14 -0
- package/src/services/sync/database/factory.ts +10 -0
- package/src/services/sync/errors/boundary.ts +45 -0
- package/src/services/sync/errors/index.ts +49 -0
- package/src/services/sync/fetch.ts +310 -0
- package/src/services/sync/index.ts +80 -0
- package/src/services/sync/manager.ts +139 -0
- package/src/services/sync/models/Base.ts +47 -0
- package/src/services/sync/models/ContentLike.ts +16 -0
- package/src/services/sync/models/ContentProgress.ts +69 -0
- package/src/services/sync/models/Practice.ts +72 -0
- package/src/services/sync/models/PracticeDayNote.ts +23 -0
- package/src/services/sync/models/index.ts +4 -0
- package/src/services/sync/repositories/base.ts +247 -0
- package/src/services/sync/repositories/content-likes.ts +26 -0
- package/src/services/sync/repositories/content-progress.ts +160 -0
- package/src/services/sync/repositories/index.ts +4 -0
- package/src/services/sync/repositories/practice-day-notes.ts +4 -0
- package/src/services/sync/repositories/practices.ts +52 -0
- package/src/services/sync/repository-proxy.ts +48 -0
- package/src/services/sync/resolver.ts +84 -0
- package/src/services/sync/retry.ts +88 -0
- package/src/services/sync/run-scope.ts +30 -0
- package/src/services/sync/schema/index.ts +66 -0
- package/src/services/sync/serializers/index.ts +2 -0
- package/src/services/sync/serializers/model.ts +32 -0
- package/src/services/sync/serializers/raw.ts +21 -0
- package/src/services/sync/store/index.ts +779 -0
- package/src/services/sync/store/push-coalescer.ts +57 -0
- package/src/services/sync/store-configs.ts +41 -0
- package/src/services/sync/strategies/base.ts +21 -0
- package/src/services/sync/strategies/index.ts +12 -0
- package/src/services/sync/strategies/initial.ts +11 -0
- package/src/services/sync/strategies/polling.ts +54 -0
- package/src/services/sync/telemetry/index.ts +140 -0
- package/src/services/sync/telemetry/sampling.ts +91 -0
- package/src/services/sync/utils/event-emitter.ts +24 -0
- package/src/services/sync/utils/index.ts +1 -0
- package/src/services/sync/utils/throttle.ts +93 -0
- package/src/services/sync/utils/timers.ts +9 -0
- package/src/services/userActivity.js +83 -148
- package/test/contentProgress.test.js +6 -39
- package/test/live/contentProgressLive.test.js +2 -31
- package/tools/generate-index.cjs +10 -4
- package/.claude/settings.local.json +0 -8
- 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
|
-
|
|
81
|
-
if (userId
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
return data?.[DATA_KEY_PRACTICES] ?? {}
|
|
81
|
+
return await fetchUserPractices(userId)
|
|
87
82
|
}
|
|
88
83
|
}
|
|
89
84
|
|
|
90
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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:
|
|
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
|
|
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
|
|
280
|
-
* recordUserPractice({
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
459
|
-
|
|
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
|
-
|
|
546
|
-
if (!meta.data.length) return { data: { practices: [], practiceDuration: 0 } }
|
|
484
|
+
let data
|
|
547
485
|
|
|
548
|
-
|
|
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(
|
|
571
|
-
|
|
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
|
-
|
|
622
|
-
|
|
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
|
-
|
|
640
|
-
|
|
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
|
|
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
|
})
|
package/tools/generate-index.cjs
CHANGED
|
@@ -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
|
|
84
|
-
addFunctionsToFileExports(
|
|
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`
|
package/babel.config.cjs
DELETED