rez_core 3.1.86 → 3.1.88

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 (23) hide show
  1. package/dist/module/integration/controller/integration.controller.d.ts +9 -1
  2. package/dist/module/integration/controller/integration.controller.js +21 -0
  3. package/dist/module/integration/controller/integration.controller.js.map +1 -1
  4. package/dist/module/integration/dto/create-config.dto.d.ts +6 -0
  5. package/dist/module/integration/dto/create-config.dto.js +24 -1
  6. package/dist/module/integration/dto/create-config.dto.js.map +1 -1
  7. package/dist/module/integration/service/integration.service.d.ts +8 -0
  8. package/dist/module/integration/service/integration.service.js +27 -1
  9. package/dist/module/integration/service/integration.service.js.map +1 -1
  10. package/dist/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.d.ts +13 -2
  11. package/dist/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.js +66 -112
  12. package/dist/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.js.map +1 -1
  13. package/dist/module/mapper/controller/field-mapper.controller.d.ts +1 -1
  14. package/dist/module/mapper/service/field-mapper.service.d.ts +1 -1
  15. package/dist/module/mapper/service/field-mapper.service.js +6 -1
  16. package/dist/module/mapper/service/field-mapper.service.js.map +1 -1
  17. package/dist/tsconfig.build.tsbuildinfo +1 -1
  18. package/package.json +1 -1
  19. package/src/module/integration/controller/integration.controller.ts +21 -0
  20. package/src/module/integration/dto/create-config.dto.ts +18 -0
  21. package/src/module/integration/service/integration.service.ts +61 -3
  22. package/src/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.ts +97 -155
  23. package/src/module/mapper/service/field-mapper.service.ts +6 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rez_core",
3
- "version": "3.1.86",
3
+ "version": "3.1.88",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "private": false,
@@ -19,6 +19,7 @@ import {
19
19
  CreateConfigDto,
20
20
  CreateUserIntegrationDto,
21
21
  GenericSendMessageDto,
22
+ GetIntegrationTemplatesDto,
22
23
  SendGridTemplatesDto,
23
24
  SendGridVerifiedSendersDto,
24
25
  UpdateConfigDto,
@@ -327,6 +328,26 @@ export class IntegrationController {
327
328
  }
328
329
  }
329
330
 
331
+ @Post('templates')
332
+ @HttpCode(HttpStatus.OK)
333
+ async getIntegrationTemplates(@Body() dto: GetIntegrationTemplatesDto) {
334
+ try {
335
+ return await this.integrationService.getIntegrationTemplates(
336
+ dto.levelId,
337
+ dto.levelType,
338
+ dto.app_code,
339
+ dto.integration_type,
340
+ );
341
+ } catch (error) {
342
+ throw new BadRequestException({
343
+ success: false,
344
+ error: 'GET_TEMPLATES_ERROR',
345
+ message: error.message || 'Failed to get templates',
346
+ code: 'INTEGRATION_TEMPLATES_ERROR',
347
+ });
348
+ }
349
+ }
350
+
330
351
  @Post('config/delete/:configId')
331
352
  @HttpCode(HttpStatus.OK)
