musora-content-services 2.186.0 → 2.187.0
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 +16 -0
- package/package.json +1 -1
- package/src/lib/sanity/types/marketing.d.ts +1 -0
- package/src/services/awards/internal/award-definitions.js +4 -0
- package/src/services/contentProgress.js +19 -2
- package/src/services/marketing/promotions.ts +2 -2
- package/src/services/my-path/placement-quiz.ts +1 -1
- package/src/services/progress/mutations.ts +28 -2
- package/src/services/sync/repositories/content-progress.ts +26 -5
- package/src/version-info.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
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.187.0](https://github.com/railroadmedia/musora-content-services/compare/v2.186.1...v2.187.0) (2026-10-19)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **MU2-2024:** add disclaimer to the promotional campaign placement type ([9905117](https://github.com/railroadmedia/musora-content-services/commit/990511777622eca93758b5252c908900e484b6f5))
|
|
11
|
+
|
|
12
|
+
### [2.186.1](https://github.com/railroadmedia/musora-content-services/compare/v2.186.0...v2.186.1) (2026-10-19)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* guard against null awards list when Sanity fetch fails ([#1053](https://github.com/railroadmedia/musora-content-services/issues/1053)) ([77f0bf7](https://github.com/railroadmedia/musora-content-services/commit/77f0bf711c2fcc5a5e47cb1a8d0328f670f5107d))
|
|
18
|
+
* preserve resume time when duplicating playlist progress ([#1045](https://github.com/railroadmedia/musora-content-services/issues/1045)) ([d62d394](https://github.com/railroadmedia/musora-content-services/commit/d62d394542b170741c818182405ca53efde046d1)), closes [#1079](https://github.com/railroadmedia/musora-content-services/issues/1079)
|
|
19
|
+
* replace @/ alias imports with relative paths in src ([bc22248](https://github.com/railroadmedia/musora-content-services/commit/bc22248264a029904c7f8bae403c257268da47c6))
|
|
20
|
+
|
|
5
21
|
## [2.186.0](https://github.com/railroadmedia/musora-content-services/compare/v2.185.1...v2.186.0) (2026-10-16)
|
|
6
22
|
|
|
7
23
|
|
package/package.json
CHANGED
|
@@ -115,6 +115,7 @@ export type PromotionalCampaignDocument = {
|
|
|
115
115
|
key: string
|
|
116
116
|
title?: string
|
|
117
117
|
body?: string
|
|
118
|
+
disclaimer?: string
|
|
118
119
|
/** Resolved to a CDN url by the promotions query projection. */
|
|
119
120
|
dark_mode_image?: string
|
|
120
121
|
/** Resolved to a CDN url by the promotions query projection. */
|
|
@@ -596,7 +596,25 @@ export async function contentStatusReset(contentId, collection = null, { skipPus
|
|
|
596
596
|
}
|
|
597
597
|
|
|
598
598
|
// does not have an offline variant because it's too deeply nested within the watch session flow.
|
|
599
|
-
export async function saveContentProgress(
|
|
599
|
+
export async function saveContentProgress(contentId, collection, progress, currentSeconds, options = {}) {
|
|
600
|
+
collection = collection ?? { id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF }
|
|
601
|
+
const response = await saveProgressPercent(contentId, collection, progress, currentSeconds, options)
|
|
602
|
+
await saveResumeTime(contentId, collection, currentSeconds)
|
|
603
|
+
return response
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// resume time is last-write-wins, so it bypasses the percent gate that drops regressed progress
|
|
607
|
+
async function saveResumeTime(contentId, collection, currentSeconds) {
|
|
608
|
+
if (currentSeconds === undefined || currentSeconds === null) return
|
|
609
|
+
|
|
610
|
+
const normalizedContentId = normalizeContentId(contentId)
|
|
611
|
+
await db.contentProgress.updateResumeTime(normalizedContentId, normalizeCollection(collection), currentSeconds, { skipPush: true })
|
|
612
|
+
if (collection.type === COLLECTION_TYPE.PLAYLIST) {
|
|
613
|
+
await db.contentProgress.updateResumeTime(normalizedContentId, null, currentSeconds, { skipPush: true })
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async function saveProgressPercent(
|
|
600
618
|
contentId,
|
|
601
619
|
collection,
|
|
602
620
|
progress,
|
|
@@ -608,7 +626,6 @@ export async function saveContentProgress(
|
|
|
608
626
|
accessedDirectly = true,
|
|
609
627
|
} = {},
|
|
610
628
|
) {
|
|
611
|
-
collection = collection ?? { id: COLLECTION_ID_SELF, type: COLLECTION_TYPE.SELF }
|
|
612
629
|
const isPlaylist = collection?.type === COLLECTION_TYPE.PLAYLIST
|
|
613
630
|
|
|
614
631
|
let allProgresses = {}
|
|
@@ -5,8 +5,8 @@ import { Filters as f } from '../../lib/sanity/filter'
|
|
|
5
5
|
import { groq } from '../../lib/sanity/groq'
|
|
6
6
|
import type { GroqBuilder } from '../../lib/sanity/groq'
|
|
7
7
|
import type { PromotionalCampaignDocument } from '../../lib/sanity/types/marketing'
|
|
8
|
-
import { SanityQueryError } from '
|
|
9
|
-
import { AsyncEither } from '
|
|
8
|
+
import { SanityQueryError } from '../../lib/sanity/runner'
|
|
9
|
+
import { AsyncEither } from '../../lib/ads/async-either'
|
|
10
10
|
|
|
11
11
|
const excludeFromGeneratedIndex = [
|
|
12
12
|
'campaignQuery',
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { HttpClient } from '../../infrastructure/http/HttpClient'
|
|
5
5
|
import { globalConfig } from '../config.js'
|
|
6
|
-
import { OnboardingRecommendedContent } from '
|
|
6
|
+
import { OnboardingRecommendedContent } from '../user/onboarding'
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
const baseUrl = `/api/my-path/v1/placement-quiz`
|
|
@@ -67,14 +67,40 @@ export const save = async (
|
|
|
67
67
|
progress: number,
|
|
68
68
|
collection?: CollectionParameter,
|
|
69
69
|
currentSeconds?: number,
|
|
70
|
+
options: SaveOptions = {}
|
|
71
|
+
) => {
|
|
72
|
+
const activeCollection = withDefaultCollection(collection)
|
|
73
|
+
const response = await saveProgressPercent(contentId, progress, activeCollection, currentSeconds, options)
|
|
74
|
+
await saveResumeTime(contentId, activeCollection, currentSeconds)
|
|
75
|
+
return response
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// resume time is last-write-wins, so it bypasses the percent gate that drops regressed progress
|
|
79
|
+
const saveResumeTime = async (
|
|
80
|
+
contentId: number,
|
|
81
|
+
collection: CollectionParameter,
|
|
82
|
+
currentSeconds: number | undefined
|
|
83
|
+
): Promise<void> => {
|
|
84
|
+
if (currentSeconds === undefined || currentSeconds === null) return
|
|
85
|
+
|
|
86
|
+
await db.contentProgress.updateResumeTime(contentId, collection, currentSeconds, { skipPush: true })
|
|
87
|
+
if (isPlaylistCollection(collection)) {
|
|
88
|
+
await db.contentProgress.updateResumeTime(contentId, null, currentSeconds, { skipPush: true })
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const saveProgressPercent = async (
|
|
93
|
+
contentId: number,
|
|
94
|
+
progress: number,
|
|
95
|
+
activeCollection: CollectionParameter,
|
|
96
|
+
currentSeconds: number | undefined,
|
|
70
97
|
{
|
|
71
98
|
isOffline = false,
|
|
72
99
|
hierarchy = null,
|
|
73
100
|
skipPush = false,
|
|
74
101
|
accessedDirectly = true,
|
|
75
|
-
}: SaveOptions
|
|
102
|
+
}: SaveOptions
|
|
76
103
|
) => {
|
|
77
|
-
const activeCollection = withDefaultCollection(collection)
|
|
78
104
|
const isPlaylist = isPlaylistCollection(activeCollection)
|
|
79
105
|
|
|
80
106
|
let progresses: Record<number, number> = { [contentId]: progress }
|
|
@@ -12,6 +12,8 @@ interface MetadataParameter {
|
|
|
12
12
|
parent_id: number
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
const MIN_INITIAL_RESUME_TIME_SECONDS = 10
|
|
16
|
+
|
|
15
17
|
export default class ProgressRepository extends SyncRepository<ContentProgress> {
|
|
16
18
|
|
|
17
19
|
static collectionTypeFilter(
|
|
@@ -185,11 +187,7 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
185
187
|
r.content_type = metadata.type
|
|
186
188
|
r.content_parent_id = metadata.parent_id
|
|
187
189
|
|
|
188
|
-
|
|
189
|
-
if (resumeTime >= 10 || r.resume_time_seconds !== null) {
|
|
190
|
-
r.resume_time_seconds = Math.floor(resumeTime)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
190
|
+
ProgressRepository.applyResumeTime(r, resumeTime)
|
|
193
191
|
|
|
194
192
|
if (accessedDirectly && r.collection_type === COLLECTION_TYPE.SELF) {
|
|
195
193
|
r.last_interacted_a_la_carte = r.updated_at
|
|
@@ -263,6 +261,29 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
|
|
|
263
261
|
//todo add event emitting for bulk updates?
|
|
264
262
|
}
|
|
265
263
|
|
|
264
|
+
async updateResumeTime(contentId: number, collection: CollectionParameter | null, resumeTime: number, { skipPush = false } = {}) {
|
|
265
|
+
const id = ContentProgress.generateId(contentId, collection)
|
|
266
|
+
const existing = await this.readOne(id)
|
|
267
|
+
if (!existing.data) return
|
|
268
|
+
|
|
269
|
+
const currentResumeTime = existing.data.resume_time_seconds
|
|
270
|
+
const isUnchanged = currentResumeTime === Math.floor(resumeTime)
|
|
271
|
+
if (isUnchanged || !ProgressRepository.shouldWriteResumeTime(currentResumeTime, resumeTime)) return
|
|
272
|
+
|
|
273
|
+
return this.upsertOne(id, (r) => ProgressRepository.applyResumeTime(r, resumeTime), { skipPush })
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private static shouldWriteResumeTime(currentResumeTime: number | null, resumeTime: number) {
|
|
277
|
+
return resumeTime >= MIN_INITIAL_RESUME_TIME_SECONDS || currentResumeTime !== null
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private static applyResumeTime(r: ContentProgress, resumeTime: number | undefined) {
|
|
281
|
+
if (typeof resumeTime == 'undefined') return
|
|
282
|
+
if (ProgressRepository.shouldWriteResumeTime(r.resume_time_seconds, resumeTime)) {
|
|
283
|
+
r.resume_time_seconds = Math.floor(resumeTime)
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
266
287
|
eraseProgress(contentId: number, collection: CollectionParameter | null, { skipPush = false } = {}) {
|
|
267
288
|
return this.deleteOne(ContentProgress.generateId(contentId, collection), { skipPush })
|
|
268
289
|
}
|
package/src/version-info.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const MCS_VERSION = '2.
|
|
1
|
+
export const MCS_VERSION = '2.187.0'
|