musora-content-services 1.0.171 → 1.0.172

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 CHANGED
@@ -2,6 +2,8 @@
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
+ ### [1.0.172](https://github.com/railroadmedia/musora-content-services/compare/v1.0.171...v1.0.172) (2024-11-13)
6
+
5
7
  ### [1.0.171](https://github.com/railroadmedia/musora-content-services/compare/v1.0.170...v1.0.171) (2024-11-12)
6
8
 
7
9
  ### [1.0.170](https://github.com/railroadmedia/musora-content-services/compare/v1.0.169...v1.0.170) (2024-11-12)
package/README.md CHANGED
File without changes
File without changes
package/docs/index.html CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/jest.config.js CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "1.0.171",
3
+ "version": "1.0.172",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
File without changes
File without changes
package/src/index.d.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  } from './services/contentLikes.js';
13
13
 
14
14
  import {
15
+ assignmentStatusCompleted,
15
16
  contentStatusCompleted,
16
17
  contentStatusReset,
17
18
  getAllCompleted,
@@ -77,6 +78,7 @@ import {
77
78
  fetchAllSongs,
78
79
  fetchArtistLessons,
79
80
  fetchArtists,
81
+ fetchAssignments,
80
82
  fetchByRailContentId,
81
83
  fetchByRailContentIds,
82
84
  fetchByReference,
@@ -129,6 +131,7 @@ import {
129
131
  declare module 'musora-content-services' {
130
132
  export {
131
133
  addItemToPlaylist,
134
+ assignmentStatusCompleted,
132
135
  contentStatusCompleted,
133
136
  contentStatusReset,
134
137
  countAssignmentsAndLessons,
@@ -144,6 +147,7 @@ declare module 'musora-content-services' {
144
147
  fetchAllSongs,
145
148
  fetchArtistLessons,
146
149
  fetchArtists,
150
+ fetchAssignments,
147
151
  fetchByRailContentId,
148
152
  fetchByRailContentIds,
149
153
  fetchByReference,
package/src/index.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  } from './services/contentLikes.js';
13
13
 
14
14
  import {
15
+ assignmentStatusCompleted,
15
16
  contentStatusCompleted,
16
17
  contentStatusReset,
17
18
  getAllCompleted,
@@ -77,6 +78,7 @@ import {
77
78
  fetchAllSongs,
78
79
  fetchArtistLessons,
79
80
  fetchArtists,
81
+ fetchAssignments,
80
82
  fetchByRailContentId,
81
83
  fetchByRailContentIds,
82
84
  fetchByReference,
@@ -128,6 +130,7 @@ import {
128
130
 
129
131
  export {
130
132
  addItemToPlaylist,
133
+ assignmentStatusCompleted,
131
134
  contentStatusCompleted,
132
135
  contentStatusReset,
133
136
  countAssignmentsAndLessons,
@@ -143,6 +146,7 @@ export {
143
146
  fetchAllSongs,
144
147
  fetchArtistLessons,
145
148
  fetchArtists,
149
+ fetchAssignments,
146
150
  fetchByRailContentId,
147
151
  fetchByRailContentIds,
148
152
  fetchByReference,
File without changes
File without changes
@@ -5,7 +5,7 @@ import {
5
5
  postRecordWatchSession
6
6
  } from "./railcontent";
7
7
  import {DataContext, ContentProgressVersionKey} from "./dataContext";
8
- import {fetchHierarchy} from "./sanity";
8
+ import {fetchAssignments, fetchHierarchy} from "./sanity";
9
9
 
10
10
  const STATE_STARTED = 'started';
11
11
  const STATE_COMPLETED = 'completed';
@@ -86,38 +86,83 @@ export async function getResumeTimeSeconds(contentId) {
86
86
  return data[contentId]?.[DATA_KEY_RESUME_TIME] ?? 0;
87
87
  }
88
88
 
89
+ export async function assignmentStatusCompleted(assignmentId, parentContentId) {
90
+ await dataContext.update(
91
+ async function (localContext) {
92
+ let hierarchy = await fetchHierarchy(parentContentId);
93
+ completeStatusInLocalContext(localContext, assignmentId, hierarchy);
94
+ },
95
+ async function () {
96
+ return postContentCompleted(assignmentId);
97
+ });
98
+ }
99
+
89
100
  export async function contentStatusCompleted(contentId) {
90
101
  await dataContext.update(
91
102
  async function (localContext) {
92
103
  let hierarchy = await fetchHierarchy(contentId);
93
- completeStatusInLocalContext(contentId, localContext, hierarchy);
104
+ completeStatusInLocalContext(localContext, contentId, hierarchy);
94
105
  },
95
106
  async function () {
96
107
  return postContentCompleted(contentId);
97
108
  });
98
109
  }
99
110
 
100
- function completeStatusInLocalContext(contentId, localContext, hierarchy) {
111
+
112
+ function saveContentProgress(localContext, contentId, progress, currentSeconds, hierarchy) {
113
+ if (progress === 100) {
114
+ completeStatusInLocalContext(localContext, contentId, hierarchy);
115
+ return;
116
+ }
117
+
118
+ let data = localContext.data[contentId] ?? {};
119
+ data[DATA_KEY_PROGRESS] = progress;
120
+ data[DATA_KEY_STATUS] = STATE_STARTED;
121
+ data[DATA_KEY_RESUME_TIME] = currentSeconds;
122
+ data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000);
123
+ localContext.data[contentId] = data;
124
+
125
+ bubbleProgress(hierarchy, contentId, localContext);
126
+ }
127
+
128
+ function completeStatusInLocalContext(localContext, contentId, hierarchy) {
101
129
  let data = localContext.data[contentId] ?? {};
102
- data[DATA_KEY_STATUS] = STATE_COMPLETED;
103
130
  data[DATA_KEY_PROGRESS] = 100;
131
+ data[DATA_KEY_STATUS] = STATE_COMPLETED;
104
132
  data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000);
105
133
  localContext.data[contentId] = data;
106
134
 
135
+ if (!hierarchy) return;
107
136
  let children = hierarchy.children[contentId] ?? [];
108
137
  for (let i = 0; i < children.length; i++) {
109
138
  let childId = children[i];
110
- completeStatusInLocalContext(childId, localContext, hierarchy);
139
+ completeStatusInLocalContext(localContext, childId, hierarchy);
111
140
  }
141
+ bubbleProgress(hierarchy, contentId, localContext);
112
142
  }
113
143
 
144
+ function getChildrenToDepth(parentId, hierarchy, depth = 1) {
145
+ let childIds = hierarchy.children[parentId] ?? [];
146
+ let allChildrenIds = childIds;
147
+ childIds.forEach(id => {
148
+ allChildrenIds = allChildrenIds.concat(getChildrenToDepth(id, hierarchy, depth - 1));
149
+ })
150
+ return allChildrenIds;
151
+ }
152
+
153
+
114
154
  export async function contentStatusReset(contentId) {
115
155
  await dataContext.update(
116
- function (localContext) {
117
- const index = Object.keys(localContext.data).indexOf(contentId.toString());
118
- if (index > -1) { // only splice array when item is found
119
- delete localContext.data[contentId];
120
- }
156
+ async function (localContext) {
157
+ let hierarchy = await fetchHierarchy(contentId);
158
+ let allChildIds = getChildrenToDepth(contentId, hierarchy, 5);
159
+ allChildIds.push(contentId);
160
+ allChildIds.forEach(id => {
161
+ const index = Object.keys(localContext.data).indexOf(id.toString());
162
+ if (index > -1) { // only splice array when item is found
163
+ delete localContext.data[id];
164
+ }
165
+ });
121
166
  },
122
167
  async function () {
123
168
  return postContentReset(contentId);
@@ -144,23 +189,12 @@ export async function recordWatchSession(contentId, mediaType, mediaCategory, me
144
189
  await dataContext.update(
145
190
  async function (localContext) {
146
191
  if (contentId && updateLocalProgress) {
147
- let data = localContext.data[contentId] ?? {};
148
- let progress = data?.[DATA_KEY_PROGRESS] ?? 0;
149
- let status = data?.[DATA_KEY_STATUS] ?? 0;
150
-
151
- if (status !== STATE_COMPLETED && progress !== 100) {
152
- status = STATE_STARTED;
153
- progress = Math.min(99, Math.round(currentSeconds ?? 0 / Math.max(1, mediaLengthSeconds ?? 0) * 100));
192
+ if (mediaLengthSeconds <= 0) {
193
+ return;
154
194
  }
155
-
156
- data[DATA_KEY_PROGRESS] = progress;
157
- data[DATA_KEY_STATUS] = status;
158
- data[DATA_KEY_RESUME_TIME] = currentSeconds;
159
- data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000);
160
- localContext.data[contentId] = data;
161
-
195
+ let progress = Math.min(99, Math.round(currentSeconds ?? 0 / Math.max(1, mediaLengthSeconds ?? 0) * 100));
162
196
  let hierarchy = await fetchHierarchy(contentId);
163
- bubbleProgress(hierarchy, contentId, localContext);
197
+ saveContentProgress(localContext, contentId, progress, currentSeconds, hierarchy);
164
198
  }
165
199
  },
166
200
  async function () {
@@ -204,6 +238,7 @@ function bubbleProgress(hierarchy, contentId, localContext) {
204
238
  });
205
239
  progress = Math.round(childProgress.reduce((a, b) => a + b, 0) / childProgress.length);
206
240
  data[DATA_KEY_PROGRESS] = progress;
241
+ data[DATA_KEY_STATUS] = progress === 100 ? STATE_COMPLETED : STATE_STARTED;
207
242
  localContext.data[parentId] = data;
208
243
  }
209
244
  bubbleProgress(hierarchy, parentId, localContext);
@@ -46,7 +46,7 @@ export class DataContext {
46
46
  async ensureLocalContextLoaded() {
47
47
  if (this.context) return;
48
48
  this.verifyConfig();
49
- let localData = globalConfig.isMA ? await cache.getItem(this.localStorageKey): cache.getItem(this.localStorageKey) ;
49
+ let localData = globalConfig.isMA ? await cache.getItem(this.localStorageKey) : cache.getItem(this.localStorageKey);
50
50
  if (localData) {
51
51
  this.context = JSON.parse(localData);
52
52
  }
@@ -85,10 +85,13 @@ export class DataContext {
85
85
  cache.setItem(this.localStorageKey, data);
86
86
  cache.setItem(this.localStorageLastUpdatedKey, new Date().getTime().toString());
87
87
  }
88
- let response = await serverUpdateFunction();
89
- if (response?.version !== this.version()) {
90
- this.clearCache();
91
- }
88
+ const updatePromise = serverUpdateFunction();
89
+ updatePromise.then((response) => {
90
+ if (response?.version !== this.version()) {
91
+ this.clearCache();
92
+ }
93
+ });
94
+ return updatePromise;
92
95
  }
93
96
 
94
97
  version() {
File without changes
@@ -1399,18 +1399,22 @@ export async function fetchHierarchy(railcontentId) {
1399
1399
  let topLevelId = await fetchTopLevelParentId(railcontentId);
1400
1400
  const query = `*[railcontent_id == ${topLevelId}]{
1401
1401
  railcontent_id,
1402
+ 'assignments': assignment[]{railcontent_id},
1402
1403
  'children': child[]->{
1403
1404
  railcontent_id,
1405
+ 'assignments': assignment[]{railcontent_id},
1404
1406
  'children': child[]->{
1405
1407
  railcontent_id,
1408
+ 'assignments': assignment[]{railcontent_id},
1406
1409
  'children': child[]->{
1407
1410
  railcontent_id,
1411
+ 'assignments': assignment[]{railcontent_id},
1408
1412
  'children': child[]->{
1409
- railcontent_id,
1413
+ railcontent_id,
1410
1414
  }
1411
1415
  }
1412
1416
  }
1413
- }
1417
+ },
1414
1418
  }`;
1415
1419
  let response = await fetchSanity(query, false, {processNeedAccess: false});
1416
1420
  if (!response) return null;
@@ -1422,9 +1426,11 @@ export async function fetchHierarchy(railcontentId) {
1422
1426
  return data;
1423
1427
  }
1424
1428
 
1429
+
1425
1430
  function populateHierarchyLookups(currentLevel, data, parentId) {
1426
1431
  let contentId = currentLevel['railcontent_id'];
1427
1432
  let children = currentLevel['children'];
1433
+
1428
1434
  data.parents[contentId] = parentId;
1429
1435
  if (children) {
1430
1436
  data.children[contentId] = children.map(child => child['railcontent_id']);
@@ -1434,6 +1440,36 @@ function populateHierarchyLookups(currentLevel, data, parentId) {
1434
1440
  } else {
1435
1441
  data.children[contentId] = [];
1436
1442
  }
1443
+
1444
+ let assignments = currentLevel['assignments'];
1445
+ if (assignments) {
1446
+ let assignmentIds = assignments.map(assignment => assignment['railcontent_id']);
1447
+ data.children[contentId] = (data.children[contentId] ?? []).concat(assignmentIds);
1448
+ assignmentIds.forEach(assignmentId => {
1449
+ data.parents[assignmentId] = contentId;
1450
+ });
1451
+ }
1452
+
1453
+ }
1454
+
1455
+ /**
1456
+ * Fetch assignments for content
1457
+ *
1458
+ * @param {integer} contentId - List of ids get data for
1459
+ * @returns {Promise<array|null>} - A promise that resolves to an array containing the data
1460
+ */
1461
+ export async function fetchAssignments(contentId) {
1462
+ const fields = `"id": railcontent_id,"assignments":assignment[]{"id": railcontent_id}`;
1463
+ const query = await buildQuery(`railcontent_id == ${contentId}`,
1464
+ {bypassPermissions: true},
1465
+ fields,
1466
+ {end: 100});
1467
+ let data = await fetchSanity(query, false);
1468
+ let mapped = [];
1469
+ data.assignments.forEach(function (content) {
1470
+ mapped.push(content.id);
1471
+ });
1472
+ return mapped;
1437
1473
  }
1438
1474
 
1439
1475
  /**
@@ -1448,7 +1484,7 @@ export async function fetchCommentModContentData(ids) {
1448
1484
  const query = await buildQuery(`railcontent_id in [${idsString}]`,
1449
1485
  {bypassPermissions: true},
1450
1486
  fields,
1451
- {end:50});
1487
+ {end: 50});
1452
1488
  let data = await fetchSanity(query, true);
1453
1489
  let mapped = {};
1454
1490
  data.forEach(function (content) {
File without changes
File without changes
@@ -7,7 +7,7 @@ import {
7
7
  getProgressStateByIds,
8
8
  getAllStarted,
9
9
  getAllCompleted,
10
- contentStatusCompleted
10
+ contentStatusCompleted, assignmentStatusCompleted, contentStatusReset
11
11
  } from "../src/services/contentProgress";
12
12
  import {initializeTestService} from "./initializeTests";
13
13
  import {postContentCompleted} from "../src";
@@ -17,12 +17,23 @@ const railContentModule = require('../src/services/railcontent.js')
17
17
  describe('contentProgressDataContext', function () {
18
18
  let mock = null;
19
19
  const testVersion = 1;
20
+ let serverVersion = 2;
20
21
 
21
22
  beforeEach(() => {
22
23
  initializeTestService();
23
24
  mock = jest.spyOn(dataContext, 'fetchData');
24
25
  var json = JSON.parse(`{"version":${testVersion},"config":{"key":1,"enabled":1,"checkInterval":1,"refreshInterval":2},"data":{"234191":{"s":"started","p":6,"t":20,"u":1731108082},"233955":{"s":"started","p":1,"u":1731108083},"259426":{"s":"completed","p":100,"u":1731108085}}}`);
25
- mock.mockImplementation(() => json);
26
+ mock.mockImplementation(() =>
27
+ json);
28
+
29
+ let mock2 = jest.spyOn(railContentModule, 'postRecordWatchSession');
30
+ mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
31
+
32
+ let mock3 = jest.spyOn(railContentModule, 'postContentCompleted');
33
+ mock3.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
34
+
35
+ let mock4 = jest.spyOn(railContentModule, 'postContentReset');
36
+ mock4.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
26
37
  });
27
38
 
28
39
  test('getProgressPercentage', async () => {
@@ -72,11 +83,6 @@ describe('contentProgressDataContext', function () {
72
83
  // expect(result2).toStrictEqual([111111, 233955, 234191]);
73
84
  // });
74
85
 
75
- test('getAllCompleted', async () => {
76
- let result = await getAllCompleted();
77
- expect(result).toStrictEqual([259426]);
78
- });
79
-
80
86
  // test('getAllCompletedWithUpdate', async () => {
81
87
  // let mock2 = jest.spyOn(railContentModule, 'postContentCompleted');
82
88
  // let serverVersion = 2;
@@ -90,9 +96,7 @@ describe('contentProgressDataContext', function () {
90
96
  // });
91
97
 
92
98
  test('progressBubbling', async () => {
93
- let mock2 = jest.spyOn(railContentModule, 'postRecordWatchSession');
94
- let serverVersion = 2;
95
- mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
99
+
96
100
  let progress = await getProgressPercentage(241250); //force load context
97
101
 
98
102
  await recordWatchSession(241250, "video", "vimeo", 100, 50, 50);
@@ -117,4 +121,65 @@ describe('contentProgressDataContext', function () {
117
121
 
118
122
  });
119
123
 
124
+ test('assignmentCompleteBubbling', async () => {
125
+ let assignmentId = 286048;
126
+ let contentId = 286047;
127
+
128
+ let state = await getProgressState(contentId);
129
+ expect(state).toBe("");
130
+ let result = await assignmentStatusCompleted(assignmentId, contentId);
131
+
132
+ state = await getProgressState(assignmentId);
133
+ expect(state).toBe("completed");
134
+ state = await getProgressState(contentId); //assignment
135
+ expect(state).toBe("started");
136
+ });
137
+
138
+ // test('assignmentCompleteBubblingToCompleted', async () => {
139
+ // let assignmentId = 241685;
140
+ // let contentId = 241257;
141
+ //
142
+ // let state = await getProgressState(contentId);
143
+ // expect(state).toBe("");
144
+ // let result = await assignmentStatusCompleted(assignmentId, contentId);
145
+ //
146
+ // state = await getProgressState(assignmentId);
147
+ // expect(state).toBe("completed");
148
+ // state = await getProgressState(contentId); //assignment
149
+ // expect(state).toBe("completed");
150
+ // });
151
+ //
152
+ // test('completeBubbling', async () => {
153
+ // let state = await getProgressState(276698);
154
+ // expect(state).toBe("");
155
+ // let result = await contentStatusCompleted(276698);
156
+ //
157
+ // state = await getProgressState(276698);
158
+ // expect(state).toBe("completed");
159
+ // state = await getProgressState(276699); //assignment
160
+ // expect(state).toBe("completed");
161
+ // });
162
+ //
163
+ // test('resetBubbling', async () => {
164
+ // let assignmentId = 241686;
165
+ // let contentId = 241258;
166
+ //
167
+ // let state = await getProgressState(contentId);
168
+ // expect(state).toBe("");
169
+ // let result = await contentStatusCompleted(contentId);
170
+ // state = await getProgressState(contentId);
171
+ // expect(state).toBe("completed");
172
+ // state = await getProgressState(assignmentId); //assignment
173
+ // expect(state).toBe("completed");
174
+ //
175
+ // result = await contentStatusReset(contentId);
176
+ // state = await getProgressState(contentId);
177
+ // expect(state).toBe("");
178
+ // state = await getProgressState(assignmentId); //assignment
179
+ // expect(state).toBe("");
180
+ //
181
+ // });
182
+
183
+
184
+
120
185
  });
File without changes
File without changes
@@ -38,6 +38,7 @@ describe('contentProgressDataContextLocal', function () {
38
38
  result = await getProgressState(contentId);
39
39
  expect(result).toBe("started");
40
40
  dataContext.clearCache();
41
+ await new Promise(resolve => setTimeout(resolve, 3000)); // 3 sec
41
42
 
42
43
  result = await getProgressState(contentId);
43
44
  expect(result).toBe("started");
File without changes
package/test/log.js CHANGED
File without changes
@@ -1,5 +1,5 @@
1
1
  import {getFieldsForContentType} from "../src/contentTypeConfig";
2
- import {fetchCommentModContentData, fetchSanity} from "../src/services/sanity";
2
+ import {fetchAssignments, fetchCommentModContentData, fetchSanity} from "../src/services/sanity";
3
3
  import {log} from './log.js';
4
4
  import {initializeTestService} from "./initializeTests";
5
5
 
@@ -568,7 +568,7 @@ describe('Sanity Queries', function () {
568
568
  expect(hierarchy.parents[241250]).toBe(241249);
569
569
  expect(hierarchy.parents[241249]).toBe(241248);
570
570
  expect(hierarchy.parents[241248]).toBe(241247);
571
- expect(hierarchy.children[241250]).toStrictEqual([]);
571
+ expect(hierarchy.children[241250]).toStrictEqual([241676]);
572
572
  expect(hierarchy.children[243085]).toStrictEqual([243170, 243171, 243172, 243174, 243176]);
573
573
  });
574
574
 
@@ -581,6 +581,11 @@ describe('Sanity Queries', function () {
581
581
  expect(data[241252].title).toBe("Setting Up Your Pedals & Throne");
582
582
  });
583
583
 
584
+ test('fetchAssignments', async()=>{
585
+ let data = await fetchAssignments(241250);
586
+ expect(data).toContain(241676);
587
+ });
588
+
584
589
  });
585
590
 
586
591
  describe('Filter Builder', function () {
File without changes
File without changes