rez_core 3.1.22 → 3.1.24
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/app.module.js +2 -0
- package/dist/app.module.js.map +1 -1
- package/dist/module/filter/service/filter.service.js +22 -18
- package/dist/module/filter/service/filter.service.js.map +1 -1
- package/dist/module/integration/controller/integration.controller.d.ts +17 -1
- package/dist/module/integration/controller/integration.controller.js +56 -0
- package/dist/module/integration/controller/integration.controller.js.map +1 -1
- package/dist/module/integration/dto/create-config.dto.d.ts +7 -0
- package/dist/module/integration/dto/create-config.dto.js +29 -1
- package/dist/module/integration/dto/create-config.dto.js.map +1 -1
- package/dist/module/integration/factories/sms.factory.d.ts +9 -1
- package/dist/module/integration/factories/sms.factory.js +36 -4
- package/dist/module/integration/factories/sms.factory.js.map +1 -1
- package/dist/module/integration/factories/whatsapp.factory.d.ts +5 -1
- package/dist/module/integration/factories/whatsapp.factory.js +14 -2
- package/dist/module/integration/factories/whatsapp.factory.js.map +1 -1
- package/dist/module/integration/integration.module.js +10 -0
- package/dist/module/integration/integration.module.js.map +1 -1
- package/dist/module/integration/service/integration.service.d.ts +8 -0
- package/dist/module/integration/service/integration.service.js +65 -0
- package/dist/module/integration/service/integration.service.js.map +1 -1
- package/dist/module/integration/strategies/sms/gupshup-sms.strategy.d.ts +9 -0
- package/dist/module/integration/strategies/sms/gupshup-sms.strategy.js +104 -0
- package/dist/module/integration/strategies/sms/gupshup-sms.strategy.js.map +1 -0
- package/dist/module/integration/strategies/sms/msg91-sms.strategy.d.ts +9 -0
- package/dist/module/integration/strategies/sms/msg91-sms.strategy.js +113 -0
- package/dist/module/integration/strategies/sms/msg91-sms.strategy.js.map +1 -0
- package/dist/module/integration/strategies/sms/tubelight-sms.strategy.d.ts +9 -0
- package/dist/module/integration/strategies/sms/tubelight-sms.strategy.js +109 -0
- package/dist/module/integration/strategies/sms/tubelight-sms.strategy.js.map +1 -0
- package/dist/module/integration/strategies/telephone/ozonetel-voice.strategy.d.ts +7 -2
- package/dist/module/integration/strategies/telephone/ozonetel-voice.strategy.js +52 -5
- package/dist/module/integration/strategies/telephone/ozonetel-voice.strategy.js.map +1 -1
- package/dist/module/integration/strategies/whatsapp/gupshup-whatsapp.strategy.d.ts +19 -0
- package/dist/module/integration/strategies/whatsapp/gupshup-whatsapp.strategy.js +257 -0
- package/dist/module/integration/strategies/whatsapp/gupshup-whatsapp.strategy.js.map +1 -0
- package/dist/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.d.ts +22 -0
- package/dist/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.js +295 -0
- package/dist/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.js.map +1 -0
- package/dist/module/mapper/controller/field-mapper.controller.d.ts +6 -0
- package/dist/module/mapper/controller/field-mapper.controller.js +45 -0
- package/dist/module/mapper/controller/field-mapper.controller.js.map +1 -0
- package/dist/module/mapper/dto/field-mapper.dto.d.ts +11 -0
- package/dist/module/mapper/dto/field-mapper.dto.js +8 -0
- package/dist/module/mapper/dto/field-mapper.dto.js.map +1 -0
- package/dist/module/mapper/entity/field-lovs.entity.d.ts +7 -0
- package/dist/module/mapper/entity/field-lovs.entity.js +38 -0
- package/dist/module/mapper/entity/field-lovs.entity.js.map +1 -0
- package/dist/module/mapper/entity/field-mapper.entity.d.ts +12 -0
- package/dist/module/mapper/entity/field-mapper.entity.js +58 -0
- package/dist/module/mapper/entity/field-mapper.entity.js.map +1 -0
- package/dist/module/mapper/mapper.module.d.ts +2 -0
- package/dist/module/mapper/mapper.module.js +38 -0
- package/dist/module/mapper/mapper.module.js.map +1 -0
- package/dist/module/mapper/repository/field-lovs.repository.d.ts +8 -0
- package/dist/module/mapper/repository/field-lovs.repository.js +41 -0
- package/dist/module/mapper/repository/field-lovs.repository.js.map +1 -0
- package/dist/module/mapper/repository/field-mapper.repository.d.ts +9 -0
- package/dist/module/mapper/repository/field-mapper.repository.js +42 -0
- package/dist/module/mapper/repository/field-mapper.repository.js.map +1 -0
- package/dist/module/mapper/service/field-mapper.service.d.ts +16 -0
- package/dist/module/mapper/service/field-mapper.service.js +92 -0
- package/dist/module/mapper/service/field-mapper.service.js.map +1 -0
- package/dist/module/meta/service/resolver.service.d.ts +1 -0
- package/dist/module/meta/service/resolver.service.js +30 -0
- package/dist/module/meta/service/resolver.service.js.map +1 -1
- package/dist/module/notification/controller/notification.controller.d.ts +1 -0
- package/dist/module/notification/controller/notification.controller.js +1 -1
- package/dist/module/notification/controller/notification.controller.js.map +1 -1
- package/dist/module/notification/service/notification.service.d.ts +1 -1
- package/dist/module/notification/service/notification.service.js +11 -2
- package/dist/module/notification/service/notification.service.js.map +1 -1
- package/dist/module/workflow/entity/action.entity.d.ts +2 -0
- package/dist/module/workflow/entity/action.entity.js +8 -0
- package/dist/module/workflow/entity/action.entity.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/docs/modules/integration.md +55 -1
- package/package.json +1 -1
- package/src/app.module.ts +2 -0
- package/src/module/filter/service/filter.service.ts +39 -25
- package/src/module/integration/controller/integration.controller.ts +61 -0
- package/src/module/integration/dto/create-config.dto.ts +22 -0
- package/src/module/integration/factories/sms.factory.ts +39 -6
- package/src/module/integration/factories/whatsapp.factory.ts +10 -0
- package/src/module/integration/integration.module.ts +14 -0
- package/src/module/integration/service/integration.service.ts +128 -0
- package/src/module/integration/strategies/sms/gupshup-sms.strategy.ts +146 -0
- package/src/module/integration/strategies/sms/msg91-sms.strategy.ts +164 -0
- package/src/module/integration/strategies/sms/tubelight-sms.strategy.ts +163 -0
- package/src/module/integration/strategies/telephone/ozonetel-voice.strategy.ts +80 -7
- package/src/module/integration/strategies/whatsapp/gupshup-whatsapp.strategy.ts +360 -0
- package/src/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.ts +419 -0
- package/src/module/mapper/controller/field-mapper.controller.ts +19 -0
- package/src/module/mapper/dto/field-mapper.dto.ts +14 -0
- package/src/module/mapper/entity/field-lovs.entity.ts +21 -0
- package/src/module/mapper/entity/field-mapper.entity.ts +36 -0
- package/src/module/mapper/mapper.module.ts +26 -0
- package/src/module/mapper/repository/field-lovs.repository.ts +22 -0
- package/src/module/mapper/repository/field-mapper.repository.ts +27 -0
- package/src/module/mapper/service/field-mapper.service.ts +91 -0
- package/src/module/meta/service/resolver.service.ts +58 -0
- package/src/module/notification/controller/notification.controller.ts +8 -1
- package/src/module/notification/service/notification.service.ts +19 -4
- package/src/module/workflow/entity/action.entity.ts +6 -0
- package/src/resources/dev.properties.yaml +1 -1
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
import { Injectable, Logger } from '@nestjs/common';
|
|
2
|
+
import axios, { AxiosResponse } from 'axios';
|
|
3
|
+
import {
|
|
4
|
+
IntegrationResult,
|
|
5
|
+
IntegrationStrategy,
|
|
6
|
+
} from '../integration.strategy';
|
|
7
|
+
|
|
8
|
+
interface TubelightWhatsAppConfig {
|
|
9
|
+
userName: string;
|
|
10
|
+
password: string;
|
|
11
|
+
tenantId: string;
|
|
12
|
+
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
|
+
templateId?: string;
|
|
24
|
+
templateParams?: any[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface TubelightApiResponse {
|
|
28
|
+
success: boolean;
|
|
29
|
+
messageId?: string;
|
|
30
|
+
id?: string;
|
|
31
|
+
status?: string;
|
|
32
|
+
message?: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
details?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@Injectable()
|
|
38
|
+
export class TubelightWhatsAppStrategy implements IntegrationStrategy {
|
|
39
|
+
private readonly logger = new Logger(TubelightWhatsAppStrategy.name);
|
|
40
|
+
private readonly defaultBaseUrl =
|
|
41
|
+
'https://portal.tubelightcommunications.com/whatsapp/api/v1';
|
|
42
|
+
|
|
43
|
+
async sendMessage(
|
|
44
|
+
to: string,
|
|
45
|
+
message: string,
|
|
46
|
+
config: any,
|
|
47
|
+
): Promise<IntegrationResult> {
|
|
48
|
+
try {
|
|
49
|
+
if (!this.validateConfig(config)) {
|
|
50
|
+
throw new Error('Invalid Tubelight WhatsApp configuration');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const tubelightConfig = config as TubelightWhatsAppConfig;
|
|
54
|
+
const token = await this.authenticate(tubelightConfig);
|
|
55
|
+
|
|
56
|
+
if (!token) {
|
|
57
|
+
throw new Error('Failed to authenticate with Tubelight');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const baseUrl = tubelightConfig.baseUrl || this.defaultBaseUrl;
|
|
61
|
+
const url = `${baseUrl}/messages/send`;
|
|
62
|
+
|
|
63
|
+
const payload = this.prepareMessagePayload(to, message, config);
|
|
64
|
+
|
|
65
|
+
const response: AxiosResponse<TubelightApiResponse> = await axios.post(
|
|
66
|
+
url,
|
|
67
|
+
payload,
|
|
68
|
+
{
|
|
69
|
+
headers: {
|
|
70
|
+
'X-TENANT-ID': tubelightConfig.tenantId,
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
Authorization: `Bearer ${token}`,
|
|
73
|
+
},
|
|
74
|
+
timeout: 30000,
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
response.data?.success &&
|
|
80
|
+
(response.data?.messageId || response.data?.id)
|
|
81
|
+
) {
|
|
82
|
+
const messageId = response.data.messageId || response.data.id;
|
|
83
|
+
this.logger.log(
|
|
84
|
+
`Tubelight WhatsApp message sent successfully to ${to}, messageId: ${messageId}`,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
messageId: messageId,
|
|
90
|
+
provider: 'tubelight',
|
|
91
|
+
service: 'API',
|
|
92
|
+
timestamp: new Date(),
|
|
93
|
+
};
|
|
94
|
+
} else {
|
|
95
|
+
throw new Error(
|
|
96
|
+
response.data?.error ||
|
|
97
|
+
response.data?.message ||
|
|
98
|
+
'Invalid response from Tubelight API',
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
this.logger.error(
|
|
103
|
+
`Failed to send Tubelight WhatsApp message to ${to}`,
|
|
104
|
+
error.response?.data || error.message,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
provider: 'tubelight',
|
|
110
|
+
service: 'API',
|
|
111
|
+
error: this.extractErrorMessage(error),
|
|
112
|
+
timestamp: new Date(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private async authenticate(
|
|
118
|
+
config: TubelightWhatsAppConfig,
|
|
119
|
+
): Promise<string | null> {
|
|
120
|
+
try {
|
|
121
|
+
const baseUrl = config.baseUrl || this.defaultBaseUrl;
|
|
122
|
+
const authUrl = baseUrl.replace('/whatsapp/api/v1', '/auth/api/v1');
|
|
123
|
+
|
|
124
|
+
const response = await axios.post(
|
|
125
|
+
`${authUrl}/login`,
|
|
126
|
+
{
|
|
127
|
+
user_name: config.userName,
|
|
128
|
+
password: config.password,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': 'application/json',
|
|
133
|
+
'X-TENANT-ID': config.tenantId,
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const data = response.data as {
|
|
139
|
+
bearer_token?: string;
|
|
140
|
+
access_token?: string;
|
|
141
|
+
token?: string;
|
|
142
|
+
success?: boolean;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
if (data.success !== false) {
|
|
146
|
+
return data?.bearer_token || data?.access_token || data?.token || null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return null;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
this.logger.error(
|
|
152
|
+
'Failed to authenticate with Tubelight:',
|
|
153
|
+
error.response?.data || error.message,
|
|
154
|
+
);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private prepareMessagePayload(to: string, message: string, config: any): any {
|
|
160
|
+
const basePayload = {
|
|
161
|
+
to: this.formatPhoneNumber(to),
|
|
162
|
+
from: config.sourceNumber || config.from,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// Handle different message types based on config
|
|
166
|
+
if (config.messageType) {
|
|
167
|
+
switch (config.messageType) {
|
|
168
|
+
case 'template':
|
|
169
|
+
return {
|
|
170
|
+
...basePayload,
|
|
171
|
+
messageType: 'TEMPLATE',
|
|
172
|
+
templateId: config.templateId,
|
|
173
|
+
templateParams: config.templateParams || [],
|
|
174
|
+
language: config.templateLanguage || 'en',
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
case 'image':
|
|
178
|
+
return {
|
|
179
|
+
...basePayload,
|
|
180
|
+
messageType: 'IMAGE',
|
|
181
|
+
mediaUrl: config.mediaUrl,
|
|
182
|
+
caption: message || config.caption || '',
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
case 'document':
|
|
186
|
+
return {
|
|
187
|
+
...basePayload,
|
|
188
|
+
messageType: 'DOCUMENT',
|
|
189
|
+
mediaUrl: config.mediaUrl,
|
|
190
|
+
filename: config.filename || 'document',
|
|
191
|
+
caption: message || config.caption || '',
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
case 'audio':
|
|
195
|
+
return {
|
|
196
|
+
...basePayload,
|
|
197
|
+
messageType: 'AUDIO',
|
|
198
|
+
mediaUrl: config.mediaUrl,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
case 'video':
|
|
202
|
+
return {
|
|
203
|
+
...basePayload,
|
|
204
|
+
messageType: 'VIDEO',
|
|
205
|
+
mediaUrl: config.mediaUrl,
|
|
206
|
+
caption: message || config.caption || '',
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
case 'location':
|
|
210
|
+
return {
|
|
211
|
+
...basePayload,
|
|
212
|
+
messageType: 'LOCATION',
|
|
213
|
+
latitude: config.latitude,
|
|
214
|
+
longitude: config.longitude,
|
|
215
|
+
name: config.locationName || '',
|
|
216
|
+
address: config.locationAddress || '',
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
case 'interactive':
|
|
220
|
+
return {
|
|
221
|
+
...basePayload,
|
|
222
|
+
messageType: 'INTERACTIVE',
|
|
223
|
+
interactiveType: config.interactiveType || 'button',
|
|
224
|
+
text: message,
|
|
225
|
+
buttons: config.buttons || [],
|
|
226
|
+
sections: config.sections || [],
|
|
227
|
+
header: config.header,
|
|
228
|
+
footer: config.footer,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
default:
|
|
232
|
+
// Fall back to text message
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Default text message
|
|
238
|
+
return {
|
|
239
|
+
...basePayload,
|
|
240
|
+
messageType: 'TEXT',
|
|
241
|
+
text: message,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private formatPhoneNumber(phoneNumber: string): string {
|
|
246
|
+
// Remove any non-digit characters except +
|
|
247
|
+
let formatted = phoneNumber.replace(/[^\d+]/g, '');
|
|
248
|
+
|
|
249
|
+
// If it doesn't start with +, assume it needs country code
|
|
250
|
+
if (!formatted.startsWith('+')) {
|
|
251
|
+
formatted = `+${formatted}`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return formatted;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
private extractErrorMessage(error: any): string {
|
|
258
|
+
if (error.response?.data?.error) {
|
|
259
|
+
return error.response.data.error;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (error.response?.data?.message) {
|
|
263
|
+
return error.response.data.message;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (error.response?.data?.details) {
|
|
267
|
+
return error.response.data.details;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (error.message) {
|
|
271
|
+
return error.message;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return 'Unknown Tubelight API error';
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
validateConfig(config: any): boolean {
|
|
278
|
+
if (!config || typeof config !== 'object') {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Required fields
|
|
283
|
+
if (!config.userName || !config.password || !config.tenantId) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Validate message type specific requirements
|
|
288
|
+
if (config.messageType) {
|
|
289
|
+
switch (config.messageType) {
|
|
290
|
+
case 'template':
|
|
291
|
+
if (!config.templateId) {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
break;
|
|
295
|
+
|
|
296
|
+
case 'image':
|
|
297
|
+
case 'document':
|
|
298
|
+
case 'audio':
|
|
299
|
+
case 'video':
|
|
300
|
+
if (!config.mediaUrl) {
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
break;
|
|
304
|
+
|
|
305
|
+
case 'location':
|
|
306
|
+
if (
|
|
307
|
+
typeof config.latitude !== 'number' ||
|
|
308
|
+
typeof config.longitude !== 'number'
|
|
309
|
+
) {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
break;
|
|
313
|
+
|
|
314
|
+
case 'interactive':
|
|
315
|
+
if (
|
|
316
|
+
!config.buttons &&
|
|
317
|
+
!config.sections &&
|
|
318
|
+
(!config.interactiveType ||
|
|
319
|
+
!['button', 'list'].includes(config.interactiveType))
|
|
320
|
+
) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async getAccountInfo(config: TubelightWhatsAppConfig): Promise<any> {
|
|
331
|
+
try {
|
|
332
|
+
const token = await this.authenticate(config);
|
|
333
|
+
if (!token) {
|
|
334
|
+
throw new Error('Authentication failed');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const baseUrl = config.baseUrl || this.defaultBaseUrl;
|
|
338
|
+
const url = `${baseUrl}/account/info`;
|
|
339
|
+
|
|
340
|
+
const response = await axios.get(url, {
|
|
341
|
+
headers: {
|
|
342
|
+
'X-TENANT-ID': config.tenantId,
|
|
343
|
+
Authorization: `Bearer ${token}`,
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
return response.data;
|
|
348
|
+
} catch (error) {
|
|
349
|
+
this.logger.error(
|
|
350
|
+
'Failed to get Tubelight account info',
|
|
351
|
+
error.response?.data || error.message,
|
|
352
|
+
);
|
|
353
|
+
throw new Error(
|
|
354
|
+
`Failed to get account info: ${this.extractErrorMessage(error)}`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async getTemplates(config: TubelightWhatsAppConfig): Promise<any> {
|
|
360
|
+
try {
|
|
361
|
+
const token = await this.authenticate(config);
|
|
362
|
+
if (!token) {
|
|
363
|
+
throw new Error('Authentication failed');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const baseUrl = config.baseUrl || this.defaultBaseUrl;
|
|
367
|
+
const url = `${baseUrl}/templates`;
|
|
368
|
+
|
|
369
|
+
const response = await axios.get(url, {
|
|
370
|
+
headers: {
|
|
371
|
+
'X-TENANT-ID': config.tenantId,
|
|
372
|
+
Authorization: `Bearer ${token}`,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
return response.data;
|
|
377
|
+
} catch (error) {
|
|
378
|
+
this.logger.error(
|
|
379
|
+
'Failed to get Tubelight templates',
|
|
380
|
+
error.response?.data || error.message,
|
|
381
|
+
);
|
|
382
|
+
throw new Error(
|
|
383
|
+
`Failed to get templates: ${this.extractErrorMessage(error)}`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async getMessageStatus(
|
|
389
|
+
config: TubelightWhatsAppConfig,
|
|
390
|
+
messageId: string,
|
|
391
|
+
): Promise<any> {
|
|
392
|
+
try {
|
|
393
|
+
const token = await this.authenticate(config);
|
|
394
|
+
if (!token) {
|
|
395
|
+
throw new Error('Authentication failed');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const baseUrl = config.baseUrl || this.defaultBaseUrl;
|
|
399
|
+
const url = `${baseUrl}/messages/${messageId}/status`;
|
|
400
|
+
|
|
401
|
+
const response = await axios.get(url, {
|
|
402
|
+
headers: {
|
|
403
|
+
'X-TENANT-ID': config.tenantId,
|
|
404
|
+
Authorization: `Bearer ${token}`,
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
return response.data;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
this.logger.error(
|
|
411
|
+
'Failed to get message status',
|
|
412
|
+
error.response?.data || error.message,
|
|
413
|
+
);
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Failed to get message status: ${this.extractErrorMessage(error)}`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Controller, Inject, Post, Query, Req, UseGuards } from '@nestjs/common';
|
|
2
|
+
import { FieldMapperService } from '../service/field-mapper.service';
|
|
3
|
+
import { JwtAuthGuard } from '../../auth/guards/jwt.guard';
|
|
4
|
+
|
|
5
|
+
@Controller('field-mapper')
|
|
6
|
+
export class FieldMapperController {
|
|
7
|
+
constructor(@Inject('FieldMapperService') private readonly fieldMapperService: FieldMapperService) {
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@Post('resolve')
|
|
11
|
+
@UseGuards(JwtAuthGuard)
|
|
12
|
+
async resolve(@Req() req: any, @Query('integration_component') integration_component: string,
|
|
13
|
+
@Query('parent_type') parent_type: string,
|
|
14
|
+
@Query('parent_id') parent_id: number) {
|
|
15
|
+
const loggedInUser = req.user.userData;
|
|
16
|
+
return this.fieldMapperService.resolveData(integration_component, parent_type, parent_id, loggedInUser);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseEntity } from '../../meta/entity/base-entity.entity';
|
|
2
|
+
|
|
3
|
+
export class FieldMapperDto extends BaseEntity {
|
|
4
|
+
integration_component: string;
|
|
5
|
+
action: string;
|
|
6
|
+
source_attribute: string;
|
|
7
|
+
destination_attribute: string;
|
|
8
|
+
destination_entity_type: string;
|
|
9
|
+
mapped_entity_type: string;
|
|
10
|
+
filter_code?: string;
|
|
11
|
+
filter_json?: any;
|
|
12
|
+
|
|
13
|
+
}
|
|
14
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Column, Entity } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../../meta/entity/base-entity.entity';
|
|
3
|
+
|
|
4
|
+
@Entity({ name: 'cr_field_lov_mapper' })
|
|
5
|
+
export class FieldLovMapper extends BaseEntity {
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
this.entity_type = 'FLOV';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@Column({ name: 'mapper_field_id'})
|
|
13
|
+
mapper_field_id: number; //1
|
|
14
|
+
|
|
15
|
+
@Column({ name: 'destination_attribute_value', type: 'varchar', length: 30 })
|
|
16
|
+
destination_attribute_value: string; //INDIA
|
|
17
|
+
|
|
18
|
+
@Column({ name: 'source_attribute_value', type: 'varchar', length: 30 })
|
|
19
|
+
source_attribute_value: string; //IND
|
|
20
|
+
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Column, Entity } from 'typeorm';
|
|
2
|
+
import { BaseEntity } from '../../meta/entity/base-entity.entity';
|
|
3
|
+
|
|
4
|
+
@Entity({ name: 'cr_field_mapper' })
|
|
5
|
+
export class FieldMapper extends BaseEntity {
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
super();
|
|
9
|
+
this.entity_type = 'FMAP';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@Column({ name: 'integration_component', type: 'varchar', length: 30 })
|
|
13
|
+
integration_component: string;
|
|
14
|
+
|
|
15
|
+
@Column({ name: 'action', type: 'varchar', length: 30 })
|
|
16
|
+
action: string;
|
|
17
|
+
|
|
18
|
+
@Column({ name: 'source_attribute', type: 'varchar', length: 30 })
|
|
19
|
+
source_attribute: string;
|
|
20
|
+
|
|
21
|
+
@Column({ name: 'destination_attribute', type: 'varchar', length: 30 })
|
|
22
|
+
destination_attribute: string;
|
|
23
|
+
|
|
24
|
+
@Column({ name: 'destination_entity_type', type: 'varchar', length: 30 })
|
|
25
|
+
destination_entity_type: string;
|
|
26
|
+
|
|
27
|
+
@Column({ name: 'mapped_entity_type', type: 'varchar', length: 30, nullable: true })
|
|
28
|
+
mapped_entity_type: string;
|
|
29
|
+
|
|
30
|
+
@Column({ name: 'filter_code', type: 'varchar', length: 30, default: 'default' })
|
|
31
|
+
filter_code: string;
|
|
32
|
+
|
|
33
|
+
@Column({ name: 'is_lov_present', type: 'tinyint' })
|
|
34
|
+
is_lov_present: number;
|
|
35
|
+
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
3
|
+
import { FieldMapper } from './entity/field-mapper.entity';
|
|
4
|
+
import { FieldMapperService } from './service/field-mapper.service';
|
|
5
|
+
import { FieldMapperRepository } from './repository/field-mapper.repository';
|
|
6
|
+
import { FilterModule } from '../filter/filter.module';
|
|
7
|
+
import { EntityModule } from '../meta/entity.module';
|
|
8
|
+
import { FieldMapperController } from './controller/field-mapper.controller';
|
|
9
|
+
import { FieldLovMapper } from './entity/field-lovs.entity';
|
|
10
|
+
import { FieldLovsRepository } from './repository/field-lovs.repository';
|
|
11
|
+
|
|
12
|
+
@Module({
|
|
13
|
+
imports: [
|
|
14
|
+
TypeOrmModule.forFeature([FieldMapper, FieldLovMapper]),
|
|
15
|
+
FilterModule,
|
|
16
|
+
EntityModule,
|
|
17
|
+
],
|
|
18
|
+
providers: [
|
|
19
|
+
{ provide: 'FieldMapperService', useClass: FieldMapperService },
|
|
20
|
+
FieldMapperRepository,
|
|
21
|
+
FieldLovsRepository,
|
|
22
|
+
],
|
|
23
|
+
controllers: [FieldMapperController],
|
|
24
|
+
})
|
|
25
|
+
export class MapperModule {
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
3
|
+
import { FieldLovMapper } from '../entity/field-lovs.entity';
|
|
4
|
+
import { Repository } from 'typeorm';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class FieldLovsRepository {
|
|
8
|
+
constructor(@InjectRepository(FieldLovMapper) private readonly fieldLovMapperRepository: Repository<FieldLovMapper>) {
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async findByMapperFieldIdAndSourceAttributeValue(mapper_field_id: number, source_attribute_value: string) {
|
|
12
|
+
return await this.fieldLovMapperRepository.findOne({
|
|
13
|
+
where: { mapper_field_id, source_attribute_value },
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async findByMapperFieldIdAndDestinationAttributeValue(mapper_field_id: number, destination_attribute_value: string) {
|
|
18
|
+
return await this.fieldLovMapperRepository.findOne({
|
|
19
|
+
where: { mapper_field_id, destination_attribute_value },
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Injectable } from '@nestjs/common';
|
|
2
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
3
|
+
import { FieldMapper } from '../entity/field-mapper.entity';
|
|
4
|
+
import { Repository } from 'typeorm';
|
|
5
|
+
|
|
6
|
+
@Injectable()
|
|
7
|
+
export class FieldMapperRepository {
|
|
8
|
+
constructor(
|
|
9
|
+
@InjectRepository(FieldMapper)
|
|
10
|
+
private readonly fieldMapperRepository: Repository<FieldMapper>,
|
|
11
|
+
) {
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async findByIntegrationComponentAndDestinationEntityType(integrationComponent: string, destinationEntityType: string) {
|
|
15
|
+
return await this.fieldMapperRepository.find({
|
|
16
|
+
where: { integration_component: integrationComponent, destination_entity_type: destinationEntityType },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async save(dto: Partial<FieldMapper>) {
|
|
21
|
+
return await this.fieldMapperRepository.save(dto);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async saveBulk(dtos: Partial<FieldMapper>[]) {
|
|
25
|
+
return await this.fieldMapperRepository.save(dtos);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
2
|
+
import { FieldMapperRepository } from '../repository/field-mapper.repository';
|
|
3
|
+
import { FieldMapperDto } from '../dto/field-mapper.dto';
|
|
4
|
+
import { EntityServiceImpl } from '../../meta/service/entity-service-impl.service';
|
|
5
|
+
import { UserData } from '../../user/entity/user.entity';
|
|
6
|
+
import { SavedFilterService } from '../../filter/service/saved-filter.service';
|
|
7
|
+
import { FieldLovsRepository } from '../repository/field-lovs.repository';
|
|
8
|
+
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class FieldMapperService extends EntityServiceImpl {
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly fieldMapperRepository: FieldMapperRepository,
|
|
13
|
+
@Inject('SavedFilterService') private readonly savedFilterService: SavedFilterService,
|
|
14
|
+
private readonly fieldLovsRepository: FieldLovsRepository,
|
|
15
|
+
) {
|
|
16
|
+
super();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async createEntity(dto: FieldMapperDto, loggedInUser: UserData) {
|
|
20
|
+
if (dto.filter_json) {
|
|
21
|
+
const savedFilter = await this.savedFilterService.createEntity(dto.filter_json, loggedInUser);
|
|
22
|
+
dto.filter_code = savedFilter.code;
|
|
23
|
+
}
|
|
24
|
+
return super.createEntity(dto, loggedInUser);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async updateEntity(dto: FieldMapperDto, loggedInUser: UserData) {
|
|
28
|
+
if (dto.filter_json) {
|
|
29
|
+
const savedFilter = await this.savedFilterService.createEntity(dto.filter_json, loggedInUser);
|
|
30
|
+
dto.filter_code = savedFilter.code;
|
|
31
|
+
}
|
|
32
|
+
return super.updateEntity(dto, loggedInUser);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async createFieldMappers(dtos: FieldMapperDto[]) {
|
|
36
|
+
return await this.fieldMapperRepository.saveBulk(dtos);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async resolveData(
|
|
40
|
+
integration_component: string,
|
|
41
|
+
parent_type: string,
|
|
42
|
+
parent_id: number,
|
|
43
|
+
userData: UserData,
|
|
44
|
+
) {
|
|
45
|
+
const fieldMappers =
|
|
46
|
+
await this.fieldMapperRepository.findByIntegrationComponentAndDestinationEntityType(
|
|
47
|
+
integration_component,
|
|
48
|
+
parent_type,
|
|
49
|
+
);
|
|
50
|
+
const result: Record<string, any> = {};
|
|
51
|
+
|
|
52
|
+
const inMemory: Record<string, Record<string, any>> = {};
|
|
53
|
+
|
|
54
|
+
for (const field of fieldMappers) {
|
|
55
|
+
const entityType = field.mapped_entity_type ? field.mapped_entity_type : field.destination_entity_type;
|
|
56
|
+
const filterCode = field.filter_code || 'default';
|
|
57
|
+
if (!inMemory[entityType]) {
|
|
58
|
+
inMemory[entityType] = {};
|
|
59
|
+
}
|
|
60
|
+
if (!inMemory[entityType][filterCode] && !field.mapped_entity_type) {
|
|
61
|
+
inMemory[entityType][filterCode] = await super.getResolvedEntityData(
|
|
62
|
+
entityType,
|
|
63
|
+
parent_id,
|
|
64
|
+
userData,
|
|
65
|
+
);
|
|
66
|
+
} else {
|
|
67
|
+
// TODO: Handle mapped_entity_type with filterCode
|
|
68
|
+
inMemory[entityType][filterCode] = {};
|
|
69
|
+
}
|
|
70
|
+
const entityData = inMemory[entityType][filterCode];
|
|
71
|
+
if (entityData) {
|
|
72
|
+
let value = entityData[field.destination_attribute];
|
|
73
|
+
if (field.is_lov_present) {
|
|
74
|
+
if (field.action === 'LOOKUP') {
|
|
75
|
+
let fieldLovMapper = await this.fieldLovsRepository.findByMapperFieldIdAndDestinationAttributeValue(field.id, value);
|
|
76
|
+
if (fieldLovMapper) {
|
|
77
|
+
value = fieldLovMapper.source_attribute_value;
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
let fieldLovMapper = await this.fieldLovsRepository.findByMapperFieldIdAndSourceAttributeValue(field.id, value);
|
|
81
|
+
if (fieldLovMapper) {
|
|
82
|
+
value = fieldLovMapper.destination_attribute_value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
result[field.source_attribute] = value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
}
|