rez_core 2.2.154 → 2.2.156

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 (142) hide show
  1. package/dist/app.module.js +2 -0
  2. package/dist/app.module.js.map +1 -1
  3. package/dist/constant/global.constant.d.ts +1 -1
  4. package/dist/constant/global.constant.js +1 -1
  5. package/dist/constant/global.constant.js.map +1 -1
  6. package/dist/module/auth/strategies/google.strategy.js +1 -1
  7. package/dist/module/auth/strategies/google.strategy.js.map +1 -1
  8. package/dist/module/communication/communication.module.d.ts +2 -0
  9. package/dist/module/communication/communication.module.js +69 -0
  10. package/dist/module/communication/communication.module.js.map +1 -0
  11. package/dist/module/communication/controller/communication.controller.d.ts +54 -0
  12. package/dist/module/communication/controller/communication.controller.js +148 -0
  13. package/dist/module/communication/controller/communication.controller.js.map +1 -0
  14. package/dist/module/communication/dto/create-config.dto.d.ts +91 -0
  15. package/dist/module/communication/dto/create-config.dto.js +243 -0
  16. package/dist/module/communication/dto/create-config.dto.js.map +1 -0
  17. package/dist/module/communication/entity/communication-config.entity.d.ts +44 -0
  18. package/dist/module/communication/entity/communication-config.entity.js +45 -0
  19. package/dist/module/communication/entity/communication-config.entity.js.map +1 -0
  20. package/dist/module/communication/entity/communication-hub.entity.d.ts +20 -0
  21. package/dist/module/communication/entity/communication-hub.entity.js +105 -0
  22. package/dist/module/communication/entity/communication-hub.entity.js.map +1 -0
  23. package/dist/module/communication/examples/usage.example.d.ts +11 -0
  24. package/dist/module/communication/examples/usage.example.js +89 -0
  25. package/dist/module/communication/examples/usage.example.js.map +1 -0
  26. package/dist/module/communication/factories/base.factory.d.ts +9 -0
  27. package/dist/module/communication/factories/base.factory.js +3 -0
  28. package/dist/module/communication/factories/base.factory.js.map +1 -0
  29. package/dist/module/communication/factories/communication.factory.d.ts +33 -0
  30. package/dist/module/communication/factories/communication.factory.js +104 -0
  31. package/dist/module/communication/factories/communication.factory.js.map +1 -0
  32. package/dist/module/communication/factories/email.factory.d.ts +19 -0
  33. package/dist/module/communication/factories/email.factory.js +61 -0
  34. package/dist/module/communication/factories/email.factory.js.map +1 -0
  35. package/dist/module/communication/factories/sms.factory.d.ts +15 -0
  36. package/dist/module/communication/factories/sms.factory.js +49 -0
  37. package/dist/module/communication/factories/sms.factory.js.map +1 -0
  38. package/dist/module/communication/factories/telephone.factory.d.ts +13 -0
  39. package/dist/module/communication/factories/telephone.factory.js +43 -0
  40. package/dist/module/communication/factories/telephone.factory.js.map +1 -0
  41. package/dist/module/communication/factories/whatsapp.factory.d.ts +13 -0
  42. package/dist/module/communication/factories/whatsapp.factory.js +43 -0
  43. package/dist/module/communication/factories/whatsapp.factory.js.map +1 -0
  44. package/dist/module/communication/service/communication.service.d.ts +111 -0
  45. package/dist/module/communication/service/communication.service.js +726 -0
  46. package/dist/module/communication/service/communication.service.js.map +1 -0
  47. package/dist/module/communication/service/oauth.service.d.ts +18 -0
  48. package/dist/module/communication/service/oauth.service.js +185 -0
  49. package/dist/module/communication/service/oauth.service.js.map +1 -0
  50. package/dist/module/communication/strategies/communication.strategy.d.ts +17 -0
  51. package/dist/module/communication/strategies/communication.strategy.js +3 -0
  52. package/dist/module/communication/strategies/communication.strategy.js.map +1 -0
  53. package/dist/module/communication/strategies/email/gmail-api.strategy.d.ts +7 -0
  54. package/dist/module/communication/strategies/email/gmail-api.strategy.js +135 -0
  55. package/dist/module/communication/strategies/email/gmail-api.strategy.js.map +1 -0
  56. package/dist/module/communication/strategies/email/gmail-smtp.strategy.d.ts +5 -0
  57. package/dist/module/communication/strategies/email/gmail-smtp.strategy.js +49 -0
  58. package/dist/module/communication/strategies/email/gmail-smtp.strategy.js.map +1 -0
  59. package/dist/module/communication/strategies/email/outlook-api.strategy.d.ts +5 -0
  60. package/dist/module/communication/strategies/email/outlook-api.strategy.js +44 -0
  61. package/dist/module/communication/strategies/email/outlook-api.strategy.js.map +1 -0
  62. package/dist/module/communication/strategies/gmail-smtp.strategy.d.ts +5 -0
  63. package/dist/module/communication/strategies/gmail-smtp.strategy.js +61 -0
  64. package/dist/module/communication/strategies/gmail-smtp.strategy.js.map +1 -0
  65. package/dist/module/communication/strategies/gmail.strategy.d.ts +5 -0
  66. package/dist/module/communication/strategies/gmail.strategy.js +71 -0
  67. package/dist/module/communication/strategies/gmail.strategy.js.map +1 -0
  68. package/dist/module/communication/strategies/knowlarity.strategy.d.ts +6 -0
  69. package/dist/module/communication/strategies/knowlarity.strategy.js +115 -0
  70. package/dist/module/communication/strategies/knowlarity.strategy.js.map +1 -0
  71. package/dist/module/communication/strategies/outlook-smtp.strategy.d.ts +5 -0
  72. package/dist/module/communication/strategies/outlook-smtp.strategy.js +66 -0
  73. package/dist/module/communication/strategies/outlook-smtp.strategy.js.map +1 -0
  74. package/dist/module/communication/strategies/outlook.strategy.d.ts +5 -0
  75. package/dist/module/communication/strategies/outlook.strategy.js +64 -0
  76. package/dist/module/communication/strategies/outlook.strategy.js.map +1 -0
  77. package/dist/module/communication/strategies/sms/knowlarity.strategy.d.ts +5 -0
  78. package/dist/module/communication/strategies/sms/knowlarity.strategy.js +44 -0
  79. package/dist/module/communication/strategies/sms/knowlarity.strategy.js.map +1 -0
  80. package/dist/module/communication/strategies/sms/twilio.strategy.d.ts +5 -0
  81. package/dist/module/communication/strategies/sms/twilio.strategy.js +44 -0
  82. package/dist/module/communication/strategies/sms/twilio.strategy.js.map +1 -0
  83. package/dist/module/communication/strategies/sms.strategy.d.ts +5 -0
  84. package/dist/module/communication/strategies/sms.strategy.js +50 -0
  85. package/dist/module/communication/strategies/sms.strategy.js.map +1 -0
  86. package/dist/module/communication/strategies/telephone/knowlarity-voice.strategy.d.ts +5 -0
  87. package/dist/module/communication/strategies/telephone/knowlarity-voice.strategy.js +44 -0
  88. package/dist/module/communication/strategies/telephone/knowlarity-voice.strategy.js.map +1 -0
  89. package/dist/module/communication/strategies/whatsapp/whatsapp-cloud.strategy.d.ts +5 -0
  90. package/dist/module/communication/strategies/whatsapp/whatsapp-cloud.strategy.js +47 -0
  91. package/dist/module/communication/strategies/whatsapp/whatsapp-cloud.strategy.js.map +1 -0
  92. package/dist/module/communication/strategies/whatsapp.strategy.d.ts +5 -0
  93. package/dist/module/communication/strategies/whatsapp.strategy.js +58 -0
  94. package/dist/module/communication/strategies/whatsapp.strategy.js.map +1 -0
  95. package/dist/module/meta/entity.module.js +2 -1
  96. package/dist/module/meta/entity.module.js.map +1 -1
  97. package/dist/module/user/controller/login.controller.d.ts +4 -2
  98. package/dist/module/user/controller/login.controller.js +26 -4
  99. package/dist/module/user/controller/login.controller.js.map +1 -1
  100. package/dist/module/workflow/service/populate-workflow.service.js +2 -2
  101. package/dist/module/workflow/service/populate-workflow.service.js.map +1 -1
  102. package/dist/module/workflow/service/stage.service.js +1 -1
  103. package/dist/module/workflow/service/stage.service.js.map +1 -1
  104. package/dist/tsconfig.build.tsbuildinfo +1 -1
  105. package/package.json +4 -1
  106. package/src/app.module.ts +2 -0
  107. package/src/constant/global.constant.ts +1 -1
  108. package/src/module/auth/strategies/google.strategy.ts +1 -1
  109. package/src/module/communication/communication.module.ts +77 -0
  110. package/src/module/communication/controller/communication.controller.ts +122 -0
  111. package/src/module/communication/dto/create-config.dto.ts +234 -0
  112. package/src/module/communication/entity/communication-config.entity.ts +80 -0
  113. package/src/module/communication/entity/communication-hub.entity.ts +77 -0
  114. package/src/module/communication/examples/usage.example.ts +169 -0
  115. package/src/module/communication/factories/base.factory.ts +7 -0
  116. package/src/module/communication/factories/communication.factory.ts +103 -0
  117. package/src/module/communication/factories/email.factory.ts +51 -0
  118. package/src/module/communication/factories/sms.factory.ts +41 -0
  119. package/src/module/communication/factories/telephone.factory.ts +34 -0
  120. package/src/module/communication/factories/whatsapp.factory.ts +34 -0
  121. package/src/module/communication/service/communication.service.ts +1118 -0
  122. package/src/module/communication/service/oauth.service.ts +203 -0
  123. package/src/module/communication/strategies/communication.strategy.ts +23 -0
  124. package/src/module/communication/strategies/email/gmail-api.strategy.ts +161 -0
  125. package/src/module/communication/strategies/email/gmail-smtp.strategy.ts +51 -0
  126. package/src/module/communication/strategies/email/outlook-api.strategy.ts +44 -0
  127. package/src/module/communication/strategies/gmail-smtp.strategy.ts +64 -0
  128. package/src/module/communication/strategies/gmail.strategy.ts +68 -0
  129. package/src/module/communication/strategies/knowlarity.strategy.ts +124 -0
  130. package/src/module/communication/strategies/outlook-smtp.strategy.ts +69 -0
  131. package/src/module/communication/strategies/outlook.strategy.ts +57 -0
  132. package/src/module/communication/strategies/sms/knowlarity.strategy.ts +44 -0
  133. package/src/module/communication/strategies/sms/twilio.strategy.ts +44 -0
  134. package/src/module/communication/strategies/sms.strategy.ts +44 -0
  135. package/src/module/communication/strategies/telephone/knowlarity-voice.strategy.ts +44 -0
  136. package/src/module/communication/strategies/whatsapp/whatsapp-cloud.strategy.ts +49 -0
  137. package/src/module/communication/strategies/whatsapp.strategy.ts +53 -0
  138. package/src/module/meta/entity.module.ts +2 -1
  139. package/src/module/user/controller/login.controller.ts +34 -3
  140. package/src/module/workflow/service/populate-workflow.service.ts +3 -3
  141. package/src/module/workflow/service/stage.service.ts +1 -1
  142. package/src/resources/dev.properties.yaml +1 -0
