shuttlepro-shared 1.1.97 → 1.1.99

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.
Files changed (43) hide show
  1. package/common/repositories/descriptionTemplates.repository.js +186 -0
  2. package/common/repositories/index.js +12 -0
  3. package/common/repositories/integration.repository.js +197 -0
  4. package/common/repositories/label.repository.js +85 -0
  5. package/common/repositories/shipper.repository.js +77 -0
  6. package/common/repositories/workspace.repository.js +95 -0
  7. package/config/bull.js +78 -0
  8. package/config/config.js +14 -0
  9. package/config/database.js +7 -0
  10. package/config/index.js +13 -0
  11. package/config/redis.js +189 -0
  12. package/config/socket.js +172 -0
  13. package/constants/index.js +15 -0
  14. package/index.js +8 -0
  15. package/models/AgentActivity.js +189 -0
  16. package/models/Assignment.js +23 -0
  17. package/models/BusinessDistribution.js +23 -0
  18. package/models/Card.js +144 -0
  19. package/models/CardComments.js +33 -0
  20. package/models/Chatbot.js +16 -0
  21. package/models/Checkpoint.js +50 -0
  22. package/models/City.js +17 -0
  23. package/models/Column.js +28 -0
  24. package/models/Conversation.js +84 -0
  25. package/models/Customer.js +35 -0
  26. package/models/DescriptionTemplate.js +22 -0
  27. package/models/Integration.js +53 -0
  28. package/models/Label.js +42 -0
  29. package/models/Message.js +47 -0
  30. package/models/Order.js +131 -0
  31. package/models/OrderProduct.js +37 -0
  32. package/models/Profile.js +127 -0
  33. package/models/Report.js +27 -0
  34. package/models/Shipper.js +52 -0
  35. package/models/Status.js +58 -0
  36. package/models/StatusType.js +10 -0
  37. package/models/Step.js +50 -0
  38. package/models/Type.js +25 -0
  39. package/models/Workspace.js +190 -0
  40. package/models.js +50 -0
  41. package/package.json +13 -2
  42. package/utils/decorator-factory.js +264 -0
  43. package/utils/logger.js +41 -0
