@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.
@@ -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);
@@ -1,4 +1,3 @@
1
- // Use mongoose models directly to avoid conflicts
2
1
  const mongoose = require('mongoose');
3
2
  const { fetchConversationData, processConversations } = require('../services/conversationService');
4
3
  const { sendMessage } = require('../core/NexusMessaging');
@@ -1,11 +1,4 @@
1
- // Optional AWS config - will be undefined if not available
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
- try {
37
- const prefix = key.split('/')[0];
38
- console.log(`[MediaController] Checking S3 for objects with prefix: ${prefix}/`);
39
-
40
- s3.listObjectsV2({
41
- Bucket: bucketName,
42
- Prefix: prefix + '/',
43
- MaxKeys: 10
44
- }).promise()
45
- .then(listData => {
46
- if (listData.Contents && listData.Contents.length > 0) {
47
- console.log(`[MediaController] Found ${listData.Contents.length} objects with similar prefix:`);
48
- listData.Contents.forEach(item => {
49
- console.log(`[MediaController] - ${item.Key} (${item.Size} bytes)`);
50
- if (item.Key.includes(key.split('/').pop().substring(0, 10))) {
51
- console.log(`[MediaController] !!! POTENTIAL MATCH: ${item.Key}`);
52
- }
53
- });
54
- } else {
55
- console.log(`[MediaController] No objects found with prefix: ${prefix}/`);
56
- }
57
- })
58
- .catch(listErr => {
59
- console.error(`[MediaController] Error listing objects: ${listErr.message}`);
60
- });
61
- } catch (listErr) {
62
- console.error(`[MediaController] Error setting up bucket listing: ${listErr.message}`);
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
- // Import from Nexus core
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 = process.env.USER_DB_MONGO;
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 = process.env.USER_DB_MONGO;
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 = process.env.USER_DB_MONGO;
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
- 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.2",
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
- };