@peopl-health/nexus 1.3.0 → 1.3.1
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/assistantController.js +3 -3
- package/lib/controllers/messageController.js +38 -21
- package/lib/controllers/templateController.js +5 -17
- package/lib/models/agendaMessageModel.js +24 -0
- package/lib/services/assistantService.js +0 -1
- package/package.json +1 -1
- package/lib/services/appointmentService.js +0 -55
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { Config_ID } = require('../config/airtableConfig');
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { Thread } = require('../models/threadModel');
|
|
4
4
|
|
|
5
5
|
const { getRecordByFilter } = require('../services/airtableService');
|
|
6
6
|
const { createAssistant, addMsgAssistant, addInsAssistant } = require('../services/assistantService');
|
|
@@ -12,7 +12,7 @@ const activeAssistantController = async (req, res) => {
|
|
|
12
12
|
const { code, active } = req.body;
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
|
-
await
|
|
15
|
+
await Thread.updateOne({ code }, { $set: { active: !!active } });
|
|
16
16
|
return res.status(200).send({ message: 'Active assistant' });
|
|
17
17
|
} catch (error) {
|
|
18
18
|
console.log(error);
|
|
@@ -114,7 +114,7 @@ const stopAssistantController = async (req, res) => {
|
|
|
114
114
|
const { code, stop } = req.body;
|
|
115
115
|
|
|
116
116
|
try {
|
|
117
|
-
await
|
|
117
|
+
await Thread.updateOne({ code }, { $set: { stopped: !!stop } });
|
|
118
118
|
return res.status(200).send({ message: 'Stop assistant' });
|
|
119
119
|
} catch (error) {
|
|
120
120
|
console.log(error);
|
|
@@ -3,17 +3,19 @@ const { Message } = require('../models/messageModel.js');
|
|
|
3
3
|
// Import from Nexus core
|
|
4
4
|
const { sendMessage } = require('../core/NexusMessaging');
|
|
5
5
|
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
deleteOne: () => Promise.resolve({ success: false, error: 'Model not available' })
|
|
6
|
+
// Injectable dependencies with safe defaults
|
|
7
|
+
let injected = {
|
|
8
|
+
ScheduledMessage: null,
|
|
9
|
+
getRecordByFilter: null,
|
|
10
|
+
sendScheduledMessage: null
|
|
12
11
|
};
|
|
13
12
|
|
|
14
|
-
//
|
|
15
|
-
const getRecordByFilter =
|
|
16
|
-
|
|
13
|
+
// Allow consumers to inject their model and services
|
|
14
|
+
const configureMessageController = ({ ScheduledMessage, getRecordByFilter, sendScheduledMessage } = {}) => {
|
|
15
|
+
if (ScheduledMessage) injected.ScheduledMessage = ScheduledMessage;
|
|
16
|
+
if (getRecordByFilter) injected.getRecordByFilter = getRecordByFilter;
|
|
17
|
+
if (sendScheduledMessage) injected.sendScheduledMessage = sendScheduledMessage;
|
|
18
|
+
};
|
|
17
19
|
|
|
18
20
|
const moment = require('moment-timezone');
|
|
19
21
|
|
|
@@ -33,6 +35,9 @@ const sendMessageController = async (req, res) => {
|
|
|
33
35
|
const sendMoment = sendTime ? moment.tz(sendTime, timeZone) + 2500 : new Date();
|
|
34
36
|
|
|
35
37
|
try {
|
|
38
|
+
if (!injected.ScheduledMessage || typeof injected.ScheduledMessage.create !== 'function') {
|
|
39
|
+
return res.status(500).json({ success: false, error: 'ScheduledMessage model not configured. Call configureMessageController() to inject it.' });
|
|
40
|
+
}
|
|
36
41
|
const messageData = {
|
|
37
42
|
fileUrl,
|
|
38
43
|
message,
|
|
@@ -46,7 +51,7 @@ const sendMessageController = async (req, res) => {
|
|
|
46
51
|
extraDelay: 0,
|
|
47
52
|
variables
|
|
48
53
|
};
|
|
49
|
-
await ScheduledMessage.create(messageData);
|
|
54
|
+
await injected.ScheduledMessage.create(messageData);
|
|
50
55
|
console.log('Sending message with data:', messageData);
|
|
51
56
|
|
|
52
57
|
const result = await sendMessage(messageData);
|
|
@@ -78,12 +83,18 @@ const sendBulkMessageController = async (req, res) => {
|
|
|
78
83
|
const sendMoment = sendTime ? moment.tz(sendTime, timeZone) + 20*1000 : new Date();
|
|
79
84
|
|
|
80
85
|
try {
|
|
86
|
+
if (!injected.ScheduledMessage || typeof injected.ScheduledMessage.create !== 'function') {
|
|
87
|
+
return res.status(500).json({ success: false, error: 'ScheduledMessage model not configured. Call configureMessageController() to inject it.' });
|
|
88
|
+
}
|
|
89
|
+
if (!injected.sendScheduledMessage || typeof injected.sendScheduledMessage !== 'function') {
|
|
90
|
+
return res.status(500).json({ success: false, error: 'sendScheduledMessage not configured. Call configureMessageController() to inject it.' });
|
|
91
|
+
}
|
|
81
92
|
let numSend = 0;
|
|
82
93
|
let extraDelay = 0;
|
|
83
94
|
let curMessage = message;
|
|
84
95
|
const scheduledMessages = [];
|
|
85
96
|
for (const code of codes) {
|
|
86
|
-
const scheduledMessage = new ScheduledMessage({
|
|
97
|
+
const scheduledMessage = new injected.ScheduledMessage({
|
|
87
98
|
fileUrl,
|
|
88
99
|
message: curMessage,
|
|
89
100
|
fileType,
|
|
@@ -108,8 +119,8 @@ const sendBulkMessageController = async (req, res) => {
|
|
|
108
119
|
// Schedule all messages with Agenda in parallel
|
|
109
120
|
const sentMessages = await Promise.all(
|
|
110
121
|
scheduledMessages.map(async (message) => {
|
|
111
|
-
const savedMessage = await ScheduledMessage.create(message);
|
|
112
|
-
return sendScheduledMessage(savedMessage);
|
|
122
|
+
const savedMessage = await injected.ScheduledMessage.create(message);
|
|
123
|
+
return injected.sendScheduledMessage(savedMessage);
|
|
113
124
|
})
|
|
114
125
|
);
|
|
115
126
|
|
|
@@ -155,7 +166,16 @@ const sendBulkMessageAirtableController = async (req, res) => {
|
|
|
155
166
|
}
|
|
156
167
|
|
|
157
168
|
try {
|
|
158
|
-
|
|
169
|
+
if (!injected.getRecordByFilter || typeof injected.getRecordByFilter !== 'function') {
|
|
170
|
+
return res.status(500).json({ success: false, error: 'Airtable getRecordByFilter not configured. Call configureMessageController() to inject it.' });
|
|
171
|
+
}
|
|
172
|
+
if (!injected.ScheduledMessage || typeof injected.ScheduledMessage.create !== 'function') {
|
|
173
|
+
return res.status(500).json({ success: false, error: 'ScheduledMessage model not configured. Call configureMessageController() to inject it.' });
|
|
174
|
+
}
|
|
175
|
+
if (!injected.sendScheduledMessage || typeof injected.sendScheduledMessage !== 'function') {
|
|
176
|
+
return res.status(500).json({ success: false, error: 'sendScheduledMessage not configured. Call configureMessageController() to inject it.' });
|
|
177
|
+
}
|
|
178
|
+
const rows = await injected.getRecordByFilter(baseId, tableName, condition);
|
|
159
179
|
let extraDelay = 0;
|
|
160
180
|
let curMessage = message;
|
|
161
181
|
const sentPhones = new Set();
|
|
@@ -194,18 +214,14 @@ const sendBulkMessageAirtableController = async (req, res) => {
|
|
|
194
214
|
variables
|
|
195
215
|
};
|
|
196
216
|
|
|
197
|
-
// Add to scheduledMessages for both saving and scheduling
|
|
198
217
|
scheduledMessages.push(scheduledMessage);
|
|
199
|
-
|
|
200
|
-
// Increment delay and message counter
|
|
201
218
|
extraDelay += Math.floor(Math.random() * 5001) + 5000;
|
|
202
219
|
}
|
|
203
220
|
|
|
204
|
-
// Schedule all messages with Agenda in parallel
|
|
205
221
|
const sentMessages = await Promise.all(
|
|
206
222
|
scheduledMessages.map(async (message) => {
|
|
207
|
-
const savedMessage = await ScheduledMessage.create(message);
|
|
208
|
-
return sendScheduledMessage(savedMessage);
|
|
223
|
+
const savedMessage = await injected.ScheduledMessage.create(message);
|
|
224
|
+
return injected.sendScheduledMessage(savedMessage);
|
|
209
225
|
})
|
|
210
226
|
);
|
|
211
227
|
|
|
@@ -256,5 +272,6 @@ module.exports = {
|
|
|
256
272
|
sendMessageController,
|
|
257
273
|
sendBulkMessageController,
|
|
258
274
|
sendBulkMessageAirtableController,
|
|
259
|
-
getLastInteractionController
|
|
275
|
+
getLastInteractionController,
|
|
276
|
+
configureMessageController
|
|
260
277
|
};
|
|
@@ -17,33 +17,21 @@ const checkTemplateSupport = () => {
|
|
|
17
17
|
throw new Error('Template operations are only supported with Twilio provider');
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
-
|
|
20
|
+
|
|
21
21
|
const { handleApiError } = require('../utils/errorHandler');
|
|
22
22
|
|
|
23
23
|
const { Template } = require('../templates/templateStructure');
|
|
24
24
|
const predefinedTemplates = require('../templates/predefinedTemplates');
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
const getTemplateModel = () => {
|
|
28
|
-
|
|
29
|
-
return mongoose.models.Template;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// If not in mongoose.models, require and return the model
|
|
28
|
+
// Require the concrete model; enforce presence instead of stubbing
|
|
33
29
|
try {
|
|
30
|
+
// If already registered in mongoose.models, require still returns the same model
|
|
34
31
|
const TemplateModel = require('../models/templateModel');
|
|
35
32
|
return TemplateModel;
|
|
36
33
|
} catch (error) {
|
|
37
|
-
|
|
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
|
-
};
|
|
34
|
+
throw new Error('Template model not available. Ensure models are loaded before using template controllers.');
|
|
47
35
|
}
|
|
48
36
|
};
|
|
49
37
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const mongoose = require('mongoose');
|
|
2
|
+
|
|
3
|
+
const scheduledMessageSchema = new mongoose.Schema({
|
|
4
|
+
fileUrl: String,
|
|
5
|
+
message: String,
|
|
6
|
+
fileType: String,
|
|
7
|
+
sendTime: Date,
|
|
8
|
+
hidePreview: Boolean,
|
|
9
|
+
contentSid: String,
|
|
10
|
+
code: String,
|
|
11
|
+
author: String,
|
|
12
|
+
variables: Object,
|
|
13
|
+
extraDelay: Number,
|
|
14
|
+
timeZone: { type: String, default: 'Etc/GMT'},
|
|
15
|
+
status: { type: String, default: 'pending' },
|
|
16
|
+
wa_id: { type: String, default: null },
|
|
17
|
+
createdAt: { type: Date, default: Date.now }
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const ScheduledMessage = mongoose.model('ScheduledMessage', scheduledMessageSchema);
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
ScheduledMessage
|
|
24
|
+
};
|
package/package.json
CHANGED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
const { Calendar_ID } = require('../config/airtableConfig.js');
|
|
2
|
-
|
|
3
|
-
const { ISO_DATE, parseStartTime, addDays } = require('../utils/dateUtils.js');
|
|
4
|
-
const { dateAndTimeFromStart } = require('../utils/dateUtils.js');
|
|
5
|
-
|
|
6
|
-
const { getRecordByFilter, updateRecordByFilter } = require('../services/airtableService.js');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const getNextAppointmentForPatient = async (name, after = new Date()) => {
|
|
10
|
-
const filter = `AND(patient_name = "${name}", start_time >= '${ISO_DATE(after)}')`;
|
|
11
|
-
const rows = await getRecordByFilter(Calendar_ID, 'calendar_quimio', filter);
|
|
12
|
-
if (!rows?.length) return null;
|
|
13
|
-
return rows.sort((a, b) => new Date(a.start_time) - new Date(b.start_time))[0];
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const getAppointmentsBetween = async (start, end) => {
|
|
17
|
-
const filter = `AND(start_time >= '${ISO_DATE(start)}', start_time < '${ISO_DATE(end)}')`;
|
|
18
|
-
return getRecordByFilter(Calendar_ID, 'calendar_quimio', filter);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const updateAppointmentById = async (recordId, data) => {
|
|
22
|
-
return updateRecordByFilter(
|
|
23
|
-
Calendar_ID,
|
|
24
|
-
'calendar_quimio',
|
|
25
|
-
`REGEX_MATCH({record_id}, '${recordId}')`,
|
|
26
|
-
data
|
|
27
|
-
);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const buildAvailabilityWindow = async originalDate => {
|
|
31
|
-
const start = addDays(originalDate, 1);
|
|
32
|
-
const end = addDays(originalDate, 7);
|
|
33
|
-
|
|
34
|
-
const allSlots = await getAppointmentsBetween(start, addDays(end, 1));
|
|
35
|
-
const availableSlots = allSlots.filter(row => !row.patient);
|
|
36
|
-
|
|
37
|
-
const result = {};
|
|
38
|
-
availableSlots.forEach(row => {
|
|
39
|
-
const { date, time } = dateAndTimeFromStart(row.start_time);
|
|
40
|
-
if (!result[date]) result[date] = [];
|
|
41
|
-
result[date].push({ time, availableSpots: 1 });
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return result;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const parseStart = parseStartTime;
|
|
48
|
-
|
|
49
|
-
module.exports = {
|
|
50
|
-
getNextAppointmentForPatient,
|
|
51
|
-
getAppointmentsBetween,
|
|
52
|
-
updateAppointmentById,
|
|
53
|
-
parseStart,
|
|
54
|
-
buildAvailabilityWindow
|
|
55
|
-
};
|