package/config/bull.js ADDED
@@ -0,0 +1,78 @@
1
+ const Queue = require("bull");
2
+ const config = require("./config");
3
+
4
+ /**
5
+ * Create a new queue.
6
+ * @param {string} queueName - The name of the queue.
7
+ * @param {Object} [redisConfig=config.redis] - Redis configuration.
8
+ * @returns {Object} - A collection of queue-related functions.
9
+ */
10
+ const createQueue = (queueName, redisConfig = config.redis) => {
11
+ const queue = new Queue(queueName, { redis: redisConfig });
12
+
13
+ // Attach event listeners for logging
14
+ queue.on("completed", (job) => console.log(`Job ${job.id} completed.`));
15
+ queue.on("failed", (job, err) => console.error(`Job ${job.id} failed:`, err));
16
+
17
+ /**
18
+ * Add a job to the queue.
19
+ * @param {Object} data - Data to be processed by the job.
20
+ * @param {Object} [options] - Bull job options (e.g., delay, attempts).
21
+ * @returns {Promise<Job>} - The created job.
22
+ */
23
+ const addJob = async (
24
+ data,
25
+ options = {
26
+ attempts: 3,
27
+ removeOnComplete: true,
28
+ }
29
+ ) => {
30
+ return await queue.add(data, options);
31
+ };
32
+
33
+ /**
34
+ * Process jobs in the queue.
35
+ * @param {Function} processor - The function to process each job.
36
+ */
37
+ const processJobs = (processor) => {
38
+ queue.process(processor);
39
+ };
40
+
41
+ /**
42
+ * Close the queue connection.
43
+ * @returns {Promise<void>}
44
+ */
45
+ const closeConnection = async () => {
46
+ await queue.close();
47
+ console.log(`Queue "${queue.name}" connection closed.`);
48
+ };
49
+
50
+ /**
51
+ * Pause the queue.
52
+ * @returns {Promise<void>}
53
+ */
54
+ const pauseQueue = async () => {
55
+ await queue.pause();
56
+ console.log(`Queue "${queue.name}" paused.`);
57
+ };
58
+
59
+ /**
60
+ * Resume the queue.
61
+ * @returns {Promise<void>}
62
+ */
63
+ const resumeQueue = async () => {
64
+ await queue.resume();
65
+ console.log(`Queue "${queue.name}" resumed.`);
66
+ };
67
+
68
+ // Return the functional interface
69
+ return {
70
+ addJob,
71
+ processJobs,
72
+ closeConnection,
73
+ pauseQueue,
74
+ resumeQueue,
75
+ };
76
+ };
77
+
78
+ module.exports = { createQueue };
@@ -0,0 +1,14 @@
1
+ const ALLOWED_URLS = process.env.ALLOWED_ORIGIN.split(",");
2
+ const WEBHOOK_API_KEY = process.env.API_KEY || process.env.WEBHOOK_API_KEY;
3
+ const mode = process.env.MODE || "production";
4
+
5
+ const redis = {
6
+ url: process.env.REDIS_URI,
7
+ };
8
+
9
+ module.exports = {
10
+ WEBHOOK_API_KEY,
11
+ ALLOWED_URLS,
12
+ mode,
13
+ redis,
14
+ };
@@ -0,0 +1,7 @@
1
+ /* eslint-disable no-undef */
2
+ module.exports = {
3
+ url:
4
+ process.env.NODE_ENV === "test"
5
+ ? process.env.TEST_DB_URL
6
+ : process.env.DB_URL,
7
+ };
@@ -0,0 +1,13 @@
1
+ const bullConfig = require("./bull");
2
+ const redisConfig = require("./redis");
3
+ const config = require("./config");
4
+ const databaseConfig = require("./database");
5
+ const socketConfig = require("./socket");
6
+
7
+ module.exports = {
8
+ bullConfig,
9
+ redisConfig,
10
+ config,
11
+ databaseConfig,
12
+ socketConfig,
13
+ };
@@ -0,0 +1,189 @@
1
+ const { createClient } = require("redis");
2
+ require("dotenv").config();
3
+
4
+ const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";
5
+
6
+ // ✅ Create main Redis client
7
+ const client = createClient({
8
+ url: REDIS_URL,
9
+ socket: { reconnectStrategy: (retries) => Math.min(retries * 50, 1000) }, // Exponential backoff
10
+ });
11
+
12
+ // ✅ Create separate Pub/Sub clients
13
+ const publisher = client.duplicate();
14
+ const subscriber = client.duplicate();
15
+
16
+ // ✅ Handle Redis Connection Events
17
+ client.on("error", (err) => console.error("❌ Redis Error:", err));
18
+ client.on("connect", () => console.log("✅ Redis Connected"));
19
+ client.on("ready", () => console.log("🚀 Redis Ready to use"));
20
+ client.on("end", () => console.log("❗ Redis Connection Closed"));
21
+
22
+ // ✅ Ensure Redis is connected before performing operations
23
+ const connectRedis = async () => {
24
+ if (!client.isOpen) {
25
+ try {
26
+ await client.connect();
27
+ await publisher.connect();
28
+ await subscriber.connect();
29
+ } catch (err) {
30
+ console.error("❌ Failed to connect to Redis:", err);
31
+ }
32
+ }
33
+ await client.configSet("notify-keyspace-events", "Ex");
34
+ };
35
+
36
+ /**
37
+ * ✅ Publish Data to a Channel
38
+ * @param {string} channel - The Redis Pub/Sub channel
39
+ * @param {any} message - The message to send
40
+ */
41
+ const publishToChannel = async (channel, message) => {
42
+ try {
43
+ await connectRedis();
44
+ await publisher.publish(channel, JSON.stringify(message));
45
+ console.log(`📢 Message published to channel: ${channel}`);
46
+ } catch (err) {
47
+ console.error("❌ Error publishing message:", err);
48
+ }
49
+ };
50
+
51
+ /**
52
+ * ✅ Subscribe & Listen for Messages
53
+ * @param {string} channel - The Redis Pub/Sub channel
54
+ * @param {function} callback - Callback function to handle messages
55
+ */
56
+ const subscribeToChannel = async (channel, callback) => {
57
+ try {
58
+ await connectRedis();
59
+ await subscriber.subscribe(channel, (message) => {
60
+ console.log(`📥 Message received on channel: ${channel}`);
61
+ callback(JSON.parse(message));
62
+ });
63
+ } catch (err) {
64
+ console.error("❌ Error subscribing to channel:", err);
65
+ }
66
+ };
67
+
68
+ /**
69
+ * ✅ Set Data in Redis (Supports String & Hash)
70
+ * @param {string} key - The Redis key
71
+ * @param {any} value - The value to store
72
+ * @param {string|null} field - Optional field for Hash storage
73
+ * @param {number} expiryInSeconds - Expiry time in seconds (default: 3600)
74
+ */
75
+ const setRedisData = async (
76
+ key,
77
+ value,
78
+ field = null,
79
+ expiryInSeconds = 3600
80
+ ) => {
81
+ try {
82
+ await connectRedis();
83
+ let result;
84
+ if (field) {
85
+ result = await client.hSet(key, field, JSON.stringify(value));
86
+ } else {
87
+ result = await client.set(key, JSON.stringify(value), {
88
+ EX: expiryInSeconds,
89
+ });
90
+ }
91
+ return result;
92
+ } catch (err) {
93
+ console.error("❌ Error setting Redis data:", err);
94
+ return null;
95
+ }
96
+ };
97
+ const addDataToRedisSet = async (key, value) => {
98
+ try {
99
+ await connectRedis();
100
+ await redisClient.sAdd(key, value);
101
+ return true;
102
+ } catch (err) {
103
+ return false;
104
+ }
105
+ };
106
+ const removeDataFromRedisSet = async (key, value) => {
107
+ try {
108
+ await connectRedis();
109
+ await redisClient.sRem(key, value);
110
+ return true;
111
+ } catch (err) {
112
+ return false;
113
+ }
114
+ };
115
+ const isMemberOfRedisSet = async (key, value) => {
116
+ try {
117
+ await connectRedis();
118
+ await redisClient.sIsMember(key, value);
119
+ } catch (err) {
120
+ return false;
121
+ }
122
+ };
123
+ /**
124
+ * ✅ Get Data from Redis (Supports String & Hash)
125
+ * @param {string} key - The Redis key
126
+ * @param {string|null} field - Optional field for Hash retrieval
127
+ */
128
+ const getRedisData = async (key, field = null) => {
129
+ try {
130
+ await connectRedis();
131
+ let result = field ? await client.hGet(key, field) : await client.get(key);
132
+ return result ? JSON.parse(result) : null;
133
+ } catch (err) {
134
+ console.error("❌ Error getting Redis data:", err);
135
+ return null;
136
+ }
137
+ };
138
+
139
+ /**
140
+ * ✅ Delete Data from Redis (Supports String & Hash)
141
+ * @param {string} key - The Redis key
142
+ * @param {string|null} field - Optional field for Hash deletion
143
+ */
144
+ const deleteRedisData = async (key, field = null) => {
145
+ try {
146
+ await connectRedis();
147
+ return field ? await client.hDel(key, field) : await client.del(key);
148
+ } catch (err) {
149
+ console.error("❌ Error deleting Redis data:", err);
150
+ return null;
151
+ }
152
+ };
153
+
154
+ /**
155
+ * ✅ Graceful Shutdown Handling
156
+ */
157
+ const closeClients = async () => {
158
+ if (client.isOpen) await client.quit();
159
+ if (publisher.isOpen) await publisher.quit();
160
+ if (subscriber.isOpen) await subscriber.quit();
161
+ console.log("❗ Redis Clients Disconnected Gracefully");
162
+ };
163
+
164
+ // ✅ Ensure Redis disconnects properly on process exit
165
+ process.on("exit", closeClients);
166
+ process.on("SIGINT", async () => {
167
+ await closeClients();
168
+ process.exit();
169
+ });
170
+ process.on("SIGTERM", async () => {
171
+ await closeClients();
172
+ process.exit();
173
+ });
174
+
175
+ // ✅ Export Functions
176
+ module.exports = {
177
+ client,
178
+ publisher,
179
+ subscriber,
180
+ setRedisData,
181
+ getRedisData,
182
+ deleteRedisData,
183
+ publishToChannel,
184
+ subscribeToChannel,
185
+ connectRedis,
186
+ addDataToRedisSet,
187
+ removeDataFromRedisSet,
188
+ isMemberOfRedisSet,
189
+ };
@@ -0,0 +1,172 @@
1
+ const { Server } = require("socket.io");
2
+ const { publisher, subscriber, connectRedis } = require("./redis");
3
+ require("dotenv").config();
4
+
5
+ let io;
6
+ const namespaceSockets = {}; // Store different namespace sockets
7
+
8
+ /**
9
+ * Initialize Socket.IO server with namespaces
10
+ * @param {http.Server} server - HTTP server instance
11
+ * @param {string} namespaceParam - Socket namespace name (default: "conversation")
12
+ * @param {string} redisChannel - Redis channel for this namespace (default: "socket_events")
13
+ * @returns {SocketIO.Namespace} - The initialized namespace
14
+ */
15
+ const initializeSocket = async (
16
+ server,
17
+ namespaceParam = "conversation",
18
+ redisChannel = "socket_events"
19
+ ) => {
20
+ // Initialize Socket.IO server if not already done
21
+ if (!io) {
22
+ io = new Server(server, {
23
+ cors: {
24
+ origin: "*",
25
+ methods: ["GET", "POST"],
26
+ },
27
+ });
28
+ console.log("✅ Socket.IO server initialized");
29
+ }
30
+
31
+ // Return existing namespace if already initialized
32
+ if (namespaceSockets[namespaceParam]) {
33
+ console.log(`📢 Using existing namespace: /${namespaceParam}`);
34
+ return namespaceSockets[namespaceParam];
35
+ }
36
+
37
+ // Create and store new namespace
38
+ const namespace = io.of(`/${namespaceParam}`);
39
+ namespaceSockets[namespaceParam] = namespace;
40
+ console.log(`🚀 Created new namespace: /${namespaceParam}`);
41
+
42
+ // Ensure Redis is connected
43
+ await connectRedis();
44
+
45
+ // Create a unique Redis channel for this namespace
46
+ const nsRedisChannel = `${redisChannel}_${namespaceParam}`;
47
+
48
+ // Subscribe to Redis events for this namespace
49
+ await subscriber.subscribe(nsRedisChannel, (messageStr) => {
50
+ try {
51
+ const message = JSON.parse(messageStr);
52
+ const { workspaceId, event, data } = message;
53
+
54
+ console.log(`📥 Redis message received on ${nsRedisChannel}:`, {
55
+ workspaceId,
56
+ event,
57
+ });
58
+
59
+ // Emit to specific workspace room in this namespace
60
+ if (workspaceId) {
61
+ namespace.to(workspaceId).emit(event, data);
62
+ console.log(
63
+ `📢 Event ${event} emitted to workspace ${workspaceId} in /${namespaceParam}`
64
+ );
65
+ } else {
66
+ // Broadcast to all clients in this namespace if no workspaceId specified
67
+ namespace.emit(event, data);
68
+ console.log(
69
+ `📢 Event ${event} broadcasted to all clients in /${namespaceParam}`
70
+ );
71
+ }
72
+ } catch (err) {
73
+ console.error(
74
+ `❌ Error handling Redis message on ${nsRedisChannel}:`,
75
+ err
76
+ );
77
+ }
78
+ });
79
+
80
+ // Handle new socket connections to this namespace
81
+ namespace.on("connection", (socket) => {
82
+ const { workspaceId } = socket.handshake.query;
83
+
84
+ if (!workspaceId) {
85
+ console.warn(`⚠️ Connection rejected: No workspaceId provided`);
86
+ socket.disconnect(true);
87
+ return;
88
+ }
89
+
90
+ console.log(
91
+ `✅ Client connected: ${socket.id} (Workspace: ${workspaceId}) in /${namespaceParam}`
92
+ );
93
+
94
+ // Join workspace room
95
+ socket.join(workspaceId);
96
+
97
+ // Send connection confirmation to client
98
+ socket.emit("connected", {
99
+ status: "connected",
100
+ socketId: socket.id,
101
+ namespace: namespaceParam,
102
+ workspaceId,
103
+ });
104
+
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
+ });
110
+
111
+ // Handle disconnect
112
+ socket.on("disconnect", (reason) => {
113
+ console.log(
114
+ `❌ Client disconnected: ${socket.id} (Workspace: ${workspaceId}, Reason: ${reason})`
115
+ );
116
+ socket.leave(workspaceId);
117
+ });
118
+
119
+ // Handle errors
120
+ socket.on("error", (error) => {
121
+ console.error(`❌ Socket error for ${socket.id}:`, error);
122
+ });
123
+ });
124
+
125
+ console.log(
126
+ `✅ Namespace /${namespaceParam} initialized and listening on Redis channel: ${nsRedisChannel}`
127
+ );
128
+ return namespace;
129
+ };
130
+
131
+ /**
132
+ * Send event to clients via Redis pub/sub
133
+ * @param {object} params - Event parameters
134
+ * @param {string} params.workspaceId - Target workspace ID
135
+ * @param {string} params.event - Event name
136
+ * @param {any} params.data - Event data payload
137
+ * @param {string} [params.namespace="conversation"] - Target namespace
138
+ * @param {string} [params.redisChannel="socket_events"] - Base Redis channel
139
+ * @returns {Promise<void>}
140
+ */
141
+ const sendEventToServer = async ({
142
+ workspaceId,
143
+ event,
144
+ data,
145
+ namespace = "conversation",
146
+ redisChannel = "socket_events",
147
+ }) => {
148
+ try {
149
+ // Create namespace-specific Redis channel
150
+ const nsRedisChannel = `${redisChannel}_${namespace}`;
151
+
152
+ // Ensure Redis is connected
153
+ await connectRedis();
154
+
155
+ // Create message payload
156
+ const message = JSON.stringify({ workspaceId, event, data });
157
+
158
+ // Publish to Redis
159
+ await publisher.publish(nsRedisChannel, message);
160
+
161
+ console.log(
162
+ `📢 Event published to Redis channel ${nsRedisChannel}: ${event} (Workspace: ${workspaceId})`
163
+ );
164
+
165
+ return true;
166
+ } catch (err) {
167
+ console.error("❌ Error publishing to Redis:", err);
168
+ throw err;
169
+ }
170
+ };
171
+
172
+ module.exports = { initializeSocket, sendEventToServer };
@@ -23,3 +23,18 @@ exports.GenericMessages = {
23
23
  URL_FETCH_SUCCESS: "Url Fetched successfully!",
24
24
  WORKSPACE_NOT_FOUND: "Workspace not found!",
25
25
  };
