shuttlepro-shared 1.4.23 → 1.4.24

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.
@@ -39,7 +39,12 @@ const updateCustomerProfile = async (id, data) => {
39
39
  }).exec();
40
40
  return updated;
41
41
  };
42
-
42
+ const updateManyCustomerProfiles = async (filter, data) => {
43
+ const result = await CustomerProfile.updateMany(filter, {
44
+ $set: data,
45
+ }).exec();
46
+ return result;
47
+ };
43
48
  const updateCustomerProfileByFilter = async (
44
49
  filter,
45
50
  data,
@@ -112,4 +117,5 @@ module.exports = {
112
117
  findAllCustomerProfilesByFilter,
113
118
  findAllCustomerProfilesByFilterFromDb,
114
119
  searchCustomerProfilesByName,
120
+ updateManyCustomerProfiles,
115
121
  };
@@ -15,9 +15,7 @@ const notificationSettingsRepository = require("./notificationSettings.repositor
15
15
  const customerProfileRepository = require("./customerProfile.repository");
16
16
  const customerTimelineRepository = require("./customerTimeline.repository");
17
17
  const shopRepository = require("./shop.repository");
18
- const callRepository = require("./call.repository");
19
18
  const interactiveRepository = require("./interactive.repository");
20
-
21
19
  exports.module = {
22
20
  workspaceRepository,
23
21
  integrationRepository,
@@ -36,6 +34,5 @@ exports.module = {
36
34
  customerProfileRepository,
37
35
  customerTimelineRepository,
38
36
  shopRepository,
39
- callRepository,
40
37
  interactiveRepository,
41
38
  };
package/config/socket.js CHANGED
@@ -4,46 +4,27 @@ require("dotenv").config();
4
4
 
5
5
  let io;
6
6
  const namespaceSockets = {}; // Store different namespace sockets
7
- const namespaceHandlers = {}; // Store custom handlers for each namespace
8
-
9
- /**
10
- * Register custom event handlers for a specific namespace
11
- * @param {string} namespace - Namespace name
12
- * @param {Function} handlerFunction - Function that sets up socket event handlers
13
- */
14
- const registerNamespaceHandlers = (namespace, handlerFunction) => {
15
- namespaceHandlers[namespace] = handlerFunction;
16
- console.log(`📋 Registered custom handlers for namespace: /${namespace}`);
17
- };
18
7
 
19
8
  /**
20
9
  * Initialize Socket.IO server with namespaces
21
10
  * @param {http.Server} server - HTTP server instance
22
11
  * @param {string} namespaceParam - Socket namespace name (default: "conversation")
23
12
  * @param {string} redisChannel - Redis channel for this namespace (default: "socket_events")
24
- * @param {Object} options - Additional options for namespace configuration
25
- * @param {Function} options.customHandlers - Custom socket event handlers for this namespace
26
- * @param {boolean} options.requireWorkspaceId - Whether workspace ID is required (default: true)
27
13
  * @returns {SocketIO.Namespace} - The initialized namespace
28
14
  */
29
15
  const initializeSocket = async (
30
16
  server,
31
17
  namespaceParam = "conversation",
32
- redisChannel = "socket_events",
33
- options = {}
18
+ redisChannel = "socket_events"
34
19
  ) => {
35
- const {
36
- customHandlers,
37
- requireWorkspaceId = true,
38
- cors = {
39
- origin: "*",
40
- methods: ["GET", "POST"],
41
- },
42
- } = options;
43
-
44
20
  // Initialize Socket.IO server if not already done
45
21
  if (!io) {
46
- io = new Server(server, { cors });
22
+ io = new Server(server, {
23
+ cors: {
24
+ origin: "*",
25
+ methods: ["GET", "POST"],
26
+ },
27
+ });
47
28
  console.log("✅ Socket.IO server initialized");
48
29
  }
49
30
 
@@ -68,36 +49,21 @@ const initializeSocket = async (
68
49
  await subscriber.subscribe(nsRedisChannel, (messageStr) => {
69
50
  try {
70
51
  const message = JSON.parse(messageStr);
71
- const { workspaceId, event, data, target } = message;
52
+ const { workspaceId, event, data } = message;
72
53
 
73
54
  console.log(`📥 Redis message received on ${nsRedisChannel}:`, {
74
55
  workspaceId,
75
56
  event,
76
- target,
77
57
  });
78
58
 
79
- // Handle different targeting options
80
- if (target === "broadcast") {
81
- // Broadcast to all clients in this namespace
82
- namespace.emit(event, data);
83
- console.log(
84
- `📢 Event ${event} broadcasted to all clients in /${namespaceParam}`
85
- );
86
- } else if (target && target.startsWith("socket:")) {
87
- // Target specific socket ID
88
- const socketId = target.replace("socket:", "");
89
- namespace.to(socketId).emit(event, data);
90
- console.log(
91
- `📢 Event ${event} emitted to socket ${socketId} in /${namespaceParam}`
92
- );
93
- } else if (workspaceId) {
94
- // Emit to specific workspace room in this namespace
59
+ // Emit to specific workspace room in this namespace
60
+ if (workspaceId) {
95
61
  namespace.to(workspaceId).emit(event, data);
96
62
  console.log(
97
63
  `📢 Event ${event} emitted to workspace ${workspaceId} in /${namespaceParam}`
98
64
  );
99
65
  } else {
100
- // Default: broadcast to all clients in this namespace
66
+ // Broadcast to all clients in this namespace if no workspaceId specified
101
67
  namespace.emit(event, data);
102
68
  console.log(
103
69
  `📢 Event ${event} broadcasted to all clients in /${namespaceParam}`
@@ -113,44 +79,20 @@ const initializeSocket = async (
113
79
 
114
80
  // Handle new socket connections to this namespace
115
81
  namespace.on("connection", (socket) => {
116
- const { workspaceId, callId, agentId } = socket.handshake.query;
82
+ const { workspaceId } = socket.handshake.query;
117
83
 
118
- // Workspace ID validation (can be disabled for certain namespaces)
119
- if (requireWorkspaceId && !workspaceId) {
120
- console.warn(
121
- `⚠️ Connection rejected: No workspaceId provided for namespace /${namespaceParam}`
122
- );
84
+ if (!workspaceId) {
85
+ console.warn(`⚠️ Connection rejected: No workspaceId provided`);
123
86
  socket.disconnect(true);
124
87
  return;
125
88
  }
126
89
 
127
90
  console.log(
128
- `✅ Client connected: ${socket.id} (Workspace: ${
129
- workspaceId || "N/A"
130
- }, CallId: ${callId || "N/A"}) in /${namespaceParam}`
91
+ `✅ Client connected: ${socket.id} (Workspace: ${workspaceId}) in /${namespaceParam}`
131
92
  );
132
93
 
133
- // Join workspace room if workspaceId is provided
134
- if (workspaceId) {
135
- socket.join(workspaceId);
136
- }
137
-
138
- // Join additional rooms based on query parameters
139
- if (callId) {
140
- socket.join(`call:${callId}`);
141
- }
142
- if (agentId) {
143
- socket.join(`agent:${agentId}`);
144
- }
145
-
146
- // Store socket metadata
147
- socket.metadata = {
148
- workspaceId,
149
- callId,
150
- agentId,
151
- namespace: namespaceParam,
152
- connectedAt: new Date(),
153
- };
94
+ // Join workspace room
95
+ socket.join(workspaceId);
154
96
 
155
97
  // Send connection confirmation to client
156
98
  socket.emit("connected", {
@@ -158,70 +100,25 @@ const initializeSocket = async (
158
100
  socketId: socket.id,
159
101
  namespace: namespaceParam,
160
102
  workspaceId,
161
- callId,
162
- agentId,
163
103
  });
164
104
 
165
- // Apply custom handlers if provided
166
- if (customHandlers && typeof customHandlers === "function") {
167
- try {
168
- customHandlers(socket, namespace);
169
- console.log(
170
- `📋 Applied custom handlers for socket ${socket.id} in /${namespaceParam}`
171
- );
172
- } catch (error) {
173
- console.error(
174
- `❌ Error applying custom handlers for socket ${socket.id}:`,
175
- error
176
- );
177
- }
178
- }
179
-
180
- // Apply registered namespace handlers
181
- if (namespaceHandlers[namespaceParam]) {
182
- try {
183
- namespaceHandlers[namespaceParam](socket, namespace);
184
- console.log(
185
- `📋 Applied registered handlers for socket ${socket.id} in /${namespaceParam}`
186
- );
187
- } catch (error) {
188
- console.error(
189
- `❌ Error applying registered handlers for socket ${socket.id}:`,
190
- error
191
- );
192
- }
193
- }
194
-
195
- // Default event handlers for backward compatibility
196
- setupDefaultHandlers(socket, namespace, namespaceParam);
105
+ // Handle custom events from clients
106
+ socket.on("client_event", (data) => {
107
+ console.log(`📥 Client event from ${socket.id}:`, data);
108
+ // You can process client events here
109
+ });
197
110
 
198
111
  // Handle disconnect
199
112
  socket.on("disconnect", (reason) => {
200
113
  console.log(
201
- `❌ Client disconnected: ${socket.id} (Workspace: ${
202
- workspaceId || "N/A"
203
- }, Reason: ${reason}) from /${namespaceParam}`
114
+ `❌ Client disconnected: ${socket.id} (Workspace: ${workspaceId}, Reason: ${reason})`
204
115
  );
205
-
206
- // Leave all rooms
207
- if (workspaceId) socket.leave(workspaceId);
208
- if (callId) socket.leave(`call:${callId}`);
209
- if (agentId) socket.leave(`agent:${agentId}`);
210
-
211
- // Emit disconnect event to namespace for cleanup
212
- namespace.emit("client_disconnected", {
213
- socketId: socket.id,
214
- metadata: socket.metadata,
215
- reason,
216
- });
116
+ socket.leave(workspaceId);
217
117
  });
218
118
 
219
119
  // Handle errors
220
120
  socket.on("error", (error) => {
221
- console.error(
222
- `❌ Socket error for ${socket.id} in /${namespaceParam}:`,
223
- error
224
- );
121
+ console.error(`❌ Socket error for ${socket.id}:`, error);
225
122
  });
226
123
  });
227
124
 
@@ -231,64 +128,15 @@ const initializeSocket = async (
231
128
  return namespace;
232
129
  };
233
130
 
234
- /**
235
- * Setup default event handlers for backward compatibility
236
- * @param {Socket} socket - Socket.IO socket instance
237
- * @param {Namespace} namespace - Socket.IO namespace instance
238
- * @param {string} namespaceParam - Namespace name
239
- */
240
- const setupDefaultHandlers = (socket, namespace, namespaceParam) => {
241
- // Handle custom events from clients (backward compatibility)
242
- socket.on("client_event", (data) => {
243
- console.log(
244
- `📥 Client event from ${socket.id} in /${namespaceParam}:`,
245
- data
246
- );
247
-
248
- // Emit to Redis for other instances to handle
249
- sendEventToServer({
250
- workspaceId: socket.metadata.workspaceId,
251
- event: "client_event_received",
252
- data: {
253
- socketId: socket.id,
254
- originalData: data,
255
- metadata: socket.metadata,
256
- },
257
- namespace: namespaceParam,
258
- }).catch((err) => {
259
- console.error(`❌ Error publishing client_event to Redis:`, err);
260
- });
261
- });
262
-
263
- // Handle room join requests
264
- socket.on("join_room", (roomName) => {
265
- if (roomName && typeof roomName === "string") {
266
- socket.join(roomName);
267
- socket.emit("room_joined", { room: roomName, success: true });
268
- console.log(`📥 Socket ${socket.id} joined room: ${roomName}`);
269
- }
270
- });
271
-
272
- // Handle room leave requests
273
- socket.on("leave_room", (roomName) => {
274
- if (roomName && typeof roomName === "string") {
275
- socket.leave(roomName);
276
- socket.emit("room_left", { room: roomName, success: true });
277
- console.log(`📤 Socket ${socket.id} left room: ${roomName}`);
278
- }
279
- });
280
- };
281
-
282
131
  /**
283
132
  * Send event to clients via Redis pub/sub
284
133
  * @param {object} params - Event parameters
285
- * @param {string} [params.workspaceId] - Target workspace ID
134
+ * @param {string} params.workspaceId - Target workspace ID
286
135
  * @param {string} params.event - Event name
287
136
  * @param {any} params.data - Event data payload
288
137
  * @param {string} [params.namespace="conversation"] - Target namespace
289
138
  * @param {string} [params.redisChannel="socket_events"] - Base Redis channel
290
- * @param {string} [params.target] - Specific target (broadcast, socket:socketId, etc.)
291
- * @returns {Promise<boolean>}
139
+ * @returns {Promise<void>}
292
140
  */
293
141
  const sendEventToServer = async ({
294
142
  workspaceId,
@@ -296,7 +144,6 @@ const sendEventToServer = async ({
296
144
  data,
297
145
  namespace = "conversation",
298
146
  redisChannel = "socket_events",
299
- target,
300
147
  }) => {
301
148
  try {
302
149
  // Create namespace-specific Redis channel
@@ -306,21 +153,13 @@ const sendEventToServer = async ({
306
153
  await connectRedis();
307
154
 
308
155
  // Create message payload
309
- const message = JSON.stringify({
310
- workspaceId,
311
- event,
312
- data,
313
- target,
314
- timestamp: new Date().toISOString(),
315
- });
156
+ const message = JSON.stringify({ workspaceId, event, data });
316
157
 
317
158
  // Publish to Redis
318
159
  await publisher.publish(nsRedisChannel, message);
319
160
 
320
161
  console.log(
321
- `📢 Event published to Redis channel ${nsRedisChannel}: ${event} (Workspace: ${
322
- workspaceId || "N/A"
323
- }, Target: ${target || "default"})`
162
+ `📢 Event published to Redis channel ${nsRedisChannel}: ${event} (Workspace: ${workspaceId})`
324
163
  );
325
164
 
326
165
  return true;
@@ -330,62 +169,4 @@ const sendEventToServer = async ({
330
169
  }
331
170
  };
332
171
 
333
- /**
334
- * Get a specific namespace instance
335
- * @param {string} namespace - Namespace name
336
- * @returns {SocketIO.Namespace|null} - Namespace instance or null if not found
337
- */
338
- const getNamespace = (namespace) => {
339
- return namespaceSockets[namespace] || null;
340
- };
341
-
342
- /**
343
- * Get all active namespaces
344
- * @returns {Object} - Object containing all active namespaces
345
- */
346
- const getAllNamespaces = () => {
347
- return { ...namespaceSockets };
348
- };
349
-
350
- /**
351
- * Get Socket.IO server instance
352
- * @returns {SocketIO.Server|null} - Server instance or null if not initialized
353
- */
354
- const getSocketServer = () => {
355
- return io || null;
356
- };
357
-
358
- /**
359
- * Emit event to specific room in a namespace
360
- * @param {string} namespace - Namespace name
361
- * @param {string} room - Room name
362
- * @param {string} event - Event name
363
- * @param {any} data - Event data
364
- * @returns {boolean} - Success status
365
- */
366
- const emitToRoom = (namespace, room, event, data) => {
367
- try {
368
- const ns = namespaceSockets[namespace];
369
- if (!ns) {
370
- console.warn(`❌ Namespace /${namespace} not found`);
371
- return false;
372
- }
373
-
374
- ns.to(room).emit(event, data);
375
- console.log(`📢 Event ${event} emitted to room ${room} in /${namespace}`);
376
- return true;
377
- } catch (error) {
378
- console.error(`❌ Error emitting to room ${room} in /${namespace}:`, error);
379
- return false;
380
- }
381
- };
382
-
383
- module.exports = {
384
- initializeSocket,
385
- sendEventToServer,
386
- registerNamespaceHandlers,
387
- getNamespace,
388
- getAllNamespaces,
389
- getSocketServer,
390
- emitToRoom,
391
- };
172
+ module.exports = { initializeSocket, sendEventToServer };
@@ -10,6 +10,11 @@ const AttributeSchema = new Schema(
10
10
  ref: "Workspace",
11
11
  default: null,
12
12
  },
13
+ websiteId: {
14
+ type: Schema.Types.ObjectId,
15
+ ref: "Website",
16
+ default: null,
17
+ },
13
18
  oldId: { type: String, default: "" },
14
19
  createdBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
15
20
  updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
@@ -55,6 +55,32 @@ const AutomationAction = new Schema({
55
55
  templateId: descriptionJoin,
56
56
  delay: defaultStringType,
57
57
  },
58
+ ticketList: {
59
+ enabled: { type: Boolean, default: false },
60
+ templateId: descriptionJoin,
61
+ delay: defaultStringType,
62
+ ticketList: [
63
+ {
64
+ id: {
65
+ type: mongoose.Schema.Types.ObjectId,
66
+ ref: "Column",
67
+ required: true,
68
+ },
69
+ title: {
70
+ type: String,
71
+ required: true,
72
+ },
73
+ name: {
74
+ type: String,
75
+ required: true,
76
+ },
77
+ type: {
78
+ type: String,
79
+ required: true,
80
+ },
81
+ },
82
+ ],
83
+ },
58
84
  markAsSpam: {
59
85
  enabled: { type: Boolean, default: false },
60
86
  },
@@ -64,6 +90,10 @@ const AutomationAction = new Schema({
64
90
  hideComment: {
65
91
  enabled: { type: Boolean, default: false },
66
92
  },
93
+ futureRestriction: {
94
+ enabled: { type: Boolean, default: false },
95
+ action: { type: String, default: "" },
96
+ },
67
97
  closeChat: {
68
98
  enabled: { type: Boolean, default: false },
69
99
  },
@@ -169,6 +199,7 @@ const AutomationCondition = new Schema({
169
199
  "shift",
170
200
  "newCommentPost",
171
201
  "profile",
202
+ "moveTicket",
172
203
  ],
173
204
  },
174
205
  keyValue: {
@@ -193,6 +224,7 @@ const AutomationCondition = new Schema({
193
224
  "orderPublish",
194
225
  "newCommentPost",
195
226
  "profile",
227
+ "moveTicket",
196
228
  ],
197
229
  },
198
230
  subKeyValue: {
@@ -11,6 +11,11 @@ const CategorySchema = new Schema(
11
11
  ref: "Workspace",
12
12
  default: null,
13
13
  },
14
+ websiteId: {
15
+ type: Schema.Types.ObjectId,
16
+ ref: "Website",
17
+ default: null,
18
+ },
14
19
  oldId: { type: String, default: "" },
15
20
  webCategoryId: { type: Number, default: null },
16
21
  parentId: { type: Schema.Types.ObjectId, ref: "Category", default: null },
@@ -14,6 +14,11 @@ const CheckpointSchema = new Schema(
14
14
  ref: "Workspace",
15
15
  default: null,
16
16
  },
17
+ websiteId: {
18
+ type: Schema.Types.ObjectId,
19
+ ref: "Website",
20
+ default: null,
21
+ },
17
22
  },
18
23
  { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
19
24
  );
@@ -18,6 +18,11 @@ const CustomerSchema = new Schema(
18
18
  ref: "Workspace",
19
19
  default: null,
20
20
  },
21
+ websiteId: {
22
+ type: Schema.Types.ObjectId,
23
+ ref: "Website",
24
+ default: null,
25
+ },
21
26
  isBlackListed: { type: Boolean, default: false },
22
27
  createdBy: {
23
28
  type: Schema.Types.ObjectId,
@@ -24,6 +24,9 @@ const CustomerProfileSchema = new Schema(
24
24
  default: null,
25
25
  },
26
26
  body: {},
27
+ blocked: { type: Boolean, default: false }, // To Block User (Current Automation case)
28
+ hideComments: { type: Boolean, default: false }, // For Hide Comment Check
29
+ deleteComments: { type: Boolean, default: false }, // For Delete Comment Check
27
30
  },
28
31
  { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
29
32
  );
@@ -14,6 +14,11 @@ const LocationSchema = new Schema(
14
14
  oldId: { type: String, default: "" },
15
15
  webLocationId: { type: Number, default: null },
16
16
  bcLocationId: { type: String, default: "" },
17
+ websiteId: {
18
+ type: Schema.Types.ObjectId,
19
+ ref: "Website",
20
+ default: null,
21
+ },
17
22
  workspaceId: {
18
23
  type: Schema.Types.ObjectId,
19
24
  ref: "Workspace",
package/models/Order.js CHANGED
@@ -103,6 +103,11 @@ const OrderSchema = new Schema(
103
103
  ref: "Workspace",
104
104
  default: null,
105
105
  },
106
+ websiteId: {
107
+ type: Schema.Types.ObjectId,
108
+ ref: "Website",
109
+ default: null,
110
+ },
106
111
  orderType: { type: String, default: "" },
107
112
  barCode: { type: String, default: "" },
108
113
  quantity: { type: Number, default: 0 },
@@ -223,6 +228,7 @@ const markOrUnMarkOrderAsDuplicate = async (doc) => {
223
228
  const phoneRegex = getPhoneRegex(doc?.customerPhone);
224
229
  let orders = await Order.find({
225
230
  workspaceId: doc?.workspaceId,
231
+ websiteId: doc?.websiteId,
226
232
  isDeleted: false,
227
233
  statusType: "pending",
228
234
  customerPhone: phoneRegex,
@@ -20,6 +20,11 @@ const OrderPdfScehma = new mongoose.Schema(
20
20
  ref: "Workspace",
21
21
  default: null,
22
22
  },
23
+ websiteId: {
24
+ type: Schema.Types.ObjectId,
25
+ ref: "Website",
26
+ default: null,
27
+ },
23
28
  createdBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
24
29
  updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
25
30
  },
@@ -31,6 +31,11 @@ const OrderProductSchema = new Schema(
31
31
  ref: "Workspace",
32
32
  default: null,
33
33
  },
34
+ websiteId: {
35
+ type: Schema.Types.ObjectId,
36
+ ref: "Website",
37
+ default: null,
38
+ },
34
39
  },
35
40
  { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
36
41
  );
package/models/Product.js CHANGED
@@ -31,6 +31,11 @@ const ProductSchema = new Schema(
31
31
  type: Schema.Types.ObjectId,
32
32
  ref: "Workspace",
33
33
  default: null,
34
+ },
35
+ websiteId: {
36
+ type: Schema.Types.ObjectId,
37
+ ref: "Website",
38
+ default: null,
34
39
  },
35
40
  storeType: { type: String, default: "LOCAL" },
36
41
  isDeleted: { type: Boolean, default: false },
@@ -22,6 +22,11 @@ const ProductAttachmentSchema = new Schema(
22
22
  ref: "Workspace",
23
23
  default: null,
24
24
  },
25
+ websiteId: {
26
+ type: Schema.Types.ObjectId,
27
+ ref: "Website",
28
+ default: null,
29
+ },
25
30
  height: { type: Number, default: 0 },
26
31
  variantIds: [
27
32
  { type: Schema.Types.ObjectId, ref: "ProductVariant", default: null },
@@ -16,6 +16,11 @@ const ProductAttributeSchema = new mongoose.Schema(
16
16
  ref: "Workspace",
17
17
  default: null,
18
18
  },
19
+ websiteId: {
20
+ type: Schema.Types.ObjectId,
21
+ ref: "Website",
22
+ default: null,
23
+ },
19
24
  isDeleted: { type: Boolean, default: false },
20
25
  position: { type: String, default: "1" },
21
26
  },
@@ -11,6 +11,11 @@ const ProductCategorySchema = new Schema(
11
11
  ref: "Workspace",
12
12
  default: null,
13
13
  },
14
+ websiteId: {
15
+ type: Schema.Types.ObjectId,
16
+ ref: "Website",
17
+ default: null,
18
+ },
14
19
  createdBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
15
20
  updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
16
21
  isDeleted: { type: Boolean, default: false },
@@ -11,6 +11,11 @@ const ProductTagSchema = new Schema(
11
11
  ref: "Workspace",
12
12
  default: null,
13
13
  },
14
+ websiteId: {
15
+ type: Schema.Types.ObjectId,
16
+ ref: "Website",
17
+ default: null,
18
+ },
14
19
  createdBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
15
20
  updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
16
21
  isDeleted: { type: Boolean, default: false },
@@ -17,6 +17,11 @@ const ProductVariantSchema = new Schema(
17
17
  ref: "Workspace",
18
18
  default: null,
19
19
  },
20
+ websiteId: {
21
+ type: Schema.Types.ObjectId,
22
+ ref: "Website",
23
+ default: null,
24
+ },
20
25
  webVariantId: { type: Number, default: null },
21
26
  inventoryPolicy: { type: String, default: "deny" }, //deny,continue
22
27
  taxable: { type: Boolean, default: false },
package/models/Step.js CHANGED
@@ -45,6 +45,23 @@ const StepSchema = new Schema(
45
45
  default: null,
46
46
  },
47
47
  chatbotId: { type: Schema.Types.ObjectId, ref: "Chatbot", default: null },
48
+ complaintCreation: {
49
+ successTemplate: {
50
+ type: Schema.Types.ObjectId,
51
+ ref: "DescriptionTemplate",
52
+ default: null,
53
+ },
54
+ alreadyExistTemplate: {
55
+ type: Schema.Types.ObjectId,
56
+ ref: "DescriptionTemplate",
57
+ default: null,
58
+ },
59
+ columnId: {
60
+ type: Schema.Types.ObjectId,
61
+ ref: "Column",
62
+ default: null,
63
+ },
64
+ },
48
65
  },
49
66
  { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
50
67
  );
package/models/Tag.js CHANGED
@@ -8,6 +8,11 @@ const TagSchema = new Schema(
8
8
  ref: "Workspace",
9
9
  default: null,
10
10
  },
11
+ websiteId: {
12
+ type: Schema.Types.ObjectId,
13
+ ref: "Website",
14
+ default: null,
15
+ },
11
16
  oldId: { type: String, default: "" },
12
17
  webTagId: { type: Number, default: "" },
13
18
  createdBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
@@ -13,6 +13,11 @@ const VariantLocationSchema = new Schema(
13
13
  ref: "Workspace",
14
14
  default: null,
15
15
  },
16
+ websiteId: {
17
+ type: Schema.Types.ObjectId,
18
+ ref: "Website",
19
+ default: null,
20
+ },
16
21
  productId: { type: Schema.Types.ObjectId, ref: "Product", default: null },
17
22
  locationId: { type: Schema.Types.ObjectId, ref: "Location", default: null },
18
23
  webVariantLocationId: { type: Number, default: null },
package/models.js CHANGED
@@ -105,7 +105,6 @@ const WhatsappFlow = require("./models/WhatsappFlow");
105
105
  const Workflow = require("./models/Workflow");
106
106
  const Workspace = require("./models/Workspace");
107
107
  const Shop = require("./models/Shop");
108
- const Call = require("./models/Call");
109
108
 
110
109
  module.exports = {
111
110
  Activity,
@@ -215,5 +214,4 @@ module.exports = {
215
214
  Workflow,
216
215
  Workspace,
217
216
  Shop,
218
- Call,
219
217
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shuttlepro-shared",
3
- "version": "1.4.23",
3
+ "version": "1.4.24",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,390 +0,0 @@
1
- const Call = require("../../models/Call");
2
- const { Types } = require("mongoose");
3
-
4
- class CallRepository {
5
- /* ----------------- Internal Helpers ----------------- */
6
-
7
- _utcDayBounds(from, to) {
8
- const f = new Date(from);
9
- const t = new Date(to);
10
-
11
- return {
12
- from: new Date(
13
- Date.UTC(
14
- f.getUTCFullYear(),
15
- f.getUTCMonth(),
16
- f.getUTCDate(),
17
- 0,
18
- 0,
19
- 0,
20
- 0
21
- )
22
- ),
23
- to: new Date(
24
- Date.UTC(
25
- t.getUTCFullYear(),
26
- t.getUTCMonth(),
27
- t.getUTCDate(),
28
- 23,
29
- 59,
30
- 59,
31
- 999
32
- )
33
- ),
34
- };
35
- }
36
-
37
- _filterAllowedFields(data, allowed) {
38
- return Object.keys(data).reduce((acc, key) => {
39
- if (allowed.includes(key)) acc[key] = data[key];
40
- return acc;
41
- }, {});
42
- }
43
-
44
- _historyEntry({
45
- action,
46
- agentId = null,
47
- details = "",
48
- direction = "Agent Initiated",
49
- platformTimestamp = "",
50
- }) {
51
- return {
52
- timestamp: new Date(),
53
- platformTimestamp,
54
- action,
55
- agentId,
56
- details,
57
- direction,
58
- };
59
- }
60
-
61
- async _safeExec(promise, errorMsg, fallback = null) {
62
- try {
63
- return await promise;
64
- } catch (error) {
65
- console.error(`❌ ${errorMsg}`, error);
66
- return fallback;
67
- }
68
- }
69
-
70
- /* ----------------- Core CRUD ----------------- */
71
-
72
- createCall(callData) {
73
- const entry = this._historyEntry({
74
- action: "created",
75
- direction: "User Initiated",
76
- details: "Call created from webhook",
77
- platformTimestamp: callData.platformTimestamp || "",
78
- });
79
-
80
- return this._safeExec(
81
- Call.create({
82
- callId: callData.callId,
83
- callerName: callData.callerName,
84
- callerNumber: callData.callerNumber,
85
- receiver: callData.receiver || "",
86
- platformId: callData.platformId || null,
87
- workspaceId: callData.workspaceId || null,
88
- profileId: callData.profileId || null,
89
- status: callData.status || "pending",
90
- agentId: callData.agentId || null,
91
- queuePosition: callData.queuePosition || 0,
92
- whatsappSdp: callData.whatsappSdp || null,
93
- metadata: callData.metadata || {},
94
- tags: callData.tags || [],
95
- callHistory: [entry],
96
- }),
97
- `Failed to create call ${callData.callId}`
98
- );
99
- }
100
-
101
- getCall(callId) {
102
- return this._safeExec(
103
- Call.findOne({ callId }).lean().exec(),
104
- `Failed to get call ${callId}`
105
- );
106
- }
107
-
108
- endCall(callId, agentId = null, updateObj = {}, history = {}) {
109
- const entry = this._historyEntry({ ...history, agentId });
110
- return this._safeExec(
111
- Call.findOneAndUpdate(
112
- { callId },
113
- { $set: { ...updateObj }, $push: { callHistory: entry } },
114
- { new: true }
115
- ),
116
- `Failed to end call ${callId}`
117
- );
118
- }
119
-
120
- updateCall(callId, updateData) {
121
- const allowed = [
122
- "status",
123
- "agentId",
124
- "callerName",
125
- "callerNumber",
126
- "tags",
127
- "metadata",
128
- "platformId",
129
- "workspaceId",
130
- "profileId",
131
- "receiver",
132
- ];
133
- const filtered = this._filterAllowedFields(updateData, allowed);
134
-
135
- const updateOps = {
136
- $set: { ...filtered, "connectionMetadata.lastHeartbeat": new Date() },
137
- };
138
-
139
- if (updateData.status) {
140
- updateOps.$push = {
141
- callHistory: this._historyEntry({
142
- action: updateData.status,
143
- agentId: updateData.agentId,
144
- details:
145
- updateData.reason || `Status changed to ${updateData.status}`,
146
- }),
147
- };
148
- }
149
-
150
- return this._safeExec(
151
- Call.findOneAndUpdate({ callId }, updateOps, { new: true }),
152
- `Failed to update call ${callId}`
153
- );
154
- }
155
-
156
- assignCallToAgent(callId, agentId) {
157
- const entry = this._historyEntry({
158
- action: "answered",
159
- agentId,
160
- details: "Call answered by agent",
161
- });
162
-
163
- return this._safeExec(
164
- Call.findOneAndUpdate(
165
- { callId, status: { $in: ["pending", "hold"] } },
166
- {
167
- $set: {
168
- agentId,
169
- status: "active",
170
- startTime: new Date(),
171
- "connectionMetadata.lastHeartbeat": new Date(),
172
- },
173
- $push: { callHistory: entry },
174
- },
175
- { new: true }
176
- ),
177
- `Failed to assign call ${callId}`
178
- );
179
- }
180
-
181
- bulkUpdateCalls(operations) {
182
- const bulkOps = operations.map((op) => ({
183
- updateOne: {
184
- filter: { callId: op.callId },
185
- update: {
186
- $set: {
187
- ...op.updateData,
188
- "connectionMetadata.lastHeartbeat": new Date(),
189
- },
190
- $push: {
191
- callHistory: this._historyEntry({
192
- action: op.action || "bulk_update",
193
- agentId: op.agentId,
194
- details: op.details || "Bulk operation",
195
- }),
196
- },
197
- },
198
- },
199
- }));
200
-
201
- return this._safeExec(Call.bulkWrite(bulkOps), "Bulk update failed", []);
202
- }
203
-
204
- moveCallToHoldQueue(callId, reason = "Agent disconnected") {
205
- const entry = this._historyEntry({
206
- action: "disconnected",
207
- details: reason,
208
- });
209
-
210
- return this._safeExec(
211
- Call.findOneAndUpdate(
212
- { callId },
213
- {
214
- $set: {
215
- agentId: null,
216
- status: "hold",
217
- autoAccepted: true,
218
- "connectionMetadata.lastHeartbeat": new Date(),
219
- },
220
- $push: { callHistory: entry },
221
- },
222
- { new: true }
223
- ),
224
- `Failed to move call ${callId} to hold queue`
225
- );
226
- }
227
-
228
- /* ----------------- Queries ----------------- */
229
-
230
- async getCallsByWorkspace(
231
- { workspaceId, from, to },
232
- {
233
- filters = {},
234
- select = null,
235
- sortBy = { createdAt: -1 },
236
- limit,
237
- skip,
238
- } = {}
239
- ) {
240
- const { from: fromDate, to: toDate } = this._utcDayBounds(from, to);
241
-
242
- let query = Call.find({
243
- workspaceId,
244
- createdAt: { $gte: fromDate, $lte: toDate },
245
- ...filters,
246
- })
247
- .select(select || {})
248
- .sort(sortBy)
249
- .lean();
250
-
251
- if (limit) query = query.limit(limit);
252
- if (skip) query = query.skip(skip);
253
-
254
- return query.exec();
255
- }
256
-
257
- getCallsByAgent(
258
- agentId,
259
- {
260
- filters = {},
261
- select = null,
262
- sortBy = { createdAt: -1 },
263
- limit,
264
- skip,
265
- } = {}
266
- ) {
267
- let query = Call.find({ agentId, ...filters })
268
- .select(select || {})
269
- .sort(sortBy)
270
- .lean();
271
- if (limit) query = query.limit(limit);
272
- if (skip) query = query.skip(skip);
273
- return query.exec();
274
- }
275
-
276
- getCallsByReceiver(receiver, filters = {}) {
277
- return Call.find({ receiver, ...filters })
278
- .sort({ createdAt: -1 })
279
- .lean()
280
- .exec();
281
- }
282
-
283
- getActiveCalls(workspaceId) {
284
- return Call.find({ workspaceId, status: { $in: ["active", "hold"] } });
285
- }
286
-
287
- getPendingCalls(workspaceId) {
288
- return Call.find({ workspaceId, status: "pending" });
289
- }
290
-
291
- getStaleCalls(timeoutMinutes = 5) {
292
- const timeout = new Date(Date.now() - timeoutMinutes * 60 * 1000);
293
- return Call.find({
294
- status: { $in: ["active", "hold", "pending"] },
295
- "connectionMetadata.lastHeartbeat": { $lt: timeout },
296
- });
297
- }
298
-
299
- /* ----------------- Reporting ----------------- */
300
-
301
- async getAgentStats({ from, to }, filters = {}) {
302
- const { from: fromDate, to: toDate } = this._utcDayBounds(from, to);
303
-
304
- const match = { createdAt: { $gte: fromDate, $lte: toDate }, ...filters };
305
- if (match.workspaceId && typeof match.workspaceId === "string")
306
- match.workspaceId = new Types.ObjectId(match.workspaceId);
307
- if (match.agentId && typeof match.agentId === "string")
308
- match.agentId = new Types.ObjectId(match.agentId);
309
-
310
- const [stats] = await Call.aggregate([
311
- { $match: match },
312
- {
313
- $group: {
314
- _id: filters.agentId ? "$agentId" : null,
315
- totalCalls: { $sum: 1 },
316
- pending: { $sum: { $cond: [{ $eq: ["$status", "pending"] }, 1, 0] } },
317
- abandoned: {
318
- $sum: { $cond: [{ $eq: ["$status", "abandoned"] }, 1, 0] },
319
- },
320
- ended: { $sum: { $cond: [{ $eq: ["$status", "ended"] }, 1, 0] } },
321
- },
322
- },
323
- {
324
- $project: { _id: 0, totalCalls: 1, pending: 1, abandoned: 1, ended: 1 },
325
- },
326
- ]);
327
-
328
- return stats || { totalCalls: 0, pending: 0, abandoned: 0, ended: 0 };
329
- }
330
-
331
- getDailySummary(workspaceId, from, to, filters = {}, groupBy = null) {
332
- const matchStage = {
333
- workspaceId,
334
- createdAt: { $gte: from, $lte: to },
335
- ...filters,
336
- };
337
-
338
- const groupId = {
339
- day: { $dateToString: { format: "%Y-%m-%d", date: "$createdAt" } },
340
- };
341
- if (groupBy && ["agentId", "receiver", "platformId"].includes(groupBy))
342
- groupId[groupBy] = `$${groupBy}`;
343
-
344
- return Call.aggregate([
345
- { $match: matchStage },
346
- {
347
- $group: {
348
- _id: groupId,
349
- total: { $sum: 1 },
350
- ended: { $sum: { $cond: [{ $eq: ["$status", "ended"] }, 1, 0] } },
351
- abandoned: {
352
- $sum: { $cond: [{ $eq: ["$status", "abandoned"] }, 1, 0] },
353
- },
354
- avgDuration: { $avg: "$duration" },
355
- minDuration: { $min: "$duration" },
356
- maxDuration: { $max: "$duration" },
357
- },
358
- },
359
- {
360
- $project: {
361
- _id: 0,
362
- day: "$_id.day",
363
- groupBy: groupBy ? `$_id.${groupBy}` : null,
364
- total: 1,
365
- ended: 1,
366
- abandoned: 1,
367
- avgDuration: { $round: ["$avgDuration", 2] },
368
- minDuration: 1,
369
- maxDuration: 1,
370
- },
371
- },
372
- { $sort: { day: 1 } },
373
- ]);
374
- }
375
-
376
- getQueueStats(workspaceId) {
377
- return Call.aggregate([
378
- { $match: { workspaceId, status: "hold" } },
379
- {
380
- $group: {
381
- _id: "$receiver",
382
- count: { $sum: 1 },
383
- avgQueuePosition: { $avg: "$queuePosition" },
384
- },
385
- },
386
- ]);
387
- }
388
- }
389
-
390
- module.exports = new CallRepository();
package/models/Call.js DELETED
@@ -1,192 +0,0 @@
1
- const mongoose = require("mongoose");
2
- const { Schema } = mongoose;
3
-
4
- const callSchema = new mongoose.Schema(
5
- {
6
- workspaceId: { type: Schema.Types.ObjectId, ref: "Workspace" },
7
- callId: {
8
- type: String,
9
- required: true,
10
- unique: true,
11
- index: true,
12
- },
13
- callerName: {
14
- type: String,
15
- required: true,
16
- },
17
- callerNumber: {
18
- type: String,
19
- required: true,
20
- index: true,
21
- },
22
- profileId: { type: Schema.Types.ObjectId, ref: "Profile" },
23
- receiver: {
24
- type: String,
25
- default: "",
26
- },
27
- platformId: {
28
- type: Schema.Types.ObjectId,
29
- ref: "Integration",
30
- default: null,
31
- },
32
- type: { type: String, default: "inbound" },
33
- status: {
34
- type: String,
35
- enum: ["pending", "hold", "active", "ended", "disconnected", "abandoned"],
36
- default: "pending",
37
- index: true,
38
- },
39
- queuePosition: {
40
- type: Number,
41
- default: 0,
42
- },
43
- agentId: {
44
- type: String,
45
- default: null,
46
- index: true,
47
- },
48
- startTime: {
49
- type: Date,
50
- default: null,
51
- },
52
- endTime: {
53
- type: Date,
54
- default: null,
55
- },
56
- duration: {
57
- type: Number,
58
- default: 0,
59
- },
60
- whatsappSdp: {
61
- type: String,
62
- default: null,
63
- },
64
- browserSdp: {
65
- type: String,
66
- default: null,
67
- },
68
- callHistory: [
69
- {
70
- action: {
71
- type: String,
72
- enum: [
73
- "created",
74
- "answered",
75
- "held",
76
- "unheld",
77
- "transferred",
78
- "ended",
79
- "disconnected",
80
- "reconnected",
81
- "abandoned",
82
- ],
83
- },
84
- direction: String, // USER INITIATED // AGENT INITIATED
85
- agentId: String, // agent id
86
- details: String, // "Status changed to hold"
87
- startTime: String, // platform start time
88
- endTime: String, // platform endTime time
89
- duration: Number, // platform call duration
90
- timestamp: {
91
- // system timestamp
92
- type: Date,
93
- default: Date.now,
94
- },
95
- platformTimestamp: String, // platformtimestamp
96
- },
97
- ],
98
- metadata: {},
99
- tags: [],
100
- connectionMetadata: {
101
- browserConnectionState: {
102
- type: String,
103
- default: "new",
104
- },
105
- whatsappConnectionState: {
106
- type: String,
107
- default: "new",
108
- },
109
- lastHeartbeat: {
110
- type: Date,
111
- default: Date.now,
112
- },
113
- },
114
- rating: {
115
- type: Number,
116
- default: 0,
117
- },
118
- },
119
- {
120
- timestamps: true,
121
- collection: "calls",
122
- }
123
- );
124
-
125
- // Indexes for performance
126
- callSchema.index({ status: 1, createdAt: 1 });
127
- callSchema.index({ agentId: 1, status: 1 });
128
- callSchema.index({ callerNumber: 1, createdAt: -1 });
129
- callSchema.index({ isOnHold: 1, status: 1 });
130
-
131
- // Virtual for call duration calculation
132
- callSchema.virtual("currentDuration").get(function () {
133
- if (this.startTime) {
134
- const endTime = this.endTime || new Date();
135
- return Math.floor((endTime - this.startTime) / 1000);
136
- }
137
- return 0;
138
- });
139
-
140
- // Methods
141
- callSchema.methods.addToHistory = function (
142
- action,
143
- agentId = null,
144
- details = null
145
- ) {
146
- this.callHistory.push({
147
- action,
148
- agentId,
149
- details,
150
- timestamp: new Date(),
151
- });
152
- };
153
-
154
- callSchema.methods.updateHeartbeat = function () {
155
- this.connectionMetadata.lastHeartbeat = new Date();
156
- };
157
-
158
- callSchema.methods.isStale = function (timeoutMinutes = 5) {
159
- const timeout = timeoutMinutes * 60 * 1000; // Convert to milliseconds
160
- return new Date() - this.connectionMetadata.lastHeartbeat > timeout;
161
- };
162
-
163
- // Static methods
164
- callSchema.statics.getActiveCallsForAgent = function (agentId) {
165
- return this.find({
166
- agentId,
167
- status: { $in: ["active", "hold"] },
168
- }).sort({ createdAt: 1 });
169
- };
170
-
171
- callSchema.statics.getHoldQueue = function () {
172
- return this.find({
173
- status: "hold",
174
- agentId: null,
175
- }).sort({ createdAt: 1 });
176
- };
177
-
178
- callSchema.statics.getPendingCalls = function () {
179
- return this.find({
180
- status: "pending",
181
- }).sort({ createdAt: 1 });
182
- };
183
-
184
- callSchema.statics.getStaleConnections = function (timeoutMinutes = 5) {
185
- const timeout = new Date(Date.now() - timeoutMinutes * 60 * 1000);
186
- return this.find({
187
- status: { $in: ["active", "hold", "pending"] },
188
- "connectionMetadata.lastHeartbeat": { $lt: timeout },
189
- });
190
- };
191
-
192
- module.exports = mongoose.model("Call", callSchema);