shuttlepro-shared 1.1.67 → 1.1.69

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/config/index.js CHANGED
@@ -2,10 +2,12 @@ const bullConfig = require("./bull");
2
2
  const redisConfig = require("./redis");
3
3
  const config = require("./config");
4
4
  const databaseConfig = require("./database");
5
+ const socketConfig = require("./socket");
5
6
 
6
7
  module.exports = {
7
8
  bullConfig,
8
9
  redisConfig,
9
10
  config,
10
11
  databaseConfig,
12
+ socketConfig,
11
13
  };
package/config/redis.js CHANGED
@@ -1,18 +1,40 @@
1
1
  const { createClient } = require("redis");
2
- const { redis } = require("./config");
2
+ require("dotenv").config();
3
3
 
4
+ const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";
5
+
6
+ // ✅ Create a single Redis client instance
4
7
  const client = createClient({
5
- url: redis.url,
8
+ url: REDIS_URL,
9
+ socket: { reconnectStrategy: (retries) => Math.min(retries * 50, 1000) }, // Exponential backoff for reconnections
6
10
  });
7
- client.on("error", (err) => console.log("Redis Client Error", err));
8
11
 
12
+ // ✅ Handle Redis Connection Events
13
+ client.on("error", (err) => console.error("❌ Redis Error:", err));
14
+ client.on("connect", () => console.log("✅ Redis Connected"));
15
+ client.on("reconnecting", () => console.warn("⚠️ Redis Reconnecting..."));
16
+ client.on("ready", () => console.log("🚀 Redis Ready to use"));
17
+ client.on("end", () => console.log("❗ Redis Connection Closed"));
18
+
19
+ // ✅ Ensure Redis is connected before performing operations
9
20
  const connectRedis = async () => {
10
21
  if (!client.isOpen) {
11
- await client.connect();
22
+ try {
23
+ await client.connect();
24
+ } catch (err) {
25
+ console.error("❌ Failed to connect to Redis:", err);
26
+ }
12
27
  }
13
28
  };
14
29
 
