shuttlepro-shared 1.1.54 → 1.1.56

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/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;
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,78 @@
1
+ const { createClient } = require("redis");
2
+ const { redis } = require("../config/config");
3
+
4
+ const client = createClient({
5
+ url: redis.url,
6
+ });
7
+ client.on("error", (err) => console.log("Redis Client Error", err));
8
+
9
+ const connectRedis = async () => {
10
+ if (!client.isOpen) {
11
+ await client.connect();
12
+ }
13
+ };
14
+
15
+ exports.setRedisData = async (
16
+ key,
17
+ value,
18
+ field = null,
19
+ expiryInSeconds = 3600
20
+ ) => {
21
+ try {
22
+ await connectRedis();
23
+ let resp;
24
+ if (field) {
25
+ resp = await client.hSet(key, field, JSON.stringify(value));
26
+ } else {
27
+ resp = await client.set(key, JSON.stringify(value));
28
+ }
29
+ await client.expire(key, expiryInSeconds); // Set expiry for the key
30
+ return resp;
31
+ } catch (err) {
32
+ console.error("Error setting Redis data:", err);
33
+ throw err;
34
+ }
35
+ };
36
+
37
+ exports.getRedisData = async (key, field = null) => {
38
+ try {
39
+ 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;
47
+ } catch (err) {
48
+ console.error("Error getting Redis data:", err);
49
+ throw err;
50
+ }
51
+ };
52
+
53
+ exports.deleteRedisData = async (key) => {
54
+ try {
55
+ await connectRedis();
56
+ return await client.del(key);
57
+ } catch (err) {
58
+ console.error("Error deleting Redis data:", err);
59
+ throw err;
60
+ }
61
+ };
62
+
63
+ // Ensure the Redis client is closed when the application exits
64
+ const closeClient = () => {
65
+ if (client.isOpen) {
66
+ client.quit();
67
+ }
68
+ };
69
+
70
+ process.on("exit", closeClient);
71
+ process.on("SIGINT", () => {
72
+ closeClient();
73
+ process.exit();
74
+ });
75
+ process.on("SIGTERM", () => {
76
+ closeClient();
77
+ process.exit();
78
+ });
package/index.js CHANGED
@@ -1,7 +1,11 @@
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");
3
5
 
4
6
  module.exports = {
5
7
  sharedModels,
6
8
  sharedFunctions,
9
+ logger,
10
+ shuttlePro,
7
11
  };
@@ -1,10 +1,6 @@
1
1
  const mongoose = require("mongoose");
2
2
 
