@peopl-health/nexus 1.3.0 → 1.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.
- package/lib/controllers/assistantController.js +3 -3
- package/lib/controllers/conversationController.js +0 -1
- package/lib/controllers/mediaController.js +40 -38
- package/lib/controllers/messageController.js +7 -22
- 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);
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
let downloadFileFromS3, s3;
|
|
3
|
-
try {
|
|
4
|
-
downloadFileFromS3 = require('../config/awsConfig')?.downloadFileFromS3;
|
|
5
|
-
s3 = require('../config/awsConfig')?.s3;
|
|
6
|
-
} catch (e) {
|
|
7
|
-
// AWS config not available
|
|
8
|
-
}
|
|
1
|
+
const { s3, downloadFileFromS3 } = require('../config/awsConfig');
|
|
9
2
|
const bucketName = process.env.AWS_S3_BUCKET_NAME;
|
|
10
3
|
|
|
11
4
|
|
|
@@ -24,6 +17,14 @@ const getMediaController = async (req, res) => {
|
|
|
24
17
|
});
|
|
25
18
|
}
|
|
26
19
|
|
|
20
|
+
// Validate configuration
|
|
21
|
+
if (!downloadFileFromS3 || typeof downloadFileFromS3 !== 'function') {
|
|
22
|
+
return res.status(500).json({ success: false, error: 'downloadFileFromS3 not configured. Call configureMediaController() to inject it.' });
|
|
23
|
+
}
|
|
24
|
+
if (!bucketName) {
|
|
25
|
+
return res.status(500).json({ success: false, error: 'AWS_S3_BUCKET_NAME not configured. Pass bucketName to configureMediaController() or set env.' });
|
|
26
|
+
}
|
|
27
|
+
|
|
27
28
|
let mediaKey = key;
|
|
28
29
|
|
|
29
30
|
console.log(`[MediaController] Final S3 key to fetch: ${mediaKey}`);
|
|
@@ -32,36 +33,37 @@ const getMediaController = async (req, res) => {
|
|
|
32
33
|
|
|
33
34
|
if (!fileData || !fileData.Body) {
|
|
34
35
|
console.error(`[MediaController] Media not found in S3: ${key}`);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
36
|
+
|
|
37
|
+
if (s3) {
|
|
38
|
+
try {
|
|
39
|
+
const prefix = key.split('/') [0];
|
|
40
|
+
console.log(`[MediaController] Checking S3 for objects with prefix: ${prefix}/`);
|
|
41
|
+
s3.listObjectsV2({
|
|
42
|
+
Bucket: bucketName,
|
|
43
|
+
Prefix: prefix + '/',
|
|
44
|
+
MaxKeys: 10
|
|
45
|
+
}).promise()
|
|
46
|
+
.then(listData => {
|
|
47
|
+
if (listData.Contents && listData.Contents.length > 0) {
|
|
48
|
+
console.log(`[MediaController] Found ${listData.Contents.length} objects with similar prefix:`);
|
|
49
|
+
listData.Contents.forEach(item => {
|
|
50
|
+
console.log(`[MediaController] - ${item.Key} (${item.Size} bytes)`);
|
|
51
|
+
if (item.Key.includes(key.split('/').pop().substring(0, 10))) {
|
|
52
|
+
console.log(`[MediaController] !!! POTENTIAL MATCH: ${item.Key}`);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
console.log(`[MediaController] No objects found with prefix: ${prefix}/`);
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
.catch(listErr => {
|
|
60
|
+
console.error(`[MediaController] Error listing objects: ${listErr.message}`);
|
|
61
|
+
});
|
|
62
|
+
} catch (listErr) {
|
|
63
|
+
console.error(`[MediaController] Error setting up bucket listing: ${listErr.message}`);
|
|
64
|
+
}
|
|
63
65
|
}
|
|
64
|
-
|
|
66
|
+
|
|
65
67
|
return res.status(404).json({
|
|
66
68
|
success: false,
|
|
67
69
|
error: 'Media not found in S3',
|
|
@@ -102,4 +104,4 @@ const getMediaController = async (req, res) => {
|
|
|
102
104
|
|
|
103
105
|
module.exports = {
|
|
104
106
|
getMediaController
|
|
105
|
-
};
|
|
107
|
+
};
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
const { Message } = require('../models/messageModel.js');
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const { sendMessage } = require('../core/NexusMessaging');
|
|
5
|
-
|
|
6
|
-
// Stub for missing model
|
|
7
|
-
const ScheduledMessage = {
|
|
8
|
-
create: () => Promise.resolve({ success: false, error: 'Model not available' }),
|
|
9
|
-
find: () => Promise.resolve([]),
|
|
10
|
-
findById: () => Promise.resolve(null),
|
|
11
|
-
deleteOne: () => Promise.resolve({ success: false, error: 'Model not available' })
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// Stub functions for missing services
|
|
15
|
-
const getRecordByFilter = () => Promise.resolve(null);
|
|
16
|
-
const sendScheduledMessage = () => Promise.resolve({ success: false, error: 'Service not available' });
|
|
2
|
+
const { ScheduledMessage } = require('../models/agendaMessageModel.js');
|
|
3
|
+
const { getRecordByFilter } = require('../services/airtableService.js');
|
|
4
|
+
const { sendMessage, sendScheduledMessage } = require('../core/NexusMessaging');
|
|
17
5
|
|
|
18
6
|
const moment = require('moment-timezone');
|
|
19
7
|
|
|
8
|
+
|
|
20
9
|
const sendMessageController = async (req, res) => {
|
|
21
10
|
const {
|
|
22
11
|
fileUrl,
|
|
@@ -29,7 +18,7 @@ const sendMessageController = async (req, res) => {
|
|
|
29
18
|
contentSid = null,
|
|
30
19
|
variables = null
|
|
31
20
|
} = req.body || {};
|
|
32
|
-
const author =
|
|
21
|
+
const author = (require('../config/runtimeConfig').get('USER_DB_MONGO'));
|
|
33
22
|
const sendMoment = sendTime ? moment.tz(sendTime, timeZone) + 2500 : new Date();
|
|
34
23
|
|
|
35
24
|
try {
|
|
@@ -74,7 +63,7 @@ const sendBulkMessageController = async (req, res) => {
|
|
|
74
63
|
contentSid = null,
|
|
75
64
|
variables = null
|
|
76
65
|
} = req.body || {};
|
|
77
|
-
const author =
|
|
66
|
+
const author = (require('../config/runtimeConfig').get('USER_DB_MONGO'));
|
|
78
67
|
const sendMoment = sendTime ? moment.tz(sendTime, timeZone) + 20*1000 : new Date();
|
|
79
68
|
|
|
80
69
|
try {
|
|
@@ -144,7 +133,7 @@ const sendBulkMessageAirtableController = async (req, res) => {
|
|
|
144
133
|
condition = '1',
|
|
145
134
|
variables = null
|
|
146
135
|
} = req.body || {};
|
|
147
|
-
const author =
|
|
136
|
+
const author = (require('../config/runtimeConfig').get('USER_DB_MONGO'));
|
|
148
137
|
const sendMoment = sendTime ? moment.tz(sendTime, timeZone) + 20*1000 : new Date();
|
|
149
138
|
|
|
150
139
|
const regex = /\[(.*?)\]/g;
|
|
@@ -194,14 +183,10 @@ const sendBulkMessageAirtableController = async (req, res) => {
|
|
|
194
183
|
variables
|
|
195
184
|
};
|
|
196
185
|
|
|
197
|
-
// Add to scheduledMessages for both saving and scheduling
|
|
198
186
|
scheduledMessages.push(scheduledMessage);
|
|
199
|
-
|
|
200
|
-
// Increment delay and message counter
|
|
201
187
|
extraDelay += Math.floor(Math.random() * 5001) + 5000;
|
|
202
188
|
}
|
|
203
189
|
|
|
204
|
-
// Schedule all messages with Agenda in parallel
|
|
205
190
|
const sentMessages = await Promise.all(
|
|
206
191
|
scheduledMessages.map(async (message) => {
|
|
207
192
|
const savedMessage = await ScheduledMessage.create(message);
|
|
@@ -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
|
-
};
|