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.
- package/dist/module/communication/controller/communication.controller.d.ts +11 -4
- package/dist/module/communication/controller/communication.controller.js +14 -6
- package/dist/module/communication/controller/communication.controller.js.map +1 -1
- package/dist/module/communication/dto/create-config.dto.d.ts +16 -1
- package/dist/module/communication/dto/create-config.dto.js +51 -2
- package/dist/module/communication/dto/create-config.dto.js.map +1 -1
- package/dist/module/communication/service/communication.service.d.ts +20 -1
- package/dist/module/communication/service/communication.service.js +93 -2
- package/dist/module/communication/service/communication.service.js.map +1 -1
- package/dist/module/communication/service/wrapper.service.d.ts +28 -0
- package/dist/module/communication/service/wrapper.service.js +151 -0
- package/dist/module/communication/service/wrapper.service.js.map +1 -0
- package/dist/module/communication/strategies/communication.strategy.d.ts +12 -1
- package/dist/module/communication/strategies/communication.strategy.js +37 -0
- package/dist/module/communication/strategies/communication.strategy.js.map +1 -1
- package/dist/module/communication/strategies/email/gmail-api.strategy.d.ts +2 -0
- package/dist/module/communication/strategies/email/gmail-api.strategy.js +64 -20
- package/dist/module/communication/strategies/email/gmail-api.strategy.js.map +1 -1
- package/dist/module/communication/strategies/email/sendgrid-api.strategy.d.ts +10 -0
- package/dist/module/communication/strategies/email/sendgrid-api.strategy.js +61 -1
- package/dist/module/communication/strategies/email/sendgrid-api.strategy.js.map +1 -1
- package/dist/module/workflow/service/populate-workflow.service.js +1 -1
- package/dist/module/workflow/service/populate-workflow.service.js.map +1 -1
- package/dist/module/workflow/service/task.service.js +3 -0
- package/dist/module/workflow/service/task.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/module/communication/controller/communication.controller.ts +14 -3
- package/src/module/communication/dto/create-config.dto.ts +38 -1
- package/src/module/communication/service/communication.service.ts +157 -3
- package/src/module/communication/service/wrapper.service.ts +185 -0
- package/src/module/communication/strategies/communication.strategy.ts +63 -1
- package/src/module/communication/strategies/email/gmail-api.strategy.ts +121 -24
- package/src/module/communication/strategies/email/sendgrid-api.strategy.ts +82 -2
- package/src/module/workflow/service/populate-workflow.service.ts +1 -1
- package/src/module/workflow/service/task.service.ts +3 -1
package/package.json
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
1746
|
-
|
|
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?:
|
|
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
|
}
|