332
353
  async disconnectConfig(@Param('configId', ParseIntPipe) configId: number) {
@@ -360,6 +360,24 @@ export class SendGridTemplatesDto {
360
360
  app_code: string;
361
361
  }
362
362
 
363
+ export class GetIntegrationTemplatesDto {
364
+ @IsNotEmpty()
365
+ @IsNumber()
366
+ levelId: number;
367
+
368
+ @IsNotEmpty()
369
+ @IsString()
370
+ levelType: string;
371
+
372
+ @IsNotEmpty()
373
+ @IsString()
374
+ app_code: string;
375
+
376
+ @IsNotEmpty()
377
+ @IsString()
378
+ integration_type: 'EMAIL' | 'SMS' | 'WA' | 'TELEPHONE';
379
+ }
380
+
363
381
  export class VerifiedSenderDto {
364
382
  @IsString()
365
383
  label: string;
@@ -1000,7 +1000,7 @@ export class IntegrationService {
1000
1000
  let variables;
1001
1001
  let externalTemplateId: string | undefined = undefined;
1002
1002
  let richText;
1003
- if (templateId && entity_type && entity_id) {
1003
+ if (templateId) {
1004
1004
  const templateData = await this.processTemplate(
1005
1005
  parseInt(templateId, 10),
1006
1006
  entity_type,
@@ -1255,8 +1255,8 @@ export class IntegrationService {
1255
1255
 
1256
1256
  private async processTemplate(
1257
1257
  templateId: number,
1258
- entity_type: string,
1259
- entity_id: number,
1258
+ entity_type: any,
1259
+ entity_id: any,
1260
1260
  ): Promise<{ variables?: Record<string, any>; externalTemplateId?: string; rich_text?: string }> {
1261
1261
  const commTemplate: any = await this.entityService.getEntityData(COMM_TEMPLATE, templateId, {} as any);
1262
1262
  if (!commTemplate) {
@@ -2019,6 +2019,64 @@ export class IntegrationService {
2019
2019
  }
2020
2020
  }
2021
2021
 
2022
+ async getIntegrationTemplates(
2023
+ levelId: number,
2024
+ levelType: string,
2025
+ app_code: string,
2026
+ integration_type: 'EMAIL' | 'SMS' | 'WA' | 'TELEPHONE',
2027
+ ): Promise<{
2028
+ success: boolean;
2029
+ data?: Array<{
2030
+ label: string;
2031
+ value: string;
2032
+ }>;
2033
+ error?: string;
2034
+ }> {
2035
+ try {
2036
+ // Get active configuration for the integration type
2037
+ const config = await this.getSingleActiveConfig(
2038
+ levelId,
2039
+ levelType,
2040
+ app_code,
2041
+ integration_type,
2042
+ );
2043
+
2044
+ if (!config) {
2045
+ return {
2046
+ success: false,
2047
+ error: `No active ${integration_type} configuration found for this level`,
2048
+ };
2049
+ }
2050
+
2051
+ // Create strategy instance
2052
+ const strategy = this.integrationFactory.create(
2053
+ config.integration_type,
2054
+ 'API',
2055
+ config.integration_provider,
2056
+ );
2057
+
2058
+ // Check if strategy supports getTemplates
2059
+ if (typeof (strategy as any).getTemplates !== 'function') {
2060
+ return {
2061
+ success: false,
2062
+ error: `Template retrieval not supported for provider: ${config.integration_provider}`,
2063
+ };
2064
+ }
2065
+
2066
+ // Call getTemplates method
2067
+ return await (strategy as any).getTemplates(config.config_json);
2068
+ } catch (error) {
2069
+ this.logger.error(
2070
+ `Error fetching templates for ${integration_type} level ${levelId}/${levelType}:`,
2071
+ error.message,
2072
+ );
2073
+ return {
2074
+ success: false,
2075
+ error: `Failed to fetch templates: ${error.message}`,
2076
+ };
2077
+ }
2078
+ }
2079
+
2022
2080
  async getSendGridTemplates(
2023
2081
  levelId: number,
2024
2082
  levelType: string,
@@ -1,5 +1,6 @@
1
1
  import { Injectable, Logger } from '@nestjs/common';
2
2
  import axios, { AxiosResponse } from 'axios';
3
+ import { randomUUID } from 'crypto';
3
4
  import {
4
5
  IntegrationResult,
5
6
  IntegrationStrategy,
@@ -9,19 +10,12 @@ interface TubelightWhatsAppConfig {
9
10
  userName: string;
10
11
  password: string;
11
12
  tenantId: string;
13
+ custUuid?: string;
12
14
  baseUrl?: string;
13
- sourceNumber?: string;
14
- }
15
-
16
- interface TubelightMessage {
17
- to: string;
18
- message: string;
19
- messageType?: string;
20
- mediaUrl?: string;
21
- caption?: string;
22
- filename?: string;
23
15
  templateId?: string;
24
- templateParams?: any[];
16
+ templateName?: string;
17
+ templateLanguage?: string;
18
+ variables?: string;
25
19
  }
26
20
 
27
21
  interface TubelightApiResponse {
@@ -38,7 +32,7 @@ interface TubelightApiResponse {
38
32
  export class TubelightWhatsAppStrategy implements IntegrationStrategy {
39
33
  private readonly logger = new Logger(TubelightWhatsAppStrategy.name);
40
34
  private readonly defaultBaseUrl =
41
- 'https://portal.tubelightcommunications.com/whatsapp/api/v1';
35
+ 'https://portal.tubelightcommunications.com/whatsapp/api/v1';
42
36
 
43
37
  async sendMessage(
44
38
  to: string,
@@ -58,16 +52,15 @@ export class TubelightWhatsAppStrategy implements IntegrationStrategy {
58
52
  }
59
53
 
60
54
  const baseUrl = tubelightConfig.baseUrl || this.defaultBaseUrl;
61
- const url = `${baseUrl}/messages/send`;
55
+ const url = `${baseUrl}/bulk/send`;
62
56
 
63
- const payload = this.prepareMessagePayload(to, message, config);
57
+ const payload = [this.prepareMessagePayload(to, message, config)];
64
58
 
65
59
  const response: AxiosResponse<TubelightApiResponse> = await axios.post(
66
60
  url,
67
61
  payload,
68
62
  {
69
63
  headers: {
70
- 'X-TENANT-ID': tubelightConfig.tenantId,
71
64
  'Content-Type': 'application/json',
72
65
  Authorization: `Bearer ${token}`,
73
66
  },
@@ -76,8 +69,7 @@ export class TubelightWhatsAppStrategy implements IntegrationStrategy {
76
69
  );
77
70
 
78
71
  if (
79
- response.data?.success &&
80
- (response.data?.messageId || response.data?.id)
72
+ response.status === 200
81
73
  ) {
82
74
  const messageId = response.data.messageId || response.data.id;
83
75
  this.logger.log(
@@ -166,98 +158,55 @@ export class TubelightWhatsAppStrategy implements IntegrationStrategy {
166
158
 
167
159
  private prepareMessagePayload(to: string, message: string, config: any): any {
168
160
  const basePayload = {
169
- to: this.formatPhoneNumber(to),
170
- from: config.sourceNumber || config.from,
171
- };
161
+ cust_uuid: config.custUuid || randomUUID(),
162
+ to: [this.formatPhoneNumber(to)],
163
+ };
164
+
165
+ // If templateId exists, send template message
166
+ if (config.templateId || config.templateName) {
167
+ const messagePayload: any = {
168
+ type: 'template',
169
+ template_name: config.templateId || config.templateName,
170
+ language: config.templateLanguage || 'en',
171
+ };
172
172
 
173
- // Handle different message types based on config
174
- if (config.messageType) {
175
- switch (config.messageType) {
176
- case 'template':
177
- return {
178
- ...basePayload,
179
- messageType: 'TEMPLATE',
180
- templateId: config.templateId,
181
- templateParams: config.templateParams || [],
182
- language: config.templateLanguage || 'en',
183
- };
184
-
185
- case 'image':
186
- return {
187
- ...basePayload,
188
- messageType: 'IMAGE',
189
- mediaUrl: config.mediaUrl,
190
- caption: message || config.caption || '',
191
- };
192
-
193
- case 'document':
194
- return {
195
- ...basePayload,
196
- messageType: 'DOCUMENT',
197
- mediaUrl: config.mediaUrl,
198
- filename: config.filename || 'document',
199
- caption: message || config.caption || '',
200
- };
201
-
202
- case 'audio':
203
- return {
204
- ...basePayload,
205
- messageType: 'AUDIO',
206
- mediaUrl: config.mediaUrl,
207
- };
208
-
209
- case 'video':
210
- return {
211
- ...basePayload,
212
- messageType: 'VIDEO',
213
- mediaUrl: config.mediaUrl,
214
- caption: message || config.caption || '',
215
- };
216
-
217
- case 'location':
218
- return {
219
- ...basePayload,
220
- messageType: 'LOCATION',
221
- latitude: config.latitude,
222
- longitude: config.longitude,
223
- name: config.locationName || '',
224
- address: config.locationAddress || '',
225
- };
226
-
227
- case 'interactive':
228
- return {
229
- ...basePayload,
230
- messageType: 'INTERACTIVE',
231
- interactiveType: config.interactiveType || 'button',
232
- text: message,
233
- buttons: config.buttons || [],
234
- sections: config.sections || [],
235
- header: config.header,
236
- footer: config.footer,
237
- };
238
-
239
- default:
240
- // Fall back to text message
241
- break;
173
+ // Add body_params if provided
174
+ if (config.variables) {
175
+ try {
176
+ // Parse JSON string
177
+ const parsedVariables = typeof config.variables === 'string'
178
+ ? JSON.parse(config.variables)
179
+ : config.variables;
180
+
181
+ // Convert object to array of values, or use array as-is
182
+ messagePayload.body_params = Array.isArray(parsedVariables)
183
+ ? parsedVariables
184
+ : Object.values(parsedVariables);
185
+ } catch (e) {
186
+ this.logger.error('Failed to parse variables JSON:', e.message);
187
+ messagePayload.body_params = [];
188
+ }
242
189
  }
190
+
191
+ return {
192
+ ...basePayload,
193
+ message: messagePayload,
194
+ };
243
195
  }
244
196
 
245
197
  // Default text message
246
198
  return {
247
199
  ...basePayload,
248
- messageType: 'TEXT',
249
- text: message,
200
+ message: {
201
+ type: 'text',
202
+ text: message,
203
+ },
250
204
  };
251
205
  }
252
206
 
253
207
  private formatPhoneNumber(phoneNumber: string): string {
254
- // Remove any non-digit characters except +
255
- let formatted = phoneNumber.replace(/[^\d+]/g, '');
256
-
257
- // If it doesn't start with +, assume it needs country code
258
- if (!formatted.startsWith('+')) {
259
- formatted = `+${formatted}`;
260
- }
208
+ // Remove any non-digit characters
209
+ let formatted = phoneNumber.replace(/\D/g, '');
261
210
 
262
211
  return formatted;
263
212
  }
@@ -292,46 +241,6 @@ export class TubelightWhatsAppStrategy implements IntegrationStrategy {
292
241
  return false;
293
242
  }
294
243
 
295
- // Validate message type specific requirements
296
- if (config.messageType) {
297
- switch (config.messageType) {
298
- case 'template':
299
- if (!config.templateId) {
300
- return false;
301
- }
302
- break;
303
-
304
- case 'image':
305
- case 'document':
306
- case 'audio':
307
- case 'video':
308
- if (!config.mediaUrl) {
309
- return false;
310
- }
311
- break;
312
-
313
- case 'location':
314
- if (
315
- typeof config.latitude !== 'number' ||
316
- typeof config.longitude !== 'number'
317
- ) {
318
- return false;
319
- }
320
- break;
321
-
322
- case 'interactive':
323
- if (
324
- !config.buttons &&
325
- !config.sections &&
326
- (!config.interactiveType ||
327
- !['button', 'list'].includes(config.interactiveType))
328
- ) {
329
- return false;
330
- }
331
- break;
332
- }
333
- }
334
-
335
244
  return true;
336
245
  }
337
246
 
@@ -347,7 +256,6 @@ export class TubelightWhatsAppStrategy implements IntegrationStrategy {
347
256
 
348
257
  const response = await axios.get(url, {
349
258
  headers: {
350
- 'X-TENANT-ID': config.tenantId,
351
259
  Authorization: `Bearer ${token}`,
352
260
  },
353
261
  });
@@ -364,7 +272,14 @@ export class TubelightWhatsAppStrategy implements IntegrationStrategy {
364
272
  }
365
273
  }
366
274
 
367
- async getTemplates(config: TubelightWhatsAppConfig): Promise<any> {
275
+ async getTemplates(config: TubelightWhatsAppConfig): Promise<{
276
+ success: boolean;
277
+ data?: Array<{
278
+ label: string;
279
+ value: string;
280
+ }>;
281
+ error?: string;
282
+ }> {
368
283
  try {
369
284
  const token = await this.authenticate(config);
370
285
  if (!token) {
@@ -372,24 +287,52 @@ export class TubelightWhatsAppStrategy implements IntegrationStrategy {
372
287
  }
373
288
 
374
289
  const baseUrl = config.baseUrl || this.defaultBaseUrl;
375
- const url = `${baseUrl}/templates`;
290
+ const url = `${baseUrl}/retreive/template`;
376
291
 
377
- const response = await axios.get(url, {
378
- headers: {
379
- 'X-TENANT-ID': config.tenantId,
380
- Authorization: `Bearer ${token}`,
292
+ const response = await axios.post(
293
+ url,
294
+ {},
295
+ {
296
+ headers: {
297
+ 'Content-Type': 'application/json',
298
+ Authorization: `Bearer ${token}`,
299
+ },
381
300
  },
382
- });
301
+ );
383
302
 
384
- return response.data;
303
+ const templates = response.data?.data || response.data?.templates || response.data || [];
304
+
305
+ // Format the response with templateName as both label and value
306
+ const formattedTemplates = Array.isArray(templates)
307
+ ? templates.map((template: any) => ({
308
+ label: template.templateName,
309
+ value: template.templateName,
310
+ }))
311
+ : [];
312
+
313
+ return {
314
+ success: true,
315
+ data: formattedTemplates,
316
+ };
385
317
  } catch (error) {
386
- this.logger.error(
387
- 'Failed to get Tubelight templates',
388
- error.response?.data || error.message,
389
- );
390
- throw new Error(
391
- `Failed to get templates: ${this.extractErrorMessage(error)}`,
392
- );
318
+ let errorMessage = 'Failed to fetch Tubelight templates';
319
+
320
+ if (error.response?.status === 401) {
321
+ errorMessage = 'Authentication failed';
322
+ } else if (error.response?.data?.message) {
323
+ errorMessage = error.response.data.message;
324
+ } else if (error.response?.data?.error) {
325
+ errorMessage = error.response.data.error;
326
+ } else if (error.message) {
327
+ errorMessage = error.message;
328
+ }
329
+
330
+ this.logger.error('Failed to get Tubelight templates', errorMessage);
331
+
332
+ return {
333
+ success: false,
334
+ error: errorMessage,
335
+ };
393
336
  }
394
337
  }
395
338
 
@@ -408,7 +351,6 @@ export class TubelightWhatsAppStrategy implements IntegrationStrategy {
408
351
 
409
352
  const response = await axios.get(url, {
410
353
  headers: {
411
- 'X-TENANT-ID': config.tenantId,
412
354
  Authorization: `Bearer ${token}`,
413
355
  },
414
356
  });
@@ -65,7 +65,12 @@ export class FieldMapperService extends EntityServiceImpl {
65
65
  }
66
66
 
67
67
  async getMapperFields(mapperId: number) {
68
- return await this.fieldMapperRepository.findByMapperId(mapperId);
68
+ let fieldMappers = await this.fieldMapperRepository.findByMapperId(mapperId);
69
+ const fieldMapperDtos = fieldMappers as unknown as FieldMapperDto[];
70
+ for(const fieldMapper of fieldMapperDtos) {
71
+ fieldMapper.field_lovs = await this.fieldLovsRepository.findByMapperFieldId(fieldMapper.id);
72
+ }
73
+ return fieldMapperDtos;
69
74
  }
70
75
 
71
76
  async getFieldLovs(mapperFieldId: number) {