15
- exports.setRedisData = async (
30
+ /**
31
+ * ✅ Set Data in Redis (Supports String & Hash)
32
+ * @param {string} key - The Redis key
33
+ * @param {any} value - The value to store
34
+ * @param {string|null} field - Optional field for Hash storage
35
+ * @param {number} expiryInSeconds - Expiry time in seconds (default: 3600)
36
+ */
37
+ const setRedisData = async (
16
38
  key,
17
39
  value,
18
40
  field = null,
@@ -20,59 +42,78 @@ exports.setRedisData = async (
20
42
  ) => {
21
43
  try {
22
44
  await connectRedis();
23
- let resp;
45
+ let result;
24
46
  if (field) {
25
- resp = await client.hSet(key, field, JSON.stringify(value));
47
+ result = await client.hSet(key, field, JSON.stringify(value));
26
48
  } else {
27
- resp = await client.set(key, JSON.stringify(value));
49
+ result = await client.set(key, JSON.stringify(value), {
50
+ EX: expiryInSeconds,
51
+ });
28
52
  }
29
- await client.expire(key, expiryInSeconds); // Set expiry for the key
30
- return resp;
53
+ return result;
31
54
  } catch (err) {
32
- console.error("Error setting Redis data:", err);
55
+ console.error("Error setting Redis data:", err);
33
56
  throw err;
34
57
  }
35
58
  };
36
59
 
37
- exports.getRedisData = async (key, field = null) => {
60
+ /**
61
+ * ✅ Get Data from Redis (Supports String & Hash)
62
+ * @param {string} key - The Redis key
63
+ * @param {string|null} field - Optional field for Hash retrieval
64
+ */
65
+ const getRedisData = async (key, field = null) => {
38
66
  try {
39
67
  await connectRedis();
40
- let resp;
41
- if (field) {
42
- resp = await client.hGet(key, field);
43
- } else {
44
- resp = await client.get(key);
45
- }
46
- return resp ? JSON.parse(resp) : null;
68
+ let result = field ? await client.hGet(key, field) : await client.get(key);
69
+ return result ? JSON.parse(result) : null;
47
70
  } catch (err) {
48
- console.error("Error getting Redis data:", err);
71
+ console.error("Error getting Redis data:", err);
49
72
  throw err;
50
73
  }
51
74
  };
52
75
 
53
- exports.deleteRedisData = async (key) => {
76
+ /**
77
+ * ✅ Delete Data from Redis (Supports String & Hash)
78
+ * @param {string} key - The Redis key
79
+ * @param {string|null} field - Optional field for Hash deletion
80
+ */
81
+ const deleteRedisData = async (key, field = null) => {
54
82
  try {
55
83
  await connectRedis();
56
- return await client.del(key);
84
+ return field ? await client.hDel(key, field) : await client.del(key);
57
85
  } catch (err) {
58
- console.error("Error deleting Redis data:", err);
86
+ console.error("Error deleting Redis data:", err);
59
87
  throw err;
60
88
  }
61
89
  };
62
90
 
63
- // Ensure the Redis client is closed when the application exits
64
- const closeClient = () => {
91
+ /**
92
+ * Graceful Shutdown Handling
93
+ */
94
+ const closeClient = async () => {
65
95
  if (client.isOpen) {
66
- client.quit();
96
+ await client.quit();
97
+ console.log("❗ Redis Disconnected Gracefully");
67
98
  }
68
99
  };
69
100
 
101
+ // ✅ Ensure Redis disconnects properly on process exit
70
102
  process.on("exit", closeClient);
71
- process.on("SIGINT", () => {
72
- closeClient();
103
+ process.on("SIGINT", async () => {
104
+ await closeClient();
73
105
  process.exit();
74
106
  });
75
- process.on("SIGTERM", () => {
76
- closeClient();
107
+ process.on("SIGTERM", async () => {
108
+ await closeClient();
77
109
  process.exit();
78
110
  });
111
+
112
+ // ✅ Export Functions
113
+ module.exports = {
114
+ client, // Export Redis client for reuse
115
+ setRedisData,
116
+ getRedisData,
117
+ deleteRedisData,
118
+ connectRedis,
119
+ };
@@ -0,0 +1,106 @@
1
+ const { Server } = require("socket.io");
2
+ const { client: redisClient, connectRedis } = require("./redis");
3
+ const { createClient } = require("redis");
4
+ require("dotenv").config();
5
+
6
+ const REDIS_CHANNEL = "socket_events"; // Redis Pub/Sub Channel
7
+ let io;
8
+
9
+ /**
10
+ * ✅ Initialize WebSocket Server with Redis Pub/Sub
11
+ * @param {Object} server - HTTP Server instance
12
+ * @param {string|null} namespace - Optional namespace (passed from `index.js`)
13
+ */
14
+ const initializeSocket = async (server, namespace = null) => {
15
+ await connectRedis(); // Ensure Redis is connected
16
+
17
+ io = new Server(server, {
18
+ cors: { origin: "*" },
19
+ });
20
+
21
+ console.log(
22
+ `✅ WebSocket Server Initialized ${
23
+ namespace ? `with namespace: ${namespace}` : "globally"
24
+ }`
25
+ );
26
+
27
+ // Create Redis Pub/Sub Clients
28
+ const pubClient = redisClient;
29
+ const subClient = createClient({ url: process.env.REDIS_URL });
30
+ await subClient.connect();
31
+
32
+ // ✅ Subscribe to Redis channel
33
+ await subClient.subscribe(REDIS_CHANNEL, (message) => {
34
+ const { workspaceId, event, data } = JSON.parse(message);
35
+
36
+ if (namespace) {
37
+ io.of(namespace).to(workspaceId).emit(event, data);
38
+ } else {
39
+ io.to(workspaceId).emit(event, data);
40
+ }
41
+ });
42
+
43
+ // ✅ Handle WebSocket Connection (Global or Namespace)
44
+ const socketServer = namespace ? io.of(namespace) : io;
45
+ socketServer.on("connection", (socket) =>
46
+ handleConnection(socket, namespace)
47
+ );
48
+ };
49
+
50
+ /**
51
+ * ✅ Handle WebSocket Connection
52
+ * @param {Object} socket - WebSocket client socket
53
+ * @param {string} [namespace] - Optional namespace
54
+ */
55
+ const handleConnection = async (socket, namespace = null) => {
56
+ const { workspaceId } = socket.handshake.query;
57
+
58
+ if (!workspaceId) {
59
+ console.warn(`❌ Client ${socket.id} rejected due to missing workspaceId`);
60
+ socket.disconnect(true);
61
+ return;
62
+ }
63
+
64
+ console.log(
65
+ `✅ Client connected: ${socket.id} (Workspace: ${workspaceId}) Namespace: ${
66
+ namespace || "Global"
67
+ }`
68
+ );
69
+
70
+ await redisClient.hSet(
71
+ `${workspaceId}:sockets`,
72
+ socket.id,
73
+ JSON.stringify({
74
+ id: socket.id,
75
+ workspaceId,
76
+ namespace,
77
+ connectedAt: new Date().toISOString(),
78
+ })
79
+ );
80
+
81
+ // ✅ Handle Disconnection
82
+ socket.on("disconnect", async () => {
83
+ console.log(
84
+ `❌ Client disconnected: ${socket.id} (Workspace: ${workspaceId})`
85
+ );
86
+ await redisClient.hDel(`${workspaceId}:sockets`, socket.id);
87
+ });
88
+ };
89
+
90
+ /**
91
+ * ✅ Publish WebSocket Events via Redis
92
+ * @param {string} workspaceId - Target workspace ID
93
+ * @param {string} event - Event name
94
+ * @param {Object} data - Event payload
95
+ */
96
+ const sendEventToServer = async ({ workspaceId, event, data }) => {
97
+ await redisClient.publish(
98
+ REDIS_CHANNEL,
99
+ JSON.stringify({ workspaceId, event, data })
100
+ );
101
+ };
102
+
103
+ module.exports = {
104
+ initializeSocket,
105
+ sendEventToServer,
106
+ };
@@ -0,0 +1,120 @@
1
+ const mongoose = require("mongoose");
2
+ const { Schema } = mongoose;
3
+ const { setRedisData, getRedisData } = require("../utils");
4
+
5
+ const ProfileSchema = new Schema(
6
+ {
7
+ name: { type: String, default: "" },
8
+ userName: { type: String, default: "" },
9
+ profilePicture: { type: String, default: "" },
10
+ profileId: { type: String, default: "" },
11
+ isSpam: { type: Boolean, default: false },
12
+ workspaceId: { type: String, default: "" },
13
+ email: { type: String, default: "" },
14
+ phoneNo: { type: String, default: "" },
15
+ },
16
+ { timestamps: true, toJSON: { virtuals: true }, toObject: { virtuals: true } }
17
+ );
18
+ const Profile = mongoose.model("Profile", ProfileSchema);
19
+
20
+ const addOrUpdateProfilesInRedis = async (profile) => {
21
+ try {
22
+ const docId = profile?._id.toString();
23
+ let profiles = await fetchProfiles(profile?.workspaceId);
24
+ const profilesMap = profiles.reduce((map, p) => {
25
+ map[p._id.toString()] = p;
26
+ return map;
27
+ }, {});
28
+ profilesMap[docId] = profile.toObject();
29
+ profiles = Object.values(profilesMap);
30
+ setRedisData(`${profile?.workspaceId}-profiles`, profiles);
31
+ return { code: 200 };
32
+ } catch (err) {
33
+ return { code: 400 };
34
+ }
35
+ };
36
+
37
+ const fetchProfiles = async (workspaceId) => {
38
+ try {
39
+ let profiles = await getRedisData(`${workspaceId}-profiles`);
40
+ if (!profiles || profiles.length === 0) {
41
+ profiles = await Profile.find({ workspaceId }).lean().exec();
42
+ setRedisData(`${workspaceId}-profiles`, profiles);
43
+ }
44
+ return profiles;
45
+ } catch (err) {
46
+ return [];
47
+ }
48
+ };
49
+
50
+ const fetchByProfileIdAndWorkspaceId = async (id, workspaceId) => {
51
+ try {
52
+ let profiles = await fetchProfiles(workspaceId);
53
+ let profile = profiles.find(
54
+ (p) =>
55
+ (p?.profileId === id || p._id.toString() === id.toString()) &&
56
+ p?.workspaceId.toString() === workspaceId.toString()
57
+ );
58
+ return profile || null;
59
+ } catch (err) {
60
+ return null;
61
+ }
62
+ };
63
+
64
+ const fetchProfileByEmailAndWorkspaceId = async (
65
+ workspaceId,
66
+ userName = ""
67
+ ) => {
68
+ try {
69
+ let profiles = await fetchProfiles(workspaceId);
70
+ let profile = profiles.find(
71
+ (p) => p?.userName === userName && p?.workspaceId === workspaceId
72
+ );
73
+ return profile || null;
74
+ } catch (err) {
75
+ console.log(err, "err");
76
+ return null;
77
+ }
78
+ };
79
+
80
+ const createProfile = async (obj) => {
81
+ try {
82
+ let profile = await Profile.create(obj);
83
+ await addOrUpdateProfilesInRedis(profile);
84
+ return profile;
85
+ } catch (err) {
86
+ console.log(err, "err");
87
+ return null;
88
+ }
89
+ };
90
+ const createUpdateProfileModified = async (filter, obj) => {
91
+ try {
92
+ let profile = await Profile.findOneAndUpdate(filter, obj, { new: true });
93
+ await addOrUpdateProfilesInRedis(profile);
94
+ return profile;
95
+ } catch (err) {
96
+ console.log(err, "err");
97
+ return null;
98
+ }
99
+ };
100
+ const updateProfile = async (query, payload) => {
101
+ try {
102
+ const profile = await Profile.findOneAndUpdate(query, payload, {
103
+ new: true,
104
+ });
105
+ await addOrUpdateProfilesInRedis(profile);
106
+ return profile;
107
+ } catch (err) {
108
+ return null;
109
+ }
110
+ };
111
+
112
+ module.exports = {
113
+ fetchByProfileIdAndWorkspaceId,
114
+ fetchProfileByEmailAndWorkspaceId,
115
+ createProfile,
116
+ updateProfile,
117
+ fetchProfiles,
118
+ Profile,
119
+ createUpdateProfileModified,
120
+ };
package/models.js CHANGED
@@ -34,6 +34,7 @@ const StatusType = require("./models/StatusType");
34
34
  const Type = require("./models/Type");
35
35
  const Workspace = require("./models/Workspace");
36
36
  const Card = require("./models/Card");
37
+ const Profile = require("./models/Profile");
37
38
 
38
39
  module.exports = {
39
40
  Website,
@@ -72,4 +73,5 @@ module.exports = {
72
73
  StatusType,
73
74
  Card,
74
75
  DescriptionTemplate,
76
+ Profile,
75
77
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shuttlepro-shared",
3
- "version": "1.1.67",
3
+ "version": "1.1.69",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {