rez_core 2.2.192 → 2.2.193

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 (36) hide show
  1. package/dist/module/communication/controller/communication.controller.d.ts +11 -4
  2. package/dist/module/communication/controller/communication.controller.js +14 -6
  3. package/dist/module/communication/controller/communication.controller.js.map +1 -1
  4. package/dist/module/communication/dto/create-config.dto.d.ts +16 -1
  5. package/dist/module/communication/dto/create-config.dto.js +51 -2
  6. package/dist/module/communication/dto/create-config.dto.js.map +1 -1
  7. package/dist/module/communication/service/communication.service.d.ts +20 -1
  8. package/dist/module/communication/service/communication.service.js +93 -2
  9. package/dist/module/communication/service/communication.service.js.map +1 -1
  10. package/dist/module/communication/service/wrapper.service.d.ts +28 -0
  11. package/dist/module/communication/service/wrapper.service.js +151 -0
  12. package/dist/module/communication/service/wrapper.service.js.map +1 -0
  13. package/dist/module/communication/strategies/communication.strategy.d.ts +12 -1
  14. package/dist/module/communication/strategies/communication.strategy.js +37 -0
  15. package/dist/module/communication/strategies/communication.strategy.js.map +1 -1
  16. package/dist/module/communication/strategies/email/gmail-api.strategy.d.ts +2 -0
  17. package/dist/module/communication/strategies/email/gmail-api.strategy.js +64 -20
  18. package/dist/module/communication/strategies/email/gmail-api.strategy.js.map +1 -1
  19. package/dist/module/communication/strategies/email/sendgrid-api.strategy.d.ts +10 -0
  20. package/dist/module/communication/strategies/email/sendgrid-api.strategy.js +61 -1
  21. package/dist/module/communication/strategies/email/sendgrid-api.strategy.js.map +1 -1
  22. package/dist/module/workflow/service/populate-workflow.service.js +1 -1
  23. package/dist/module/workflow/service/populate-workflow.service.js.map +1 -1
  24. package/dist/module/workflow/service/task.service.js +3 -0
  25. package/dist/module/workflow/service/task.service.js.map +1 -1
  26. package/dist/tsconfig.build.tsbuildinfo +1 -1
  27. package/package.json +1 -1
  28. package/src/module/communication/controller/communication.controller.ts +14 -3
  29. package/src/module/communication/dto/create-config.dto.ts +38 -1
  30. package/src/module/communication/service/communication.service.ts +157 -3
  31. package/src/module/communication/service/wrapper.service.ts +185 -0
  32. package/src/module/communication/strategies/communication.strategy.ts +63 -1
  33. package/src/module/communication/strategies/email/gmail-api.strategy.ts +121 -24
  34. package/src/module/communication/strategies/email/sendgrid-api.strategy.ts +82 -2
  35. package/src/module/workflow/service/populate-workflow.service.ts +1 -1
  36. package/src/module/workflow/service/task.service.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rez_core",
3
- "version": "2.2.192",
3
+ "version": "2.2.193",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "private": false,
@@ -12,7 +12,6 @@ import {
12
12
  Query,
13
13
  } from '@nestjs/common';
14
14
  import { CommunicationService } from '../service/communication.service';
