@memberjunction/server 6.2.0-edge.1 → 6.2.0-edge.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.
Files changed (220) hide show
  1. package/README.md +30 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/config.d.ts +2189 -71
  4. package/dist/config.d.ts.map +1 -1
  5. package/dist/config.js +123 -3
  6. package/dist/config.js.map +1 -1
  7. package/dist/context.d.ts.map +1 -1
  8. package/dist/context.js +11 -23
  9. package/dist/context.js.map +1 -1
  10. package/dist/generated/graphql-schemas/__mj.d.ts +2143 -205
  11. package/dist/generated/graphql-schemas/__mj.d.ts.map +1 -1
  12. package/dist/generated/graphql-schemas/__mj.js +24441 -14433
  13. package/dist/generated/graphql-schemas/__mj.js.map +1 -1
  14. package/dist/generic/FireAndForgetHeartbeat.d.ts +15 -1
  15. package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -1
  16. package/dist/generic/FireAndForgetHeartbeat.js +16 -2
  17. package/dist/generic/FireAndForgetHeartbeat.js.map +1 -1
  18. package/dist/generic/OrphanedConversationDetailReconciler.d.ts +31 -0
  19. package/dist/generic/OrphanedConversationDetailReconciler.d.ts.map +1 -0
  20. package/dist/generic/OrphanedConversationDetailReconciler.js +215 -0
  21. package/dist/generic/OrphanedConversationDetailReconciler.js.map +1 -0
  22. package/dist/generic/PushStatusResolver.d.ts +38 -0
  23. package/dist/generic/PushStatusResolver.d.ts.map +1 -1
  24. package/dist/generic/PushStatusResolver.js +84 -1
  25. package/dist/generic/PushStatusResolver.js.map +1 -1
  26. package/dist/generic/ResolverBase.d.ts +56 -4
  27. package/dist/generic/ResolverBase.d.ts.map +1 -1
  28. package/dist/generic/ResolverBase.js +191 -74
  29. package/dist/generic/ResolverBase.js.map +1 -1
  30. package/dist/generic/RunViewResolver.d.ts +4 -0
  31. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  32. package/dist/generic/RunViewResolver.js +33 -1
  33. package/dist/generic/RunViewResolver.js.map +1 -1
  34. package/dist/index.d.ts +4 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +204 -50
  37. package/dist/index.js.map +1 -1
  38. package/dist/postgresqlCompat.d.ts +8 -0
  39. package/dist/postgresqlCompat.d.ts.map +1 -0
  40. package/dist/postgresqlCompat.js +19 -0
  41. package/dist/postgresqlCompat.js.map +1 -0
  42. package/dist/postgresqlPoolSettings.d.ts +108 -0
  43. package/dist/postgresqlPoolSettings.d.ts.map +1 -0
  44. package/dist/postgresqlPoolSettings.js +134 -0
  45. package/dist/postgresqlPoolSettings.js.map +1 -0
  46. package/dist/resolvers/AdhocQueryResolver.d.ts +23 -18
  47. package/dist/resolvers/AdhocQueryResolver.d.ts.map +1 -1
  48. package/dist/resolvers/AdhocQueryResolver.js +77 -167
  49. package/dist/resolvers/AdhocQueryResolver.js.map +1 -1
  50. package/dist/resolvers/AgentRunStatusPublisher.d.ts +55 -0
  51. package/dist/resolvers/AgentRunStatusPublisher.d.ts.map +1 -0
  52. package/dist/resolvers/AgentRunStatusPublisher.js +141 -0
  53. package/dist/resolvers/AgentRunStatusPublisher.js.map +1 -0
  54. package/dist/resolvers/ConversationRunEventsResolver.d.ts +90 -0
  55. package/dist/resolvers/ConversationRunEventsResolver.d.ts.map +1 -0
  56. package/dist/resolvers/ConversationRunEventsResolver.js +283 -0
  57. package/dist/resolvers/ConversationRunEventsResolver.js.map +1 -0
  58. package/dist/resolvers/DuplicateEntryCheckResolver.d.ts +98 -0
  59. package/dist/resolvers/DuplicateEntryCheckResolver.d.ts.map +1 -0
  60. package/dist/resolvers/DuplicateEntryCheckResolver.js +227 -0
  61. package/dist/resolvers/DuplicateEntryCheckResolver.js.map +1 -0
  62. package/dist/resolvers/EntityRecordNameResolver.d.ts +7 -23
  63. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  64. package/dist/resolvers/EntityRecordNameResolver.js +9 -47
  65. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  66. package/dist/resolvers/FetchEntityVectorsResolver.d.ts.map +1 -1
  67. package/dist/resolvers/FetchEntityVectorsResolver.js +2 -1
  68. package/dist/resolvers/FetchEntityVectorsResolver.js.map +1 -1
  69. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  70. package/dist/resolvers/GetDataResolver.js +9 -0
  71. package/dist/resolvers/GetDataResolver.js.map +1 -1
  72. package/dist/resolvers/HumanHandoffResolver.d.ts +107 -0
  73. package/dist/resolvers/HumanHandoffResolver.d.ts.map +1 -0
  74. package/dist/resolvers/HumanHandoffResolver.js +324 -0
  75. package/dist/resolvers/HumanHandoffResolver.js.map +1 -0
  76. package/dist/resolvers/MeetingResolver.d.ts +69 -0
  77. package/dist/resolvers/MeetingResolver.d.ts.map +1 -0
  78. package/dist/resolvers/MeetingResolver.js +381 -0
  79. package/dist/resolvers/MeetingResolver.js.map +1 -0
  80. package/dist/resolvers/MergeRecordsResolver.d.ts +7 -0
  81. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  82. package/dist/resolvers/MergeRecordsResolver.js +16 -2
  83. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  84. package/dist/resolvers/RealtimeBridgeResolver.d.ts +34 -1
  85. package/dist/resolvers/RealtimeBridgeResolver.d.ts.map +1 -1
  86. package/dist/resolvers/RealtimeBridgeResolver.js +202 -7
  87. package/dist/resolvers/RealtimeBridgeResolver.js.map +1 -1
  88. package/dist/resolvers/RealtimeClientSessionResolver.d.ts +45 -89
  89. package/dist/resolvers/RealtimeClientSessionResolver.d.ts.map +1 -1
  90. package/dist/resolvers/RealtimeClientSessionResolver.js +94 -240
  91. package/dist/resolvers/RealtimeClientSessionResolver.js.map +1 -1
  92. package/dist/resolvers/RunAIAgentResolver.d.ts +0 -16
  93. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  94. package/dist/resolvers/RunAIAgentResolver.js +23 -153
  95. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  96. package/dist/resolvers/RunDecisionResolver.d.ts +149 -0
  97. package/dist/resolvers/RunDecisionResolver.d.ts.map +1 -0
  98. package/dist/resolvers/RunDecisionResolver.js +396 -0
  99. package/dist/resolvers/RunDecisionResolver.js.map +1 -0
  100. package/dist/resolvers/VectorizeEntityResolver.d.ts +1 -1
  101. package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
  102. package/dist/resolvers/VectorizeEntityResolver.js +14 -10
  103. package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
  104. package/dist/resolvers/adhocTimeout.d.ts +7 -0
  105. package/dist/resolvers/adhocTimeout.d.ts.map +1 -0
  106. package/dist/resolvers/adhocTimeout.js +12 -0
  107. package/dist/resolvers/adhocTimeout.js.map +1 -0
  108. package/dist/resolvers/meetings/index.d.ts +5 -0
  109. package/dist/resolvers/meetings/index.d.ts.map +1 -0
  110. package/dist/resolvers/meetings/index.js +5 -0
  111. package/dist/resolvers/meetings/index.js.map +1 -0
  112. package/dist/resolvers/meetings/meetingHelpers.d.ts +17 -0
  113. package/dist/resolvers/meetings/meetingHelpers.d.ts.map +1 -0
  114. package/dist/resolvers/meetings/meetingHelpers.js +212 -0
  115. package/dist/resolvers/meetings/meetingHelpers.js.map +1 -0
  116. package/dist/resolvers/meetings/meetingMutations.d.ts +12 -0
  117. package/dist/resolvers/meetings/meetingMutations.d.ts.map +1 -0
  118. package/dist/resolvers/meetings/meetingMutations.js +418 -0
  119. package/dist/resolvers/meetings/meetingMutations.js.map +1 -0
  120. package/dist/resolvers/meetings/meetingQueries.d.ts +7 -0
  121. package/dist/resolvers/meetings/meetingQueries.d.ts.map +1 -0
  122. package/dist/resolvers/meetings/meetingQueries.js +113 -0
  123. package/dist/resolvers/meetings/meetingQueries.js.map +1 -0
  124. package/dist/resolvers/meetings/meetingTypes.d.ts +89 -0
  125. package/dist/resolvers/meetings/meetingTypes.d.ts.map +1 -0
  126. package/dist/resolvers/meetings/meetingTypes.js +345 -0
  127. package/dist/resolvers/meetings/meetingTypes.js.map +1 -0
  128. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  129. package/dist/rest/ViewOperationsHandler.js +8 -5
  130. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  131. package/dist/services/TaskGraphActionRunner.d.ts +8 -0
  132. package/dist/services/TaskGraphActionRunner.d.ts.map +1 -1
  133. package/dist/services/TaskGraphActionRunner.js +27 -1
  134. package/dist/services/TaskGraphActionRunner.js.map +1 -1
  135. package/dist/services/WorkQueueHostService.d.ts +36 -0
  136. package/dist/services/WorkQueueHostService.d.ts.map +1 -0
  137. package/dist/services/WorkQueueHostService.js +97 -0
  138. package/dist/services/WorkQueueHostService.js.map +1 -0
  139. package/dist/services/workQueueConfig.d.ts +103 -0
  140. package/dist/services/workQueueConfig.d.ts.map +1 -0
  141. package/dist/services/workQueueConfig.js +48 -0
  142. package/dist/services/workQueueConfig.js.map +1 -0
  143. package/dist/types.d.ts +5 -0
  144. package/dist/types.d.ts.map +1 -1
  145. package/dist/types.js.map +1 -1
  146. package/package.json +95 -92
  147. package/src/__tests__/AdhocQueryResolver.bugs.test.ts +14 -35
  148. package/src/__tests__/AdhocQueryResolver.pagination.test.ts +6 -2
  149. package/src/__tests__/AdhocQueryResolver.scopeGuard.test.ts +3 -3
  150. package/src/__tests__/AgentRunStatusPublisher.test.ts +175 -0
  151. package/src/__tests__/DuplicateEntryCheckResolver.test.ts +300 -0
  152. package/src/__tests__/EntityRecordNameResolver.fieldSecurity.test.ts +27 -72
  153. package/src/__tests__/HumanHandoffResolver.test.ts +233 -0
  154. package/src/__tests__/MeetingResolver.test.ts +374 -0
  155. package/src/__tests__/RealtimeBridgeResolver.test.ts +240 -2
  156. package/src/__tests__/RealtimeClientSessionResolver.test.ts +216 -2
  157. package/src/__tests__/RemoteBrowserActionRecovery.test.ts +7 -1
  158. package/src/__tests__/RemoteBrowserAudioStream.test.ts +7 -1
  159. package/src/__tests__/RemoteBrowserSnapshot.test.ts +7 -1
  160. package/src/__tests__/ResolverBase.adhocReadScope.test.ts +96 -0
  161. package/src/__tests__/ResolverBase.clientOldValues.test.ts +52 -0
  162. package/src/__tests__/ResolverBase.filterEscaping.test.ts +21 -2
  163. package/src/__tests__/ResolverBase.includeBinaryFields.test.ts +291 -0
  164. package/src/__tests__/RunAIAgentResolver.historyFrom.test.ts +182 -12
  165. package/src/__tests__/RunAIAgentResolver.streaming.test.ts +18 -27
  166. package/src/__tests__/RunDecisionResolver.test.ts +699 -0
  167. package/src/__tests__/TaskGraphActionRunner.durablePayload.test.ts +43 -0
  168. package/src/__tests__/VectorizeEntityResolver.test.ts +106 -0
  169. package/src/__tests__/WorkQueueHostService.test.ts +133 -0
  170. package/src/__tests__/adhocTimeoutClamp.test.ts +26 -0
  171. package/src/__tests__/conversationRunEvents.test.ts +399 -0
  172. package/src/__tests__/orphanedConversationDetailReconciler.test.ts +283 -0
  173. package/src/__tests__/postgresqlPoolSettings.test.ts +131 -0
  174. package/src/__tests__/pushStatusFanOut.test.ts +206 -0
  175. package/src/__tests__/translateBracketsToPG.test.ts +20 -0
  176. package/src/auth/__tests__/backward-compatibility.test.ts +1 -0
  177. package/src/config.ts +130 -3
  178. package/src/context.ts +11 -24
  179. package/src/generated/graphql-schemas/__mj.ts +24183 -17182
  180. package/src/generic/FireAndForgetHeartbeat.ts +16 -2
  181. package/src/generic/OrphanedConversationDetailReconciler.ts +267 -0
  182. package/src/generic/PushStatusResolver.ts +97 -1
  183. package/src/generic/ResolverBase.ts +220 -76
  184. package/src/generic/RunViewResolver.ts +33 -2
  185. package/src/index.ts +228 -51
  186. package/src/postgresqlCompat.ts +21 -0
  187. package/src/postgresqlPoolSettings.ts +213 -0
  188. package/src/resolvers/AdhocQueryResolver.ts +88 -188
  189. package/src/resolvers/AgentRunStatusPublisher.ts +175 -0
  190. package/src/resolvers/ConversationRunEventsResolver.ts +308 -0
  191. package/src/resolvers/DuplicateEntryCheckResolver.ts +232 -0
  192. package/src/resolvers/EntityRecordNameResolver.ts +9 -51
  193. package/src/resolvers/FetchEntityVectorsResolver.ts +2 -1
  194. package/src/resolvers/GetDataResolver.ts +10 -0
  195. package/src/resolvers/HumanHandoffResolver.ts +304 -0
  196. package/src/resolvers/MeetingResolver.ts +438 -0
  197. package/src/resolvers/MergeRecordsResolver.ts +14 -2
  198. package/src/resolvers/RealtimeBridgeResolver.ts +227 -13
  199. package/src/resolvers/RealtimeClientSessionResolver.ts +99 -287
  200. package/src/resolvers/RunAIAgentResolver.ts +28 -174
  201. package/src/resolvers/RunDecisionResolver.ts +435 -0
  202. package/src/resolvers/VectorizeEntityResolver.ts +16 -11
  203. package/src/resolvers/__tests__/MergeRecordsResolver.test.ts +93 -0
  204. package/src/resolvers/adhocTimeout.ts +12 -0
  205. package/src/resolvers/meetings/index.ts +4 -0
  206. package/src/resolvers/meetings/meetingHelpers.ts +307 -0
  207. package/src/resolvers/meetings/meetingMutations.ts +535 -0
  208. package/src/resolvers/meetings/meetingQueries.ts +172 -0
  209. package/src/resolvers/meetings/meetingTypes.ts +238 -0
  210. package/src/rest/ViewOperationsHandler.ts +8 -5
  211. package/src/services/TaskGraphActionRunner.ts +29 -1
  212. package/src/services/WorkQueueHostService.ts +129 -0
  213. package/src/services/workQueueConfig.ts +52 -0
  214. package/src/types.ts +5 -0
  215. package/dist/resolvers/adhoc-query-helpers.d.ts +0 -33
  216. package/dist/resolvers/adhoc-query-helpers.d.ts.map +0 -1
  217. package/dist/resolvers/adhoc-query-helpers.js +0 -50
  218. package/dist/resolvers/adhoc-query-helpers.js.map +0 -1
  219. package/src/__tests__/adhoc-query-helpers.test.ts +0 -50
  220. package/src/resolvers/adhoc-query-helpers.ts +0 -67