@@ -0,0 +1,1118 @@
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import { InjectRepository } from '@nestjs/typeorm';
3
+ import { Repository } from 'typeorm';
4
+ import { ConfigService } from '@nestjs/config';
5
+ import { google } from 'googleapis';
6
+ import {
7
+ CommunicationHub,
8
+ CommunicationConfigType,
9
+ } from '../entity/communication-hub.entity';
10
+ import { CommunicationConfig } from '../entity/communication-config.entity';
11
+ import { CommunicationFactory } from '../factories/communication.factory';
12
+ import { CommunicationResult } from '../strategies/communication.strategy';
13
+ import { GmailApiStrategy } from '../strategies/email/gmail-api.strategy';
14
+
15
+ export interface SendMessageDto {
16
+ levelId: number;
17
+ levelType: string;
18
+ to: string;
19
+ message: string;
20
+ mode?: CommunicationConfigType;
21
+ priority?: number;
22
+ }
23
+
24
+ export interface GenericMessageDto {
25
+ levelId: number;
26
+ levelType: string;
27
+ to: string | string[];
28
+ message: string;
29
+ subject?: string;
30
+ type?: 'EMAIL' | 'SMS' | 'WA' | 'TELEPHONE';
31
+ priority?: 'high' | 'medium' | 'low';
32
+ cc?: string | string[];
33
+ bcc?: string | string[];
34
+ html?: string;
35
+ attachments?: any[];
36
+ mediaUrl?: string;
37
+ templateId?: string;
38
+ variables?: Record<string, any>;
39
+ }
40
+
41
+ export interface BulkMessageDto {
42
+ levelId: number;
43
+ levelType: string;
44
+ recipients: string[];
45
+ message: string;
46
+ subject?: string;
47
+ type?: 'EMAIL' | 'SMS' | 'WA' | 'TELEPHONE';
48
+ priority?: 'high' | 'medium' | 'low';
49
+ html?: string;
50
+ templateId?: string;
51
+ variables?: Record<string, any>;
52
+ batchSize?: number;
53
+ }
54
+
55
+ export interface CommunicationHubWithConfig extends CommunicationHub {
56
+ config: CommunicationConfig;
57
+ }
58
+
59
+ interface GmailOAuthState {
60
+ levelId: number;
61
+ levelType: string;
62
+ email?: string;
63
+ timestamp: number;
64
+ }
65
+
66
+ export interface GmailSSOResult {
67
+ hubId: number;
68
+ configId: number;
69
+ }
70
+
71
+ @Injectable()
72
+ export class CommunicationService {
73
+ private readonly logger = new Logger(CommunicationService.name);
74
+ private readonly gmailOAuthStates = new Map<string, GmailOAuthState>();
75
+
76
+ constructor(
77
+ @InjectRepository(CommunicationHub)
78
+ private readonly hubRepository: Repository<CommunicationHub>,
79
+
80
+ @InjectRepository(CommunicationConfig)
81
+ private readonly configRepository: Repository<CommunicationConfig>,
82
+
83
+ private readonly communicationFactory: CommunicationFactory,
84
+ private readonly gmailApiStrategy: GmailApiStrategy,
85
+ private readonly configService: ConfigService,
86
+ ) {}
87
+
88
+ async sendMessage({
89
+ levelId,
90
+ levelType,
91
+ to,
92
+ message,
93
+ mode,
94
+ priority = 1,
95
+ }: SendMessageDto): Promise<CommunicationResult> {
96
+ try {
97
+ // Get active communication hubs for the level
98
+ const hubs = await this.getActiveHubs(levelId, levelType, mode);
99
+
100
+ if (!hubs.length) {
101
+ throw new Error(
102
+ `No active communication configuration found for ${levelType} ${levelId}`,
103
+ );
104
+ }
105
+
106
+ // Sort by priority if provided
107
+ const sortedHubs = this.sortHubsByPriority(hubs, priority);
108
+
109
+ // Try each hub until one succeeds
110
+ for (const hub of sortedHubs) {
111
+ try {
112
+ const result = await this.sendViaHub(hub, to, message);
113
+ if (result.success) {
114
+ this.logger.log(
115
+ `Message sent successfully via ${hub.provider}/${hub.service}`,
116
+ );
117
+ return result;
118
+ }
119
+
120
+ this.logger.warn(
121
+ `Failed to send via ${hub.provider}/${hub.service}: ${result.error}`,
122
+ );
123
+ } catch (error) {
124
+ this.logger.error(
125
+ `Error sending via ${hub.provider}/${hub.service}:`,
126
+ error.message,
127
+ );
128
+ continue;
129
+ }
130
+ }
131
+
132
+ throw new Error('All communication providers failed');
133
+ } catch (error) {
134
+ this.logger.error('Communication service error:', error.message);
135
+ throw error;
136
+ }
137
+ }
138
+
139
+ async getActiveHubs(
140
+ levelId: number,
141
+ levelType: string,
142
+ mode?: CommunicationConfigType,
143
+ ): Promise<CommunicationHubWithConfig[]> {
144
+ let query = this.hubRepository
145
+ .createQueryBuilder('hub')
146
+ .leftJoin('communication_config', 'config', 'config.id = hub.config_id')
147
+ .select([
148
+ 'hub.id',
149
+ 'hub.status',
150
+ 'hub.config_id',
151
+ 'hub.communication_config_type',
152
+ 'hub.service',
153
+ 'hub.provider',
154
+ 'hub.priority',
155
+ 'hub.is_default',
156
+ 'hub.created_at',
157
+ 'hub.updated_at',
158
+ 'config.id as config_id',
159
+ 'config.config_json as config_json',
160
+ 'config.created_at as config_created_at',
161
+ 'config.updated_at as config_updated_at'
162
+ ])
163
+ .where('hub.level_id = :levelId', { levelId })
164
+ .andWhere('hub.level_type = :levelType', { levelType })
165
+ .andWhere('hub.status = :status', { status: 1 });
166
+
167
+ if (mode) {
168
+ query = query.andWhere('hub.communication_config_type = :mode', { mode });
169
+ }
170
+
171
+ const results = await query.getRawMany();
172
+
173
+ // Transform the raw results into the expected format
174
+ const hubsWithConfig: CommunicationHubWithConfig[] = results.map(row => ({
175
+ id: row.hub_id,
176
+ level_id: row.hub_level_id,
177
+ level_type: row.hub_level_type,
178
+ status: row.hub_status,
179
+ config_id: row.hub_config_id,
180
+ communication_config_type: row.hub_communication_config_type,
181
+ service: row.hub_service,
182
+ provider: row.hub_provider,
183
+ priority: row.hub_priority || 1,
184
+ is_default: row.hub_is_default || false,
185
+ created_at: row.hub_created_at,
186
+ updated_at: row.hub_updated_at,
187
+ config: {
188
+ id: row.config_id,
189
+ config_json: row.config_json,
190
+ created_at: row.config_created_at,
191
+ updated_at: row.config_updated_at,
192
+ },
193
+ }));
194
+
195
+ return hubsWithConfig;
196
+ }
197
+
198
+ private sortHubsByPriority(
199
+ hubs: CommunicationHubWithConfig[],
200
+ _priority: number,
201
+ ): CommunicationHubWithConfig[] {
202
+ // For now, just return as-is. In the future, implement priority logic
203
+ return hubs;
204
+ }
205
+
206
+ private async sendViaHub(
207
+ hub: CommunicationHubWithConfig,
208
+ to: string,
209
+ message: string,
210
+ ): Promise<CommunicationResult> {
211
+ const strategy = this.communicationFactory.create(
212
+ hub.communication_config_type,
213
+ hub.service,
214
+ hub.provider,
215
+ );
216
+
217
+ return strategy.sendMessage(to, message, hub.config.config_json);
218
+ }
219
+
220
+ async createCommunicationConfig(
221
+ levelId: number,
222
+ levelType: string,
223
+ configType: CommunicationConfigType,
224
+ service: string,
225
+ provider: string,
226
+ config: any,
227
+ priority?: number,
228
+ is_default?: boolean,
229
+ ): Promise<CommunicationHub | { authUrl: string; state: string; message: string }> {
230
+ // Validate service/provider combination using supported combinations
231
+ const supportedCombinations = await this.getSupportedCombinations();
232
+ const isValidCombination = supportedCombinations.some(
233
+ combo => combo.mode === configType &&
234
+ combo.service.toLowerCase() === service.toLowerCase() &&
235
+ combo.provider.toLowerCase() === provider.toLowerCase()
236
+ );
237
+
238
+ if (!isValidCombination) {
239
+ throw new Error(`Unsupported combination: ${configType}/${service}/${provider}`);
240
+ }
241
+
242
+ // Check if this requires OAuth flow
243
+ const requiresOAuth = this.requiresOAuthFlow(configType, service, provider, config);
244
+
245
+ if (requiresOAuth) {
246
+ // Generate OAuth URL and return it instead of creating config immediately
247
+ return await this.generateOAuthUrl(levelId, levelType, configType, service, provider, config, priority, is_default);
248
+ }
249
+
250
+ // Direct config creation (non-OAuth flow)
251
+ return await this.createDirectConfig(levelId, levelType, configType, service, provider, config, priority, is_default);
252
+ }
253
+
254
+ async getSupportedCombinations(): Promise<
255
+ {
256
+ mode: string;
257
+ service: string;
258
+ provider: string;
259
+ }[]
260
+ > {
261
+ return this.communicationFactory.getAllSupportedCombinations();
262
+ }
263
+
264
+ async getLevelConfigs(
265
+ levelId: number,
266
+ levelType: string,
267
+ ): Promise<CommunicationHub[]> {
268
+ return this.hubRepository.find({
269
+ where: { level_id: levelId, level_type: levelType },
270
+ order: { created_at: 'DESC' },
271
+ });
272
+ }
273
+
274
+ async updateConfigStatus(hubId: number, status: number): Promise<void> {
275
+ await this.hubRepository.update(hubId, { status });
276
+ }
277
+
278
+ async sendGenericMessage(messageDto: GenericMessageDto): Promise<CommunicationResult> {
279
+ try {
280
+ const {
281
+ levelId,
282
+ levelType,
283
+ to,
284
+ message,
285
+ type,
286
+ priority = 'medium',
287
+ subject,
288
+ html,
289
+ cc,
290
+ bcc,
291
+ attachments,
292
+ mediaUrl,
293
+ templateId,
294
+ variables
295
+ } = messageDto;
296
+
297
+ // Auto-detect communication type if not specified
298
+ const communicationType = this.detectCommunicationType(to, type);
299
+
300
+ // Get active hubs for the detected type
301
+ const hubs = await this.getActiveHubs(levelId, levelType, communicationType);
302
+
303
+ if (!hubs.length) {
304
+ throw new Error(`No active ${communicationType} configuration found for ${levelType} ${levelId}`);
305
+ }
306
+
307
+ // Sort hubs by priority and default preference
308
+ const sortedHubs = this.sortHubsByPriorityAndDefault(hubs, priority);
309
+
310
+ // Prepare enhanced config with additional parameters
311
+ const enhancedConfig = {
312
+ subject,
313
+ html,
314
+ cc,
315
+ bcc,
316
+ attachments,
317
+ mediaUrl,
318
+ };
319
+
320
+ // Handle multiple recipients by sending to each individually
321
+ if (Array.isArray(to)) {
322
+ const results: CommunicationResult[] = [];
323
+ let hasSuccess = false;
324
+
325
+ for (const recipient of to) {
326
+ for (const hub of sortedHubs) {
327
+ try {
328
+ const strategy = this.communicationFactory.create(
329
+ hub.communication_config_type,
330
+ hub.service,
331
+ hub.provider,
332
+ );
333
+
334
+ // Process template if provided
335
+ let processedMessage = message;
336
+ if (templateId && variables) {
337
+ processedMessage = this.processTemplate(message, variables);
338
+ }
339
+
340
+ // Merge config with enhanced parameters
341
+ const finalConfig = { ...hub.config.config_json, ...enhancedConfig };
342
+
343
+ const result = await strategy.sendMessage(recipient, processedMessage, finalConfig);
344
+
345
+ if (result.success) {
346
+ this.logger.log(
347
+ `Generic message sent successfully via ${hub.provider}/${hub.service} to ${recipient}`,
348
+ );
349
+ results.push(result);
350
+ hasSuccess = true;
351
+ break; // Move to next recipient
352
+ }
353
+
354
+ this.logger.warn(
355
+ `Failed to send via ${hub.provider}/${hub.service} to ${recipient}: ${result.error}`,
356
+ );
357
+ } catch (error) {
358
+ this.logger.error(
359
+ `Error sending via ${hub.provider}/${hub.service} to ${recipient}:`,
360
+ error.message,
361
+ );
362
+ continue;
363
+ }
364
+ }
365
+ }
366
+
367
+ if (hasSuccess) {
368
+ return {
369
+ success: true,
370
+ messageId: results.map(r => r.messageId).join(','),
371
+ provider: 'multiple',
372
+ service: 'multiple',
373
+ timestamp: new Date(),
374
+ };
375
+ }
376
+ } else {
377
+ // Handle single recipient
378
+ for (const hub of sortedHubs) {
379
+ try {
380
+ const strategy = this.communicationFactory.create(
381
+ hub.communication_config_type,
382
+ hub.service,
383
+ hub.provider,
384
+ );
385
+
386
+ // Process template if provided
387
+ let processedMessage = message;
388
+ if (templateId && variables) {
389
+ processedMessage = this.processTemplate(message, variables);
390
+ }
391
+
392
+ // Merge config with enhanced parameters
393
+ const finalConfig = { ...hub.config.config_json, ...enhancedConfig };
394
+
395
+ const result = await strategy.sendMessage(to, processedMessage, finalConfig);
396
+
397
+ if (result.success) {
398
+ this.logger.log(
399
+ `Generic message sent successfully via ${hub.provider}/${hub.service} to ${to}`,
400
+ );
401
+ return result;
402
+ }
403
+
404
+ this.logger.warn(
405
+ `Failed to send via ${hub.provider}/${hub.service}: ${result.error}`,
406
+ );
407
+ } catch (error) {
408
+ this.logger.error(
409
+ `Error sending via ${hub.provider}/${hub.service}:`,
410
+ error.message,
411
+ );
412
+ continue;
413
+ }
414
+ }
415
+ }
416
+
417
+ throw new Error('All communication providers failed');
418
+ } catch (error) {
419
+ this.logger.error('Generic communication service error:', error.message);
420
+ throw error;
421
+ }
422
+ }
423
+
424
+ async sendBulkMessage(bulkDto: BulkMessageDto): Promise<{ results: CommunicationResult[]; summary: any }> {
425
+ try {
426
+ const {
427
+ levelId,
428
+ levelType,
429
+ recipients,
430
+ message,
431
+ type,
432
+ priority = 'low',
433
+ subject,
434
+ html,
435
+ templateId,
436
+ variables,
437
+ batchSize = 10
438
+ } = bulkDto;
439
+
440
+ const results: CommunicationResult[] = [];
441
+ const batches = this.chunkArray(recipients, batchSize);
442
+
443
+ for (let i = 0; i < batches.length; i++) {
444
+ const batch = batches[i];
445
+ this.logger.log(`Processing batch ${i + 1}/${batches.length} with ${batch.length} recipients`);
446
+
447
+ const batchPromises = batch.map(async (recipient) => {
448
+ try {
449
+ const messageDto: GenericMessageDto = {
450
+ levelId,
451
+ levelType,
452
+ to: recipient,
453
+ message,
454
+ type,
455
+ priority,
456
+ subject,
457
+ html,
458
+ templateId,
459
+ variables,
460
+ };
461
+
462
+ return await this.sendGenericMessage(messageDto);
463
+ } catch (error) {
464
+ return {
465
+ success: false,
466
+ provider: 'unknown',
467
+ service: 'unknown',
468
+ error: error.message,
469
+ timestamp: new Date(),
470
+ } as CommunicationResult;
471
+ }
472
+ });
473
+
474
+ const batchResults = await Promise.allSettled(batchPromises);
475
+ const processedResults = batchResults.map((result) => {
476
+ if (result.status === 'fulfilled') {
477
+ return result.value;
478
+ } else {
479
+ return {
480
+ success: false,
481
+ provider: 'unknown',
482
+ service: 'unknown',
483
+ error: result.reason?.message || 'Unknown error',
484
+ timestamp: new Date(),
485
+ } as CommunicationResult;
486
+ }
487
+ });
488
+
489
+ results.push(...processedResults);
490
+
491
+ // Rate limiting delay between batches
492
+ if (i < batches.length - 1) {
493
+ await new Promise(resolve => setTimeout(resolve, 1000));
494
+ }
495
+ }
496
+
497
+ const summary = {
498
+ total: results.length,
499
+ successful: results.filter(r => r.success).length,
500
+ failed: results.filter(r => !r.success).length,
501
+ successRate: ((results.filter(r => r.success).length / results.length) * 100).toFixed(2) + '%',
502
+ };
503
+
504
+ this.logger.log(`Bulk message completed: ${summary.successful}/${summary.total} successful`);
505
+
506
+ return { results, summary };
507
+ } catch (error) {
508
+ this.logger.error('Bulk communication service error:', error.message);
509
+ throw error;
510
+ }
511
+ }
512
+
513
+ async scheduleMessage(scheduledDto: any): Promise<{ scheduled: boolean; scheduleId?: string }> {
514
+ // For now, return a placeholder. In production, integrate with a job queue like Bull or Agenda
515
+ this.logger.log(`Message scheduled for ${scheduledDto.scheduleFor}`);
516
+
517
+ return {
518
+ scheduled: true,
519
+ scheduleId: `sched_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
520
+ };
521
+ }
522
+
523
+ async sendTemplateMessage(templateDto: any): Promise<CommunicationResult> {
524
+ // Get template content (integrate with your template system)
525
+ const template = await this.getTemplate(templateDto.templateId);
526
+
527
+ const processedMessage = this.processTemplate(template.content, templateDto.variables);
528
+ const processedSubject = template.subject ? this.processTemplate(template.subject, templateDto.variables) : undefined;
529
+
530
+ const messageDto: GenericMessageDto = {
531
+ levelId: templateDto.levelId,
532
+ levelType: templateDto.levelType,
533
+ to: templateDto.to,
534
+ message: processedMessage,
535
+ subject: processedSubject,
536
+ type: templateDto.type,
537
+ templateId: templateDto.templateId,
538
+ variables: templateDto.variables,
539
+ };
540
+
541
+ return this.sendGenericMessage(messageDto);
542
+ }
543
+
544
+ private detectCommunicationType(to: string | string[], type?: string): CommunicationConfigType {
545
+ if (type) {
546
+ return type as CommunicationConfigType;
547
+ }
548
+
549
+ const recipient = Array.isArray(to) ? to[0] : to;
550
+
551
+ // Email detection
552
+ if (recipient.includes('@')) {
553
+ return CommunicationConfigType.EMAIL;
554
+ }
555
+
556
+ // Phone number detection (basic)
557
+ if (/^\+?[\d\s\-\(\)]+$/.test(recipient)) {
558
+ // Could be SMS or WhatsApp, default to SMS
559
+ return CommunicationConfigType.SMS;
560
+ }
561
+
562
+ // Default to EMAIL if unsure
563
+ return CommunicationConfigType.EMAIL;
564
+ }
565
+
566
+ private sortHubsByPriorityAndDefault(
567
+ hubs: CommunicationHubWithConfig[],
568
+ _priority: string,
569
+ ): CommunicationHubWithConfig[] {
570
+ return hubs.sort((a, b) => {
571
+ // First sort by default (true comes first)
572
+ if (a.is_default !== b.is_default) {
573
+ return b.is_default ? 1 : -1;
574
+ }
575
+
576
+ // Then sort by priority (lower number = higher priority)
577
+ if (a.priority !== b.priority) {
578
+ return a.priority - b.priority;
579
+ }
580
+
581
+ // Finally sort by created_at descending (newest first)
582
+ return new Date(b.created_at).getTime() - new Date(a.created_at).getTime();
583
+ });
584
+ }
585
+
586
+ private processTemplate(template: string, variables: Record<string, any>): string {
587
+ return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
588
+ const value = variables[key];
589
+ return value !== undefined ? String(value) : match;
590
+ });
591
+ }
592
+
593
+ private async getTemplate(templateId: string) {
594
+ // Placeholder for template system integration
595
+ // In production, this would connect to your template database/service
596
+ return {
597
+ id: templateId,
598
+ subject: 'Default Subject {{variable}}',
599
+ content: 'Hello {{name}}, this is a template message with {{variable}}.',
600
+ };
601
+ }
602
+
603
+ private chunkArray<T>(array: T[], chunkSize: number): T[][] {
604
+ const chunks: T[][] = [];
605
+ for (let i = 0; i < array.length; i += chunkSize) {
606
+ chunks.push(array.slice(i, i + chunkSize));
607
+ }
608
+ return chunks;
609
+ }
610
+
611
+ async initGmailOAuth(
612
+ levelId: number,
613
+ levelType: string,
614
+ email?: string,
615
+ ): Promise<{ authUrl: string; state: string }> {
616
+ try {
617
+ // Get system OAuth credentials from config
618
+ const clientId = this.configService.get<string>('CLIENT_ID');
619
+ const clientSecret = this.configService.get<string>('CLIENT_SECRET');
620
+
621
+ if (!clientId || !clientSecret) {
622
+ throw new Error('Gmail OAuth credentials not configured');
623
+ }
624
+
625
+ const state = this.generateSecureState();
626
+ // Use the existing registered callback URL
627
+ const callbackUrl = this.configService.get<string>('CALLBACK_URL') || 'http://localhost:5001/auth/google/callback';
628
+
629
+ this.gmailOAuthStates.set(state, {
630
+ levelId,
631
+ levelType,
632
+ email,
633
+ timestamp: Date.now(),
634
+ });
635
+
636
+ // Auto-cleanup after 10 minutes
637
+ setTimeout(() => {
638
+ this.gmailOAuthStates.delete(state);
639
+ }, 10 * 60 * 1000);
640
+
641
+ const oauth2Client = new google.auth.OAuth2(
642
+ clientId,
643
+ clientSecret,
644
+ callbackUrl,
645
+ );
646
+
647
+ const scopes = [
648
+ 'https://www.googleapis.com/auth/gmail.send',
649
+ 'https://www.googleapis.com/auth/gmail.readonly',
650
+ 'https://www.googleapis.com/auth/userinfo.email',
651
+ ];
652
+
653
+ const authUrl = oauth2Client.generateAuthUrl({
654
+ access_type: 'offline',
655
+ scope: scopes,
656
+ state: `gmail_config:${state}`, // Prefix to identify Gmail config requests
657
+ prompt: 'consent',
658
+ login_hint: email,
659
+ });
660
+
661
+ return { authUrl, state };
662
+ } catch (error) {
663
+ this.logger.error('Error initializing Gmail OAuth:', error.message);
664
+ throw new Error('Failed to initialize Gmail OAuth');
665
+ }
666
+ }
667
+
668
+ async handleGmailOAuthCallback(
669
+ code: string,
670
+ state: string,
671
+ ): Promise<GmailSSOResult> {
672
+ try {
673
+ const oauthState = this.gmailOAuthStates.get(state);
674
+ if (!oauthState) {
675
+ throw new Error('Invalid or expired OAuth state');
676
+ }
677
+
678
+ this.gmailOAuthStates.delete(state);
679
+
680
+ if (Date.now() - oauthState.timestamp > 10 * 60 * 1000) {
681
+ throw new Error('OAuth state expired');
682
+ }
683
+
684
+ // Get system OAuth credentials
685
+ const clientId = this.configService.get<string>('CLIENT_ID');
686
+ const clientSecret = this.configService.get<string>('CLIENT_SECRET');
687
+ const callbackUrl = this.configService.get<string>('CALLBACK_URL') || 'http://localhost:5001/auth/google/callback';
688
+
689
+ const oauth2Client = new google.auth.OAuth2(
690
+ clientId,
691
+ clientSecret,
692
+ callbackUrl,
693
+ );
694
+
695
+ const { tokens } = await oauth2Client.getToken(code);
696
+
697
+ if (!tokens.access_token) {
698
+ throw new Error('Failed to obtain access token');
699
+ }
700
+
701
+ // Get user email from Google
702
+ oauth2Client.setCredentials(tokens);
703
+ const oauth2 = google.oauth2({ version: 'v2', auth: oauth2Client });
704
+ const userInfo = await oauth2.userinfo.get();
705
+ const email = userInfo.data.email;
706
+
707
+ if (!email) {
708
+ throw new Error('Failed to get user email');
709
+ }
710
+
711
+ // Verify email matches if hint was provided
712
+ if (oauthState.email && oauthState.email !== email) {
713
+ throw new Error('Email mismatch with OAuth hint');
714
+ }
715
+
716
+ const gmailConfig = {
717
+ clientId,
718
+ clientSecret,
719
+ email: email,
720
+ accessToken: tokens.access_token,
721
+ refreshToken: tokens.refresh_token,
722
+ scope: tokens.scope,
723
+ tokenType: tokens.token_type,
724
+ expiryDate: tokens.expiry_date,
725
+ };
726
+
727
+ // Create communication config
728
+ const communicationConfig = this.configRepository.create({
729
+ config_json: gmailConfig as any,
730
+ });
731
+ const savedConfig = await this.configRepository.save(communicationConfig);
732
+
733
+ // Create communication hub
734
+ const hub = this.hubRepository.create({
735
+ level_id: oauthState.levelId,
736
+ level_type: oauthState.levelType,
737
+ config_id: savedConfig.id,
738
+ communication_config_type: CommunicationConfigType.EMAIL,
739
+ service: 'API',
740
+ provider: 'gmail',
741
+ status: 1,
742
+ });
743
+
744
+ const savedHub = await this.hubRepository.save(hub);
745
+
746
+ this.logger.log(
747
+ `Gmail OAuth configuration created successfully for ${oauthState.levelType} ${oauthState.levelId} and email ${email}`,
748
+ );
749
+
750
+ return {
751
+ hubId: savedHub.id,
752
+ configId: savedConfig.id,
753
+ };
754
+ } catch (error) {
755
+ this.logger.error('Error handling Gmail OAuth callback:', error.message);
756
+ throw new Error(`Failed to complete Gmail OAuth: ${error.message}`);
757
+ }
758
+ }
759
+
760
+ async handleGmailTokensCallback(
761
+ email: string,
762
+ accessToken: string,
763
+ refreshToken: string,
764
+ state: string,
765
+ ): Promise<GmailSSOResult> {
766
+ try {
767
+ const oauthState = this.gmailOAuthStates.get(state);
768
+ if (!oauthState) {
769
+ throw new Error('Invalid or expired OAuth state');
770
+ }
771
+
772
+ this.gmailOAuthStates.delete(state);
773
+
774
+ if (Date.now() - oauthState.timestamp > 10 * 60 * 1000) {
775
+ throw new Error('OAuth state expired');
776
+ }
777
+
778
+ // Verify email matches if hint was provided
779
+ if (oauthState.email && oauthState.email !== email) {
780
+ throw new Error('Email mismatch with OAuth hint');
781
+ }
782
+
783
+ // Pure SSO configuration - no client credentials stored per user
784
+ const gmailConfig = {
785
+ email: email,
786
+ accessToken: accessToken,
787
+ refreshToken: refreshToken,
788
+ authMethod: 'GOOGLE_SSO',
789
+ scopes: [
790
+ 'https://www.googleapis.com/auth/gmail.send',
791
+ 'https://www.googleapis.com/auth/userinfo.email',
792
+ ],
793
+ };
794
+
795
+ // Create communication config
796
+ const communicationConfig = this.configRepository.create({
797
+ config_json: gmailConfig as any,
798
+ });
799
+ const savedConfig = await this.configRepository.save(communicationConfig);
800
+
801
+ // Create communication hub
802
+ const hub = this.hubRepository.create({
803
+ level_id: oauthState.levelId,
804
+ level_type: oauthState.levelType,
805
+ config_id: savedConfig.id,
806
+ communication_config_type: CommunicationConfigType.EMAIL,
807
+ service: 'API',
808
+ provider: 'gmail',
809
+ status: 1,
810
+ });
811
+
812
+ const savedHub = await this.hubRepository.save(hub);
813
+
814
+ this.logger.log(
815
+ `Gmail tokens configuration created successfully for ${oauthState.levelType} ${oauthState.levelId} and email ${email}`,
816
+ );
817
+
818
+ return {
819
+ hubId: savedHub.id,
820
+ configId: savedConfig.id,
821
+ };
822
+ } catch (error) {
823
+ this.logger.error('Error handling Gmail tokens callback:', error.message);
824
+ throw new Error(`Failed to complete Gmail tokens callback: ${error.message}`);
825
+ }
826
+ }
827
+
828
+ async testGmailConfig(hubId: number): Promise<{ success: boolean; error?: string }> {
829
+ try {
830
+ const hub = await this.hubRepository.findOne({
831
+ where: { id: hubId },
832
+ });
833
+
834
+ if (!hub) {
835
+ throw new Error('Communication hub not found');
836
+ }
837
+
838
+ const config = await this.configRepository.findOne({
839
+ where: { id: hub.config_id },
840
+ });
841
+
842
+ if (!config) {
843
+ throw new Error('Configuration not found');
844
+ }
845
+
846
+ const isValid = await this.gmailApiStrategy.validateConnection(config.config_json);
847
+
848
+ if (!isValid) {
849
+ return { success: false, error: 'Gmail configuration is invalid or expired' };
850
+ }
851
+
852
+ return { success: true };
853
+ } catch (error) {
854
+ this.logger.error('Error testing Gmail config:', error.message);
855
+ return { success: false, error: error.message };
856
+ }
857
+ }
858
+
859
+ private generateSecureState(): string {
860
+ return Math.random().toString(36).substring(2) + Date.now().toString(36);
861
+ }
862
+
863
+ private requiresOAuthFlow(
864
+ configType: CommunicationConfigType,
865
+ service: string,
866
+ provider: string,
867
+ config: any
868
+ ): boolean {
869
+ // Check if config indicates OAuth is needed (missing tokens or explicit OAuth request)
870
+ const key = `${configType.toLowerCase()}_${service.toLowerCase()}_${provider.toLowerCase()}`;
871
+
872
+ switch (key) {
873
+ case 'email_api_gmail':
874
+ // Requires OAuth if no access token provided or OAuth explicitly requested
875
+ return !config.accessToken || config.useOAuth === true;
876
+
877
+ case 'email_api_outlook':
878
+ // Requires OAuth if no access token provided or OAuth explicitly requested
879
+ return !config.accessToken || config.useOAuth === true;
880
+
881
+ default:
882
+ // Most other providers don't require OAuth
883
+ return false;
884
+ }
885
+ }
886
+
887
+ private async generateOAuthUrl(
888
+ levelId: number,
889
+ levelType: string,
890
+ configType: CommunicationConfigType,
891
+ service: string,
892
+ provider: string,
893
+ config: any,
894
+ priority?: number,
895
+ is_default?: boolean
896
+ ): Promise<{ authUrl: string; state: string; message: string }> {
897
+ const key = `${configType.toLowerCase()}_${service.toLowerCase()}_${provider.toLowerCase()}`;
898
+
899
+ switch (key) {
900
+ case 'email_api_gmail':
901
+ const gmailResult = await this.initGmailOAuth(levelId, levelType, config.email);
902
+ return {
903
+ authUrl: gmailResult.authUrl,
904
+ state: gmailResult.state,
905
+ message: 'Please complete Gmail OAuth authorization. Configuration will be created automatically after authorization.'
906
+ };
907
+
908
+ case 'email_api_outlook':
909
+ const outlookResult = await this.initOutlookOAuth(levelId, levelType, config.email);
910
+ return {
911
+ authUrl: outlookResult.authUrl,
912
+ state: outlookResult.state,
913
+ message: 'Please complete Outlook OAuth authorization. Configuration will be created automatically after authorization.'
914
+ };
915
+
916
+ default:
917
+ throw new Error(`OAuth not supported for ${configType}/${service}/${provider}`);
918
+ }
919
+ }
920
+
921
+ private async createDirectConfig(
922
+ levelId: number,
923
+ levelType: string,
924
+ configType: CommunicationConfigType,
925
+ service: string,
926
+ provider: string,
927
+ config: any,
928
+ priority?: number,
929
+ is_default?: boolean
930
+ ): Promise<CommunicationHub> {
931
+ // If setting as default, remove default from other configurations of same type
932
+ if (is_default) {
933
+ await this.hubRepository.update(
934
+ {
935
+ level_id: levelId,
936
+ level_type: levelType,
937
+ communication_config_type: configType,
938
+ },
939
+ { is_default: false }
940
+ );
941
+ }
942
+
943
+ // First, create the config
944
+ const communicationConfig = this.configRepository.create({
945
+ config_json: config as any,
946
+ });
947
+ const savedConfig = await this.configRepository.save(communicationConfig);
948
+
949
+ // Then create the hub
950
+ const hub = this.hubRepository.create({
951
+ level_id: levelId,
952
+ level_type: levelType,
953
+ config_id: savedConfig.id,
954
+ communication_config_type: configType,
955
+ service: service as any,
956
+ provider: provider as any,
957
+ priority: priority || 1,
958
+ is_default: is_default || false,
959
+ status: 1,
960
+ });
961
+
962
+ const savedHub = await this.hubRepository.save(hub);
963
+ this.logger.log(`Communication config created: ${configType}/${service}/${provider} for ${levelType} ${levelId}`);
964
+
965
+ return Array.isArray(savedHub) ? savedHub[0] : savedHub;
966
+ }
967
+
968
+ async initOutlookOAuth(
969
+ levelId: number,
970
+ levelType: string,
971
+ email?: string,
972
+ ): Promise<{ authUrl: string; state: string }> {
973
+ try {
974
+ // Get system OAuth credentials from config
975
+ const clientId = this.configService.get<string>('OUTLOOK_CLIENT_ID');
976
+ const tenantId = this.configService.get<string>('OUTLOOK_TENANT_ID');
977
+
978
+ if (!clientId || !tenantId) {
979
+ throw new Error('Outlook OAuth credentials not configured');
980
+ }
981
+
982
+ const state = this.generateSecureState();
983
+ const callbackUrl = this.configService.get<string>('OUTLOOK_CALLBACK_URL') || 'http://localhost:5001/auth/outlook/callback';
984
+
985
+ this.gmailOAuthStates.set(state, {
986
+ levelId,
987
+ levelType,
988
+ email,
989
+ timestamp: Date.now(),
990
+ });
991
+
992
+ // Auto-cleanup after 10 minutes
993
+ setTimeout(() => {
994
+ this.gmailOAuthStates.delete(state);
995
+ }, 10 * 60 * 1000);
996
+
997
+ const scopes = [
998
+ 'https://graph.microsoft.com/Mail.Send',
999
+ 'https://graph.microsoft.com/User.Read',
1000
+ ];
1001
+
1002
+ const authUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize?` +
1003
+ `client_id=${clientId}&` +
1004
+ `response_type=code&` +
1005
+ `redirect_uri=${encodeURIComponent(callbackUrl)}&` +
1006
+ `scope=${encodeURIComponent(scopes.join(' '))}&` +
1007
+ `state=outlook_config:${state}&` +
1008
+ `response_mode=query&` +
1009
+ `prompt=consent` +
1010
+ (email ? `&login_hint=${encodeURIComponent(email)}` : '');
1011
+
1012
+ return { authUrl, state };
1013
+ } catch (error) {
1014
+ this.logger.error('Error initializing Outlook OAuth:', error.message);
1015
+ throw new Error('Failed to initialize Outlook OAuth');
1016
+ }
1017
+ }
1018
+
1019
+ async handleOutlookOAuthCallback(
1020
+ code: string,
1021
+ state: string,
1022
+ ): Promise<GmailSSOResult> {
1023
+ try {
1024
+ const oauthState = this.gmailOAuthStates.get(state);
1025
+ if (!oauthState) {
1026
+ throw new Error('Invalid or expired OAuth state');
1027
+ }
1028
+
1029
+ this.gmailOAuthStates.delete(state);
1030
+
1031
+ if (Date.now() - oauthState.timestamp > 10 * 60 * 1000) {
1032
+ throw new Error('OAuth state expired');
1033
+ }
1034
+
1035
+ const clientId = this.configService.get<string>('OUTLOOK_CLIENT_ID');
1036
+ const clientSecret = this.configService.get<string>('OUTLOOK_CLIENT_SECRET');
1037
+ const tenantId = this.configService.get<string>('OUTLOOK_TENANT_ID');
1038
+ const callbackUrl = this.configService.get<string>('OUTLOOK_CALLBACK_URL') || 'http://localhost:5001/auth/outlook/callback';
1039
+
1040
+ // Exchange code for tokens
1041
+ const tokenResponse = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
1042
+ method: 'POST',
1043
+ headers: {
1044
+ 'Content-Type': 'application/x-www-form-urlencoded',
1045
+ },
1046
+ body: new URLSearchParams({
1047
+ client_id: clientId!,
1048
+ client_secret: clientSecret!,
1049
+ code: code,
1050
+ redirect_uri: callbackUrl,
1051
+ grant_type: 'authorization_code',
1052
+ }),
1053
+ });
1054
+
1055
+ const tokens = await tokenResponse.json();
1056
+
1057
+ if (!tokens.access_token) {
1058
+ throw new Error('Failed to obtain access token');
1059
+ }
1060
+
1061
+ // Get user info from Microsoft Graph
1062
+ const userResponse = await fetch('https://graph.microsoft.com/v1.0/me', {
1063
+ headers: {
1064
+ 'Authorization': `Bearer ${tokens.access_token}`,
1065
+ },
1066
+ });
1067
+
1068
+ const userInfo = await userResponse.json();
1069
+ const email = userInfo.mail || userInfo.userPrincipalName;
1070
+
1071
+ if (!email) {
1072
+ throw new Error('Failed to get user email');
1073
+ }
1074
+
1075
+ const outlookConfig = {
1076
+ clientId,
1077
+ tenantId,
1078
+ email: email,
1079
+ accessToken: tokens.access_token,
1080
+ refreshToken: tokens.refresh_token,
1081
+ scope: tokens.scope,
1082
+ tokenType: tokens.token_type,
1083
+ expiresIn: tokens.expires_in,
1084
+ };
1085
+
1086
+ // Create communication config
1087
+ const communicationConfig = this.configRepository.create({
1088
+ config_json: outlookConfig as any,
1089
+ });
1090
+ const savedConfig = await this.configRepository.save(communicationConfig);
1091
+
1092
+ // Create communication hub
1093
+ const hub = this.hubRepository.create({
1094
+ level_id: oauthState.levelId,
1095
+ level_type: oauthState.levelType,
1096
+ config_id: savedConfig.id,
1097
+ communication_config_type: CommunicationConfigType.EMAIL,
1098
+ service: 'API',
1099
+ provider: 'outlook',
1100
+ status: 1,
1101
+ });
1102
+
1103
+ const savedHub = await this.hubRepository.save(hub);
1104
+
1105
+ this.logger.log(
1106
+ `Outlook OAuth configuration created successfully for ${oauthState.levelType} ${oauthState.levelId} and email ${email}`,
1107
+ );
1108
+
1109
+ return {
1110
+ hubId: savedHub.id,
1111
+ configId: savedConfig.id,
1112
+ };
1113
+ } catch (error) {
1114
+ this.logger.error('Error handling Outlook OAuth callback:', error.message);
1115
+ throw new Error(`Failed to complete Outlook OAuth: ${error.message}`);
1116
+ }
1117
+ }
1118
+ }