musora-content-services 2.93.1 → 2.94.1

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.
Files changed (155) hide show
  1. package/.claude/settings.local.json +14 -0
  2. package/.coderabbit.yaml +0 -0
  3. package/.editorconfig +0 -0
  4. package/.github/pull_request_template.md +0 -0
  5. package/.github/workflows/conventional-commits.yaml +0 -0
  6. package/.github/workflows/node.js.yml +0 -0
  7. package/.prettierignore +0 -0
  8. package/.prettierrc +0 -0
  9. package/.yarnrc.yml +1 -0
  10. package/CHANGELOG.md +24 -0
  11. package/README.md +0 -0
  12. package/jest.config.js +0 -0
  13. package/jsdoc.json +0 -0
  14. package/package.json +1 -1
  15. package/src/contentMetaData.js +0 -0
  16. package/src/filterBuilder.js +0 -0
  17. package/src/index.d.ts +4 -0
  18. package/src/index.js +4 -0
  19. package/src/infrastructure/http/HttpClient.ts +0 -0
  20. package/src/infrastructure/http/executors/FetchRequestExecutor.ts +0 -0
  21. package/src/infrastructure/http/index.ts +0 -0
  22. package/src/infrastructure/http/interfaces/HeaderProvider.ts +0 -0
  23. package/src/infrastructure/http/interfaces/HttpError.ts +0 -0
  24. package/src/infrastructure/http/interfaces/NetworkError.ts +0 -0
  25. package/src/infrastructure/http/interfaces/RequestExecutor.ts +0 -0
  26. package/src/infrastructure/http/interfaces/RequestOptions.ts +0 -0
  27. package/src/infrastructure/http/providers/DefaultHeaderProvider.ts +0 -0
  28. package/src/lib/httpHelper.js +0 -0
  29. package/src/lib/lastUpdated.js +0 -0
  30. package/src/lib/sanity/query.ts +0 -0
  31. package/src/services/api/types.js +0 -0
  32. package/src/services/api/types.ts +0 -0
  33. package/src/services/config.js +1 -1
  34. package/src/services/content/content.ts +0 -0
  35. package/src/services/content-org/content-org.js +0 -0
  36. package/src/services/content-org/guided-courses.ts +0 -0
  37. package/src/services/content-org/learning-paths.ts +31 -18
  38. package/src/services/content-org/playlists-types.js +0 -0
  39. package/src/services/content-org/playlists.js +0 -0
  40. package/src/services/content.js +0 -0
  41. package/src/services/contentAggregator.js +0 -0
  42. package/src/services/contentLikes.js +0 -0
  43. package/src/services/contentProgress.js +50 -13
  44. package/src/services/dataContext.js +0 -0
  45. package/src/services/dateUtils.js +0 -0
  46. package/src/services/eventsAPI.js +0 -0
  47. package/src/services/forums/categories.ts +0 -0
  48. package/src/services/forums/forums.ts +0 -0
  49. package/src/services/forums/posts.ts +0 -0
  50. package/src/services/forums/threads.ts +0 -0
  51. package/src/services/forums/types.ts +0 -0
  52. package/src/services/gamification/awards.ts +0 -0
  53. package/src/services/gamification/gamification.js +0 -0
  54. package/src/services/imageSRCBuilder.js +0 -0
  55. package/src/services/imageSRCVerify.js +0 -0
  56. package/src/services/liveTesting.ts +0 -0
  57. package/src/services/permissions/PermissionsAdapter.ts +0 -0
  58. package/src/services/permissions/PermissionsAdapterFactory.ts +0 -0
  59. package/src/services/permissions/PermissionsV1Adapter.ts +0 -0
  60. package/src/services/permissions/README.md +0 -0
  61. package/src/services/permissions/index.ts +0 -0
  62. package/src/services/progress-row/method-card.js +2 -1
  63. package/src/services/railcontent.js +0 -0
  64. package/src/services/recommendations.js +0 -0
  65. package/src/services/reporting/README.md +0 -0
  66. package/src/services/reporting/types.ts +0 -0
  67. package/src/services/sanity.js +27 -3
  68. package/src/services/sentry/.indexignore +0 -0
  69. package/src/services/sentry/index.ts +0 -0
  70. package/src/services/sync/.indexignore +0 -0
  71. package/src/services/sync/adapters/factory.ts +0 -0
  72. package/src/services/sync/adapters/lokijs.ts +0 -0
  73. package/src/services/sync/adapters/sqlite.ts +0 -0
  74. package/src/services/sync/concurrency-safety.ts +0 -0
  75. package/src/services/sync/context/index.ts +0 -0
  76. package/src/services/sync/context/providers/base.ts +0 -0
  77. package/src/services/sync/context/providers/connectivity.ts +0 -0
  78. package/src/services/sync/context/providers/durability.ts +0 -0
  79. package/src/services/sync/context/providers/index.ts +0 -0
  80. package/src/services/sync/context/providers/session.ts +0 -0
  81. package/src/services/sync/context/providers/tabs.ts +0 -0
  82. package/src/services/sync/context/providers/visibility.ts +0 -0
  83. package/src/services/sync/database/factory.ts +0 -0
  84. package/src/services/sync/errors/index.ts +0 -0
  85. package/src/services/sync/index.ts +0 -0
  86. package/src/services/sync/models/Base.ts +0 -0
  87. package/src/services/sync/models/ContentLike.ts +0 -0
  88. package/src/services/sync/models/ContentProgress.ts +1 -1
  89. package/src/services/sync/models/Practice.ts +0 -0
  90. package/src/services/sync/models/PracticeDayNote.ts +0 -0
  91. package/src/services/sync/models/index.ts +0 -0
  92. package/src/services/sync/repositories/base.ts +0 -0
  93. package/src/services/sync/repositories/content-likes.ts +0 -0
  94. package/src/services/sync/repositories/content-progress.ts +34 -7
  95. package/src/services/sync/repositories/index.ts +0 -0
  96. package/src/services/sync/repositories/practice-day-notes.ts +0 -0
  97. package/src/services/sync/repositories/practices.ts +0 -0
  98. package/src/services/sync/resolver.ts +0 -0
  99. package/src/services/sync/run-scope.ts +0 -0
  100. package/src/services/sync/schema/index.ts +0 -0
  101. package/src/services/sync/serializers/index.ts +0 -0
  102. package/src/services/sync/serializers/model.ts +0 -0
  103. package/src/services/sync/serializers/raw.ts +0 -0
  104. package/src/services/sync/store/push-coalescer.ts +0 -0
  105. package/src/services/sync/store-configs.ts +0 -0
  106. package/src/services/sync/strategies/base.ts +0 -0
  107. package/src/services/sync/strategies/index.ts +0 -0
  108. package/src/services/sync/strategies/initial.ts +0 -0
  109. package/src/services/sync/strategies/polling.ts +0 -0
  110. package/src/services/sync/utils/event-emitter.ts +0 -0
  111. package/src/services/sync/utils/index.ts +0 -0
  112. package/src/services/sync/utils/throttle.ts +0 -0
  113. package/src/services/sync/utils/timers.ts +0 -0
  114. package/src/services/types.js +0 -0
  115. package/src/services/user/account.ts +0 -0
  116. package/src/services/user/chat.js +0 -0
  117. package/src/services/user/interests.js +0 -0
  118. package/src/services/user/management.js +0 -0
  119. package/src/services/user/memberships.ts +0 -0
  120. package/src/services/user/notifications.js +0 -0
  121. package/src/services/user/payments.ts +0 -0
  122. package/src/services/user/permissions.js +0 -0
  123. package/src/services/user/profile.js +0 -0
  124. package/src/services/user/sessions.js +0 -0
  125. package/src/services/user/types.d.ts +0 -0
  126. package/src/services/user/types.js +0 -0
  127. package/src/services/user/user-management-system.js +0 -0
  128. package/src/services/userActivity.js +3 -2
  129. package/test/HttpClient.test.js +0 -0
  130. package/test/content.test.js +0 -0
  131. package/test/contentLikes.test.js +0 -0
  132. package/test/contentProgress.test.js +0 -0
  133. package/test/dataContext.test.js +0 -0
  134. package/test/forum.test.js +0 -0
  135. package/test/imageSRCBuilder.test.js +0 -0
  136. package/test/imageSRCVerify.test.js +0 -0
  137. package/test/initializeTests.js +0 -0
  138. package/test/learningPaths.test.js +0 -0
  139. package/test/lib/lastUpdated.test.js +0 -0
  140. package/test/live/contentProgressLive.test.js +0 -0
  141. package/test/live/railcontentLive.test.js +0 -0
  142. package/test/localStorageMock.js +0 -0
  143. package/test/log.js +0 -0
  144. package/test/mockData/mockData_fetchByRailContentIds_one_content.json +0 -0
  145. package/test/mockData/mockData_progress_content.json +0 -0
  146. package/test/mockData/mockData_sanity_progress_content.json +0 -0
  147. package/test/mockData/mockData_user_practices.json +0 -0
  148. package/test/notifications.test.js +0 -0
  149. package/test/progressRows.test.js +0 -0
  150. package/test/reporting.test.js +132 -0
  151. package/test/sanityQueryService.test.js +0 -0
  152. package/test/streakMessage.test.js +0 -0
  153. package/test/user/permissions.test.js +0 -0
  154. package/test/userActivity.test.js +0 -0
  155. package/tools/generate-index.cjs +0 -0
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -3,7 +3,7 @@ import { SYNC_TABLES } from '../schema'
3
3
 
