@messenger-box/platform-server 10.0.3-alpha.7 → 10.0.3-alpha.72

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 (149) hide show
  1. package/lib/containers/containers.js +4 -1
  2. package/lib/containers/containers.js.map +1 -1
  3. package/lib/containers/context-services-from-container.d.ts +1 -1
  4. package/lib/containers/context-services-from-container.js +1 -1
  5. package/lib/containers/context-services-from-container.js.map +1 -1
  6. package/lib/graphql/resolvers/channel-member.d.ts +3 -2
  7. package/lib/graphql/resolvers/channel-member.js +30 -5
  8. package/lib/graphql/resolvers/channel-member.js.map +1 -1
  9. package/lib/graphql/resolvers/channel.d.ts +3 -2
  10. package/lib/graphql/resolvers/channel.js +279 -53
  11. package/lib/graphql/resolvers/channel.js.map +1 -1
  12. package/lib/graphql/resolvers/extended-token-account.d.ts +3 -2
  13. package/lib/graphql/resolvers/extended-token-account.js +90 -23
  14. package/lib/graphql/resolvers/extended-token-account.js.map +1 -1
  15. package/lib/graphql/resolvers/index.d.ts +1 -1
  16. package/lib/graphql/resolvers/post-thread.d.ts +1 -1
  17. package/lib/graphql/resolvers/post-thread.js +294 -132
  18. package/lib/graphql/resolvers/post-thread.js.map +1 -1
  19. package/lib/graphql/resolvers/post.d.ts +2 -3
  20. package/lib/graphql/resolvers/post.js +696 -234
  21. package/lib/graphql/resolvers/post.js.map +1 -1
  22. package/lib/graphql/resolvers/reaction.d.ts +3 -2
  23. package/lib/graphql/resolvers/reaction.js +96 -14
  24. package/lib/graphql/resolvers/reaction.js.map +1 -1
  25. package/lib/graphql/schema/channel-member.graphql +110 -21
  26. package/lib/graphql/schema/channel-member.graphql.js +1 -1
  27. package/lib/graphql/schema/channel.graphql +337 -38
  28. package/lib/graphql/schema/channel.graphql.js +1 -1
  29. package/lib/graphql/schema/post-thread.graphql +167 -21
  30. package/lib/graphql/schema/post-thread.graphql.js +1 -1
  31. package/lib/graphql/schema/post.graphql +284 -40
  32. package/lib/graphql/schema/post.graphql.js +1 -1
  33. package/lib/graphql/schema/reaction.graphql +71 -13
  34. package/lib/graphql/schema/reaction.graphql.js +1 -1
  35. package/lib/graphql/schema/services.graphql +2 -0
  36. package/lib/graphql/schema/users.graphql +76 -13
  37. package/lib/graphql/schema/users.graphql.js +1 -1
  38. package/lib/index.js +1 -1
  39. package/lib/index.js.map +1 -1
  40. package/lib/interfaces/index.d.ts +0 -1
  41. package/lib/interfaces/services.d.ts +1 -1
  42. package/lib/migrations/dbMigrations/AddPostsConfigurationsMigration.d.ts +17 -0
  43. package/lib/migrations/dbMigrations/AddPostsConfigurationsMigration.js +44 -0
  44. package/lib/migrations/dbMigrations/AddPostsConfigurationsMigration.js.map +1 -0
  45. package/lib/migrations/dbMigrations/index.d.ts +1 -0
  46. package/lib/migrations/index.d.ts +1 -0
  47. package/lib/migrations/mail-template-migration.js +1 -1
  48. package/lib/migrations/message-notification-template-migration.d.ts +1 -1
  49. package/lib/migrations/message-notification-template-migration.js +1 -1
  50. package/lib/plugins/channel-moleculer-service.d.ts +21 -1
  51. package/lib/plugins/channel-moleculer-service.js +417 -115
  52. package/lib/plugins/channel-moleculer-service.js.map +1 -1
  53. package/lib/plugins/extended-token-account-moleculer-service.d.ts +25 -1
  54. package/lib/plugins/extended-token-account-moleculer-service.js +348 -22
  55. package/lib/plugins/extended-token-account-moleculer-service.js.map +1 -1
  56. package/lib/plugins/messenger-notification-moleculer-service.d.ts +27 -3
  57. package/lib/plugins/messenger-notification-moleculer-service.js +404 -58
  58. package/lib/plugins/messenger-notification-moleculer-service.js.map +1 -1
  59. package/lib/plugins/post-moleculer-service.d.ts +85 -21
  60. package/lib/plugins/post-moleculer-service.js +986 -256
  61. package/lib/plugins/post-moleculer-service.js.map +1 -1
  62. package/lib/plugins/post-thread-moleculer-service.d.ts +33 -1
  63. package/lib/plugins/post-thread-moleculer-service.js +326 -8
  64. package/lib/plugins/post-thread-moleculer-service.js.map +1 -1
  65. package/lib/plugins/reaction-moleculer-service.js +1 -1
  66. package/lib/plugins/reaction-moleculer-service.js.map +1 -1
  67. package/lib/preferences/settings/post-settings.d.ts +2 -0
  68. package/lib/preferences/settings/post-settings.js +47 -9
  69. package/lib/preferences/settings/post-settings.js.map +1 -1
  70. package/lib/services/channel-service.d.ts +179 -33
  71. package/lib/services/channel-service.js +821 -274
  72. package/lib/services/channel-service.js.map +1 -1
  73. package/lib/services/extended-token-account-service.d.ts +130 -14
  74. package/lib/services/extended-token-account-service.js +462 -52
  75. package/lib/services/extended-token-account-service.js.map +1 -1
  76. package/lib/services/index.d.ts +1 -0
  77. package/lib/services/messenger-notification-service.d.ts +106 -13
  78. package/lib/services/messenger-notification-service.js +824 -442
  79. package/lib/services/messenger-notification-service.js.map +1 -1
  80. package/lib/services/post-service.d.ts +182 -16
  81. package/lib/services/post-service.js +731 -115
  82. package/lib/services/post-service.js.map +1 -1
  83. package/lib/services/post-thread-service.d.ts +114 -5
  84. package/lib/services/post-thread-service.js +400 -13
  85. package/lib/services/post-thread-service.js.map +1 -1
  86. package/lib/services/proxy-services/channel-microservice.d.ts +5 -3
  87. package/lib/services/proxy-services/channel-microservice.js +19 -10
  88. package/lib/services/proxy-services/channel-microservice.js.map +1 -1
  89. package/lib/services/proxy-services/messenger-notification-microservice.d.ts +128 -8
  90. package/lib/services/proxy-services/messenger-notification-microservice.js +324 -29
  91. package/lib/services/proxy-services/messenger-notification-microservice.js.map +1 -1
  92. package/lib/services/proxy-services/post-microservice.d.ts +186 -12
  93. package/lib/services/proxy-services/post-microservice.js +543 -54
  94. package/lib/services/proxy-services/post-microservice.js.map +1 -1
  95. package/lib/services/proxy-services/post-thread-microservice.d.ts +134 -3
  96. package/lib/services/proxy-services/post-thread-microservice.js +388 -6
  97. package/lib/services/proxy-services/post-thread-microservice.js.map +1 -1
  98. package/lib/services/proxy-services/reaction-microservice.d.ts +161 -3
  99. package/lib/services/proxy-services/reaction-microservice.js +474 -2
  100. package/lib/services/proxy-services/reaction-microservice.js.map +1 -1
  101. package/lib/services/reaction-service.d.ts +124 -4
  102. package/lib/services/reaction-service.js +415 -3
  103. package/lib/services/reaction-service.js.map +1 -1
  104. package/lib/services/redis-cache-manager.d.ts +18 -0
  105. package/lib/services/redis-cache-manager.js +83 -0
  106. package/lib/services/redis-cache-manager.js.map +1 -0
  107. package/lib/store/models/account-token-store.d.ts +1 -1
  108. package/lib/store/models/account-token-store.js.map +1 -1
  109. package/lib/store/models/channel.d.ts +2 -3
  110. package/lib/store/models/channel.js +181 -72
  111. package/lib/store/models/channel.js.map +1 -1
  112. package/lib/store/models/post-thread.d.ts +3 -3
  113. package/lib/store/models/post-thread.js +96 -14
  114. package/lib/store/models/post-thread.js.map +1 -1
  115. package/lib/store/models/post.d.ts +2 -3
  116. package/lib/store/models/post.js +143 -23
  117. package/lib/store/models/post.js.map +1 -1
  118. package/lib/store/models/reaction.d.ts +2 -3
  119. package/lib/store/models/reaction.js +67 -8
  120. package/lib/store/models/reaction.js.map +1 -1
  121. package/lib/store/repositories/__tests__/__fixtures__/team-repository.d.ts +3 -3
  122. package/lib/store/repositories/channel-repository.d.ts +6 -6
  123. package/lib/store/repositories/channel-repository.js +5 -2
  124. package/lib/store/repositories/channel-repository.js.map +1 -1
  125. package/lib/store/repositories/post-repository.d.ts +6 -6
  126. package/lib/store/repositories/post-repository.js +5 -2
  127. package/lib/store/repositories/post-repository.js.map +1 -1
  128. package/lib/store/repositories/post-thread-repository.d.ts +6 -6
  129. package/lib/store/repositories/post-thread-repository.js +5 -2
  130. package/lib/store/repositories/post-thread-repository.js.map +1 -1
  131. package/lib/store/repositories/reaction-repository.d.ts +6 -6
  132. package/lib/store/repositories/reaction-repository.js +5 -2
  133. package/lib/store/repositories/reaction-repository.js.map +1 -1
  134. package/lib/templates/constants/SERVER_TYPES.ts.template +0 -3
  135. package/lib/templates/repositories/ChannelRepository.ts.template +3 -3
  136. package/lib/templates/repositories/PostRepository.ts.template +3 -3
  137. package/lib/templates/repositories/PostThreadRepository.ts.template +3 -3
  138. package/lib/templates/repositories/ReactionRepository.ts.template +3 -4
  139. package/lib/templates/services/ChannelService.ts.template +280 -39
  140. package/lib/templates/services/ExtendedTokenAccountService.ts.template +104 -9
  141. package/lib/templates/services/MessengerNotificationService.ts.template +94 -19
  142. package/lib/templates/services/PostService.ts.template +184 -20
  143. package/lib/templates/services/PostThreadService.ts.template +151 -6
  144. package/lib/templates/services/ReactionService.ts.template +129 -3
  145. package/lib/templates/services/RedisCacheManager.ts.template +22 -0
  146. package/package.json +6 -5
  147. package/lib/interfaces/context.d.ts +0 -14
  148. package/lib/store/models/common-options.js +0 -20
  149. package/lib/store/models/common-options.js.map +0 -1
