From 477cad7cc99c373faed310bd83474093c7df3b7a Mon Sep 17 00:00:00 2001 From: Alexander Holliday Date: Fri, 26 Jul 2024 18:56:39 -0700 Subject: [PATCH] Feat/monitor endpoint refactor (#463) * Moved all methods to modules * removed old mongoDB file * Refactored endpoints to take query params, removed duplicate methods, updated routes, updated Client queries * Added comment * Add model differentiation * Add sort order, fix minor bug, add validation for query params --- Client/src/Features/Monitors/monitorsSlice.js | 2 +- Client/src/Pages/Incidents/index.jsx | 2 +- Client/src/Pages/Monitors/Details/index.jsx | 13 +- Client/src/Pages/Monitors/index.jsx | 5 +- README.md | 25 ++- Server/controllers/monitorController.js | 72 +-------- Server/db/mongo/modules/monitorModule.js | 142 +++++++++--------- .../db/mongo/modules/pageSpeedCheckModule.js | 2 +- Server/routes/monitorRoute.js | 9 -- Server/service/networkService.js | 1 + Server/validation/joi.js | 25 ++- 11 files changed, 132 insertions(+), 166 deletions(-) diff --git a/Client/src/Features/Monitors/monitorsSlice.js b/Client/src/Features/Monitors/monitorsSlice.js index 8086f1eb4..2d6f8e2ba 100644 --- a/Client/src/Features/Monitors/monitorsSlice.js +++ b/Client/src/Features/Monitors/monitorsSlice.js @@ -51,7 +51,7 @@ export const getMonitorsByUserId = createAsyncThunk( const user = jwtDecode(token); try { const res = await axiosInstance.get( - `/monitors/user/${user._id}?limit=25`, + `/monitors/user/${user._id}?limit=25&type=http&type=ping&sortOrder=desc`, { headers: { Authorization: `Bearer ${token}`, diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index b2765d628..8f588fe73 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -33,7 +33,7 @@ const Incidents = () => { useEffect(() => { const fetchIncidents = async () => { const res = await axiosInstance.get( - `/monitors/incidents/user/${authState.user._id}`, + `/monitors/user/${authState.user._id}?status=false`, { headers: { Authorization: `Bearer ${authState.authToken}`, diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 2cce554e8..18ff56e6a 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -82,11 +82,14 @@ const DetailsPage = () => { useEffect(() => { const fetchMonitor = async () => { - const res = await axiosInstance.get(`/monitors/${monitorId}`, { - headers: { - Authorization: `Bearer ${authToken}`, - }, - }); + const res = await axiosInstance.get( + `/monitors/${monitorId}?sortOrder=asc`, + { + headers: { + Authorization: `Bearer ${authToken}`, + }, + } + ); setMonitor(res.data.data); const data = { cols: [ diff --git a/Client/src/Pages/Monitors/index.jsx b/Client/src/Pages/Monitors/index.jsx index 5e193fe62..42498d2f9 100644 --- a/Client/src/Pages/Monitors/index.jsx +++ b/Client/src/Pages/Monitors/index.jsx @@ -100,6 +100,9 @@ const Monitors = () => { : "var(--env-var-color-19)", }; + // Reverse checks so latest check is on the right + const reversedChecks = monitor.checks.slice().reverse(); + return { id: monitor._id, handleClick: () => navigate(`/monitors/${monitor._id}`), @@ -117,7 +120,7 @@ const Monitors = () => { /> ), }, - { id: idx + 2, data: }, + { id: idx + 2, data: }, { id: idx + 3, data: monitor.type }, { id: idx + 4, data: "TODO" }, ], diff --git a/README.md b/README.md index e40dc81e6..c1d645004 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,12 @@ ![](https://img.shields.io/github/issues-pr/bluewave-labs/bluewave-uptime) ![](https://img.shields.io/github/issues/bluewave-labs/bluewave-uptime) -

BlueWave Uptime

An open source server monitoring application

- - ![bluewave-uptime-screenshot](https://github.com/user-attachments/assets/765bf060-e734-45e6-bd00-2a2eb37c9c02) - BlueWave Uptime is an open source server monitoring application used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performs optimally, providing real-time alerts and reports on the monitored services' availability, downtime, and response time. ## Contributing @@ -776,7 +772,7 @@ curl --request GET \
-GET /api/v1/monitor/{id} +GET /api/v1/monitor/{id}?status ###### Method/Headers @@ -785,6 +781,14 @@ curl --request GET \ > | Method | GET | > | content-type | application/json | +###### Query Params + +> | Name | Type | Required | Notes | +> | --------- | --------- | -------- | ------------------------------------------------ | +> | status | `boolean` | Optional | Check status | +> | limit | `number` | Optional | Number of checks to return | +> | sortOrder | `string` | Optional | `desc`:Newest -> Oldest, `asc`: Oldest -> Newest | + ###### Response Payload > | Type | Notes | @@ -795,7 +799,7 @@ curl --request GET \ ``` curl --request GET \ - --url http://localhost:5000/api/v1/monitors/664d070786e62625ac612ca1 \ + --url http://localhost:5000/api/v1/monitors/664d070786e62625ac612ca1?status=true?limit=0 \ --header '' \ ``` @@ -857,6 +861,15 @@ curl --request GET \ > | Method | GET | > | content-type | application/json | +###### Query Params + +> | Name | Type | Required | Notes | +> | --------- | --------- | -------- | ------------------------------------------------- | +> | status | `boolean` | Optional | Check status | +> | type | `string` | Optional | Multiple allowed: `http` \| `ping` \| `pagespeed` | +> | limit | `number` | Optional | Monitor status | +> | sortOrder | `string` | Optional | `desc`:Newest -> Oldest, `asc`: Oldest -> Newest | + ###### Response Payload > | Type | Notes | diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 1e3f3db4e..5e3899005 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -1,8 +1,10 @@ const { - getMonitorByIdValidation, + getMonitorByIdParamValidation, + getMonitorByIdQueryValidation, getMonitorsByUserIdValidation, monitorValidation, editMonitorBodyValidation, + getMonitorsByUserIdQueryValidation, } = require("../validation/joi"); const SERVICE_NAME = "monitorController"; @@ -41,7 +43,8 @@ const getAllMonitors = async (req, res, next) => { */ const getMonitorById = async (req, res, next) => { try { - await getMonitorByIdValidation.validateAsync(req.params); + await getMonitorByIdParamValidation.validateAsync(req.params); + await getMonitorByIdQueryValidation.validateAsync(req.query); } catch (error) { error.status = 422; error.message = error.details[0].message; @@ -77,14 +80,9 @@ const getMonitorById = async (req, res, next) => { * @throws {Error} */ const getMonitorsByUserId = async (req, res, next) => { - const { error } = getMonitorsByUserIdValidation.validate(req.params); - if (error) { - return res - .status(422) - .json({ success: false, msg: error.details[0].message }); - } - try { + await getMonitorsByUserIdValidation.validateAsync(req.params); + await getMonitorsByUserIdQueryValidation.validateAsync(req.query); const userId = req.params.userId; const monitors = await req.db.getMonitorsByUserId(req, res); @@ -99,60 +97,6 @@ const getMonitorsByUserId = async (req, res, next) => { } }; -/** - * Returns monitor with matching ID and incidents - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorByIdForIncidents = async (req, res, next) => { - try { - await getMonitorByIdValidation.validateAsync(req.params); - - let monitorWithIncidents = await req.db.getMonitorByIdForIncidents( - req, - res - ); - - return res.json({ - success: true, - msg: successMessages.MONTIOR_GET_BY_ID, - data: monitorWithIncidents, - }); - } catch (error) { - error.service = SERVICE_NAME; - next(error); - } -}; - -/** - * Returns all monitors with incidents that belong to User with UserID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorsByUserIdForIncidents = async (req, res, next) => { - try { - await getMonitorsByUserIdValidation.validateAsync(req.params); - let monitorsWithIncidents = await req.db.getMonitorsByUserIdForIncidents( - req, - res - ); - return res.json({ - success: true, - msg: successMessages.MONITOR_GET_BY_USER_ID(req.params.userId), - data: monitorsWithIncidents, - }); - } catch (error) { - error.service = SERVICE_NAME; - next(error); - } -}; - /** * Creates a new monitor * @async @@ -282,8 +226,6 @@ module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId, - getMonitorByIdForIncidents, - getMonitorsByUserIdForIncidents, createMonitor, deleteMonitor, deleteAllMonitors, diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 694bcf745..a06ad9c05 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -1,6 +1,6 @@ const Monitor = require("../../../models/Monitor"); const Check = require("../../../models/Check"); - +const PageSpeedCheck = require("../../../models/PageSpeedCheck"); const { errorMessages } = require("../../../utils/messages"); /** @@ -30,10 +30,40 @@ const getAllMonitors = async (req, res) => { */ const getMonitorById = async (req, res) => { try { - const monitor = await Monitor.findById(req.params.monitorId); - const checks = await Check.find({ monitorId: monitor._id }).sort({ - createdAt: 1, - }); + + const { monitorId } = req.params; + let { status, limit, sortOrder } = req.query; + + // This effectively removes limit, returning all checks + if (limit === undefined) limit = 0; + + // Default sort order is newest -> oldest + if (sortOrder === "asc") { + sortOrder = 1; + } else if (sortOrder === "desc") { + sortOrder = -1; + } else sortOrder = -1; + + const monitor = await Monitor.findById(monitorId); + + const checksQuery = { monitorId: monitor._id }; + + if (status !== undefined) { + checksQuery.status = status; + } + + // Determine model type + let model = + monitor.type === "http" || monitor.type === "ping" + ? Check + : PageSpeedCheck; + + const checks = await model + .find(checksQuery) + .sort({ + createdAt: sortOrder, + }) + .limit(limit); const monitorWithChecks = { ...monitor.toObject(), checks }; return monitorWithChecks; } catch (error) { @@ -51,79 +81,45 @@ const getMonitorById = async (req, res) => { */ const getMonitorsByUserId = async (req, res) => { try { - const limit = req.query ? req.query.limit : undefined; - const monitors = await Monitor.find({ userId: req.params.userId }); - console.log("monitors", monitors); + let { limit, type, status, sortOrder } = req.query; + const monitorQuery = { userId: req.params.userId }; + + if (type !== undefined) { + const types = Array.isArray(type) ? type : [type]; + monitorQuery.type = { $in: types }; + } + + // Default sort order is newest -> oldest + if (sortOrder === "asc") { + sortOrder = 1; + } else if (sortOrder === "desc") { + sortOrder = -1; + } else sortOrder = -1; + + // This effectively removes limit, returning all checks + if (limit === undefined) limit = 0; + + const monitors = await Monitor.find(monitorQuery); // Map each monitor to include its associated checks const monitorsWithChecks = await Promise.all( monitors.map(async (monitor) => { - if (limit) { - // Checks are order newest -> oldest - const checks = await Check.find({ monitorId: monitor._id }) - .sort({ - createdAt: -1, - }) - .limit(limit); - return { ...monitor.toObject(), checks }; - } else { - const checks = await Check.find({ monitorId: monitor._id }).sort({ - createdAt: -1, - }); - return { ...monitor.toObject(), checks }; + const checksQuery = { monitorId: monitor._id }; + if (status !== undefined) { + checksQuery.status = status; } - }) - ); - return monitorsWithChecks; - } catch (error) { - throw error; - } -}; + let model = + monitor.type === "http" || monitor.type === "ping" + ? Check + : PageSpeedCheck; -/** - * Get monitors by UserID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise} - * @throws {Error} - */ -const getMonitorByIdForIncidents = async (req, res, next) => { - try { - const monitor = await Monitor.findById(req.params.monitorId); - const checks = await Check.find({ - monitorId: monitor._id, - status: false, - }).sort({ - createdAt: 1, - }); - const monitorWithChecks = { ...monitor.toObject(), checks }; - return monitorWithChecks; - } catch (error) { - throw error; - } -}; - -/** - * Get monitors by UserID - * @async - * @param {Express.Request} req - * @param {Express.Response} res - * @returns {Promise>} - * @throws {Error} - */ -const getMonitorsByUserIdForIncidents = async (req, res) => { - try { - const monitors = await Monitor.find({ userId: req.params.userId }); - // Map each monitor to include its associated checks - const monitorsWithChecks = await Promise.all( - monitors.map(async (monitor) => { - const checks = await Check.find({ - monitorId: monitor._id, - status: false, - }).sort({ - createdAt: 1, - }); + // Checks are order newest -> oldest + const checks = await model + .find(checksQuery) + .sort({ + createdAt: sortOrder, + }) + .limit(limit); return { ...monitor.toObject(), checks }; }) ); @@ -228,8 +224,6 @@ module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId, - getMonitorByIdForIncidents, - getMonitorsByUserIdForIncidents, createMonitor, deleteMonitor, deleteAllMonitors, diff --git a/Server/db/mongo/modules/pageSpeedCheckModule.js b/Server/db/mongo/modules/pageSpeedCheckModule.js index 77f076311..65376a324 100644 --- a/Server/db/mongo/modules/pageSpeedCheckModule.js +++ b/Server/db/mongo/modules/pageSpeedCheckModule.js @@ -14,7 +14,7 @@ const PageSpeedCheck = require("../../../models/PageSpeedCheck"); */ const createPageSpeedCheck = async (pageSpeedCheckData) => { try { - const pageSpeedCheck = await new pageSpeedCheck({ + const pageSpeedCheck = await new PageSpeedCheck({ ...pageSpeedCheckData, }).save(); return pageSpeedCheck; diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index 9ba703244..ae7a5852e 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -7,15 +7,6 @@ router.get("/", monitorController.getAllMonitors); router.get("/:monitorId", monitorController.getMonitorById); router.get("/user/:userId", monitorController.getMonitorsByUserId); -router.get( - "/incidents/:monitorId", - monitorController.getMonitorByIdForIncidents -); -router.get( - "/incidents/user/:userId", - monitorController.getMonitorsByUserIdForIncidents -); - router.post("/", monitorController.createMonitor); router.post( "/delete/:monitorId", diff --git a/Server/service/networkService.js b/Server/service/networkService.js index 0ed093e8c..0ee65c424 100644 --- a/Server/service/networkService.js +++ b/Server/service/networkService.js @@ -189,6 +189,7 @@ class NetworkService { const insertedCheck = await writeToDB(data); return insertedCheck.status; } catch (error) { + console.log(error); logger.error(`Error wrtiting check for ${data.monitorId}`, { service: this.SERVICE_NAME, monitorId: data.monitorId, diff --git a/Server/validation/joi.js b/Server/validation/joi.js index ffb9a3f6e..704ac7cb4 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -118,14 +118,32 @@ const inviteVerifciationBodyValidation = joi.object({ // Monitors //**************************************** -const getMonitorByIdValidation = joi.object({ +const getMonitorByIdParamValidation = joi.object({ monitorId: joi.string().required(), }); +const getMonitorByIdQueryValidation = joi.object({ + status: joi.boolean(), + sortOrder: joi.string().valid("asc", "desc"), + limit: joi.number(), +}); + const getMonitorsByUserIdValidation = joi.object({ userId: joi.string().required(), }); +const getMonitorsByUserIdQueryValidation = joi.object({ + status: joi.boolean(), + sortOrder: joi.string().valid("asc", "desc"), + limit: joi.number(), + type: joi + .alternatives() + .try( + joi.string().valid("http", "ping", "pagespeed"), + joi.array().items(joi.string().valid("http", "ping", "pagespeed")) + ), +}); + const monitorValidation = joi.object({ _id: joi.string(), userId: joi.string().required(), @@ -244,9 +262,10 @@ module.exports = { inviteRoleValidation, inviteBodyValidation, inviteVerifciationBodyValidation, - getMonitorByIdValidation, + getMonitorByIdParamValidation, + getMonitorByIdQueryValidation, getMonitorsByUserIdValidation, - monitorValidation, + getMonitorsByUserIdQueryValidation, editMonitorBodyValidation, editUserParamValidation, editUserBodyValidation,