@@ -0,0 +1,307 @@
1
+ import {
2
+ IMetadataProvider,
3
+ LogError,
4
+ RunView,
5
+ UserInfo,
6
+ } from '@memberjunction/core';
7
+ import { EscapeSQLString, UUIDsEqual } from '@memberjunction/global';
8
+ import type {
9
+ MJMeetingEntity,
10
+ MJMeetingParticipantEntity,
11
+ MJPhoneNumberEntity,
12
+ MJInteractionEntity,
13
+ MJInteractionEventEntity,
14
+ } from '@memberjunction/core-entities';
15
+ import { MeetingDialInService } from '@memberjunction/livekit-room-server';
16
+ import { NotificationEngine } from '@memberjunction/notifications';
17
+ import { MeetingParticipantType, MeetingType } from './meetingTypes.js';
18
+
19
+ export async function LoadMeetingEntity(
20
+ id: string,
21
+ user: UserInfo,
22
+ provider: IMetadataProvider,
23
+ ): Promise<MJMeetingEntity | null> {
24
+ const rv = provider ? RunView.FromMetadataProvider(provider) : new RunView();
25
+ const escapedID = EscapeSQLString(id.trim());
26
+ const res = await rv.RunView<MJMeetingEntity>(
27
+ {
28
+ EntityName: 'MJ: Meetings',
29
+ ExtraFilter: `ID = '${escapedID}'`,
30
+ ResultType: 'entity_object',
31
+ },
32
+ user,
33
+ );
34
+ if (res.Success && res.Results && res.Results.length > 0) {
35
+ return res.Results[0];
36
+ }
37
+ return null;
38
+ }
39
+
40
+ export async function LoadParticipants(
41
+ meetingID: string,
42
+ user: UserInfo,
43
+ provider: IMetadataProvider,
44
+ ): Promise<MeetingParticipantType[]> {
45
+ const rv = provider ? RunView.FromMetadataProvider(provider) : new RunView();
46
+ const escapedID = EscapeSQLString(meetingID.trim());
47
+ const res = await rv.RunView<MJMeetingParticipantEntity>(
48
+ {
49
+ EntityName: 'MJ: Meeting Participants',
50
+ ExtraFilter: `MeetingID = '${escapedID}'`,
51
+ ResultType: 'entity_object',
52
+ },
53
+ user,
54
+ );
55
+ if (!res.Success || !res.Results) {
56
+ return [];
57
+ }
58
+ return res.Results.map((p) => MapParticipantToType(p));
59
+ }
60
+
61
+ export async function IsUserHostOrCoHost(
62
+ meeting: MJMeetingEntity,
63
+ user: UserInfo,
64
+ provider: IMetadataProvider,
65
+ ): Promise<boolean> {
66
+ if (UUIDsEqual(meeting.HostUserID, user.ID)) {
67
+ return true;
68
+ }
69
+ const rv = provider ? RunView.FromMetadataProvider(provider) : new RunView();
70
+ const escapedID = EscapeSQLString(meeting.ID);
71
+ const escapedUserID = EscapeSQLString(user.ID);
72
+ const res = await rv.RunView<MJMeetingParticipantEntity>(
73
+ {
74
+ EntityName: 'MJ: Meeting Participants',
75
+ ExtraFilter: `MeetingID = '${escapedID}' AND UserID = '${escapedUserID}' AND Role IN ('Host', 'CoHost')`,
76
+ ResultType: 'entity_object',
77
+ },
78
+ user,
79
+ );
80
+ return Boolean(res.Success && res.Results && res.Results.length > 0);
81
+ }
82
+
83
+ export async function ResolveDefaultPhoneNumber(
84
+ user: UserInfo,
85
+ provider: IMetadataProvider,
86
+ ): Promise<MJPhoneNumberEntity | null> {
87
+ const rv = provider ? RunView.FromMetadataProvider(provider) : new RunView();
88
+ const res = await rv.RunView<MJPhoneNumberEntity>(
89
+ {
90
+ EntityName: 'MJ: Phone Numbers',
91
+ ExtraFilter: "Status = 'Active'",
92
+ OrderBy: '__mj_CreatedAt ASC',
93
+ ResultType: 'entity_object',
94
+ },
95
+ user,
96
+ );
97
+ if (res.Success && res.Results && res.Results.length > 0) {
98
+ return res.Results[0];
99
+ }
100
+ return null;
101
+ }
102
+
103
+ export async function GenerateUniqueDialInCode(
104
+ phoneNumberID: string,
105
+ user: UserInfo,
106
+ provider: IMetadataProvider,
107
+ ): Promise<string | undefined> {
108
+ return MeetingDialInService.Instance.GenerateUniqueDialInCode(phoneNumberID, user, provider);
109
+ }
110
+
111
+ export async function EnsureMeetingInteraction(
112
+ meeting: MJMeetingEntity,
113
+ user: UserInfo,
114
+ provider: IMetadataProvider,
115
+ ): Promise<MJInteractionEntity | null> {
116
+ try {
117
+ const rv = provider ? RunView.FromMetadataProvider(provider) : new RunView();
118
+ const escapedRoom = EscapeSQLString(meeting.RoomName);
119
+
120
+ const existing = await rv.RunView<MJInteractionEntity>(
121
+ {
122
+ EntityName: 'MJ: Interactions',
123
+ ExtraFilter: `RoomName = '${escapedRoom}'`,
124
+ ResultType: 'entity_object',
125
+ },
126
+ user,
127
+ );
128
+
129
+ if (existing.Success && existing.Results && existing.Results.length > 0) {
130
+ return existing.Results[0];
131
+ }
132
+
133
+ const entity = await provider.GetEntityObject<MJInteractionEntity>('MJ: Interactions', user);
134
+ entity.Channel = 'Meeting';
135
+ entity.Direction = 'Inbound';
136
+ entity.Status = 'Active';
137
+ entity.RoomName = meeting.RoomName;
138
+ entity.StartedAt = new Date();
139
+ entity.AnsweredAt = new Date();
140
+ entity.RecordingEnabled = meeting.RecordingPolicy === 'Automatic' || meeting.RecordingPolicy === 'Allowed';
141
+
142
+ if (await entity.Save()) {
143
+ await RecordInteractionEvent(entity.ID, 'Created', user, provider, { MeetingID: meeting.ID, RoomName: meeting.RoomName });
144
+ await RecordInteractionEvent(entity.ID, 'Answered', user, provider);
145
+ return entity;
146
+ }
147
+ return null;
148
+ } catch (err) {
149
+ LogError(`[MeetingResolver] ensureMeetingInteraction failed: ${err instanceof Error ? err.message : String(err)}`);
150
+ return null;
151
+ }
152
+ }
153
+
154
+ export async function RecordInteractionEvent(
155
+ interactionID: string,
156
+ eventType: MJInteractionEventEntity['EventType'],
157
+ user: UserInfo,
158
+ provider: IMetadataProvider,
159
+ details?: Record<string, unknown>,
160
+ ): Promise<void> {
161
+ try {
162
+ const eventEntity = await provider.GetEntityObject<MJInteractionEventEntity>('MJ: Interaction Events', user);
163
+ eventEntity.InteractionID = interactionID;
164
+ eventEntity.EventType = eventType;
165
+ eventEntity.ActorUserID = user.ID;
166
+ eventEntity.OccurredAt = new Date();
167
+ if (details) {
168
+ eventEntity.Details = JSON.stringify(details);
169
+ }
170
+ await eventEntity.Save();
171
+ } catch (err) {
172
+ LogError(`[MeetingResolver] recordInteractionEvent (${eventType}) failed: ${err instanceof Error ? err.message : String(err)}`);
173
+ }
174
+ }
175
+
176
+ export async function CloseMeetingInteraction(
177
+ roomName: string,
178
+ user: UserInfo,
179
+ provider: IMetadataProvider,
180
+ ): Promise<void> {
181
+ try {
182
+ const rv = provider ? RunView.FromMetadataProvider(provider) : new RunView();
183
+ const escapedRoom = EscapeSQLString(roomName);
184
+
185
+ const res = await rv.RunView<MJInteractionEntity>(
186
+ {
187
+ EntityName: 'MJ: Interactions',
188
+ ExtraFilter: `RoomName = '${escapedRoom}' AND Status = 'Active'`,
189
+ ResultType: 'entity_object',
190
+ },
191
+ user,
192
+ );
193
+
194
+ if (res.Success && res.Results && res.Results.length > 0) {
195
+ const interaction = res.Results[0];
196
+ interaction.Status = 'Ended';
197
+ interaction.EndedAt = new Date();
198
+ await interaction.Save();
199
+ await RecordInteractionEvent(interaction.ID, 'Ended', user, provider);
200
+ }
201
+ } catch (err) {
202
+ LogError(`[MeetingResolver] closeMeetingInteraction failed: ${err instanceof Error ? err.message : String(err)}`);
203
+ }
204
+ }
205
+
206
+ export async function SendMeetingInviteNotification(
207
+ meeting: MJMeetingEntity,
208
+ targetUserID: string,
209
+ contextUser: UserInfo,
210
+ provider: IMetadataProvider,
211
+ ): Promise<void> {
212
+ try {
213
+ await NotificationEngine.Instance.Config(false, contextUser, provider);
214
+ const inviter = contextUser.Name?.trim() || contextUser.Email || 'Someone';
215
+ await NotificationEngine.Instance.SendNotification(
216
+ {
217
+ userId: targetUserID,
218
+ typeNameOrId: 'Meeting Invitation',
219
+ title: `Invitation: ${meeting.Title}`,
220
+ message: `${inviter} invited you to the meeting "${meeting.Title}".`,
221
+ resourceConfiguration: { type: 'meeting', meetingID: meeting.ID, roomName: meeting.RoomName },
222
+ },
223
+ contextUser,
224
+ );
225
+ } catch (err) {
226
+ LogError(`[MeetingResolver] sendMeetingInviteNotification failed for user ${targetUserID}: ${err instanceof Error ? err.message : String(err)}`);
227
+ }
228
+ }
229
+
230
+ export function NormalizeParticipantRole(
231
+ roleInput?: string,
232
+ defaultRole: MJMeetingParticipantEntity['Role'] = 'Attendee',
233
+ ): MJMeetingParticipantEntity['Role'] {
234
+ switch (roleInput?.trim().toLowerCase()) {
235
+ case 'host':
236
+ return 'Host';
237
+ case 'cohost':
238
+ return 'CoHost';
239
+ case 'agent':
240
+ return 'Agent';
241
+ case 'attendee':
242
+ default:
243
+ return defaultRole;
244
+ }
245
+ }
246
+
247
+ export function NormalizeRecordingPolicy(
248
+ policyInput?: string,
249
+ ): MJMeetingEntity['RecordingPolicy'] {
250
+ switch (policyInput?.trim().toLowerCase()) {
251
+ case 'allowed':
252
+ return 'Allowed';
253
+ case 'automatic':
254
+ return 'Automatic';
255
+ case 'off':
256
+ default:
257
+ return 'Off';
258
+ }
259
+ }
260
+
261
+ export function MapMeetingToType(m: MJMeetingEntity, participants?: MeetingParticipantType[]): MeetingType {
262
+ return {
263
+ ID: m.ID,
264
+ Title: m.Title,
265
+ Description: m.Description ?? undefined,
266
+ HostUserID: m.HostUserID,
267
+ HostUserName: m.HostUser ?? undefined,
268
+ RoomName: m.RoomName,
269
+ Status: m.Status,
270
+ ScheduledStartAt: m.ScheduledStartAt ?? undefined,
271
+ ScheduledEndAt: m.ScheduledEndAt ?? undefined,
272
+ StartedAt: m.StartedAt ?? undefined,
273
+ EndedAt: m.EndedAt ?? undefined,
274
+ AllowPhoneDialIn: m.AllowPhoneDialIn,
275
+ DialInPhoneNumberID: m.DialInPhoneNumberID ?? undefined,
276
+ DialInPhoneNumber: m.DialInPhoneNumber ?? undefined,
277
+ DialInCode: m.DialInCode ?? undefined,
278
+ RecordingPolicy: m.RecordingPolicy,
279
+ ConversationID: m.ConversationID ?? undefined,
280
+ Participants: participants,
281
+ CreatedAt: m.__mj_CreatedAt,
282
+ UpdatedAt: m.__mj_UpdatedAt,
283
+ };
284
+ }
285
+
286
+ export function MapParticipantToType(
287
+ p: MJMeetingParticipantEntity,
288
+ userNameFallback?: string | null,
289
+ userEmailFallback?: string | null,
290
+ ): MeetingParticipantType {
291
+ return {
292
+ ID: p.ID,
293
+ MeetingID: p.MeetingID,
294
+ UserID: p.UserID ?? undefined,
295
+ UserName: p.User ?? userNameFallback ?? undefined,
296
+ UserEmail: userEmailFallback ?? undefined,
297
+ AgentID: p.AgentID ?? undefined,
298
+ AgentName: p.Agent ?? undefined,
299
+ ExternalName: p.ExternalName ?? undefined,
300
+ ExternalEmail: p.ExternalEmail ?? undefined,
301
+ ExternalPhone: p.ExternalPhone ?? undefined,
302
+ Role: p.Role,
303
+ InviteStatus: p.InviteStatus,
304
+ JoinedAt: p.JoinedAt ?? undefined,
305
+ LeftAt: p.LeftAt ?? undefined,
306
+ };
307
+ }