@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.
@@ -1,6 +1,6 @@
1
1
  const { Config_ID } = require('../config/airtableConfig');
2
2
 
3
- const { updateThreadActive, updateThreadStop, Thread } = require('../models/threadModel');
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 updateThreadActive(code, active);
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 updateThreadStop(code, stop);
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
- // 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' })
6
+ // Injectable dependencies with safe defaults
7
+ let injected = {
8
+ ScheduledMessage: null,
9
+ getRecordByFilter: null,
10
+ sendScheduledMessage: null
12
11
  };
13
12
 
14
- // Stub functions for missing services
15
- const getRecordByFilter = () => Promise.resolve(null);
16
- const sendScheduledMessage = () => Promise.resolve({ success: false, error: 'Service not available' });
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
- const rows = await getRecordByFilter(baseId, tableName, condition);
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
- const mongoose = require('mongoose');
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
- // Use mongoose models to avoid conflicts
26
+
27
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
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
- 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
- };
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
+ };
@@ -40,7 +40,6 @@ const getAssistantById = (assistant_id, thread) => {
40
40
  }
41
41
 
42
42
  if (!assistantConfig) {
43
- // Provide a more permissive default: allow registry usage without full config
44
43
  assistantConfig = {};
45
44
  }
46
45
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -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
- };