@peopl-health/nexus 3.1.0 → 3.1.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.
|
@@ -46,8 +46,8 @@ const reportBugController = async (req, res) => {
|
|
|
46
46
|
|
|
47
47
|
if (!reporter) return res.status(400).json({ success: false, error: 'Reporter username is required' });
|
|
48
48
|
if (!whatsapp_id) return res.status(400).json({ success: false, error: 'WhatsApp ID is required' });
|
|
49
|
-
if (!severity || !['low', 'medium', 'high'].includes(severity)) {
|
|
50
|
-
return res.status(400).json({ success: false, error: 'Severity must be low, medium, or
|
|
49
|
+
if (!severity || !['low', 'medium', 'high', 'critical'].includes(severity)) {
|
|
50
|
+
return res.status(400).json({ success: false, error: 'Severity must be low, medium, high, or critical' });
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
logBugReportToAirtable(reporter, whatsapp_id, description, severity, messages).catch(err =>
|
|
@@ -182,7 +182,11 @@ async function downloadMediaAndCreateFile(code, reply) {
|
|
|
182
182
|
|
|
183
183
|
const sanitizedCode = sanitizeFilename(code, 20);
|
|
184
184
|
const sanitizedSubType = sanitizeFilename(subType, 10);
|
|
185
|
-
|
|
185
|
+
|
|
186
|
+
const fileExt = path.extname(fileName);
|
|
187
|
+
const fileBaseName = path.basename(fileName, fileExt);
|
|
188
|
+
const sanitizedBaseName = sanitizeFilename(fileBaseName, 50 - fileExt.length);
|
|
189
|
+
const sanitizedFileName = sanitizedBaseName + fileExt;
|
|
186
190
|
|
|
187
191
|
const sourceFile = `${sanitizedCode}-${sanitizedSubType}-${sanitizedFileName}`;
|
|
188
192
|
const downloadPath = path.join(__dirname, 'assets', 'tmp', sourceFile);
|
|
@@ -1,27 +1,28 @@
|
|
|
1
1
|
const { Message } = require('../models/messageModel');
|
|
2
2
|
const { logger } = require('../utils/logger');
|
|
3
|
+
const { handle24HourWindowError } = require('./templateRecoveryHelper');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Update message delivery status in the database based on Twilio status callback data
|
|
6
7
|
*/
|
|
7
8
|
async function updateMessageStatus(messageSid, status, errorCode = null, errorMessage = null) {
|
|
8
9
|
try {
|
|
9
|
-
const
|
|
10
|
-
status,
|
|
11
|
-
updatedAt: new Date()
|
|
10
|
+
const updateData = {
|
|
11
|
+
'statusInfo.status': status,
|
|
12
|
+
'statusInfo.updatedAt': new Date()
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
if (errorCode) {
|
|
15
|
-
statusInfo.errorCode = errorCode;
|
|
16
|
+
updateData['statusInfo.errorCode'] = errorCode;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
if (errorMessage) {
|
|
19
|
-
statusInfo.errorMessage = errorMessage;
|
|
20
|
+
updateData['statusInfo.errorMessage'] = errorMessage;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const updated = await Message.findOneAndUpdate(
|
|
23
24
|
{ message_id: messageSid },
|
|
24
|
-
{ $set:
|
|
25
|
+
{ $set: updateData },
|
|
25
26
|
{ new: true }
|
|
26
27
|
);
|
|
27
28
|
|
|
@@ -62,12 +63,20 @@ async function handleStatusCallback(twilioStatusData) {
|
|
|
62
63
|
return null;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
|
|
66
|
+
const updated = await updateMessageStatus(
|
|
66
67
|
MessageSid,
|
|
67
68
|
MessageStatus.toLowerCase(),
|
|
68
69
|
ErrorCode || null,
|
|
69
70
|
ErrorMessage || null
|
|
70
71
|
);
|
|
72
|
+
|
|
73
|
+
if ((ErrorCode === 63016 || ErrorCode === '63016') && updated) {
|
|
74
|
+
handle24HourWindowError(updated, MessageSid).catch(err =>
|
|
75
|
+
logger.error('[MessageStatus] Recovery error', { messageSid: MessageSid, error: err.message })
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return updated;
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
/**
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const { logger } = require('../utils/logger');
|
|
2
|
+
const { getDefaultInstance } = require('../core/NexusMessaging');
|
|
3
|
+
const { Template, configureNexusProvider: configureTemplateProvider } = require('../templates/templateStructure');
|
|
4
|
+
const { sendMessage } = require('../core/NexusMessaging');
|
|
5
|
+
const { Message } = require('../models/messageModel');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handle 24-hour window error by creating template and sending when approved
|
|
9
|
+
*/
|
|
10
|
+
async function handle24HourWindowError(message, messageSid) {
|
|
11
|
+
try {
|
|
12
|
+
if (!message?.body || !message?.numero) return;
|
|
13
|
+
|
|
14
|
+
const messaging = getDefaultInstance();
|
|
15
|
+
const provider = messaging?.getProvider();
|
|
16
|
+
if (!provider?.createTemplate) return;
|
|
17
|
+
|
|
18
|
+
configureTemplateProvider(provider);
|
|
19
|
+
|
|
20
|
+
const templateName = `auto_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
21
|
+
const template = new Template(templateName, 'UTILITY', 'es');
|
|
22
|
+
template.setBody(message.body, []);
|
|
23
|
+
const twilioContent = await template.save();
|
|
24
|
+
|
|
25
|
+
// Submit for approval
|
|
26
|
+
const approvalName = `${templateName}_${Date.now()}`.toLowerCase().replace(/[^a-z0-9_]/g, '_');
|
|
27
|
+
await provider.submitForApproval(twilioContent.sid, approvalName, 'UTILITY');
|
|
28
|
+
|
|
29
|
+
await Message.updateOne(
|
|
30
|
+
{ message_id: messageSid },
|
|
31
|
+
{ $set: { 'statusInfo.recoveryTemplateSid': twilioContent.sid } }
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
logger.info('[TemplateRecovery] Template created', { messageSid, templateSid: twilioContent.sid });
|
|
35
|
+
|
|
36
|
+
const checkApproval = async (attempt = 0, maxAttempts = 40) => {
|
|
37
|
+
if (attempt >= maxAttempts) {
|
|
38
|
+
logger.warn('[TemplateRecovery] Max attempts reached, template not approved yet', { messageSid, templateSid: twilioContent.sid });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setTimeout(async () => {
|
|
43
|
+
try {
|
|
44
|
+
const status = await provider.checkApprovalStatus(twilioContent.sid);
|
|
45
|
+
const approvalStatus = status?.approvalRequest?.status?.toUpperCase();
|
|
46
|
+
|
|
47
|
+
if (approvalStatus === 'APPROVED') {
|
|
48
|
+
await sendMessage({ code: message.numero, contentSid: twilioContent.sid, variables: {} });
|
|
49
|
+
logger.info('[TemplateRecovery] Template sent', { messageSid, templateSid: twilioContent.sid });
|
|
50
|
+
} else if (approvalStatus === 'REJECTED') {
|
|
51
|
+
logger.warn('[TemplateRecovery] Template rejected', { messageSid, templateSid: twilioContent.sid });
|
|
52
|
+
} else {
|
|
53
|
+
checkApproval(attempt + 1, maxAttempts);
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
logger.error('[TemplateRecovery] Error checking approval', { error: err.message, attempt });
|
|
57
|
+
// Retry on error (but count as attempt)
|
|
58
|
+
if (attempt + 1 < maxAttempts) {
|
|
59
|
+
checkApproval(attempt + 1, maxAttempts);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}, 15 * 60 * 1000);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
checkApproval(0);
|
|
66
|
+
|
|
67
|
+
} catch (error) {
|
|
68
|
+
logger.error('[TemplateRecovery] Error', { messageSid, error: error.message });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { handle24HourWindowError };
|
|
@@ -124,6 +124,9 @@ async function insertMessage(values) {
|
|
|
124
124
|
updatedAt: values.delivery_status_updated_at || null
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
|
+
if (messageData.tools_executed === undefined) {
|
|
128
|
+
messageData.tools_executed = [];
|
|
129
|
+
}
|
|
127
130
|
|
|
128
131
|
await Message.findOneAndUpdate(
|
|
129
132
|
{ message_id: values.message_id, body: values.body },
|
package/lib/routes/index.js
CHANGED
|
@@ -39,6 +39,8 @@ const messageRouteDefinitions = {
|
|
|
39
39
|
'GET /last': 'getLastInteractionController',
|
|
40
40
|
'GET /scheduled-status': 'checkScheduledMessageStatusController',
|
|
41
41
|
'GET /status': 'checkMessageStatusController',
|
|
42
|
+
'GET /status/:messageSid': 'getMessageStatusController',
|
|
43
|
+
'POST /status-callback': 'messageStatusCallbackController',
|
|
42
44
|
'POST /quality': 'addQualityVoteController',
|
|
43
45
|
'GET /quality/:message_id': 'getQualityVotesByMessageController',
|
|
44
46
|
'GET /quality/:message_id/voter/:voter_username': 'getQualityVoteByMessageAndVoterController',
|
|
@@ -97,6 +99,7 @@ const conversationController = require('../controllers/conversationController');
|
|
|
97
99
|
const interactionController = require('../controllers/interactionController');
|
|
98
100
|
const mediaController = require('../controllers/mediaController');
|
|
99
101
|
const messageController = require('../controllers/messageController');
|
|
102
|
+
const messageStatusController = require('../controllers/messageStatusController');
|
|
100
103
|
const patientController = require('../controllers/patientController');
|
|
101
104
|
const qualityMessageController = require('../controllers/qualityMessageController');
|
|
102
105
|
const templateController = require('../controllers/templateController');
|
|
@@ -144,6 +147,8 @@ const builtInControllers = {
|
|
|
144
147
|
getLastInteractionController: messageController.getLastInteractionController,
|
|
145
148
|
checkScheduledMessageStatusController: messageController.checkScheduledMessageStatusController,
|
|
146
149
|
checkMessageStatusController: messageController.checkMessageStatusController,
|
|
150
|
+
getMessageStatusController: messageStatusController.getMessageStatusController,
|
|
151
|
+
messageStatusCallbackController: messageStatusController.messageStatusCallbackController,
|
|
147
152
|
addQualityVoteController: qualityMessageController.addQualityVoteController,
|
|
148
153
|
getQualityVotesByMessageController: qualityMessageController.getQualityVotesByMessageController,
|
|
149
154
|
getQualityVoteByMessageAndVoterController: qualityMessageController.getQualityVoteByMessageAndVoterController,
|