musora-content-services 1.0.163 → 1.0.165

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,10 @@
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.165](https://github.com/railroadmedia/musora-content-services/compare/v1.0.164...v1.0.165) (2024-11-11)
6
+
7
+ ### [1.0.164](https://github.com/railroadmedia/musora-content-services/compare/v1.0.163...v1.0.164) (2024-11-09)
8
+
5
9
  ### [1.0.163](https://github.com/railroadmedia/musora-content-services/compare/v1.0.162...v1.0.163) (2024-11-08)
6
10
 
7
11
  ### [1.0.162](https://github.com/railroadmedia/musora-content-services/compare/v1.0.161...v1.0.162) (2024-11-08)
package/jest.config.js CHANGED
@@ -92,7 +92,7 @@ const config = {
92
92
  // moduleNameMapper: {},
93
93
 
94
94
  // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
95
- // modulePathIgnorePatterns: [],
95
+ modulePathIgnorePatterns: ["<rootDir>/test/live"],
96
96
 
97
97
  // Activates notifications for test results
98
98
  // notify: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "musora-content-services",
3
- "version": "1.0.163",
3
+ "version": "1.0.165",
4
4
  "description": "A package for Musoras content services ",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -32,7 +32,8 @@ const excludeFromGeneratedIndex = [];
32
32
  * @param {Object} config.railcontentConfig - Configuration for user services.
33
33
  * @param {string} config.railcontentConfig.token - The token for authenticating user-specific requests.
34
34
  * @param {string} config.railcontentConfig.userId - The user ID for fetching user-specific data.
35
- * @param {string} config.railcontentConfig.baseUrl - The url for the enviroment.
35
+ * @param {string} config.railcontentConfig.baseUrl - The url for the environment.
36
+ * @param {string} config.railcontentConfig.authToken - The bearer authorization token.
36
37
  * @param {Object} config.localStorage - Cache to use for localStorage
37
38
  * @param {boolean} config.isMA - Variable that tells if the library is used by MA or FEW
38
39
 
@@ -2,17 +2,17 @@ import {
2
2
  fetchContentProgress,
3
3
  postContentCompleted,
4
4
  postContentReset,
5
- postContentStarted,
6
5
  postRecordWatchSession
7
6
  } from "./railcontent";
8
7
  import {DataContext, ContentProgressVersionKey} from "./dataContext";
9
- import {fetchHierarchy, fetchParentByRailContentId} from "./sanity";
8
+ import {fetchHierarchy} from "./sanity";
10
9
 
11
10
  const STATE_STARTED = 'started';
12
11
  const STATE_COMPLETED = 'completed';
13
12
  const DATA_KEY_STATUS = 's';
14
13
  const DATA_KEY_PROGRESS = 'p';
15
14
  const DATA_KEY_RESUME_TIME = 't';
15
+ const DATA_KEY_LAST_UPDATED_TIME = 'u';
16
16
  export let dataContext = new DataContext(ContentProgressVersionKey, fetchContentProgress);
17
17
 
18
18
  export async function getProgressPercentage(contentId) {
@@ -43,23 +43,41 @@ export async function getProgressStateByIds(contentIds) {
43
43
  return progress;
44
44
  }
45
45
 
46
- export async function getAllStarted() {
46
+ export async function getAllStarted(limit = null) {
47
47
  const data = await dataContext.getData();
48
48
  let ids = Object.keys(data).filter(function (key) {
49
49
  return data[parseInt(key)][DATA_KEY_STATUS] === STATE_STARTED;
50
50
  }).map(function (key) {
51
51
  return parseInt(key);
52
+ }).sort(function (a, b) {
53
+ let v1 = data[a][DATA_KEY_LAST_UPDATED_TIME];
54
+ let v2 = data[b][DATA_KEY_LAST_UPDATED_TIME];
55
+ if (v1 > v2) return -1;
56
+ else if (v1 < v2) return 1;
57
+ return 0;
52
58
  });
59
+ if (limit) {
60
+ ids = ids.slice(0, limit);
61
+ }
53
62
  return ids;
54
63
  }
55
64
 
56
- export async function getAllCompleted() {
65
+ export async function getAllCompleted(limit = null) {
57
66
  const data = await dataContext.getData();
58
67
  let ids = Object.keys(data).filter(function (key) {
59
68
  return data[parseInt(key)][DATA_KEY_STATUS] === STATE_COMPLETED;
60
69
  }).map(function (key) {
61
70
  return parseInt(key);
71
+ }).sort(function (a, b) {
72
+ let v1 = data[a][DATA_KEY_LAST_UPDATED_TIME];
73
+ let v2 = data[b][DATA_KEY_LAST_UPDATED_TIME];
74
+ if (v1 > v2) return -1;
75
+ else if (v1 < v2) return 1;
76
+ return 0;
62
77
  });
78
+ if (limit) {
79
+ ids = ids.slice(0, limit);
80
+ }
63
81
  return ids;
64
82
  }
65
83
 
@@ -70,8 +88,8 @@ export async function getResumeTimeSeconds(contentId) {
70
88
 
71
89
  export async function contentStatusCompleted(contentId) {
72
90
  await dataContext.update(
73
- function (localContext) {
74
- let hierarchy = fetchHierarchy(contentId);
91
+ async function (localContext) {
92
+ let hierarchy = await fetchHierarchy(contentId);
75
93
  completeStatusInLocalContext(contentId, localContext, hierarchy);
76
94
  },
77
95
  async function () {
@@ -80,9 +98,10 @@ export async function contentStatusCompleted(contentId) {
80
98
  }
81
99
 
82
100
  function completeStatusInLocalContext(contentId, localContext, hierarchy) {
83
- let data = localContext.data[contentId] ?? [];
101
+ let data = localContext.data[contentId] ?? {};
84
102
  data[DATA_KEY_STATUS] = STATE_COMPLETED;
85
103
  data[DATA_KEY_PROGRESS] = 100;
104
+ data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000);
86
105
  localContext.data[contentId] = data;
87
106
 
88
107
  let children = hierarchy.children[contentId] ?? [];
@@ -95,7 +114,7 @@ function completeStatusInLocalContext(contentId, localContext, hierarchy) {
95
114
  export async function contentStatusReset(contentId) {
96
115
  await dataContext.update(
97
116
  function (localContext) {
98
- const index = Object.keys(localContext.data).indexOf(contentId);
117
+ const index = Object.keys(localContext.data).indexOf(contentId.toString());
99
118
  if (index > -1) { // only splice array when item is found
100
119
  delete localContext.data[contentId];
101
120
  }
@@ -125,7 +144,7 @@ export async function recordWatchSession(contentId, mediaType, mediaCategory, me
125
144
  await dataContext.update(
126
145
  async function (localContext) {
127
146
  if (contentId && updateLocalProgress) {
128
- let data = localContext.data[contentId] ?? [];
147
+ let data = localContext.data[contentId] ?? {};
129
148
  let progress = data?.[DATA_KEY_PROGRESS] ?? 0;
130
149
  let status = data?.[DATA_KEY_STATUS] ?? 0;
131
150
 
@@ -137,6 +156,7 @@ export async function recordWatchSession(contentId, mediaType, mediaCategory, me
137
156
  data[DATA_KEY_PROGRESS] = progress;
138
157
  data[DATA_KEY_STATUS] = status;
139
158
  data[DATA_KEY_RESUME_TIME] = currentSeconds;
159
+ data[DATA_KEY_LAST_UPDATED_TIME] = Math.round(new Date().getTime() / 1000);
140
160
  localContext.data[contentId] = data;
141
161
 
142
162
  let hierarchy = await fetchHierarchy(contentId);
@@ -175,7 +195,7 @@ function uuidv4() {
175
195
  function bubbleProgress(hierarchy, contentId, localContext) {
176
196
  let parentId = hierarchy.parents[contentId];
177
197
  if (!parentId) return;
178
- let data = localContext.data[parentId] ?? [];
198
+ let data = localContext.data[parentId] ?? {};
179
199
  let progress = data[DATA_KEY_PROGRESS];
180
200
  let status = data[DATA_KEY_STATUS];
181
201
  if (status !== STATE_COMPLETED && progress !== 100) {
@@ -256,6 +256,9 @@ export async function fetchHandler(url, method = "get", dataVersion = null, body
256
256
  'Content-Type': 'application/json',
257
257
  'X-CSRF-TOKEN': globalConfig.railcontentConfig.token,
258
258
  };
259
+ if (globalConfig.railcontentConfig.authToken) {
260
+ headers['Authorization'] = `Bearer ${globalConfig.railcontentConfig.authToken}`;
261
+ }
259
262
  if (dataVersion) headers['Data-Version'] = dataVersion;
260
263
  const options = {
261
264
  method,
@@ -269,7 +272,7 @@ export async function fetchHandler(url, method = "get", dataVersion = null, body
269
272
  if (response.ok) {
270
273
  return await response.json();
271
274
  } else {
272
- console.log('fetch error:', response.status);
275
+ console.error(`Fetch error: ${method} ${url} ${response.status} ${response.statusText}`);
273
276
  console.log(response);
274
277
  }
275
278
  } catch (error) {
@@ -826,13 +829,13 @@ export async function fetchPlaylistItem(payload) {
826
829
  }
827
830
 
828
831
  export async function postContentCompleted(contentId) {
829
- let url = `/content/${contentId}/completed`;
830
- return postDataHandler(url);
832
+ let url = `/content/user/progress/complete`;
833
+ return postDataHandler(url, {"contentId": contentId});
831
834
  }
832
835
 
833
836
  export async function postContentReset(contentId) {
834
- let url = `/content/${contentId}/reset`;
835
- return postDataHandler(url);
837
+ let url = `/content/user/progress/reset`;
838
+ return postDataHandler(url, {"contentId": contentId});
836
839
  }
837
840
 
838
841
  /**
@@ -918,4 +921,4 @@ function fetchAbsolute(url, params) {
918
921
  }
919
922
  }
920
923
  return fetch(url, params);
921
- }
924
+ }
@@ -2,9 +2,15 @@ import {
2
2
  getProgressPercentage,
3
3
  dataContext,
4
4
  recordWatchSession,
5
- getProgressPercentageByIds, getProgressState, getProgressStateByIds, getAllStarted, getAllCompleted
5
+ getProgressPercentageByIds,
6
+ getProgressState,
7
+ getProgressStateByIds,
8
+ getAllStarted,
9
+ getAllCompleted,
10
+ contentStatusCompleted
6
11
  } from "../src/services/contentProgress";
7
12
  import {initializeTestService} from "./initializeTests";
13
+ import {postContentCompleted} from "../src";
8
14
 
9
15
  const railContentModule = require('../src/services/railcontent.js')
10
16
 
@@ -15,9 +21,8 @@ describe('contentProgressDataContext', function () {
15
21
  beforeEach(() => {
16
22
  initializeTestService();
17
23
  mock = jest.spyOn(dataContext, 'fetchData');
18
- var json = JSON.parse(`{"version":${testVersion},"data":{"234191":{"s":"started","p":6,"t":20},"233955":{"s":"started","p":1},"259426":{"s":"completed","p":100}}}`);
24
+ 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}}}`);
19
25
  mock.mockImplementation(() => json);
20
-
21
26
  });
22
27
 
23
28
  test('getProgressPercentage', async () => {
@@ -49,15 +54,41 @@ describe('contentProgressDataContext', function () {
49
54
 
50
55
  test('getAllStarted', async () => {
51
56
  let result = await getAllStarted();
52
- expect(result).toContain(234191);
53
- expect(result).toContain(233955);
57
+ expect(result).toStrictEqual([233955, 234191]);
58
+
59
+ result = await getAllStarted(1);
60
+ expect(result).toStrictEqual([233955]);
54
61
  });
55
62
 
63
+ // test('getAllStartedWithUpdate', async () => {
64
+ // let mock2 = jest.spyOn(railContentModule, 'postRecordWatchSession');
65
+ // let serverVersion = 2;
66
+ // mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
67
+ // let result = await getAllStarted();
68
+ // expect(result).toStrictEqual([233955, 234191]);
69
+ // await recordWatchSession(111111, "video", "vimeo", 100, 50, 50);
70
+ //
71
+ // let result2 = await getAllStarted();
72
+ // expect(result2).toStrictEqual([111111, 233955, 234191]);
73
+ // });
74
+
56
75
  test('getAllCompleted', async () => {
57
76
  let result = await getAllCompleted();
58
- expect(result).toContain(259426);
77
+ expect(result).toStrictEqual([259426]);
59
78
  });
60
79
 
80
+ // test('getAllCompletedWithUpdate', async () => {
81
+ // let mock2 = jest.spyOn(railContentModule, 'postContentCompleted');
82
+ // let serverVersion = 2;
83
+ // mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
84
+ //
85
+ // let result = await getAllCompleted();
86
+ // expect(result).toStrictEqual([259426]);
87
+ // await contentStatusCompleted(111111);
88
+ // let result2 = await getAllCompleted();
89
+ // expect(result2).toStrictEqual([111111, 259426]);
90
+ // });
91
+
61
92
  test('progressBubbling', async () => {
62
93
  let mock2 = jest.spyOn(railContentModule, 'postRecordWatchSession');
63
94
  let serverVersion = 2;
@@ -1,8 +1,16 @@
1
- import {initializeService} from '../src';
1
+ import {globalConfig, initializeService} from '../src';
2
2
  import {LocalStorageMock} from "./localStorageMock";
3
+
3
4
  const railContentModule = require('../src/services/railcontent.js')
5
+ let token = null;
6
+ let userId = null;
4
7
 
5
- export function initializeTestService() {
8
+ export async function initializeTestService(useLive = false) {
9
+ if (useLive && !token && process.env.RAILCONTENT_BASE_URL) {
10
+ let data = await fetchLoginToken(process.env.RAILCONTENT_EMAIL, process.env.RAILCONTENT_PASSWORD);
11
+ token = data['token'];
12
+ userId = data['userId'];
13
+ }
6
14
  const config = {
7
15
  sanityConfig: {
8
16
  token: process.env.SANITY_API_TOKEN,
@@ -13,6 +21,11 @@ export function initializeTestService() {
13
21
  debug: process.env.DEBUG === 'true' || false,
14
22
  useDummyRailContentMethods: true,
15
23
  },
24
+ railcontentConfig: {
25
+ baseUrl: process.env.RAILCONTENT_BASE_URL,
26
+ userId: userId,
27
+ authToken: token,
28
+ },
16
29
  localStorage: new LocalStorageMock()
17
30
  };
18
31
  initializeService(config);
@@ -20,4 +33,28 @@ export function initializeTestService() {
20
33
  let mock = jest.spyOn(railContentModule, 'fetchUserPermissionsData');
21
34
  let testData = {"permissions": [78, 91, 92], "isAdmin": false};
22
35
  mock.mockImplementation(() => testData);
36
+ }
37
+
38
+ async function fetchLoginToken(email, password) {
39
+ try {
40
+ const url = `${process.env.RAILCONTENT_BASE_URL}/user-management-system/login/token`;
41
+ const response = await fetch(url, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Accept': 'application/json',
45
+ 'Content-Type': 'application/json'
46
+ },
47
+ body: JSON.stringify({"email": email, "password": password, "device_name": "test"})
48
+ });
49
+ if (response.ok) {
50
+ let data = await response.json();
51
+ return {token: data.token, userId: data.user.id};
52
+ } else {
53
+ console.log('fetch error:', response.status);
54
+ console.log(response);
55
+ }
56
+ } catch (error) {
57
+ console.error('Fetch error:', error);
58
+ }
59
+ return null;
23
60
  }
@@ -0,0 +1,112 @@
1
+ import {
2
+ recordWatchSession,
3
+ getProgressPercentage,
4
+ dataContext,
5
+ getProgressState, contentStatusCompleted, contentStatusReset
6
+ } from "../../src/services/contentProgress";
7
+ import {initializeTestService} from "../initializeTests";
8
+
9
+
10
+ describe('contentProgressDataContextLocal', function () {
11
+ beforeEach(async () => {
12
+ await initializeTestService(true);
13
+ }, 1000000);
14
+
15
+ test('verifyProgressPercentage', async () => {
16
+ let contentId = 241250;
17
+ await contentStatusReset(contentId);
18
+
19
+ await recordWatchSession(contentId, "video", "vimeo", 100, 50, 50);
20
+
21
+ let result = await getProgressPercentage(contentId);
22
+ expect(result).toBe(50);
23
+ dataContext.clearCache();
24
+
25
+ result = await getProgressPercentage(contentId);
26
+ expect(result).toBe(50);
27
+ }, 100000);
28
+
29
+ test('verifyState', async () => {
30
+ let contentId = 241251;
31
+ await contentStatusReset(contentId);
32
+
33
+ let result = await getProgressState(contentId);
34
+ expect(result).toBe("");
35
+
36
+ await recordWatchSession(contentId, "video", "vimeo", 100, 50, 50);
37
+
38
+ result = await getProgressState(contentId);
39
+ expect(result).toBe("started");
40
+ dataContext.clearCache();
41
+
42
+ result = await getProgressState(contentId);
43
+ expect(result).toBe("started");
44
+ }, 100000);
45
+
46
+ test('verifyStateCompleted', async () => {
47
+ let contentId = 241252;
48
+ await contentStatusReset(contentId);
49
+
50
+ await contentStatusCompleted(241252);
51
+ let result = await getProgressState(241252);
52
+ expect(result).toBe("completed");
53
+
54
+ result = await getProgressState(contentId);
55
+ expect(result).toBe("completed");
56
+ dataContext.clearCache();
57
+
58
+ result = await getProgressState(contentId);
59
+ expect(result).toBe("completed");
60
+
61
+
62
+ }, 100000);
63
+
64
+ test('verifyStateReset', async () => {
65
+ let contentId = 241253;
66
+ await contentStatusReset(contentId);
67
+
68
+ await contentStatusCompleted(contentId);
69
+
70
+ let result = await getProgressState(contentId);
71
+ expect(result).toBe("completed");
72
+ await contentStatusReset(contentId);
73
+
74
+ result = await getProgressState(contentId);
75
+ expect(result).toBe("");
76
+ dataContext.clearCache();
77
+
78
+ result = await getProgressState(contentId);
79
+ expect(result).toBe("");
80
+ }, 100000);
81
+
82
+
83
+ //
84
+ // test('progressBubbling', async () => {
85
+ // let serverVersion = 2;
86
+ // let mock2 = jest.spyOn(railContentModule, 'postRecordWatchSession');
87
+ // mock2.mockImplementation(() => JSON.parse(`{"version": ${serverVersion}}`));
88
+ // let progress = await getProgressPercentage(241250); //force load context
89
+ //
90
+ // await recordWatchSession(241250, "video", "vimeo", 100, 50, 50);
91
+ // serverVersion++;
92
+ // await recordWatchSession(241251, "video", "vimeo", 100, 50, 50);
93
+ // serverVersion++;
94
+ // await recordWatchSession(241252, "video", "vimeo", 100, 50, 50);
95
+ // serverVersion++;
96
+ // await recordWatchSession(241260, "video", "vimeo", 100, 100, 100);
97
+ // serverVersion++;
98
+ // await recordWatchSession(241261, "video", "vimeo", 100, 100, 100);
99
+ // serverVersion++;
100
+ // progress = await getProgressPercentage(241250);
101
+ //
102
+ // expect(progress).toBe(50);
103
+ // let progress241249 = await getProgressPercentage(241249);
104
+ // expect(progress241249).toBe(15);
105
+ // let progress241248 = await getProgressPercentage(241248);
106
+ // expect(progress241248).toBe(7);
107
+ // let progress241247 = await getProgressPercentage(241247);
108
+ // expect(progress241247).toBe(1);
109
+ //
110
+ // }, 100000);
111
+
112
+ });