@peopl-health/nexus 3.5.8 → 3.5.10
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/controllers/conversationController.js +30 -1
- package/lib/core/NexusMessaging.js +3 -0
- package/lib/helpers/templateApprovalPoller.js +59 -0
- package/lib/helpers/templateRecoveryHelper.js +41 -53
- package/lib/helpers/threadHelper.js +20 -2
- package/lib/routes/index.js +2 -0
- package/lib/services/conversationService.js +108 -1
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ const { withThreadRecovery } = require('../helpers/threadRecoveryHelper');
|
|
|
13
13
|
const { ensureWhatsAppFormat } = require('../helpers/twilioHelper');
|
|
14
14
|
|
|
15
15
|
const { getRecordByFilter } = require('../services/airtableService');
|
|
16
|
-
const { fetchConversationData, processConversations } = require('../services/conversationService');
|
|
16
|
+
const { fetchConversationData, processConversations, startConversation } = require('../services/conversationService');
|
|
17
17
|
|
|
18
18
|
const { sendMessage } = require('../core/NexusMessaging');
|
|
19
19
|
|
|
@@ -672,6 +672,34 @@ const searchMessagesByNumberController = async (req, res) => {
|
|
|
672
672
|
}
|
|
673
673
|
};
|
|
674
674
|
|
|
675
|
+
const startConversationController = async (req, res) => {
|
|
676
|
+
try {
|
|
677
|
+
const { phoneNumber, name, message } = req.body;
|
|
678
|
+
|
|
679
|
+
if (!phoneNumber || !message) {
|
|
680
|
+
return res.status(400).json({
|
|
681
|
+
success: false,
|
|
682
|
+
error: 'Phone number and message are required'
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const formattedPhoneNumber = ensureWhatsAppFormat(phoneNumber);
|
|
687
|
+
const result = await startConversation(formattedPhoneNumber, message, name);
|
|
688
|
+
|
|
689
|
+
res.status(201).json({
|
|
690
|
+
success: true,
|
|
691
|
+
message: 'Conversation started, template pending approval',
|
|
692
|
+
...result
|
|
693
|
+
});
|
|
694
|
+
} catch (error) {
|
|
695
|
+
logger.error('[StartConversation] Error', { error: error.message });
|
|
696
|
+
res.status(500).json({
|
|
697
|
+
success: false,
|
|
698
|
+
error: error.message || 'Failed to start conversation'
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
675
703
|
module.exports = {
|
|
676
704
|
getConversationController,
|
|
677
705
|
getConversationMessagesController,
|
|
@@ -682,5 +710,6 @@ module.exports = {
|
|
|
682
710
|
markMessagesAsReadController,
|
|
683
711
|
searchConversationsController,
|
|
684
712
|
sendTemplateToNewNumberController,
|
|
713
|
+
startConversationController,
|
|
685
714
|
getOpenAIThreadMessagesController
|
|
686
715
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { logger } = require('../utils/logger');
|
|
2
|
+
|
|
3
|
+
const getMessaging = () => require('../core/NexusMessaging');
|
|
4
|
+
|
|
5
|
+
const MAX_ATTEMPTS = 40;
|
|
6
|
+
const POLL_INTERVAL_MS = 15 * 60 * 1000;
|
|
7
|
+
|
|
8
|
+
function pollTemplateApproval(templateSid, { label, logContext = {}, onApproved, onRejected }) {
|
|
9
|
+
const { requireProvider } = getMessaging();
|
|
10
|
+
const provider = requireProvider();
|
|
11
|
+
|
|
12
|
+
const poll = (attempt = 0) => {
|
|
13
|
+
if (attempt >= MAX_ATTEMPTS) {
|
|
14
|
+
logger.warn(`${label} Max approval poll attempts reached, giving up`, { ...logContext, templateSid, attempts: MAX_ATTEMPTS });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
logger.info(`${label} Scheduling approval check`, { ...logContext, templateSid, attempt, nextCheckInMs: POLL_INTERVAL_MS });
|
|
19
|
+
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
(async () => {
|
|
22
|
+
logger.info(`${label} Checking approval status`, { ...logContext, templateSid, attempt });
|
|
23
|
+
|
|
24
|
+
const status = await provider.checkApprovalStatus(templateSid);
|
|
25
|
+
const approvalStatus = status?.approvalRequest?.status?.toUpperCase();
|
|
26
|
+
|
|
27
|
+
logger.info(`${label} Approval status received`, { ...logContext, templateSid, approvalStatus, attempt });
|
|
28
|
+
|
|
29
|
+
if (approvalStatus === 'APPROVED') {
|
|
30
|
+
await onApproved(provider);
|
|
31
|
+
} else if (approvalStatus === 'REJECTED') {
|
|
32
|
+
if (onRejected) {
|
|
33
|
+
await onRejected(provider);
|
|
34
|
+
} else {
|
|
35
|
+
logger.warn(`${label} Template rejected`, { ...logContext, templateSid });
|
|
36
|
+
try {
|
|
37
|
+
await provider.deleteTemplate(templateSid);
|
|
38
|
+
logger.info(`${label} Rejected template deleted`, { ...logContext, templateSid });
|
|
39
|
+
} catch (deleteErr) {
|
|
40
|
+
logger.warn(`${label} Failed to delete rejected template`, { ...logContext, templateSid, error: deleteErr.message });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
logger.info(`${label} Still pending, will retry`, { ...logContext, templateSid, approvalStatus, attempt });
|
|
45
|
+
poll(attempt + 1);
|
|
46
|
+
}
|
|
47
|
+
})().catch((err) => {
|
|
48
|
+
logger.error(`${label} Error during approval poll`, { ...logContext, templateSid, error: err.message, attempt });
|
|
49
|
+
if (attempt + 1 < MAX_ATTEMPTS) {
|
|
50
|
+
poll(attempt + 1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}, POLL_INTERVAL_MS);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
poll();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { pollTemplateApproval };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const { logger } = require('../utils/logger');
|
|
2
2
|
const { Message } = require('../models/messageModel');
|
|
3
3
|
const { Template } = require('../templates/templateStructure');
|
|
4
|
+
const { pollTemplateApproval } = require('./templateApprovalPoller');
|
|
4
5
|
|
|
5
6
|
const getMessaging = () => require('../core/NexusMessaging');
|
|
6
7
|
|
|
@@ -29,7 +30,10 @@ async function handle24HourWindowError(message, messageSid) {
|
|
|
29
30
|
|
|
30
31
|
const { requireProvider, sendMessage } = getMessaging();
|
|
31
32
|
const provider = requireProvider();
|
|
32
|
-
if (typeof provider.createTemplate !== 'function')
|
|
33
|
+
if (typeof provider.createTemplate !== 'function') {
|
|
34
|
+
logger.warn('[TemplateRecovery] Provider does not support createTemplate, skipping', { messageSid });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
33
37
|
|
|
34
38
|
const templateName = `auto_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
|
35
39
|
const template = new Template(templateName, 'UTILITY', 'es');
|
|
@@ -46,62 +50,46 @@ async function handle24HourWindowError(message, messageSid) {
|
|
|
46
50
|
|
|
47
51
|
logger.info('[TemplateRecovery] Template created', { messageSid, templateSid: twilioContent.sid });
|
|
48
52
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
pollTemplateApproval(twilioContent.sid, {
|
|
54
|
+
label: '[TemplateRecovery]',
|
|
55
|
+
logContext: { messageSid },
|
|
56
|
+
onApproved: async (prov) => {
|
|
57
|
+
const claimSend = await Message.updateOne(
|
|
58
|
+
{ message_id: messageSid, 'statusInfo.recoverySentAt': { $exists: false } },
|
|
59
|
+
{ $set: { 'statusInfo.recoverySentAt': new Date() } }
|
|
60
|
+
);
|
|
61
|
+
if (!claimSend?.modifiedCount && !claimSend?.nModified) {
|
|
62
|
+
logger.info('[TemplateRecovery] Send already claimed by another process', { messageSid, templateSid: twilioContent.sid });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
55
65
|
|
|
56
|
-
setTimeout(async () => {
|
|
57
66
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
);
|
|
66
|
-
if (!claimSend?.modifiedCount && !claimSend?.nModified) return;
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
await sendMessage({ code: message.numero, contentSid: twilioContent.sid, variables: {} });
|
|
70
|
-
logger.info('[TemplateRecovery] Template sent', { messageSid, templateSid: twilioContent.sid });
|
|
71
|
-
try {
|
|
72
|
-
await provider.deleteTemplate(twilioContent.sid);
|
|
73
|
-
logger.info('[TemplateRecovery] Template deleted', { messageSid, templateSid: twilioContent.sid });
|
|
74
|
-
} catch (deleteErr) {
|
|
75
|
-
logger.warn('[TemplateRecovery] Failed to delete template after send', { messageSid, templateSid: twilioContent.sid, error: deleteErr.message });
|
|
76
|
-
}
|
|
77
|
-
} catch (sendErr) {
|
|
78
|
-
await Message.updateOne(
|
|
79
|
-
{ message_id: messageSid },
|
|
80
|
-
{ $unset: { 'statusInfo.recoverySentAt': '' } }
|
|
81
|
-
);
|
|
82
|
-
logger.error('[TemplateRecovery] Error sending approved template', { messageSid, templateSid: twilioContent.sid, error: sendErr.message });
|
|
83
|
-
}
|
|
84
|
-
} else if (approvalStatus === 'REJECTED') {
|
|
85
|
-
logger.warn('[TemplateRecovery] Template rejected', { messageSid, templateSid: twilioContent.sid });
|
|
86
|
-
try {
|
|
87
|
-
await provider.deleteTemplate(twilioContent.sid);
|
|
88
|
-
logger.info('[TemplateRecovery] Rejected template deleted', { messageSid, templateSid: twilioContent.sid });
|
|
89
|
-
} catch (deleteErr) {
|
|
90
|
-
logger.warn('[TemplateRecovery] Failed to delete rejected template', { messageSid, templateSid: twilioContent.sid, error: deleteErr.message });
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
checkApproval(attempt + 1);
|
|
94
|
-
}
|
|
95
|
-
} catch (err) {
|
|
96
|
-
logger.error('[TemplateRecovery] Error checking approval', { error: err.message, attempt });
|
|
97
|
-
if (attempt + 1 < MAX_ATTEMPTS) {
|
|
98
|
-
checkApproval(attempt + 1);
|
|
67
|
+
await sendMessage({ code: message.numero, contentSid: twilioContent.sid, variables: {} });
|
|
68
|
+
logger.info('[TemplateRecovery] Template sent', { messageSid, templateSid: twilioContent.sid });
|
|
69
|
+
try {
|
|
70
|
+
await prov.deleteTemplate(twilioContent.sid);
|
|
71
|
+
logger.info('[TemplateRecovery] Template deleted', { messageSid, templateSid: twilioContent.sid });
|
|
72
|
+
} catch (deleteErr) {
|
|
73
|
+
logger.warn('[TemplateRecovery] Failed to delete template after send', { messageSid, templateSid: twilioContent.sid, error: deleteErr.message });
|
|
99
74
|
}
|
|
75
|
+
} catch (sendErr) {
|
|
76
|
+
await Message.updateOne(
|
|
77
|
+
{ message_id: messageSid },
|
|
78
|
+
{ $unset: { 'statusInfo.recoverySentAt': '' } }
|
|
79
|
+
);
|
|
80
|
+
logger.error('[TemplateRecovery] Error sending approved template', { messageSid, templateSid: twilioContent.sid, error: sendErr.message });
|
|
100
81
|
}
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
82
|
+
},
|
|
83
|
+
onRejected: async (prov) => {
|
|
84
|
+
logger.warn('[TemplateRecovery] Template rejected', { messageSid, templateSid: twilioContent.sid });
|
|
85
|
+
try {
|
|
86
|
+
await prov.deleteTemplate(twilioContent.sid);
|
|
87
|
+
logger.info('[TemplateRecovery] Rejected template deleted', { messageSid, templateSid: twilioContent.sid });
|
|
88
|
+
} catch (deleteErr) {
|
|
89
|
+
logger.warn('[TemplateRecovery] Failed to delete rejected template', { messageSid, templateSid: twilioContent.sid, error: deleteErr.message });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
105
93
|
|
|
106
94
|
} catch (error) {
|
|
107
95
|
logger.error('[TemplateRecovery] Error', { messageSid, error: error.message });
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
const { Config_ID } = require('../config/airtableConfig.js');
|
|
2
|
+
const runtimeConfig = require('../config/runtimeConfig.js');
|
|
3
|
+
|
|
1
4
|
const { logger } = require('../utils/logger');
|
|
2
5
|
|
|
3
6
|
const { Thread } = require('../models/threadModel.js');
|
|
4
7
|
|
|
8
|
+
const { getRecordByFilter } = require('../services/airtableService.js');
|
|
9
|
+
|
|
5
10
|
const getThread = async (code, message = null) => {
|
|
6
11
|
try {
|
|
7
12
|
let thread = await Thread.findOne({ code });
|
|
@@ -33,9 +38,22 @@ const createPlaceholderThread = async (code) => {
|
|
|
33
38
|
const existing = await Thread.findOne({ code });
|
|
34
39
|
if (existing) return existing;
|
|
35
40
|
|
|
36
|
-
const
|
|
41
|
+
const nodeEnv = runtimeConfig.get('NODE_ENV');
|
|
42
|
+
const assistantStatus = nodeEnv === 'production' ? 'prod' : nodeEnv === 'development' ? 'dev' : nodeEnv;
|
|
43
|
+
const record = await getRecordByFilter(Config_ID, 'responses', `AND({code}="PIPO_ASST", {status}="${assistantStatus}")`, undefined, ['prompt_id']);
|
|
44
|
+
const prompt_id = record?.[0]?.['prompt_id'] || null;
|
|
45
|
+
|
|
46
|
+
const thread = new Thread({ code, assistant_id: null, prompt_id, conversation_id: null, stopped: true });
|
|
37
47
|
await thread.save();
|
|
38
|
-
logger.info('[createPlaceholderThread] Created', { code });
|
|
48
|
+
logger.info('[createPlaceholderThread] Created', { code, prompt_id });
|
|
49
|
+
|
|
50
|
+
if (prompt_id) {
|
|
51
|
+
// Imported here to avoid circular dependency
|
|
52
|
+
const { createAssistant } = require('../services/assistantService.js');
|
|
53
|
+
await createAssistant(code, prompt_id, [], true);
|
|
54
|
+
logger.info('[createPlaceholderThread] Assistant created', { code, prompt_id });
|
|
55
|
+
}
|
|
56
|
+
|
|
39
57
|
return thread;
|
|
40
58
|
} catch (error) {
|
|
41
59
|
logger.error('[createPlaceholderThread] Error', { code, error: error.message });
|
package/lib/routes/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const conversationRouteDefinitions = {
|
|
|
21
21
|
'GET /:phoneNumber/search': 'searchMessagesByNumberController',
|
|
22
22
|
'POST /reply': 'getConversationReplyController',
|
|
23
23
|
'POST /send-template': 'sendTemplateToNewNumberController',
|
|
24
|
+
'POST /start': 'startConversationController',
|
|
24
25
|
'POST /:phoneNumber/read': 'markMessagesAsReadController',
|
|
25
26
|
'POST /case-documentation': 'caseDocumentationController',
|
|
26
27
|
'POST /report-bug': 'reportBugController',
|
|
@@ -133,6 +134,7 @@ const builtInControllers = {
|
|
|
133
134
|
getOpenAIThreadMessagesController: conversationController.getOpenAIThreadMessagesController,
|
|
134
135
|
getConversationReplyController: conversationController.getConversationReplyController,
|
|
135
136
|
sendTemplateToNewNumberController: conversationController.sendTemplateToNewNumberController,
|
|
137
|
+
startConversationController: conversationController.startConversationController,
|
|
136
138
|
markMessagesAsReadController: conversationController.markMessagesAsReadController,
|
|
137
139
|
searchMessagesByNumberController: conversationController.searchMessagesByNumberController,
|
|
138
140
|
reportBugController: bugReportController.reportBugController,
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
const { Historial_Clinico_ID } = require('../config/airtableConfig');
|
|
2
2
|
|
|
3
3
|
const { logger } = require('../utils/logger');
|
|
4
|
+
const { parseDate } = require('../utils/dateUtils');
|
|
4
5
|
|
|
5
6
|
const { Message } = require('../models/messageModel');
|
|
7
|
+
const { Thread } = require('../models/threadModel');
|
|
8
|
+
const TemplateModel = require('../models/templateModel');
|
|
9
|
+
|
|
10
|
+
const { pollTemplateApproval } = require('../helpers/templateApprovalPoller');
|
|
6
11
|
|
|
7
12
|
const { getRecordByFilter } = require('../services/airtableService');
|
|
8
13
|
|
|
14
|
+
const { sendMessage, requireProvider } = require('../core/NexusMessaging');
|
|
15
|
+
|
|
16
|
+
const { Template } = require('../templates/templateStructure');
|
|
17
|
+
|
|
9
18
|
const BASE_MATCH = { group_id: null };
|
|
10
19
|
const UNREAD_MATCH = { from_me: false, $or: [{ read: false }, { read: { $exists: false } }] };
|
|
11
20
|
const PENDING_REVIEW_MATCH = { $or: [{ review: false }, { review: null }] };
|
|
@@ -151,7 +160,105 @@ const processConversations = async (conversations, nameMap, unreadMap) => {
|
|
|
151
160
|
}
|
|
152
161
|
};
|
|
153
162
|
|
|
163
|
+
const startConversation = async (phoneNumber, message, name) => {
|
|
164
|
+
const thread = await Thread.findOneAndUpdate(
|
|
165
|
+
{ code: phoneNumber },
|
|
166
|
+
{
|
|
167
|
+
code: phoneNumber,
|
|
168
|
+
...(name && { nombre: name }),
|
|
169
|
+
active: true
|
|
170
|
+
},
|
|
171
|
+
{ upsert: true, new: true }
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
logger.info('[StartConversation] Thread ready', { phoneNumber, threadId: thread._id, name });
|
|
175
|
+
|
|
176
|
+
const provider = requireProvider();
|
|
177
|
+
const templateName = `start_${Date.now()}`;
|
|
178
|
+
const template = new Template(templateName, 'UTILITY', 'es');
|
|
179
|
+
template.setBody(message, []);
|
|
180
|
+
const twilioContent = await template.save();
|
|
181
|
+
|
|
182
|
+
const currentDate = new Date();
|
|
183
|
+
const dateCreated = parseDate(twilioContent.dateCreated, currentDate);
|
|
184
|
+
|
|
185
|
+
await TemplateModel.create({
|
|
186
|
+
sid: twilioContent.sid,
|
|
187
|
+
name: (twilioContent.friendlyName || `template_${twilioContent.sid}`).replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase(),
|
|
188
|
+
friendlyName: twilioContent.friendlyName,
|
|
189
|
+
category: 'UTILITY',
|
|
190
|
+
language: 'es',
|
|
191
|
+
status: twilioContent.status,
|
|
192
|
+
body: message,
|
|
193
|
+
variables: [],
|
|
194
|
+
components: twilioContent.components || [],
|
|
195
|
+
dateCreated,
|
|
196
|
+
lastUpdated: currentDate
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const timestamp = Date.now().toString();
|
|
200
|
+
const randomSuffix = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
|
|
201
|
+
const approvalName = `${templateName}_${timestamp}_${randomSuffix}`.toLowerCase().replace(/[^a-z0-9_]/g, '_');
|
|
202
|
+
const approvalResponse = await provider.submitForApproval(twilioContent.sid, approvalName, 'UTILITY');
|
|
203
|
+
|
|
204
|
+
const validSubmittedDate = parseDate(approvalResponse.date_created || approvalResponse.dateCreated);
|
|
205
|
+
const validUpdatedDate = parseDate(approvalResponse.date_updated || approvalResponse.dateUpdated, validSubmittedDate);
|
|
206
|
+
|
|
207
|
+
await TemplateModel.updateOne(
|
|
208
|
+
{ sid: twilioContent.sid },
|
|
209
|
+
{
|
|
210
|
+
status: 'PENDING',
|
|
211
|
+
approvalRequest: {
|
|
212
|
+
sid: approvalResponse.sid,
|
|
213
|
+
status: approvalResponse.status || 'PENDING',
|
|
214
|
+
dateSubmitted: validSubmittedDate,
|
|
215
|
+
dateUpdated: validUpdatedDate,
|
|
216
|
+
rejectionReason: approvalResponse.rejection_reason || ''
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
logger.info('[StartConversation] Template created and submitted for approval', {
|
|
222
|
+
phoneNumber,
|
|
223
|
+
templateSid: twilioContent.sid,
|
|
224
|
+
approvalStatus: approvalResponse.status
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
pollTemplateApproval(twilioContent.sid, {
|
|
228
|
+
label: '[StartConversation]',
|
|
229
|
+
logContext: { phoneNumber },
|
|
230
|
+
onApproved: async (prov) => {
|
|
231
|
+
await sendMessage({ code: phoneNumber, contentSid: twilioContent.sid, variables: {} });
|
|
232
|
+
logger.info('[StartConversation] Template sent successfully', { phoneNumber, templateSid: twilioContent.sid });
|
|
233
|
+
await TemplateModel.updateOne({ sid: twilioContent.sid }, { status: 'APPROVED' });
|
|
234
|
+
try {
|
|
235
|
+
await prov.deleteTemplate(twilioContent.sid);
|
|
236
|
+
logger.info('[StartConversation] Template cleaned up', { templateSid: twilioContent.sid });
|
|
237
|
+
} catch (deleteErr) {
|
|
238
|
+
logger.warn('[StartConversation] Failed to delete template after send', { templateSid: twilioContent.sid, error: deleteErr.message });
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
onRejected: async (prov) => {
|
|
242
|
+
logger.warn('[StartConversation] Template rejected', { phoneNumber, templateSid: twilioContent.sid });
|
|
243
|
+
await TemplateModel.updateOne({ sid: twilioContent.sid }, { status: 'REJECTED' });
|
|
244
|
+
try {
|
|
245
|
+
await prov.deleteTemplate(twilioContent.sid);
|
|
246
|
+
} catch (deleteErr) {
|
|
247
|
+
logger.warn('[StartConversation] Failed to delete rejected template', { templateSid: twilioContent.sid, error: deleteErr.message });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
phoneNumber,
|
|
254
|
+
threadId: thread._id,
|
|
255
|
+
contentSid: twilioContent.sid,
|
|
256
|
+
approvalStatus: approvalResponse.status || 'PENDING'
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
|
|
154
260
|
module.exports = {
|
|
155
261
|
fetchConversationData,
|
|
156
|
-
processConversations
|
|
262
|
+
processConversations,
|
|
263
|
+
startConversation
|
|
157
264
|
};
|