@peopl-health/nexus 1.1.5 → 1.1.7
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/lib/adapters/BaileysProvider.js +1 -1
- package/lib/config/awsConfig.js +103 -0
- package/lib/config/llmConfig.js +4 -0
- package/lib/controllers/assistantController.js +9 -28
- package/lib/controllers/conversationController.js +13 -2
- package/lib/controllers/templateController.js +69 -34
- package/lib/controllers/templateFlowController.js +108 -0
- package/lib/controllers/uploadController.js +82 -0
- package/lib/core/NexusMessaging.js +15 -1
- package/lib/helpers/assistantHelper.js +292 -0
- package/lib/helpers/baileysHelper.js +149 -0
- package/lib/helpers/filesHelper.js +134 -0
- package/lib/helpers/llmsHelper.js +202 -0
- package/lib/helpers/mediaHelper.js +72 -0
- package/lib/helpers/mongoHelper.js +45 -0
- package/lib/helpers/qrHelper.js +22 -0
- package/lib/helpers/twilioHelper.js +138 -0
- package/lib/helpers/whatsappHelper.js +75 -0
- package/lib/models/templateModel.js +72 -0
- package/lib/routes/index.js +5 -3
- package/lib/services/assistantService.js +42 -46
- package/lib/templates/predefinedTemplates.js +81 -0
- package/lib/templates/templateStructure.js +204 -0
- package/lib/utils/errorHandler.js +8 -0
- package/package.json +5 -1
- package/lib/services/whatsappService.js +0 -23
|
@@ -15,7 +15,7 @@ class BaileysProvider extends MessageProvider {
|
|
|
15
15
|
async initialize() {
|
|
16
16
|
try {
|
|
17
17
|
const { default: makeWASocket, useMultiFileAuthState } = require('baileys');
|
|
18
|
-
const { useMongoDBAuthState } = require('../
|
|
18
|
+
const { useMongoDBAuthState } = require('../config/mongoAuthConfig');
|
|
19
19
|
const pino = require('pino');
|
|
20
20
|
|
|
21
21
|
const logger = pino({ level: 'warn' });
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const AWS = require('aws-sdk');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
AWS.config.update({
|
|
6
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
7
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
|
|
8
|
+
region: 'us-east-1',
|
|
9
|
+
signatureVersion: 'v4'
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const s3 = new AWS.S3();
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async function uploadBufferToS3(buffer, bucketName, key, contentType, isWhatsAppMedia = false) {
|
|
16
|
+
const params = {
|
|
17
|
+
Bucket: bucketName,
|
|
18
|
+
Key: key,
|
|
19
|
+
Body: buffer,
|
|
20
|
+
ContentType: contentType,
|
|
21
|
+
Metadata: isWhatsAppMedia ? {
|
|
22
|
+
'x-amz-meta-whatsapp': 'true',
|
|
23
|
+
'x-amz-meta-expiry': new Date(new Date().getTime() + (7 * 24 * 60 * 60 * 1000)).toISOString() // 7 days expiry
|
|
24
|
+
} : {}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const data = await s3.upload(params).promise();
|
|
29
|
+
console.log(`File uploaded successfully at ${data.Location}`);
|
|
30
|
+
return data.Location;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error(`Error uploading file: ${error.message}`);
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function downloadFileFromS3(bucketName, key, downloadPath) {
|
|
38
|
+
console.log(`[S3] Attempting to download file from S3 - Bucket: ${bucketName}, Key: ${key}`);
|
|
39
|
+
|
|
40
|
+
const headParams = {
|
|
41
|
+
Bucket: bucketName,
|
|
42
|
+
Key: key
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await s3.headObject(headParams).promise();
|
|
47
|
+
console.log(`[S3] Key exists in bucket: ${key}`);
|
|
48
|
+
} catch (headErr) {
|
|
49
|
+
console.error(`[S3] Object check failed - Key: ${key}, Error: ${headErr.code} - ${headErr.message}`);
|
|
50
|
+
if (headErr.code === 'NotFound') {
|
|
51
|
+
console.log(`[S3] Key does not exist in bucket: ${key}`);
|
|
52
|
+
}
|
|
53
|
+
throw headErr;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const params = {
|
|
57
|
+
Bucket: bucketName,
|
|
58
|
+
Key: key
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
console.log(`[S3] Downloading file data - Key: ${key}`);
|
|
63
|
+
const data = await s3.getObject(params).promise();
|
|
64
|
+
console.log(`[S3] Successfully retrieved file - Key: ${key}, ContentType: ${data.ContentType}, Size: ${data.ContentLength} bytes`);
|
|
65
|
+
|
|
66
|
+
if (downloadPath) {
|
|
67
|
+
const directory = path.dirname(downloadPath);
|
|
68
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
69
|
+
fs.writeFileSync(downloadPath, data.Body);
|
|
70
|
+
console.log(`[S3] File saved to disk at: ${downloadPath}`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return data;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(`[S3] Error downloading file - Key: ${key}, Error: ${error.code} - ${error.message}`);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function generatePresignedUrl(bucketName, key, expiration = 300) {
|
|
82
|
+
const params = {
|
|
83
|
+
Bucket: bucketName,
|
|
84
|
+
Key: key,
|
|
85
|
+
Expires: expiration
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const url = s3.getSignedUrlPromise('getObject', params);
|
|
90
|
+
console.log(`Presigned URL generated: ${url}`);
|
|
91
|
+
return url;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error(`Error generating presigned URL: ${error.message}`);
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
s3,
|
|
100
|
+
uploadBufferToS3,
|
|
101
|
+
downloadFileFromS3,
|
|
102
|
+
generatePresignedUrl
|
|
103
|
+
};
|
|
@@ -1,30 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
// Optional model imports
|
|
10
|
-
let updateThreadActive, updateThreadStop, Thread;
|
|
11
|
-
try {
|
|
12
|
-
const threadModel = require('../models/threadModel');
|
|
13
|
-
updateThreadActive = threadModel.updateThreadActive;
|
|
14
|
-
updateThreadStop = threadModel.updateThreadStop;
|
|
15
|
-
Thread = threadModel.Thread;
|
|
16
|
-
} catch (e) {
|
|
17
|
-
// Models not available
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Optional service imports - stub functions for missing services
|
|
21
|
-
const getRecordByFilter = () => Promise.resolve(null);
|
|
22
|
-
const createAssistant = () => Promise.resolve({ success: false, error: 'Service not available' });
|
|
23
|
-
const addMsgAssistant = () => Promise.resolve({ success: false, error: 'Service not available' });
|
|
24
|
-
const addInsAssistant = () => Promise.resolve({ success: false, error: 'Service not available' });
|
|
25
|
-
const getThreadInfo = () => Promise.resolve(null);
|
|
26
|
-
const switchAssistant = () => Promise.resolve({ success: false, error: 'Service not available' });
|
|
27
|
-
const sendMessage = () => Promise.resolve({ success: false, error: 'Service not available' });
|
|
1
|
+
const { Config_ID } = require('../config/airtableConfig');
|
|
2
|
+
|
|
3
|
+
const { updateThreadActive, updateThreadStop, Thread } = require('../models/threadModel');
|
|
4
|
+
|
|
5
|
+
const { getRecordByFilter } = require('../services/airtableService');
|
|
6
|
+
const { createAssistant, addMsgAssistant, addInsAssistant } = require('../services/assistantService');
|
|
7
|
+
const { getThreadInfo, switchAssistant } = require('../services/assistantService');
|
|
8
|
+
const { sendMessage } = require('../core/NexusMessaging');
|
|
28
9
|
|
|
29
10
|
|
|
30
11
|
const activeAssistantController = async (req, res) => {
|
|
@@ -96,7 +77,7 @@ const createAssistantController = async (req, res) => {
|
|
|
96
77
|
};
|
|
97
78
|
|
|
98
79
|
const getInfoAssistantController = async (req, res) => {
|
|
99
|
-
const { code } = req.
|
|
80
|
+
const { code } = req.body;
|
|
100
81
|
|
|
101
82
|
try {
|
|
102
83
|
let threadInfo = await getThreadInfo(code);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// Use mongoose models directly to avoid conflicts
|
|
2
|
+
const mongoose = require('mongoose');
|
|
2
3
|
const { fetchConversationData, processConversations } = require('../services/conversationService');
|
|
3
4
|
const { sendMessage } = require('../core/NexusMessaging');
|
|
4
5
|
|
|
6
|
+
const Message = mongoose.models.Message;
|
|
7
|
+
|
|
5
8
|
const getConversationController = async (req, res) => {
|
|
6
9
|
const startTime = Date.now();
|
|
7
10
|
console.log('Starting getConversationController at', new Date().toISOString());
|
|
@@ -15,7 +18,15 @@ const getConversationController = async (req, res) => {
|
|
|
15
18
|
|
|
16
19
|
console.log(`Pagination: page ${page}, limit ${limit}, skip ${skip}, filter: ${filter}`);
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
if (!Message) {
|
|
22
|
+
console.log('Message model not found, returning empty conversations list');
|
|
23
|
+
return res.status(200).json({
|
|
24
|
+
success: true,
|
|
25
|
+
conversations: [],
|
|
26
|
+
emptyDatabase: true
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
19
30
|
const messageCount = await Message.countDocuments({});
|
|
20
31
|
console.log('Total message count:', messageCount);
|
|
21
32
|
|
|
@@ -1,23 +1,51 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
// Nexus provider will be injected - templates only work with Twilio
|
|
2
|
+
let nexusProvider = null;
|
|
3
|
+
|
|
4
|
+
// Configure Nexus provider
|
|
5
|
+
const configureNexusProvider = (provider) => {
|
|
6
|
+
nexusProvider = provider;
|
|
7
7
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
|
|
9
|
+
// Check if provider supports templates
|
|
10
|
+
const checkTemplateSupport = () => {
|
|
11
|
+
if (!nexusProvider) {
|
|
12
|
+
throw new Error('Nexus provider not configured');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Only Twilio provider supports template operations
|
|
16
|
+
if (!nexusProvider.listTemplates || typeof nexusProvider.listTemplates !== 'function') {
|
|
17
|
+
throw new Error('Template operations are only supported with Twilio provider');
|
|
18
|
+
}
|
|
11
19
|
};
|
|
20
|
+
const mongoose = require('mongoose');
|
|
21
|
+
const { handleApiError } = require('../utils/errorHandler');
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
const { Template } = require('../templates/templateStructure');
|
|
24
|
+
const predefinedTemplates = require('../templates/predefinedTemplates');
|
|
25
|
+
|
|
26
|
+
// Use mongoose models to avoid conflicts
|
|
27
|
+
const getTemplateModel = () => {
|
|
28
|
+
if (mongoose.models.Template) {
|
|
29
|
+
return mongoose.models.Template;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If not in mongoose.models, require and return the model
|
|
33
|
+
try {
|
|
34
|
+
const TemplateModel = require('../models/templateModel');
|
|
35
|
+
return TemplateModel;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Failed to load Template model:', error);
|
|
38
|
+
// Return a stub model with required methods
|
|
39
|
+
return {
|
|
40
|
+
deleteMany: () => Promise.resolve({ deletedCount: 0 }),
|
|
41
|
+
find: () => ({ sort: () => ({ limit: () => ({ lean: () => Promise.resolve([]) }) }) }),
|
|
42
|
+
findOne: () => Promise.resolve(null),
|
|
43
|
+
create: () => Promise.resolve({}),
|
|
44
|
+
updateOne: () => Promise.resolve({}),
|
|
45
|
+
deleteOne: () => Promise.resolve({})
|
|
46
|
+
};
|
|
47
|
+
}
|
|
19
48
|
};
|
|
20
|
-
const predefinedTemplates = [];
|
|
21
49
|
|
|
22
50
|
/**
|
|
23
51
|
* Create a new template and store it in both Twilio and our database
|
|
@@ -109,6 +137,7 @@ const createTemplate = async (req, res) => {
|
|
|
109
137
|
dateCreated = currentDate;
|
|
110
138
|
}
|
|
111
139
|
|
|
140
|
+
const TemplateModel = getTemplateModel();
|
|
112
141
|
const newDbTemplate = await TemplateModel.create({
|
|
113
142
|
sid: twilioContent.sid,
|
|
114
143
|
name: (twilioContent.friendlyName || `template_${twilioContent.sid}`).replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase(),
|
|
@@ -152,8 +181,13 @@ const createTemplate = async (req, res) => {
|
|
|
152
181
|
const listTemplates = async (req, res) => {
|
|
153
182
|
try {
|
|
154
183
|
const { status: queryStatus, type, limit = 50, showFlows: queryShowFlows } = req.query;
|
|
184
|
+
const TemplateModel = getTemplateModel();
|
|
155
185
|
|
|
156
|
-
|
|
186
|
+
console.log('nexusProvider:', nexusProvider ? 'configured' : 'not configured');
|
|
187
|
+
console.log('nexusProvider methods:', nexusProvider ? Object.keys(nexusProvider) : 'none');
|
|
188
|
+
|
|
189
|
+
checkTemplateSupport();
|
|
190
|
+
const twilioRawTemplates = await nexusProvider.listTemplates({ limit: parseInt(limit, 10) });
|
|
157
191
|
|
|
158
192
|
const showFlows = type === 'flow' || queryShowFlows === 'true';
|
|
159
193
|
const filteredTwilioTemplates = twilioRawTemplates.filter(template => {
|
|
@@ -176,7 +210,7 @@ const listTemplates = async (req, res) => {
|
|
|
176
210
|
};
|
|
177
211
|
|
|
178
212
|
try {
|
|
179
|
-
const approvalInfo = await
|
|
213
|
+
const approvalInfo = await nexusProvider.checkApprovalStatus(twilioTemplate.sid);
|
|
180
214
|
if (approvalInfo && approvalInfo.approvalRequest) {
|
|
181
215
|
const reqData = approvalInfo.approvalRequest;
|
|
182
216
|
updateFields.approvalRequest = {
|
|
@@ -260,10 +294,12 @@ const getTemplate = async (req, res) => {
|
|
|
260
294
|
try {
|
|
261
295
|
const { id } = req.params;
|
|
262
296
|
|
|
297
|
+
const TemplateModel = getTemplateModel();
|
|
263
298
|
let template = await TemplateModel.findOne({ sid: id });
|
|
264
299
|
let twilioTemplate;
|
|
265
300
|
|
|
266
|
-
|
|
301
|
+
checkTemplateSupport();
|
|
302
|
+
twilioTemplate = await nexusProvider.getTemplate(id);
|
|
267
303
|
|
|
268
304
|
if (!template) {
|
|
269
305
|
// If template wasn't in our database, create it based on Twilio data
|
|
@@ -345,7 +381,8 @@ const submitForApproval = async (req, res) => {
|
|
|
345
381
|
const approvalName = `${name || 'template'}_${timestamp}_${randomSuffix}`.toLowerCase().replace(/[^a-z0-9_]/g, '_');
|
|
346
382
|
const approvalCategory = category || 'UTILITY';
|
|
347
383
|
|
|
348
|
-
|
|
384
|
+
checkTemplateSupport();
|
|
385
|
+
const response = await nexusProvider.submitForApproval(contentSid, approvalName, approvalCategory);
|
|
349
386
|
|
|
350
387
|
const dateCreated = response.date_created || response.dateCreated;
|
|
351
388
|
const dateUpdated = response.date_updated || response.dateUpdated || dateCreated;
|
|
@@ -356,6 +393,7 @@ const submitForApproval = async (req, res) => {
|
|
|
356
393
|
const validSubmittedDate = !isNaN(submittedDate.getTime()) ? submittedDate : new Date();
|
|
357
394
|
const validUpdatedDate = !isNaN(updatedDate.getTime()) ? updatedDate : new Date();
|
|
358
395
|
|
|
396
|
+
const TemplateModel = getTemplateModel();
|
|
359
397
|
await TemplateModel.updateOne(
|
|
360
398
|
{ sid: contentSid },
|
|
361
399
|
{
|
|
@@ -397,10 +435,12 @@ const checkApprovalStatus = async (req, res) => {
|
|
|
397
435
|
return res.status(400).json({ success: false, error: 'Content SID is required' });
|
|
398
436
|
}
|
|
399
437
|
|
|
438
|
+
const TemplateModel = getTemplateModel();
|
|
400
439
|
const dbTemplate = await TemplateModel.findOne({ sid: contentSid });
|
|
401
440
|
|
|
402
441
|
try {
|
|
403
|
-
|
|
442
|
+
checkTemplateSupport();
|
|
443
|
+
const status = await nexusProvider.checkApprovalStatus(contentSid);
|
|
404
444
|
|
|
405
445
|
if (dbTemplate) {
|
|
406
446
|
// Use approval status as the authoritative source if available
|
|
@@ -475,8 +515,10 @@ const deleteTemplate = async (req, res) => {
|
|
|
475
515
|
try {
|
|
476
516
|
const { id } = req.params;
|
|
477
517
|
|
|
478
|
-
|
|
518
|
+
checkTemplateSupport();
|
|
519
|
+
await nexusProvider.deleteTemplate(id);
|
|
479
520
|
|
|
521
|
+
const TemplateModel = getTemplateModel();
|
|
480
522
|
await TemplateModel.deleteOne({ sid: id });
|
|
481
523
|
|
|
482
524
|
return res.json({
|
|
@@ -529,6 +571,7 @@ const getCompleteTemplate = async (req, res) => {
|
|
|
529
571
|
try {
|
|
530
572
|
const { sid } = req.params;
|
|
531
573
|
|
|
574
|
+
const TemplateModel = getTemplateModel();
|
|
532
575
|
let template = await TemplateModel.findOne({ sid }).lean();
|
|
533
576
|
|
|
534
577
|
if (!template) {
|
|
@@ -540,7 +583,8 @@ const getCompleteTemplate = async (req, res) => {
|
|
|
540
583
|
|
|
541
584
|
if (!template.body || !template.variables || template.variables.length === 0) {
|
|
542
585
|
try {
|
|
543
|
-
|
|
586
|
+
checkTemplateSupport();
|
|
587
|
+
const twilioTemplate = await nexusProvider.getTemplate(sid);
|
|
544
588
|
console.log('Fetched template from Twilio:', twilioTemplate);
|
|
545
589
|
|
|
546
590
|
let body = '';
|
|
@@ -603,6 +647,7 @@ const getCompleteTemplate = async (req, res) => {
|
|
|
603
647
|
updateData.actions = template.actions;
|
|
604
648
|
}
|
|
605
649
|
|
|
650
|
+
const TemplateModel = getTemplateModel();
|
|
606
651
|
await TemplateModel.updateOne(
|
|
607
652
|
{ sid },
|
|
608
653
|
{ $set: updateData }
|
|
@@ -628,15 +673,6 @@ const getCompleteTemplate = async (req, res) => {
|
|
|
628
673
|
}
|
|
629
674
|
};
|
|
630
675
|
|
|
631
|
-
// Flow functions (inline since templateFlowController was removed)
|
|
632
|
-
const createFlow = async (req, res) => {
|
|
633
|
-
return res.status(501).json({ success: false, error: 'Flow creation not available' });
|
|
634
|
-
};
|
|
635
|
-
|
|
636
|
-
const deleteFlow = async (req, res) => {
|
|
637
|
-
return res.status(501).json({ success: false, error: 'Flow deletion not available' });
|
|
638
|
-
};
|
|
639
|
-
|
|
640
676
|
module.exports = {
|
|
641
677
|
createTemplate,
|
|
642
678
|
listTemplates,
|
|
@@ -646,6 +682,5 @@ module.exports = {
|
|
|
646
682
|
checkApprovalStatus,
|
|
647
683
|
deleteTemplate,
|
|
648
684
|
getCompleteTemplate,
|
|
649
|
-
|
|
650
|
-
deleteFlow
|
|
685
|
+
configureNexusProvider
|
|
651
686
|
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Nexus provider will be injected
|
|
2
|
+
let nexusProvider = null;
|
|
3
|
+
|
|
4
|
+
// Configure Nexus provider
|
|
5
|
+
const configureNexusProvider = (provider) => {
|
|
6
|
+
nexusProvider = provider;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Check if provider supports templates
|
|
10
|
+
const checkTemplateSupport = () => {
|
|
11
|
+
if (!nexusProvider) {
|
|
12
|
+
throw new Error('Nexus provider not configured. Call configureNexusProvider() first.');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!nexusProvider.createTemplate || typeof nexusProvider.createTemplate !== 'function') {
|
|
16
|
+
throw new Error('Template operations are only supported with Twilio provider');
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const { handleApiError } = require('../utils/errorHandler');
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
const createFlow = async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const { friendlyName, language, flowType, body, buttonText, subtitle, pages } = req.body;
|
|
25
|
+
|
|
26
|
+
const missingParams = [];
|
|
27
|
+
if (!friendlyName) missingParams.push('friendlyName');
|
|
28
|
+
if (!language) missingParams.push('language');
|
|
29
|
+
if (!pages || !Array.isArray(pages)) missingParams.push('pages');
|
|
30
|
+
|
|
31
|
+
if (missingParams.length > 0) {
|
|
32
|
+
return res.status(400).json({
|
|
33
|
+
success: false,
|
|
34
|
+
error: `Missing required parameters: ${missingParams.join(', ')}`
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Generate unique identifiers for anti-rejection
|
|
39
|
+
const timestamp = Date.now().toString();
|
|
40
|
+
const uniqueId = require('uuid').v4().substring(0, 6);
|
|
41
|
+
const uniqueFriendlyName = `${friendlyName}_${timestamp}_${uniqueId}`;
|
|
42
|
+
|
|
43
|
+
// Match Twilio's expected structure for flex templates
|
|
44
|
+
checkTemplateSupport();
|
|
45
|
+
const content = await nexusProvider.createTemplate({
|
|
46
|
+
friendly_name: uniqueFriendlyName,
|
|
47
|
+
language,
|
|
48
|
+
categories: ['UTILITY'],
|
|
49
|
+
types: {
|
|
50
|
+
'twilio/flows': {
|
|
51
|
+
body: body,
|
|
52
|
+
button_text: buttonText,
|
|
53
|
+
subtitle: subtitle || null,
|
|
54
|
+
pages: pages,
|
|
55
|
+
type: flowType
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return res.status(201).json({
|
|
61
|
+
success: true,
|
|
62
|
+
message: 'Flow template created successfully',
|
|
63
|
+
contentSid: content.sid,
|
|
64
|
+
friendlyName: content.friendly_name,
|
|
65
|
+
approvalStatus: 'Pending review',
|
|
66
|
+
approvalLinks: content.links
|
|
67
|
+
});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return handleApiError(res, error, 'Error creating WhatsApp flow template');
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
const deleteFlow = async (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
const { sid: contentSid } = req.params;
|
|
77
|
+
|
|
78
|
+
if (!contentSid) {
|
|
79
|
+
return res.status(400).json({
|
|
80
|
+
success: false,
|
|
81
|
+
error: 'Content SID is required'
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
checkTemplateSupport();
|
|
86
|
+
await nexusProvider.deleteTemplate(contentSid);
|
|
87
|
+
|
|
88
|
+
return res.status(200).json({
|
|
89
|
+
success: true,
|
|
90
|
+
message: 'Template deleted successfully'
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error.code === 20404) {
|
|
94
|
+
return res.status(404).json({
|
|
95
|
+
success: false,
|
|
96
|
+
error: 'Template not found'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return handleApiError(res, error, 'Error deleting template');
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
createFlow,
|
|
106
|
+
deleteFlow,
|
|
107
|
+
configureNexusProvider
|
|
108
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const multer = require('multer');
|
|
2
|
+
const { v4: uuidv4 } = require('uuid');
|
|
3
|
+
const { generatePresignedUrl, uploadBufferToS3 } = require('../config/awsConfig');
|
|
4
|
+
const bucketName = process.env.AWS_S3_BUCKET_NAME;
|
|
5
|
+
|
|
6
|
+
const storage = multer.memoryStorage();
|
|
7
|
+
|
|
8
|
+
const fileFilter = (req, file, cb) => {
|
|
9
|
+
if (file.mimetype.startsWith('image/') ||
|
|
10
|
+
file.mimetype.startsWith('application/') ||
|
|
11
|
+
file.mimetype.startsWith('audio/') ||
|
|
12
|
+
file.mimetype.startsWith('video/')) {
|
|
13
|
+
cb(null, true);
|
|
14
|
+
} else {
|
|
15
|
+
cb(new Error('Unsupported file type'), false);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const upload = multer({
|
|
20
|
+
storage: storage,
|
|
21
|
+
fileFilter: fileFilter,
|
|
22
|
+
limits: {
|
|
23
|
+
fileSize: 50 * 1024 * 1024, // Increased to 50MB
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const handleFileUpload = async (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
if (!req.file) {
|
|
30
|
+
return res.status(400).json({
|
|
31
|
+
success: false,
|
|
32
|
+
error: 'No file provided'
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const file = req.file;
|
|
37
|
+
|
|
38
|
+
let filePrefix = 'document';
|
|
39
|
+
if (file.mimetype.startsWith('image/')) {
|
|
40
|
+
filePrefix = 'image';
|
|
41
|
+
} else if (file.mimetype.startsWith('video/')) {
|
|
42
|
+
filePrefix = 'video';
|
|
43
|
+
} else if (file.mimetype.startsWith('audio/')) {
|
|
44
|
+
filePrefix = 'audio';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const uniqueId = uuidv4();
|
|
48
|
+
const originalFileName = file.originalname;
|
|
49
|
+
const sanitizedFileName = file.originalname.replace(/[^a-zA-Z0-9.]/g, '_').replace('.pdf', '');
|
|
50
|
+
|
|
51
|
+
const key = `${filePrefix}/${sanitizedFileName}_${uniqueId}`;
|
|
52
|
+
|
|
53
|
+
await uploadBufferToS3(file.buffer, bucketName, key, file.mimetype, true);
|
|
54
|
+
const whatsappExpirationSeconds = 7 * 24 * 60 * 60;
|
|
55
|
+
const presignedUrl = await generatePresignedUrl(bucketName, key, whatsappExpirationSeconds);
|
|
56
|
+
|
|
57
|
+
res.status(200).json({
|
|
58
|
+
success: true,
|
|
59
|
+
message: 'File uploaded successfully',
|
|
60
|
+
fileUrl: presignedUrl,
|
|
61
|
+
key: key,
|
|
62
|
+
fileType: filePrefix,
|
|
63
|
+
fileName: originalFileName,
|
|
64
|
+
sanitizedFileName: sanitizedFileName,
|
|
65
|
+
fileSize: file.size,
|
|
66
|
+
contentType: file.mimetype
|
|
67
|
+
});
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Error uploading file:', error);
|
|
70
|
+
res.status(500).json({
|
|
71
|
+
success: false,
|
|
72
|
+
error: error.message || 'Failed to upload file'
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const uploadSingleFile = upload.single('file');
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
uploadSingleFile,
|
|
81
|
+
handleFileUpload
|
|
82
|
+
};
|
|
@@ -325,4 +325,18 @@ class NexusMessaging {
|
|
|
325
325
|
}
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
-
|
|
328
|
+
const defaultInstance = new NexusMessaging();
|
|
329
|
+
|
|
330
|
+
const sendMessage = async (messageData) => {
|
|
331
|
+
return await defaultInstance.sendMessage(messageData);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const sendScheduledMessage = async (scheduledMessage) => {
|
|
335
|
+
return await defaultInstance.sendScheduledMessage(scheduledMessage);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
module.exports = {
|
|
339
|
+
NexusMessaging,
|
|
340
|
+
sendMessage,
|
|
341
|
+
sendScheduledMessage
|
|
342
|
+
};
|