shuttlepro-shared 1.3.18 → 1.3.20
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/common/repositories/customerTimeline.repository.js +78 -0
- package/common/repositories/index.js +2 -0
- package/config/redis.js +73 -72
- package/middlewares/checkPermission/index.js +36 -23
- package/models/Customer.js +0 -1
- package/models/CustomerProfile.js +3 -0
- package/models/CustomerTimeline.js +28 -0
- package/models/Order.js +18 -8
- package/models/Workspace.js +0 -10
- package/package.json +1 -1
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const CustomerTimeline = require("../../models/CustomerTimeline");
|
|
2
|
+
|
|
3
|
+
const createCustomerTimeline = async (data) => {
|
|
4
|
+
const newTimeline = new CustomerTimeline(data);
|
|
5
|
+
return await newTimeline.save();
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const findCustomerTimelinesByWorkspaceId = async (workspaceId) => {
|
|
9
|
+
return await CustomerTimeline.find({ workspaceId }).lean().exec();
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const findCustomerTimelinesByWorkspace = async (workspaceId, filter = {}) => {
|
|
13
|
+
return await CustomerTimeline.find({ workspaceId, ...filter })
|
|
14
|
+
.lean()
|
|
15
|
+
.exec();
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const findCustomerTimelineByFilter = async (filter = {}, bodyFilter = {}) => {
|
|
19
|
+
const all = await CustomerTimeline.find(filter).lean().exec();
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
all.find((item) =>
|
|
23
|
+
Object.entries(bodyFilter).every(
|
|
24
|
+
([key, value]) => item.body?.[key] === value
|
|
25
|
+
)
|
|
26
|
+
) || null
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const findAllCustomerTimelinesByFilter = async (
|
|
31
|
+
filter = {},
|
|
32
|
+
bodyFilter = {}
|
|
33
|
+
) => {
|
|
34
|
+
const all = await CustomerTimeline.find(filter).lean().exec();
|
|
35
|
+
return all.filter((item) =>
|
|
36
|
+
Object.entries(bodyFilter).every(
|
|
37
|
+
([key, value]) => item.body?.[key] === value
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const updateCustomerTimeline = async (id, data) => {
|
|
43
|
+
return await CustomerTimeline.findByIdAndUpdate(id, data, {
|
|
44
|
+
new: true,
|
|
45
|
+
}).exec();
|
|
46
|
+
};
|
|
47
|
+
const updateManyCustomerTimelinesByFilter = async (filter = {}, data = {}) => {
|
|
48
|
+
await CustomerTimeline.updateMany(filter, data).exec();
|
|
49
|
+
const updatedDocs = await CustomerTimeline.find(filter).lean().exec();
|
|
50
|
+
return updatedDocs;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const updateCustomerTimelineByFilter = async (filter, data) => {
|
|
54
|
+
return await CustomerTimeline.findOneAndUpdate(filter, data, {
|
|
55
|
+
new: true,
|
|
56
|
+
}).exec();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const deleteCustomerTimeline = async (id) => {
|
|
60
|
+
return await CustomerTimeline.findByIdAndDelete(id).exec();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const deleteAllCustomerTimelinesByWorkspace = async (workspaceId) => {
|
|
64
|
+
return await CustomerTimeline.deleteMany({ workspaceId }).exec();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
createCustomerTimeline,
|
|
69
|
+
findCustomerTimelinesByWorkspaceId,
|
|
70
|
+
findCustomerTimelinesByWorkspace,
|
|
71
|
+
findCustomerTimelineByFilter,
|
|
72
|
+
updateCustomerTimeline,
|
|
73
|
+
updateCustomerTimelineByFilter,
|
|
74
|
+
deleteCustomerTimeline,
|
|
75
|
+
deleteAllCustomerTimelinesByWorkspace,
|
|
76
|
+
findAllCustomerTimelinesByFilter,
|
|
77
|
+
updateManyCustomerTimelinesByFilter,
|
|
78
|
+
};
|
|
@@ -13,6 +13,7 @@ const userPermissionRepository = require("./userPermission.repository");
|
|
|
13
13
|
const userRolePermissionRepository = require("./userRolePermission.repository");
|
|
14
14
|
const notificationSettingsRepository = require("./notificationSettings.repository");
|
|
15
15
|
const customerProfileRepository = require("./customerProfile.repository");
|
|
16
|
+
const customerTimelineRepository = require("./customerTimeline.repository");
|
|
16
17
|
exports.module = {
|
|
17
18
|
workspaceRepository,
|
|
18
19
|
integrationRepository,
|
|
@@ -29,4 +30,5 @@ exports.module = {
|
|
|
29
30
|
userPermissionRepository,
|
|
30
31
|
userRolePermissionRepository,
|
|
31
32
|
customerProfileRepository,
|
|
33
|
+
customerTimelineRepository,
|
|
32
34
|
};
|
package/config/redis.js
CHANGED
|
@@ -8,51 +8,53 @@ const client = createClient({
|
|
|
8
8
|
url: REDIS_URL,
|
|
9
9
|
socket: { reconnectStrategy: (retries) => Math.min(retries * 50, 1000) }, // Exponential backoff
|
|
10
10
|
});
|
|
11
|
+
|
|
12
|
+
// ✅ Create separate Pub/Sub clients
|
|
11
13
|
const publisher = client.duplicate();
|
|
12
14
|
const subscriber = client.duplicate();
|
|
13
15
|
|
|
14
|
-
// ✅ Redis Events
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
client.on("end", () => console.log(`❗ ${label} Connection Closed`));
|
|
20
|
-
client.on("reconnecting", () => console.warn(`🔄 ${label} Reconnecting...`));
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
handleEvents(client, "Main Redis");
|
|
24
|
-
handleEvents(publisher, "Publisher Redis");
|
|
25
|
-
handleEvents(subscriber, "Subscriber Redis");
|
|
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"));
|
|
26
21
|
|
|
27
|
-
// ✅ Ensure Redis is connected
|
|
22
|
+
// ✅ Ensure Redis is connected before performing operations
|
|
28
23
|
const connectRedis = async () => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!config["notify-keyspace-events"]?.includes("Ex")) {
|
|
37
|
-
await client.configSet("notify-keyspace-events", "Ex");
|
|
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);
|
|
38
31
|
}
|
|
39
|
-
} catch (err) {
|
|
40
|
-
console.error("❌ Failed to connect to Redis:", err);
|
|
41
32
|
}
|
|
33
|
+
await client.configSet("notify-keyspace-events", "Ex");
|
|
42
34
|
};
|
|
43
35
|
|
|
44
|
-
|
|
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
|
+
*/
|
|
45
41
|
const publishToChannel = async (channel, message) => {
|
|
46
42
|
try {
|
|
43
|
+
await connectRedis();
|
|
47
44
|
await publisher.publish(channel, JSON.stringify(message));
|
|
48
45
|
} catch (err) {
|
|
49
46
|
console.error("❌ Error publishing message:", err);
|
|
50
47
|
}
|
|
51
48
|
};
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
/**
|
|
51
|
+
* ✅ Subscribe & Listen for Messages
|
|
52
|
+
* @param {string} channel - The Redis Pub/Sub channel
|
|
53
|
+
* @param {function} callback - Callback function to handle messages
|
|
54
|
+
*/
|
|
54
55
|
const subscribeToChannel = async (channel, callback, expiry = false) => {
|
|
55
56
|
try {
|
|
57
|
+
await connectRedis();
|
|
56
58
|
await subscriber.subscribe(channel, (message) => {
|
|
57
59
|
if (expiry) callback(message);
|
|
58
60
|
else callback(JSON.parse(message));
|
|
@@ -62,7 +64,13 @@ const subscribeToChannel = async (channel, callback, expiry = false) => {
|
|
|
62
64
|
}
|
|
63
65
|
};
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
/**
|
|
68
|
+
* ✅ Set Data in Redis (Supports String & Hash)
|
|
69
|
+
* @param {string} key - The Redis key
|
|
70
|
+
* @param {any} value - The value to store
|
|
71
|
+
* @param {string|null} field - Optional field for Hash storage
|
|
72
|
+
* @param {number} expiryInSeconds - Expiry time in seconds (default: 3600)
|
|
73
|
+
*/
|
|
66
74
|
const setRedisData = async (
|
|
67
75
|
key,
|
|
68
76
|
value,
|
|
@@ -70,6 +78,7 @@ const setRedisData = async (
|
|
|
70
78
|
expiryInSeconds = 3600
|
|
71
79
|
) => {
|
|
72
80
|
try {
|
|
81
|
+
await connectRedis();
|
|
73
82
|
const stringifiedValue = JSON.stringify(value);
|
|
74
83
|
let result;
|
|
75
84
|
|
|
@@ -90,27 +99,43 @@ const setRedisData = async (
|
|
|
90
99
|
}
|
|
91
100
|
};
|
|
92
101
|
|
|
93
|
-
|
|
94
|
-
const getRedisData = async (key, field = null) => {
|
|
102
|
+
const addDataToRedisSet = async (key, value) => {
|
|
95
103
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
return result ? JSON.parse(result) : null;
|
|
104
|
+
await connectRedis();
|
|
105
|
+
await client.sAdd(key, value);
|
|
106
|
+
return true;
|
|
100
107
|
} catch (err) {
|
|
101
|
-
|
|
102
|
-
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const removeDataFromRedisSet = async (key, value) => {
|
|
113
|
+
try {
|
|
114
|
+
await connectRedis();
|
|
115
|
+
await client.sRem(key, value);
|
|
116
|
+
return true;
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const isMemberOfRedisSet = async (key, value) => {
|
|
122
|
+
try {
|
|
123
|
+
await connectRedis();
|
|
124
|
+
return await client.sIsMember(key, value);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.log(err, "err");
|
|
127
|
+
return false;
|
|
103
128
|
}
|
|
104
129
|
};
|
|
105
130
|
/**
|
|
106
|
-
* ✅ Get
|
|
131
|
+
* ✅ Get Data from Redis (Supports String & Hash)
|
|
107
132
|
* @param {string} key - The Redis key
|
|
108
133
|
* @param {string|null} field - Optional field for Hash retrieval
|
|
109
134
|
*/
|
|
110
|
-
const
|
|
135
|
+
const getRedisData = async (key, field = null) => {
|
|
111
136
|
try {
|
|
112
137
|
await connectRedis();
|
|
113
|
-
let result = await client.
|
|
138
|
+
let result = field ? await client.hGet(key, field) : await client.get(key);
|
|
114
139
|
return result ? JSON.parse(result) : null;
|
|
115
140
|
} catch (err) {
|
|
116
141
|
console.error("❌ Error getting Redis data:", err);
|
|
@@ -118,9 +143,14 @@ const getRedisDataTime = async (key) => {
|
|
|
118
143
|
}
|
|
119
144
|
};
|
|
120
145
|
|
|
121
|
-
|
|
146
|
+
/**
|
|
147
|
+
* ✅ Delete Data from Redis (Supports String & Hash)
|
|
148
|
+
* @param {string} key - The Redis key
|
|
149
|
+
* @param {string|null} field - Optional field for Hash deletion
|
|
150
|
+
*/
|
|
122
151
|
const deleteRedisData = async (key, field = null) => {
|
|
123
152
|
try {
|
|
153
|
+
await connectRedis();
|
|
124
154
|
return field ? await client.hDel(key, field) : await client.del(key);
|
|
125
155
|
} catch (err) {
|
|
126
156
|
console.error("❌ Error deleting Redis data:", err);
|
|
@@ -128,37 +158,9 @@ const deleteRedisData = async (key, field = null) => {
|
|
|
128
158
|
}
|
|
129
159
|
};
|
|
130
160
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
await client.sAdd(key, value);
|
|
135
|
-
return true;
|
|
136
|
-
} catch (err) {
|
|
137
|
-
console.error("❌ Error adding to Redis set:", err);
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const removeDataFromRedisSet = async (key, value) => {
|
|
143
|
-
try {
|
|
144
|
-
await client.sRem(key, value);
|
|
145
|
-
return true;
|
|
146
|
-
} catch (err) {
|
|
147
|
-
console.error("❌ Error removing from Redis set:", err);
|
|
148
|
-
return false;
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
const isMemberOfRedisSet = async (key, value) => {
|
|
153
|
-
try {
|
|
154
|
-
return await client.sIsMember(key, value);
|
|
155
|
-
} catch (err) {
|
|
156
|
-
console.error("❌ Error checking membership in Redis set:", err);
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// ✅ Graceful Shutdown
|
|
161
|
+
/**
|
|
162
|
+
* ✅ Graceful Shutdown Handling
|
|
163
|
+
*/
|
|
162
164
|
const closeClients = async () => {
|
|
163
165
|
if (client.isOpen) await client.quit();
|
|
164
166
|
if (publisher.isOpen) await publisher.quit();
|
|
@@ -182,13 +184,12 @@ module.exports = {
|
|
|
182
184
|
client,
|
|
183
185
|
publisher,
|
|
184
186
|
subscriber,
|
|
185
|
-
connectRedis,
|
|
186
187
|
setRedisData,
|
|
187
|
-
getRedisDataTime,
|
|
188
188
|
getRedisData,
|
|
189
189
|
deleteRedisData,
|
|
190
190
|
publishToChannel,
|
|
191
191
|
subscribeToChannel,
|
|
192
|
+
connectRedis,
|
|
192
193
|
addDataToRedisSet,
|
|
193
194
|
removeDataFromRedisSet,
|
|
194
195
|
isMemberOfRedisSet,
|
|
@@ -31,29 +31,42 @@ exports.checkPermission = async (req, res, next, values) => {
|
|
|
31
31
|
const accessToken = token.split(" ")[1];
|
|
32
32
|
const body = getBodyFromDecodeToken(accessToken);
|
|
33
33
|
if (body) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
34
|
+
req.user = {
|
|
35
|
+
id: body.id,
|
|
36
|
+
type: body.type,
|
|
37
|
+
email: body.email,
|
|
38
|
+
firstName: body.firstName,
|
|
39
|
+
lastName: body.lastName,
|
|
40
|
+
suspended: body.suspended,
|
|
41
|
+
isVerified: body.isVerified,
|
|
42
|
+
role: body.role,
|
|
43
|
+
isMember: body.isMember,
|
|
44
|
+
uuid: body.uuid,
|
|
45
|
+
};
|
|
46
|
+
return next();
|
|
47
|
+
// const { module, action } = values;
|
|
48
|
+
// const moduleObj = getModule(body, module);
|
|
49
|
+
// if (moduleObj && moduleObj.includes(action)) {
|
|
50
|
+
// req.user = {
|
|
51
|
+
// id: body.id,
|
|
52
|
+
// type: body.type,
|
|
53
|
+
// email: body.email,
|
|
54
|
+
// firstName: body.firstName,
|
|
55
|
+
// lastName: body.lastName,
|
|
56
|
+
// suspended: body.suspended,
|
|
57
|
+
// isVerified: body.isVerified,
|
|
58
|
+
// role: body.role,
|
|
59
|
+
// isMember: body.isMember,
|
|
60
|
+
// uuid: body.uuid,
|
|
61
|
+
// };
|
|
62
|
+
// req.currentModulePermission = moduleObj;
|
|
63
|
+
// return next();
|
|
64
|
+
// } else {
|
|
65
|
+
// return res.status(HttpStatusCode.UNAUTHORIZED).json({
|
|
66
|
+
// code: HttpStatusCode.UNAUTHORIZED,
|
|
67
|
+
// message: GenericMessages.PERMISSION_DENIED,
|
|
68
|
+
// });
|
|
69
|
+
// }
|
|
57
70
|
}
|
|
58
71
|
return res.status(HttpStatusCode.UNAUTHORIZED).json({
|
|
59
72
|
code: HttpStatusCode.UNAUTHORIZED,
|
package/models/Customer.js
CHANGED
|
@@ -8,6 +8,9 @@ const CustomerProfileSchema = new Schema(
|
|
|
8
8
|
picture: { type: String, default: "" },
|
|
9
9
|
fbId: { type: String, default: "" },
|
|
10
10
|
igId: { type: String, default: "" },
|
|
11
|
+
totalOrders: { type: Number, default: 0 },
|
|
12
|
+
totalConversations: { type: Number, default: 0 },
|
|
13
|
+
totalTickets: { type: Number, default: 0 },
|
|
11
14
|
blackList: { type: Boolean, default: false },
|
|
12
15
|
workspaceId: {
|
|
13
16
|
type: Schema.Types.ObjectId,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
const { Schema } = mongoose;
|
|
3
|
+
const CustomerTimelineSchema = new Schema(
|
|
4
|
+
{
|
|
5
|
+
type: { type: String, default: "" },
|
|
6
|
+
referenceId: { type: String, default: "" },
|
|
7
|
+
timestamp: { type: String, default: "" },
|
|
8
|
+
platformId: {
|
|
9
|
+
type: Schema.Types.ObjectId,
|
|
10
|
+
ref: "Integration",
|
|
11
|
+
default: null,
|
|
12
|
+
},
|
|
13
|
+
workspaceId: {
|
|
14
|
+
type: Schema.Types.ObjectId,
|
|
15
|
+
ref: "Workspace",
|
|
16
|
+
default: null,
|
|
17
|
+
},
|
|
18
|
+
profileId: {
|
|
19
|
+
type: Schema.Types.ObjectId,
|
|
20
|
+
ref: "CustomerProfile",
|
|
21
|
+
default: null,
|
|
22
|
+
},
|
|
23
|
+
body: {},
|
|
24
|
+
},
|
|
25
|
+
{ timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
module.exports = mongoose.model("CustomerTimeline", CustomerTimelineSchema);
|
package/models/Order.js
CHANGED
|
@@ -136,17 +136,27 @@ const OrderSchema = new Schema(
|
|
|
136
136
|
paymentDate: { type: String, default: "" },
|
|
137
137
|
paymentDetails: {},
|
|
138
138
|
},
|
|
139
|
-
merged: {
|
|
140
|
-
status: { type: String, default: "open" },
|
|
141
|
-
webOrderNumbers: [],
|
|
142
|
-
orderNumbers: [],
|
|
143
|
-
localOrderIds: [],
|
|
144
|
-
webOrderIds: [],
|
|
145
|
-
details: [],
|
|
146
|
-
},
|
|
147
139
|
credentials: {},
|
|
148
140
|
ticketId: { type: String, default: "" },
|
|
149
141
|
sender: { type: String, default: "" },
|
|
142
|
+
shipperAdvice: {
|
|
143
|
+
reAttempt: {
|
|
144
|
+
made: { type: Boolean, default: false },
|
|
145
|
+
details: {
|
|
146
|
+
attempt: { type: String, default: "0" },
|
|
147
|
+
remarks: { type: String, default: "" },
|
|
148
|
+
date: { type: String, default: "" },
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
return: {
|
|
152
|
+
made: { type: Boolean, default: false },
|
|
153
|
+
details: {
|
|
154
|
+
attempt: { type: String, default: "0" },
|
|
155
|
+
remarks: { type: String, default: "" },
|
|
156
|
+
date: { type: String, default: "" },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
150
160
|
orderName: { type: String, default: "" },
|
|
151
161
|
totalWeight: { type: String, default: "" },
|
|
152
162
|
},
|
package/models/Workspace.js
CHANGED
|
@@ -280,16 +280,6 @@ const workspaceSchema = new mongoose.Schema(
|
|
|
280
280
|
default: "everyone",
|
|
281
281
|
},
|
|
282
282
|
defaultShift: { type: String, default: "" },
|
|
283
|
-
automationManager: {
|
|
284
|
-
delayThreshold: {
|
|
285
|
-
type: Number,
|
|
286
|
-
default: 5,
|
|
287
|
-
},
|
|
288
|
-
messageCount: {
|
|
289
|
-
type: Number,
|
|
290
|
-
default: 3,
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
283
|
},
|
|
294
284
|
{ timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
|
|
295
285
|
);
|