15
- import { SendGridApiStrategy } from '../strategies/email/sendgrid-api.strategy';
16
15
  import {
17
16
  BulkMessageDto,
18
17
  CreateConfigDto,
@@ -20,6 +19,7 @@ import {
20
19
  GmailOAuthInitDto,
21
20
  OutlookOAuthInitDto,
22
21
  SendGridVerifiedSendersDto,
22
+ SendGridTemplatesDto,
23
23
  UpdateConfigDto,
24
24
  UpdateConfigStatusDto,
25
25
  } from '../dto/create-config.dto';
@@ -33,7 +33,6 @@ export class ScheduledMessageDto extends GenericSendMessageDto {
33
33
  export class CommunicationController {
34
34
  constructor(
35
35
  private readonly communicationService: CommunicationService,
36
- private readonly sendGridApiStrategy: SendGridApiStrategy,
37
36
  ) {}
38
37
 
39
38
  @Post('send')
@@ -232,7 +231,19 @@ export class CommunicationController {
232
231
  @Post('sendgrid/verified-senders')
233
232
  @HttpCode(HttpStatus.OK)
234
233
  async getSendGridVerifiedSenders(@Body() dto: SendGridVerifiedSendersDto) {
235
- return this.sendGridApiStrategy.getVerifiedSenders(dto.apiKey);
234
+ return this.communicationService.getSendGridVerifiedSenders(
235
+ dto.levelId,
236
+ dto.levelType,
237
+ );
238
+ }
239
+
240
+ @Post('sendgrid/templates')
241
+ @HttpCode(HttpStatus.OK)
242
+ async getSendGridTemplates(@Body() dto: SendGridTemplatesDto) {
243
+ return this.communicationService.getSendGridTemplates(
244
+ dto.levelId,
245
+ dto.levelType,
246
+ );
236
247
  }
237
248
 
238
249
  @Post('config/delete/:hubId')
@@ -324,9 +324,23 @@ export class OutlookOAuthInitDto {
324
324
  }
325
325
 
326
326
  export class SendGridVerifiedSendersDto {
327
+ @IsNotEmpty()
328
+ @IsNumber()
329
+ levelId: number;
330
+
331
+ @IsNotEmpty()
332
+ @IsString()
333
+ levelType: string;
334
+ }
335
+
336
+ export class SendGridTemplatesDto {
337
+ @IsNotEmpty()
338
+ @IsNumber()
339
+ levelId: number;
340
+
327
341
  @IsNotEmpty()
328
342
  @IsString()
329
- apiKey: string;
343
+ levelType: string;
330
344
  }
331
345
 
332
346
  export class VerifiedSenderDto {
@@ -337,6 +351,17 @@ export class VerifiedSenderDto {
337
351
  value: string;
338
352
  }
339
353
 
354
+ export class TemplateDto {
355
+ @IsString()
356
+ id: string;
357
+
358
+ @IsString()
359
+ name: string;
360
+
361
+ @IsString()
362
+ generation: string;
363
+ }
364
+
340
365
  export class VerifiedSendersResponseDto {
341
366
  @IsBoolean()
342
367
  success: boolean;
@@ -348,3 +373,15 @@ export class VerifiedSendersResponseDto {
348
373
  @IsString()
349
374
  error?: string;
350
375
  }
376
+
377
+ export class TemplatesResponseDto {
378
+ @IsBoolean()
379
+ success: boolean;
380
+
381
+ @IsOptional()
382
+ data?: TemplateDto[];
383
+
384
+ @IsOptional()
385
+ @IsString()
386
+ error?: string;
387
+ }
@@ -11,6 +11,7 @@ import { CommunicationConfig } from '../entity/communication-config.entity';
11
11
  import { CommunicationFactory } from '../factories/communication.factory';
12
12
  import { CommunicationResult } from '../strategies/communication.strategy';
13
13
  import { GmailApiStrategy } from '../strategies/email/gmail-api.strategy';
14
+ import { SendGridApiStrategy } from '../strategies/email/sendgrid-api.strategy';
14
15
  import { CommunicationQueueService } from './communication-queue.service';
15
16
 
16
17
  export interface SendMessageDto {
@@ -83,6 +84,7 @@ export class CommunicationService {
83
84
 
84
85
  private readonly communicationFactory: CommunicationFactory,
85
86
  private readonly gmailApiStrategy: GmailApiStrategy,
87
+ private readonly sendGridApiStrategy: SendGridApiStrategy,
86
88
  private readonly configService: ConfigService,
87
89
  @Inject(forwardRef(() => CommunicationQueueService))
88
90
  private readonly queueService?: CommunicationQueueService,
@@ -647,6 +649,35 @@ export class CommunicationService {
647
649
  }
648
650
 
649
651
  async updateConfigStatus(hubId: number, status: number): Promise<void> {
652
+ // Validate active status change to prevent multiple active configurations
653
+ if (status === 1) {
654
+ // Find the hub to get level and type information
655
+ const hub = await this.hubRepository.findOne({
656
+ where: { id: hubId },
657
+ });
658
+
659
+ if (!hub) {
660
+ throw new Error('Communication configuration not found');
661
+ }
662
+
663
+ // Check for existing active configuration of same type
664
+ const existingActiveConfig = await this.hubRepository.findOne({
665
+ where: {
666
+ level_id: hub.level_id,
667
+ level_type: hub.level_type,
668
+ communication_config_type: hub.communication_config_type,
669
+ status: 1, // Active status
670
+ id: Not(hubId), // Exclude current hub
671
+ },
672
+ });
673
+
674
+ if (existingActiveConfig) {
675
+ throw new Error(
676
+ `An active ${hub.communication_config_type} configuration already exists for ${hub.level_type} ${hub.level_id}. Please deactivate the existing configuration first.`,
677
+ );
678
+ }
679
+ }
680
+
650
681
  await this.hubRepository.update(hubId, { status });
651
682
  }
652
683
 
@@ -708,6 +739,25 @@ export class CommunicationService {
708
739
  });
709
740
  }
710
741
 
742
+ // Validate active status change to prevent multiple active configurations
743
+ if (updateData.status === 1) {
744
+ const existingActiveConfig = await this.hubRepository.findOne({
745
+ where: {
746
+ level_id: hub.level_id,
747
+ level_type: hub.level_type,
748
+ communication_config_type: hub.communication_config_type,
749
+ status: 1, // Active status
750
+ id: Not(hubId), // Exclude current hub
751
+ },
752
+ });
753
+
754
+ if (existingActiveConfig) {
755
+ throw new Error(
756
+ `An active ${hub.communication_config_type} configuration already exists for ${hub.level_type} ${hub.level_id}. Please deactivate the existing configuration first.`,
757
+ );
758
+ }
759
+ }
760
+
711
761
  // Handle default configuration logic
712
762
  if (updateData.is_default === true) {
713
763
  // Remove default from other configurations of same type and level
@@ -1741,9 +1791,14 @@ export class CommunicationService {
1741
1791
  }
1742
1792
  }
1743
1793
 
1744
- async getCommunicationConfigById(
1745
- hubId: number,
1746
- ): Promise<(CommunicationHub & { config: CommunicationConfig; linkedSource?: string; configDetails?: any }) | null> {
1794
+ async getCommunicationConfigById(hubId: number): Promise<
1795
+ | (CommunicationHub & {
1796
+ config: CommunicationConfig;
1797
+ linkedSource?: string;
1798
+ configDetails?: any;
1799
+ })
1800
+ | null
1801
+ > {
1747
1802
  try {
1748
1803
  // Find the communication hub by ID
1749
1804
  const hub = await this.hubRepository.findOne({
@@ -1802,4 +1857,103 @@ export class CommunicationService {
1802
1857
  );
1803
1858
  }
1804
1859
  }
1860
+
1861
+ async getSendGridTemplates(
1862
+ levelId: number,
1863
+ levelType: string,
1864
+ ): Promise<{
1865
+ success: boolean;
1866
+ data?: Array<{
1867
+ id: string;
1868
+ name: string;
1869
+ generation: string;
1870
+ }>;
1871
+ error?: string;
1872
+ }> {
1873
+ try {
1874
+ // Find active SendGrid configurations for this level
1875
+ const hubs = await this.getActiveHubs(levelId, levelType, CommunicationConfigType.EMAIL);
1876
+
1877
+ // Look for SendGrid provider
1878
+ const sendGridHub = hubs.find(hub => hub.provider === 'sendgrid');
1879
+
1880
+ if (!sendGridHub) {
1881
+ return {
1882
+ success: false,
1883
+ error: 'No active SendGrid configuration found for this level',
1884
+ };
1885
+ }
1886
+
1887
+ // Extract API key from configuration
1888
+ const apiKey = sendGridHub.config?.config_json?.apiKey;
1889
+
1890
+ if (!apiKey) {
1891
+ return {
1892
+ success: false,
1893
+ error: 'SendGrid API key not found in configuration',
1894
+ };
1895
+ }
1896
+
1897
+ // Use the SendGrid strategy to fetch templates
1898
+ return this.sendGridApiStrategy.getTemplates(apiKey);
1899
+ } catch (error) {
1900
+ this.logger.error(
1901
+ `Error fetching SendGrid templates for level ${levelId}/${levelType}:`,
1902
+ error.message,
1903
+ );
1904
+ return {
1905
+ success: false,
1906
+ error: `Failed to fetch SendGrid templates: ${error.message}`,
1907
+ };
1908
+ }
1909
+ }
1910
+
1911
+ async getSendGridVerifiedSenders(
1912
+ levelId: number,
1913
+ levelType: string,
1914
+ ): Promise<{
1915
+ success: boolean;
1916
+ data?: Array<{
1917
+ label: string;
1918
+ value: string;
1919
+ }>;
1920
+ error?: string;
1921
+ }> {
1922
+ try {
1923
+ // Find active SendGrid configurations for this level
1924
+ const hubs = await this.getActiveHubs(levelId, levelType, CommunicationConfigType.EMAIL);
1925
+
1926
+ // Look for SendGrid provider
1927
+ const sendGridHub = hubs.find(hub => hub.provider === 'sendgrid');
1928
+
1929
+ if (!sendGridHub) {
1930
+ return {
1931
+ success: false,
1932
+ error: 'No active SendGrid configuration found for this level',
1933
+ };
1934
+ }
1935
+
1936
+ // Extract API key from configuration
1937
+ const apiKey = sendGridHub.config?.config_json?.apiKey;
1938
+
1939
+ if (!apiKey) {
1940
+ return {
1941
+ success: false,
1942
+ error: 'SendGrid API key not found in configuration',
1943
+ };
1944
+ }
1945
+
1946
+ // Use the SendGrid strategy to fetch verified senders
1947
+ return this.sendGridApiStrategy.getVerifiedSenders(apiKey);
1948
+ } catch (error) {
1949
+ this.logger.error(
1950
+ `Error fetching SendGrid verified senders for level ${levelId}/${levelType}:`,
1951
+ error.message,
1952
+ );
1953
+ return {
1954
+ success: false,
1955
+ error: `Failed to fetch SendGrid verified senders: ${error.message}`,
1956
+ };
1957
+ }
1958
+ }
1805
1959
  }
@@ -0,0 +1,185 @@
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import { DataSource } from 'typeorm';
3
+ import {
4
+ CommunicationService,
5
+ GenericMessageDto,
6
+ } from './communication.service';
7
+ import { GoogleService } from './calendar-event.service';
8
+ import { IcsMeetingService } from 'src/module/ics/service/ics.service';
9
+
10
+ @Injectable()
11
+ export class WrapperService {
12
+ private readonly logger = new Logger(WrapperService.name);
13
+
14
+ constructor(
15
+ private readonly datasource: DataSource,
16
+ private readonly communicationService: CommunicationService,
17
+ private readonly googleService: GoogleService,
18
+ private readonly icsService: IcsMeetingService,
19
+ ) {}
20
+
21
+ /**
22
+ * Wrapper for sending mail
23
+ */
24
+ async sendMailWrapper(payload: any, loggedInUser: any) {
25
+ try {
26
+ const { level_id, level_type } = loggedInUser;
27
+
28
+ // 1. Check for config at user’s level
29
+ const configs = await this.datasource.query(
30
+ `SELECT *
31
+ FROM cr_communication_hub
32
+ WHERE level_id = ?
33
+ AND level_type = ?
34
+ AND communication_config_type = 'EMAIL'`,
35
+ [level_id, level_type],
36
+ );
37
+
38
+ let payloadSendMail: GenericMessageDto;
39
+
40
+ if (configs && configs.length > 0) {
41
+ payloadSendMail = {
42
+ levelId: level_id,
43
+ levelType: level_type,
44
+ to: payload.to,
45
+ message: payload.message,
46
+ subject: payload.subject,
47
+ type: 'EMAIL',
48
+ cc: payload.cc,
49
+ bcc: payload.bcc,
50
+ html: payload.html,
51
+ attachments: payload.attachments,
52
+ templateId: payload.templateId,
53
+ variables: payload.variables,
54
+ };
55
+ } else {
56
+ // 2. Fallback → ORG-level config
57
+ const fallbackConfigs = await this.datasource.query(
58
+ `SELECT *
59
+ FROM cr_communication_hub
60
+ WHERE level_type = 'ORG'
61
+ AND communication_config_type = 'EMAIL'`,
62
+ );
63
+
64
+ if (!fallbackConfigs || fallbackConfigs.length === 0) {
65
+ throw new Error('No active email communication config found');
66
+ }
67
+
68
+ payloadSendMail = {
69
+ levelId: 1,
70
+ levelType: 'ORG',
71
+ to: payload.to,
72
+ message: payload.message,
73
+ subject: payload.subject,
74
+ type: 'EMAIL',
75
+ cc: payload.cc,
76
+ bcc: payload.bcc,
77
+ html: payload.html,
78
+ attachments: payload.attachments,
79
+ templateId: payload.templateId,
80
+ variables: payload.variables,
81
+ };
82
+ }
83
+
84
+ // 3. Call communication service
85
+ const result =
86
+ await this.communicationService.sendGenericMessage(payloadSendMail);
87
+
88
+ return {
89
+ success: true,
90
+ data: result,
91
+ };
92
+ } catch (error: any) {
93
+ this.logger.error('sendMailWrapper error', error.message);
94
+ return {
95
+ success: false,
96
+ error: error.message,
97
+ };
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Wrapper for scheduling meeting in Google Calendar
103
+ */
104
+ async scheduleMeetingWrapper(payload: any, loggedInUser: any) {
105
+ try {
106
+ this.logger.log(
107
+ `scheduleMeetingWrapper called by user: ${loggedInUser?.id || 'unknown'}`,
108
+ );
109
+
110
+ const { level_id, level_type } = loggedInUser;
111
+
112
+ // 1. Check for config at user’s level
113
+ const configs = await this.datasource.query(
114
+ `SELECT *
115
+ FROM cr_communication_hub
116
+ WHERE level_id = ?
117
+ AND level_type = ?
118
+ AND communication_config_type = 'EMAIL' AND provider = 'gmail' AND service = 'API'`,
119
+ [level_id, level_type],
120
+ );
121
+
122
+ let configCred;
123
+
124
+ if (configs && configs.length > 0) {
125
+ // only if type is gmail
126
+ configCred = await this.datasource.query(
127
+ `SELECT config_json
128
+ FROM cr_communication_config
129
+ WHERE id = ?`,
130
+ [configs[0].config_id],
131
+ );
132
+
133
+ if (!configCred || configCred.length === 0) {
134
+ throw new Error('No Google credentials found for user-level config');
135
+ }
136
+
137
+ configCred = configCred[0].config_json;
138
+ // 3. Call google service
139
+ const result = await this.googleService.createEvent(
140
+ configCred,
141
+ payload,
142
+ );
143
+ } else {
144
+ // 2. Fallback → ORG-level config
145
+ const fallbackConfigs = await this.datasource.query(
146
+ `SELECT *
147
+ FROM cr_communication_hub
148
+ WHERE level_type = 'ORG'
149
+ AND communication_config_type = 'CALENDAR'`,
150
+ );
151
+
152
+ configCred = await this.datasource.query(
153
+ `SELECT config_json
154
+ FROM cr_communication_config
155
+ WHERE id = ?`,
156
+ [fallbackConfigs[0]?.config_id],
157
+ );
158
+
159
+ const base64String = await this.icsService.generateIcs(payload);
160
+
161
+ const result = await this.communicationService.sendGenericMessage({
162
+ ...payload,
163
+ attachments: [
164
+ {
165
+ content: base64String,
166
+ type: 'text/calendar',
167
+ filename: 'invite.ics',
168
+ disposition: 'attachment',
169
+ },
170
+ ],
171
+ });
172
+ }
173
+
174
+ return {
175
+ success: true,
176
+ };
177
+ } catch (error: any) {
178
+ this.logger.error('scheduleMeetingWrapper error', error.message);
179
+ return {
180
+ success: false,
181
+ error: error.message,
182
+ };
183
+ }
184
+ }
185
+ }
@@ -17,8 +17,70 @@ export interface CommunicationResult {
17
17
  refreshedToken?: string;
18
18
  }
19
19
 
20
+ export interface EmailAttachment {
21
+ filename: string;
22
+ content: string; // base64 encoded content
23
+ contentType: string; // MIME type (e.g., 'application/pdf', 'image/png')
24
+ disposition?: 'attachment' | 'inline'; // default: 'attachment'
25
+ contentId?: string; // for inline images
26
+ }
27
+
20
28
  export interface MessageTemplate {
21
29
  subject?: string;
22
30
  body: string;
23
- attachments?: any[];
31
+ attachments?: EmailAttachment[];
32
+ }
33
+
34
+ export class EmailAttachmentValidator {
35
+ static validate(attachments: EmailAttachment[], maxSizeBytes: number, providerName: string): void {
36
+ let totalSize = 0;
37
+
38
+ for (const attachment of attachments) {
39
+ // Validate required fields
40
+ if (!attachment.filename) {
41
+ throw new Error('Attachment filename is required');
42
+ }
43
+
44
+ if (!attachment.content) {
45
+ throw new Error(
46
+ `Attachment content is required for ${attachment.filename}`,
47
+ );
48
+ }
49
+
50
+ if (!attachment.contentType) {
51
+ throw new Error(
52
+ `Attachment contentType is required for ${attachment.filename}`,
53
+ );
54
+ }
55
+
56
+ // Validate base64 format
57
+ if (!this.isValidBase64(attachment.content)) {
58
+ throw new Error(
59
+ `Invalid base64 content for attachment ${attachment.filename}`,
60
+ );
61
+ }
62
+
63
+ // Calculate size (base64 is ~4/3 of original size)
64
+ const attachmentSize = (attachment.content.length * 3) / 4;
65
+ totalSize += attachmentSize;
66
+
67
+ if (totalSize > maxSizeBytes) {
68
+ const maxSizeMB = Math.floor(maxSizeBytes / (1024 * 1024));
69
+ throw new Error(`Total attachment size exceeds ${providerName} limit of ${maxSizeMB}MB`);
70
+ }
71
+ }
72
+ }
73
+
74
+ private static isValidBase64(str: string): boolean {
75
+ try {
76
+ // Check if string is valid base64
77
+ const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
78
+ return (
79
+ base64Regex.test(str) &&
80
+ Buffer.from(str, 'base64').toString('base64') === str
81
+ );
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
24
86
  }