From b2c8e50043d323ecd7b0eead9b96a575f7d01063 Mon Sep 17 00:00:00 2001 From: MuhammadKhalilzadeh Date: Fri, 17 May 2024 09:46:17 +0330 Subject: [PATCH] downloaded Server folder and replaced it --- Server/controllers/authController.js | 16 +- Server/controllers/monitorController.js | 13 +- Server/db/FakeDb.js | 4 +- Server/index.js | 43 ++-- Server/package-lock.json | 267 ++++++++++++++++++++++++ Server/package.json | 1 + Server/service/pingService.js | 123 +++++++++++ 7 files changed, 440 insertions(+), 27 deletions(-) create mode 100644 Server/service/pingService.js diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index eba12db7d..750552dab 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -1,7 +1,5 @@ const express = require("express"); -const UserModel = require("../models/user"); -const { registerValidation } = require("../validation/joi"); -const logger = require('../utils/logger') +const logger = require("../utils/logger"); /** * @function @@ -23,26 +21,28 @@ const registerController = async (req, res) => { try { const isUser = await req.db.getUserByEmail(req, res); if (isUser) { - logger.warning("User already exists!", { "service": "auth", "userId": isUser._id }); + logger.warning("User already exists!", { + service: "auth", + userId: isUser._id, + }); return res .status(400) .json({ success: false, msg: "User already exists!" }); } } catch (error) { - logger.error(error.message, { "service": "auth" }); + logger.error(error.message, { service: "auth" }); return res.status(500).json({ success: false, msg: error.message }); } - try { // Create a new user const newUser = await req.db.insertUser(req, res); // TODO: Send an email to user // Will add this later - logger.info("New user created!", { "service": "auth", "userId": newUser._id }); + logger.info("New user created!", { service: "auth", userId: newUser._id }); return res.json({ success: true, msg: "User created}", data: newUser }); } catch (error) { - logger.error(error.message, { "service": "auth" }); + logger.error(error.message, { service: "auth" }); return res.status(500).json({ success: false, msg: error.message }); } }; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index c85da0f9d..b64352e35 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -2,7 +2,7 @@ const { getMonitorsByIdValidation, getMonitorsByUserIdValidation, } = require("../validation/joi"); -const logger = require('../utils/logger') +const logger = require("../utils/logger"); // Gets all monitors const getAllMonitors = async (req, res) => { @@ -10,7 +10,7 @@ const getAllMonitors = async (req, res) => { const monitors = await req.db.getAllMonitors(); return res.json({ success: true, msg: "Monitors found", data: monitors }); } catch (error) { - logger.error(error.message, { "service": "monitor" }); + logger.error(error.message, { service: "monitor" }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -29,7 +29,7 @@ const getMonitorById = async (req, res) => { const monitor = await req.db.getMonitorById(monitorId); return res.json({ success: true, msg: "Monitor found", data: monitor }); } catch (error) { - logger.error(error.message, { "service": "monitor" }); + logger.error(error.message, { service: "monitor" }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -46,14 +46,17 @@ const getMonitorsByUserId = async (req, res) => { try { const userId = req.params.userId; const monitors = await req.db.getMonitorsByUserId(userId); - logger.info(`Monitors for user ${userId} found`, { "service": "monitor", "userId":userId }); + logger.info(`Monitors for user ${userId} found`, { + service: "monitor", + userId: userId, + }); return res.json({ success: true, msg: `Monitors for user ${userId} found`, data: monitors, }); } catch (error) { - logger.error(error.message, { "service": "monitor" }); + logger.error(error.message, { service: "monitor" }); return res.status(500).json({ success: false, msg: error.message }); } }; diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js index 8cf05cf89..241e176e9 100644 --- a/Server/db/FakeDb.js +++ b/Server/db/FakeDb.js @@ -26,7 +26,7 @@ const UserModel = require("../models/user"); const FAKE_MONITOR_DATA = []; const USERS = []; -for (let i = 0; i < 10; i++) { +for (let i = 0; i < 1; i++) { FAKE_MONITOR_DATA.push( new Monitor({ userId: i % 2 === 0 ? 1 : 2, @@ -34,7 +34,7 @@ for (let i = 0; i < 10; i++) { description: `Description for Monitor ${i}`, url: `https://monitor${i}.com`, isActive: true, - interval: 60000, + interval: Math.floor(Math.random() * 10) + 1, updated_at: new Date(), created_at: new Date(), }) diff --git a/Server/index.js b/Server/index.js index 7e68c72f2..b2669b544 100644 --- a/Server/index.js +++ b/Server/index.js @@ -7,20 +7,29 @@ const { connectDbAndRunServer } = require("./configs/db"); require("dotenv").config(); const logger = require("./utils/logger"); // const { sendEmail } = require('./utils/sendEmail') +const pingSerivce = require("./service/pingService"); -// ************************** -// Here is where we can swap out DBs easily. Spin up a mongoDB instance and try it out. -// Simply comment out the FakeDB and uncomment the MongoDB or vice versa. -// We can easily swap between any type of data source as long as the methods are implemented -// -// FakeDB -// const db = require("./db/FakeDb"); -// -// MongoDB -const db = require("./db/MongoDB"); -// -// ************************** +/** + * DB Notes + * Here is where we can swap out DBs easily. Spin up a mongoDB instance and try it out. + * Simply comment out the FakeDB and uncomment the MongoDB or vice versa. + * We can easily swap between any type of data source as long as the methods are implemented + * + * FakeDB + * const db = require("./db/FakeDb"); + * + * MongoDB + * const db = require("./db/MongoDB"); + */ +const DB_TYPE = { + MongoDB: () => require("./db/MongoDB"), + FakedDB: () => require("./db/FakeDb"), +}; + +const db = DB_TYPE[process.env.DB_TYPE] + ? DB_TYPE[process.env.DB_TYPE]() + : require("./db/FakeDb"); /** * NOTES * Email Service will be added @@ -70,3 +79,13 @@ app.use("/api/v1/healthy", (req, res) => { }); connectDbAndRunServer(app, db); +pingSerivce.startPingService(db); + +// Cleanup + +const cleanup = async () => { + await pingSerivce.cleanup(); + process.exit(0); +}; +process.on("SIGINT", cleanup); +process.on("SIGTERM", cleanup); diff --git a/Server/package-lock.json b/Server/package-lock.json index 495d5bc2e..9846fa5d7 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "bcrypt": "^5.1.1", + "bullmq": "5.7.8", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", @@ -54,6 +55,11 @@ "@hapi/hoek": "^9.0.0" } }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", @@ -95,6 +101,78 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz", + "integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz", + "integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz", + "integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz", + "integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz", + "integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz", + "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -310,6 +388,20 @@ "node": ">=16.20.1" } }, + "node_modules/bullmq": { + "version": "5.7.8", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.7.8.tgz", + "integrity": "sha512-F/Haeu6AVHkFrfeaU/kLOjhfrH6x3CaKAZlQQ+76fa8l3kfI9oaUHeFMW+1mYVz0NtYPF7PNTWFq4ylAHYcCgA==", + "dependencies": { + "cron-parser": "^4.6.0", + "ioredis": "^5.4.1", + "msgpackr": "^1.10.1", + "node-abort-controller": "^3.1.1", + "semver": "^7.5.4", + "tslib": "^2.0.0", + "uuid": "^9.0.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -368,6 +460,14 @@ "node": ">=10" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -470,6 +570,17 @@ "node": ">= 0.10" } }, + "node_modules/cron-parser": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", + "integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==", + "dependencies": { + "luxon": "^3.2.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -499,6 +610,14 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -978,6 +1097,50 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ioredis": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, + "node_modules/ioredis/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ioredis/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1086,6 +1249,16 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, "node_modules/logform": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", @@ -1118,6 +1291,14 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/mailersend": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/mailersend/-/mailersend-2.2.0.tgz", @@ -1384,6 +1565,35 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/msgpackr": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.2.tgz", + "integrity": "sha512-L60rsPynBvNE+8BWipKKZ9jHcSGbtyJYIwjRq0VrIvQ08cRjntGXJYW/tmciZ2IHWIY8WEW32Qa2xbh5+SKBZA==", + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz", + "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.0.7" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1392,6 +1602,11 @@ "node": ">= 0.6" } }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, "node_modules/node-addon-api": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", @@ -1435,6 +1650,17 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", + "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==", + "optional": true, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, "node_modules/nodemon": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz", @@ -1684,6 +1910,25 @@ "node": ">=8.10.0" } }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1875,6 +2120,11 @@ "node": "*" } }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1999,6 +2249,11 @@ "node": ">= 14.0.0" } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2043,6 +2298,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/Server/package.json b/Server/package.json index 6a082cbdc..80dda3835 100644 --- a/Server/package.json +++ b/Server/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "bcrypt": "^5.1.1", + "bullmq": "5.7.8", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", diff --git a/Server/service/pingService.js b/Server/service/pingService.js new file mode 100644 index 000000000..aeb43cd69 --- /dev/null +++ b/Server/service/pingService.js @@ -0,0 +1,123 @@ +const { Queue, Worker } = require("bullmq"); +const logger = require("../utils/logger"); +require("dotenv").config(); + +/** + * NOTES + * This code will be resource heavy as we will be creating + * many workers and jobs. Operations should be robust and have good + * error handling and retry functionality. + * There will be a lot of I/O operations as well, so we should be + * aware of asynchronous operations and handle them properly. + */ + +const connection = { + host: process.env.BULL_MQ_HOST || "127.0.0.1", + port: process.env.BULL_MQ_PORT || 6379, +}; + +const JOBS_PER_WORKER = 5; +const MAX_CREATE_WORKER_RETRIES = 3; + +const SERVICE_NAME = "pingService"; +const QUEUE_NAME = "monitors"; + +let queue; +let workers = []; + +const enqueueJob = async (queue, monitor) => { + try { + await queue.add( + QUEUE_NAME, + { monitor }, + { delay: monitor.interval * 1000 } + ); + logger.info("Job enqueued", { service: SERVICE_NAME }); + } catch (error) { + logger.error(error.message, { service: SERVICE_NAME }); + } +}; + +const pingMonitor = (monitor) => { + // TODO Implement pinging the monitor + console.log(`{url: ${monitor.url},`, `interval: ${monitor.interval}}`); +}; + +const createWorker = (queue, retries = 0) => { + try { + const worker = new Worker( + QUEUE_NAME, + async (job) => { + //Ping the monitor and enqueue the job again to constantly monitor + pingMonitor(job.data.monitor); + enqueueJob(queue, job.data.monitor); + }, + { + connection, + } + ); + logger.info("Worker created", { service: SERVICE_NAME }); + return worker; + } catch (error) { + logger.error(error.message, { service: SERVICE_NAME }); + // If creating a worker fails, recursively retry MAX_CREATE_WORKER_RETRIES times + if (retries < MAX_CREATE_WORKER_RETRIES) { + logger.info( + `Retrying to create worker (${ + retries + 1 + }/${MAX_CREATE_WORKER_RETRIES})`, + { service: SERVICE_NAME } + ); + return createWorker(queue, retries + 1); + } + + return null; + } +}; + +const startPingService = async (db) => { + queue = new Queue(QUEUE_NAME, { + connection, + }); + + const monitors = await db.getAllMonitors(); + + for (let i = 0; i < monitors.length; i++) { + if (i % JOBS_PER_WORKER === 0) { + const worker = createWorker(queue); + worker && workers.push(worker); + } + enqueueJob(queue, monitors[i]); + } + + // Make sure a worker was created, none will be created if jobs < JOBS_PER_WORKER + if (workers.length === 0) { + const worker = createWorker(queue); + worker && workers.push(worker); + if (!worker) { + logger.error("Failed to create workers", { service: SERVICE_NAME }); + } + } +}; + +const cleanup = async () => { + try { + logger.info("Cleaning up Ping Service...", { service: SERVICE_NAME }); + + // Clean out the queue + const jobs = await queue.getJobs(["active", "waiting", "delayed"]); + // There could be a great many jobs, handle this asynchronously with Promise.all + await Promise.all(jobs.map((job) => job.remove())); + + // Close workers. Same as jobs, there could be many workers we don't want to wait for each one to close. + await Promise.all(workers.map((worker) => worker.close())); + + // Close the queue + await queue.close(); + logger.info("Ping Service cleaned up", { service: SERVICE_NAME }); + } catch (error) { + logger.error(error.message, { service: SERVICE_NAME }); + } +}; + +module.exports = { startPingService, cleanup };