@@ -1,76 +1,471 @@
1
- import {__decorate,__param,__metadata}from'tslib';import {omit}from'lodash-es';import {sub,compareAsc}from'date-fns';import fetch from'node-fetch';import {ServiceBroker}from'moleculer';import {injectable,inject}from'inversify';import {SERVER_TYPES,MoleculerServiceName,MailServiceAction,MailTemplateId,SmsServiceActions,AccountServiceAction,PostTypeEnum,RoomType}from'common';import {CommonType}from'@common-stack/core';import'@cdm-logger/core';import {config}from'../config/env-config.js';let MessengerNotificationService = class MessengerNotificationService {
1
+ import {__decorate,__param,__metadata}from'tslib';import {omit}from'lodash-es';import {sub,compareAsc}from'date-fns';import fetch from'node-fetch';import {ServiceBroker}from'moleculer';import {injectable,inject}from'inversify';import {SERVER_TYPES,MoleculerServiceName,MailServiceAction,MailTemplateId,SmsServiceActions,AccountServiceAction,PostTypeEnum,RoomType}from'common/server';import {CommonType}from'@common-stack/core';import'@cdm-logger/core';import {DisposableCollection}from'@adminide-stack/core';import {config}from'../config/env-config.js';/**
2
+ * Messenger Notification Service Implementation
3
+ *
4
+ * This service handles comprehensive notification management within the messenger platform,
5
+ * providing operations for sending various types of notifications including email, SMS,
6
+ * push notifications, and Expo notifications across different messaging contexts.
7
+ *
8
+ * Key capabilities:
9
+ * - Multi-channel notification delivery (email, SMS, push, Expo)
10
+ * - Unread message notification scheduling and management
11
+ * - User preference-based notification filtering
12
+ * - Support service notification handling
13
+ * - Alert message notification broadcasting
14
+ * - Thread-based notification management
15
+ * - Real-time push notification integration
16
+ * - Template-based notification customization
17
+ * - Channel and post-based notification routing
18
+ * - User aggregation and preference management
19
+ *
20
+ * The service integrates with multiple external services through Moleculer broker
21
+ * and provides comprehensive error handling and logging for notification workflows.
22
+ */
23
+ let MessengerNotificationService = class MessengerNotificationService {
2
24
  channelRepository;
3
25
  postRepository;
4
26
  postThreadRepository;
5
27
  countryRepository;
6
28
  broker;
7
- logger;
29
+ toDispose = new DisposableCollection();
8
30
  users = {};
9
31
  userPreferences = {};
10
32
  supportServiceenvConfig = config?.SUPPORT_SERVICE_SETTINGS ?? null;
33
+ logger;
11
34
  constructor(channelRepository, postRepository, postThreadRepository, countryRepository, broker, logger) {
12
35
  this.channelRepository = channelRepository;
13
36
  this.postRepository = postRepository;
14
37
  this.postThreadRepository = postThreadRepository;
15
38
  this.countryRepository = countryRepository;
16
39
  this.broker = broker;
17
- this.logger = logger;
40
+ this.logger = logger?.child({
41
+ className: 'MessengerNotificationService'
42
+ });
43
+ }
44
+ /**
45
+ * Disposes of resources used by the service
46
+ */
47
+ dispose() {
48
+ this.toDispose.dispose();
18
49
  }
50
+ /**
51
+ * Sets the support service configuration
52
+ *
53
+ * @description Updates the support service notification configuration for customized notifications
54
+ *
55
+ * @param {ISupportServiceConfig} config - Support service configuration
56
+ * @returns {Promise<void | Error>} - Void promise or error
57
+ */
58
+ // @ts-ignore - Type compatibility issue between interface and implementation
19
59
  async setSupportServiceConfig(config) {
20
- this.supportServiceenvConfig = {
21
- ...this.supportServiceenvConfig,
22
- ...config
23
- };
60
+ try {
61
+ if (!config) {
62
+ return new Error('Support service configuration is required');
63
+ }
64
+ this.supportServiceenvConfig = {
65
+ ...this.supportServiceenvConfig,
66
+ ...config
67
+ };
68
+ this.logger.debug('Support service configuration updated', {
69
+ hasEmail: !!config.email,
70
+ hasTemplateId: !!config.templateId,
71
+ hasVariables: !!config.variables
72
+ });
73
+ } catch (error) {
74
+ this.logger.error('Error setting support service config: %o', error);
75
+ return error instanceof Error ? error : new Error('Unknown error occurred while setting support service config');
76
+ }
24
77
  }
25
- async getLatestMessageInAllChannels(unit, value) {
26
- const today = new Date();
27
- const lastDay = sub(today, {
28
- [unit]: value
29
- });
30
- const channels = await this.channelRepository.model.aggregate([{
31
- $match: {
32
- lastPostAt: {
33
- $gte: lastDay,
34
- $lte: today
78
+ /**
79
+ * Sends notification for unread messages within a specified time frame
80
+ *
81
+ * @description Aggregates unread messages across channels and sends notifications
82
+ * to users based on their preferences within the specified duration
83
+ *
84
+ * @param {NotificationDurationUnitEnum} unit - Time unit for the duration
85
+ * @param {number} value - Number of time units to look back
86
+ * @returns {Promise<boolean | Error>} - Success status or error
87
+ */
88
+ // @ts-ignore - Type compatibility issue between interface and implementation
89
+ async sendNotificationOfUnreadMessages(unit, value) {
90
+ try {
91
+ if (!unit || typeof value !== 'number' || value <= 0) {
92
+ return new Error('Valid time unit and positive value are required');
93
+ }
94
+ this.logger.debug('Starting unread message notification process', {
95
+ unit,
96
+ value
97
+ });
98
+ const channels = await this.getLatestMessageInAllChannels(unit, value);
99
+ if (!channels || channels.length === 0) {
100
+ this.logger.debug('No channels with unread messages found');
101
+ return true;
102
+ }
103
+ const membersWithUnreadMessagesInChannels = this.groupByMember(channels);
104
+ const notificationPromises = Object.entries(membersWithUnreadMessagesInChannels).map(entry => this.sendNotificationToUser(entry));
105
+ await Promise.all(notificationPromises);
106
+ this.logger.debug('Unread message notifications sent successfully', {
107
+ channelCount: channels.length,
108
+ userCount: Object.keys(membersWithUnreadMessagesInChannels).length
109
+ });
110
+ return true;
111
+ } catch (error) {
112
+ this.logger.error('Error sending unread message notifications: %o', error);
113
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending unread message notifications');
114
+ }
115
+ }
116
+ /**
117
+ * Sends Expo push notifications
118
+ *
119
+ * @description Sends push notifications through the Expo notification service
120
+ *
121
+ * @param {IExpoNotificationData} data - Notification data
122
+ * @returns {Promise<IExpoNotification | Error>} - Notification result or error
123
+ */
124
+ async sendPushNotificationsExpo(data) {
125
+ try {
126
+ if (!data) {
127
+ return new Error('Notification data is required');
128
+ }
129
+ if (!data.to || Array.isArray(data.to) && data.to.length === 0) {
130
+ return new Error('Notification recipients are required');
131
+ }
132
+ this.logger.debug('Sending Expo push notification', {
133
+ recipients: Array.isArray(data.to) ? data.to.length : 1,
134
+ hasTitle: !!data.title,
135
+ hasBody: !!data.body
136
+ });
137
+ const response = await fetch('https://exp.host/--/api/v2/push/send/', {
138
+ method: 'POST',
139
+ headers: {
140
+ Accept: 'application/json',
141
+ 'Accept-Encoding': 'gzip, deflate',
142
+ 'Content-Type': 'application/json'
143
+ },
144
+ body: JSON.stringify(data)
145
+ });
146
+ if (!response.ok) {
147
+ return new Error(`Expo notification service returned ${response.status}: ${response.statusText}`);
148
+ }
149
+ const result = await response.json();
150
+ this.logger.debug('Expo push notification sent successfully', {
151
+ status: result.status,
152
+ id: result.id
153
+ });
154
+ return result;
155
+ } catch (error) {
156
+ this.logger.error('Error sending Expo push notification: %o', error);
157
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending Expo notification');
158
+ }
159
+ }
160
+ /**
161
+ * Sends Expo notification when a post is created
162
+ *
163
+ * @description Handles push notification logic for new posts with thread and channel support
164
+ *
165
+ * @param {IPost} post - The post that triggered the notification
166
+ * @param {IExpoNotificationBodyData} notificationData - Optional notification data override
167
+ * @returns {Promise<boolean | Error>} - Success status or error
168
+ */
169
+ // @ts-ignore - Type compatibility issue between interface and implementation
170
+ async sendExpoNotificationOnPost(post, notificationData) {
171
+ try {
172
+ if (!post) {
173
+ return new Error('Post data is required');
174
+ }
175
+ this.logger.debug('Processing post notification', {
176
+ postId: post.id,
177
+ channelId: post.channel,
178
+ hasNotificationData: !!notificationData
179
+ });
180
+ const currentPost = {
181
+ ...post
182
+ };
183
+ const notificationParams = notificationData && notificationData?.url ? notificationData : currentPost?.props?.notificationParams;
184
+ if (!notificationParams) {
185
+ this.logger.debug('No notification parameters found, skipping notification');
186
+ return false;
187
+ }
188
+ // Get thread participants if thread-based notification
189
+ const postThread = notificationParams?.thread?.id ? await this.getThreadWithParticipants(notificationParams?.thread?.id?.toString()) : null;
190
+ // Get channel members
191
+ const channel = await this.getChannelWithMembers(currentPost?.channel?.toString());
192
+ if (!channel) {
193
+ return new Error('Channel not found for post notification');
194
+ }
195
+ // Get sender information
196
+ const userId = notificationParams?.senderId?.toString() || currentPost?.editedBy?.toString();
197
+ const user = await this.getUser(userId);
198
+ if (!user) {
199
+ return new Error('User not found for post notification');
200
+ }
201
+ const {
202
+ givenName,
203
+ familyName,
204
+ username
205
+ } = user;
206
+ const fullName = givenName ? `${givenName} ${familyName}` : username || 'Message';
207
+ const notificationParamsOtherData = notificationParams?.other ?? {};
208
+ const title = notificationParams?.title || fullName;
209
+ const body = notificationParams?.body || (currentPost?.message === '' ? 'Sent a file' : currentPost?.message || 'New message');
210
+ const sound = notificationParamsOtherData?.sound ?? 'default';
211
+ // Collect notification tokens
212
+ let tokens = [];
213
+ if (postThread) {
214
+ tokens = this.extractNotificationTokens(postThread.participants?.filter(p => p?.user?.id !== userId) || [], 'participants');
215
+ } else {
216
+ tokens = this.extractNotificationTokens(channel.members?.filter(member => member?.user?.id !== userId) || [], 'members');
217
+ }
218
+ if (tokens.length === 0) {
219
+ this.logger.debug('No notification tokens found for recipients');
220
+ return false;
221
+ }
222
+ // Send the notification
223
+ const notificationResult = await this.sendPushNotificationsExpo({
224
+ to: tokens,
225
+ data: notificationParams,
226
+ title,
227
+ body,
228
+ sound
229
+ });
230
+ if (notificationResult instanceof Error) {
231
+ return notificationResult;
232
+ }
233
+ this.logger.debug('Post notification sent successfully', {
234
+ postId: post.id,
235
+ recipientCount: tokens.length
236
+ });
237
+ return true;
238
+ } catch (error) {
239
+ this.logger.error('Error sending post notification: %o', error);
240
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending post notification');
241
+ }
242
+ }
243
+ /**
244
+ * Sends general notifications for a post
245
+ *
246
+ * @description Handles email and SMS notifications for posts with template support
247
+ *
248
+ * @param {IPost} post - The post that triggered the notification
249
+ * @returns {Promise<boolean | Error>} - Success status or error
250
+ */
251
+ // @ts-ignore - Type compatibility issue between interface and implementation
252
+ async sendNotificationOnPost(post) {
253
+ try {
254
+ if (!post) {
255
+ return new Error('Post data is required');
256
+ }
257
+ this.logger.debug('Processing post notification', {
258
+ postId: post.id
259
+ });
260
+ const currentPost = {
261
+ ...post
262
+ };
263
+ const template = currentPost?.props?.template ?? null;
264
+ // Extract template configuration
265
+ const guestTemplateId = template?.guest?.id ?? null;
266
+ const guestTemplateVariables = this.extractTemplateVariables(template?.guest?.variables);
267
+ const hostTemplateId = template?.host?.id ?? null;
268
+ const hostTemplateVariables = this.extractTemplateVariables(template?.host?.variables);
269
+ // Get guest user information
270
+ const guestId = guestTemplateVariables?.userId?.toString() || currentPost?.editedBy?.toString();
271
+ const user = await this.getUser(guestId);
272
+ if (!user) {
273
+ return new Error('Guest user not found');
274
+ }
275
+ const userPreferences = await this.getGlobalNotificationSettings(user.id);
276
+ const {
277
+ email,
278
+ sms,
279
+ browser
280
+ } = userPreferences?.global?.notification?.offlineMessages || {};
281
+ // Get channel and host information
282
+ const channel = await this.getChannelWithMembers(currentPost?.channel?.toString());
283
+ if (!channel) {
284
+ return new Error('Channel not found');
285
+ }
286
+ const channelOwner = this.findChannelOwner(channel);
287
+ const hostId = hostTemplateVariables?.userId?.toString() || channelOwner;
288
+ const owner = hostId ? await this.getUser(hostId) : null;
289
+ // Prepare notification content
290
+ const {
291
+ guestSubject,
292
+ hostSubject,
293
+ guestText,
294
+ hostText
295
+ } = this.prepareNotificationContent(currentPost, user, owner, guestTemplateVariables, hostTemplateVariables);
296
+ // Send notifications based on preferences
297
+ const notificationPromises = [];
298
+ if (email) {
299
+ // Send to guest
300
+ notificationPromises.push(this.sendMailOnPost(user, currentPost, channel, guestSubject, guestText, guestTemplateId, guestTemplateVariables));
301
+ // Send to host if exists
302
+ if (owner) {
303
+ notificationPromises.push(this.sendMailOnPost(owner, currentPost, channel, hostSubject, hostText, hostTemplateId, hostTemplateVariables));
35
304
  }
36
305
  }
37
- }, {
38
- $project: {
39
- _id: true,
40
- lastPostAt: true,
41
- creator: true,
42
- members: {
43
- $filter: {
44
- input: '$members',
45
- as: 'item',
46
- cond: {
47
- $and: [{
48
- $lte: ['$$item.lastViewedAt', '$lastPostAt']
49
- }]
50
- }
51
- }
306
+ if (sms) {
307
+ notificationPromises.push(this.sendSmsOnPost(user, currentPost, guestText));
308
+ if (owner) {
309
+ notificationPromises.push(this.sendSmsOnPost(owner, currentPost, hostText));
52
310
  }
53
311
  }
54
- }]);
55
- const posts = await this.postRepository.getAll({
56
- criteria: {
57
- channel: {
58
- $in: channels.map(({
59
- _id
60
- }) => _id)
61
- },
62
- createdAt: {
63
- $in: channels.map(({
64
- lastPostAt
65
- }) => lastPostAt)
312
+ await Promise.all(notificationPromises);
313
+ this.logger.debug('Post notifications sent successfully', {
314
+ postId: post.id,
315
+ notificationTypes: {
316
+ email,
317
+ sms,
318
+ browser
66
319
  }
320
+ });
321
+ return true;
322
+ } catch (error) {
323
+ this.logger.error('Error sending post notification: %o', error);
324
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending post notification');
325
+ }
326
+ }
327
+ /**
328
+ * Sends notifications for alert messages within a time frame
329
+ *
330
+ * @description Processes and sends notifications for alert-type messages
331
+ *
332
+ * @param {NotificationDurationUnitEnum} unit - Time unit for the duration
333
+ * @param {number} value - Number of time units to look back
334
+ * @returns {Promise<boolean | Error>} - Success status or error
335
+ */
336
+ // @ts-ignore - Type compatibility issue between interface and implementation
337
+ async sendNotificationOfAlertMessages(unit, value) {
338
+ try {
339
+ if (!unit || typeof value !== 'number' || value <= 0) {
340
+ return new Error('Valid time unit and positive value are required');
67
341
  }
68
- });
69
- return channels.map(channel => ({
70
- ...channel,
71
- // eslint-disable-next-line no-underscore-dangle
72
- post: posts.find(post => post.channel.toString() === channel._id.toString())
73
- }));
342
+ this.logger.debug('Processing alert message notifications', {
343
+ unit,
344
+ value
345
+ });
346
+ const posts = await this.getLatestAlertMessage(unit, value);
347
+ if (!posts || posts.length === 0) {
348
+ this.logger.debug('No alert messages found');
349
+ return true;
350
+ }
351
+ const notificationPromises = posts.map(post => this.sendNotificationOnPost(post));
352
+ const results = await Promise.all(notificationPromises);
353
+ // Check if any notifications failed
354
+ const failures = results.filter(result => result instanceof Error);
355
+ if (failures.length > 0) {
356
+ this.logger.warn(`${failures.length} alert notifications failed out of ${posts.length}`);
357
+ }
358
+ this.logger.debug('Alert message notifications processed', {
359
+ totalPosts: posts.length,
360
+ failures: failures.length
361
+ });
362
+ return true;
363
+ } catch (error) {
364
+ this.logger.error('Error sending alert message notifications: %o', error);
365
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending alert notifications');
366
+ }
367
+ }
368
+ /**
369
+ * Sends notifications for unread service messages
370
+ *
371
+ * @description Handles notifications for support service channels
372
+ *
373
+ * @param {NotificationDurationUnitEnum} unit - Time unit for the duration
374
+ * @param {number} value - Number of time units to look back
375
+ * @returns {Promise<boolean | Error>} - Success status or error
376
+ */
377
+ // @ts-ignore - Type compatibility issue between interface and implementation
378
+ async sendNotificationOfUnreadServiceMessages(unit, value) {
379
+ try {
380
+ if (!unit || typeof value !== 'number' || value <= 0) {
381
+ return new Error('Valid time unit and positive value are required');
382
+ }
383
+ this.logger.debug('Processing service message notifications', {
384
+ unit,
385
+ value
386
+ });
387
+ const threads = await this.getServiceThreads(unit, value);
388
+ if (!threads || threads.length === 0) {
389
+ this.logger.debug('No service threads found');
390
+ return true;
391
+ }
392
+ const posts = await Promise.all(threads.map(thread => this.getServicePosts(thread)));
393
+ const validPosts = posts.filter(post => post !== null);
394
+ if (validPosts.length === 0) {
395
+ this.logger.debug('No valid service posts found');
396
+ return true;
397
+ }
398
+ const notificationPromises = validPosts.map(post => this.sendServiceNotificationOnPost(post));
399
+ const results = await Promise.all(notificationPromises);
400
+ const failures = results.filter(result => result instanceof Error);
401
+ if (failures.length > 0) {
402
+ this.logger.warn(`${failures.length} service notifications failed out of ${validPosts.length}`);
403
+ }
404
+ this.logger.debug('Service message notifications processed', {
405
+ totalThreads: threads.length,
406
+ validPosts: validPosts.length,
407
+ failures: failures.length
408
+ });
409
+ return true;
410
+ } catch (error) {
411
+ this.logger.error('Error sending service message notifications: %o', error);
412
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending service notifications');
413
+ }
414
+ }
415
+ // Private helper methods
416
+ async getLatestMessageInAllChannels(unit, value) {
417
+ try {
418
+ const today = new Date();
419
+ const lastDay = sub(today, {
420
+ [unit]: value
421
+ });
422
+ const channels = await this.channelRepository.model.aggregate([{
423
+ $match: {
424
+ lastPostAt: {
425
+ $gte: lastDay,
426
+ $lte: today
427
+ }
428
+ }
429
+ }, {
430
+ $project: {
431
+ _id: true,
432
+ lastPostAt: true,
433
+ creator: true,
434
+ members: {
435
+ $filter: {
436
+ input: '$members',
437
+ as: 'item',
438
+ cond: {
439
+ $and: [{
440
+ $lte: ['$$item.lastViewedAt', '$lastPostAt']
441
+ }]
442
+ }
443
+ }
444
+ }
445
+ }
446
+ }]);
447
+ const posts = await this.postRepository.getAll({
448
+ criteria: {
449
+ channel: {
450
+ $in: channels.map(({
451
+ _id
452
+ }) => _id)
453
+ },
454
+ createdAt: {
455
+ $in: channels.map(({
456
+ lastPostAt
457
+ }) => lastPostAt)
458
+ }
459
+ }
460
+ });
461
+ return channels.map(channel => ({
462
+ ...channel,
463
+ post: posts.find(post => post.channel.toString() === channel._id.toString())
464
+ }));
465
+ } catch (error) {
466
+ this.logger.error('Error getting latest messages in channels: %o', error);
467
+ return [];
468
+ }
74
469
  }
75
470
  groupByMember(channels) {
76
471
  return channels.reduce((acc, curr) => {
@@ -88,447 +483,434 @@ import {__decorate,__param,__metadata}from'tslib';import {omit}from'lodash-es';i
88
483
  }) => compareAsc(channelMessage.lastPostAt, lastViewedAt), []);
89
484
  }
90
485
  async sendMail(user, channels) {
91
- this.logger.debug('Outgoing request to SendMail', {
92
- user: this.users[user],
93
- channels
94
- });
95
- const {
96
- notificationEmail,
97
- email,
98
- username
99
- } = this.users[user];
100
- await this.broker.call(`${MoleculerServiceName.MailService}.${MailServiceAction.Send}`, {
101
- request: {
102
- topic: 'Unread Messages Notification',
103
- to: notificationEmail || email,
104
- templateId: MailTemplateId.MessageNotificationServiceId,
105
- from: config.MAIL_SEND_DEFAULT_EMAIL,
106
- subject: `You have Unread Messages in ${channels.length} chats`,
107
- variables: {
108
- text: `Hey ${username},\nYou have Unread Messages in ${channels.length} chats`,
109
- user,
110
- channel: channels
111
- }
486
+ try {
487
+ this.logger.debug('Sending email notification', {
488
+ userId: user,
489
+ channelCount: channels.length
490
+ });
491
+ const userAccount = this.users[user];
492
+ if (!userAccount) {
493
+ throw new Error(`User account not found for user ${user}`);
112
494
  }
113
- });
495
+ const {
496
+ notificationEmail,
497
+ email,
498
+ username
499
+ } = userAccount;
500
+ await this.broker.call(`${MoleculerServiceName.MailService}.${MailServiceAction.Send}`, {
501
+ request: {
502
+ topic: 'Unread Messages Notification',
503
+ to: notificationEmail || email,
504
+ templateId: MailTemplateId.MessageNotificationServiceId,
505
+ from: config.MAIL_SEND_DEFAULT_EMAIL,
506
+ subject: `You have Unread Messages in ${channels.length} chats`,
507
+ variables: {
508
+ text: `Hey ${username},\nYou have Unread Messages in ${channels.length} chats`,
509
+ user,
510
+ channel: channels
511
+ }
512
+ }
513
+ });
514
+ } catch (error) {
515
+ this.logger.error('Error sending email notification: %o', error);
516
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending email');
517
+ }
114
518
  }
115
519
  async sendText(user, channels) {
116
- this.logger.debug('Outgoing request to SendSMS', {
117
- user: this.users[user],
118
- channels
119
- });
120
- // #Todo Get User Full Name and Phone Number
121
- const {
122
- username,
123
- phoneNumber
124
- } = this.users[user];
125
- const [number] = phoneNumber;
126
- if (!number) {
127
- this.logger.debug('Outgoing request to SendSMS Skipped, reason No phoneNumber found', {
128
- user: this.users[user],
129
- channels
520
+ try {
521
+ this.logger.debug('Sending SMS notification', {
522
+ userId: user,
523
+ channelCount: channels.length
130
524
  });
525
+ const userAccount = this.users[user];
526
+ if (!userAccount) {
527
+ throw new Error(`User account not found for user ${user}`);
528
+ }
529
+ const {
530
+ username,
531
+ phoneNumber
532
+ } = userAccount;
533
+ const [number] = phoneNumber || [];
534
+ if (!number) {
535
+ this.logger.debug('SMS notification skipped - no phone number', {
536
+ userId: user
537
+ });
538
+ return;
539
+ }
540
+ await this.broker.call(`${MoleculerServiceName.SmsService}.${SmsServiceActions.Send}`, {
541
+ to: number,
542
+ body: `Hey ${username},\nYou have Unread Messages in ${channels.length} chats`
543
+ });
544
+ } catch (error) {
545
+ this.logger.error('Error sending SMS notification: %o', error);
546
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending SMS');
131
547
  }
132
- await this.broker.call(`${MoleculerServiceName.SmsService}.${SmsServiceActions.Send}`, {
133
- to: number,
134
- body: `Hey ${username},\nYou have Unread Messages in ${channels.length} chats`
135
- });
136
548
  }
137
549
  async sendPushNotifications(user, channels) {
138
- // #Todo Dispatch Push notification once support is added
139
- this.logger.debug('Outgoing request to SendPushNotification', {
140
- user: this.users[user],
141
- channels
550
+ this.logger.debug('Push notification placeholder called', {
551
+ userId: user,
552
+ channelCount: channels.length
142
553
  });
143
- return Promise.resolve();
554
+ // Placeholder for future push notification implementation
144
555
  }
145
- getGlobalNotificationSettings(user) {
146
- // #Todo fetch from preferences
147
- return Promise.resolve({
148
- global: {
149
- notification: {
150
- offlineMessages: {
151
- sms: true,
152
- email: true,
153
- browser: true
556
+ async getGlobalNotificationSettings(userId) {
557
+ try {
558
+ // Placeholder for preference service integration
559
+ return {
560
+ global: {
561
+ notification: {
562
+ offlineMessages: {
563
+ sms: true,
564
+ email: true,
565
+ browser: true
566
+ }
154
567
  }
155
568
  }
156
- }
157
- });
569
+ };
570
+ } catch (error) {
571
+ this.logger.error('Error getting notification settings: %o', error);
572
+ return {
573
+ global: {
574
+ notification: {
575
+ offlineMessages: {
576
+ sms: false,
577
+ email: false,
578
+ browser: false
579
+ }
580
+ }
581
+ }
582
+ };
583
+ }
158
584
  }
159
585
  async getUser(id) {
160
- return this.broker.call(`${MoleculerServiceName.AccountUser}.${AccountServiceAction.FindAccountById}`, {
161
- id
162
- });
586
+ try {
587
+ if (!id) {
588
+ return null;
589
+ }
590
+ return await this.broker.call(`${MoleculerServiceName.AccountUser}.${AccountServiceAction.FindAccountById}`, {
591
+ id
592
+ });
593
+ } catch (error) {
594
+ this.logger.error('Error getting user: %o', error);
595
+ return null;
596
+ }
163
597
  }
164
598
  async fetchUsersForMembers(users) {
165
- return Promise.all(users.filter(user => !Object.keys(this.users).includes(user)).map(async user => {
166
- this.users[user] = await this.getUser(user);
167
- this.userPreferences[user] = await this.getGlobalNotificationSettings(user);
168
- }));
599
+ try {
600
+ const missingUsers = users.filter(user => !Object.keys(this.users).includes(user));
601
+ await Promise.all(missingUsers.map(async user => {
602
+ const userAccount = await this.getUser(user);
603
+ if (userAccount) {
604
+ this.users[user] = userAccount;
605
+ this.userPreferences[user] = await this.getGlobalNotificationSettings(user);
606
+ }
607
+ }));
608
+ } catch (error) {
609
+ this.logger.error('Error fetching users: %o', error);
610
+ }
169
611
  }
170
612
  async sendNotificationToUser([user, channels]) {
171
- await this.fetchUsersForMembers([user, ...channels.map(({
172
- post
173
- }) => post.editedBy.toString())]);
174
- const {
175
- email,
176
- sms,
177
- browser
178
- } = this.userPreferences[user]?.global?.notification?.offlineMessages || {};
179
- const filteredChannels = channels.filter(({
180
- post
181
- }) => user !== post.editedBy.toString());
182
- if (filteredChannels?.length === 0) {
183
- return;
184
- }
185
- if (email) {
186
- await this.sendMail(user, filteredChannels);
187
- }
188
- if (sms) {
189
- await this.sendText(user, filteredChannels);
190
- }
191
- if (browser) {
192
- await this.sendPushNotifications(user, filteredChannels);
613
+ try {
614
+ await this.fetchUsersForMembers([user, ...channels.map(({
615
+ post
616
+ }) => post.editedBy.toString())]);
617
+ const userPrefs = this.userPreferences[user];
618
+ const {
619
+ email,
620
+ sms,
621
+ browser
622
+ } = userPrefs?.global?.notification?.offlineMessages || {};
623
+ const filteredChannels = channels.filter(({
624
+ post
625
+ }) => user !== post.editedBy.toString());
626
+ if (filteredChannels.length === 0) {
627
+ return;
628
+ }
629
+ const notificationPromises = [];
630
+ if (email) {
631
+ notificationPromises.push(this.sendMail(user, filteredChannels));
632
+ }
633
+ if (sms) {
634
+ notificationPromises.push(this.sendText(user, filteredChannels));
635
+ }
636
+ if (browser) {
637
+ notificationPromises.push(this.sendPushNotifications(user, filteredChannels));
638
+ }
639
+ await Promise.all(notificationPromises);
640
+ } catch (error) {
641
+ this.logger.error('Error sending notification to user: %o', error);
642
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending user notification');
193
643
  }
194
644
  }
195
- async sendNotificationOfUnreadMessages(unit, value) {
196
- const channels = await this.getLatestMessageInAllChannels(unit, value);
197
- const membersWithUnreadMessagesInChannels = this.groupByMember(channels);
198
- await Promise.all(Object.entries(membersWithUnreadMessagesInChannels).map(this.sendNotificationToUser.bind(this)));
199
- }
200
- async sendPushNotificationsExpo(data) {
201
- this.logger.debug('Outgoing request to ExpoPushNotification');
202
- const response = await fetch('https://exp.host/--/api/v2/push/send/', {
203
- method: 'POST',
204
- headers: {
205
- Accept: 'application/json',
206
- 'Accept-Encoding': 'gzip, deflate',
207
- 'Content-Type': 'application/json'
208
- },
209
- body: JSON.stringify(data)
210
- });
211
- const result = await response.json();
212
- this.logger.debug('ExpoPushNotification response', result);
213
- return result;
214
- }
215
645
  async getThreadWithParticipants(id) {
216
- const thread = await this.postThreadRepository.model.findOne({
217
- _id: id
218
- }).lean();
219
- const participants = await Promise.all({
220
- ...thread
221
- }?.participants?.map(async m => ({
222
- ...m,
223
- user: await this.getUser(m?.user?.toString())
224
- })));
225
- const threadWithParticipants = await Promise.all([{
226
- ...thread,
227
- participants: participants
228
- }]);
229
- return threadWithParticipants?.[0];
646
+ try {
647
+ const thread = await this.postThreadRepository.model.findOne({
648
+ _id: id
649
+ }).lean();
650
+ if (!thread) {
651
+ return null;
652
+ }
653
+ const participants = await Promise.all((thread.participants || []).map(async m => ({
654
+ ...m,
655
+ user: await this.getUser(m?.user?.toString())
656
+ })));
657
+ return {
658
+ ...thread,
659
+ participants
660
+ };
661
+ } catch (error) {
662
+ this.logger.error('Error getting thread with participants: %o', error);
663
+ return null;
664
+ }
230
665
  }
231
666
  async getChannelWithMembers(id) {
232
- const channel = await this.channelRepository.model.findOne({
233
- _id: id
234
- }).lean();
235
- const channelMembers = await Promise.all({
236
- ...channel
237
- }?.members?.map(async m => ({
238
- ...m,
239
- user: await this.getUser(m?.user?.toString())
240
- })));
241
- const channelWithMembers = await Promise.all([{
242
- ...channel,
243
- members: channelMembers
244
- }]);
245
- return channelWithMembers?.[0];
667
+ try {
668
+ const channel = await this.channelRepository.model.findOne({
669
+ _id: id
670
+ }).lean();
671
+ if (!channel) {
672
+ return null;
673
+ }
674
+ const members = await Promise.all((channel.members || []).map(async m => ({
675
+ ...m,
676
+ user: await this.getUser(m?.user?.toString())
677
+ })));
678
+ return {
679
+ ...channel,
680
+ members
681
+ };
682
+ } catch (error) {
683
+ this.logger.error('Error getting channel with members: %o', error);
684
+ return null;
685
+ }
246
686
  }
247
- async sendExpoNotificationOnPost(post, notificationData) {
248
- this.logger.debug('sendExpoNotificationOnPost');
249
- const currentPost = {
250
- ...post
251
- };
252
- const notificationParams = notificationData && notificationData?.url ? notificationData : currentPost?.props?.notificationParams;
253
- const postThread = notificationParams?.thread?.id ? await this.getThreadWithParticipants(notificationParams?.thread?.id?.toString()) : null;
254
- const channel = await this.getChannelWithMembers(currentPost?.channel?.toString());
255
- const userId = notificationParams?.senderId?.toString() || currentPost?.editedBy?.toString();
256
- const user = await this.getUser(userId);
257
- const {
258
- givenName,
259
- familyName,
260
- username
261
- } = user;
262
- const fullName = givenName ? givenName + ' ' + familyName : '';
263
- const notificationParamsOtherData = notificationParams?.other ?? null;
264
- const title = notificationParams && notificationParams?.title ? notificationParams?.title : fullName ? fullName : 'Message';
265
- const body = notificationParams && notificationParams?.body ? notificationParams?.body : currentPost?.message == '' ? 'Send a file' : currentPost?.message;
266
- const sound = notificationParamsOtherData?.sound ?? 'default';
267
- let tokens;
268
- if (postThread) {
269
- tokens = postThread?.participants?.filter(p => p?.user?.id != userId)?.map(m => m?.user?.tokens?.filter(t => t?.type == 'EXPO_NOTIFICATION_TOKEN')?.map(et => et?.token) ?? [])?.flat(1)?.filter(t => t)?.filter((value, index, array) => array.indexOf(value) === index) ?? [];
270
- } else {
271
- tokens = channel?.members?.filter(member => member?.user?.id != userId)?.map(m => m?.user?.tokens?.filter(t => t?.type == 'EXPO_NOTIFICATION_TOKEN')?.map(et => et?.token) ?? [])?.flat(1)?.filter(t => t)?.filter((value, index, array) => array.indexOf(value) === index) ?? [];
687
+ extractNotificationTokens(entities, entityType) {
688
+ try {
689
+ return entities.map(entity => entity?.user?.tokens?.filter(t => t?.type === 'EXPO_NOTIFICATION_TOKEN')?.map(et => et?.token) || []).flat().filter(token => token).filter((value, index, array) => array.indexOf(value) === index);
690
+ } catch (error) {
691
+ this.logger.error(`Error extracting notification tokens from ${entityType}: %o`, error);
692
+ return [];
272
693
  }
273
- if (tokens?.length > 0 && notificationParams) {
274
- //this.logger.debug('sendExpoNotif tokens', tokens);
275
- // const data: IExpoNotificationData = {
276
- // to: tokens,
277
- // data: notificationParams,
278
- // title,
279
- // body,
280
- // sound,
281
- // };
282
- const data = {
283
- to: tokens,
284
- data: notificationParams,
285
- title,
286
- body,
287
- sound
694
+ }
695
+ extractTemplateVariables(variables) {
696
+ try {
697
+ return variables && typeof variables === 'object' && variables.constructor === Object ? {
698
+ ...variables
699
+ } : null;
700
+ } catch (error) {
701
+ this.logger.error('Error extracting template variables: %o', error);
702
+ return null;
703
+ }
704
+ }
705
+ findChannelOwner(channel) {
706
+ try {
707
+ return channel?.members?.filter(m => m?.roles === 'OWNER')?.map(u => u?.user?.toString())?.[0] || null;
708
+ } catch (error) {
709
+ this.logger.error('Error finding channel owner: %o', error);
710
+ return null;
711
+ }
712
+ }
713
+ prepareNotificationContent(post, user, owner, guestVariables, hostVariables) {
714
+ try {
715
+ // Prepare subject and content based on post properties
716
+ const postPropsAttachmentKeys = post?.props?.attachment ? Object.keys(post?.props?.attachment) : null;
717
+ const attachmentRefKey = postPropsAttachmentKeys?.find(key => key?.toLowerCase() === post?.props?.ref?.toLowerCase());
718
+ const refType = attachmentRefKey ? post?.props?.attachment[attachmentRefKey]?.type?.toLowerCase() : ' Notification';
719
+ const subject = post?.props?.ref ? `${post.props.ref.charAt(0).toUpperCase() + post.props.ref.slice(1)} ${refType.charAt(0).toUpperCase() + refType.slice(1)}` : 'You have Unread Messages in 1 chats';
720
+ const userName = user.givenName && user.familyName ? `${user.givenName} ${user.familyName}` : user.username || 'User';
721
+ const ownerName = owner?.givenName && owner?.familyName ? `${owner.givenName} ${owner.familyName}` : owner?.username || 'Owner';
722
+ return {
723
+ guestSubject: guestVariables?.subject || subject,
724
+ hostSubject: hostVariables?.subject || subject,
725
+ guestText: guestVariables?.bodyText || `Hi ${userName}, ${post?.message}`,
726
+ hostText: hostVariables?.bodyText || `Hi ${ownerName}, ${post?.message}`
288
727
  };
289
- await this.sendPushNotificationsExpo(data);
290
- return true;
291
- } else return false;
728
+ } catch (error) {
729
+ this.logger.error('Error preparing notification content: %o', error);
730
+ return {
731
+ guestSubject: 'Notification',
732
+ hostSubject: 'Notification',
733
+ guestText: 'You have a new message',
734
+ hostText: 'You have a new message'
735
+ };
736
+ }
292
737
  }
293
738
  async sendMailOnPost(user, post, channel, subject, text, templateId = null, variables = null, recipientMail = null) {
294
- this.logger.debug('Outgoing request to SendMail', {
295
- user: user,
296
- post
297
- });
298
- const {
299
- notificationEmail,
300
- email
301
- } = user;
302
- const topic = subject;
303
- await this.broker.call(`${MoleculerServiceName.MailService}.${MailServiceAction.Send}`, {
304
- request: {
305
- topic: topic,
306
- to: recipientMail || notificationEmail || email,
307
- templateId: templateId || MailTemplateId.MessageNotificationServiceId,
308
- namespace: config.NAMESPACE,
309
- from: config.MAIL_SEND_DEFAULT_EMAIL,
310
- subject,
311
- variables: variables || {
312
- text,
313
- user,
314
- post,
315
- channel
739
+ try {
740
+ this.logger.debug('Sending mail notification for post', {
741
+ userId: user.id,
742
+ postId: post.id,
743
+ hasTemplate: !!templateId
744
+ });
745
+ const {
746
+ notificationEmail,
747
+ email
748
+ } = user;
749
+ await this.broker.call(`${MoleculerServiceName.MailService}.${MailServiceAction.Send}`, {
750
+ request: {
751
+ topic: subject,
752
+ to: recipientMail || notificationEmail || email,
753
+ templateId: templateId || MailTemplateId.MessageNotificationServiceId,
754
+ namespace: config.NAMESPACE,
755
+ from: config.MAIL_SEND_DEFAULT_EMAIL,
756
+ subject,
757
+ variables: variables || {
758
+ text,
759
+ user,
760
+ post,
761
+ channel
762
+ }
316
763
  }
317
- }
318
- });
319
- }
320
- async sendSmsOnPost(user, post, text) {
321
- this.logger.debug('Outgoing request to SendSMS', {
322
- user,
323
- post
324
- });
325
- const {
326
- username,
327
- givenName,
328
- familyName,
329
- phoneNumber
330
- } = user;
331
- const [number] = phoneNumber;
332
- const {
333
- countryCode,
334
- phoneNumber: mobile
335
- } = number;
336
- const countryData = await this.countryRepository.model.find({
337
- _id: countryCode
338
- });
339
- const [country] = countryData;
340
- const {
341
- phone_code
342
- } = country;
343
- const mobileWithCountryCode = phone_code + mobile;
344
- if (!mobileWithCountryCode) {
345
- this.logger.debug('Outgoing request to SendSMS Skipped, reason No phoneNumber found', {
346
- user: user,
347
- post
348
764
  });
765
+ } catch (error) {
766
+ this.logger.error('Error sending mail for post: %o', error);
767
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending mail');
349
768
  }
350
- await this.broker.call(`${MoleculerServiceName.SmsService}.${SmsServiceActions.Send}`, {
351
- to: mobileWithCountryCode,
352
- body: text
353
- });
354
769
  }
355
- async sendNotificationOnPost(post) {
356
- const currentPost = {
357
- ...post
358
- };
359
- const template = currentPost?.props?.template ?? null;
360
- const guestTemplateId = template ? template?.guest?.id ?? null : null;
361
- const guestTemlateVarables = template && template?.guest?.variables && typeof template?.guest?.variables === 'object' && template?.guest?.variables.constructor === Object ? {
362
- ...template?.guest?.variables
363
- } : null;
364
- const hostTemplateId = template ? template?.host?.id ?? null : null;
365
- const hostTemlateVarables = template && template?.host?.variables && typeof template?.host?.variables === 'object' && template?.host?.variables.constructor === Object ? {
366
- ...template?.host?.variables
367
- } : null;
368
- const guestId = guestTemlateVarables?.userId?.toString() || currentPost?.editedBy?.toString();
369
- const user = await this.getUser(guestId);
370
- const {
371
- givenName,
372
- familyName,
373
- username
374
- } = user;
375
- const userPreferences = await this.getGlobalNotificationSettings(user);
376
- const {
377
- email,
378
- sms,
379
- browser
380
- } = userPreferences?.global?.notification?.offlineMessages || {};
381
- const channel = await this.channelRepository.model.findOne({
382
- _id: currentPost?.channel
383
- });
384
- const channelOwner = channel?.members?.filter(m => m?.roles == 'OWNER')?.map(u => u?.user?.toString())?.[0] ?? null;
385
- const hostId = hostTemlateVarables?.userId?.toString() || channelOwner;
386
- const owner = hostId ? await this.getUser(hostId) : null;
387
- const {
388
- givenName: ownerGivenName,
389
- familyName: ownerFamilyName,
390
- username: ownerUserName
391
- } = owner;
392
- const postPropsAttachmentKeys = currentPost?.props?.attachment ? Object.keys(currentPost?.props?.attachment) : null;
393
- const attachmentRefKey = postPropsAttachmentKeys ? Array.isArray(postPropsAttachmentKeys) ? postPropsAttachmentKeys.filter(v => v == currentPost?.props?.ref.toLowerCase())[0] : Object.keys(currentPost?.props?.attachment).find(key => key?.toLowerCase() === currentPost?.props?.ref?.toLowerCase()) : null;
394
- const refType = attachmentRefKey ? currentPost?.props?.attachment[attachmentRefKey]?.type?.toLowerCase() : ' Notification';
395
- currentPost?.props?.ref?.toLowerCase()?.charAt(0)?.toUpperCase() + currentPost?.props?.ref?.toLowerCase()?.slice(1);
396
- refType?.toLowerCase()?.charAt(0)?.toUpperCase() + refType?.toLowerCase()?.slice(1);
397
- const subject = currentPost?.props?.ref ? `${currentPost.props.ref.toLowerCase().charAt(0).toUpperCase() + currentPost.props.ref.toLowerCase().slice(1)} ${refType.toLowerCase().charAt(0).toUpperCase() + refType.toLowerCase().slice(1)}` : 'You have Unread Messages in 1 chats';
398
- const guestSubject = guestTemlateVarables?.subject || subject;
399
- const hostSubject = hostTemlateVarables?.subject || subject;
400
- const userName = givenName + ' ' + familyName || username;
401
- const userText = `Hi ${userName}, ${currentPost?.message}`;
402
- const ownerName = ownerGivenName + ' ' + ownerFamilyName || ownerUserName;
403
- const ownerText = `Hi ${ownerName}, ${currentPost?.message}`;
404
- const guestText = guestTemlateVarables?.bodyText || userText;
405
- const hostText = hostTemlateVarables?.bodyText || ownerText;
406
- if (email) {
407
- if (guestTemlateVarables && !guestTemlateVarables.hasOwnProperty('text')) guestTemlateVarables.text = guestText;
408
- if (guestTemlateVarables && !guestTemlateVarables.hasOwnProperty('user')) guestTemlateVarables.user = user;
409
- if (guestTemlateVarables && !guestTemlateVarables.hasOwnProperty('post')) guestTemlateVarables.post = currentPost;
410
- if (guestTemlateVarables && !guestTemlateVarables.hasOwnProperty('channel')) guestTemlateVarables.channel = channel;
411
- await this.sendMailOnPost(user, currentPost, channel, guestSubject, guestText, guestTemplateId, guestTemlateVarables);
412
- if (owner) {
413
- if (hostTemlateVarables && !hostTemlateVarables.hasOwnProperty('text')) hostTemlateVarables.text = hostText;
414
- if (hostTemlateVarables && !hostTemlateVarables.hasOwnProperty('user')) hostTemlateVarables.user = owner;
415
- if (hostTemlateVarables && !hostTemlateVarables.hasOwnProperty('post')) hostTemlateVarables.post = currentPost;
416
- if (hostTemlateVarables && !hostTemlateVarables.hasOwnProperty('channel')) hostTemlateVarables.channel = channel;
417
- await this.sendMailOnPost(owner, currentPost, channel, hostSubject, hostText, hostTemplateId, hostTemlateVarables);
770
+ async sendSmsOnPost(user, post, text) {
771
+ try {
772
+ this.logger.debug('Sending SMS notification for post', {
773
+ userId: user.id,
774
+ postId: post.id
775
+ });
776
+ const {
777
+ phoneNumber
778
+ } = user;
779
+ const [number] = phoneNumber || [];
780
+ if (!number) {
781
+ this.logger.debug('SMS notification skipped - no phone number', {
782
+ userId: user.id
783
+ });
784
+ return;
418
785
  }
419
- }
420
- if (sms) {
421
- await this.sendSmsOnPost(user, currentPost, guestText);
422
- if (owner) {
423
- await this.sendSmsOnPost(owner, currentPost, hostText);
786
+ const {
787
+ countryCode,
788
+ phoneNumber: mobile
789
+ } = number;
790
+ const countryData = await this.countryRepository.model.find({
791
+ _id: countryCode
792
+ });
793
+ const [country] = countryData || [];
794
+ if (!country) {
795
+ throw new Error('Country data not found for phone number');
424
796
  }
797
+ const {
798
+ phone_code
799
+ } = country;
800
+ const mobileWithCountryCode = phone_code + mobile;
801
+ await this.broker.call(`${MoleculerServiceName.SmsService}.${SmsServiceActions.Send}`, {
802
+ to: mobileWithCountryCode,
803
+ body: text
804
+ });
805
+ } catch (error) {
806
+ this.logger.error('Error sending SMS for post: %o', error);
807
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending SMS');
425
808
  }
426
- return true;
427
809
  }
428
810
  async getLatestAlertMessage(unit, value) {
429
- const today = new Date();
430
- const lastDay = sub(today, {
431
- [unit]: value
432
- });
433
- const filteredPosts = await this.postRepository.model.aggregate([{
434
- $match: {
435
- type: PostTypeEnum.Alert,
436
- updatedAt: {
437
- $gte: lastDay,
438
- $lte: today
811
+ try {
812
+ const today = new Date();
813
+ const lastDay = sub(today, {
814
+ [unit]: value
815
+ });
816
+ const filteredPosts = await this.postRepository.model.aggregate([{
817
+ $match: {
818
+ type: PostTypeEnum.Alert,
819
+ updatedAt: {
820
+ $gte: lastDay,
821
+ $lte: today
822
+ }
439
823
  }
440
- }
441
- }]);
442
- const posts = filteredPosts?.map(p => {
443
- let post = {
444
- ...p
445
- };
446
- post.editedBy = post?.editedBy?.toString();
447
- return post;
448
- }) ?? [];
449
- return posts;
450
- }
451
- async sendNotificationOfAlertMessages(unit, value) {
452
- const posts = await this.getLatestAlertMessage(unit, value);
453
- if (posts && posts?.length > 0) await Promise.all(posts.map(this.sendNotificationOnPost.bind(this)));
824
+ }]);
825
+ return filteredPosts?.map(p => ({
826
+ ...p,
827
+ editedBy: p?.editedBy?.toString()
828
+ })) || [];
829
+ } catch (error) {
830
+ this.logger.error('Error getting latest alert messages: %o', error);
831
+ return [];
832
+ }
454
833
  }
455
834
  async sendServiceNotificationOnPost(post) {
456
- const currentPost = {
457
- ...post
458
- };
459
- const channel = await this.channelRepository.model.findOne({
460
- _id: currentPost?.channel
461
- });
462
- const hostId = channel?.members?.filter(m => m?.roles == 'OWNER')?.map(u => u?.user?.toString())?.[0] ?? null;
463
- const userId = hostId && currentPost?.editedBy?.toString() != hostId ? hostId : null;
464
- const supportServiceConfig = this?.supportServiceenvConfig ?? null;
465
- if (userId && supportServiceConfig.email) {
835
+ try {
836
+ const currentPost = {
837
+ ...post
838
+ };
839
+ const channel = await this.getChannelWithMembers(currentPost?.channel?.toString());
840
+ if (!channel) {
841
+ return new Error('Channel not found for service notification');
842
+ }
843
+ const hostId = this.findChannelOwner(channel);
844
+ const userId = hostId && currentPost?.editedBy?.toString() !== hostId ? hostId : null;
845
+ if (!userId || !this.supportServiceenvConfig?.email) {
846
+ this.logger.debug('Service notification skipped - no recipient or config');
847
+ return;
848
+ }
466
849
  const postUserId = currentPost?.editedBy?.toString();
467
850
  const postUser = await this.getUser(postUserId);
468
- const {
469
- givenName: postUserGivenName,
470
- familyName: postUserFamilyname,
471
- username: postUserUsername
472
- } = postUser;
473
- const postUserFullName = postUserGivenName + ' ' + postUserFamilyname || postUserUsername;
474
851
  const user = await this.getUser(userId);
475
- const {
476
- givenName,
477
- familyName,
478
- username,
479
- notificationEmail,
480
- email
481
- } = user;
852
+ if (!postUser || !user) {
853
+ return new Error('Required users not found for service notification');
854
+ }
855
+ const postUserFullName = postUser.givenName && postUser.familyName ? `${postUser.givenName} ${postUser.familyName}` : postUser.username || 'User';
856
+ const userName = user.givenName && user.familyName ? `${user.givenName} ${user.familyName}` : user.username || 'User';
482
857
  const subject = 'You have Unread Messages in support service chat';
483
- // const userText = `Hi ${userName}, ${postUserFullName} sent you ${currentPost?.message}`;
484
- const userText = `Hi , you have unread message in support service chat sent by ${postUserFullName} - \n ${currentPost?.message}.`;
485
- await this.sendMailOnPost(user, currentPost, channel, subject, userText, supportServiceConfig?.templateId || null, supportServiceConfig?.variables || null, supportServiceConfig?.email);
858
+ const userText = `Hi, you have unread message in support service chat sent by ${postUserFullName} - \n ${currentPost?.message}.`;
859
+ await this.sendMailOnPost(user, currentPost, channel, subject, userText, this.supportServiceenvConfig?.templateId || null, this.supportServiceenvConfig?.variables || null, this.supportServiceenvConfig?.email);
860
+ } catch (error) {
861
+ this.logger.error('Error sending service notification: %o', error);
862
+ return error instanceof Error ? error : new Error('Unknown error occurred while sending service notification');
486
863
  }
487
864
  }
488
865
  async getServiceThreads(unit, value) {
489
- const today = new Date();
490
- const lastDay = sub(today, {
491
- [unit]: value
492
- });
493
- const channels = await this.channelRepository.model.aggregate([{
494
- $match: {
495
- type: RoomType.Service
496
- }
497
- }, {
498
- $project: {
499
- _id: {
500
- $toString: '$_id'
866
+ try {
867
+ const today = new Date();
868
+ const lastDay = sub(today, {
869
+ [unit]: value
870
+ });
871
+ const channels = await this.channelRepository.model.aggregate([{
872
+ $match: {
873
+ type: RoomType.Service
501
874
  }
502
- }
503
- }]);
504
- const channelIds = channels?.map(c => c?._id?.toString())?.filter(id => id);
505
- const postThreads = await this.postThreadRepository.model.find({
506
- lastReplyAt: {
507
- $gte: lastDay,
508
- $lte: today
509
- },
510
- channel: {
511
- $in: channelIds
512
- }
513
- });
514
- return postThreads;
515
- }
516
- async getServicePosts(postThread) {
517
- const post = await this.postRepository.model.findOne({
518
- $or: [{
519
- parentId: postThread?.post?.toString()
520
875
  }, {
521
- _id: postThread?.post?.toString()
522
- }]
523
- }).lean().sort({
524
- updatedAt: -1
525
- });
526
- return post;
876
+ $project: {
877
+ _id: {
878
+ $toString: '$_id'
879
+ }
880
+ }
881
+ }]);
882
+ const channelIds = channels?.map(c => c?._id?.toString())?.filter(id => id) || [];
883
+ const postThreads = await this.postThreadRepository.model.find({
884
+ lastReplyAt: {
885
+ $gte: lastDay,
886
+ $lte: today
887
+ },
888
+ channel: {
889
+ $in: channelIds
890
+ }
891
+ });
892
+ return postThreads || [];
893
+ } catch (error) {
894
+ this.logger.error('Error getting service threads: %o', error);
895
+ return [];
896
+ }
527
897
  }
528
- async sendNotificationOfUnreadServiceMessages(unit, value) {
529
- const threads = await this.getServiceThreads(unit, value);
530
- const posts = await Promise.all(threads.map(this.getServicePosts.bind(this)));
531
- if (posts && posts?.length > 0) await Promise.all(posts.map(this.sendServiceNotificationOnPost.bind(this)));
898
+ async getServicePosts(postThread) {
899
+ try {
900
+ const post = await this.postRepository.model.findOne({
901
+ $or: [{
902
+ parentId: postThread?.post?.toString()
903
+ }, {
904
+ _id: postThread?.post?.toString()
905
+ }]
906
+ }).lean().sort({
907
+ updatedAt: -1
908
+ });
909
+ return post || null;
910
+ } catch (error) {
911
+ this.logger.error('Error getting service posts: %o', error);
912
+ return null;
913
+ }
532
914
  }
533
915
  };
534
916
  MessengerNotificationService = __decorate([injectable(), __param(0, inject(SERVER_TYPES.ChannelRepository)), __param(1, inject(SERVER_TYPES.PostRepository)), __param(2, inject(SERVER_TYPES.PostThreadRepository)), __param(3, inject(SERVER_TYPES.CountryRepository)), __param(4, inject(CommonType.MOLECULER_BROKER)), __param(5, inject('Logger')), __metadata("design:paramtypes", [Object, Object, Object, Object, ServiceBroker, Object])], MessengerNotificationService);export{MessengerNotificationService};//# sourceMappingURL=messenger-notification-service.js.map