4
4
  export enum COLLECTION_TYPE {
5
5
  SKILL_PACK = 'skill-pack',
6
- LEARNING_PATH = 'learning-path',
6
+ LEARNING_PATH = 'learning-path-v2',
7
7
  PLAYLIST = 'playlist',
8
8
  }
9
9
 
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -115,10 +115,10 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
115
115
  return await this.queryAll(...clauses)
116
116
  }
117
117
 
118
- recordProgressRemotely(contentId: number, collection: { type: COLLECTION_TYPE; id: number } | null, progressPct: number, resumeTime?: number) {
118
+ recordProgress(contentId: number, collection: { type: COLLECTION_TYPE; id: number } | null, progressPct: number, resumeTime?: number) {
119
119
  const id = ProgressRepository.generateId(contentId, collection)
120
120
 
121
- return this.upsertOneRemote(id, (r) => {
121
+ return this.upsertOne(id, (r) => {
122
122
  r.content_id = contentId
123
123
  r.collection_type = collection?.type ?? null
124
124
  r.collection_id = collection?.id ?? null
@@ -132,12 +132,16 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
132
132
  })
133
133
  }
134
134
 
135
- recordProgressesTentative(contentProgresses: Map<number, number>, collection: { type: COLLECTION_TYPE; id: number } | null) {
136
- return this.upsertSomeTentative(
135
+ recordProgresses(
136
+ contentIds: number[],
137
+ collection: { type: COLLECTION_TYPE; id: number } | null,
138
+ progressPct: number
139
+ ) {
140
+ return this.upsertSome(
137
141
  Object.fromEntries(
138
- Array.from(contentProgresses, ([contentId, progressPct]) => [
139
- ProgressRepository.generateId(contentId, null),
140
- (r) => {
142
+ contentIds.map((contentId) => [
143
+ ProgressRepository.generateId(contentId, collection),
144
+ (r: ContentProgress) => {
141
145
  r.content_id = contentId
142
146
  r.collection_type = collection?.type ?? null
143
147
  r.collection_id = collection?.id ?? null
@@ -150,6 +154,29 @@ export default class ProgressRepository extends SyncRepository<ContentProgress>
150
154
  )
151
155
  }
152
156
 
157
+ recordProgressesTentative(
158
+ contentProgresses: Record<string, number>, // Accept plain object
159
+ collection: { type: COLLECTION_TYPE; id: number } | null
160
+ ) {
161
+ const data = Object.fromEntries(
162
+ Object.entries(contentProgresses).map(([contentId, progressPct]) => {
163
+ const generatedId = ProgressRepository.generateId(Number(contentId), collection)
164
+ console.log('Processing:', { contentId, progressPct, generatedId, collection })
165
+ return [
166
+ generatedId,
167
+ (record: ContentProgress) => {
168
+ record.content_id = Number(contentId)
169
+ record.collection_type = collection?.type ?? null
170
+ record.collection_id = collection?.id ?? null
171
+ record.state = progressPct === 100 ? STATE.COMPLETED : STATE.STARTED
172
+ record.progress_percent = progressPct
173
+ },
174
+ ]
175
+ })
176
+ )
177
+ return this.upsertSomeTentative(data)
178
+ }
179
+
153
180
  eraseProgress(contentId: number, collection: { type: COLLECTION_TYPE; id: number } | null) {
154
181
  return this.deleteOne(ProgressRepository.generateId(contentId, collection))
155
182
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -40,6 +40,7 @@ import dayjs from 'dayjs'
40
40
  import { addContextToContent } from './contentAggregator.js'
41
41
  import { getMethodCard } from './progress-row/method-card.js'
42
42
  import { db, Q } from './sync'
43
+ import {COLLECTION_TYPE} from "./sync/models/ContentProgress";
43
44
 
44
45
  const DATA_KEY_PRACTICES = 'practices'
45
46
 
@@ -1039,7 +1040,7 @@ export async function getProgressRows({ brand = 'drumeo', limit = 8 } = {}) {
1039
1040
  switch (item.type) {
1040
1041
  case 'playlist':
1041
1042
  return processPlaylistItem(item)
1042
- case 'learning-path-v2':
1043
+ case COLLECTION_TYPE.LEARNING_PATH:
1043
1044
  case 'method':
1044
1045
  return item
1045
1046
  default:
@@ -1282,7 +1283,7 @@ function mergeAndSortItems(items, limit) {
1282
1283
 
1283
1284
  export function findIncompleteLesson(progressOnItems, currentContentId, contentType) {
1284
1285
  const ids = Object.keys(progressOnItems).map(Number)
1285
- if (contentType === 'guided-course' || contentType === 'learning-path-v2') {
1286
+ if (contentType === 'guided-course' || contentType === COLLECTION_TYPE.LEARNING_PATH) {
1286
1287
  // Return first incomplete lesson
1287
1288
  return ids.find((id) => progressOnItems[id] !== 'completed') || ids.at(0)
1288
1289
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
package/test/log.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Tests for Reporting Service
3
+ *
4
+ * Note: These tests use mocked HTTP calls. For integration tests,
5
+ * ensure the backend API is running at the configured baseUrl.
6
+ */
7
+
8
+ import { initializeTestService } from './initializeTests.js'
9
+ import {
10
+ reportContent,
11
+ reportForumPost,
12
+ submitReport
13
+ } from '../src/services/reporting/reporting.ts'
14
+
15
+ // Mock HttpClient to avoid actual API calls in tests
16
+ jest.mock('../src/infrastructure/http/HttpClient', () => {
17
+ return {
18
+ HttpClient: jest.fn().mockImplementation(() => ({
19
+ post: jest.fn().mockResolvedValue({
20
+ report_id: 12345,
21
+ message: 'Report submitted successfully',
22
+ is_duplicate: false
23
+ })
24
+ }))
25
+ }
26
+ })
27
+
28
+ describe('Reporting Service', () => {
29
+ beforeEach(() => {
30
+ initializeTestService()
31
+ })
32
+
33
+ describe('reportContent', () => {
34
+ test('should submit a content report with video_not_working category', async () => {
35
+ const result = await reportContent({
36
+ contentId: 123,
37
+ category: 'video_not_working',
38
+ description: 'Video freezes at 2:30'
39
+ })
40
+
41
+ expect(result).toBeDefined()
42
+ expect(result.report_id).toBe(12345)
43
+ expect(result.message).toBe('Report submitted successfully')
44
+ expect(result.is_duplicate).toBe(false)
45
+ })
46
+
47
+ test('should submit a content report without description', async () => {
48
+ const result = await reportContent({
49
+ contentId: 456,
50
+ category: 'incorrect_content'
51
+ })
52
+
53
+ expect(result).toBeDefined()
54
+ expect(result.report_id).toBeDefined()
55
+ })
56
+
57
+ test('should submit a content report with other category and description', async () => {
58
+ const result = await reportContent({
59
+ contentId: 789,
60
+ category: 'other',
61
+ description: 'Audio quality is very poor'
62
+ })
63
+
64
+ expect(result).toBeDefined()
65
+ expect(result.report_id).toBeDefined()
66
+ expect(result.message).toBeDefined()
67
+ })
68
+ })
69
+
70
+ describe('reportForumPost', () => {
71
+ test('should submit a forum post report with required brand', async () => {
72
+ const result = await reportForumPost({
73
+ postId: 555,
74
+ brand: 'drumeo',
75
+ category: 'spam'
76
+ })
77
+
78
+ expect(result).toBeDefined()
79
+ expect(result.report_id).toBeDefined()
80
+ })
81
+
82
+ test('should submit a forum post report with description', async () => {
83
+ const result = await reportForumPost({
84
+ postId: 666,
85
+ brand: 'pianote',
86
+ category: 'harassment',
87
+ description: 'User is harassing other members'
88
+ })
89
+
90
+ expect(result).toBeDefined()
91
+ expect(result.report_id).toBeDefined()
92
+ expect(result.message).toBeDefined()
93
+ })
94
+ })
95
+
96
+ describe('submitReport', () => {
97
+ test('should submit a generic report for content', async () => {
98
+ const result = await submitReport({
99
+ reportableType: 'content',
100
+ reportableId: 999,
101
+ category: 'technical_issue',
102
+ description: 'Page not loading'
103
+ })
104
+
105
+ expect(result).toBeDefined()
106
+ expect(result.report_id).toBeDefined()
107
+ })
108
+
109
+ test('should submit a generic report for playlist', async () => {
110
+ const result = await submitReport({
111
+ reportableType: 'playlist',
112
+ reportableId: 777,
113
+ category: 'incorrect_content'
114
+ })
115
+
116
+ expect(result).toBeDefined()
117
+ expect(result.report_id).toBeDefined()
118
+ })
119
+
120
+ test('should submit a generic report for comment', async () => {
121
+ const result = await submitReport({
122
+ reportableType: 'comment',
123
+ reportableId: 888,
124
+ category: 'inappropriate',
125
+ description: 'Comment contains offensive language'
126
+ })
127
+
128
+ expect(result).toBeDefined()
129
+ expect(result.report_id).toBeDefined()
130
+ })
131
+ })
132
+ })
File without changes
File without changes
File without changes
File without changes
File without changes