rez_core 2.3.1 → 2.3.2

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 (80) hide show
  1. package/dist/module/enterprise/repository/school.repository.js +0 -1
  2. package/dist/module/enterprise/repository/school.repository.js.map +1 -1
  3. package/dist/module/filter/repository/saved-filter.repository.js +0 -1
  4. package/dist/module/filter/repository/saved-filter.repository.js.map +1 -1
  5. package/dist/module/integration/factories/whatsapp.factory.d.ts +5 -1
  6. package/dist/module/integration/factories/whatsapp.factory.js +14 -2
  7. package/dist/module/integration/factories/whatsapp.factory.js.map +1 -1
  8. package/dist/module/integration/integration.module.js +4 -0
  9. package/dist/module/integration/integration.module.js.map +1 -1
  10. package/dist/module/integration/strategies/whatsapp/gupshup-whatsapp.strategy.d.ts +19 -0
  11. package/dist/module/integration/strategies/whatsapp/gupshup-whatsapp.strategy.js +257 -0
  12. package/dist/module/integration/strategies/whatsapp/gupshup-whatsapp.strategy.js.map +1 -0
  13. package/dist/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.d.ts +22 -0
  14. package/dist/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.js +295 -0
  15. package/dist/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.js.map +1 -0
  16. package/dist/module/layout_preference/service/layout_preference.service.js +0 -2
  17. package/dist/module/layout_preference/service/layout_preference.service.js.map +1 -1
  18. package/dist/module/listmaster/service/list-master.service.js +0 -1
  19. package/dist/module/listmaster/service/list-master.service.js.map +1 -1
  20. package/dist/module/master/service/master.service.js +0 -2
  21. package/dist/module/master/service/master.service.js.map +1 -1
  22. package/dist/module/meta/repository/entity-table.repository.js +0 -1
  23. package/dist/module/meta/repository/entity-table.repository.js.map +1 -1
  24. package/dist/module/meta/service/entity-dynamic.service.js +0 -1
  25. package/dist/module/meta/service/entity-dynamic.service.js.map +1 -1
  26. package/dist/module/meta/service/entity-service-impl.service.js +0 -2
  27. package/dist/module/meta/service/entity-service-impl.service.js.map +1 -1
  28. package/dist/module/meta/service/entity-table.service.js +0 -2
  29. package/dist/module/meta/service/entity-table.service.js.map +1 -1
  30. package/dist/module/meta/service/field-group.service.js +0 -1
  31. package/dist/module/meta/service/field-group.service.js.map +1 -1
  32. package/dist/module/module/service/module-access.service.js +0 -6
  33. package/dist/module/module/service/module-access.service.js.map +1 -1
  34. package/dist/module/notification/service/email.service.js +0 -1
  35. package/dist/module/notification/service/email.service.js.map +1 -1
  36. package/dist/module/user/service/login.service.js +0 -1
  37. package/dist/module/user/service/login.service.js.map +1 -1
  38. package/dist/module/workflow/repository/stage.repository.js +0 -1
  39. package/dist/module/workflow/repository/stage.repository.js.map +1 -1
  40. package/dist/module/workflow/service/action.service.js +0 -1
  41. package/dist/module/workflow/service/action.service.js.map +1 -1
  42. package/dist/module/workflow/service/comm-template.service.js +0 -1
  43. package/dist/module/workflow/service/comm-template.service.js.map +1 -1
  44. package/dist/module/workflow/service/populate-workflow.service.js +0 -5
  45. package/dist/module/workflow/service/populate-workflow.service.js.map +1 -1
  46. package/dist/module/workflow/service/task.service.js +0 -1
  47. package/dist/module/workflow/service/task.service.js.map +1 -1
  48. package/dist/module/workflow/service/workflow-meta.service.js +0 -2
  49. package/dist/module/workflow/service/workflow-meta.service.js.map +1 -1
  50. package/dist/module/workflow-automation/service/workflow-automation-engine.service.js +0 -1
  51. package/dist/module/workflow-automation/service/workflow-automation-engine.service.js.map +1 -1
  52. package/dist/tsconfig.build.tsbuildinfo +1 -1
  53. package/docs/modules/integration.md +55 -1
  54. package/package.json +1 -1
  55. package/src/module/enterprise/repository/school.repository.ts +0 -1
  56. package/src/module/filter/repository/saved-filter.repository.ts +0 -1
  57. package/src/module/integration/factories/whatsapp.factory.ts +10 -0
  58. package/src/module/integration/integration.module.ts +4 -0
  59. package/src/module/integration/strategies/whatsapp/gupshup-whatsapp.strategy.ts +360 -0
  60. package/src/module/integration/strategies/whatsapp/tubelight-whatsapp.strategy.ts +419 -0
  61. package/src/module/layout_preference/service/layout_preference.service.ts +0 -2
  62. package/src/module/listmaster/service/list-master.service.ts +1 -10
  63. package/src/module/master/service/master.service.ts +0 -2
  64. package/src/module/meta/repository/entity-table.repository.ts +0 -1
  65. package/src/module/meta/service/entity-dynamic.service.ts +1 -5
  66. package/src/module/meta/service/entity-service-impl.service.ts +0 -4
  67. package/src/module/meta/service/entity-table.service.ts +0 -2
  68. package/src/module/meta/service/field-group.service.ts +0 -1
  69. package/src/module/module/service/module-access.service.ts +0 -13
  70. package/src/module/notification/service/email.service.ts +0 -1
  71. package/src/module/user/service/login.service.ts +0 -1
  72. package/src/module/workflow/repository/stage.repository.ts +1 -2
  73. package/src/module/workflow/service/action.service.ts +0 -1
  74. package/src/module/workflow/service/comm-template.service.ts +0 -1
  75. package/src/module/workflow/service/populate-workflow.service.ts +2 -12
  76. package/src/module/workflow/service/task.service.ts +0 -1
  77. package/src/module/workflow/service/workflow-meta.service.ts +1 -4
  78. package/src/module/workflow-automation/service/workflow-automation-engine.service.ts +0 -1
  79. package/src/resources/dev.properties.yaml +1 -1
  80. package/.vscode/extensions.json +0 -5