26
+
27
+ exports.DYNAMIC_FIELD_TYPES = [
28
+ "text",
29
+ "autocomplete",
30
+ "radio",
31
+ "text area",
32
+ "number",
33
+ "date",
34
+ "time",
35
+ "email",
36
+ "range",
37
+ "url",
38
+ "colour",
39
+ "file",
40
+ ];
package/index.js CHANGED
@@ -1,7 +1,15 @@
1
1
  const sharedModels = require("./models");
2
2
  const sharedFunctions = require("./functions");
3
+ const logger = require("./utils/logger");
4
+ const shuttlePro = require("./utils/decorator-factory");
5
+ const configs = require("./config");
6
+ const repositories = require("./common/repositories");
3
7
 
4
8
  module.exports = {
5
9
  sharedModels,
6
10
  sharedFunctions,
11
+ logger,
12
+ shuttlePro,
13
+ configs,
14
+ repositories,
7
15
  };
@@ -0,0 +1,189 @@
1
+ const mongoose = require("mongoose");
2
+ const { Schema } = mongoose;
3
+
4
+ const breakSchema = new mongoose.Schema({
5
+ refId: { type: String, required: true },
6
+ breakId: { type: String, required: false },
7
+ startTime: { type: Date, required: true },
8
+ endTime: { type: Date, required: false },
9
+ });
10
+
11
+ const responseSchema = new mongoose.Schema({
12
+ queryMessageId: { type: [String], default: [] },
13
+ responseMessageId: { type: [String], default: [] },
14
+ responseTime: { type: Number, required: false },
15
+ accuracy: { type: Number, required: false, default: 10 },
16
+ });
17
+
18
+ const activitySchema = new mongoose.Schema({
19
+ refId: { type: String, required: true },
20
+ type: {
21
+ type: String,
22
+ required: true,
23
+ default: "other",
24
+ enum: ["conversation", "ticket", "order", "other"],
25
+ },
26
+ platformName: { type: String, required: false },
27
+ platformType: { type: String, required: false },
28
+ customerName: { type: String, default: "", required: false },
29
+ conversationId: { type: String, default: "", required: false },
30
+ profileId: { type: String, required: false },
31
+ conversationType: {
32
+ type: String,
33
+ enum: ["email", "comment", "message", ""],
34
+ default: "",
35
+ required: false,
36
+ },
37
+ status: { type: String, required: false, default: "unassigned" },
38
+ labels: [{ type: Schema.Types.ObjectId, ref: "Label" }],
39
+ platformId: { type: String, required: false },
40
+ assignTime: { type: Date, required: false },
41
+ qaScore: { type: Number, required: false, default: 10 },
42
+ startTime: { type: String, required: false },
43
+ endTime: { type: String, required: false },
44
+ resolutionTime: { type: String, required: false },
45
+ responses: { type: [responseSchema], default: [] },
46
+ remarks: { type: String, required: false },
47
+ avgAccuracy: { type: Number, required: false, default: 10 },
48
+ });
49
+
50
+ const totalActivitySchema = new mongoose.Schema({
51
+ type: { type: String, required: true },
52
+ platformType: { type: String, required: false },
53
+ count: { type: Number, required: true },
54
+ });
55
+
56
+ const agentActivitySchema = new mongoose.Schema(
57
+ {
58
+ _id: {
59
+ type: mongoose.Schema.Types.ObjectId,
60
+ default: mongoose.Types.ObjectId,
61
+ },
62
+ agentId: { type: String, required: true },
63
+ agentRole: { type: String, default: "" },
64
+ agentName: { type: String, required: false },
65
+ agentShift: { type: Object, default: {}, required: false },
66
+ workspaceId: {
67
+ type: mongoose.Schema.Types.ObjectId,
68
+ required: true,
69
+ ref: "Workspace",
70
+ },
71
+ date: { type: String, required: true },
72
+ lateMinutes: {
73
+ type: Number,
74
+ default: 0,
75
+ },
76
+ earlyLeaveMinutes: {
77
+ type: Number,
78
+ default: 0,
79
+ },
80
+ shiftStartTime: { type: [Date], default: [] },
81
+ shiftEndTime: { type: [Date], default: [] },
82
+ shifts: [
83
+ {
84
+ shiftStartTime: { type: Date, required: true, default: null },
85
+ shiftEndTime: { type: Date, required: false, default: null },
86
+ },
87
+ ],
88
+ isLate: { type: Boolean, required: true, default: false },
89
+ isEarlyLeave: { type: Boolean, required: true, default: false },
90
+ breaks: { type: [breakSchema], default: [] },
91
+ activities: { type: [activitySchema], default: [] },
92
+ totalPsc: { type: Number, required: false, default: 0 },
93
+ totalQa: { type: Number, required: false, default: 0 },
94
+ weightedQa: { type: Number, required: false, default: 0 },
95
+ weightedPsc: { type: Number, required: false, default: 0 },
96
+ kpiScore: { type: Number, required: false, default: 0 },
97
+ totalActivities: { type: [totalActivitySchema], default: [] },
98
+ totalWorkTime: { type: Number, required: false, default: 0 },
99
+ totalBreakTime: { type: Number, required: false, default: 0 },
100
+ totalIdleTime: { type: Number, required: false, default: 0 },
101
+ },
102
+ { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
103
+ );
104
+
105
+ agentActivitySchema.index({ workspaceId: 1 });
106
+ agentActivitySchema.index({ date: 1 });
107
+ agentActivitySchema.index({ agentId: 1 });
108
+ agentActivitySchema.index({ "activities.platformId": 1 });
109
+ agentActivitySchema.index({ "activities.labels": 1 });
110
+ agentActivitySchema.index({ workspaceId: 1, date: 1, agentId: 1 });
111
+ agentActivitySchema.index({
112
+ workspaceId: 1,
113
+ date: 1,
114
+ agentId: 1,
115
+ "activities.platformId": 1,
116
+ "activities.labels": 1,
117
+ });
118
+ agentActivitySchema.index({
119
+ workspaceId: 1,
120
+ date: 1,
121
+ agentId: 1,
122
+ "activities.platformId": 1,
123
+ });
124
+ agentActivitySchema.index({
125
+ workspaceId: 1,
126
+ date: 1,
127
+ agentId: 1,
128
+ "activities.labels": 1,
129
+ });
130
+ agentActivitySchema.index(
131
+ { "activities.platformId": 1, "activities.labels": 1 },
132
+ {
133
+ partialFilterExpression: {
134
+ "activities.platformId": { $exists: true },
135
+ "activities.labels": { $exists: true },
136
+ },
137
+ }
138
+ );
139
+ const AgentActivity = mongoose.model("AgentActivity", agentActivitySchema);
140
+
141
+ const findOneAndUpdateAgentActivity = async (
142
+ findQuery = {},
143
+ updateQuery = null
144
+ ) => {
145
+ try {
146
+ if (!findQuery || Object.values(findQuery).length === 0) {
147
+ console.log("Find Query is required.");
148
+ return null;
149
+ }
150
+
151
+ const agentActivityForActivitiesUpdated = await AgentActivity.findOne(
152
+ findQuery
153
+ ).sort({ createdAt: -1 });
154
+
155
+ if (!agentActivityForActivitiesUpdated) {
156
+ console.log("No matching record found.");
157
+ return null;
158
+ }
159
+
160
+ const bsonSize = require("bson").calculateObjectSize(
161
+ agentActivityForActivitiesUpdated
162
+ );
163
+
164
+ if (bsonSize <= 1000000) {
165
+ return await AgentActivity.findOneAndUpdate(findQuery, updateQuery);
166
+ } else {
167
+ const { breaks, activities, totalActivities, ...restData } =
168
+ agentActivityForActivitiesUpdated.toObject();
169
+
170
+ const newAgentActivity = await AgentActivity.create({
171
+ ...restData,
172
+ });
173
+
174
+ const agentActivityRecord = await AgentActivity.findOneAndUpdate(
175
+ { ...findQuery, _id: newAgentActivity._id },
176
+ updateQuery
177
+ );
178
+
179
+ return agentActivityRecord;
180
+ }
181
+ } catch (err) {
182
+ console.error("Error:", err);
183
+ return null;
184
+ }
185
+ };
186
+ module.exports = {
187
+ AgentActivity,
188
+ findOneAndUpdateAgentActivity,
189
+ };