musora-content-services 2.117.1 → 2.117.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.
@@ -0,0 +1,9 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(rg:*)",
5
+ "Bash(npm run lint:*)"
6
+ ],
7
+ "deny": []
8
+ }
9
+ }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [2.117.3](https://github.com/railroadmedia/musora-content-services/compare/v2.117.2...v2.117.3) (2026-01-13)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * resolve foryou issue on mpf ([#703](https://github.com/railroadmedia/musora-content-services/issues/703)) ([3e8f0dd](https://github.com/railroadmedia/musora-content-services/commit/3e8f0ddc60eb51a5c510df6433e1cca1c5f5496a))
11
+
12
+ ### [2.117.2](https://github.com/railroadmedia/musora-content-services/compare/v2.117.1...v2.117.2) (2026-01-13)
13
+
5
14
  ### [2.117.1](https://github.com/railroadmedia/musora-content-services/compare/v2.117.0...v2.117.1) (2026-01-13)
6
15
 
7
16
  ## [2.117.0](https://github.com/railroadmedia/musora-content-services/compare/v2.116.0...v2.117.0) (2026-01-13)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "2.117.1",
3
+ "version": "2.117.3",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,8 +1,6 @@
1
1
  // Metadata is taken from the 'common' element and then merged with the <brand> metadata.
2
2
  // Brand values are prioritized and will override the same property in the 'common' element.
3
3
 
4
- import { ALWAYS_VISIBLE_TABS } from './services/sanity.js'
5
-
6
4
  const PROGRESS_NAMES = ['All', 'In Progress', 'Completed', 'Not Started']
7
5
  const DIFFICULTY_STRINGS = ['Introductory', 'Beginner', 'Intermediate', 'Advanced', 'Expert']
8
6
 
@@ -128,6 +126,13 @@ export class Tabs {
128
126
  static Entertainment = { name: 'Entertainment', short_name: 'Entertainment', value: 'type,Entertainment' }
129
127
  }
130
128
 
129
+ /**
130
+ * Song/Lesson tabs that are always visible.
131
+ *
132
+ * @type {object[]}
133
+ */
134
+ export const ALWAYS_VISIBLE_TABS = [Tabs.ForYou, Tabs.ExploreAll]
135
+
131
136
  export const TabResponseType = {
132
137
  SECTIONS: 'sections',
133
138
  CATALOG: 'catalog',
package/src/index.d.ts CHANGED
@@ -109,6 +109,7 @@ import {
109
109
  contentStatusCompletedMany,
110
110
  contentStatusReset,
111
111
  contentStatusStarted,
112
+ flushWatchSession,
112
113
  getAllCompleted,
113
114
  getAllCompletedByIds,
114
115
  getAllStarted,
@@ -568,6 +569,7 @@ declare module 'musora-content-services' {
568
569
  fetchUserPracticeNotes,
569
570
  fetchUserPractices,
570
571
  findIncompleteLesson,
572
+ flushWatchSession,
571
573
  followThread,
572
574
  generateAuthSessionUrl,
573
575
  generateCommentUrl,
package/src/index.js CHANGED
@@ -113,6 +113,7 @@ import {
113
113
  contentStatusCompletedMany,
114
114
  contentStatusReset,
115
115
  contentStatusStarted,
116
+ flushWatchSession,
116
117
  getAllCompleted,
117
118
  getAllCompletedByIds,
118
119
  getAllStarted,
@@ -567,6 +568,7 @@ export {
567
568
  fetchUserPracticeNotes,
568
569
  fetchUserPractices,
569
570
  findIncompleteLesson,
571
+ flushWatchSession,
570
572
  followThread,
571
573
  generateAuthSessionUrl,
572
574
  generateCommentUrl,
@@ -10,6 +10,7 @@ import { fetchBrandsByContentIds } from './sanity.js'
10
10
  const STATE_STARTED = STATE.STARTED
11
11
  const STATE_COMPLETED = STATE.COMPLETED
12
12
  const MAX_DEPTH = 3
13
+ const PUSH_INTERVAL = 30_000
13
14
 
14
15
  export async function getProgressState(contentId, collection = null) {
15
16
  return getById(normalizeContentId(contentId), normalizeCollection(collection), 'state', '')
@@ -414,16 +415,42 @@ export async function recordWatchSession(
414
415
  contentId = normalizeContentId(contentId)
415
416
  collection = normalizeCollection(collection)
416
417
 
418
+ if (!prevSession) {
419
+ prevSession = {
420
+ practiceSession: new Map(),
421
+ pushInterval: null
422
+ }
423
+ }
424
+
425
+ // Track practice and progress locally (no immediate push)
417
426
  const [session] = await Promise.all([
418
- trackPractice(contentId, secondsPlayed, prevSession, { instrumentId, categoryId }),
427
+ trackPractice(contentId, secondsPlayed, prevSession.practiceSession, { instrumentId, categoryId }),
419
428
  trackProgress(contentId, collection, currentSeconds, mediaLengthSeconds),
420
429
  ])
421
430
 
422
- return session
431
+ if (!prevSession.pushInterval) {
432
+ prevSession.pushInterval = setInterval(() => {
433
+ flushWatchSession()
434
+ }, PUSH_INTERVAL)
435
+ }
436
+
437
+ prevSession.practiceSession = session
438
+
439
+ return prevSession
440
+ }
441
+
442
+ export async function flushWatchSession(sessionToFlush = null, shouldClearInterval = true) {
443
+ if (shouldClearInterval && sessionToFlush?.pushInterval) {
444
+ clearInterval(sessionToFlush.pushInterval)
445
+ sessionToFlush.pushInterval = null
446
+ }
447
+
448
+ db.contentProgress.requestPushUnsynced()
449
+ db.practices.requestPushUnsynced()
423
450
  }
424
451
 
425
- async function trackPractice(contentId, secondsPlayed, prevSession, details = {}) {
426
- const session = prevSession || new Map()
452
+ async function trackPractice(contentId, secondsPlayed, practiceSession, details = {}) {
453
+ const session = practiceSession || new Map()
427
454
 
428
455
  const secondsSinceLastUpdate = Math.ceil(secondsPlayed - (session.get(contentId) ?? 0))
429
456
  session.set(contentId, secondsPlayed)
@@ -437,7 +464,7 @@ async function trackProgress(contentId, collection, currentSeconds, mediaLengthS
437
464
  99,
438
465
  Math.round(((currentSeconds ?? 0) / Math.max(1, mediaLengthSeconds)) * 100)
439
466
  ))
440
- return saveContentProgress(contentId, collection, progress, currentSeconds)
467
+ return saveContentProgress(contentId, collection, progress, currentSeconds, { skipPush: true })
441
468
  }
442
469
 
443
470
  export async function contentStatusCompleted(contentId, collection = null) {
@@ -33,7 +33,7 @@ import {
33
33
  SONG_TYPES_WITH_CHILDREN,
34
34
  } from '../contentTypeConfig.js'
35
35
  import { fetchSimilarItems, recommendations } from './recommendations.js'
36
- import { getSongType, processMetadata, Tabs } from '../contentMetaData.js'
36
+ import { getSongType, processMetadata, ALWAYS_VISIBLE_TABS } from '../contentMetaData.js'
37
37
  import { GET } from '../infrastructure/http/HttpClient.ts'
38
38
 
39
39
  import { globalConfig } from './config.js'
@@ -50,13 +50,6 @@ import { fetchRecentActivitiesActiveTabs } from './userActivity.js'
50
50
  */
51
51
  const excludeFromGeneratedIndex = ['fetchRelatedByLicense']
52
52
 
53
- /**
54
- * Song/Lesson tabs that are always visible.
55
- *
56
- * @type {object[]}
57
- */
58
- export const ALWAYS_VISIBLE_TABS = [Tabs.ForYou, Tabs.ExploreAll]
59
-
60
53
  /**
61
54
  * Mapping from tab names to their underlying Sanity content types.
62
55
  * Used to determine if a tab has any content available.
@@ -238,7 +238,7 @@ export default class SyncRepository<TModel extends BaseModel> {
238
238
  return result
239
239
  }
240
240
 
241
- protected async _requestPushUnsynced() {
242
- await this.store.pushUnsyncedWithRetry()
241
+ requestPushUnsynced() {
242
+ this.store.pushUnsyncedWithRetry()
243
243
  }
244
244
  }
@@ -220,10 +220,6 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
220
220
  return this.deleteOne(ProgressRepository.generateId(contentId, collection), { skipPush })
221
221
  }
222
222
 
223
- async requestPushUnsynced() {
224
- await this._requestPushUnsynced()
225
- }
226
-
227
223
  private static generateId(
228
224
  contentId: number,
229
225
  collection: CollectionParameter | null
@@ -18,7 +18,7 @@ export default class PracticesRepository extends SyncRepository<Practice> {
18
18
  return Math.round(totalSeconds / 60)
19
19
  }
20
20
 
21
- async trackAutoPractice(contentId: number, date: string, incrementalDurationSeconds: number) {
21
+ async trackAutoPractice(contentId: number, date: string, incrementalDurationSeconds: number, skipPush = true) {
22
22
  return await this.upsertOne(PracticesRepository.generateAutoId(contentId, date), r => {
23
23
  r._raw.id = PracticesRepository.generateAutoId(contentId, date);
24
24
  r.auto = true;
@@ -26,7 +26,7 @@ export default class PracticesRepository extends SyncRepository<Practice> {
26
26
  r.date = date;
27
27
 
28
28
  r.duration_seconds = Math.min((r.duration_seconds || 0) + incrementalDurationSeconds, 59999);
29
- })
29
+ }, { skipPush })
30
30
  }
31
31
 
32
32
  async recordManualPractice(date: string, durationSeconds: number, details: Partial<Pick<Practice, 'title' | 'instrument_id' | 'category_id' | 'thumbnail_url'>> = {}) {
@@ -64,4 +64,12 @@ export default class PracticesRepository extends SyncRepository<Practice> {
64
64
  private static generateManualId(manualId: string) {
65
65
  return `manual:${manualId}`;
66
66
  }
67
+
68
+ async getAutoPracticesOnDate(date: string) {
69
+ return await this.queryAll(
70
+ Q.where('date', date),
71
+ Q.where('auto', true),
72
+ Q.sortBy('created_at', 'asc')
73
+ )
74
+ }
67
75
  }
@@ -298,7 +298,7 @@ export async function recordUserPractice(practiceDetails) {
298
298
 
299
299
  export async function trackUserPractice(contentId, incSeconds) {
300
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);
301
+ return await db.practices.trackAutoPractice(contentId, day, incSeconds, { skipPush: true }); // NOTE - SKIPS PUSH
302
302
  }
303
303
 
304
304
  /**