@@ -139,7 +139,7 @@ export class IntegrationService {
139
139
  ```json
140
140
  {
141
141
  "app_code": "MYAPP",
142
- "integration_type": "WA",
142
+ "integration_type": "WA",
143
143
  "integration_provider": "whatsapp",
144
144
  "integration_source_id": 3,
145
145
  "config_json": {
@@ -150,6 +150,60 @@ export class IntegrationService {
150
150
  }
151
151
  ```
152
152
 
153
+ ### Gupshup WhatsApp
154
+ ```json
155
+ {
156
+ "app_code": "MYAPP",
157
+ "integration_type": "WA",
158
+ "integration_provider": "gupshup",
159
+ "integration_source_id": 4,
160
+ "config_json": {
161
+ "apiKey": "your-gupshup-api-key",
162
+ "appName": "your-app-name",
163
+ "sourceNumber": "your-source-number",
164
+ "baseUrl": "https://api.gupshup.io/sm/api/v1"
165
+ }
166
+ }
167
+ ```
168
+
169
+ **Gupshup Message Types:**
170
+ - **Text Message**: Default type, just send message
171
+ - **Template**: Requires `messageType: "template"`, `templateId`, optional `templateParams`
172
+ - **Image**: Requires `messageType: "image"`, `mediaUrl`, optional `previewUrl`, `caption`
173
+ - **Document**: Requires `messageType: "document"`, `mediaUrl`, optional `filename`, `caption`
174
+ - **Audio**: Requires `messageType: "audio"`, `mediaUrl`
175
+ - **Video**: Requires `messageType: "video"`, `mediaUrl`, optional `caption`
176
+ - **Location**: Requires `messageType: "location"`, `latitude`, `longitude`, optional `locationName`, `locationAddress`
177
+ - **List**: Requires `messageType: "list"`, `listItems`, optional `listTitle`, `globalButtons`
178
+ - **Quick Reply**: Requires `messageType: "quick_reply"`, `quickReplyOptions`
179
+
180
+ ### Tubelight WhatsApp
181
+ ```json
182
+ {
183
+ "app_code": "MYAPP",
184
+ "integration_type": "WA",
185
+ "integration_provider": "tubelight",
186
+ "integration_source_id": 5,
187
+ "config_json": {
188
+ "userName": "your-tubelight-username",
189
+ "password": "your-tubelight-password",
190
+ "tenantId": "your-tenant-id",
191
+ "sourceNumber": "your-source-number",
192
+ "baseUrl": "https://portal.tubelightcommunications.com/whatsapp/api/v1"
193
+ }
194
+ }
195
+ ```
196
+
197
+ **Tubelight Message Types:**
198
+ - **Text Message**: Default type with `messageType: "TEXT"` or no messageType specified
199
+ - **Template**: Requires `messageType: "template"`, `templateId`, optional `templateParams`, `templateLanguage` (default: "en")
200
+ - **Image**: Requires `messageType: "image"`, `mediaUrl`, optional `caption`
201
+ - **Document**: Requires `messageType: "document"`, `mediaUrl`, optional `filename`, `caption`
202
+ - **Audio**: Requires `messageType: "audio"`, `mediaUrl`
203
+ - **Video**: Requires `messageType: "video"`, `mediaUrl`, optional `caption`
204
+ - **Location**: Requires `messageType: "location"`, `latitude`, `longitude`, optional `locationName`, `locationAddress`
205
+ - **Interactive**: Requires `messageType: "interactive"`, `interactiveType` ("button" or "list"), optional `buttons`, `sections`, `header`, `footer`
206
+
153
207
  ### Knowlarity Voice
154
208
  ```json
155
209
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rez_core",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "private": false,
@@ -37,7 +37,6 @@ export class SchoolRepository {
37
37
  .andWhere('urm.level_type = :levelType', { levelType: 'ORG' })
38
38
  .getRawMany();
39
39
 
40
- console.log('hasOrgAccess', hasOrgAccess);
41
40
 
42
41
  let currentORGLevel_id = hasOrgAccess[0]?.urm_level_id;
43
42
  if (hasOrgAccess[0]?.urm_level_id == 1) {
@@ -35,7 +35,6 @@ export class SavedFilterRepositoryService {
35
35
  }
36
36
 
37
37
  async getDetailsByCode(code: string): Promise<SavedFilterDetail[]> {
38
- console.log("IN GET DETAILS BY CODE", code);
39
38
  return this.savedFilterDetailRepo.find({
40
39
  where: { mapped_filter_code: code },
41
40
  });
@@ -3,12 +3,16 @@ import { BaseFactory } from './base.factory';
3
3
  import { IntegrationStrategy } from '../strategies/integration.strategy';
4
4
  import { WhatsAppStrategy } from '../strategies/whatsapp/whatsapp.strategy';
5
5
  import { WhatsAppCloudStrategy } from '../strategies/whatsapp/whatsapp-cloud.strategy';
6
+ import { GupshupWhatsAppStrategy } from '../strategies/whatsapp/gupshup-whatsapp.strategy';
7
+ import { TubelightWhatsAppStrategy } from '../strategies/whatsapp/tubelight-whatsapp.strategy';
6
8
 
7
9
  @Injectable()
8
10
  export class WhatsAppFactory implements BaseFactory {
9
11
  constructor(
10
12
  private whatsappStrategy: WhatsAppStrategy,
11
13
  private whatsappCloudStrategy: WhatsAppCloudStrategy,
14
+ private gupshupWhatsAppStrategy: GupshupWhatsAppStrategy,
15
+ private tubelightWhatsAppStrategy: TubelightWhatsAppStrategy,
12
16
  ) {}
13
17
 
14
18
  createProvider(service: string, provider: string): IntegrationStrategy {
@@ -20,6 +24,10 @@ export class WhatsAppFactory implements BaseFactory {
20
24
  case 'api_whatsapp-cloud':
21
25
  case 'api_meta':
22
26
  return this.whatsappCloudStrategy;
27
+ case 'api_gupshup':
28
+ return this.gupshupWhatsAppStrategy;
29
+ case 'api_tubelight':
30
+ return this.tubelightWhatsAppStrategy;
23
31
 
24
32
  default:
25
33
  throw new Error(
@@ -33,6 +41,8 @@ export class WhatsAppFactory implements BaseFactory {
33
41
  { service: 'API', provider: 'whatsapp' },
34
42
  { service: 'API', provider: 'whatsapp-cloud' },
35
43
  { service: 'API', provider: 'meta' },
44
+ { service: 'API', provider: 'gupshup' },
45
+ { service: 'API', provider: 'tubelight' },
36
46
  ];
37
47
  }
38
48
 
@@ -17,6 +17,8 @@ import { SendGridApiStrategy } from './strategies/email/sendgrid-api.strategy';
17
17
  import { WhatsAppCloudStrategy } from './strategies/whatsapp/whatsapp-cloud.strategy';
18
18
  import { OzonetelVoiceStrategy } from './strategies/telephone/ozonetel-voice.strategy';
19
19
  import { TubelightVoiceStrategy } from './strategies/telephone/tubelight-voice.strategy';
20
+ import { GupshupWhatsAppStrategy } from './strategies/whatsapp/gupshup-whatsapp.strategy';
21
+ import { TubelightWhatsAppStrategy } from './strategies/whatsapp/tubelight-whatsapp.strategy';
20
22
 
21
23
  // New unified strategies
22
24
  import { OutlookStrategy } from './strategies/email/outlook.strategy';
@@ -61,6 +63,8 @@ import { UserIntegration } from './entity/user-integration.entity';
61
63
  WhatsAppCloudStrategy,
62
64
  OzonetelVoiceStrategy,
63
65
  TubelightVoiceStrategy,
66
+ GupshupWhatsAppStrategy,
67
+ TubelightWhatsAppStrategy,
64
68
 
65
69
  // New unified strategies
66
70
  OutlookStrategy,
@@ -0,0 +1,360 @@
1
+ import { Injectable, Logger } from '@nestjs/common';
2
+ import axios, { AxiosResponse } from 'axios';
3
+ import {
4
+ IntegrationStrategy,
5
+ IntegrationResult,
6
+ } from '../integration.strategy';
7
+
8
+ interface GupshupConfig {
9
+ apiKey: string;
10
+ appName: string;
11
+ sourceNumber: string;
12
+ baseUrl?: string;
13
+ }
14
+
15
+ interface GupshupMessage {
16
+ channel: string;
17
+ source: string;
18
+ destination: string;
19
+ 'src.name': string;
20
+ message: {
21
+ type: string;
22
+ text?: string;
23
+ url?: string;
24
+ caption?: string;
25
+ filename?: string;
26
+ };
27
+ }
28
+
29
+ interface GupshupApiResponse {
30
+ status: string;
31
+ messageId?: string;
32
+ response?: {
33
+ id: string;
34
+ phone: string;
35
+ details: string;
36
+ };
37
+ error?: {
38
+ code: number;
39
+ message: string;
40
+ };
41
+ }
42
+
43
+ @Injectable()
44
+ export class GupshupWhatsAppStrategy implements IntegrationStrategy {
45
+ private readonly logger = new Logger(GupshupWhatsAppStrategy.name);
46
+ private readonly defaultBaseUrl = 'https://api.gupshup.io/sm/api/v1';
47
+
48
+ async sendMessage(
49
+ to: string,
50
+ message: string,
51
+ config: any,
52
+ ): Promise<IntegrationResult> {
53
+ try {
54
+ if (!this.validateConfig(config)) {
55
+ throw new Error('Invalid Gupshup WhatsApp configuration');
56
+ }
57
+
58
+ const gupshupConfig = config as GupshupConfig;
59
+ const baseUrl = gupshupConfig.baseUrl || this.defaultBaseUrl;
60
+ const url = `${baseUrl}/msg`;
61
+
62
+ const payload = this.prepareMessagePayload(to, message, config);
63
+
64
+ const response: AxiosResponse<GupshupApiResponse> = await axios.post(
65
+ url,
66
+ payload,
67
+ {
68
+ headers: {
69
+ apikey: gupshupConfig.apiKey,
70
+ 'Content-Type': 'application/x-www-form-urlencoded',
71
+ },
72
+ timeout: 30000,
73
+ },
74
+ );
75
+
76
+ if (response.data?.status === 'submitted' && response.data?.messageId) {
77
+ this.logger.log(
78
+ `Gupshup WhatsApp message sent successfully to ${to}, messageId: ${response.data.messageId}`,
79
+ );
80
+
81
+ return {
82
+ success: true,
83
+ messageId: response.data.messageId,
84
+ provider: 'gupshup',
85
+ service: 'API',
86
+ timestamp: new Date(),
87
+ };
88
+ } else if (response.data?.response?.id) {
89
+ this.logger.log(
90
+ `Gupshup WhatsApp message sent successfully to ${to}, messageId: ${response.data.response.id}`,
91
+ );
92
+
93
+ return {
94
+ success: true,
95
+ messageId: response.data.response.id,
96
+ provider: 'gupshup',
97
+ service: 'API',
98
+ timestamp: new Date(),
99
+ };
100
+ } else {
101
+ throw new Error(
102
+ response.data?.error?.message || 'Invalid response from Gupshup API',
103
+ );
104
+ }
105
+ } catch (error) {
106
+ this.logger.error(
107
+ `Failed to send Gupshup WhatsApp message to ${to}`,
108
+ error.response?.data || error.message,
109
+ );
110
+
111
+ return {
112
+ success: false,
113
+ provider: 'gupshup',
114
+ service: 'API',
115
+ error: this.extractErrorMessage(error),
116
+ timestamp: new Date(),
117
+ };
118
+ }
119
+ }
120
+
121
+ private prepareMessagePayload(
122
+ to: string,
123
+ message: string,
124
+ config: any,
125
+ ): URLSearchParams {
126
+ const gupshupConfig = config as GupshupConfig;
127
+ const params = new URLSearchParams();
128
+
129
+ params.append('channel', 'whatsapp');
130
+ params.append('source', gupshupConfig.sourceNumber);
131
+ params.append('destination', this.formatPhoneNumber(to));
132
+ params.append('src.name', gupshupConfig.appName);
133
+
134
+ // Handle different message types based on config
135
+ if (config.messageType) {
136
+ switch (config.messageType) {
137
+ case 'template':
138
+ params.append('message', JSON.stringify({
139
+ type: 'template',
140
+ template: {
141
+ id: config.templateId,
142
+ params: config.templateParams || [],
143
+ },
144
+ }));
145
+ break;
146
+
147
+ case 'image':
148
+ params.append('message', JSON.stringify({
149
+ type: 'image',
150
+ originalUrl: config.mediaUrl,
151
+ previewUrl: config.previewUrl || config.mediaUrl,
152
+ caption: message || config.caption || '',
153
+ }));
154
+ break;
155
+
156
+ case 'document':
157
+ params.append('message', JSON.stringify({
158
+ type: 'file',
159
+ url: config.mediaUrl,
160
+ filename: config.filename || 'document',
161
+ caption: message || config.caption || '',
162
+ }));
163
+ break;
164
+
165
+ case 'audio':
166
+ params.append('message', JSON.stringify({
167
+ type: 'audio',
168
+ url: config.mediaUrl,
169
+ }));
170
+ break;
171
+
172
+ case 'video':
173
+ params.append('message', JSON.stringify({
174
+ type: 'video',
175
+ url: config.mediaUrl,
176
+ caption: message || config.caption || '',
177
+ }));
178
+ break;
179
+
180
+ case 'location':
181
+ params.append('message', JSON.stringify({
182
+ type: 'location',
183
+ longitude: config.longitude,
184
+ latitude: config.latitude,
185
+ name: config.locationName || '',
186
+ address: config.locationAddress || '',
187
+ }));
188
+ break;
189
+
190
+ case 'list':
191
+ params.append('message', JSON.stringify({
192
+ type: 'list',
193
+ title: config.listTitle || 'Select an option',
194
+ body: message,
195
+ globalButtons: config.globalButtons || [],
196
+ items: config.listItems || [],
197
+ }));
198
+ break;
199
+
200
+ case 'quick_reply':
201
+ params.append('message', JSON.stringify({
202
+ type: 'quick_reply',
203
+ content: {
204
+ type: 'text',
205
+ text: message,
206
+ },
207
+ options: config.quickReplyOptions || [],
208
+ }));
209
+ break;
210
+
211
+ default:
212
+ // Fall back to text message
213
+ break;
214
+ }
215
+ }
216
+
217
+ // Default text message if no specific type or fallback
218
+ if (!config.messageType || !['template', 'image', 'document', 'audio', 'video', 'location', 'list', 'quick_reply'].includes(config.messageType)) {
219
+ params.append('message', JSON.stringify({
220
+ type: 'text',
221
+ text: message,
222
+ }));
223
+ }
224
+
225
+ return params;
226
+ }
227
+
228
+ private formatPhoneNumber(phoneNumber: string): string {
229
+ // Remove any non-digit characters except +
230
+ let formatted = phoneNumber.replace(/[^\d+]/g, '');
231
+
232
+ // If it doesn't start with +, assume it needs country code
233
+ if (!formatted.startsWith('+')) {
234
+ // Add + if missing
235
+ formatted = `+${formatted}`;
236
+ }
237
+
238
+ return formatted;
239
+ }
240
+
241
+ private extractErrorMessage(error: any): string {
242
+ if (error.response?.data?.error?.message) {
243
+ return error.response.data.error.message;
244
+ }
245
+
246
+ if (error.response?.data?.message) {
247
+ return error.response.data.message;
248
+ }
249
+
250
+ if (error.response?.data?.response?.details) {
251
+ return error.response.data.response.details;
252
+ }
253
+
254
+ if (error.message) {
255
+ return error.message;
256
+ }
257
+
258
+ return 'Unknown Gupshup API error';
259
+ }
260
+
261
+ validateConfig(config: any): boolean {
262
+ if (!config || typeof config !== 'object') {
263
+ return false;
264
+ }
265
+
266
+ // Required fields
267
+ if (!config.apiKey || !config.appName || !config.sourceNumber) {
268
+ return false;
269
+ }
270
+
271
+ // Validate message type specific requirements
272
+ if (config.messageType) {
273
+ switch (config.messageType) {
274
+ case 'template':
275
+ if (!config.templateId) {
276
+ return false;
277
+ }
278
+ break;
279
+
280
+ case 'image':
281
+ case 'document':
282
+ case 'audio':
283
+ case 'video':
284
+ if (!config.mediaUrl) {
285
+ return false;
286
+ }
287
+ break;
288
+
289
+ case 'location':
290
+ if (
291
+ typeof config.latitude !== 'number' ||
292
+ typeof config.longitude !== 'number'
293
+ ) {
294
+ return false;
295
+ }
296
+ break;
297
+
298
+ case 'list':
299
+ if (!config.listItems || !Array.isArray(config.listItems)) {
300
+ return false;
301
+ }
302
+ break;
303
+
304
+ case 'quick_reply':
305
+ if (!config.quickReplyOptions || !Array.isArray(config.quickReplyOptions)) {
306
+ return false;
307
+ }
308
+ break;
309
+ }
310
+ }
311
+
312
+ return true;
313
+ }
314
+
315
+ async getAppInfo(config: GupshupConfig): Promise<any> {
316
+ try {
317
+ const baseUrl = config.baseUrl || this.defaultBaseUrl;
318
+ const url = `${baseUrl}/app`;
319
+
320
+ const response = await axios.get(url, {
321
+ headers: {
322
+ apikey: config.apiKey,
323
+ },
324
+ });
325
+
326
+ return response.data;
327
+ } catch (error) {
328
+ this.logger.error(
329
+ 'Failed to get Gupshup app info',
330
+ error.response?.data || error.message,
331
+ );
332
+ throw new Error(
333
+ `Failed to get app info: ${this.extractErrorMessage(error)}`,
334
+ );
335
+ }
336
+ }
337
+
338
+ async getTemplates(config: GupshupConfig): Promise<any> {
339
+ try {
340
+ const baseUrl = config.baseUrl || this.defaultBaseUrl;
341
+ const url = `${baseUrl}/template/list/${config.appName}`;
342
+
343
+ const response = await axios.get(url, {
344
+ headers: {
345
+ apikey: config.apiKey,
346
+ },
347
+ });
348
+
349
+ return response.data;
350
+ } catch (error) {
351
+ this.logger.error(
352
+ 'Failed to get Gupshup templates',
353
+ error.response?.data || error.message,
354
+ );
355
+ throw new Error(
356
+ `Failed to get templates: ${this.extractErrorMessage(error)}`,
357
+ );
358
+ }
359
+ }
360
+ }