3
3
  const daySchema = new mongoose.Schema({
4
- _id: {
5
- type: mongoose.Schema.Types.ObjectId,
6
- default: mongoose.Types.ObjectId,
7
- },
8
4
  day: { type: String, required: true },
9
5
  startTime: { type: String, required: false },
10
6
  endTime: { type: String, required: false },
@@ -31,10 +27,6 @@ const userRoleSchema = new mongoose.Schema(
31
27
  },
32
28
  isOwner: { type: Boolean, default: false },
33
29
  userShift: {
34
- _id: {
35
- type: mongoose.Schema.Types.ObjectId,
36
- default: mongoose.Types.ObjectId,
37
- },
38
30
  shiftName: { type: String, required: false },
39
31
  days: { type: [daySchema], required: false },
40
32
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shuttlepro-shared",
3
- "version": "1.1.54",
3
+ "version": "1.1.56",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -11,7 +11,17 @@
11
11
  "license": "ISC",
12
12
  "access": "restricted",
13
13
  "dependencies": {
14
+ "@babel/core": "^7.22.5",
15
+ "@babel/plugin-proposal-class-properties": "^7.18.6",
16
+ "@babel/plugin-proposal-decorators": "^7.22.5",
17
+ "@babel/preset-env": "^7.22.5",
18
+ "@babel/register": "^7.22.5",
19
+ "redis": "^4.6.14",
20
+ "express": "^4.17.1",
14
21
  "jsonwebtoken": "^9.0.2",
15
- "mongoose": "^8.7.1"
22
+ "mongoose": "^8.7.1",
23
+ "cors": "^2.8.5",
24
+ "helmet": "^8.0.0",
25
+ "bull": "^4.10.4"
16
26
  }
17
27
  }
@@ -0,0 +1,264 @@
1
+ const express = require("express");
2
+ const logger = require("./logger");
3
+ const cors = require("cors");
4
+ const helmet = require("helmet");
5
+
6
+ // Controller Decorator
7
+ function Controller(basePath) {
8
+ return function (target) {
9
+ target.prototype.basePath = basePath;
10
+ };
11
+ }
12
+
13
+ // Modified method decorator factory to handle pending middlewares
14
+ function createMethodDecorator(method) {
15
+ return function (path = "") {
16
+ return function (target, key, descriptor) {
17
+ if (!target.constructor.prototype.routes) {
18
+ target.constructor.prototype.routes = [];
19
+ }
20
+
21
+ // Create route object
22
+ const route = {
23
+ method,
24
+ path,
25
+ handlerName: key,
26
+ handler: descriptor.value,
27
+ middlewares: [],
28
+ guards: [],
29
+ interceptors: [],
30
+ pipes: [],
31
+ auth: null,
32
+ };
33
+
34
+ // Check if there are any pending middlewares for this route
35
+ if (
36
+ target.constructor.prototype._pendingMiddlewares &&
37
+ target.constructor.prototype._pendingMiddlewares[key]
38
+ ) {
39
+ route.middlewares.push(
40
+ ...target.constructor.prototype._pendingMiddlewares[key]
41
+ );
42
+ // Clear the pending middlewares
43
+ delete target.constructor.prototype._pendingMiddlewares[key];
44
+ }
45
+
46
+ target.constructor.prototype.routes.push(route);
47
+
48
+ return descriptor;
49
+ };
50
+ };
51
+ }
52
+
53
+ // HTTP Method Decorators
54
+ const Get = createMethodDecorator("get");
55
+ const Post = createMethodDecorator("post");
56
+ const Put = createMethodDecorator("put");
57
+ const Delete = createMethodDecorator("delete");
58
+ const Patch = createMethodDecorator("patch");
59
+
60
+ function formatResponse(data, code = 200, message = "Success") {
61
+ return { code, message, data };
62
+ }
63
+
64
+ function Middleware(middlewares = []) {
65
+ return function (target) {
66
+ if (!target.prototype.middlewares) {
67
+ target.prototype.middlewares = [];
68
+ }
69
+ target.prototype.middlewares = [
70
+ ...target.prototype.middlewares,
71
+ ...middlewares,
72
+ ]; // Append instead of overwrite
73
+ };
74
+ }
75
+
76
+ function UseMiddleware(...middlewares) {
77
+ return function (target, key, descriptor) {
78
+ // Initialize routes array if it doesn't exist
79
+ if (!target.constructor.prototype.routes) {
80
+ target.constructor.prototype.routes = [];
81
+ }
82
+
83
+ // Find the route by handlerName
84
+ const route = target.constructor.prototype.routes.find(
85
+ (r) => r.handlerName === key
86
+ );
87
+
88
+ if (route) {
89
+ // Ensure route.middlewares is initialized
90
+ if (!route.middlewares) {
91
+ route.middlewares = [];
92
+ }
93
+ // Add the middlewares to the route
94
+ route.middlewares.push(...middlewares);
95
+ } else {
96
+ // If the route doesn't exist yet (decorator order issue),
97
+ // we need to create a route object that will be used later
98
+ target.constructor.prototype._pendingMiddlewares =
99
+ target.constructor.prototype._pendingMiddlewares || {};
100
+
101
+ if (!target.constructor.prototype._pendingMiddlewares[key]) {
102
+ target.constructor.prototype._pendingMiddlewares[key] = [];
103
+ }
104
+
105
+ target.constructor.prototype._pendingMiddlewares[key].push(
106
+ ...middlewares
107
+ );
108
+ }
109
+
110
+ return descriptor;
111
+ };
112
+ }
113
+
114
+ // Guard Decorator
115
+ function Guard(...guards) {
116
+ return function (target, key, descriptor) {
117
+ const route = target.constructor.prototype.routes.find(
118
+ (r) => r.handlerName === key
119
+ );
120
+ if (route) {
121
+ route.guards = [...(route.guards || []), ...guards];
122
+ }
123
+ return descriptor;
124
+ };
125
+ }
126
+
127
+ // Auth Decorator
128
+ function Auth(authConfig) {
129
+ return function (target, key, descriptor) {
130
+ const route = target.constructor.prototype.routes.find(
131
+ (r) => r.handlerName === key
132
+ );
133
+ if (route) {
134
+ route.auth = authConfig;
135
+ }
136
+ return descriptor;
137
+ };
138
+ }
139
+
140
+ // Pipe Decorator
141
+ function Pipe(...pipes) {
142
+ return function (target, key, descriptor) {
143
+ const route = target.constructor.prototype.routes.find(
144
+ (r) => r.handlerName === key
145
+ );
146
+ if (route) {
147
+ route.pipes = [...(route.pipes || []), ...pipes];
148
+ }
149
+ return descriptor;
150
+ };
151
+ }
152
+
153
+ // Error Handling Middleware
154
+ function asyncHandler(fn) {
155
+ return async (req, res, next) => {
156
+ try {
157
+ const result = await fn(req, res, next);
158
+ res.status(result.code || 200).json(result);
159
+ } catch (error) {
160
+ next(error);
161
+ }
162
+ };
163
+ }
164
+
165
+ function createRouterFromController(ControllerClass) {
166
+ const controller = new ControllerClass();
167
+ const router = express.Router();
168
+ const basePath = controller.constructor.prototype.basePath || "";
169
+ const globalMiddlewares = controller.constructor.prototype.middlewares || []; // Retrieve global middlewares
170
+
171
+ if (controller.constructor.prototype.routes) {
172
+ controller.constructor.prototype.routes.forEach((route) => {
173
+ const { method, path, handler, middlewares, guards, pipes, auth } = route;
174
+
175
+ const middlewareChain = [
176
+ ...(auth ? [createAuthMiddleware(auth)] : []),
177
+ ...globalMiddlewares, // Apply global middlewares first
178
+ ...guards.map((guard) => createGuardMiddleware(guard)),
179
+ ...middlewares, // Then apply route-specific middlewares
180
+ asyncHandler(async (req) => {
181
+ if (pipes.length > 0) {
182
+ req.body = pipes.reduce(
183
+ (data, pipe) => pipe.transform(data),
184
+ req.body
185
+ );
186
+ }
187
+ return await handler.call(controller, req);
188
+ }),
189
+ ];
190
+
191
+ router[method](path, ...middlewareChain);
192
+ });
193
+ }
194
+
195
+ router.use((error, req, res, next) => {
196
+ console.error("Route error:", error);
197
+ res
198
+ .status(500)
199
+ .json({ code: 500, message: error.message || "Internal Server Error" });
200
+ });
201
+
202
+ return { basePath, router };
203
+ }
204
+
205
+ /**
206
+ * Configure Express middleware and settings
207
+ * @param {Express.Application} app - Express application instance
208
+ */
209
+
210
+ function configureMiddleware(app, config) {
211
+ // Request parsing middleware
212
+ app.use(express.json({ limit: config?.limit || "10mb" }));
213
+ app.use(
214
+ express.urlencoded({ limit: config?.limit || "10mb", extended: true })
215
+ );
216
+
217
+ // Security middleware
218
+ app.use(
219
+ cors({
220
+ origin: config?.ALLOWED_URLS,
221
+ credentials: true,
222
+ })
223
+ );
224
+ app.use(helmet());
225
+
226
+ // Request logging
227
+ app.use((req, res, next) => {
228
+ logger.info(`${req.method} ${req.originalUrl}`);
229
+ if (req.method !== "GET") {
230
+ logger.debug("Request Body:", req.body);
231
+ }
232
+ next();
233
+ });
234
+ }
235
+
236
+ // App Initialization
237
+ function createApp(controllers, config) {
238
+ const app = express(); // Ensure express is initialized
239
+ configureMiddleware(app, config);
240
+
241
+ controllers.forEach((ControllerClass) => {
242
+ const { basePath, router } = createRouterFromController(ControllerClass);
243
+ app.use(`${config?.globalPrefix}${basePath}`, router); // Apply global prefix
244
+ });
245
+
246
+ return app; // Return a properly configured express app
247
+ }
248
+
249
+ module.exports = {
250
+ Controller,
251
+ Get,
252
+ Post,
253
+ Put,
254
+ Delete,
255
+ Patch,
256
+ UseMiddleware,
257
+ Guard,
258
+ Auth,
259
+ Pipe,
260
+ createRouterFromController,
261
+ createApp,
262
+ formatResponse,
263
+ Middleware,
264
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Simple logger utility for consistent logging
3
+ * Can be replaced with a more robust solution like Winston or Pino
4
+ */
5
+ const logger = {
6
+ /**
7
+ * Log info level messages
8
+ * @param {...any} args - Arguments to log
9
+ */
10
+ info: (...args) => {
11
+ console.log(`[${new Date().toISOString()}] [INFO]`, ...args);
12
+ },
13
+
14
+ /**
15
+ * Log debug level messages
16
+ * @param {...any} args - Arguments to log
17
+ */
18
+ debug: (...args) => {
19
+ if (process.env.NODE_ENV !== "production") {
20
+ console.log(`[${new Date().toISOString()}] [DEBUG]`, ...args);
21
+ }
22
+ },
23
+
24
+ /**
25
+ * Log error level messages
26
+ * @param {...any} args - Arguments to log
27
+ */
28
+ error: (...args) => {
29
+ console.error(`[${new Date().toISOString()}] [ERROR]`, ...args);
30
+ },
31
+
32
+ /**
33
+ * Log warning level messages
34
+ * @param {...any} args - Arguments to log
35
+ */
36
+ warn: (...args) => {
37
+ console.warn(`[${new Date().toISOString()}] [WARN]`, ...args);
38
+ },
39
+ };
40
+
41
+ module.exports = logger;